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

Рис. 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. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.