Инструменты пользователя

Инструменты сайта


learning:ps

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
learning:ps [31.05.2019 13:28] – [080 ПАРАМЕТР VERBOSE] viacheslavlearning:ps [12.02.2025 09:59] (текущий) – [Регулярные выражения (regex) для разбора текстовых файлов] viacheslav
Строка 1: Строка 1:
 +===== Курс в "Специалисте" =====
 +<code powershell>
 +get-service | get-member
 +# или
 +get-service | gm
 +</code>
 +
 +==== 7. Форматирование результатов ====
 +<code powershell>
 +get-command -verb format -module *util*
 +
 +dir c:\windows | ft BaseName,Extension,Length -Autosize -GroupBy Extension
 +
 +# Выравнивание по правому краю:
 +Get-Service | ft @{n="Name-right"; e={$_.Name}; align="right"},Status,CanStop,CanShutdown -AutoSize
 +
 +# Список:
 +Get-Service | fl Name,Status
 +
 +# Широкий - несколько колонок:
 +Get-Service | sort status | format-wide Name -Column 4 -GroupBy status
 +</code>
 +
 +==== 8. Начало и конец конвейера ====
 +Конец - обычно выгрузка куда-то, начало - запрос или импорт.
 +<code powershell>
 +Get-Command -Verb Export,ConvertTo -Module *utility
 +ConvertTo-Csv    
 +ConvertTo-Html   
 +ConvertTo-Json   
 +ConvertTo-Xml    
 +Export-Alias     
 +Export-Clixml    
 +Export-Csv       
 +Export-FormatData
 +Export-PSSession
 +
 +# Результаты в окошке с фильтрами
 +get-service | Out-Gridview
 +
 +Get-Service | ConvertTo-Html -Title "Список служб" -PreContent "<h1>Службы на локальном компьютере</h1>" | Out-file .\services.html
 +</code>
 +==== 9. Привязка параметров в конвейере ====
 +<code powershell>
 +Одно и то же:
 +dir c:\windows | format-wide
 +Format-Wide -InputObject (dir c:\windows)
 +</code>
 +Круглые скобки нужны для добавления результатов другого конвейера команде, у которой несколько параметров могут принимать данные по конвейеру.
 +
 +==== 10. Переменные ====
 +Жизненный цикл переменной: объявление, инициализация (запись), использование.
 +<code powershell>
 +get-command -noun variable
 +
 +# Есть диск с именем Variable:
 +Get-PSDrive
 +# список переменных
 +dir variable:
 +</code>
 +
 +Свойства вызываются через точку, методы - добавляются скобки
 +<code powershell>
 +$test1.length
 +$test1.gettype()
 +$test1.Substring(2, 2)
 +</code>
 +
 +<code powershell>
 +# Менять тип переменной можно на ходу, но можно задать тип жёстко:
 +[int]$test5 = 100
 +# можно записать туда дробное число,
 +$test5 = 100.25
 +# но значение всё равно будет целочисленным, с округлением.
 +# Строку и т. п. записать туда не получится.
 +</code>
 +
 +Вывести количество символов имени самого большого файла в c:\Windows
 +<code powershell>
 +$test1 = dir c:\windows |sort Length -Descending | select -ExpandProperty Name -First 1
 +$test1.length
 +
 +# Массив переменных
 +$test1 = 10, 20, 30, 40, 50
 +
 +# Нумерация - с нуля:
 +$test1[2] #выведет 30
 +$test1.count #кол-во элементов
 +$test1[$test1.count - 1] # последний элемент массива
 +$test1.gettype() # тип данных - массив (array)
 +</code>
 +Если в массиве изменить какие-то данные на другой тип, то тип данных всего массива превратится в строку (в 5-й версии powershell - нет).
 +<code powershell>
 +$test1[2] = "word"
 +
 +$s = get-service
 +$s[5].status.tostring().length
 +</code>
 +
 +==== 11. Программные конструкции ====
 +Цикл.\\
 +С предусловием - выполнять цикл, пока условие верно:
 +<code powershell>
 +$i=8
 +while ($i -gt 0)
 +{
 +$i; # Полезная нагрузка
 +$i = $i - 1;
 +}
 +</code>
 +
 +С проверкой в конце цикла, а не в начале. Цикл в любом случае выполняется один раз:
 +<code powershell>
 +$i=8
 +Do
 +{
 +$i;
 +$i = $i - 1;
 +}
 +while ($i -gt 0) # условие продолжения цикла
 +# если условие верно, цикл продолжается
 +</code>
 +
 +Выполнять цикл, пока не наступит условие:
 +<code powershell>
 +$i=8
 +Do
 +{
 +$i;
 +$i = $i + 10;
 +}
 +until ($i -gt 1000) # условие окончания цикла
 +# если условие верно, цикл останавливается
 +</code>
 +
 +Цикл FOR - все условия задаются вне самого цикла, внутри - только полезная нагрузка.
 +<code powershell>
 +for (
 +$i=8; # выполняется один раз при входе в цикл
 +$i -GT 0; # условие продолжения цикла
 +$i = $i - 1 # выполняется после каждого повторения
 +)
 +{
 +$i;
 +}
 +</code>
 +
 +Перебор (foreach) - для перебора существующего массива.\\
 +Чтобы не городить конструкцию типа 
 +<code powershell>
 +$srv=get-service
 +for (
 +$i=0; # выполняется один раз при входе в цикл
 +$i -LT $srv.count; # условие продолжения цикла
 +$i = $i + 1 # выполняется после каждого повторения
 +)
 +{
 +$srv[$i].Name;
 +}
 +</code>
 +пишем
 +<code powershell>
 +$srv = get-service
 +foreach ($s in $srv)
 +{
 +$s.Name
 +}
 +</code>
 +
 +Условие (IF).\\
 +Простой случай:
 +<code powershell>
 +$t = 8
 +if ($t -gt 3)
 +{
 +Write-Host "Больше трёх"
 +}
 +</code>
 +
 +Выполнить что-то другое, если условие не выполнено (if-else):
 +<code powershell>
 +cls
 +$t = 2
 +if ($t -gt 3)
 +{
 +Write-Host "Больше трёх"
 +}
 +else
 +{
 +Write-Host "Не больше трёх"
 +}
 +</code>
 +
 +Несколько условий (if-elseif-else)
 +<code powershell>
 +$t = 1
 +if ($t -gt 3)
 +{
 +Write-Host "Больше трёх"
 +}
 +elseif ($t -eq 3)
 +{
 +Write-Host "Равно трём"
 +}
 +elseif ($t -eq 2)
 +{
 +Write-Host "Равно двум"
 +}
 +else
 +{
 +Write-Host "Меньше двух"
 +}
 +</code>
 +
 +Найти каталог на C:\, в котором больше всего вложенных каталогов.
 +<code powershell>
 +$folder = c:\
 +$dirs = dir $folder -Directory
 +foreach ($d in $dirs)
 +{
 +$subd = $d.GetDirectories().Count
 +if ($subd -gt $max) {$max = $subd; $dirname = $d.Name}
 +}
 +
 +Write-Host $dirname
 +Write-Host $max
 +</code>
 +
 +==== 13. Запуск сценария с параметрами ====
 +Вместо задания переменной в коде вынести её в начало скрипта таким образом:
 +<code powershell>
 +Param(
 +[string]$folder
 +)
 +</code>
 +
 +<code powershell>
 +# После этого можно передавать путь как параметр к скрипту.
 +.\script.ps1 C:\
 +# Также, табом можно перебирать эти параметры, если их несколько:
 +.\script.ps1 -folder C:\ -extension exe
 +</code>
 +
 +Обязательные параметры:
 +<code powershell>
 +# Перед блоком Param() написать
 +[CmdletBinding()]
 +# Также, перед параметром поставить
 +[Parameter(Mandatory=$True)]
 +</code>
 +
 +Для необязательных параметров можно задать значение по умолчанию:
 +<code powershell>
 +[string]$extension = "*"
 +</code>
 +
 +Для заданной папки создать в каждой подпапке вложенную папку с названием "Специалист":
 +<code powershell>
 +param(
 +$parent = ""
 +)
 +
 +# Создать
 +gci $parent -Directory | foreach {New-item -path ($_).FullName -Name Специалист -ItemType Directory}
 +# А можно вызывать метод напрямую:
 +gci $parent -Directory | foreach CreateSubDirectory Специалист
 +</code>
 +
 +==== 15. Обработка ошибок ====
 +<code powershell>
 +try { code }
 +catch { errors handling }
 +</code>
 +
 +Try и Catch должны идти последовательно друг за другом. Если в try всё в порядке, catch пропускается, если ошибка - try прерывается и переходит в catch.
 +<code powershell>
 +# Удалить
 +gci $parent -Directory -Recurse | where Name -like Специалист |Remove-Item
 +
 +write-output
 +write-warning
 +write-error
 +write-verbose
 +write-debug
 +
 +[CmdletBinding()] # даёт возможность показывать отладочную информацию write-verbose и write-debug.
 +# По умолчанию, эта информация не выводится.
 +
 +Param(
 +[int]$a,
 +[int]$b,
 +[switch]$c # такого типа нет, но если не указывать его как параметр скрипта,
 +# то будет false, если указывать - true.
 +)
 +
 +Write-Verbose ("A = " + $a)
 +Write-Verbose ("B = " + $b)
 +Write-Verbose ("C = " + $c)
 +
 +# $error - переменная, отвечающая за вывод ошибок, вставляется в catch {}
 +# $error |gm
 +# $error.categoryinfo
 +# $error.errordetails
 +
 +$a = 0
 +try {
 +$a + 1
 +$a * 2
 +10 / $a
 +$a - 1
 +Write-Output "Ошибок нет"
 +}
 +catch {
 +Write-Output "Ошибка!"
 +$error
 +}
 +</code>
 +
 +==== 16. Удалённое исполнение команд ====
 +На удалённой машине должен работать сервис WinRM.\\
 +Для настройки удалённого доступа выполнить команду winrm quickconfig на целевой машине.
 +
 +Удалённый запуск команды:\\
 +Будут работать команды, где есть параметр -ComputerName, например,
 +<code powershell>
 +Get-Service -ComputerName DC1,DC2,Serv3
 +
 +# Запуск оболочки на удалённом компьютере:
 +Enter-PSSession DC1
 +Exit-PSSession
 +
 +# Для одновременного запуска на большом кол-ве машин:
 +Invoke-Command -ComputerName DC1,DC2,Serv3 -ScriptBlock {Get-Service; Get-Process}
 +</code>
 +
 +==== 17. Фоновое выполнение команд ====
 +<code powershell>
 +Start-Job {dir c:\ -recurse |measure-object}
 +# Каждой задаче присваивается ID, узнать их состояние можно командой
 +Get-Job
 +# Получить результат задачи, который можно, например, отправить дальше по конвейеру.
 +Receive-Job -Id 2
 +# Можно добавить его в переменную и использовать в дальнейшем
 +$s = Receive-Job -Id 2 | select -first 5
 +
 +get-command -noun Job
 +</code>
 +
 +==== 18. Планировщик задач ====
 +<code powershell>
 +get-command -noun scheduledjob
 +</code>
 +
 +==== 19. Функции, модули ====
 +<code powershell>
 +Function set-myinfo
 +{
 +[cmdletbinding()]
 +Param(
 +[parameter(mandatory=$true)]
 +[string]$filename,
 +[parameter(mandatory=$true)]
 +[string]$text
 +)
 +set-content -path $filename -value $text
 +}
 +
 +Function get-myinfo {
 +[cmdletbinding()]
 +Param(
 +[parameter(mandatory=$true)]
 +[string]$filename
 +)
 +get-content -path $filename
 +}
 +
 +##################################
 +# Вызов функции
 +set-myinfo -filename "c:\temp\myfile.txt" -text "test text"
 +get-myinfo -filename "c:\temp\myfile.txt"
 +</code>
 +
 +Из скрипта с функциями можно сделать модуль - расширение .psm1. Там нужно оставить только функции.\\
 +Каталоги с модулями PS по умолчанию ($env:PSModulePath):
 +<code powershell>
 +$env:userprofile\Documents\WindowsPowerShell\Modules # для каждого юзера
 +</code>
 +В ней создать папку с именем модуля, и скопировать туда модуль С ТЕМ ЖЕ ИМЕНЕМ, что и папка. Для каждого модуля - своя папка, т. е.,
 +<code powershell>
 +$env:userprofile\Documents\WindowsPowerShell\Modules\modulename\modulename.psm1
 +
 +import-module -listavailable
 +import-module modulename
 +</code>
 +
 +Далее можно вызывать функции в модуле как стандартные командлеты, будет работать также
 +<code powershell>
 +get-command -noun myinfo
 +</code>
 +
 +Добавить справку в модуль: после ''Function get-myinfo {'' вставить спецкомментарий:
 +<code powershell>
 +<#
 +.SYNOPSIS
 + Краткое описание
 +.DESCRIPTION
 + Подробное описание
 +.PARAMETER filename
 + Описание параметра
 +.PARAMETER text
 + Описание параметра
 +.EXAMPLE
 + примеры
 +#>
 +</code>
 +
 +=== Запуск функции удалённо ===
 +<code powershell>
 +# will NOT work:
 +Invoke-Command -ScriptBlock { Get-Software -DisplayName *Office* } -ComputerName server01
 +# will work (provided server01 exists and is set up for remoting):
 +Invoke-Command -ScriptBlock ${function:Get-Software} -ArgumentList '*Office*' -ComputerName server01
 +</code>
 +https://powershell.one/code/12.html
 +
 +
 +==== 21. AD ====
 +<code powershell>
 +get-command -module activedirectory
 +dir ad: # список разделов
 +get-item "cn=user,dc=domain,dc=com" | get-member
 +</code>
 +
 +==== 080 Параметр Verbose ====
 +Чтобы не городить огород с логическими операторами и циклами типа
 +<code powershell>
 +Function List-Service {
 +[cmdletbinding()]
 +Param(
 +[string]$ServiceName = "*",
 +[parameter(mandatory=$true)]
 +[int]$Top
 +[switch]$Protocol
 +)
 +if ($Protocol) { Write-Host "Вывод списка служб" }
 +get-service -name $servicename | select -first $top
 +if ($Protocol) { Write-Host "Вывод списка служб окончен" }
 +}
 +
 +# Запуск:
 +List-Service -Top 2 -Protocol
 +</code>
 +
 +имеется встроенный параметр -Verbose с подсветкой вывода, т.е. параметр $Protocol можно вообще убрать.
 +<code powershell>
 +Function List-Service {
 +[cmdletbinding()]
 +Param(
 +[string]$ServiceName = "*",
 +[parameter(mandatory=$true)]
 +[int]$Top
 +)
 +Write-Verbose "Вывод списка служб"
 +get-service -name $servicename | select -first $top
 +Write-Verbose "Вывод списка служб окончен"
 +}
 +
 +# Запуск:
 +List-Service -Top 2 -Verbose
 +</code>
 +
 +Параметр Verbose сквозной, т. е. если он был вызван, то все дочерние функции также будут вызываться с этим параметром.
 +
 +==== 090 Синонимы для параметров ====
 +Синоним (алиас) задаётся непосредственно перед каждым целевым параметром.
 +<code powershell>
 +Function mynewfunc {
 +[cmdletbinding()]
 +Param(
 +[Alias('str', 'ms')]
 +[string]$MyString
 +)
 +
 +Write-Host $MyString
 +
 +}
 +
 +mynewfunc -str "Test string"
 +mynewfunc -ms "Test string"
 +</code>
 +
 +==== 100 Проверка параметров ====
 +<code powershell>
 +Function mynewfunc {
 +[cmdletbinding()]
 +Param(
 +[Alias('str', 'ms')]
 +[ValidateLength(3, 5)]
 +[string]$MyString
 +)
 +
 +Write-Host $MyString
 +
 +}
 +
 +mynewfunc -ms "Test string" # вызовет ошибку
 +mynewfunc -ms "Test" # сработает
 +</code>
 +
 +<code powershell>
 +# Валидатор для числовых параметров:
 +[ValidateRange(3, 5)]
 +# Список вариантов:
 +[ValidateSet('Create', 'Delete', 'Modify')]
 +</code>
 +
 +Сделать свой валидатор:
 +<code powershell>
 +[ValidateScript({if ($_ -gt 10) {$True} else {$False}})]
 +[int]$MyInt
 +mynewfunc -MyInt 9 # ошибка
 +mynewfunc -MyInt 11 # сработает
 +
 +[ValidatePattern('My\d')] # значение должно соответствовать шаблону My+число.
 +[ValidateLength(3, 5)] # доп. проверка на длину
 +[string]$MyString
 +
 +mynewfunc -MyString "Myd" # ошибка
 +mynewfunc -MyString "My1234" # ошибка
 +mynewfunc -MyString "My123" # сработает
 +</code>
 +
 +==== 110. Приём списков в качестве параметров ====
 +<code powershell>
 +Function mylistfunc {
 +[cmdletbinding()]
 +Param(
 +[string[]]$FolderList
 +)
 +  foreach ($Folder in $FolderList) {
 +    dir $Folder -Directory | measure
 +  }
 +}
 +
 +mylistfunc -folderlist "C:\Windows\System32", "C:\Users", "C:\Program Files"
 +</code>
 +Если указать, что принимается массив строк %%[string[]]%% без цикла foreach, будет подсчитано общее количество.
 +
 +==== 120. Приём параметров из конвейера ====
 +<code powershell>
 +Function mylistfunc {
 +[cmdletbinding()]
 +Param(
 +[parameter(ValueFromPipeline=$True)]
 +[string[]]$FolderList
 +)
 +BEGIN {}
 +PROCESS {
 +  foreach ($Folder in $FolderList) {
 +    dir $Folder -Directory | measure
 +  }
 +}
 +END {}
 +}
 +
 +gc .\folderslist.txt | mylistfunc
 +</code>
 +
 +Если просто добавить ''[parameter(ValueFromPipeline=$True)]'' без конструкции BEGIN-PROCESS-END и цикла, то будет выведено только последнее значение из конвейера. Дело в том, что процесс передачи по конвейеру разбит на эти три этапа, где перечисление собственно данных из конвейера выполняется в блоке PROCESS. Блоки BEGIN и END выполняются один раз, а PROCESS - столько раз, сколько строк в конвейере.
 +
 +==== 130. Создание объектов ====
 +PSObject - универсальный тип объекта
 +<code powershell>
 +function MyFunc1 {
 +$Properties = @{'Name' = "MyName"; 'Size' = 10; 'Color' = "Yellow"}
 +New-Object -TypeName PSObject -Property $Properties
 +}
 +
 +cls
 +MyFunc1
 +</code>
 +
 +==== 140. Работа с объектами .NET ====
 +Можно использовать классы .NET, если в PS нет соотв. команды, или по каким-то другим причинам.
 +<code powershell>
 +$test = New-Object -TypeName System.Net.HttpWebRequest
 +$test |gm
 +
 +$d = New-Object -TypeName datetime
 +$d
 +$d = [datetime]'12.03.2005'
 +$d
 +$d.AddDays(10)
 +$d.Year
 +$d.Month
 +$d.DayOfWeek
 +
 +# 1 января 0001 г. 0:00:00
 +# 3 декабря 2005 г. 0:00:00
 +# 13 декабря 2005 г. 0:00:00
 +# 2005
 +# 12
 +# Saturday
 +</code>
 +
 +==== 150. Инструменты и управляющие сценарии ====
 +При усложнении сценария целесообразно разбивать его на части. Рекомендуется делить программу на две группы - инструменты и управляющие сценарии. Инструменты решают конкретную задачу, например, заведение пользователя в AD. Инструменты могут собирать данные (Get-, Import-, ConvertFrom-), выполнять действия (Send-, New-, Set-, Remove-, Save-), выводить результат (Export-, ConvertTo-, Format- Out-). Управляющие сценарии решают более сложные, бизнес-задачи, вызывая различные инструменты. Также в них делается интерфейс общения с пользователем, который должен выбирать из каких-то пунктов и т. п.
 +
 +==== 160. Директива Requires ====
 +Проверка необходимых компонентов и условий для работы скрипта. Оформляется как комментарий.
 +<code powershell>
 +#Requires -Version 2.0
 +#Requires -Module ActiveDirectory
 +#Requires -RunAsAdministrator
 +
 +Get-Service
 +</code>
 +
 +==== 170. Взаимодействие с пользователем ====
 +<code powershell>
 +write-host # выводит информацию на экран
 +read-host # захватывает ввод пользователя
 +clear-host # очистка экрана (cls)
 +</code>
 +<code powershell>
 +Write-Host "Выберите действие:"
 +Write-Host "1. Установить"
 +Write-Host "2. Удалить"
 +Write-Host "3. Запилить"
 +
 +$r = Read-Host
 +if ($r -eq 1) {
 +Write-Host "Выберите вариант установки:"
 +Write-Host "1. Полный"
 +Write-Host "2. Минимальный"
 +Write-Host "3. Отмена"
 +
 +$r2 = Read-Host "Введите цифру"
 +
 +Write-Host "Вы выбрали $r -> $r2"
 +}
 +</code>
 +
 +==== 180. Организация работы с главным меню ====
 +<code powershell>
 +Do {
 +Clear-Host
 +
 +Write-Host "Выберите действие:"
 +Write-Host "1. Установить"
 +Write-Host "2. Удалить"
 +Write-Host "3. Запилить"
 +
 +$r = Read-Host
 +if ($r -eq 1) {
 +Write-Host "Выберите вариант установки:"
 +Write-Host "1. Полный"
 +Write-Host "2. Минимальный"
 +Write-Host "3. Выбор компонентов"
 +Write-Host "4. Отмена"
 +
 +$r2 = Read-Host "Введите цифру"
 +
 +Write-Host "Вы выбрали $r -> $r2"
 +
 +Read-Host "Нажмите любую клавишу"
 +    }
 +}
 +while ($r2 -ne 4)
 +</code>
 +==== 190. Генерация отчётов в HTML ====
 +У ConvertTo-Html есть параметр -Fragment, который генерирует только содержимое без открывающих и закрывающих тэгов.
 +Таким образом, с помощью Out-File -Append можно сначала добавить тэги, затем нужную информацию (можно несколько запросов сгенерировать) добавить в файл, затем закрывающие тэги.
 +При добавлении запрошенной информации в переменные полезно использовать команду Out-String, чтобы данные были одной строкой, а не массивом.
 +
 +==== 200. Обработка нештатных ситуаций ====
 +2 типа ошибок - останавливающая, при которой обработка прерывается, и неостанавливающая, когда валятся ошибки, но обработка продолжается.
 +Имеется системная переменная $ErrorActionPreference со значением Continue по умолчанию, можно поменять её значение на 
 +<code powershell>
 +$ErrorActionPreference = "SilentlyContinue"
 +# ошибки выводиться не будут, но это плохая практика.
 +
 +$ErrorActionPreference = "Inquire"
 +# спрашивать пользователя, что делать дальше.
 +
 +$ErrorActionPreference = "Stop"
 +# остановка обработки, все неостанавливающие ошибки превращаются в останавливающие.
 +# Также, это позволяет перехватывать ошибки.
 +</code>
 +Ключ -ErrorAction у каждого командлета позволяет задавать поведение для конкретного действия, а не глобально.\\ Этого переключателя нет для методов!
 +
 +Для обработки ошибок используется конструкция
 +<code powershell>
 +try { code }
 +catch { errors handling }
 +finally { code }
 +</code>
 +Try и Catch должны идти последовательно друг за другом. Если в try всё в порядке, catch пропускается, если ошибка - try прерывается и переходит в catch. Finally выполняется в любом случае после Catch.
 +
 +<code powershell>
 +try { dir C:\Windows\System32\*.exe -Recurse -ErrorAction Stop }
 +catch { Write-Warning "Ошибка!" }
 +finally { Write-Host "Конец" }
 +</code>
 +Без указания -ErrorAction Stop блок catch выполняться не будет.
 +
 +Неплохо бы знать также, что за ошибка произошла и в каком месте.
 +<code powershell>
 +try { dir C:\Windows\System32\*.exe -Recurse -ErrorAction Stop -ErrorVariable $err1}
 +catch {
 +Write-Warning "Ошибка!"
 +$_ | gm # переменная с ошибкой, можно использовать методы для получения информации
 +$Error[0] # массив, куда пишутся ошибки. 0 - последняя
 +}
 +finally { Write-Host "Конец" }
 +</code>
 +
 +-ErrorVariable может создавать переменную ошибки для каждой команды, что удобно, т. к. в блоке %%catch{}%% можно управлять разными ошибками разных команд.
 +
 +==== 210. Работа с XML ====
 +XML удобен тем, что хранит и данные, и структуру, как БД.
 +<code xml>
 +<> # элемент, внутри может быть атрибут, типа
 +<lan>
 +<computer name="comp1">Компьютер</computer>
 +<addr ip="192.168.0.10" mask="255.255.255.0" />
 +</lan>
 +</code>
 +Атрибут чаще используется для неповторяющегося свойства, а элементы - для повторяющегося.
 +
 +===== Getting Started with Microsoft PowerShell =====
 +
 +[[https://mva.microsoft.com/en-us/training-courses/getting-started-with-microsoft-powershell-8276|Курс на Microsoft Virtual Academy]]
 +
 +https://powershell.org/
 +==== Don't fear the shell ====
 +
 +  * Cmdlets: verb-noun
 +  * Native commands work - ping, ipconfig, calc, notepad, mspaint
 +  * cls - Clear-Host
 +  * cd - Set-Location
 +  * dir, ls - Get-Childitem
 +  * type, cat - Get-Content
 +  * Copy, cp - Copy-Item
 +
 +<code powershell>
 +Get-Alias *sv # список псевдонимов, заканчивающихся на sv
 +Get-Alias -Definition get-process # показать псевдонимы для get-process
 +# работает как в линуксе
 +cd /
 +cd ~
 +</code>
 +
 +http://video.ch9.ms/ch9/eedc/a09df7fe-b46c-4d6e-b6f6-17e7980deedc/GetStartedPowerShell3M01_high.mp4
 +
 +==== The help system ====
 +<code powershell>
 +update-help # обновить справку
 +get-help get-service # справка по get-service
 +get-help get-service -online # открыть онлайн-справку в браузере
 +get-help get-service -detailed # более подробная справка
 +get-help get-service -examples # примеры
 +get-help get-service -showwindow # открыть справку в отдельном окне
 +man get-service # то же самое
 +get-verb # глаголы, используемые в PS
 +</code>
 +Синтаксис:
 +<code powershell>
 +Get-Service [[-Name] <String[]>] [-ComputerName <String[]>] [-DependentServices] [-Exclude <String[]>] [-Include <String[]>] [-RequiredServices] [<CommonParameters>]
 +
 +# Всё, что начинается с дефиса (-Name) - параметр
 +# <> - значение (аргумент)
 +# [] вокруг параметров и аргументов - это опциональный параметр
 +# [] вокруг самого параметра - можно писать сразу аргументы к командлету, параметр будет подразумеваться, например,
 +Get-Service bits
 +# или
 +gsv bits
 +# [] внутри <> - значение может быть множественным (разделённым запятыми)
 +</code>
 +
 +<code powershell>
 +Get-Service -Name b*, c*
 +Get-Service -DisplayName *bit*
 +get-eventlog -logname system -newest 3 -entrytype error -computername dc1,server2,server5
 +get-help about* # список разъяснительных статей о разных вещах
 +get-help -Category CmdLet # вывести список статей по категории командлетов
 +</code>
 +; - разделитель типа && в линуксе (сделай это и потом сделай то)
 +
 +http://video.ch9.ms/ch9/96dc/5e431f9c-e65f-4ac3-9339-4a67989496dc/GetStartedPowerShell3M02_high.mp4
 +
 +==== The pipeline: getting connected & extending the shell ====
 +<code powershell>
 +get-service | export-csv -Path c:\service.csv
 +import-csv c:/service.csv
 +
 +get-service | export-clixml -Path c:\service.xml
 +
 +Compare-Object -ReferenceObject (Import-Clixml c:\service.xml) -DifferenceOject (Get-Process) -Property name
 +
 +get-service | Out-file -filepath c:\services.txt
 +get-content c:\services.txt
 +
 +get-service | Convertto-html -Property Name,Status | out-file c:\service.htm
 +
 +get-service -DisplayName *bi* | stop-service -whatif # А что будет если (без реального выполнения)
 +get-service -DisplayName *bi* | stop-service -confirm # подтверждение выполнения каждого совпадения
 +
 +get-module -ListAvailable # доступные модули
 +get-module # уже загруженные модули
 +
 +</code>
 +
 +
 +http://video.ch9.ms/ch9/2888/54ac4547-0430-444e-9ee4-347678b92888/GetStartedPowerShell3M03rs_high.mp4
 +
 +==== Objects for the Admin ====
 +<code powershell>
 +get-process | where {$_.handles -gt 900} # gt - greater than | sort handles
 +get-process | where handles -gt 900 # то же самое, но упрощённая версия без скобок
 +get-service | get-member # получить список методов и свойств командлета
 +get-eventlog -logname system -newest 5 | select -property eventid, timewritten, message | sort -property timewritten | convertto-html | out-file C:\eventlog.htm
 +
 +# Пример парсинга xml (Ромео и Джульетта)
 +# https://www.ibiblio.org/xml/examples/shakespeare/r_and_j.xml
 +# прочитать содержимое и задать переменную
 +$x = [xml](get-content .\r_and_j.xml)
 +# Вывести все реплики из 1 акта 1 сцены
 +$x.PLAY.Act[0].scene[0].SPEECH
 +# Подсчитать количество реплик, сгруппировав по герою, от больших значений к меньшим
 +$x.PLAY.Act.scene.SPEECH | group speaker | sort count -descending
 +
 +get-history # история команд
 +
 +get-service | where {$_.status -eq "Running"}
 +# То же самое, $PSItem - это $_
 +get-service | where {$PSItem.status -eq "Running"}
 +get-service | where {$PSItem.status -eq "Running" -and $_.Name -like "b*"}
 +
 +# Разница в концепциях
 +# Плохо из-за того, что фильтрация после сортировки - бессмысленная работа
 +get-stuff | sort | where -somestuff | out-file
 +# Сортировка после фильтрации - гораздо лучше
 +get-stuff | where -somestuff | sort | out-file
 +
 +
 +</code>
 +
 +http://video.ch9.ms/ch9/74d2/b70e7c0a-96a7-4067-80cb-ce8352c774d2/GetStartedPowerShell3M04_high.mp4
 +
 +==== The pipeline: deeper ====
 +Что передаётся через пайп (конвейер): ByValue, ByPropertyName.
 +
 +<code powershell>
 +# посмотреть, какие параметры принимают данные конвейера
 +get-help get-service -full
 +
 +-InputObject <ServiceController[]>
 +    Specifies ServiceController objects representing the services to be retrieved. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe a service object to this cmdlet.
 +
 +    Требуется?                   false
 +    Позиция?                     named
 +    Значение по умолчанию                None
 +    Принимать входные данные конвейера? True (ByValue)
 +    Принимать подстановочные знаки? false
 +
 +-Name <String[]>
 +    Specifies the service names of services to be retrieved. Wildcards are permitted. By default, this cmdlet gets all of the services on the computer.
 +
 +    Требуется?                   false
 +    Позиция?                     0
 +    Значение по умолчанию                None
 +    Принимать входные данные конвейера? True (ByPropertyName, ByValue)
 +    Принимать подстановочные знаки?false
 +
 +# благодаря совпадению имён параметров в разных командлетах возможны такие сочетания, как
 +get-service | stop-process -whatif # когда имена сервисов передаются как имена процессов
 +get-process calc | dir # показать местонахождение calc.exe
 +
 +# если параметры не совпадают, например, в случае
 +get-adcomputer -filter * | get-service -name bits # это не сработает
 +# можно задать параметр, который будет совпадать, т. е., ComputerName задаётся равным Name
 +get-adcomputer -filter * | select -property @{name='ComputerName';expression={$_.name}} | get-service -name bits
 +
 +# Иногда бывает так, что командлет вообще не принимает параметров по конвейеру
 +# Это не сработает, т. к. get-wmiobject на вход ничего не принимает:
 +get-adcomputer -filter * | get-wmiobject -class win32_bios
 +# Тем не менее, у get-wmiobject есть параметр -ComputerName, которому нужно скормить строки из Get-ADComputer. Чтобы получить строки, нужно использовать параметр -ExpandProperty, чтобы убрать заголовок в столбце Name.
 +get-wmiobject -class win32_bios -ComputerName (get-adcomputer -filter * | Select -expandproperty name)
 +# Начиная с 3-ей версии Powershell, можно написать ту же команду короче:
 +get-wmiobject -class win32_bios -ComputerName (get-adcomputer -filter * ).name
 +# А можно и так ({} обозначает скрипт для выполнения), некий обход ограничений:
 +get-adcomputer -filter * | get-wmiobject win32_bios -computername {$_.name}
 +# Также, существует более новый аналог get-wmiobject - get-ciminstance, который умеет принимать параметры по конвейеру
 +</code>
 +
 +http://video.ch9.ms/ch9/b233/9ddae023-a85e-4e72-bcf3-00f143fab233/GetStartedPowerShell3M05_high.mp4
 +==== The PowerShell in the shell: remoting ====
 +Как подключиться
 +<code powershell>
 +Enter-PSSession server1
 +[Server1]: PS c:\Users\Administrator.SERVER1\Documents>
 +</code>
 +Включить возможность удалённого подключения
 +<code powershell>
 +Enable-PSRemoting
 +</code>
 +Включить в политике:\\
 +Computer configuration/Policies/Administrative templates/Windows Components/Windows remote management\\
 +В Windows 2012 это включено по умолчанию.
 +
 +<code powershell>
 +invoke-command -computername dc,server1,server2 {get-eventlog -logname system -new 3} | sort timewritten | format-table -property timewritten, message -Autosize
 +invoke-command -computername dc,server1,server2 {restart-computer}
 +icm dc,server1,server2 {get-volume} | sort sizeremaining | select -last 3
 +</code>
 +Установить веб-доступ к powershell на удалённой машине
 +<code powershell>
 +install-windowsfeature WindowsPowershellWebAccess
 +get-help *pswa*
 +install-pswawebapplication
 +add-pswaauthorizationrule -computergroupname # какая группа машин имеет доступ
 +add-pswaauthorizationrule -usergroupname # какая группа пользователей имеет доступ
 +</code>
 +
 +http://video.ch9.ms/ch9/f33b/15dcc7ae-ff1c-4e1d-9200-be3f132bf33b/GetStartedPowerShell3M06_high.mp4?600
 +==== Getting prepared for automation ====
 +**AllSigned** - абсолютно все скрипты должны быть подписаны\\
 +**RemoteSigned** - должны быть подписаны все скрипты, кроме созданных локально (значение по умолчанию с Windows Server 2012 R2)
 +
 +<code powershell>
 +# сделать самоподписанный сертификат
 +New-SelfSignedCertificate
 +# вывести все диски (в т. ч. поставщиков Powershell)
 +get-psdrive
 +# показать все сертификаты подписи кода и создать переменную $a
 +dir Cert:\Currentuser -Recurse -CodeSigningCert -outvariable a
 +# тут не понял
 +$cert = $a[0]
 +# посмотреть текущую политику выполнения
 +get-executionpolicy
 +# изменить её на AllSigned
 +set-executionpolicy "allsigned"
 +</code>
 +После этого обычный скрипт (test.ps1) не выполнится
 +<code powershell>
 +# Подписать скрипт test.ps1
 +Set-AuthenticodeSignature -Certificate $cert -Filepath .\Test.ps1
 +</code>
 +Посде подписи в конец скрипта добавляется цифровая подпись, и при запуске он выполняется, но спрашивает, выполнять ли скрипт от недоверенного издателя, в т.ч. есть вариант ответа "всегда доверять".
 +
 +=== Переменные ===
 +<code powershell>
 +get-help *variable*
 +
 +$MyVar="Hello"
 +$MyVar= get-service bits
 +$MyVar.status
 +$MyVar.stop()
 +$MyVar.refresh() # если не обновить, то сервис будет показываться как запущенный
 +$MyVar.status
 +
 +#Задать переменную интерактивно
 +$var=read-host "Введите имя компьютера"
 +get-service bits -computername $var
 +
 +write-host $var -foregroundcolor red -backgroundcolor green
 +write-host $var | get-member # не сработает
 +</code>
 +Write-host ничего не передаёт и вообще его использование не рекомендуется, нужно использовать write-output:
 +<code powershell>
 +write-output $var | get-member # а вот это сработает
 +</code>
 +Цвет текста в консоли
 +<code powershell>
 +# предупреждение
 +write-warning "Стоять близко к краю платформы опасно"
 +write-error "Стоять близко к краю платформы опасно"
 +</code>
 +Тем не менее, следует не увлекаться тем, что //показывается// в консоли, т. к. подобные вещи не относятся к автоматизации.
 +
 +Переменные можно задавать любые, в том числе, с пробелами и неанглийские, обрамляя их фигурными скобками, и они будут работать.
 +<code powershell>
 +${привет, как дела?} = 555
 +</code>
 +Если задать переменной путь к файлу, то он вернёт содержимое
 +<code powershell>
 +1..5 > C:\temp\test.txt
 +${C:\temp\test.txt)
 +1
 +2
 +3
 +4
 +5
 +</code>
 +Но если задать переменной значение, то будет возвращаться именно оно, причём, оно также будет отображаться и в другом экземпляре консоли
 +<code powershell>
 +${C:\temp\test.txt) = "Переменная №1"
 +</code>
 +
 +http://video.ch9.ms/ch9/0922/1307856c-4ef2-4bda-9914-045f4a390922/GetStartedPowerShell3M07_high.mp4
 +==== Automation in scale: remoting ====
 +Особенность запуска удалённого кода в том, что после того, как он отработает, выгружается всё, что требуется для исполнения этого кода - и консоль, и профиль, и всё остальное. В связи с этим есть особенности написания кода для удалённого выполнения.
 +
 +Это не сработает, так как между сессиями переменная не передаётся:
 +<code powershell>
 +icm -comp dc {$var=2}
 +icm -comp dc {write-output $var}
 +</code>
 +
 +Правильный вариант - обращаться в ту же сессию, что и ранее
 +<code powershell>
 +$sessions=new-pssession -computername dc
 +icm -session $sessions {$var=2}
 +icm -session $sessions {$var}
 +
 +# можно померить время, за которое выполняется команда
 +measure-command {icm -computername dc {get-process}}
 +# это должно быть быстрее
 +measure-command {icm -session $sessions {get-process}}
 +</code>
 +
 +<code powershell>
 +# Взять два сервера
 +$servers= 's1', 's2'
 +# убедиться, что на них нет веб-страниц
 +$servers | foreach {start iexplore http://$_}
 +# сделать переменную для сессий
 +$sessions=new-pssession -computername $servers
 +# поставить IIS
 +icm -session $sessions {install-windowsfeature web-server}
 +# теперь проверка будет положительной
 +$servers | foreach {start iexplore http://$_}
 +# можно создать "страничку" в notepad и разложить её по серверам как главную
 +notepad c:\default.htm # написать там что-то
 +$servers | foreach {copy-item c:\default.htm -destination \\$_\c$\inetpub\wwwroot}
 +</code>
 +
 +Удалённые сессии полезны ещё и потому, что не нужно ставить доп. компонентов типа Exchange management shell локально, потому что можно подключиться непосредственно к серверу, где эти командлеты имеются изначально.
 +<code powershell>
 +$s=new-pssession -computername dc
 +import-pssession -session $s -module activedirectory -prefix remote
 +</code>
 +После этого командлеты AD становятся доступны на локальной машине, и можно выполнять запросы типа
 +<code powershell>
 +get-remoteadcomputer -filter *
 +</code>
 +Но по факту всё это выполняется удалённо, под теми учётными данными, под которыми была создана удалённая сессия.
 +
 +Просмотр параметров командлетов
 +<code powershell>
 +$c = get-command get-process
 +$c.parameters
 +$c.parameters["Name"]
 +</code>
 +
 +Если посмотреть определение удалённого командлета, то окажется, что это не сам по себе командлет, а его динамически генерируемый код, который вызывает выполнение удалённой функции.
 +<code powershell>
 +(get-command get-remoteadcomputer).definition
 +</code>
 +
 +Присвоение префикса командлету
 +<code powershell>
 +$s = nsn
 +import-session $s -commandname get-process -prefix wow
 +get-wowprocess
 +</code>
 +
 +http://video.ch9.ms/ch9/7b29/933b0f5e-ca54-45fe-849b-4e4b07127b29/GetStartedPowerShell3M08_high.mp4
 +==== Introducing scripting and toolmaking ====
 +Если нажать в Powershell ISE сочетание Ctrl+Space, то это может показать классы WMI.
 +<code powershell>
 +Get-CimInstance Wind32_Logical#Ctrl+Space
 +</code>
 +После пайпа можно нажать Enter, и в интерактивном режиме команду можно продолжать набирать.
 +<code powershell>
 +Get-WmiObject win32_logicaldisk -filter "DeviceID='c:'"
 +>> Select @{n='freegb';e={$_.freespace / 1gb -as [int]}}
 +</code>
 +Задать параметр:
 +<code powershell>
 +param(
 +  $Computername='localhost',
 +  $bogus
 +  )
 +Get-WmiObject -computername $Computername win32_logicaldisk -filter "DeviceID='c:'" | Select @{n='freegb';e={$_.freespace / 1gb -as [int]}}
 +</code>
 +Эти параметры можно выбирать после запуска сохранённого скрипта клавишей Tab и задавать их на лету.
 +
 +После сохранения скрипта с параметрами и вызова этого скрипта через get-help, вывод выглядит как спавка к командлету, где командлетом является сам скрипт. В данном случае $Computername - это объект, но если написать в скрипте
 +<code powershell>
 +  [string]$Computername
 +</code>
 +, то параметр будет иметь значение "строка". Для полноценного строкового параметра не хватает скобочек, их надо добавить:
 +<code powershell>
 +  [string[]]$Computername='localhost'
 +</code>
 +Если в начале скрипта добавить
 +<code powershell>
 +  [CmdletBinding()]
 +</code>
 +, то будут доступны все прочие параметры [<Commonparameters>]
 +
 +Если в список параметров добавить
 +<code powershell>
 +param(
 +  [Parameter(Mandatory=$True)]
 +</code>
 +, то следующий за ним параметр (один), будет обязательным.
 +
 +http://video.ch9.ms/ch9/e523/055c141f-5e9f-400f-b5b1-da61a747e523/GetStartedPowerShell3M09_high.mp4
 +
 +===== Learn Windows Powershell in a month of lunches (3rd edition), краткий конспект =====
 +Поезные ресурсы:\\
 +https://docs.microsoft.com/en-us/powershell/\\
 +https://powershell.org/
 +<code powershell>
 +# Узнать установленную версию (v4 и выше)
 +$PSVersionTable
 +</code>
 +
 +==== Справка ====
 +<code powershell>
 +help Get-Service # Получить справку по Get-Service
 +Update-Help # Обновить справку
 +Save-Help # Сохранить справку локально
 +Update-Help -Source path # Загрузить справку из локального источника
 +help *log* # найти командлеты, в которых упоминается log
 +help Get-Service -ShowWindow # показать справку в отдельном окне
 +help Get-Service -Online # показать справку в интернете
 +Help Get-EventLog -example # показать примеры использования
 +Help about_* # список справочной информации по областям применения
 +</code>
 +
 +=== Интерпретация синтаксиса справки ===
 +<code powershell>
 +Get-EventLog [-LogName] <String> [[-InstanceId] <Int64[]>] [-After <DateTime>] [-AsBaseObject]
 +[-Before <DateTime>] [-ComputerName <String[]>] [-EntryType <String[]>] [-Index <Int32[]>]
 +[-Message <String>] [-Newest <Int32>] [-Source <String[]>] [-UserName <String[]>] [<CommonParameters>]
 +
 +Get-EventLog [-AsString] [-ComputerName <String[]>] [-List] [<CommonParameters>]
 +</code>
 +В данном случае есть два набора параметров, которые могут быть использованы с этим командлетом; некоторые параметры используются и там, и там. Если параметр есть только в одном наборе, то нельзя использовать вместе с ним параметры из другого (если только они не прописаны в обоих наборах).\\
 +[<CommonParameters>] - стандартные параметры для всех командлетов.\\
 +[ ] - необязательный (optional) параметр.\\
 +Positional parameter - обязательный параметр, который может использоваться без указания имени параметра, в данном случае [-LogName] <String>, где обязательно указание только <String>. Например, можно написать
 +<code powershell>
 +Get-EventLog System -Newest 20
 +</code>
 +Выяснить свойства параметров можно в полной справке по командлету с ключом -Full.
 +=== Значения параметров ===
 +Параметр "переключатель" (switches) - не нуждаются во вводных данных, например, в данном случае - [-AsString]. Такие параметры никогда не являются Positional parameter, поэтому всегда нужно печатать имя параметра, а также, они всегда необязательны.
 +
 +Типы вводных данных:\\
 +Строка (String) - ряд букв и цифр. Если с пробелами (типа C:\Program Files), то нужно заключать в одинарные кавычки (' ').\\
 +Целые числа (Int, Int32, Int64) - без десятичных значений.\\
 +Дата и время (DataTime) - строка, интерпретируемая как указатель времени в соответствии с региональными параметрами системы.
 +
 +Квадратные скобки, которые указаны рядом ([-ComputerName <string[]>]) означают, что значений может быть несколько (array, collection, list), разделённых запятыми. Например:
 +<code powershell>
 +Get-EventLog Security -computer Server-R2,DC4,Files02
 +</code>
 +Множественное значение может быть прочитано из файла, например
 +<code powershell>
 +Get-EventLog Application -computer (Get-Content names.txt)
 +</code>
 +Если параметр обязательный, можно выполнить командлет вообще без параметров, и Powershell будет спрашивать значения одно за одним, пока не будет нажат "ввод" без указания значения.
 +
 +==== Запуск команд ====
 +Не интерпретировать параметры, передаваемые стороннему приложению: %%--%%%
 +<code powershell>
 +C:\windows\system32\sc.exe --% qc bits
 +</code>
 +
 +==== Провайдеры (providers) ====
 +Провайдеры - это хранилища информации, выглядящие как диски - это заданные псевдонимы команд, реестр, файловая система и т. д.
 +<code powershell>
 +Get-PSProvider # вывести список доступных провайдеров
 +</code>
 +Для работы с провайдерами по большей части используются командлеты, содержащие в себе слово Item:
 +<code powershell>
 +Get-Command -noun *item*
 +</code>
 +Суть в том, что можно запрашивать любые объекты провайдера одними и теми же командлетами.
 +<code powershell>
 +Set-Location -Path C:\Windows # файловая система
 +Set-Location -Path hkcu: # реестр
 +Set-Location -Path Env: # системные переменные
 +# если не указать тип, то PS спросит об этом, т. к. непонятен тип объекта
 +New-Item TestFolder -Type Directory
 +</code>
 +По умолчанию, путь к объекту обязательный и может содержать маску (* и ?), но можно задать необязательный параметр -LiteralPath, который не будет интерпретировать следующее за ним значение.
 +
 +Можно выгружать информацию в CSV и XML с помощью командлетов Export-CliXML и Export-Csv. в XML выгружается больше подробностей. Подобную выгрузку можно использовать для сравнения, например, если выгрузить с одного компьютера список процессов, перенести полученный файл на другой компьютер и там запустить сравнение списка в файле со списком работающих процессов там (в данном случае - по имени):
 +<code powershell>
 +Diff -reference (Import-CliXML reference.xml) -difference (Get-Process) -property Name
 +</code>
 +Если выпустить -Property, то сравнение будет идти по всем полям. Diff в Powershell не очень хорошо работает для сравнения текстовых файлов.
 +
 +<code powershell>
 +# Тупо выгрузить в файл
 +Dir > DirectoryList.txt
 +# А вот здесь можно задать параметры выгрузки - кодировка, доп. инфо и т. д.
 +Dir | Out-File DirectoryList.txt
 +
 +help out* # посмотреть, куда можно выводить результат
 +get-service | out-printer # напечатать список служб
 +Get-Service | ConvertTo-HTML > services.html # создать HTML-страничку со списком служб
 +</code>
 +
 +У командлетов есть определённый //уровень воздействия (impact level)// на систему, который задаётся параметром $confirmpreference (значение по умолчанию - High). Если уровень воздействия командлета равен или превышает этот уровень, то выдаётся запрос подтверждения.
 +<code powershell>
 +# Можно форсировать запросы подтверждения
 +Get-Service | Stop-Service -confirm
 +# Эмуляция выполнения
 +Get-Service | Stop-Service -whatif
 +</code>
 +
 +Отличие Import-CSV от Get-Content: Get-Content импортирует сырые неструктурированные данные из CSV, тогда как Import-CSV разбирает CSV и представляет его в удобном виде. Та же параллель с XML.
 +<code powershell>
 +# Без комментария о типе файла, не перезаписывать сущ. файл,
 +# использовать в качестве разделителя региональные параметры, подтверждать действие
 +Get-Service | Export-CSV services.csv -NoTypeInformation -NoClobber -UseCulture -Confirm
 +</code>
 +
 +==== Расширения ====
 +Два типа расширений: оснастка (snap-in) и модуль (module). От использования оснасток уходят в пользу модулей.
 +<code powershell>
 +# вывести список установленных, но не загруженных оснасток
 +get-pssnapin –registered
 +# загрузить оснастку (указать имя)
 +add-pssnapin sqlservercmdletsnapin100
 +# Вывести список команд, принадлежащих к загруженной оснастке
 +Get-Command -pssnapin sqlservercmdletsnapin100
 +</code>
 +Оснастка может добавить новых провайдеров, например в случае с загрузкой sqlservercmdletsnapin100 к списку провайдеров добавится SqlServer.
 +
 +Чтобы загружать нужные оснастки прямо при запуске PS, нужно загрузить эти оснастки, а затем сохранить их:
 +<code powershell>
 +Export-Console c:\myshell.psc
 +# Загрузить PS уже с нужными оснастками
 +%windir%\system32\WindowsPowerShell\v1.0\powershell.exe -noexit -psconsolefile c:\myshell.psc
 +</code>
 +Модули в этом способе не участвуют.
 +
 +У модулей есть системная переменная modulepath, где прописаны пути к их местоположению.
 +<code powershell>
 +get-content env:psmodulepath
 +get-module # список модулей
 +import-module # загрузить модуль
 +</code>
 +С 3-й версии PS нужно прописывать путь в переменной, если где-то лежит модуль и его нужно использовать. Это нужно для автоматической загрузки нужного модуля при вызове его командлета. Как и оснастка, модуль может добавить нового провайдера.
 +
 +Для автозагрузки модулей при старте PS есть механизм //сценария профиля// (profile script):
 +  - Сделать подпапку WindowsPowerShell в "Документах", создать в ней файл profile.ps1
 +  - В этом файле прописать все нужные команды Add-PSSnapin и Import-Module для загрузки нужных расширений.
 +  - В PS от админа выполнить команду<code powershell>Set-ExecutionPolicy RemoteSigned</code>
 +  - Переоткрыть PS, после этого profile.ps1 будет загружен автоматически.
 +
 +Галерея модулей: http://powershellgallery.com. Есть модуль [[https://docs.microsoft.com/ru-ru/powershell/gallery/psget/get_psget_module|PowerShellGet]] для удобной установки модулей из галереи.
 +
 +==== Объекты ====
 +Если вывести Get-Process, то будет таблица с видом по умолчанию, большинство свойств будут скрыты - это обусловлено тем, что выводить все свойства на экран нецелесообразно из-за ограниченного пространства на экране. Один из способов показать все свойства без фильтрации - это направить вывод в HTML:
 +<code powershell>
 +Get-Process | ConvertTo-HTML | Out-File processes.html
 +</code>
 +Собственно, //объектом (object)// называется строка в "таблице", представляющая собой одну сущность - процесс, службу и т. д.\\
 +//Свойством (property)// называется колонка таблицы - один тип информации о процессах, сервисах и т. п.\\
 +//Метод (method)// - также обозначается как //действие (action)//. Взаимодействует с объектом - например, останавливает процесс, запускает службу и т. д.\\
 +//Набор (collection)// - все объекты, которые перечислены в этой "таблице".
 +<code powershell>
 +Get-Process | Get-Member # (или gm) показать свойства и методы командлета
 +</code>
 +К примеру, чтобы убить процесс, есть три пути: вызвать метод kill,
 +<code powershell>
 +# передать через пайп
 +Get-Process -Name Notepad | Stop-Process
 +# или обойтись одним командлетом
 +Stop-Process -name Notepad
 +</code>
 +=== Сортировка ===
 +<code powershell>
 +Get-Process | Sort-Object -property VM
 +Get-Process | Sort VM -desc # сокращённо и в обратном порядке
 +# Если несколько процессов занимают одинаковое кол-во памяти, сортировать по ID
 +Get-Process | Sort VM,ID -desc
 +</code>
 +=== Выбор свойств ===
 +Select-Object можно написать как просто select.
 +<code powershell>
 +Get-Process | Select-Object -property Name,ID,VM,PM | Convert-ToHTML | Out-File test2.html
 +Get-Process | Select -First 10 # 10 первых, -Last - 10 последних
 +</code>
 +Нужно не путать Select-Object (выбор свойств для отображения) с Where-Object (фильтрует объекты после пайпа на основании заданных критериев).
 +
 +==== Ещё про пайп ====
 +Положим, есть список имён компьютеров в текстовике, и PS позволяет сделать так:
 +<code powershell>
 +Get-Content .\computers.txt | Get-Service
 +</code>
 +Это нзывается привязкой параметров (parameter binding). PS смотрит на тип полученных данных и далее применяет его к параметру второй команды, способному принять такой тип данных. В данном случае Get-Content производит данные типа System.String (или, коротко, String). Get-Service принимает тип данных String параметром -Name. Тип выдаваемых данных можно посмотреть командой Get-Member (или gm), а тип принимаемых данных - в справке по командлету в строке "Accept pipeline input?" того или иного параметра.
 +
 +В большинстве случаев, если в командлетах одно существительное, они успешно взаимодействуют через пайп:
 +<code powershell>
 +get-process -name note* | Stop-Process
 +</code>
 +
 +Если привязка по значению (ByValue) не удаётся, PS пробует другой подход - по имени параметра (ByPropertyName). Благодаря этому возможно такое (обратите внимание, что параметр -name указан явно):
 +<code powershell>
 +get-service -name s* | stop-process
 +</code>
 +В данном примере, данные успешно передаются через пайп, но будет куча ошибок, так как имена сервисов и процессов различаются.
 +
 +Более успешный пример - создать файл .csv cо следующим содержанием:
 +<file csv aliases.csv>
 +Name,Value
 +d,Get-ChildItem
 +sel,Select-Object
 +go,Invoke-Command
 +</file>
 +
 +Если выполнить Get-Member, то можно узнать, что Import-CSV выдаёт на выходе
 +<code powershell>
 +import-csv .\aliases.csv | gm
 +</code>
 +Далее, если выполнить 
 +<code powershell>
 +import-csv .\aliases.csv | new-alias
 +</code>
 +то окажется, что создались три новых алиаса для командлетов, так как строки при импорте преобразовались в объекты, а параметры Name и Value совпали с одноимёнными параметрами в командлете New-Alias.
 +=== Замена параметров при передаче ===
 +Например, есть файл CSV, который был прислан извне, и на базе него нужно создать пользователей в AD.
 +<file csv newusers.csv>
 +login,dept,city,title
 +DonJ,IT,Las Vegas,CTO
 +GregS,Custodial,Denver,Janitor
 +JeffH,IT,Syracuse,Network Engineer
 +</file>
 +Для создания пользователей используется New-ADUser, но там нет параметров dept и т. д., то есть, просто передать данные в сыром виде через пайп не получится. Для этого нужно указать соответствие передаваемых параметров.
 +<code powershell>
 +import-csv .\newusers.csv | select-object -property *,
 +>> @{name='samAccountName';expression={$_.login}},
 +>> @{label='Name';expression={$_.login}},
 +>> @{n='Department';e={$_.Dept}} | New-ADUser
 +</code>
 +-property * - вывести все доступные свойства.\\
 +@{key=value} - создание хэш-таблиц соответствия. Ключ может указываться как Name, N, Label или L. Значение - как Expression или E.\\
 +$_ - обращение к исходному объекту, передаваемому через пайп.
 +=== Скобки ===
 +Некоторые командлеты не принимают данные через пайп, например, Get-WmiObject -ComputerName. Выход - использовать скобки, которые действуют как в алгебре, т. е., в скобках действие выполняется в первую очередь.
 +<code powershell>
 +# Так работать не будет:
 +get-content .\computers.txt | get-wmiobject -class win32_bios
 +# А так - будет:
 +Get-WmiObject -class Win32_BIOS -ComputerName (Get-Content .\computers.txt)
 +</code>
 +Необходимо, чтобы результат в скобках был соответствующего типа для параметра, который будет получать результат.
 +<code powershell>
 +# Так работать не будет, в скобках тип будет ADComputer,
 +# а нужно String:
 +Get-Service -computerName (Get-ADComputer -filter * -searchBase "ou=domain controllers,dc=company,dc=pri")
 +# А так - будет (передать только имена):
 +Get-Service -computerName (Get-ADComputer -filter * -searchbase "ou=domain controllers,dc=company,dc=pri" | select -expand name)
 +# -expand - вывести значения без названия столбца
 +</code>
 +
 +==== Форматирование ====
 +В папке $pshome есть файл otnettypes.format.ps1xml, отвечающий за форматирование вывода команд. Например, если выполнить
 +<code powershell>
 +Get-Process | gm
 +</code>
 +скопировать тип (System.Diagnostics.Process) и поискать его в файле otnettypes.format.ps1xml, то под строкой %%<Name>process</Name>%% будут находиться стандартные параметры форматирования вывода команды.
 +
 +В некоторых случаях предустановленный вывод отсутствует, например, в случае с
 +<code powershell>
 +Get-WmiObject Win32_OperatingSystem | Gm
 +</code>
 +Такого типа данных нет в otnettypes.format.ps1xml, в этом случае используется "второе правило форматирования" - вывод по умолчанию, описанный в Types.ps1xml (DefaultDisplayPropertySet).
 +
 +"Третье правило" форматирует вывод согласно его типу, например, если отображается до 4 параметров, используется таблица, 5 и более - список.
 +=== Команды форматирования ===
 +**Format-Table (ft)** - таблица.\\
 +-AutoSize - подгоняет ширину столбцов под длину строк.\\
 +-Property - выбор столбцов для отображения, перечисляются через запятую. Звёздочка - вывести все столбцы.\\
 +Примеры:
 +<code powershell>
 +Get-Process | Format-Table -property *
 +Get-Process | Format-Table -property ID,Name,Responding -autoSize
 +Get-Process | Format-Table * -autoSize
 +</code>
 +-groupby - сортировка по какому-то из столбцов.\\
 +-Wrap - перенос строк в столбце.
 +
 +**Format-List (fl)** - список. Используются те же параметры, что и для таблицы.\\
 +В какой-то степени fl можно использовать как gm, с той разницей, что fl покажет и значения параметров.
 +<code powershell>
 +Get-Service | Fl *
 +</code>
 +
 +**Format-Wide (fw)** - широкий список. Выводит только одно свойство (-Property), но в несколько колонок.
 +<code powershell>
 +Get-Process | Format-Wide name -col 4
 +</code>
 +
 +Более комплексные примеры:
 +<code powershell>
 +Get-Service | Format-Table @{name='ServiceName';expression={$_.Name}},Status,DisplayName
 +Get-Process | Format-Table Name,@{name='VM(MB)';expression={$_.VM / 1MB -as [int]}} -autosize
 +Get-Process | Format-Table Name,@{name='VM(MB)';expression={$_.VM};formatstring='F2';align='right'} -autosize
 +</code>
 +formatstring - код стандартного форматирования чисел и дат ([[https://msdn.microsoft.com/en-us/library/fbxft59x(v=vs.95).aspx]]).\\
 +-Width - задаваемая ширина столбца.\\
 +align - выравнивание.
 +=== Вывод ===
 +Если используется команда, начинающаяся с Format-, то правила форматирования для этих команд заданы так, что вывод делается в Out-Default, который передаёт данные в Out-Host (на экран). То есть, эти две строки дадут одинаковый результат:
 +<code powershell>
 +Get-Service | Format-Wide | Out-Host
 +Get-Service | Format-Wide
 +</code>
 +Можно вывести данные в файл и на принтер, т. е., использовать Out-File и Out-Printer. У каждой из этих команд свои правила форматирования по умолчанию.\\
 +Также, есть команда Out-GridView, которая создаёт стандартное окно Windows с выводимой информацией, в консоль ничего не выводится (в других системах может не работать). Out-GridView не принимает правил форматирования и обрабатывает только стандартные объекты.
 +
 +Команды форматирования (Format-*) должны идти последними в строке, единственное исключение - команды вывода (Out-*). Пример неправильного использования (в файле services.html будет ересь, т. к. в него передаются инструкции форматирования вместо объектов):
 +<code powershell>
 +Get-Service | Select Name,DisplayName,Status | Format-Table | ConvertTo-HTML | Out-File services.html
 +</code>
 +
 +Для корректной обработки данных, необходимо передавать через пайп только один тип объектов, если нет какой-то специальной хорошо понимаемой задачи, диктующей обратное.
 +
 +==== Фильтр результатов и сравнение ====
 +Два подхода: запрос сразу нужной информации (the //filter left// technique) и последующая фильтрация результатов другой командой. Желательно по-максимуму использовать первый как потребляющий меньше ресурсов.\\
 +Для последующей фильтрации используется команда Where-Object (where).
 +
 +=== Операторы сравнения ===
 +-eq - Equality\\
 +-ne - Not equal to\\
 +-ge - Greater than or equal to\\
 +-le - Less than or equal to\\
 +-gt - Greater than\\
 +-lt - Less than
 +
 +Для сравнения нескольких строк одновременно используются -and и -or. Например, (5 -gt 10) -and (10 -gt 100) - это False, т. к. одно утверждение неверно, а (5 -gt 10) -or (10 -lt 100) - True, так как одно утверждение верно.\\
 +Оператор -not меняет True или False на противоположное значение. Например,
 +<code powershell>
 +$_.Responding -eq $False
 +# можно написать как
 +-not $_.Responding
 +
 +# Ещё пример фильтра
 +Get-ADComputer -Filter 'Name -notlike "*0000*" -and Enabled -eq "True"' | select Name | sort Name
 +</code>
 +Иногда -not пишется как !.
 +
 +-like - понимает маски, например %%"Hello" -like "*ll*"%% - true. Противоположный оператор - -notlike.\\
 +-match - сравнение текстовой строки и регулярного выражения. Противоположный оператор - -notmatch.
 +
 +Если нужно использовать чувствительные к регистру операторы, в начале добавляется "c": -ceq, -cne, -cgt, -clt, -cge, -cle, -clike, -cnotlike, -cmatch, -cnotmatch. Полезно для операций со строками.
 +<code powershell>
 +# Справка
 +help about_comparison_operators
 +</code>
 +Примеры использования фильтрации уже полученных результатов:
 +<code powershell>
 +Get-Service | Where Status -eq 'Running'
 +get-service | where-object {$_.status -eq 'running' -AND $_.StartType -eq 'Manual'}
 +</code>
 +
 +==== Удалённый доступ ====
 +<code powershell>
 +# Включить на целевой машине
 +Enable-PSRemoting
 +# Справка:
 +about_remote_troubleshooting
 +</code>
 +Иногда советуют запускать Set-WSManQuickConfig, но это не нужно, т.к. Enable-PSRemoting сам запускает Set-WSManQuickConfig и выполняет дополнительные действия, такие, как создание правила на файрволле. WinRM v2 использует порты TCP 5985 (HTTP) и 5986 (HTTPS), Enable-PSRemoting стандартно конфигурирует доступ через 5985 (HTTP). В домене можно включить удалённый доступ через GPO: Computer Configuration -> Administrative Templates -> Windows Components -> Remote Shell + Windows Remote Management. Через GPO также можно настроить длительность неактивных сессий, кол-во одновременных подключений, права и т. д.
 +
 +Менять номер порта не рекомендуется, но возможно:
 +<code powershell>
 +Winrm set winrm/config/listener?Address=*+Transport=HTTP
 +@{Port="1234"}
 +</code>
 +
 +=== Два способа подключения: one-to-one (1:1) и one-to-many (1:N) ===
 +1:1 как RDP, только консоль.
 +<code powershell>
 +Enter-PSSession -computerName Server-R2
 +[server-r2] PS C:\>
 +# Выйти:
 +Exit-PSSession
 +</code>
 +Ограничения:
 +  - PS-профили, которые есть на удалённой машине, загружены не будут.
 +  - Удалённая сессия будет ограничена политикой выполнения (execution policy) удалённой машины, а не локальной.
 +
 +Без крайней необходимости лучше не запускать удалённые сессии из-под удалённых сессий (remoting chain), т. к. это трудно отслеживать. Иногда это бывает нужно, например, если третья машина находится за шлюзом и напрямую сессию запустить нельзя.
 +
 +One-to-many (1:N) - каждый комп запускает команду у себя и возвращает результат.
 +<code powershell>
 +Invoke-Command -computerName Server-R2,Server-DC4,Server12
 +-command { Get-EventLog Security -newest 200 |
 +Where { $_.EventID -eq 1212 }}
 +# Вместо -command можно использовать -scriptblock, указывая путь к скрипту.
 +</code>
 +По умолчанию PS запускает 32 удалённых запроса одновременно, остальные ставит в очередь, это можно изменить с помощью параметра -throttleLimit.
 +<code powershell>
 +# Запуск команды на серверах в списке
 +Invoke-Command -command { dir }
 +-computerName (Get-Content webservers.txt)
 +# Взять список из AD (нужен RSAT на клиенте)
 +Invoke-Command -command { dir } -computerName (
 +Get-ADComputer -filter * -searchBase "ou=Sales,dc=company,dc=pri" |
 +Select-Object -expand Name )
 +</code>
 +
 +=== Разница между Invoke-Command и -computerName ===
 +<code powershell>
 +Get-EventLog Security -newest 200
 +-computerName Server-R2,Server-DC4,Server12
 +| Where { $_.EventID -eq 1212 }
 +</code>
 +  - Команда запускается последовательно на каждой машине. не параллельно.
 +  - В выводе нет свойства PSComputerName, т. е. трудно сказать, с какой машины какой результат.
 +  - WinRM не используется, и могут быть проблемы с выполнением команд на машинах за шлюзами.
 +  - Фильтр по событию 1212 применяется уже после запроса, следовательно, по сети будут передаваться ненужные события.
 +  - События являются объектами, т. е., полностью функциональны.
 +
 +<code powershell>
 +Invoke-Command -computerName Server-R2,Server-DC4,Server12
 +-command { Get-EventLog Security -newest 200 |
 +Where { $_.EventID -eq 1212 }}
 +</code>
 +  - Команда запускается одновременно на всех машинах, выполнится быстрее.
 +  - В выводе есть свойство PSComputerName, легко определить что откуда.
 +  - Используется WinRM, проще сделать правила на файрволлах между клиентом и серверами.
 +  - Фильтрация происходит на серверах, и по сети передаётся уже готовый вывод.
 +  - Перед передачей на клиента, серверы формируют (serialize) результат в виде XML, клиент при получении обрабатывает (deserialize) информацию и выводит её на экран. Это выглядит как объекты, но ими не является.
 +
 +=== Локальная и удалённая обработка ===
 +<code powershell>
 +# Фильтрация на удалённых серверах
 +Invoke-Command -computerName Server-R2,Server-DC4,Server12
 +-command { Get-EventLog Security -newest 200 |
 +Where { $_.EventID -eq 1212 }}
 +# Фильтрация на клиенте (плохая идея)
 +Invoke-Command -computerName Server-R2,Server-DC4,Server12
 +-command { Get-EventLog Security -newest 200 } |
 +Where { $_.EventID -eq 1212 }
 +</code>
 +<code powershell>
 +# Работать не будет, т.к. результат выдачи с сервера - не объект
 +Invoke-Command -computerName Server-R2
 +-command { Get-Process -name Notepad } |
 +Stop-Process
 +# Сработает
 +Invoke-Command -computerName Server-R2
 +-command { Get-Process -name Notepad |
 +Stop-Process }
 +</code>
 +<code powershell>
 +# На локальной машине доступны методы
 +get-service | get-member
 +# Здесь методов не будет, потому что вывод - не объекты
 +Invoke-Command -ScriptBlock { Get-Service } -ComputerName
 +server2 | Get-Member
 +</code>
 +
 +=== Настройки удалённой сессии ===
 +Параметр -SessionOption при запуске Invoke-Command или Enter-PSSession.
 +  - Задаёт таймауты сессий
 +  - Выключает сжатие и шифрование данных
 +  - Пропуск проверки SSL-сертификатов, имени и других проверок безопасности
 +<code powershell>
 +Enter-PSSession -ComputerName server2
 +-SessionOption (New-PSSessionOption -SkipCNCheck)
 +</code>
 +
 +=== Примечания ===
 +  * Удалённые сессии работают с реальными именами компьютеров - IP и алиасы использовать нельзя.
 +  * Конфигурация по умолчанию рассчитана на домен, если что - надо читать help about_remote_troubleshooting, например, если надо настроить работу удалённых сессий между доменами.
 +  * Invoke-Command - это значит, что команда выполняется на удалённой машине, и PS там закрывается, соответственно, данные больше недоступны. При множестве последовательных взаимозависимых команд нужно запускать их в рамках одной сессии.
 +  * Для успешной работы нужно убедиться, что она идёт под админом, либо нужно использовать параметр -credential, где указывать админскую учётку.
 +  * Enable-PSRemoting создаёт правило только для Windows firewall, другие файрволлы надо настраивать отдельно.
 +  * GPO перекрывает локальные настройки, если что-то не работает, полезно проверить настройки GPO.
 +
 +Рекомендуется книга [[https://powershell.org/2012/08/ebook-secrets-of-powershell-remoting/|Secrets of PowerShell Remoting]].
 +
 +==== WMI и CIM ====
 +WMI содержит пространства имён (namespaces), например, root\CIMv2 содержит данные о Windows и железе, root\MicrosoftDNS - о роли DNS (если она установлена), root\SecurityCenter (root\SecurityCenter2) - файрволл, антивирус, антишпионское ПО.\\
 +Также, WMI содержит классы - управляемые компоненты, к которым WMI умеет обращаться. Например, Antivirus-
 +Product в root\SecurityCenter, Win32_LogicalDisk в root\CIMv2. Наличие какого-то класса не означает, что в системе есть такой компонент - например,  класс Win32_TapeDrive есть во всех версиях Windows независимо от того, есть стример в реальности или нет.
 +<code powershell>
 +Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiSpywareProduct
 +
 +displayName              : Kaspersky Endpoint Security для Windows
 +instanceGuid             : {B1D2E896-6D96-7460-F17A-838B9D00DD65}
 +pathToSignedProductExe   : C:\Program Files (x86)\Kaspersky Lab\Kaspersky Endpoint Security for Windows\wmias.exe
 +pathToSignedReportingExe : C:\Program Files (x86)\Kaspersky Lab\Kaspersky Endpoint Security for Windows\x64\wmi64.exe
 +productState             : 266240
 +timestamp                : Thu, 18 Apr 2019 12:14:05 GMT
 +PSComputerName           :
 +
 +displayName              : Windows Defender
 +instanceGuid             : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
 +pathToSignedProductExe   : %ProgramFiles%\Windows Defender\MSASCui.exe
 +pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
 +productState             : 393472
 +timestamp                : Tue, 23 May 2017 06:40:25 GMT
 +PSComputerName           :
 +</code>
 +
 +Просматривать репозиторий WMI удобно с помощью [[https://powershell.org/2013/03/wmi-explorer/|WMI explorer]]. Можно искать и так:
 +<code powershell>
 +Get-WmiObject -Namespace root/CIMV2 -list |where name -match 'disk' |sort name
 +</code>
 +
 +WIM-команды, типа Get-WmiObject и Invoke-WmiMethod - устаревшие, не развиваются, работают через RPC, пробросить их через файрволл трудно. Их можно использовать на старых системах, в ОС Windows Server 2012 R2 и новее они отключены по умолчанию. CIM-команды, типа Get-CimInstance и Invoke-CimMethod - современная замена, работают через WS-MAN (WinRM).
 +
 +<code powershell>
 +# Вывести все классы в пространстве имён cimv2
 +Get-WmiObject -namespace root\cimv2 -list
 +# Содержимое класса win32_desktop
 +Get-WmiObject -namespace root\cimv2 -class win32_desktop
 +Get-WmiObject win32_desktop # или так
 +# использование алиаса
 +gwmi antispywareproduct -namespace root\securitycenter2
 +# методы и свойства класса
 +gwmi win32_operatingsystem | gm
 +# фильтр
 +gwmi win32_desktop -filter "name='COMPANY\\Administrator'"
 +</code>
 +  * Операторы сравнения не как в PS (-like, -eq), а =, <, >= и т. д. Можно использовать LIKE, и как указатель wildcard нужно писать не *, а %, например, "NAME LIKE '%administrator%'".
 +  * Строки сравнения - в одиночных кавычках, весь фильтр - в двойных.
 +  * Обратная косая черта (\) - экранирующий знак, если нужно поставить в строке \, то придётся удваивать - \\.
 +  * Вывод gwmi всегда содержит ряд системных свойств, которые начинаются с двойного подчёркивания, по умолчанию PS часто подавляет их вывод, но это можно переопределить. Пара полезных свойств: %%__SERVER%% - имя компьютера, с которого была запрошена информация (это же имя есть в PSComputerName), %%__PATH%% - абсолютный путь к запущенному экземпляру.
 +<code powershell>
 +# выполняется последовательно, если комп недоступен - выдаёт ошибку и идёт дальше
 +Gwmi Win32_BIOS -comp server-r2,server3,dc4
 +# вариант с форматированием и названием колонок
 +gwmi win32_bios -computer server-r2,localhost |
 +ft @{label='ComputerName';expression={$_.__SERVER}},
 +@{label='BIOSSerial';expression={$_.SerialNumber}},
 +@{label='OSBuild';expression= {gwmi -class \win32_operatingsystem
 +-computer $_.__SERVER | select -expand BuildNumber}} –autosize
 +</code>
 +
 +Отличия Get-CimInstance от Get-WmiObject:
 +  - Используется -ClassName, а не -Class
 +  - Нет параметра -List, вместо этого нужно использовать Get-CimClass с параметром -Namespace
 +  - Нет параметра -Credential, если нужны другие учётные данные для запуска, надо использовать Invoke-Command
 +
 +Особенность работы с WMI в Powershell - справка малоэффективна, нужно гуглить или пользоваться всякими костылями типа WMI Explorer.
 +
 +==== Несколько задач одновременно, фоновый режим ====
 +В обычном режиме команды запускаются //синхронно,// т. е., нажимаем ввод и ждём результата. В фоновом режиме команды выполняются //асинхронно// - задача запускается, и консоль можно использовать дальше для других задач.
 +  * В синхронном режиме можно отвечать на возникающие запросы, в асинхронном - возникший запрос остановит задачу.
 +  * В синхронном режиме сразу появляются сообщения об ошибках, если что-то идёт не так, в асинхронном сразу увидеть ошибки нельзя, но можно их захватывать определённым способом.
 +  * PS в синхронном режиме запрашивает значения обязательных параметров, если они пропущены, в асинхронном такой возможности нет, и выполнение задачи прерывается.
 +  * Результаты выполнения задачи в синхронном режиме отображаются сразу же, в асинхронном - после выполнения задачи доступны в кэше.
 +
 +В фоне нужно запускать только хорошо отлаженные сценарии, с наибольшим шансом успешного выполнения. Фоновые команды в PS называются задачи (jobs). Эти задачи можно создавать несколькими способами, также есть несколько способов управления ими.
 +
 +<code powershell>
 +# Запустить задачу
 +Start-Job -ScriptBlock {dir} -Name Job1 -Credential DOMAIN\Username
 +# Задача из файла, на другой машине
 +Start-Job -FilePath C:\temp\script.ps1 -Name Job2 -Credential DOMAIN\Username -Computer server2
 +</code>
 +Все эти задачи называются локальными, т. к. запуск идёт с локальной машины. После запуска задачам присваивается ID, причём, номера ID идут непоследовательно, так как у каждой задачи есть дочерние процессы (child jobs). Несмотря на то, что задачи локальные, они требуют работающего механизма PowerShell’s remoting system, иначе работать не будет.
 +
 +У Get-WmiObject есть параметр -AsJob, который запускает командлет в фоне, но там нельзя выбрать произвольное имя задачи.
 +<code powershell>
 +get-wmiobject win32_operatingsystem -computername (get-content allservers.txt) –asjob
 +
 +WARNING: column "Command" does not fit into the display and was removed.
 +Id Name State HasMoreData Location
 +-- ---- ----- ----------- --------
 +5 Job5 Running False server-r2,localhost
 +
 +# Посмотреть другие команды, у которых есть параметр -AsJob
 +Help * -parameter asjob
 +</code>
 +У Get-CimInstance нет параметра -AsJob, его нужно использовать в сочетании с Start-Job или Invoke-Command.
 +
 +Создание удалённой фоновой задачи:
 +<code powershell>
 +invoke-command -command { get-process }
 +-computername (get-content .\allservers.txt )
 +-asjob -jobname MyRemoteJob
 +
 +WARNING: column "Command" does not fit into the display and was removed.
 +Id Name State HasMoreData Location
 +-- ---- ----- ----------- --------
 +8 MyRemoteJob Running True server-r2,localhost
 +</code>
 +
 +Просмотр результатов
 +<code powershell>
 +get-job # список задач
 +get-job -id 1 | fl * # подробности первой задачи
 +receive-job -id 1 # вывод результатов. Нужно указать ID или имя задачи
 +# сортировка результата удалённой задачи
 +receive-job -name myremotejob | sort-object PSComputerName |
 +Format-Table -groupby PSComputerName
 +</code>
 +  * Если вывести результаты материнской задачи, они будут включать результаты дочерних. Как вариант, можно выводить результаты одной или нескольких дочерних задач.
 +  * Обычно после получения результатов кэш стирается, и эти результаты нельзя запросить второй раз. Можно указать параметр -keep для сохранения результатов в кэше или выводить его в CliXML.
 +  * Результаты могут не быть объектами (deserialized objects), и методы к ним не могут применяться, но можно форматировать вывод (sort, fl и т. д.)
 +
 +Контекст запуска задачи и команд в этой задаче различается, например,
 +<code powershell>
 +C:\> start-job -scriptblock { dir }
 +# выведет список по пути
 +$env:userprofile\Documents
 +</code>
 +Поэтому, в командах нужно указывать пути.
 +
 +Работа с задачами
 +  * Remove-Job - удалить вместе с результатом выполнения в кэше
 +  * Stop-Job - прервать выполнение, результаты останутся на момент прерывания
 +  * Wait-Job - полезно в скрипте, указание ждать выполнения задачи
 +
 +<code powershell>
 +# Показать дочерние задания
 +get-job -id 1 | select -expand childjobs
 +# грохнуть все задачи, где уже нет результатов
 +get-job | where { -not $_.HasMoreData } | remove-job
 +</code>
 +Если фоновая задача выполнилась с ошибкой (State failed), то текст ошибки хранится как результат в дочерней задаче.
 +
 +=== Запланированные задачи (scheduled jobs) ===
 +Это несколько отличается как от фоновых задач PS, так и от заданий в планировщике Windows (scheduled tasks). Чтобы создать задачу, нужно задать триггер (New-JobTrigger), определяющий, когда задача запускается, параметры задачи (New-ScheduledTaskOption), затем задача регистрируется в Планировщике - на диске создаётся файл XML и иерархия каталогов для хранения результатов выполнения.
 +<code powershell>
 +Register-ScheduledJob -Name DailyProcList -ScriptBlock {
 +Get-Process } -Trigger (New-JobTrigger -Daily -At 2am)
 +-ScheduledJobOption (New-ScheduledJobOption -WakeToRun -RunElevated)
 +</code>
 +Если запросить список задач (Get-Job), то видно, что каждый раз при выполнении запланированной задачи создаётся задача в списке, причём, если запрашивать результат выполнения, то они не удалятся, так как хранятся на диске, а не в памяти. Но если удалить задачу, то результаты будут также удалены с диска. Количество создаваемых и хранимых задач регулируется параметром -MaxResultCount в команде Register-ScheduledJob.
 +
 +Как делать не надо:
 +<code powershell>
 +# после запуска соединение прерывается, и результаты задачи уже не будут доступны
 +invoke-command -command { Start-Job -scriptblock { dir } } -computername Server-R2
 +# здесь нужно использовать -AsJob у Invoke-Command
 +start-job -scriptblock { invoke-command -command { dir } -computername SERVER-R2 }
 +</code>
 +Вообще, не надо мешать в кучу рассмотренные три способа запуска задач.
 +
 +Задачи существуют, пока запущена консоль. Исключение - запланированные задачи, т. к. они хранятся на диске; к ним может получить доступ тот, кто имеет соответствующие права.
 +<code powershell>
 +# Последние 25 ошибок из системного лога выгружать в XML
 +# с пн по пт в 6:00
 +Register-ScheduledJob -ScriptBlock {Get-EventLog -LogName System -Newest 25 -EntryType Error |
 +Export-Clixml -Path C:\Temp\job.xml} -Name EventLogErrors -Trigger (
 +New-JobTrigger -Weekly -DaysOfWeek 1,2,3,4,5 -At 6AM)
 +</code>
 +
 +==== Работа со многими объектами одновременно ====
 +В отличие от подхода, когда запрашивается какое-то количество объектов и потом они обрабатываются, перебираясь по одному (foreach), есть более эффективные способы обработки.
 +
 +Предпочтительный путь - использование "пакетных" команд. Это команды, способные принимать данные через пайп.
 +<code powershell>
 +Get-Service | Stop-Service
 +Get-Service -name BITS,Spooler,W32Time | Set-Service -startuptype Automatic
 +
 +# Недостаток - не видно вывода от предыдущих команд
 +Get-Service -name BITS,Spooler,W32Time -computer Server1,Server2,Server3 | Set-Service -startuptype Automatic
 +# Теперь видно, с использованием -PassThru
 +Get-Service -name BITS -computer Server1,Server2,Server3 | Start-Service -passthru | Out-File NewServiceStatus.txt
 +</code>
 +
 +Другой способ - вызов метода. Положим, надо включить DHCP на сетевых адаптерах Intel.
 +<code powershell>
 +# Запрос интеловских адаптеров
 +gwmi win32_networkadapterconfiguration -filter "description like '%intel%'"
 +# Вывод доступных объектов внутри, там есть метод EnableDHCP().
 +gwmi win32_networkadapterconfiguration -filter "description like '%intel%'" | gm
 +# Вот так работать не будет:
 +gwmi win32_networkadapterconfiguration -filter "description like '%intel%'" | EnableDHCP()
 +# Нужно использовать специальную команду для вызова методов WMI
 +# Код возврата выполненного метода (ReturnValue) можно погуглить
 +gwmi win32_networkadapterconfiguration -filter "description like '%intel%'" | Invoke-WmiMethod -name EnableDHCP
 +# То же самое с CIM
 +gcim -classname win32_networkadapterconfiguration -filter "description like '%intel%'" |Invoke-CimMethod -methodname EnableDHCP
 +</code>
 +Также, можно в случае с WMI/CIM поискать другой, родной для PS командлет, выполняющий нужную функцию - в данном случае Set-NetIPAddress.
 +
 +Тем не менее, иногда приходится использовать перебор. Например, нужно задать пароль для запуска служб, и ни Get-Service, ни Set-Service с паролями работать не умеют, придётся использовать WMI. В методе change много параметров, для пропуска ненужных используется переменная $null.
 +<code powershell>
 +# Так работать не будет:
 +gwmi win32_service -filter "name = 'BITS'" | invoke-wmimethod -name
 +change -arg $null,$null,$null,$null,$null,$null,$null,"P@ssw0rd"
 +# Invoke-WmiMethod : Input string was not in a correct format.
 +
 +# Можно долго выяснять, в чём дело, но быстрее и проще использовать перебор (% = foreach):
 +gwmi win32_service -filter "name = 'BITS'" |
 +% {$_.change($null,$null,$null,$null,$null,$null,$null,"P@ssw0rd") }
 +</code>
 +
 +Иллюстрация разных подходов
 +<code powershell>
 +Get-Service -name *B* | Stop-Service # пакетная команда
 +Get-Service -name *B* | % { $_.Stop()} # перебор
 +Get-WmiObject Win32_Service -filter "name LIKE '%B%'" |
 +Invoke-WmiMethod -name StopService # WMI
 +Get-WmiObject Win32_Service -filter "name LIKE '%B%'" |
 +% { $_.StopService()} # WMI с перебором
 +Stop-Service -name *B* # специализированная команда
 +</code>
 +Нельзя передать что-либо методу через пайп, передать можно только другой команде. Если команда не умеет делать то, что нужно, а метод умеет, тогда используется foreach и дальше вызывается нужный метод.
 +<code powershell>
 +Get-Something | ForEach-Object { $_.Delete() }
 +</code>
 +
 +==== Безопасность ====
 +Политика выполнения скриптов - в клиентских системах вообще запрещено (Restricted), на серверах можно выполнять те, что созданы локально или подписанные (RemoteSigned).\\
 +Можно управлять политикой выполнения через GPO (Computer Configuration -> Policies -> Administrative Templates -> Windows Components -> Windows PowerShell).
 +AllSigned - все скрипты должны быть подписаны доверенным УЦ.\\
 +Unrestricted - можно всё.\\
 +Bypass - для использования в приложениях, работающих с PS.
 +
 +<WRAP round tip 80%>
 +Plenty of experts, including Microsoft’s own “Scripting Guy,” suggest
 +using the Unrestricted setting for ExecutionPolicy. Their feeling is that the
 +feature doesn’t provide a layer of security, and you shouldn’t give yourself
 +false confidence that it’s protecting you from anything.
 +</WRAP>
 +
 +Дополнительные меры:
 +  - Скрипты .ps1 не выполняются по двойному клику мышкой, а открываются как текстовые файлы.
 +  - Запустить скрипт из консоли невозможно, просто набирая его имя, т. к. консоль не ищет скрипты в локальной папке.
 +
 +Сделать самоподписанный сертификат для подписи кода
 +<code powershell>
 +help about_signing
 +New-SelfSignedCertificate
 +</code>
 +
 +==== Переменные - место для хранения ====
 +Переменные могут содержать пробелы, тогда нужно их заключать в фигурные скобки: ${variable 1}. Переменные не сохраняются между сессиями PS. PS воспринимает заключённое в одинарные кавычки буквально, поэтому
 +<code powershell>
 +$var = 'What does $var contain?'
 +$var
 +What does $var contain?
 +
 +$computername = 'SERVER-R2'
 +$phrase = "The computer name is $computername"
 +$phrase
 +The computer name is SERVER-R2
 +</code>
 +
 +Символ ` (escape character, под ~) - отменяет действие последующего символа, т. е.,
 +<code powershell>
 +$computername = 'SERVER-R2'
 +$phrase = "`$computername contains $computername"
 +$phrase
 +$computername contains SERVER-R2
 +
 +$phrase = "`$computername`ncontains`n$computername"
 +$phrase
 +$computername
 +contains
 +SERVER-R2
 +
 +# справка
 +help about_escape
 +</code>
 +<code powershell>
 +# Несколько объектов в переменной
 +$computers = 'SERVER-R2','SERVER1','localhost'
 +$computers
 +SERVER-R2
 +SERVER1
 +Localhost
 +
 +# Извлечение отдельных элементов из массива
 +# Первый объект - 0, второй - 1, последний - -1, предпоследний - -2
 +$computers[0]
 +SERVER-R2
 +$computers[1]
 +SERVER1
 +$computers[-1]
 +localhost
 +$computers[-2]
 +SERVER1
 +
 +# Подсчёт кол-ва объектов
 +$computers.count
 +3
 +
 +# Вызов свойств и методов
 +$computername.length
 +9
 +$computername.toupper()
 +SERVER-R2
 +$computername.tolower()
 +server-r2
 +$computername.replace('R2','2008')
 +SERVER-2008
 +$computername
 +SERVER-R2
 +
 +# Применительно к отдельному объекту массива
 +$computers[0].tolower()
 +server-r2
 +$computers[1].replace('SERVER','CLIENT')
 +CLIENT1
 +
 +# Заменить значение в массиве
 +$computers[1] = $computers[1].replace('SERVER','CLIENT')
 +$computers
 +SERVER-R2
 +CLIENT1
 +Localhost
 +
 +# Обработка всех значений в массиве
 +$computers = $computers | ForEach-Object { $_.ToLower()}
 +$computers
 +server-r2
 +client1
 +localhost
 +</code>
 +
 +В PS v1 и v2 у переменных, содержащих множественные значения, нельзя было использовать методы и свойства, с v3 сделали авторазвёртывание (automatic unrolling), т. е.
 +<code powershell>
 +# PS v3 понимает, что у $services нет св-ва Name, но у дочерних объектов есть
 +$services = Get-Service
 +$services.Name
 +# Для предыдущих версий нужно было бы делать так
 +Get-Service | ForEach-Object { Write-Output $_.Name }
 +# или так
 +Get-Service | Select-Object –ExpandProperty Name
 +
 +# то же работает и для методов
 +$objects = Get-WmiObject –class Win32_Service –filter "name='BITS'"
 +$objects.ChangeStartMode('Disabled')
 +</code>
 +
 +<code powershell>
 +# Так работать не будет - двойные кавычки не воспринимают
 +# конструкцию [0].name как функциональную часть
 +$services = get-service
 +$firstname = "$services[0].name"
 +$firstname
 +AeLookupSvc ALG AllUserInstallAgent AppIDSvc ... wudfsvc WwanSvc[0].name
 +# без кавычек это будет работать
 +
 +# если кавычки всё же нужны, то используется субпеременная $()
 +$services = get-service
 +$firstname = "The first name is $($services[0].name)"
 +$firstname
 +The first name is AeLookupSvc
 +</code>
 +
 +Иногда принудительное указание типа переменной необходимо.
 +<code powershell>
 +$number = Read-Host "Enter a number"
 +Enter a number: 100
 +$number = $number * 10
 +$number
 +100100100100100100100100100100
 +# в данном случае 100 воспринимается как строка,
 +# и повторяется 10 раз вместо умножения.
 +# Посмотреть тип переменной:
 +$number |gm
 +
 +# надо указать переменную так:
 +[int]$number = Read-Host "Enter a number"
 +</code>
 +Популярные типы переменных:
 +  * [int] - целое число
 +  * [single] и [double] - число с одним или двумя знаками после разделителя
 +  * [string] - строка
 +  * [char] - один символ
 +  * [xml] - документ с разметкой XML
 +  * [adsi] - запрос Active Directory Service Interfaces
 +
 +==== Ввод и вывод ====
 +Запрос информации от пользователя
 +<code powershell>
 +$computername = read-host "Enter a computer name"
 +Enter a computer name: SERVER-R2
 +</code>
 +Чтобы создать при запросе графическое окно, нужно использовать .NET
 +<code powershell>
 +# the void data type is a special type that means “throw the result away.”
 +# Another way to do the same thing would be to pipe the result to Out-Null.
 +[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
 +# 1 - текст запроса, 2 - заголовок окна, 3 - предлагаемое значение
 +$computername = [Microsoft.VisualBasic.Interaction]::InputBox('Enter a computer name','Computer Name','localhost')
 +</code>
 +Вывод
 +<code powershell>
 +write-host "COLORFUL!" -fore yellow -back magenta
 +</code>
 +Write-Host используется только для показа информации, не для форматирования, но тем не менее, он не используется для сообщений типа “now connecting to SERVER2,” “testing for folder,” и т. д. Для этого лучше использовать Write-Verbose. С 5-й версии PS, Write-Host по сути стал алиасом к более новой команде [[https://docs.microsoft.com/ru-ru/powershell/module/Microsoft.PowerShell.Utility/Write-Information?view=powershell-5.1|Write-Information]].
 +
 +В отличие от Write-Host, Write-Output умеет передавать информацию через пайп и технически не предназначена для отображения.
 +<code powershell>
 +# Если написать
 +write-output "Hello"
 +# то информация пройдёт путь write-output "Hello" -> Out-Default -> Out-Host -> Hello.
 +
 +# ничего не выведется
 +write-output "Hello" | where-object { $_.length -gt 10 }
 +# Hello будет выведено, т. к. пайп в данном случае не работает
 +write-host "Hello" | where-object { $_.length -gt 10 }
 +</code>
 +
 +Другие команды вывода, работают подобно Write-Host. Чтобы их использовать, нужно задать их конфигурацию как Continue, изменив стандартное SilentlyContinue.
 +^ Cmdlet ^ Purpose ^ Configuration variable ^
 +| Write-Warning | Displays warning text, in yellow by default, and preceded by the label WARNING: | $WarningPreference (Continue by default) | 
 +| Write-Verbose | Displays additional informative text, in yellow by default, and preceded by the label VERBOSE: | $VerbosePreference (SilentlyContinue by default) | 
 +| Write-Debug | Displays debugging text, in yellow by default, and preceded by the label DEBUG: | $DebugPreference (SilentlyContinue by default) | 
 +| Write-Error | Produces an error message (работает немного по-другому, т. к. пишет ошибку в поток вывода ошибок PS) | $ErrorActionPreference (Continue by default) | 
 +
 +Есть ещё Write-Progress, отображающий прогресс-бар, но он работает совсем по-другому.
 +
 +==== Сессии: облегчение удалённого управления ====
 +В отличие от рассматривавшихся ранее Invoke-Command и Enter-PSSession, где нужно каждый раз задавать параметры подключения, механизм аутентификации, порты и т. д., существуют //сессии// - постоянное подключение локального PS к удалённому.
 +
 +Используя New-PSSession, параметры задаются один раз, затем объект сессии хранится в памяти PS:
 +<code powershell>
 +new-pssession -computername server-r2,server17,dc5
 +# вызвать сессии
 +get-pssession
 +# удобно помещать в переменную
 +$iis_servers = new-pssession -comp web1,web2,web3 -credential WebAdmin
 +# закрыть
 +$iis_servers | remove-pssession
 +# закрыть все (при закрытии консоли сессии закрываются автоматически)
 +get-pssession | remove-pssession
 +# зайти на первый сервер
 +enter-pssession -session $iis_servers [0]
 +# или так, если порядок неизвестен
 +enter-pssession -session ($iis_servers |where { $_.computername -eq 'web1' })
 +# или так (PS хранит сессии в общем списке, даже если они записаны в переменную)
 +enter-pssession -session (get-pssession -computer web1)
 +Get-PSSession -ComputerName web1 | Enter-PSSession # как вариант
 +# выйти
 +exit-pssession
 +</code>
 +Записывать сессии в переменную имеет смысл только тогда, когда компьютеров несколько.
 +
 +<code powershell>
 +$serv = New-PSSession -ComputerName serv1,serv2,serv3,serv4
 +Invoke-Command -Command {gwmi win32_process |select processname,csname |ft} -Session $serv
 +</code>
 +У Get-WmiObject есть свой параметр -computername (у Get-CimInstance нет, он заточен под использование удалённых сессий), но лучше его не использовать, так как
 +  * Удалённый доступ PS использует определённый порт, WMI нет.
 +  * Удалённый доступ экономит ресурсы, т.к. не одна машина запрашивает данные, а каждый выполняет свою работу и высылает результаты
 +  * Удалённые запросы выполняются параллельно, WMI работает последовательно
 +  * Ранее заданные сессии не могут быть использованы с Get-WmiObject
 +
 +<code powershell>
 +# Параметры -session можно дать результат команды в скобках.
 +Invoke-Command -Command {gwmi win32_process |select processname,csname |ft} -Session (Get-PSSession -ComputerName serv2,serv3)
 +# В данном случае, Invoke-Command не умеет получить объекты через пайп как Enter-PSSession.
 +</code>
 +
 +Неявный доступ (implicit remoting) - импорт модулей и оснасток с удалённой машины. Удобно в случаях, когда нельзя поставить их на локальную тачку, например RSAT для 2008 R2 на Windows XP  и т.п.
 +<code powershell>
 +# установка соединения
 +$session = new-pssession -comp server-r2
 +# Загрузка нужного модуля
 +invoke-command -command { import-module activedirectory } -session $session
 +# Импорт команд модуля с добавлением префикса rem, чтобы не было путаницы (New-remADUser)
 +import-pssession -session $session -module activedirectory -prefix rem
 +# создаётся временный локальный модуль
 +</code>
 +Команды выполняются на удалённой машине и будут доступны до закрытия сессии или окна терминала. Минус - результат выполнения команд не будет объектами (deserialized).
 +
 +С PSv3 можно возобновлять закрытые сессии
 +<code powershell>
 +# Id Name ComputerName State
 +# 4 Session4 COMPUTER2 Disconnected
 +Get-PSSession -computerName COMPUTER2 | Connect-PSSession
 +</code>
 +Управлять параметрами сессий можно через GPO или WSMan drive (WSMan:\localhost\Shell, WSMan:\localhost\Service).
 +==== Скриптинг ====
 +С параметрами и документацией
 +<file powershell Get-DiskInventory.ps1>
 +<#
 +.SYNOPSIS
 +Get-DiskInventory retrieves logical disk information from one or
 +more computers.
 +.DESCRIPTION
 +Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk
 +instances from one or more computers. It displays each disk's
 +drive letter, free space, total size, and percentage of free
 +space.
 +.PARAMETER computername
 +The computer name, or names, to query. Default: Localhost.
 +.PARAMETER drivetype
 +The drive type to query. See Win32_LogicalDisk documentation
 +for values. 3 is a fixed disk, and is the default.
 +.EXAMPLE
 +Get-DiskInventory -computername SERVER-R2 -drivetype 3
 +#>
 +param (
 +$computername = 'localhost',
 +$drivetype = 3
 +)
 +gwmi Win32_LogicalDisk -comp $computername -filter "drivetype=3" |
 +Sort DeviceID |
 +ft DeviceID,@{n='FreeSpace(MB)';e={$_.FreeSpace / 1MB -as [int]}},
 +@{n='Size(GB)';e={$_.Size / 1GB -as [int]}},
 +@{n='%Free';e={$_.FreeSpace / $_.Size * 100 -as [int]}}
 +</file>
 +Значения параметров - значения по умолчанию.
 +<code powershell>
 +# Справка
 +help .\Get-DiskInventory -full
 +# почитать про справку
 +help about_comment_based_help
 +</code>
 +
 +Скрипт выполняется в одном потоке, тогда как последовательные команды - каждый в своём.
 +
 +Область (scope) - форма контейнера для хранения элементов PS. Сама консоль - global scope, верхний уровень. При запуске скрипта образуется script scope, являющийся дочерним (child) к global scope (parent). У функций есть свои private scopes, дочерние к скрипту. Все области существуют, пока запущен породивший их процесс. Если данные (например, переменная), не найдены в пределах области, PS смотрит на уровень выше, нет ли данных там.
 +==== Совершенствование задания параметров в скриптах ====
 +Сделать параметр Computername обязательным, с подсказкой при запросе и алиасом. Алиас позволяет при запуске указать тот же параметр другим именем. Также, проверяется параметр типа диска - принимается только 2 или 3.
 +
 +Добавлен более подробный вывод процесса выполнения в консоль с помощью Write-Verbose - для того, чтобы эти сообщения показывались, нужно запускать скрипт с параметром -Verbose.
 +<code powershell>
 +[CmdletBinding()]
 +param (
 +[Parameter(Mandatory=$True,HelpMessage="Enter a computer name to query")]
 +[Alias('hostname')]
 +[string]$computername,
 +[ValidateSet(2,3)]
 +[int]$drivetype = 3
 +)
 +Write-Verbose "Подключение к $computername"
 +Write-Verbose "Поиск дисков типа $drivetype"
 +gwmi Win32_LogicalDisk -comp $computername -filter "drivetype=3" |
 +Sort DeviceID |
 +ft DeviceID,@{n='FreeSpace(MB)';e={$_.FreeSpace / 1MB -as [int]}},
 +@{n='Size(GB)';e={$_.Size / 1GB -as [int]}},
 +@{n='%Free';e={$_.FreeSpace / $_.Size * 100 -as [int]}}
 +Write-Verbose "Операция успешно завершена"
 +</code>
 +  * Все параметры заключены в блок param()
 +  * Один параметр может иметь несколько декораторов (decorators), которые могут быть написаны одной строкой или разделены на несколько для удобства восприятия
 +  * Имена параметров разделяются запятыми, кроме последнего, после которого запятой не нужно
 +  * Между параметрами лучше оставлять пустую строку, чтобы легче было читать
 +  * Параметры задаются как переменные, но используются при запуске скрипта как параметры запуска
 +<code powershell>
 +# Прочесть про опции параметров:
 +help about_functions_advanced_parameters
 +</code>
 +Ещё пример
 +<code powershell>
 +[CmdletBinding()]
 +param(
 +[Parameter(Mandatory=$True)]
 +[Alias('hostname')]
 +$computername
 +)
 +Write-Verbose "Подключение к $computername"
 +gwmi win32_networkadapter -computername $computername |
 +where { $_.PhysicalAdapter } |
 +select MACAddress,AdapterType,DeviceID,Name,Speed
 +Write-Verbose "Завершено"
 +</code>
 +
 +==== Углублённая настройка удалённого управления ====
 +Иногда нужно указывать битность при использовании, например, удалённой 32-битной оснастки с 64-битной системы. Это одна из конфигураций сессии (endpoints, session configurations).
 +<code powershell>
 +# Список доступных конфигураций
 +Get-PSSessionConfiguration
 +# Подключение с указанием конфигурации
 +Enter-PSSession -ComputerName DONJONES1D96 -ConfigurationName 'Microsoft.PowerShell32'
 +</code>
 +
 +Создание конфигурации: New-PSSessionConfigurationFile создаёт файл конфигурации (.pssc), где перечислены всё характеристики. Register-PSSessionConfiguration считывает файл и создаёт endpoint, также можно задать разные параметры, например, права доступа, изменить эти параметры можно позже с помощью Set-PSSession-Configuration.
 +
 +Это можно делать, к примеру, для делегирования кому-то выполнения некоторых команд
 +<code powershell>
 +New-PSSessionConfigurationFile -Path C:\HelpDeskEndpoint.pssc -ModulesToImport NetAdapter `
 +-SessionType RestrictedRemoteServer -CompanyName "Our Company" -Author "Don Jones" `
 + -Description "Net adapter commands for use by help desk" -PowerShellVersion '3.0'
 +# -SessionType RestrictedRemoteServer - удаляет все базовые команды PS для этой сессии, за исключением необходимых
 +
 +# Запуск конфигурации, будет запрос пароля для HelpDeskProxyAdmin
 +# -ShowSecurityDescriptorUI выводит окно с запросом, каким пользователям давать права
 +Register-PSSessionConfiguration -Path .\HelpDeskEndpoint.pssc `
 +-RunAsCredential COMPANY\HelpDeskProxyAdmin -ShowSecurityDescriptorUI `
 +-Name HelpDesk
 +
 +# Использование созданной конфигурации
 +Enter-PSSession -ComputerName DONJONES1D96 -ConfigurationName HelpDesk
 +# Список доступных команд
 +Get-Command
 +</code>
 +
 +Многоступенчатый удалённый доступ (multihop remoting). Если попробовать создать сессию на 3-й компьютер с удалённого, то ничего не выйдет, т. к. удалённый компьютер не имеет права делегирования прав локальной машины далее. Начиная с Windows Vista, это можно переопределить.
 +<code powershell>
 +# На локальной машине (x - удалённый комп)
 +Enable-WSManCredSSP -Role Client -DelegateComputer x
 +# На удалённой машине
 +Enable-WSManCredSSP -Role Server
 +</code>
 +
 +При удалённом доступе PS проверяет подлинность в том числе удалённой машины для защиты от спуфинга. Если указывается IP-адрес или алиас, то стандартный метод проверки не работает - надо задействовать SSL или TrustedHosts. См. бесплатную книгу //Secrets of PowerShell Remoting// для более подробной информации.
 +==== Регулярные выражения (regex) для разбора текстовых файлов ====
 +В данном случае, нужны для описания текстовых шаблонов, чтобы удобно получать нужные данные из текста.
 +
 +В PS слово уже является regex, т. к. запросы нечувствительны к регистру, например, запросив Don, можно получить DON, don, Don, DoN и т. д.
 +  * \w - буква, цифра или подчёркивание, но не знак препинания и не пробел (\won = Don, Ron, ton...)
 +  * \W - наоборот
 +  * \d - цифра
 +  * \D - не цифра
 +  * \s - любой "белый" разделитель - TAB, пробел или Enter
 +  * \S - не \s
 +  * . - один символ
 +  * [abcde] - набор символов для сравнения, используется один (d[aeiou]n = don или dan, но не doun или deen)
 +  * [a-z] - диапазон символов для сравнения, можно указывать несколько: [a-f,m-z], используется больше одного символа
 +  * [^abcde] - набор символов НЕ для сравнения, используются несколько (d[^aeiou] = dns, но не don)
 +  * ? - обозначает один экземпляр предыдущего символа или его отсутствие. do?n = don, donn или dn, но не doon.
 +  * * - любое кол-во символов
 +  * + - любое кол-во предыдущего символа или группы символов: (dn)+o - dndndndno
 +  * \ - экранирующий символ, чтобы вывести \, надо написать \\
 +  * {2} - кол-во предыдущих символов. \d{1} - одна цифра, {2,} - два символа и более, {1,3} - от одного до трёх.
 +  * ^ - начало строки. ^d.n = donrrr, но не равно rrrdon
 +  * $ - конец строки.
 +
 +<code powershell>
 +# справка
 +help about_regular_expressions
 +
 +# примеры
 +"don" -match "d[aeiou]n"
 +True
 +"dooon" -match "d[aeiou]n"
 +False
 +"dooon" -match "d[aeiou]+n"
 +True
 +"djinn" -match "d[aeiou]+n"
 +False
 +"dean" -match "d[aeiou]n"
 +False
 +"(Windows+NT+6.2;+WOW64;+rv:11.0)+Gecko" -match "6\.2;[\w\W]+\+Gecko"
 +True
 +# 6\.2; — This is 6.2; and notice that we escaped the period to make it a literal character
 +# rather than the single-character wildcard that a period normally indicates.
 +# [\w\W]+ — This is one or more word or nonword characters—in other words, anything.
 +# \+Gecko — This is a literal +, then Gecko.
 +
 +C:\logfiles> get-childitem -filter *.log -recurse |
 +select-string -pattern "\s40[0-9]\s" |
 +ft Filename,LineNumber,Line -wrap
 +
 +# имена с двумя цифрами
 +dir $env:windir |? Name -match "\d{2}"
 +
 +# выбрать из кэша записи с IP-адресами
 +Get-DNSClientCache |? Data -match "\d{1,}.\d{1,}"
 +
 +</code>
 +
 +[[https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.core/about/about_regular_expressions|about_Regular_Expressions]]
 +==== Разные прочие техники и трюки ====
 +Можно задать профиль для PS - настройки при каждом запуске, например
 +<code powershell>
 +Import-Module ActiveDirectory
 +Add-PSSnapin SqlServerCmdletSnapin100
 +cd c:\
 +
 +# справка по профилям
 +help about_profiles
 +</code>
 +Профиль относится к приложению PS (hosting application - консоль, ISE), не движку. Размещение файла профиля в порядке чтения при запуске:
 +  - $pshome\profile.ps1 - для всех пользователей и всех приложений
 +  - $pshome\Microsoft.PowerShell_profile.ps1 - для консоли, $pshome/Microsoft.PowerShellISE_profile.ps1 - для ISE.
 +  - $home\Documents\WindowsPowerShell\profile.ps1 - для данного пользователя и всех приложений
 +  - $home\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 - консоль, $home\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1 - ISE.
 +
 +"Все приложения" - консоль и ISE, но не сторонние приложения. Также на 64-битных системах может иметь значение задание профилей для 32- и 64-битных консолей и ISE, т. к. не все модули и оснастки есть для обоих вариантов. Профили - это те же скрипты, т. е., например, если execution policy Restricted, то профиль просто не запустится.
 +
 +=== Операторы ===
 +<code powershell>
 +# -as - создание объекта с другим типом
 +1000 / 3 -as [int]
 +# -is - проверяет объект и возвращает True или False
 +123.45 -is [int] # false
 +"SERVER-R2" -is [string] # true
 +$True -is [bool] # true
 +(Get-Date) -is [datetime] # true
 +# -replace
 +"192.168.34.12" -replace "34","15"
 +192.168.15.12
 +# -join
 +$array = "one","two","three","four","five"
 +PS C:\> $array
 +one
 +two
 +three
 +four
 +five
 +$array -join "|"
 +one|two|three|four|five
 +# -split
 +(gc computers.tdf) -split "`t" |set-variable array # Server1 Windows East Managed (`t - TAB)
 +Server1
 +Windows
 +East
 +Managed
 +$array[0]
 +Server1
 +# -contains
 +'this' -contains '*his*'
 +False
 +'this' -like '*his*'
 +True
 +
 +$collection = 'abc','def','ghi','jkl'
 +$collection -contains 'abc'
 +True
 +$collection -contains 'xyz'
 +False
 +
 +# -in - возвращает true or false
 +$collection = 'abc','def','ghi','jkl'
 +'abc' -in $collection
 +True
 +'xyz' -in $collection
 +False
 +</code>
 +
 +=== Манипуляции со строками ===
 +<code powershell>
 +"Hello" | gm
 +
 +# метод IndexOf()
 +"SERVER-R2".IndexOf("-")
 +6
 +
 +# Split(), Join(), and Replace() operate similarly to the -split, -join, and -replace operators
 +# ToLower() and ToUpper()
 +"HeLLo".tolower()
 +hello
 +# trim ()
 +     HeLLo".trim()
 +HeLLo
 +
 +
 +
 +</code>
 +
 +=== Манипуляции с датой ===
 +<code powershell>
 +get-date | gm
 +(get-date).month
 +(get-date).adddays(-90)
 +(get-date).ToShortDateString()
 +28.05.2019
 +</code>
 +
 +=== Даты WMI ===
 +<code powershell>
 +# У WMI нечеловеческий формат дат
 +get-wmiobject win32_operatingsystem | select lastbootuptime
 +lastbootuptime
 +--------------
 +20190528095517.490356+180
 +
 +get-wmiobject win32_operatingsystem |gm
 +# есть методы converttodatetime и convertfromdatetime
 +$i = get-wmiobject win32_operatingsystem
 +$i.converttodatetime($i.lastbootuptime)
 +28 мая 2019 г. 9:55:17
 +
 +get-wmiobject win32_operatingsystem | select BuildNumber,__SERVER,
 +@{l='LastBootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}}
 +
 +# Команды CIM сразу выдают нормальный формат
 +gcim win32_operatingsystem | select lastbootuptime
 +lastbootuptime
 +--------------
 +28.05.2019 9:55:17
 +</code>
 +
 +=== Задание стандартных значений параметров ===
 +Например, у dir стандартный параметр -Path - текущая папка.\\
 +Умолчания хранятся в спец. переменной //$PSDefaultParameterValues,// которая пуста при запуске консоли.
 +
 +Положим, нужно задать имя пользователя по умолчанию для запросов учётных данных
 +<code powershell>
 +$credential = Get-Credential -UserName Administrator -Message "Enter Admin credential"
 +$PSDefaultParameterValues.Add('*:Credential',$credential)
 +# сделать это только для Invoke-Command
 +$PSDefaultParameterValues.Add('Invoke-Command:Credential',
 +{Get-Credential -Message 'Enter administrator credential' -UserName Administrator})
 +# используется метод add()? 1-й аргумент - команда, 2-й - параметр. * - применить ко всем.
 +# посмотреть список стандартных параметров
 +$PSDefaultParameterValues
 +# справка
 +help about_parameters_default_values
 +</code>
 +$PSDefaultParameterValues, как и другие переменные, действует в рамках области (scope), т. е., в контексте сущности, где переменная появилась. (help about_scope)
 +
 +=== Scriptblocks ===
 +Скриптблок - то, что заключено в {}, кроме хэш-таблиц @{}
 +  * where -filterscript даёт скриптблок
 +  * foreach -process даёт скриптблок
 +  * Хэш-таблицы, которые используются при выборе свойств в select или format-table, принимают скриптблоки в качестве значения e= (expression).
 +  * Некоторые команды принимают скриптблоки в качестве параметра: Invoke-Command -ScriptBlock {} или Start-Job -ScriptBlock {}.
 +
 +Чтобы вызвать скриптблок, нужно вначале ставить & (call).
 +<code powershell>
 +$block = {get-process | sort -Property vm -Descending | select -first 10}
 +&$block
 +# справка
 +help about_script_blocks
 +</code>
 +
 +
 +
 +==== Памятка ====
 +При использовании чужих скриптов необходимо полностью понимать, что происходит в каждой строке, нельзя оставлять работать непонятные куски кода.
 +  * Распознать переменные, что они содержат, это поможет понять, что делает команда
 +  * Читать справку к новым командам
 +  * Использовать тестовую среду, например, VM.
 +
 +=== Пунктуация ===
 +` (обратная кавычка) - экранирует последующий символ. Например, можно написать:
 +<code powershell>
 +cd c:\Program` Files
 +</code>
 +~ - папка профиля
 +
 +( ) - как в арифметике, приоритет выполнения.
 +<code powershell>
 +Get-Service -computerName (Get-Content c:\computernames.txt)
 +</code>
 +Если скобки - это параметры метода, то они ставятся в любом случае, даже если внутри ничего нет.
 +
 +[ ] - индекс объекта в массиве, отсчёт с нуля. $services[2] - третий объект. Также, тип объекта - [string], [int] и т. д.
 +
 +{ } - скриптблок или фильтр
 +<code powershell>
 +Get-Service | Where-Object { $_.Status -eq 'Running' }
 +# ключ=значение в хэш-таблицах
 +$hashtable = @{l='Label';e={expression}}
 +# как вариант написания переменной с пробелами и другими нестандартными символами
 +${My Variable}
 +</code>
 +
 +' ' - буквальное прочтение содержимого, переменные не читаются.
 +
 +%%" "%% - переменные, escape characters читаются.
 +<code powershell>
 +$two = "Hello $one `n"
 +</code>
 +
 +$ - знак переменной
 +
 +% - foreach. It’s also the modulus operator, returning the remainder from a division operation.
 +
 +? - where
 +
 +%%>%% - направление вывода (Out-File)
 +
 ++, -, /, % - математические операторы. "+" ещё используется как соединитель строк.
 +
 +- - указывает на параметр или оператор (-computerName, -eq)
 +
 +@ - указатель на:
 +  - хэш-таблицу @{}
 +  - массив $array = @(1,2,3,4). можно обойтись просто 1,2,3,4, т.к. PS и так воспринимает список с разделителями-запятыми как массив.
 +  - here-string - %%@" "@%% - блок с буквально читаемой строкой, также могут быть описаны как ' '. help about_quoting_rules
 +  - [[https://docs.microsoft.com/en-us/previous-versions/technet-magazine/gg675931(v=msdn.10)|splat operator]])
 +
 +& - вызов (invocation operator), типа call в CMD
 +
 +; - разделитель двух команд, которые пишутся в строку.
 +
 +# - комментарий, <# #> - закомментировать много строк.
 +
 += - оператор присвоения. В сравнениях не используется, есть оператор -eq. Также используется с мат. операторами: $var +=5 прибавляет 5 к $var.
 +
 +| - передача потока данных от одной команде другой.
 +
 +/ или \ - прямой - это деление, обратный - экранирующий символ в фильтре WMI и regex. Оба используются в путях.
 +
 +. - вызов метода или свойства ($_.Status), путь к текущему каталогу, две точки - вышестоящий каталог.
 +
 +, - вне кавычек - разделитель членов массива или значений для свойства команды (Get-Process -computername Server1,Server2,Server3).
 +
 +: - (технически, ::) - доступ к статическим членам класса, например, .NET.
 +
 +! - то же, что -not
 +
 +=== В справке ===
 +''[ ]'' - необязательный параметр ''%%[-Name <string>]%%'', или parameter is positional ''%%[-Name] <string>%%'', или и то и другое ''%%[[-Name] <string>]%%''. Также, указатель на то, что параметр может принимать множественные значения ''%%<string[]>%%''.
 +
 +%%< >%% - типы данных (<string>, <int>, <process> и т. д.)
 +
 +=== Операторы ===
 +  * -eq - равенство (-ceq - чувств. к регистру)
 +  * -ne - неравенство (-cne - чувств. к регистру)
 +  * -ge - больше или равно
 +  * -le - меньше или равно
 +  * -gt - больше, чем
 +  * -lt - меньше, чем
 +  * -contains - содержит объект ($collection -contains $object), возврат true or false, -notcontains - наоборот
 +  * -in - объект содержится в ($object -in $collection), возврат true or false, -notin - наоборот
 +
 +Логические:
 +  * -not (или !) - true or false
 +  * -and - и
 +  * -or - или
 +
 +Функциональные:
 +  * -join - объединяет строки в массив
 +  * -split - разъединяет массив в строки
 +  * -replace - замена одной строки на другую
 +  * -is - true, если объект такого типа ($one -is [int])
 +  * -as - назначение типа объекта ($one -as [int])
 +  * .. - диапазон (1..10)
 +  * -f - format operator, замена значений в массиве %%"{0}, {1}" -f "Hello","World"%%
 +
 +Передача параметров через пайп делается двумя способами: сначала используется ByValue, когда PS смотрит на тип передаваемых данных и кто с другой стороны с этими данными способен работать, а второй - ByPropertyName, когда PS ищет одноимённые параметры, чтобы отдать данные туда.
 +
 +$_ используется в скриптблоках после пайпа
 +<code powershell>
 +Get-Service |? {$_.Status -eq 'Running'}
 +gwmi -class Win32_Service -filter "name='mssqlserver'" |
 +foreach -process { $_.ChangeStartMode('Automatic') }
 +</code>
 +В любом случае, $_ окружён { }.
 +
 +===== PowerShell in Depth =====
 +==== Custom Object ====
 +Вариант 1:
 +<code powershell>
 +$os = Get-WmiObject –Class Win32_OperatingSystem –comp localhost
 +$cs = Get-WmiObject –Class Win32_ComputerSystem –comp localhost
 +$bios = Get-WmiObject –Class Win32_BIOS –comp localhost
 +$proc = Get-WmiObject –Class Win32_Processor –comp localhost |
 +Select –First 1
 +$props = @{OSVersion=$os.version
 +           Model=$cs.model
 +           Manufacturer=$cs.manufacturer
 +           BIOSSerial=$bios.serialnumber
 +           ComputerName=$os.CSName
 +           OSArchitecture=$os.osarchitecture
 +           ProcArchitecture=$proc.addresswidth}
 +$obj = New-Object –TypeName PSObject –Property $props
 +Write-Output $obj
 +</code>
 +
 +В PS v3 и новее можно задать порядок свойств:
 +<code powershell>
 +$props = [ordered]@{ OSVersion=$os.version
 +           Model=$cs.model
 +           Manufacturer=$cs.manufacturer
 +           BIOSSerial=$bios.serialnumber
 +           ComputerName=$os.CSName
 +           OSArchitecture=$os.osarchitecture
 +           ProcArchitecture=$proc.addresswidth}
 +</code>
 +
 +Вариант 2 (не рекомендуется, т. к. тип объекта будет неправильным):
 +<code powershell>
 +$obj = "" | Select-Object ComputerName,OSVersion,OSArchitecture,
 +           ProcArchitecture,Model,Manufacturer,BIOSSerial
 +$obj.ComputerName = $os.CSName
 +$obj.OSVersion = $os.version
 +$obj.OSArchitecture = $os.osarchitecture
 +$obj.ProcArchitecture = $proc.addresswidth
 +$obj.BIOSSerial = $bios.serialnumber
 +$obj.Model = $cs.model
 +$obj.Manufacturer = $cs.manufacturer
 +</code>
 +
 +Вариант 3:
 +<code powershell>
 +$obj = New-Object –TypeName PSObject
 +$obj | Add-Member NoteProperty ComputerName $os.CSName
 +$obj | Add-Member NoteProperty OSVersion $os.version
 +$obj | Add-Member NoteProperty OSArchitecture $os.osarchitecture
 +$obj | Add-Member NoteProperty ProcArchitecture $proc.addresswidth
 +$obj | Add-Member NoteProperty BIOSSerial $bios.serialnumber
 +$obj | Add-Member NoteProperty Model $cs.model
 +$obj | Add-Member NoteProperty Manufacturer $cs.manufacturer
 +</code>
 +
 +или так:
 +<code powershell>
 +$obj | Add-Member NoteProperty ComputerName $os.CSName –pass |
 + Add-Member NoteProperty OSVersion $os.version –pass |
 + Add-Member NoteProperty OSArchitecture $os.osarchitecture –Pass |
 + Add-Member NoteProperty ProcArchitecture $proc.addresswidth –pass |
 + Add-Member NoteProperty BIOSSerial $bios.serialnumber –pass |
 + Add-Member NoteProperty Model $cs.model –pass |
 + Add-Member NoteProperty Manufacturer $cs.manufacturer
 +</code>
 +
 +Вариант 4 (для PS v3 или новее):
 +<code powershell>
 +$obj = [pscustomobject]@{OSVersion=$os.version
 +           Model=$cs.model
 +           Manufacturer=$cs.manufacturer
 +           BIOSSerial=$bios.serialnumber
 +           ComputerName=$os.CSName
 +           OSArchitecture=$os.osarchitecture
 +           ProcArchitecture=$proc.addresswidth
 +          }
 +</code>
 +Свойства получаются сразу упорядоченными.
 +
 +Вариант 5 - создать новый класс:
 +<code powershell>
 +$source=@"
 +public class MyObject
 +{
 +  public string ComputerName {get; set;}
 +  public string Model {get; set;}
 +  public string Manufacturer {get; set;}
 +  public string BIOSSerial {get; set;}
 +  public string OSArchitecture {get; set;}
 +  public string OSVersion {get; set;}
 +  public string ProcArchitecture {get; set;}
 +}
 +"@
 +Add-Type -TypeDefinition $source -Language CSharpversion3
 +$os = Get-WmiObject –Class Win32_OperatingSystem –comp localhost
 +$cs = Get-WmiObject –Class Win32_ComputerSystem –comp localhost
 +$bios = Get-WmiObject –Class Win32_BIOS –comp localhost
 +$proc = Get-WmiObject –Class Win32_Processor –comp localhost |
 +Select –First 1
 +$props = @{OSVersion=$os.version
 +           Model=$cs.model
 +           Manufacturer=$cs.manufacturer
 +           BIOSSerial=$bios.serialnumber
 +           ComputerName=$os.CSName
 +           OSArchitecture=$os.osarchitecture
 +           ProcArchitecture=$proc.addresswidth}
 +$obj = New-Object –TypeName MyObject –Property $props
 +Write-Output $obj
 +</code>
 +
 +По быстродействию лучше всего варианты 4 и 1.
 +
 +===== Литература =====
 +https://twitter.com/jeffhicks\\
 +https://twitter.com/concentratedDon\\
 +http://powershell.com\\
 +https://powershell.org\\
 +http://youtube.com/powershellorg\\
 +http://jdhitsolutions.com\\
 +http://donjones.com\\
 +http://devopscollective.org\\
 +[[https://learn.microsoft.com/ru-ru/powershell/scripting/learn/deep-dives/everything-about-arrays|Всё, что вы хотите знать о...]] - интересная и познавательная серия статей на learn.microsoft.com\\
 +[[https://powershellexplained.com/|PowerShell Explained with Kevin Marquette]]
 +
 +Книги:\\
 +Learn PowerShell Toolmaking in a Month of Lunches\\
 +PowerShell in Depth
 +
 +https://goalkicker.com/PowerShellBook/
 +
  

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki