Глава 10. Конвейер, глубже

на данный момент вы научились достаточно действенно работать с конвейером PowerShell. Запуск команд (скажем, Get-Process | Sort-Object VM -desc | ConvertTo-Html | Out-File procs.html) это мощное средство, исполняющее в одной строке то, что ранее занимало несколько строк сценария. Но вы можете достичь большего. В данной главе мы глубже окунёмся в конвейер и раскроем некоторые из его самых мощных возможностей, которые позволяют нам верно передавать данные между командами с меньшими усилиями.

Конвейер: включение мощности при меньшем наборе

Одна из причин по которой мы настолько любим PowerShell состоит в том, что он позволяет нам быть более действенными при администрировании без написания сложных сценариев, чем нам приходится пользоваться в Bash. Основной ключ к мощным командам в одну строку лежит в том как работает конвейер PowerShell.

Давайте внесём ясность: вы можете пропустить эту главу и по- прежнему действенно применять PowerShell, но в большинстве случаев вам придётся прибегать к сценариям и программам в стиле Bash. Хотя возможности конвейера PowerShell и могут быть сложными их, вероятно, легче освоить, нежели более сложные навыки программирования. Научившись обращаться с конвейером, вы сможете работать намного более эффективно и при этом не прибегать к сценариям.

Основная идея здесь целиком состоит в том, чтобы заставить саму оболочку выполнять для вас больше работы, причём при как можно меньшем наборе текста. Мы полагаем, что вы будете удивлены тем, насколько хорошо сама оболочка способна осуществлять это!

Как PowerShell передаёт данные вниз по конвейеру

Всякий раз, когда вы связываете вместе две команды PowerShell должен выяснить как преобразовать вывод первой команды во ввод второй команды. В идущих следом примерах мы будем называть первую команду Командой A. Это производящая нечто команда. Вторая команда это Команда B, которой требуется принять вывод Команды A и затем выполнить свои собственные вещи:


CommandA | CommandB
		

К примеру, допустим что у вас имеется текстовый файл, который содержит по одному названию модуля в каждой строке, как это показано на Рисунке 10.1.

 

Рисунок 10-1


Создание в VS Code текстового файла, содержащего названия модулей, по одному названию в строке

Вы можете пожелать воспользоваться этими названиями модулей в качестве входных данных для команды, сообщая такой команде для какого модуля вы хотите исполнять её. Рассмотрим такой пример:


Get-Content .\modules.txt | Get-Command
		

Когда исполняется Get-Content, она помещает названия модулей в свой конвейер. Затем PowerShell принимает решение как получить это в команде Get-Command. Основная хитрость с PowerShell состоит в том, что такая команда способна принимать входные данные исключительно через параметры. PowerShell должен решить какой параметр Get-Command примет вывод Get-Content. Такой процесс определения носит название связывания параметра конвейера

, и именно это мы и обсуждаем в данной главе. PowerShell обладает двумя методами для получения вывода Get-Content в неком параметре Get-Command. Первый метод который испробует его оболочка носит название ByValue (по значению); если он не сработает, он испытает ByPropertyName.

План A: Ввод конвейера по значению

При помощи этого метода связывания параметра конвейера PowerShell просматривает тип производимого Командой A объекта и пытается определить будет ли параметр Команды B принимать такой тип объекта из своего конвейера. Вы можете определить это самостоятельно: Сначала направьте вывод Команды A в Get-Member чтобы увидеть какой тип производит Команда A. Затем изучите полную подсказку Команды B (то есть Get-Help Get-Command -Full) чтобы увидеть будут ли какие- то параметры принимать этот тип данных из своего конвейера ByValue. Рисунок 10.2 отображает что вы можете выявить.

 

Рисунок 10-2


Сопоставление вывода Get-Content с параметрами ввода Get-Command

Что вы обнаружите, так это то что Get-Content производит объекты с типом System.String (или для краткости String). Вы Get-Command не обладает параметром, который принимает String из своего конвейера ByValue. Основная проблема состоит в том, что имеется параметр -Name, который согласно имеющейся подсказки "определяет массив названий. Данный командлет получает только команды, которые обладают указанным названием." Это вовсе не то что нам нужно - наш текстовый файл, а тем более наши объекты String, это названия модулей, а не названия команд. Если мы исполним


Get-Content .\modules.txt | Get-Command
		

попробует выполнить выборку команд с названиями Microsoft.PowerShell.Archive и так далее, что, скорее всего, не сработает.

Когда из своего конвейера множество параметров принимают один и тот же тип, все параметры получат одно и то же значение. Поскольку параметр -Name из своего конвейера ByValue принимает String, для любых практических целей никакие иные параметры не способны выполнять это. Что разбивает наши надежды на попытку передать названия модулей из своего текстового файла в Get-Command.

В данном случае вход конвейера работает, однако не достигает необходимых результатов, на которые мы надеялись. Давайте рассмотрим другой пример, в котором мы достигаем желаемого результата. Вот необходимая командная строка:


Get-Content ./modules.txt | Get-Module
		

Давайте направим получаемый из Команды A вывод в Get-Member и изучим полную подсказку для Команды B. Рисунок 10.3 показывает что вы обнаружите.

 

Рисунок 10-3


Связывание вывода Get-Content с параметром Get-Module

Get-Content производит объекты с типомString Get-Module способен принимать такие объекты string из своего конвейера ByValue; это он и выполняет для своего параметра -Name. Согласно своей подсказки, данный параметр "определяет названия и шаблоны имён модулей, которые получает данный командлет." Иными словами, Команда A получает один или более объектов String, а Команда B пытается обнаружить модуль со значением названия в данной строке.

[Совет]Совет

По большей части, использующие одно и то же существительное команды (как Get-Process и Stop-Process), обычно способны выполнять передачу ByValue друг для друга. Уделите некоторое время для того, чтобы увидеть сможете ли вы направить вывод Get-Process в Stop-Process.

Давайте рассмотрим ещё один пример:


Get-ChildItem -File | Stop-Process -WhatIf
		

С первого взгляда может показаться что в этом нет никакого смысла. Но давайте проверим это, направив вывод Команды A в Get-Member и повторно рассмотрев подсказку для Команды B. На Рисунке 10.4 показано что вы должны обнаружить.

 

Рисунок 10-4


Изучение вывода Get-ChildItem и параметров ввода Stop-Process

Get-ChildItem производит объекты с типом FileInfo (технически говоря, System.IO.FileInfo, но вы обычно можете применять последний фрагмент TypeName в качестве сокращения). К сожалению, нет отдельного параметра Stop-Process, который способен принимать объект FileInfo. Наш подход ByValue завершается неудачей и PowerShell предпримет попытку своего резервного плана: ByPropertyName.

План B: Ввод конвейера по имени свойства

При таком подходе вы всё ещё рассматриваете подключение получаемого из Команды A вывода к параметрам Команды B. Однако ByPropertyName слегка отличается от ByValue. При этом резервном методе имеется возможность вовлекать большое число параметров Команды B. И снова, направьте вывод Команды A в Get-Member, а затем просмотрите синтаксис для Команды B. Рисунок 10.5 отображает что вы должны обнаружить: вывод Команды A обладает одним свойством, чьё название соответствует некому параметру Команды B.

 

Рисунок 10-5


Установление соответствия свойств параметрам

Многие парни впадают в ступор от того что здесь происходит, а потому давайте проясним насколько тривиальна сама оболочка: она ищет названия свойств, которые соответствуют именам параметров. И ничего более. Поскольку само свойство Name в результате синтаксического анализа даёт тот же самый результат что и наш параметр -Name, наша оболочка пытается соединить их.

Однако она не может сделать это прямо сейчас; сначала её необходимо убедиться что это параметр -Name примет ввод из своего конвейера ByPropertyName. Чтобы выполнить такое определение бросим взгляд на полную подсказку, которая показана на Рисунке 10.6.

 

Рисунок 10-6


Проверка того, будет ли параметр -Name команды Stop-Process принимать вод конвейера ByPropertyName

В данном случае -Name на самом деле принимает ввод из конвейера ByPropertyName, а потому это подключение сработает. Теперь вот в чём наша хитрость: в отличии от ByValue, который вовлекает лишь один параметр, ByPropertyName соединяет все совпадающие свойства и параметры (что предоставляет возможность каждому разработанному параметру принимать вывод конвейера ByPropertyName). В текущем примере мы имеем совпадение только Name и -Name. И что в результате Изучите Рисунок 10.7.

 

Рисунок 10-7


Попытка отправки Get-ChildItem в Stop-Process

Мы наблюдаем кучу сообщений об ошибках. Основная проблема состоит в том, что названия файлов обычно такие вещи как chapter7.zip и computers.txt, в то время как исполняемые объекты должны выглядеть как pwsh. Stop-Process имеет дело лишь с такими исполняемыми названиями. Однако даже хотя наше свойство Name соединено через имеющийся конвейер с параметром -Name, все значения внутри свойства Name н существенны для параметра -Name, что и влечёт за собой ошибки.

Давайте взглянем на более удачный пример. Создадим в Visual Studio Code простой файл CSV, воспользовавшись примером с Рисунка 10.8.

 

Рисунок 10-8


Создание этого файла CSV в Visual Studio Code

Сохраните это файл как aliases.txt. Теперь вернитесь в свою оболочку, попробуйте его импортировать, как это показано на Рисунке 10.9. Вам также следует отправить получаемый в Import-Csv вывод в Get-Member с тем, чтобы вы могли изучить имеющихся участников вывода.

 

Рисунок 10-9


Импорт файла CSV и проверка его участников

Вы отчётливо можете наблюдать, что имеющиеся в файле CSV столбцы стали свойствами, а каждая строка данных в этом файле CSV превратилась в некий объект. Теперь изучим имеющуюся подсказку для New-Alias, как это показано на Рисунке 10.10.

 

Рисунок 10-10


Соответствие свойств названиям параметров

Оба свойства (Name и Value) соответствуют именам в New-Alias. Очевидно, это было осуществлено намеренно - при создании CSV файла вы можете называть эти столбцы как угодно. Теперь проверьте принимают ли -Name и -Value ввод конвейера ByPropertyName, как это отражено на Рисунке 10.11.

 

Рисунок 10-11


Проверка того, что Name и Value являются параметрами, которые принимают входные данные ByPropertyName

Да, это делают оба параметра, что означает что наша хитрость сработала. Попробуем выполнить свою команду


Import-Csv .\aliases.txt | New-Alias
		

Получаемым результатом являются три новых псевдонима с названиями d, sel и go, которые указывают, соответственно, на команды Get-ChildItem, Select-Object и Invoke-Command. Это мощная технология передачи данных из одной команды в другую, а также для выполнения сложных задач с минимальным числом команд.

Когда вещи не выстраиваются в линию: персонализация свойств

Наш пример с CSV это круто, но когда вы создаёте свои входные данные с нуля достаточно просто выровнять имена свойств и параметров. Когда вы вынуждены иметь дело с созданными для вас объектами или с созданными кем- то иным данными, ситуация усложняется.

Для своего следующего примера давайте поиграем с новой командой: New-ADUser. Она является частью модуля Active Directory. Вы можете получить этот модуль в компьютере клиента, установив Microsoft RSAT (Remote Server Administration Tools, Инструменты удалённого администрирования серверов). Однако на этот раз не беспокойтесь о выполнении этой команды: следуйте за нашим примером.

New-ADUser обладает параметрами, разработанным для принятия сведений о новом пользователе Active Directory. Вот некие образцы:

  • -Name (обязательный)

  • -samAccountName (технически говоря, не обязательный, однако вам следует предоставлять его чтобы превращать данную учётную запись в используемую)

  • -Department

  • -City

  • -Title

Мы можем показать и прочие, но давайте поработаем с этими. Все они принимают ввод из конвейера ByPropertyName.

Для этого примера вы снова предполагаете что у вас имеется файл CSV, однако он поступает из подразделения трудовых ресурсов или персонала. Вы десятки раз указывали им желаемый формат файла, однако они упорно давали нечто похожее, но не совсем верное, как это отражено на Рисунке 10.12.

 

Рисунок 10-12


Работа с файлом CSV, произведённого трудовыми ресурсами

Как вы можете наблюдать на Рисунке 10.12, ваша оболочка способна прекрасно импортировать этот файл CSV, получая в результате три объекта с четырьмя свойствами для каждого. Основная проблема состоит в том, что свойство dept не выравнивается с имеющимся параметром -Department для New-ADUser, свойство login бессмысленно, и у вас нет samAccountName или Name - тех двух, которые необходимы если вы желаете иметь возможность запустить эту команду чтобы создать новых пользователей:


PS C:\> import-csv .\newusers.csv | new-aduser
		

Как мы можем исправить это? Вы можете открыть данный файл CSV и исправить его, однако это означает много работы вручную с большими затратами времени, а вся цель PowerShell состоит в снижении ручного труда. Почему бы вместо этого не настроить нашу оболочку на исправление этого? Рассмотрим следующий пример:


PS C:\> import-csv .\newusers.csv |
>> select-object -property *,
>>  @{name='samAccountName';expression={$_.login}},
>>  @{label='Name';expression={$_.login}},
>>  @{n='Department';e={$_.Dept}}
>>
login          : TylerL
dept           : IT
city           : Seattle
title          : IT Engineer
samAccountName : TylerL
Name           : TylerL
Department     : IT
login          : JamesP
dept           : IT
city           : Chattanooga
title          : CTO
samAccountName : JamesP
Name           : Jamesp
Department     : IT
login          : RobinL
dept           : Custodial
city           : Denver
title          : Janitor
samAccountName : RobinL
Name           : RobinL
Department     : Custodial
		

Это достаточно вычурный синтаксис, а потому давайте разберём его по частям:

  • Мы пользуемся Select-Object и его параметром -Property. Мы начинаем с определения свойства *, что означает "все имеющиеся свойства". Обратите внимание, что за * следует запятая, что означает, что мы продолжаем свой список свойств.

  • Затем мы создаём хэш- таблицу, которая является конструкцией, начинающейся с @{ и завершающейся }. Хэш таблицы состоят из одной или более пар ключ- значение, а Select-Object был запрограммирован на просмотр конкретных предоставляемых ему ключей.

  • Самый первым ключом, который требуется для Select-Object может быть Name, N, Label или L, а соответствующим значением для такого ключа выступает собственно название того свойства, которое мы хотим создать. В своей первой хэш таблице мы определяем samAccountName; во второй Name; а в третьей Department. Это соответствует названиям необходимых для New-ADUser параметров.

  • Второй необходимый для Select-Object параметр может быть Expression либо E. Значение для этого ключа это некий блок сценария, содержащийся внутри фигурных скобок {}. Внутри данного блока сценария вы пользуетесь особым держателем места $_ чтобы ссылаться на уже имеющийся объект на входе из конвейера (оригинальная строка данных из нашего файла CSV), за которым следует точка. Этот держатель места $_ позволяет вам получать доступ к одному свойству направляемого конвейером объекта, или к столбцу нашего файла CSV. Он определяет значения содержимого для наших новых свойств.

    [Совет]Попробуйте прямо сейчас

    Двиньтесь далее и создайте необходимый файл CSV, отображённый на Рисунке 10.12. Затем попробуйте исполнить в точности ту команду, которую мы выполнили ранее - вы можете набрать её в точности как она показана.

Что мы сделали, так это мы получили содержимое своего файла CSV - вывод из Import-CSV - и видоизменили его, причём динамически, в своём конвейере. Наш новый вывод соответствует тому, что желает обнаружить New-ADUser, а потому мы теперь можем создать новых пользователей выполняя такую команду:


PS C:\> import-csv .\newusers.csv |
>> select-object -property *,
>>  @{name='samAccountName';expression={$_.login}},
>>  @{label='Name';expression={$_.login}},
>>  @{n='Department';e={$_.Dept}} |
>> new-aduser
>>
		

Получаемый синтаксис может быть слегка уродливым, но эта технология невероятно мощная. Это можно применять во многих прочих местах PowerShell, и вы обнаружите это снова и снова в примерах, содержащихся в подсказках PowerShell; запускаем Help Select -Example и ищем самостоятельно.

Работа с Azure PowerShell

Для всей остающейся части этой главы мы предполагаем, что у вас имеется установка Azure PowerShell. А потому давайте поработаем над этим. Если у вас нет подписки, вы можете подписаться для пробной версии здесь: https://azure.microsoft.com/en-us/free/. Если эта ссылка устарела, выполните поиск trial Azure PowerShell.

После того как вы получили подписку, убедитесь что у вас имеется установленным модуль Az. Вернитесь к Гаве 7, а соответствующая команда это


Install-Module az
		

Теперь, когда у вас имеется установленным Az, запустите Connect-AzAccount и следуйте его инструкциям: в данный момент он должен открыть браузер и ввести код. Он должен сообщить вам что вы подключены выводя на печать вашу электронную почту, название подписки и некую иную информацию.

Когда у вас имеется множество ассоциированных с вашей учётной записью подписок, вы можете соединиться не с той подпиской. Если это так, убедитесь что вы выбираете верную подписку. Когда названием вашей подписки выступает Visual Studio Enterprise, ваша команды выглядит как Select-AzSubscription -SubscriptionName 'Visual Studio Enterprise'.

Заключённые в скобки команды

 

Рисунок 10-13


Считывание подсказки параметра Module для Get-Command

Выделение значения из отдельного свойства

Лабораторные занятия

Ответы лабораторных занятий

Дальнейшее исследование