Windows PowerShellВозможности фильтрации
Дон Джонс (Don Jones)
В своей статье в прошлом месяце я обсуждал возможности и гибкость конвейера Windows PowerShell, позволяющего передавать набор данных — или, точнее говоря, поток объектов — от одного командлета к другому с тем, чтобы потом сделать этот набор именно таким, как вам нужно. В ней я упоминал о том, что ваши сценарии,
а не только командлеты, могут воспользоваться преимуществами конвейера. В этом месяце мне хочется обсудить эту тему подробно.
Чаще всего я пишу в Windows PowerShell™ сценарии, работающие для множества удаленных компьютеров, как правило, через Windows® Management Instrumentation (WMI). Как и в любой задаче, связанной с удаленными компьютерами, всегда существует возможность того, что один или более компьютеров не будут доступны при запуске сценария. Следовательно, нужно, чтобы мои сценарии могли с этим работать.
Разумеется, существует несколько способов предоставить сценарию возможность управлять тайм-аутом подключения WMI, но мне не особенно нравится этот подход, поскольку сам по себе период тайм-аута очень велик - около 30 секунд по умолчанию. Из-за этого мой сценарий может работать куда медленнее, если ему придется ждать несколько таймаутов. Вместо этого я хочу, чтобы мой сценарий выполнял быструю проверку, находится ли определенный компьютер в оперативном режиме, перед тем, как к нему подключаться.
Парадигма Windows PowerShell
В других языках сценариев, таких как VBScript, я имею дело с одним компьютером за раз. Это значит, что я получаю имя компьютера — возможно, из списка имен, сохраненных в текстовом файле, — и проверяю систему на предмет доступности. Если она доступна, я создаю подключение WMI и выполняю все необходимые действия. Это общепринятый подход при работе со сценариями. Я бы, на самом деле, наверное, написал весь свой код в цикле и повторял его для каждого компьютера, к которому необходимо подключиться.
Тем не менее, Windows PowerShell лучше подходит для групповых операций благодаря тому, что основана на объектах, а также возможности работы непосредственно с группами или коллекциями объектов. Парадигма в Windows PowerShell не предназначена для работы с единичными объектами или частями данных, она работает, скорее, с группами в целом, проходя группу бит за битом до тех пор, пока не выполнена поставленная задача.. Например, вместо получения за один раз имени одного компьютера из моего списка я сразу прочту всю коллекцию имен, а вместо проверки каждого из компьютеров в цикле я напишу одну процедуру, которая принимает коллекцию имен, проверяет их, а затем выводит имена тех компьютеров, к которым можно подключиться. Следующим шагом будет создать подключение WMI к остальным компьютерам — тем, которые можно достать с помощью команды ping.
Windows PowerShell использует именно этот подход для некоторых задач. Например, для получения списка запущенных служб я могу использовать нечто подобное:
Get-Service | Where-Object { $_.Status –eq "Running" }
Рис. 1 иллюстрирует, на что похож вывод результатов на моем компьютере. Вместо проверки одной службы за один раз я получит все службы в помощью Get-Service, передал их по конвейеру в Where-Object, а затем отфильтровать все незапущенные службы. Это примерно то, что я хочу сделать при помощи своего сценария: получить список имен компьютеров, отфильтровать те, которые не отвечают на команду ping, и передать список ответивших компьютеров на следующий этап.
Рис. 1** Получение списка компьютеров, отвечающих на команду ping **(Щелкните изображение, чтобы увеличить его)
Функции фильтрации
Я не хочу писать для этого собственный командлет, хотя мог бы. Для написания командлета требуется Visual Basic® или C# и достаточный опыт разработки для Microsoft® .NET Framework. И, что более важно, требуется больше труда, чем я собираюсь вложить в эту задачу. К счастью, Windows PowerShell дает мне возможность написания специального вида функции, называемой фильтром, которая замечательно действует в рамках конвейера. Основной набросок функции фильтрации выглядит примерно так:
function <name> {
BEGIN {
#<code>
}
PROCESS {
#<code>
}
END {
#<code>
}
}
Как вы видите, эта функция содержит три независимых блока сценариев, которые называются BEGIN, PROCESS и END. Функция фильтрации – это, таким образом, функция, предназначенная для фильтрации объектов в конвейере – можно иметь любую комбинацию из этих трех блоков сценариев в зависимости от того, что необходимо сделать. Они работают следующим образом:
- Блок BEGIN выполняется при первом вызове функции. При необходимости его можно использовать для настройки.
- Блок PROCESS выполняется единожды для каждого из объектов контейнера, передаваемого в функцию. Переменная $_ представляет текущий входной объект конвейера. Блок PROCESS необходим в функции фильтрации.
- Блок END выполняется после обработки всех объектов контейнера. Его можно использовать для выполнения любой работы по финализации, если это необходимо.
В моем примере мне нужно создать функцию фильтрации, которая примет коллекцию имен как объекты фильтрации, а затем попытается выдать команду ping для каждого из них. Каждое из имен, успешно ответивших на команду ping, будет выведено на конвейер, а системы, которые не ответили на ping, будут опущены. Поскольку функция ping не требует каких либо специальных настроек или финализации, я просто использую блок сценариев PROCESS. Код на рис. 2 содержит полный сценарий.
Figure 2 Ping-Address и Restart-Computer.
1 function Ping-Address {
2 PROCESS {
3 $ping = $false
4 $results = Get-WmiObject -query `
5 "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6 foreach ($result in $results) {
7 if ($results.StatusCode -eq 0) {
8 $ping = $true
9 }
10 }
11 if ($ping -eq $true) {
12 Write-Output $_
13 }
14 }
15 }
16
17 function Restart-Computer {
18 PROCESS {
19 $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20 $computer.Reboot()
21 }
22 }
23
24 Get-Content c:\computers.txt | Ping-Address | Restart-Computer
Обратите внимание на то, что я определил две функции: Ping-Address и Restart-Computer. В Windows PowerShell функции должны быть определены перед вызовом. В результате первой исполняемой строкой моего сценария является строка 24, которая использует командлет Get-Content для получения списка имен компьютеров из файла (один компьютер на строку). Этот список — в техническом плане коллекция объектов-строк — передается по конвейеру в функцию Ping-Address, которая отфильтровывает компьютеры, которые не отвечвают на ping. Результаты передаются по конвейеру в Restart-Computer, который использует WMI для удаленного перезапуска тех компьютеров, которые не отвечают на ping.
Функция Ping-Address реализует блок сценария PROCESS, это значит, что функция ожидает ввод коллекции объектов. Блок сценария PROCESS автоматически работает со введенными данными — мне не пришлось определять какие-либо входные аргументы для них. Я начинаю процесс в строке 3, присвоив переменной $ping значение $false, которое является встроенной переменной Windows PowerShell, представляющей логическое значение «ложь».
Затем я использую локальный класс WMI Win32_PingStatus для выдачи команды ping для конкретного компьютера. Обратите внимание в строке 5 на то, что переменная $_, представляющая текущий объект конвейера, входит в строку запроса WMI. Если строка содержится в двойных кавычках, Windows PowerShell всегда будет выполнять попытку заменить переменные наподобие $_ их содержимым, поэтому вам не придется возиться с построением строк. Я использую эту возможность в строке 5.
Строка 6 – это цикл, проверяющий результаты моей проверки. Если любой из этих результатов возвращается успешно (то есть StatusCode равен нулю), я устанавливаю значение $ping равным $true, что означает успешность. В строке 11 я проверяю, установлено ли для $ping значение $true. Если да, я вывожу исходный входной объект в поток выходных данных по умолчанию. Windows PowerShell автоматически управляет потоком выходных данных. Если эта функция находится в конце конвейера, то потом выходных данных преобразуется в текстовое представление. Если в конвейере больше команд, чем объектов выходного потока данных, то объекты-строки, содержащие имена компьютеров, передаются в следующую команду конвейера.
Функция Restart-Computer несколько проще, но она также использует блок PROCESS, поэтому тоже может без проблем входить в конвейер. В строке 19 функция подключается к указанному компьютеру и получает его класс WMI Win32_OperatingSystem. Строка 20 выполняет метод Reboot этого класса для перезагрузки удаленного компьютера.
И снова строка 24 – это место, где все в действительности выполняется. При запуске этого сценария, разумеется следует быть очень осторожным — он предназначен для перезагрузки любого компьютера, указанного в файле c:\computers.txt, что может иметь разрушительные последствия, если вы не обращаете внимания на имена в этом текстовом файле!
Следующие этапы
Этот сценарий не полностью неуязвим. Следует упомянуть также о возможности возникновения ошибки WMI, не связанной с основной связью, например блокировки брандмауэром необходимых портов на удаленном компьютере или ошибки безопасности WMI. С этими проблемами можно справиться с помощью обработчика ловушек – похоже, это идеальная тема для следующей статьи.
Кроме того, этот сценарий выполняет очень серьезное действие — перезапуск удаленного компьютера. Любой сценарий, выполняющие такие потенциально опасные действия, должен иметь два распространенных параметра Windows PowerShell: Confirm и WhatIf. Чтобы объяснить, как это сделать, требуется больше места, чем у меня есть в этой статье, и это станет еще одной отличной темой для будущей публикации. А до тех пор почитайте блог отдела Windows PowerShell; архитектор Джеффри Сновер (Jeffrey Snover) рассказывает об этом.
Даже если не вникать в эти функции, вы получите прекрасное представление о возможностях функций фильтрации.. В будущих статьях я расскажу об этом приеме поподробнее и покажу, как с помощью функций можно сделать повторно используемый код модульным, а также улучшить работу ваших сценариев в целом.
Дон Джонс (Don Jones) — ведущий специалист по написанию сценариев в компании SAPIEN Technologies, соавтор книги Windows PowerShell: TFM (SAPIEN Press, 2007). С ним можно связаться по адресу www.ScriptingAnswers.com.
© 2008 Корпорация Майкрософт и компания CMP Media, LLC. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.