Windows PowerShell: Превращаем команду в повторно используемый инструмент

Создание «пакетов» команд и командлетов Windows PowerShell позволяет не делать одну и ту же работу дважды.

Дон Джонс

Перед неопытным пользователем Windows PowerShell открывается масса возможностей: можно начать с выполнения простых команд, потом перейти к более сложным, а в конце преобразовать их во что-то похожее на настоящий командлет. Это называется расширенными функциями, и в просторечии их обзывают «скриптовые командлеты» (то есть командлеты, базирующиеся на сцераниях).

Допустим, вам нужно получить важные сведения о компьютерах, а именно версию Windows, серийный номер BIOS, версию последнего пакета исправлений и архитектуру процессора. Эту информацию можно получить с помощью трех команд:

Get-WmiObject –class Win32_OperatingSystem –computername SERVER-R2 | Select-Object –property __SERVER,BuildNumber,Caption,ServicePackMajorVersion
Get-WmiObject –class Win32_BIOS –computername SERVER-R2 | Select-Object –property SerialNumber
Get-WmiObject –class Win32_Processor –computername SERVER-R2 | Select-Object –property AddressWidth

Проблема состоит в том, что эти команды генерируют три разных набора результатов. Их нельзя просто передать по конвейеру в CSV-файл для хранения или в один HTML-файл, чтобы представить сведения в виде веб-страницы. Лучше объединить данные в одну параметризованную команду, которую мог бы использовать и не очень опытный пользователь. Эта команда должна:

  • Принимать из конвейера одно или несколько имен компьютеров в виде строк, например так:
Get-Content names.txt | Get-OSInfo | ConvertTo-HTML | Out-File info.html
  • Принимать одно или несколько имен компьютеров в параметре –computername, например:
Get-OSInfo –computername Server-R2,ServerDC4 | Format-Table
  • Принимать из конвейера один или несколько объектов, в свойстве –computername которого хранится имя компьютера, например так:
Get-ADComputer –filter * -searchbase "ou=West,dc=company,dc=com" | Select-Object @{label='computername';expression={$_.Name}} | Get-OSInfo | Export-CSV inventory.csv

В этом случае вам не придется волноваться, откуда поступают имена компьютеров, а также не нужно думать, как возвращать данные. Обо всем позаботится оболочка.

Кроме того, хорошо иметь параметр -logfile, в котором передается путь и имя файла журнала. Так как для подключения и запроса информации в команде придется задействовать Windows Management Instrumentation (WMI), может оказаться, что не удастся «достучаться» к некоторым компьютерам. Но их имена все равно придется записать в журнал, чтобы можно было разобраться с этим позже или попытаться повторно подключиться к этим компьютерам.

Обработка ошибок

Последнюю упомянутую задачу можно решить, используя конструкцию Try…Catch в PowerShell — просто разместите в ней первый запрос WMI. Затем определите параметр -ErrorAction Stop, чтобы перехватить все ошибки, возвращенные командой. Присвойте соответствующее значение контрольной переменной, которая служит для отслеживания ошибок, чтобы сценарий «знал», следует ли пытаться выполнить следующие два WMI-запроса:

$continue = $True
Try {
  Get-WmiObject –class Win32_BIOS –computername $computername –EA Stop
} Catch {
  $continue = $False
  $computername | Out-File –path $logfile –append
}

Разбираемся с входными данными

Это сложная часть. Входные данные поступают по одному из двух каналов:по конвейеру или через параметр. Вообще-то можно перенаправить поступившую по конвейеру информацию в параметр, используя связывание параметров в «командлетном» стиле. Вот как это выглядит:

Function Get-OSInfo {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
  [string[]]$computername,
  [string]$logfile 
 )
  BEGIN {}
  PROCESS {}
  END {}
}

Блок PROCESS в данном командлете выполняется как минимум единожды — это как раз тот случай, когда нет входных данных из конвейера. Если же имеются данные конвейера, то блок PROCESS будет выполнен по одному разу для каждого поступившего по конвейеру элемента, причем элемент будет размещен в переменной $computername.

Тут-то и зарыта собака: если входные данные передаются только через параметр, как во втором примере, блок PROCESS будет выполнен только раз. Переменная $computername будет содержать все имена компьютеров, переданные в параметре. Вам придется перечислять или разбирать их самостоятельно. Если же элементы поступают по конвейеру, обрабатывать придется по одному элементу за раз. А еще вы получите их всех в переменной $computername.

Хитрость заключается в создании второй функции, которая сделает всю работу. Используйте расширенную функцию приема входных данных любого вида и разбиения процесса на передачу одного имени компьютера за раз. Далее вызывайте вторую «рабочую» функцию для каждого имени компьютера. Вот пример «захода» в блок PROCESS основной функции:

PROCESS {
  if ($PSBoundParameters.ContainsKey('computername')) {
    foreach($computer in $computername) {
      OSInfoWorker –computername $computer –logfile $logfile
    }
  } else {
    OSInfoWorker –computername $computername –logfile $logfile
  }
}

Финальные штрихи

Преодолев основные технические сложности, можно переходить к сведению созданного и добавлению нескольких дополнительных деталей, таких как очистка старых журналов перед выполнением функции (для чего отлично подходит блок BEGIN). Готовый сценарий состоит из двух функций и немного длинноват. Однако очень немногое в нем является «программированием». Большая часть сценария — команды Windows PowerShell, которые вы обычно выполняете в командной строке. Они просто заключены в декларативную структуру, чтобы все это вело себя как командлет.

Полный текст приведен на моем веб-сайте (ow.ly/39YcX) вместе с видеороликом создания конечного кода. Это шаблон, который можно с легкостью повторно использовать. Основная функция работает только с входными данными, поэтому ее можно применять практически в неизменном виде. Вам достаточно просто изменить параметры в соответствии со своими потребностями. Реальная работа выполняется в отдельной функции.

В следующем выпуске колонки я покажу, как упаковать это все в модуль сценария, который можно с легкостью распространять. Я также расскажу, как создать общедоступное место хранения модулей сценариев, чтобы они стали доступными для использования вашим коллегам-администраторам.

Don Jones

Дон Джонс (Don Jones)— основатель компании Concentrated Technology; он отвечает на вопросы о Windows PowerShell и других технологиях на сайте ConcentratedTech.com. Он пишет для сайта Nexus.Realtimepublishers.com, на котором бесплатно распространяются многие его книги в электронном виде.

 

Дополнительно

К статье за этот месяц прилагается видеоролик, а также пример кода для загрузки. Эти материалы доступны здесь.

Дополнительные материалы