Содержание
Курс в "Специалисте"
get-service | get-member # или get-service | gm
7. Форматирование результатов
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
8. Начало и конец конвейера
Конец - обычно выгрузка куда-то, начало - запрос или импорт.
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
9. Привязка параметров в конвейере
Одно и то же: dir c:\windows | format-wide Format-Wide -InputObject (dir c:\windows)
Круглые скобки нужны для добавления результатов другого конвейера команде, у которой несколько параметров могут принимать данные по конвейеру.
10. Переменные
Жизненный цикл переменной: объявление, инициализация (запись), использование.
get-command -noun variable # Есть диск с именем Variable: Get-PSDrive # список переменных dir variable:
Свойства вызываются через точку, методы - добавляются скобки
$test1.length $test1.gettype() $test1.Substring(2, 2)
# Менять тип переменной можно на ходу, но можно задать тип жёстко: [int]$test5 = 100 # можно записать туда дробное число, $test5 = 100.25 # но значение всё равно будет целочисленным, с округлением. # Строку и т. п. записать туда не получится.
Вывести количество символов имени самого большого файла в c:\Windows
$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)
Если в массиве изменить какие-то данные на другой тип, то тип данных всего массива превратится в строку (в 5-й версии powershell - нет).
$test1[2] = "word" $s = get-service $s[5].status.tostring().length
11. Программные конструкции
Цикл.
С предусловием - выполнять цикл, пока условие верно:
$i=8 while ($i -gt 0) { $i; # Полезная нагрузка $i = $i - 1; }
С проверкой в конце цикла, а не в начале. Цикл в любом случае выполняется один раз:
$i=8 Do { $i; $i = $i - 1; } while ($i -gt 0) # условие продолжения цикла # если условие верно, цикл продолжается
Выполнять цикл, пока не наступит условие:
$i=8 Do { $i; $i = $i + 10; } until ($i -gt 1000) # условие окончания цикла # если условие верно, цикл останавливается
Цикл FOR - все условия задаются вне самого цикла, внутри - только полезная нагрузка.
for ( $i=8; # выполняется один раз при входе в цикл $i -GT 0; # условие продолжения цикла $i = $i - 1 # выполняется после каждого повторения ) { $i; }
Перебор (foreach) - для перебора существующего массива.
Чтобы не городить конструкцию типа
$srv=get-service for ( $i=0; # выполняется один раз при входе в цикл $i -LT $srv.count; # условие продолжения цикла $i = $i + 1 # выполняется после каждого повторения ) { $srv[$i].Name; }
пишем
$srv = get-service foreach ($s in $srv) { $s.Name }
Условие (IF).
Простой случай:
$t = 8 if ($t -gt 3) { Write-Host "Больше трёх" }
Выполнить что-то другое, если условие не выполнено (if-else):
cls $t = 2 if ($t -gt 3) { Write-Host "Больше трёх" } else { Write-Host "Не больше трёх" }
Несколько условий (if-elseif-else)
$t = 1 if ($t -gt 3) { Write-Host "Больше трёх" } elseif ($t -eq 3) { Write-Host "Равно трём" } elseif ($t -eq 2) { Write-Host "Равно двум" } else { Write-Host "Меньше двух" }
Найти каталог на C:\, в котором больше всего вложенных каталогов.
$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
13. Запуск сценария с параметрами
Вместо задания переменной в коде вынести её в начало скрипта таким образом:
Param( [string]$folder )
# После этого можно передавать путь как параметр к скрипту. .\script.ps1 C:\ # Также, табом можно перебирать эти параметры, если их несколько: .\script.ps1 -folder C:\ -extension exe
Обязательные параметры:
# Перед блоком Param() написать [CmdletBinding()] # Также, перед параметром поставить [Parameter(Mandatory=$True)]
Для необязательных параметров можно задать значение по умолчанию:
[string]$extension = "*"
Для заданной папки создать в каждой подпапке вложенную папку с названием «Специалист»:
param( $parent = "" ) # Создать gci $parent -Directory | foreach {New-item -path ($_).FullName -Name Специалист -ItemType Directory} # А можно вызывать метод напрямую: gci $parent -Directory | foreach CreateSubDirectory Специалист
15. Обработка ошибок
try { code } catch { errors handling }
Try и Catch должны идти последовательно друг за другом. Если в try всё в порядке, catch пропускается, если ошибка - try прерывается и переходит в catch.
# Удалить 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 }
16. Удалённое исполнение команд
На удалённой машине должен работать сервис WinRM.
Для настройки удалённого доступа выполнить команду winrm quickconfig на целевой машине.
Удалённый запуск команды:
Будут работать команды, где есть параметр -ComputerName, например,
Get-Service -ComputerName DC1,DC2,Serv3 # Запуск оболочки на удалённом компьютере: Enter-PSSession DC1 Exit-PSSession # Для одновременного запуска на большом кол-ве машин: Invoke-Command -ComputerName DC1,DC2,Serv3 -ScriptBlock {Get-Service; Get-Process}
17. Фоновое выполнение команд
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
18. Планировщик задач
get-command -noun scheduledjob
19. Функции, модули
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"
Из скрипта с функциями можно сделать модуль - расширение .psm1. Там нужно оставить только функции.
Каталоги с модулями PS по умолчанию ($env:PSModulePath):
$env:userprofile\Documents\WindowsPowerShell\Modules # для каждого юзера
В ней создать папку с именем модуля, и скопировать туда модуль С ТЕМ ЖЕ ИМЕНЕМ, что и папка. Для каждого модуля - своя папка, т. е.,
$env:userprofile\Documents\WindowsPowerShell\Modules\modulename\modulename.psm1 import-module -listavailable import-module modulename
Далее можно вызывать функции в модуле как стандартные командлеты, будет работать также
get-command -noun myinfo
Добавить справку в модуль: после Function get-myinfo {
вставить спецкомментарий:
<#
.SYNOPSIS
Краткое описание
.DESCRIPTION
Подробное описание
.PARAMETER filename
Описание параметра
.PARAMETER text
Описание параметра
.EXAMPLE
примеры
#>
Запуск функции удалённо
# 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
21. AD
get-command -module activedirectory dir ad: # список разделов get-item "cn=user,dc=domain,dc=com" | get-member
080 Параметр Verbose
Чтобы не городить огород с логическими операторами и циклами типа
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
имеется встроенный параметр -Verbose с подсветкой вывода, т.е. параметр $Protocol можно вообще убрать.
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
Параметр Verbose сквозной, т. е. если он был вызван, то все дочерние функции также будут вызываться с этим параметром.
090 Синонимы для параметров
Синоним (алиас) задаётся непосредственно перед каждым целевым параметром.
Function mynewfunc { [cmdletbinding()] Param( [Alias('str', 'ms')] [string]$MyString ) Write-Host $MyString } mynewfunc -str "Test string" mynewfunc -ms "Test string"
100 Проверка параметров
Function mynewfunc { [cmdletbinding()] Param( [Alias('str', 'ms')] [ValidateLength(3, 5)] [string]$MyString ) Write-Host $MyString } mynewfunc -ms "Test string" # вызовет ошибку mynewfunc -ms "Test" # сработает
# Валидатор для числовых параметров: [ValidateRange(3, 5)] # Список вариантов: [ValidateSet('Create', 'Delete', 'Modify')]
Сделать свой валидатор:
[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" # сработает
110. Приём списков в качестве параметров
Function mylistfunc { [cmdletbinding()] Param( [string[]]$FolderList ) foreach ($Folder in $FolderList) { dir $Folder -Directory | measure } } mylistfunc -folderlist "C:\Windows\System32", "C:\Users", "C:\Program Files"
Если указать, что принимается массив строк [string[]] без цикла foreach, будет подсчитано общее количество.
120. Приём параметров из конвейера
Function mylistfunc { [cmdletbinding()] Param( [parameter(ValueFromPipeline=$True)] [string[]]$FolderList ) BEGIN {} PROCESS { foreach ($Folder in $FolderList) { dir $Folder -Directory | measure } } END {} } gc .\folderslist.txt | mylistfunc
Если просто добавить [parameter(ValueFromPipeline=$True)]
без конструкции BEGIN-PROCESS-END и цикла, то будет выведено только последнее значение из конвейера. Дело в том, что процесс передачи по конвейеру разбит на эти три этапа, где перечисление собственно данных из конвейера выполняется в блоке PROCESS. Блоки BEGIN и END выполняются один раз, а PROCESS - столько раз, сколько строк в конвейере.
130. Создание объектов
PSObject - универсальный тип объекта
function MyFunc1 { $Properties = @{'Name' = "MyName"; 'Size' = 10; 'Color' = "Yellow"} New-Object -TypeName PSObject -Property $Properties } cls MyFunc1
140. Работа с объектами .NET
Можно использовать классы .NET, если в PS нет соотв. команды, или по каким-то другим причинам.
$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
150. Инструменты и управляющие сценарии
При усложнении сценария целесообразно разбивать его на части. Рекомендуется делить программу на две группы - инструменты и управляющие сценарии. Инструменты решают конкретную задачу, например, заведение пользователя в AD. Инструменты могут собирать данные (Get-, Import-, ConvertFrom-), выполнять действия (Send-, New-, Set-, Remove-, Save-), выводить результат (Export-, ConvertTo-, Format- Out-). Управляющие сценарии решают более сложные, бизнес-задачи, вызывая различные инструменты. Также в них делается интерфейс общения с пользователем, который должен выбирать из каких-то пунктов и т. п.
160. Директива Requires
Проверка необходимых компонентов и условий для работы скрипта. Оформляется как комментарий.
#Requires -Version 2.0 #Requires -Module ActiveDirectory #Requires -RunAsAdministrator Get-Service
170. Взаимодействие с пользователем
write-host # выводит информацию на экран read-host # захватывает ввод пользователя clear-host # очистка экрана (cls)
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" }
180. Организация работы с главным меню
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)
190. Генерация отчётов в HTML
У ConvertTo-Html есть параметр -Fragment, который генерирует только содержимое без открывающих и закрывающих тэгов. Таким образом, с помощью Out-File -Append можно сначала добавить тэги, затем нужную информацию (можно несколько запросов сгенерировать) добавить в файл, затем закрывающие тэги. При добавлении запрошенной информации в переменные полезно использовать команду Out-String, чтобы данные были одной строкой, а не массивом.
200. Обработка нештатных ситуаций
2 типа ошибок - останавливающая, при которой обработка прерывается, и неостанавливающая, когда валятся ошибки, но обработка продолжается. Имеется системная переменная $ErrorActionPreference со значением Continue по умолчанию, можно поменять её значение на
$ErrorActionPreference = "SilentlyContinue" # ошибки выводиться не будут, но это плохая практика. $ErrorActionPreference = "Inquire" # спрашивать пользователя, что делать дальше. $ErrorActionPreference = "Stop" # остановка обработки, все неостанавливающие ошибки превращаются в останавливающие. # Также, это позволяет перехватывать ошибки.
Ключ -ErrorAction у каждого командлета позволяет задавать поведение для конкретного действия, а не глобально.
Этого переключателя нет для методов!
Для обработки ошибок используется конструкция
try { code } catch { errors handling } finally { code }
Try и Catch должны идти последовательно друг за другом. Если в try всё в порядке, catch пропускается, если ошибка - try прерывается и переходит в catch. Finally выполняется в любом случае после Catch.
try { dir C:\Windows\System32\*.exe -Recurse -ErrorAction Stop } catch { Write-Warning "Ошибка!" } finally { Write-Host "Конец" }
Без указания -ErrorAction Stop блок catch выполняться не будет.
Неплохо бы знать также, что за ошибка произошла и в каком месте.
try { dir C:\Windows\System32\*.exe -Recurse -ErrorAction Stop -ErrorVariable $err1} catch { Write-Warning "Ошибка!" $_ | gm # переменная с ошибкой, можно использовать методы для получения информации $Error[0] # массив, куда пишутся ошибки. 0 - последняя } finally { Write-Host "Конец" }
-ErrorVariable может создавать переменную ошибки для каждой команды, что удобно, т. к. в блоке catch{} можно управлять разными ошибками разных команд.
210. Работа с XML
XML удобен тем, что хранит и данные, и структуру, как БД.
<> # элемент, внутри может быть атрибут, типа <lan> <computer name="comp1">Компьютер</computer> <addr ip="192.168.0.10" mask="255.255.255.0" /> </lan>
Атрибут чаще используется для неповторяющегося свойства, а элементы - для повторяющегося.
Getting Started with Microsoft PowerShell
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
Get-Alias *sv # список псевдонимов, заканчивающихся на sv Get-Alias -Definition get-process # показать псевдонимы для get-process # работает как в линуксе cd / cd ~
http://video.ch9.ms/ch9/eedc/a09df7fe-b46c-4d6e-b6f6-17e7980deedc/GetStartedPowerShell3M01_high.mp4
The help system
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
Синтаксис:
Get-Service [[-Name] <String[]>] [-ComputerName <String[]>] [-DependentServices] [-Exclude <String[]>] [-Include <String[]>] [-RequiredServices] [<CommonParameters>] # Всё, что начинается с дефиса (-Name) - параметр # <> - значение (аргумент) # [] вокруг параметров и аргументов - это опциональный параметр # [] вокруг самого параметра - можно писать сразу аргументы к командлету, параметр будет подразумеваться, например, Get-Service bits # или gsv bits # [] внутри <> - значение может быть множественным (разделённым запятыми)
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 # вывести список статей по категории командлетов
; - разделитель типа && в линуксе (сделай это и потом сделай то)
http://video.ch9.ms/ch9/96dc/5e431f9c-e65f-4ac3-9339-4a67989496dc/GetStartedPowerShell3M02_high.mp4
The pipeline: getting connected & extending the shell
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 # уже загруженные модули
Objects for the Admin
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
http://video.ch9.ms/ch9/74d2/b70e7c0a-96a7-4067-80cb-ce8352c774d2/GetStartedPowerShell3M04_high.mp4
The pipeline: deeper
Что передаётся через пайп (конвейер): ByValue, ByPropertyName.
# посмотреть, какие параметры принимают данные конвейера 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, который умеет принимать параметры по конвейеру
http://video.ch9.ms/ch9/b233/9ddae023-a85e-4e72-bcf3-00f143fab233/GetStartedPowerShell3M05_high.mp4
The PowerShell in the shell: remoting
Как подключиться
Enter-PSSession server1 [Server1]: PS c:\Users\Administrator.SERVER1\Documents>
Включить возможность удалённого подключения
Enable-PSRemoting
Включить в политике:
Computer configuration/Policies/Administrative templates/Windows Components/Windows remote management
В Windows 2012 это включено по умолчанию.
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
Установить веб-доступ к powershell на удалённой машине
install-windowsfeature WindowsPowershellWebAccess get-help *pswa* install-pswawebapplication add-pswaauthorizationrule -computergroupname # какая группа машин имеет доступ add-pswaauthorizationrule -usergroupname # какая группа пользователей имеет доступ
Getting prepared for automation
AllSigned - абсолютно все скрипты должны быть подписаны
RemoteSigned - должны быть подписаны все скрипты, кроме созданных локально (значение по умолчанию с Windows Server 2012 R2)
# сделать самоподписанный сертификат New-SelfSignedCertificate # вывести все диски (в т. ч. поставщиков Powershell) get-psdrive # показать все сертификаты подписи кода и создать переменную $a dir Cert:\Currentuser -Recurse -CodeSigningCert -outvariable a # тут не понял $cert = $a[0] # посмотреть текущую политику выполнения get-executionpolicy # изменить её на AllSigned set-executionpolicy "allsigned"
После этого обычный скрипт (test.ps1) не выполнится
# Подписать скрипт test.ps1 Set-AuthenticodeSignature -Certificate $cert -Filepath .\Test.ps1
Посде подписи в конец скрипта добавляется цифровая подпись, и при запуске он выполняется, но спрашивает, выполнять ли скрипт от недоверенного издателя, в т.ч. есть вариант ответа «всегда доверять».
Переменные
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 # не сработает
Write-host ничего не передаёт и вообще его использование не рекомендуется, нужно использовать write-output:
write-output $var | get-member # а вот это сработает
Цвет текста в консоли
# предупреждение write-warning "Стоять близко к краю платформы опасно" write-error "Стоять близко к краю платформы опасно"
Тем не менее, следует не увлекаться тем, что показывается в консоли, т. к. подобные вещи не относятся к автоматизации.
Переменные можно задавать любые, в том числе, с пробелами и неанглийские, обрамляя их фигурными скобками, и они будут работать.
${привет, как дела?} = 555
Если задать переменной путь к файлу, то он вернёт содержимое
1..5 > C:\temp\test.txt ${C:\temp\test.txt) 1 2 3 4 5
Но если задать переменной значение, то будет возвращаться именно оно, причём, оно также будет отображаться и в другом экземпляре консоли
${C:\temp\test.txt) = "Переменная №1"
http://video.ch9.ms/ch9/0922/1307856c-4ef2-4bda-9914-045f4a390922/GetStartedPowerShell3M07_high.mp4
Automation in scale: remoting
Особенность запуска удалённого кода в том, что после того, как он отработает, выгружается всё, что требуется для исполнения этого кода - и консоль, и профиль, и всё остальное. В связи с этим есть особенности написания кода для удалённого выполнения.
Это не сработает, так как между сессиями переменная не передаётся:
icm -comp dc {$var=2} icm -comp dc {write-output $var}
Правильный вариант - обращаться в ту же сессию, что и ранее
$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}}
# Взять два сервера $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}
Удалённые сессии полезны ещё и потому, что не нужно ставить доп. компонентов типа Exchange management shell локально, потому что можно подключиться непосредственно к серверу, где эти командлеты имеются изначально.
$s=new-pssession -computername dc import-pssession -session $s -module activedirectory -prefix remote
После этого командлеты AD становятся доступны на локальной машине, и можно выполнять запросы типа
get-remoteadcomputer -filter *
Но по факту всё это выполняется удалённо, под теми учётными данными, под которыми была создана удалённая сессия.
Просмотр параметров командлетов
$c = get-command get-process $c.parameters $c.parameters["Name"]
Если посмотреть определение удалённого командлета, то окажется, что это не сам по себе командлет, а его динамически генерируемый код, который вызывает выполнение удалённой функции.
(get-command get-remoteadcomputer).definition
Присвоение префикса командлету
$s = nsn import-session $s -commandname get-process -prefix wow get-wowprocess
http://video.ch9.ms/ch9/7b29/933b0f5e-ca54-45fe-849b-4e4b07127b29/GetStartedPowerShell3M08_high.mp4
Introducing scripting and toolmaking
Если нажать в Powershell ISE сочетание Ctrl+Space, то это может показать классы WMI.
Get-CimInstance Wind32_Logical#Ctrl+Space
После пайпа можно нажать Enter, и в интерактивном режиме команду можно продолжать набирать.
Get-WmiObject win32_logicaldisk -filter "DeviceID='c:'" | >> Select @{n='freegb';e={$_.freespace / 1gb -as [int]}}
Задать параметр:
param( $Computername='localhost', $bogus ) Get-WmiObject -computername $Computername win32_logicaldisk -filter "DeviceID='c:'" | Select @{n='freegb';e={$_.freespace / 1gb -as [int]}}
Эти параметры можно выбирать после запуска сохранённого скрипта клавишей Tab и задавать их на лету.
После сохранения скрипта с параметрами и вызова этого скрипта через get-help, вывод выглядит как спавка к командлету, где командлетом является сам скрипт. В данном случае $Computername - это объект, но если написать в скрипте
[string]$Computername
, то параметр будет иметь значение «строка». Для полноценного строкового параметра не хватает скобочек, их надо добавить:
[string[]]$Computername='localhost'
Если в начале скрипта добавить
[CmdletBinding()]
, то будут доступны все прочие параметры [<Commonparameters>]
Если в список параметров добавить
param( [Parameter(Mandatory=$True)]
, то следующий за ним параметр (один), будет обязательным.
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/
# Узнать установленную версию (v4 и выше)
$PSVersionTable
Справка
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_* # список справочной информации по областям применения
Интерпретация синтаксиса справки
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>]
В данном случае есть два набора параметров, которые могут быть использованы с этим командлетом; некоторые параметры используются и там, и там. Если параметр есть только в одном наборе, то нельзя использовать вместе с ним параметры из другого (если только они не прописаны в обоих наборах).
[<CommonParameters>] - стандартные параметры для всех командлетов.
[ ] - необязательный (optional) параметр.
Positional parameter - обязательный параметр, который может использоваться без указания имени параметра, в данном случае [-LogName] <String>, где обязательно указание только <String>. Например, можно написать
Get-EventLog System -Newest 20
Выяснить свойства параметров можно в полной справке по командлету с ключом -Full.
Значения параметров
Параметр «переключатель» (switches) - не нуждаются во вводных данных, например, в данном случае - [-AsString]. Такие параметры никогда не являются Positional parameter, поэтому всегда нужно печатать имя параметра, а также, они всегда необязательны.
Типы вводных данных:
Строка (String) - ряд букв и цифр. Если с пробелами (типа C:\Program Files), то нужно заключать в одинарные кавычки (' ').
Целые числа (Int, Int32, Int64) - без десятичных значений.
Дата и время (DataTime) - строка, интерпретируемая как указатель времени в соответствии с региональными параметрами системы.
Квадратные скобки, которые указаны рядом ([-ComputerName <string[]>]) означают, что значений может быть несколько (array, collection, list), разделённых запятыми. Например:
Get-EventLog Security -computer Server-R2,DC4,Files02
Множественное значение может быть прочитано из файла, например
Get-EventLog Application -computer (Get-Content names.txt)
Если параметр обязательный, можно выполнить командлет вообще без параметров, и Powershell будет спрашивать значения одно за одним, пока не будет нажат «ввод» без указания значения.
Запуск команд
Не интерпретировать параметры, передаваемые стороннему приложению: --%
C:\windows\system32\sc.exe --% qc bits
Провайдеры (providers)
Провайдеры - это хранилища информации, выглядящие как диски - это заданные псевдонимы команд, реестр, файловая система и т. д.
Get-PSProvider # вывести список доступных провайдеров
Для работы с провайдерами по большей части используются командлеты, содержащие в себе слово Item:
Get-Command -noun *item*
Суть в том, что можно запрашивать любые объекты провайдера одними и теми же командлетами.
Set-Location -Path C:\Windows # файловая система Set-Location -Path hkcu: # реестр Set-Location -Path Env: # системные переменные # если не указать тип, то PS спросит об этом, т. к. непонятен тип объекта New-Item TestFolder -Type Directory
По умолчанию, путь к объекту обязательный и может содержать маску (* и ?), но можно задать необязательный параметр -LiteralPath, который не будет интерпретировать следующее за ним значение.
Можно выгружать информацию в CSV и XML с помощью командлетов Export-CliXML и Export-Csv. в XML выгружается больше подробностей. Подобную выгрузку можно использовать для сравнения, например, если выгрузить с одного компьютера список процессов, перенести полученный файл на другой компьютер и там запустить сравнение списка в файле со списком работающих процессов там (в данном случае - по имени):
Diff -reference (Import-CliXML reference.xml) -difference (Get-Process) -property Name
Если выпустить -Property, то сравнение будет идти по всем полям. Diff в Powershell не очень хорошо работает для сравнения текстовых файлов.
# Тупо выгрузить в файл Dir > DirectoryList.txt # А вот здесь можно задать параметры выгрузки - кодировка, доп. инфо и т. д. Dir | Out-File DirectoryList.txt help out* # посмотреть, куда можно выводить результат get-service | out-printer # напечатать список служб Get-Service | ConvertTo-HTML > services.html # создать HTML-страничку со списком служб
У командлетов есть определённый уровень воздействия (impact level) на систему, который задаётся параметром $confirmpreference (значение по умолчанию - High). Если уровень воздействия командлета равен или превышает этот уровень, то выдаётся запрос подтверждения.
# Можно форсировать запросы подтверждения Get-Service | Stop-Service -confirm # Эмуляция выполнения Get-Service | Stop-Service -whatif
Отличие Import-CSV от Get-Content: Get-Content импортирует сырые неструктурированные данные из CSV, тогда как Import-CSV разбирает CSV и представляет его в удобном виде. Та же параллель с XML.
# Без комментария о типе файла, не перезаписывать сущ. файл, # использовать в качестве разделителя региональные параметры, подтверждать действие Get-Service | Export-CSV services.csv -NoTypeInformation -NoClobber -UseCulture -Confirm
Расширения
Два типа расширений: оснастка (snap-in) и модуль (module). От использования оснасток уходят в пользу модулей.
# вывести список установленных, но не загруженных оснасток get-pssnapin –registered # загрузить оснастку (указать имя) add-pssnapin sqlservercmdletsnapin100 # Вывести список команд, принадлежащих к загруженной оснастке Get-Command -pssnapin sqlservercmdletsnapin100
Оснастка может добавить новых провайдеров, например в случае с загрузкой sqlservercmdletsnapin100 к списку провайдеров добавится SqlServer.
Чтобы загружать нужные оснастки прямо при запуске PS, нужно загрузить эти оснастки, а затем сохранить их:
Export-Console c:\myshell.psc # Загрузить PS уже с нужными оснастками %windir%\system32\WindowsPowerShell\v1.0\powershell.exe -noexit -psconsolefile c:\myshell.psc
Модули в этом способе не участвуют.
У модулей есть системная переменная modulepath, где прописаны пути к их местоположению.
get-content env:psmodulepath get-module # список модулей import-module # загрузить модуль
С 3-й версии PS нужно прописывать путь в переменной, если где-то лежит модуль и его нужно использовать. Это нужно для автоматической загрузки нужного модуля при вызове его командлета. Как и оснастка, модуль может добавить нового провайдера.
Для автозагрузки модулей при старте PS есть механизм сценария профиля (profile script):
- Сделать подпапку WindowsPowerShell в «Документах», создать в ней файл profile.ps1
- В этом файле прописать все нужные команды Add-PSSnapin и Import-Module для загрузки нужных расширений.
- В PS от админа выполнить команду
Set-ExecutionPolicy RemoteSigned
- Переоткрыть PS, после этого profile.ps1 будет загружен автоматически.
Галерея модулей: http://powershellgallery.com. Есть модуль PowerShellGet для удобной установки модулей из галереи.
Объекты
Если вывести Get-Process, то будет таблица с видом по умолчанию, большинство свойств будут скрыты - это обусловлено тем, что выводить все свойства на экран нецелесообразно из-за ограниченного пространства на экране. Один из способов показать все свойства без фильтрации - это направить вывод в HTML:
Get-Process | ConvertTo-HTML | Out-File processes.html
Собственно, объектом (object) называется строка в «таблице», представляющая собой одну сущность - процесс, службу и т. д.
Свойством (property) называется колонка таблицы - один тип информации о процессах, сервисах и т. п.
Метод (method) - также обозначается как действие (action). Взаимодействует с объектом - например, останавливает процесс, запускает службу и т. д.
Набор (collection) - все объекты, которые перечислены в этой «таблице».
Get-Process | Get-Member # (или gm) показать свойства и методы командлета
К примеру, чтобы убить процесс, есть три пути: вызвать метод kill,
# передать через пайп Get-Process -Name Notepad | Stop-Process # или обойтись одним командлетом Stop-Process -name Notepad
Сортировка
Get-Process | Sort-Object -property VM Get-Process | Sort VM -desc # сокращённо и в обратном порядке # Если несколько процессов занимают одинаковое кол-во памяти, сортировать по ID Get-Process | Sort VM,ID -desc
Выбор свойств
Select-Object можно написать как просто select.
Get-Process | Select-Object -property Name,ID,VM,PM | Convert-ToHTML | Out-File test2.html Get-Process | Select -First 10 # 10 первых, -Last - 10 последних
Нужно не путать Select-Object (выбор свойств для отображения) с Where-Object (фильтрует объекты после пайпа на основании заданных критериев).
Ещё про пайп
Положим, есть список имён компьютеров в текстовике, и PS позволяет сделать так:
Get-Content .\computers.txt | Get-Service
Это нзывается привязкой параметров (parameter binding). PS смотрит на тип полученных данных и далее применяет его к параметру второй команды, способному принять такой тип данных. В данном случае Get-Content производит данные типа System.String (или, коротко, String). Get-Service принимает тип данных String параметром -Name. Тип выдаваемых данных можно посмотреть командой Get-Member (или gm), а тип принимаемых данных - в справке по командлету в строке «Accept pipeline input?» того или иного параметра.
В большинстве случаев, если в командлетах одно существительное, они успешно взаимодействуют через пайп:
get-process -name note* | Stop-Process
Если привязка по значению (ByValue) не удаётся, PS пробует другой подход - по имени параметра (ByPropertyName). Благодаря этому возможно такое (обратите внимание, что параметр -name указан явно):
get-service -name s* | stop-process
В данном примере, данные успешно передаются через пайп, но будет куча ошибок, так как имена сервисов и процессов различаются.
Более успешный пример - создать файл .csv cо следующим содержанием:
- aliases.csv
Name,Value d,Get-ChildItem sel,Select-Object go,Invoke-Command
Если выполнить Get-Member, то можно узнать, что Import-CSV выдаёт на выходе
import-csv .\aliases.csv | gm
Далее, если выполнить
import-csv .\aliases.csv | new-alias
то окажется, что создались три новых алиаса для командлетов, так как строки при импорте преобразовались в объекты, а параметры Name и Value совпали с одноимёнными параметрами в командлете New-Alias.
Замена параметров при передаче
Например, есть файл CSV, который был прислан извне, и на базе него нужно создать пользователей в AD.
- newusers.csv
login,dept,city,title DonJ,IT,Las Vegas,CTO GregS,Custodial,Denver,Janitor JeffH,IT,Syracuse,Network Engineer
Для создания пользователей используется New-ADUser, но там нет параметров dept и т. д., то есть, просто передать данные в сыром виде через пайп не получится. Для этого нужно указать соответствие передаваемых параметров.
import-csv .\newusers.csv | select-object -property *, >> @{name='samAccountName';expression={$_.login}}, >> @{label='Name';expression={$_.login}}, >> @{n='Department';e={$_.Dept}} | New-ADUser
-property * - вывести все доступные свойства.
@{key=value} - создание хэш-таблиц соответствия. Ключ может указываться как Name, N, Label или L. Значение - как Expression или E.
$_ - обращение к исходному объекту, передаваемому через пайп.
Скобки
Некоторые командлеты не принимают данные через пайп, например, Get-WmiObject -ComputerName. Выход - использовать скобки, которые действуют как в алгебре, т. е., в скобках действие выполняется в первую очередь.
# Так работать не будет: get-content .\computers.txt | get-wmiobject -class win32_bios # А так - будет: Get-WmiObject -class Win32_BIOS -ComputerName (Get-Content .\computers.txt)
Необходимо, чтобы результат в скобках был соответствующего типа для параметра, который будет получать результат.
# Так работать не будет, в скобках тип будет 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 - вывести значения без названия столбца
Форматирование
В папке $pshome есть файл otnettypes.format.ps1xml, отвечающий за форматирование вывода команд. Например, если выполнить
Get-Process | gm
скопировать тип (System.Diagnostics.Process) и поискать его в файле otnettypes.format.ps1xml, то под строкой <Name>process</Name> будут находиться стандартные параметры форматирования вывода команды.
В некоторых случаях предустановленный вывод отсутствует, например, в случае с
Get-WmiObject Win32_OperatingSystem | Gm
Такого типа данных нет в otnettypes.format.ps1xml, в этом случае используется «второе правило форматирования» - вывод по умолчанию, описанный в Types.ps1xml (DefaultDisplayPropertySet).
«Третье правило» форматирует вывод согласно его типу, например, если отображается до 4 параметров, используется таблица, 5 и более - список.
Команды форматирования
Format-Table (ft) - таблица.
-AutoSize - подгоняет ширину столбцов под длину строк.
-Property - выбор столбцов для отображения, перечисляются через запятую. Звёздочка - вывести все столбцы.
Примеры:
Get-Process | Format-Table -property * Get-Process | Format-Table -property ID,Name,Responding -autoSize Get-Process | Format-Table * -autoSize
-groupby - сортировка по какому-то из столбцов.
-Wrap - перенос строк в столбце.
Format-List (fl) - список. Используются те же параметры, что и для таблицы.
В какой-то степени fl можно использовать как gm, с той разницей, что fl покажет и значения параметров.
Get-Service | Fl *
Format-Wide (fw) - широкий список. Выводит только одно свойство (-Property), но в несколько колонок.
Get-Process | Format-Wide name -col 4
Более комплексные примеры:
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
formatstring - код стандартного форматирования чисел и дат (https://msdn.microsoft.com/en-us/library/fbxft59x(v=vs.95).aspx).
-Width - задаваемая ширина столбца.
align - выравнивание.
Вывод
Если используется команда, начинающаяся с Format-, то правила форматирования для этих команд заданы так, что вывод делается в Out-Default, который передаёт данные в Out-Host (на экран). То есть, эти две строки дадут одинаковый результат:
Get-Service | Format-Wide | Out-Host Get-Service | Format-Wide
Можно вывести данные в файл и на принтер, т. е., использовать Out-File и Out-Printer. У каждой из этих команд свои правила форматирования по умолчанию.
Также, есть команда Out-GridView, которая создаёт стандартное окно Windows с выводимой информацией, в консоль ничего не выводится (в других системах может не работать). Out-GridView не принимает правил форматирования и обрабатывает только стандартные объекты.
Команды форматирования (Format-*) должны идти последними в строке, единственное исключение - команды вывода (Out-*). Пример неправильного использования (в файле services.html будет ересь, т. к. в него передаются инструкции форматирования вместо объектов):
Get-Service | Select Name,DisplayName,Status | Format-Table | ConvertTo-HTML | Out-File services.html
Для корректной обработки данных, необходимо передавать через пайп только один тип объектов, если нет какой-то специальной хорошо понимаемой задачи, диктующей обратное.
Фильтр результатов и сравнение
Два подхода: запрос сразу нужной информации (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 на противоположное значение. Например,
$_.Responding -eq $False # можно написать как -not $_.Responding # Ещё пример фильтра Get-ADComputer -Filter 'Name -notlike "*0000*" -and Enabled -eq "True"' | select Name | sort Name
Иногда -not пишется как !.
-like - понимает маски, например "Hello" -like "*ll*" - true. Противоположный оператор - -notlike.
-match - сравнение текстовой строки и регулярного выражения. Противоположный оператор - -notmatch.
Если нужно использовать чувствительные к регистру операторы, в начале добавляется «c»: -ceq, -cne, -cgt, -clt, -cge, -cle, -clike, -cnotlike, -cmatch, -cnotmatch. Полезно для операций со строками.
# Справка
help about_comparison_operators
Примеры использования фильтрации уже полученных результатов:
Get-Service | Where Status -eq 'Running' get-service | where-object {$_.status -eq 'running' -AND $_.StartType -eq 'Manual'}
Удалённый доступ
# Включить на целевой машине Enable-PSRemoting # Справка: about_remote_troubleshooting
Иногда советуют запускать 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 также можно настроить длительность неактивных сессий, кол-во одновременных подключений, права и т. д.
Менять номер порта не рекомендуется, но возможно:
Winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="1234"}
Два способа подключения: one-to-one (1:1) и one-to-many (1:N)
1:1 как RDP, только консоль.
Enter-PSSession -computerName Server-R2 [server-r2] PS C:\> # Выйти: Exit-PSSession
Ограничения:
- PS-профили, которые есть на удалённой машине, загружены не будут.
- Удалённая сессия будет ограничена политикой выполнения (execution policy) удалённой машины, а не локальной.
Без крайней необходимости лучше не запускать удалённые сессии из-под удалённых сессий (remoting chain), т. к. это трудно отслеживать. Иногда это бывает нужно, например, если третья машина находится за шлюзом и напрямую сессию запустить нельзя.
One-to-many (1:N) - каждый комп запускает команду у себя и возвращает результат.
Invoke-Command -computerName Server-R2,Server-DC4,Server12 -command { Get-EventLog Security -newest 200 | Where { $_.EventID -eq 1212 }} # Вместо -command можно использовать -scriptblock, указывая путь к скрипту.
По умолчанию PS запускает 32 удалённых запроса одновременно, остальные ставит в очередь, это можно изменить с помощью параметра -throttleLimit.
# Запуск команды на серверах в списке 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 )
Разница между Invoke-Command и -computerName
Get-EventLog Security -newest 200 -computerName Server-R2,Server-DC4,Server12 | Where { $_.EventID -eq 1212 }
- Команда запускается последовательно на каждой машине. не параллельно.
- В выводе нет свойства PSComputerName, т. е. трудно сказать, с какой машины какой результат.
- WinRM не используется, и могут быть проблемы с выполнением команд на машинах за шлюзами.
- Фильтр по событию 1212 применяется уже после запроса, следовательно, по сети будут передаваться ненужные события.
- События являются объектами, т. е., полностью функциональны.
Invoke-Command -computerName Server-R2,Server-DC4,Server12 -command { Get-EventLog Security -newest 200 | Where { $_.EventID -eq 1212 }}
- Команда запускается одновременно на всех машинах, выполнится быстрее.
- В выводе есть свойство PSComputerName, легко определить что откуда.
- Используется WinRM, проще сделать правила на файрволлах между клиентом и серверами.
- Фильтрация происходит на серверах, и по сети передаётся уже готовый вывод.
- Перед передачей на клиента, серверы формируют (serialize) результат в виде XML, клиент при получении обрабатывает (deserialize) информацию и выводит её на экран. Это выглядит как объекты, но ими не является.
Локальная и удалённая обработка
# Фильтрация на удалённых серверах 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 }
# Работать не будет, т.к. результат выдачи с сервера - не объект Invoke-Command -computerName Server-R2 -command { Get-Process -name Notepad } | Stop-Process # Сработает Invoke-Command -computerName Server-R2 -command { Get-Process -name Notepad | Stop-Process }
# На локальной машине доступны методы get-service | get-member # Здесь методов не будет, потому что вывод - не объекты Invoke-Command -ScriptBlock { Get-Service } -ComputerName server2 | Get-Member
Настройки удалённой сессии
Параметр -SessionOption при запуске Invoke-Command или Enter-PSSession.
- Задаёт таймауты сессий
- Выключает сжатие и шифрование данных
- Пропуск проверки SSL-сертификатов, имени и других проверок безопасности
Enter-PSSession -ComputerName server2 -SessionOption (New-PSSessionOption -SkipCNCheck)
Примечания
- Удалённые сессии работают с реальными именами компьютеров - IP и алиасы использовать нельзя.
- Конфигурация по умолчанию рассчитана на домен, если что - надо читать help about_remote_troubleshooting, например, если надо настроить работу удалённых сессий между доменами.
- Invoke-Command - это значит, что команда выполняется на удалённой машине, и PS там закрывается, соответственно, данные больше недоступны. При множестве последовательных взаимозависимых команд нужно запускать их в рамках одной сессии.
- Для успешной работы нужно убедиться, что она идёт под админом, либо нужно использовать параметр -credential, где указывать админскую учётку.
- Enable-PSRemoting создаёт правило только для Windows firewall, другие файрволлы надо настраивать отдельно.
- GPO перекрывает локальные настройки, если что-то не работает, полезно проверить настройки GPO.
Рекомендуется книга 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 независимо от того, есть стример в реальности или нет.
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 :
Просматривать репозиторий WMI удобно с помощью WMI explorer. Можно искать и так:
Get-WmiObject -Namespace root/CIMV2 -list |where name -match 'disk' |sort name
WIM-команды, типа Get-WmiObject и Invoke-WmiMethod - устаревшие, не развиваются, работают через RPC, пробросить их через файрволл трудно. Их можно использовать на старых системах, в ОС Windows Server 2012 R2 и новее они отключены по умолчанию. CIM-команды, типа Get-CimInstance и Invoke-CimMethod - современная замена, работают через WS-MAN (WinRM).
# Вывести все классы в пространстве имён 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'"
- Операторы сравнения не как в PS (-like, -eq), а =, <, >= и т. д. Можно использовать LIKE, и как указатель wildcard нужно писать не *, а %, например, «NAME LIKE '%administrator%'».
- Строки сравнения - в одиночных кавычках, весь фильтр - в двойных.
- Обратная косая черта (\) - экранирующий знак, если нужно поставить в строке \, то придётся удваивать - \\.
- Вывод gwmi всегда содержит ряд системных свойств, которые начинаются с двойного подчёркивания, по умолчанию PS часто подавляет их вывод, но это можно переопределить. Пара полезных свойств: __SERVER - имя компьютера, с которого была запрошена информация (это же имя есть в PSComputerName), __PATH - абсолютный путь к запущенному экземпляру.
# выполняется последовательно, если комп недоступен - выдаёт ошибку и идёт дальше 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
Отличия Get-CimInstance от Get-WmiObject:
- Используется -ClassName, а не -Class
- Нет параметра -List, вместо этого нужно использовать Get-CimClass с параметром -Namespace
- Нет параметра -Credential, если нужны другие учётные данные для запуска, надо использовать Invoke-Command
Особенность работы с WMI в Powershell - справка малоэффективна, нужно гуглить или пользоваться всякими костылями типа WMI Explorer.
Несколько задач одновременно, фоновый режим
В обычном режиме команды запускаются синхронно, т. е., нажимаем ввод и ждём результата. В фоновом режиме команды выполняются асинхронно - задача запускается, и консоль можно использовать дальше для других задач.
- В синхронном режиме можно отвечать на возникающие запросы, в асинхронном - возникший запрос остановит задачу.
- В синхронном режиме сразу появляются сообщения об ошибках, если что-то идёт не так, в асинхронном сразу увидеть ошибки нельзя, но можно их захватывать определённым способом.
- PS в синхронном режиме запрашивает значения обязательных параметров, если они пропущены, в асинхронном такой возможности нет, и выполнение задачи прерывается.
- Результаты выполнения задачи в синхронном режиме отображаются сразу же, в асинхронном - после выполнения задачи доступны в кэше.
В фоне нужно запускать только хорошо отлаженные сценарии, с наибольшим шансом успешного выполнения. Фоновые команды в PS называются задачи (jobs). Эти задачи можно создавать несколькими способами, также есть несколько способов управления ими.
# Запустить задачу Start-Job -ScriptBlock {dir} -Name Job1 -Credential DOMAIN\Username # Задача из файла, на другой машине Start-Job -FilePath C:\temp\script.ps1 -Name Job2 -Credential DOMAIN\Username -Computer server2
Все эти задачи называются локальными, т. к. запуск идёт с локальной машины. После запуска задачам присваивается ID, причём, номера ID идут непоследовательно, так как у каждой задачи есть дочерние процессы (child jobs). Несмотря на то, что задачи локальные, они требуют работающего механизма PowerShell’s remoting system, иначе работать не будет.
У Get-WmiObject есть параметр -AsJob, который запускает командлет в фоне, но там нельзя выбрать произвольное имя задачи.
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
У Get-CimInstance нет параметра -AsJob, его нужно использовать в сочетании с Start-Job или Invoke-Command.
Создание удалённой фоновой задачи:
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
Просмотр результатов
get-job # список задач get-job -id 1 | fl * # подробности первой задачи receive-job -id 1 # вывод результатов. Нужно указать ID или имя задачи # сортировка результата удалённой задачи receive-job -name myremotejob | sort-object PSComputerName | Format-Table -groupby PSComputerName
- Если вывести результаты материнской задачи, они будут включать результаты дочерних. Как вариант, можно выводить результаты одной или нескольких дочерних задач.
- Обычно после получения результатов кэш стирается, и эти результаты нельзя запросить второй раз. Можно указать параметр -keep для сохранения результатов в кэше или выводить его в CliXML.
- Результаты могут не быть объектами (deserialized objects), и методы к ним не могут применяться, но можно форматировать вывод (sort, fl и т. д.)
Контекст запуска задачи и команд в этой задаче различается, например,
C:\> start-job -scriptblock { dir } # выведет список по пути $env:userprofile\Documents
Поэтому, в командах нужно указывать пути.
Работа с задачами
- Remove-Job - удалить вместе с результатом выполнения в кэше
- Stop-Job - прервать выполнение, результаты останутся на момент прерывания
- Wait-Job - полезно в скрипте, указание ждать выполнения задачи
# Показать дочерние задания get-job -id 1 | select -expand childjobs # грохнуть все задачи, где уже нет результатов get-job | where { -not $_.HasMoreData } | remove-job
Если фоновая задача выполнилась с ошибкой (State failed), то текст ошибки хранится как результат в дочерней задаче.
Запланированные задачи (scheduled jobs)
Это несколько отличается как от фоновых задач PS, так и от заданий в планировщике Windows (scheduled tasks). Чтобы создать задачу, нужно задать триггер (New-JobTrigger), определяющий, когда задача запускается, параметры задачи (New-ScheduledTaskOption), затем задача регистрируется в Планировщике - на диске создаётся файл XML и иерархия каталогов для хранения результатов выполнения.
Register-ScheduledJob -Name DailyProcList -ScriptBlock { Get-Process } -Trigger (New-JobTrigger -Daily -At 2am) -ScheduledJobOption (New-ScheduledJobOption -WakeToRun -RunElevated)
Если запросить список задач (Get-Job), то видно, что каждый раз при выполнении запланированной задачи создаётся задача в списке, причём, если запрашивать результат выполнения, то они не удалятся, так как хранятся на диске, а не в памяти. Но если удалить задачу, то результаты будут также удалены с диска. Количество создаваемых и хранимых задач регулируется параметром -MaxResultCount в команде Register-ScheduledJob.
Как делать не надо:
# после запуска соединение прерывается, и результаты задачи уже не будут доступны invoke-command -command { Start-Job -scriptblock { dir } } -computername Server-R2 # здесь нужно использовать -AsJob у Invoke-Command start-job -scriptblock { invoke-command -command { dir } -computername SERVER-R2 }
Вообще, не надо мешать в кучу рассмотренные три способа запуска задач.
Задачи существуют, пока запущена консоль. Исключение - запланированные задачи, т. к. они хранятся на диске; к ним может получить доступ тот, кто имеет соответствующие права.
# Последние 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)
Работа со многими объектами одновременно
В отличие от подхода, когда запрашивается какое-то количество объектов и потом они обрабатываются, перебираясь по одному (foreach), есть более эффективные способы обработки.
Предпочтительный путь - использование «пакетных» команд. Это команды, способные принимать данные через пайп.
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
Другой способ - вызов метода. Положим, надо включить DHCP на сетевых адаптерах Intel.
# Запрос интеловских адаптеров 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
Также, можно в случае с WMI/CIM поискать другой, родной для PS командлет, выполняющий нужную функцию - в данном случае Set-NetIPAddress.
Тем не менее, иногда приходится использовать перебор. Например, нужно задать пароль для запуска служб, и ни Get-Service, ни Set-Service с паролями работать не умеют, придётся использовать WMI. В методе change много параметров, для пропуска ненужных используется переменная $null.
# Так работать не будет: 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") }
Иллюстрация разных подходов
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* # специализированная команда
Нельзя передать что-либо методу через пайп, передать можно только другой команде. Если команда не умеет делать то, что нужно, а метод умеет, тогда используется foreach и дальше вызывается нужный метод.
Get-Something | ForEach-Object { $_.Delete() }
Безопасность
Политика выполнения скриптов - в клиентских системах вообще запрещено (Restricted), на серверах можно выполнять те, что созданы локально или подписанные (RemoteSigned).
Можно управлять политикой выполнения через GPO (Computer Configuration → Policies → Administrative Templates → Windows Components → Windows PowerShell).
AllSigned - все скрипты должны быть подписаны доверенным УЦ.
Unrestricted - можно всё.
Bypass - для использования в приложениях, работающих с PS.
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.
Дополнительные меры:
- Скрипты .ps1 не выполняются по двойному клику мышкой, а открываются как текстовые файлы.
- Запустить скрипт из консоли невозможно, просто набирая его имя, т. к. консоль не ищет скрипты в локальной папке.
Сделать самоподписанный сертификат для подписи кода
help about_signing
New-SelfSignedCertificate
Переменные - место для хранения
Переменные могут содержать пробелы, тогда нужно их заключать в фигурные скобки: ${variable 1}. Переменные не сохраняются между сессиями PS. PS воспринимает заключённое в одинарные кавычки буквально, поэтому
$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
Символ ` (escape character, под ~) - отменяет действие последующего символа, т. е.,
$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
# Несколько объектов в переменной $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
В PS v1 и v2 у переменных, содержащих множественные значения, нельзя было использовать методы и свойства, с v3 сделали авторазвёртывание (automatic unrolling), т. е.
# 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')
# Так работать не будет - двойные кавычки не воспринимают # конструкцию [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
Иногда принудительное указание типа переменной необходимо.
$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"
Популярные типы переменных:
- [int] - целое число
- [single] и [double] - число с одним или двумя знаками после разделителя
- [string] - строка
- [char] - один символ
- [xml] - документ с разметкой XML
- [adsi] - запрос Active Directory Service Interfaces
Ввод и вывод
Запрос информации от пользователя
$computername = read-host "Enter a computer name" Enter a computer name: SERVER-R2
Чтобы создать при запросе графическое окно, нужно использовать .NET
# 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')
Вывод
write-host "COLORFUL!" -fore yellow -back magenta
Write-Host используется только для показа информации, не для форматирования, но тем не менее, он не используется для сообщений типа “now connecting to SERVER2,” “testing for folder,” и т. д. Для этого лучше использовать Write-Verbose. С 5-й версии PS, Write-Host по сути стал алиасом к более новой команде Write-Information.
В отличие от Write-Host, Write-Output умеет передавать информацию через пайп и технически не предназначена для отображения.
# Если написать 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 }
Другие команды вывода, работают подобно 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:
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
Записывать сессии в переменную имеет смысл только тогда, когда компьютеров несколько.
$serv = New-PSSession -ComputerName serv1,serv2,serv3,serv4 Invoke-Command -Command {gwmi win32_process |select processname,csname |ft} -Session $serv
У Get-WmiObject есть свой параметр -computername (у Get-CimInstance нет, он заточен под использование удалённых сессий), но лучше его не использовать, так как
- Удалённый доступ PS использует определённый порт, WMI нет.
- Удалённый доступ экономит ресурсы, т.к. не одна машина запрашивает данные, а каждый выполняет свою работу и высылает результаты
- Удалённые запросы выполняются параллельно, WMI работает последовательно
- Ранее заданные сессии не могут быть использованы с Get-WmiObject
# Параметры -session можно дать результат команды в скобках. Invoke-Command -Command {gwmi win32_process |select processname,csname |ft} -Session (Get-PSSession -ComputerName serv2,serv3) # В данном случае, Invoke-Command не умеет получить объекты через пайп как Enter-PSSession.
Неявный доступ (implicit remoting) - импорт модулей и оснасток с удалённой машины. Удобно в случаях, когда нельзя поставить их на локальную тачку, например RSAT для 2008 R2 на Windows XP и т.п.
# установка соединения $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 # создаётся временный локальный модуль
Команды выполняются на удалённой машине и будут доступны до закрытия сессии или окна терминала. Минус - результат выполнения команд не будет объектами (deserialized).
С PSv3 можно возобновлять закрытые сессии
# Id Name ComputerName State # 4 Session4 COMPUTER2 Disconnected Get-PSSession -computerName COMPUTER2 | Connect-PSSession
Управлять параметрами сессий можно через GPO или WSMan drive (WSMan:\localhost\Shell, WSMan:\localhost\Service).
Скриптинг
С параметрами и документацией
- 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]}}
Значения параметров - значения по умолчанию.
# Справка help .\Get-DiskInventory -full # почитать про справку help about_comment_based_help
Скрипт выполняется в одном потоке, тогда как последовательные команды - каждый в своём.
Область (scope) - форма контейнера для хранения элементов PS. Сама консоль - global scope, верхний уровень. При запуске скрипта образуется script scope, являющийся дочерним (child) к global scope (parent). У функций есть свои private scopes, дочерние к скрипту. Все области существуют, пока запущен породивший их процесс. Если данные (например, переменная), не найдены в пределах области, PS смотрит на уровень выше, нет ли данных там.
Совершенствование задания параметров в скриптах
Сделать параметр Computername обязательным, с подсказкой при запросе и алиасом. Алиас позволяет при запуске указать тот же параметр другим именем. Также, проверяется параметр типа диска - принимается только 2 или 3.
Добавлен более подробный вывод процесса выполнения в консоль с помощью Write-Verbose - для того, чтобы эти сообщения показывались, нужно запускать скрипт с параметром -Verbose.
[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 "Операция успешно завершена"
- Все параметры заключены в блок param()
- Один параметр может иметь несколько декораторов (decorators), которые могут быть написаны одной строкой или разделены на несколько для удобства восприятия
- Имена параметров разделяются запятыми, кроме последнего, после которого запятой не нужно
- Между параметрами лучше оставлять пустую строку, чтобы легче было читать
- Параметры задаются как переменные, но используются при запуске скрипта как параметры запуска
# Прочесть про опции параметров:
help about_functions_advanced_parameters
Ещё пример
[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 "Завершено"
Углублённая настройка удалённого управления
Иногда нужно указывать битность при использовании, например, удалённой 32-битной оснастки с 64-битной системы. Это одна из конфигураций сессии (endpoints, session configurations).
# Список доступных конфигураций Get-PSSessionConfiguration # Подключение с указанием конфигурации Enter-PSSession -ComputerName DONJONES1D96 -ConfigurationName 'Microsoft.PowerShell32'
Создание конфигурации: New-PSSessionConfigurationFile создаёт файл конфигурации (.pssc), где перечислены всё характеристики. Register-PSSessionConfiguration считывает файл и создаёт endpoint, также можно задать разные параметры, например, права доступа, изменить эти параметры можно позже с помощью Set-PSSession-Configuration.
Это можно делать, к примеру, для делегирования кому-то выполнения некоторых команд
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
Многоступенчатый удалённый доступ (multihop remoting). Если попробовать создать сессию на 3-й компьютер с удалённого, то ничего не выйдет, т. к. удалённый компьютер не имеет права делегирования прав локальной машины далее. Начиная с Windows Vista, это можно переопределить.
# На локальной машине (x - удалённый комп) Enable-WSManCredSSP -Role Client -DelegateComputer x # На удалённой машине Enable-WSManCredSSP -Role Server
При удалённом доступе 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
- $ - конец строки.
# справка 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,}"
Разные прочие техники и трюки
Можно задать профиль для PS - настройки при каждом запуске, например
Import-Module ActiveDirectory Add-PSSnapin SqlServerCmdletSnapin100 cd c:\ # справка по профилям help about_profiles
Профиль относится к приложению 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, то профиль просто не запустится.
Операторы
# -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
Манипуляции со строками
"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
Манипуляции с датой
get-date | gm (get-date).month (get-date).adddays(-90) (get-date).ToShortDateString() 28.05.2019
Даты WMI
# У 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
Задание стандартных значений параметров
Например, у dir стандартный параметр -Path - текущая папка.
Умолчания хранятся в спец. переменной $PSDefaultParameterValues, которая пуста при запуске консоли.
Положим, нужно задать имя пользователя по умолчанию для запросов учётных данных
$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
$PSDefaultParameterValues, как и другие переменные, действует в рамках области (scope), т. е., в контексте сущности, где переменная появилась. (help about_scope)
Scriptblocks
Скриптблок - то, что заключено в {}, кроме хэш-таблиц @{}
- where -filterscript даёт скриптблок
- foreach -process даёт скриптблок
- Хэш-таблицы, которые используются при выборе свойств в select или format-table, принимают скриптблоки в качестве значения e= (expression).
- Некоторые команды принимают скриптблоки в качестве параметра: Invoke-Command -ScriptBlock {} или Start-Job -ScriptBlock {}.
Чтобы вызвать скриптблок, нужно вначале ставить & (call).
$block = {get-process | sort -Property vm -Descending | select -first 10} &$block # справка help about_script_blocks
Памятка
При использовании чужих скриптов необходимо полностью понимать, что происходит в каждой строке, нельзя оставлять работать непонятные куски кода.
- Распознать переменные, что они содержат, это поможет понять, что делает команда
- Читать справку к новым командам
- Использовать тестовую среду, например, VM.
Пунктуация
` (обратная кавычка) - экранирует последующий символ. Например, можно написать:
cd c:\Program` Files
~ - папка профиля
( ) - как в арифметике, приоритет выполнения.
Get-Service -computerName (Get-Content c:\computernames.txt)
Если скобки - это параметры метода, то они ставятся в любом случае, даже если внутри ничего нет.
[ ] - индекс объекта в массиве, отсчёт с нуля. $services[2] - третий объект. Также, тип объекта - [string], [int] и т. д.
{ } - скриптблок или фильтр
Get-Service | Where-Object { $_.Status -eq 'Running' } # ключ=значение в хэш-таблицах $hashtable = @{l='Label';e={expression}} # как вариант написания переменной с пробелами и другими нестандартными символами ${My Variable}
' ' - буквальное прочтение содержимого, переменные не читаются.
" " - переменные, escape characters читаются.
$two = "Hello $one `n"
$ - знак переменной
% - 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
& - вызов (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 ищет одноимённые параметры, чтобы отдать данные туда.
$_ используется в скриптблоках после пайпа
Get-Service |? {$_.Status -eq 'Running'} gwmi -class Win32_Service -filter "name='mssqlserver'" | foreach -process { $_.ChangeStartMode('Automatic') }
В любом случае, $_ окружён { }.
PowerShell in Depth
Custom Object
Вариант 1:
$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
В PS v3 и новее можно задать порядок свойств:
$props = [ordered]@{ OSVersion=$os.version Model=$cs.model Manufacturer=$cs.manufacturer BIOSSerial=$bios.serialnumber ComputerName=$os.CSName OSArchitecture=$os.osarchitecture ProcArchitecture=$proc.addresswidth}
Вариант 2 (не рекомендуется, т. к. тип объекта будет неправильным):
$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
Вариант 3:
$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
или так:
$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
Вариант 4 (для PS v3 или новее):
$obj = [pscustomobject]@{OSVersion=$os.version Model=$cs.model Manufacturer=$cs.manufacturer BIOSSerial=$bios.serialnumber ComputerName=$os.CSName OSArchitecture=$os.osarchitecture ProcArchitecture=$proc.addresswidth }
Свойства получаются сразу упорядоченными.
Вариант 5 - создать новый класс:
$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
По быстродействию лучше всего варианты 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
Всё, что вы хотите знать о... - интересная и познавательная серия статей на learn.microsoft.com
PowerShell Explained with Kevin Marquette
Книги:
Learn PowerShell Toolmaking in a Month of Lunches
PowerShell in Depth