Глава 15. Работа со множеством объектов по одному за раз
Содержание
Весь смысл PowerShell состоит в автоматизации администрирования, а это часто означает, что вам необходимо выполнять некоторые задачи с множеством целей. Возможно, вы пожелаете запустить несколько виртуальных машин, выполнять доставку в различные хранилища blob- объектов, изменять полномочия нескольких пользователей и тому подобное. В этой главе вы познакомитесь с двумя различными методами выполнения этих и прочих задач со множеством целей: пакетными командлетами и перечислением объектов. Вне зависимости от применяемой ОС сами понятия и методы здесь одинаковы.
![]() | Замечание |
---|---|
Это чрезвычайно сложная глава и, вероятно, она обманет ваши надежды. Будьте терпеливы, пожалуйста - сами по себе и по отношению к нам - и поверьте, к концу мы всё расставим по местам. |
Как вы уже изучали в некоторых предыдущих главах, многие командлеты PowerShell способны принимать пакеты, или
коллекции
, объектов для работы с ними. в Главе 6, например, вы изучили что объекты могут конвейером отправляться из одного командлета в другой, как
здесь (будьте добры не запускать это ни в какой системе, если только вы не хотите получить очень плохой день):
Get-Service | Stop-Service
Это пример пакетного управления при помощи командлета. В данном случае Stop-Process
специально спроектирован на приём одного объекта процесса из своего конвейера и затем останавливать его.
Set-Service
, Start-Process
,
Move-Item
и Move-AdObject
, всё это примеры
командлетов, которые принимают один или более объектов на входе и затем выполняют некую задачу или действие с каждым из
них. PowerShell знает как работать с пакетами объектов и способен обрабатывает их для вас при относительно простом синтаксисе.
Такие пакетные командлеты (batches cmdlet, это наше название - и это не официальный термин) это наш предпочтительный способ выполнения задач. Например, давайте предположим, что нам требуется изменить значение типа запуска для множества служб:
Get-Service BITS,Spooler | Set-Service -startuptype Automatic
Одним из потенциальных недостатков такого подхода является то, что выполняющие некое действие командлеты часто не
производят никакого вывода, указывающего что они выполнили своё задание. Вы не будете обладать никаким визуальным выводом не
из какой из предыдущих команд, что может приводить в замешательство. Однако такие командлеты часто обладают параметром
-PassThru
, который сообщает им выводить все объекты, которые они получают на входе. Вы
можете заставить Set-Service
выводить те же службы, которые он изменил с тем, чтобы вы
были способны убедиться что они были изменены. Вот некий образец применения
-PassThru
с другим командлетом:
Get-ChildItem .\ | Copy-Item -Destination C:\Drivers -PassThru
Данная команда получает все имеющиеся элементы из своего текущего каталога. Эти объекты затем конвейером отправляются в
Copy-Item
, который далее копирует эти элементы в свой каталог
C:\Drivers
. Поскольку в самом конце мы помещаем соответствующий параметр
-PassThru
, на вашем экране будет отражено что он выполняет. Если бы мы не сделали этого,
тогда после завершения это просто вернулось бы обратно в приглашение на ввод нашего PowerShell.
Испробуйте это сейчас
Скопируйте несколько файлов или папок из одного каталога в другой. Попробуйте это как с, так и без параметра
-PassThru
и обратите внимание на получаемое отличие.
Прежде чем мы начнём, имеются два момента, о которых нам надлежит знать:
-
Windows Management Instrumentation (WMI, инструментальные средства управления средой Windows) не работают с PowerShell 7. Вы обязаны работать с командами Common Information Model (CIM, Общей информационной модели), которая в целом работает аналогичным образом.
-
Раздел 15.2 предназначен исключительно для тех, кто применяет Windows. Мы делаем всё возможное, чтобы всё, что делается в данной книге переносилось бы между платформами. Но существуют случаи, когда это невозможно, и это один из них.
К сожалению, у нас не всегда имеются командлеты, которые мы можем брать всякий раз, когда требуется некое действие, и это
так когда речь идёт об элементах, которыми мы можем управлять через CIM. К примеру, рассмотрим имеющийся класс CIM
Win32_NetworkAdapterConfiguration
. Этот класс представляет ту конфигурацию, которая
связана с сетевым адаптером (адаптеры способны обладать множеством конфигураций, однако в данном случае давайте предположим,
что они обладают лишь одной конфигурацией на брата, что распространено в компьютерах клиентов). Скажем, нашей целью является
разрешение DHCP во всех сетевых адаптерах Intel наших компьютеров - мы не хотим включать его в своих RAS или прочих
виртуальных адаптерах.
![]() | Замечание |
---|---|
Мы ознакомим вас с краткой сюжетной линией, признанной помочь вам разобраться с тем, как парни пользуются PowerShell. Некоторые вещи могут вам показаться избыточными, но потерпите - ценен сам опыт. |
Мы можем начать с попытки запроса конфигураций для нужного нам адаптера, что позволит нам получить нечто подобное следующему выводу:
DHCPEnabled : False
IPAddress : {192.168.10.10, fe80::ec31:bd61:d42b:66f}
DefaultIPGateway :
DNSDomain :
ServiceName : E1G60
Description : Intel(R) PRO/1000 MT Network Connection
Index : 7
DHCPEnabled : True
IPAddress :
DefaultIPGateway :
DNSDomain :
ServiceName : E1G60
Description : Intel(R) PRO/1000 MT Network Connection
Index : 12
Для получения данного вывода нам потребуется выполнить запрос на соответствующий объект CIM и отфильтровать его чтобы он
содержал в своём описании лишь конфигурации с Intel. Это выполняет приводимая ниже
команда (обратите внимание, что символ %
действует как символ подстановки внутри
соответствующего синтаксиса фильтра WMI):
PS C:\> Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | -Filter "description like '%intel%'" | Format-List
Испробуйте это сейчас Вы можете следовать запускаемым нами в данном разделе командам. Чтобы они заработали, возможно, вам придётся слегка видоизменить эти команды. Например, если в вашем компьютере отсутствуют сетевые адаптеры производства Intel, вам придётся соответствующим образом изменить критерий фильтрации.
Когда мы получим такие объекты конфигурации в своём конвейере, мы хотим разрешить в них DHCP (вы можете наблюдать, что один из наших адаптеров не обладает включённым DHCP). Мы можем начать с просмотра какого- то командлета с названием, подобным Enable-DHCP. К сожалению, мы не найдём его, потому как нет такого в природе. Не существует никаких командлетов, которые способны иметь дело с объектами CIM непосредственно в пакетах.
Испробуйте это сейчас На основании того, с чем мы ознакомились на данный момент, какой именно командой вы бы воспользовались для поиска команды, которая содержит в своём названии DHCP?
Наш следующий шаг состоит в том, чтобы посмотреть обладает ли сам объект неким методом, который способен разрешать DHCP.
Для выявления этого мы исполняем команду Get-CimClass
и раскрываем соответствующее
свойство CimClassMethods
:
PS C:\> (Get-CimClass Win32_NetworkAdapterConfiguration).CimClassMethods
В самом верху мы видим некий метод с названием EnableDHCP
(Рисунок 15.1)
Наш следующий шаг, который пробует множество новичков PowerShell, состоит в отправке в этот метод конвейером полученных объектов конфигурации:
PS C:\> Get-CimInstance win32_networkadapterconfiguration -filter "description like '%intel%'" | EnableDHCP
Печально, но это не работает. Вы не можете отправлять конвейером объекты в метод; вы можете отправлять конвейер лишь в
командлет. EnableDHCP
не является командлетом PowerShell. Вместо этого, он некое
действие, которое непосредственно присоединено к самому этому объекту конфигурации.
Хотя и не существует никакого "пакетного" командлета с названием Enable-DHCP
,
вы можете применить универсальный командлет с названием Invoke-CimMethod
. Данный
командлет специально для приёма пакетных или CIM объектов, таких как наши объекты
Win32_NetworkAdapterConfiguration
и для вызова одного из методов, подключённого к таким
объектам. Вот исполняемая нами команда:
PS C:\> Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -filter "description like '%intel%'" | Invoke-CimMethod -MethodName EnableDHCP
Нам следует иметь в виду несколько моментов:
-
Значение названия метода не следует в круглых скобках.
-
Значение названия метода не чувствительно к регистру букв.
-
Invoke-CimMethod
может принимать лишь один вид объекта WMI за раз. В данном случае, мы отправляем ему только объектыWin32_NetworkAdapterConfiguration
, что означает, что это сработает как ожидалось. Нормально отправлять более одного объекта за раз (фактически, это общее место), однако все эти объекты обладают одним и тем же типом. -
Мы можем применять в
Invoke-CimMethod
-WhatIf
и-Confirm
. Однако вы не можете пользоваться ими при вызове метода непосредственно из объекта.
Вывод Invoke-CimMethod
очень прост для понимания. Он снабжает нас двумя моментами:
неким возвращаемым значением и тем компьютером, в котором он выполнен (когда имя компьютера пустое, он исполнялся в
localhost
).
ReturnValue PSComputerName
----------- --------------
84
Значение числа ReturnValue
сообщает нам значение результата данной операции.
Быстрый поиск в вашем любимом механизме поиска для Win32_NetworkAdapterConfiguration
снабдит нас страницей документации, и мы затем сможем кликнуть по искомому методу EnableDHCP
чтобы увидеть возможные возвращаемые значения и их значения. (Рисунок 15.2) показывает что мы разыскали.
Значение 0
означает успех, в то время как 84
означает, что данный IP не включён в данной конфигурации адаптера и DHCP не может быть разрешён. Однако какой из битов наших
выходных данных относится к какой из наших двух конфигураций сетевых адаптеров Трудно сказать, потому как выходные данные не
сообщают нам какой именно объект конфигурации их создаёт. Это прискорбно, но именно так работает CIM.
Invoke-CimMethod
срабатывает в большинстве ситуаций в которых мы имеем объект CIM,
который обладает методом, который мы желаем выполнить. Он также срабатывает при запросе объектов с удалённых компьютеров.
Наше основное правило это "Если вы можете получить нечто при помощи Get-CIMInstance
,
тогда Invoke-CimMethod
способен выполнять его методы".
К сожалению, мы сталкиваемся с ситуациями, когда у нас есть командлет, который способен производить объекты, но мы не знаем никакого пакетного командлета, в который мы способны конвейером отсылать такие объекты чтобы предпринимать некий вид действий. Также мы сталкивались с ситуациями, при которых командлет не получает из своего конвейера никаких входных данных. В любом случае, вы всё ещё способны выполнять всякую задачу, которую пожелаете осуществить, но вам придётся вернуться к походу с бо́льшей детализацией, предписывая компьютеру перечислять свои объекты и выполнять свою задачу по одному за раз для каждого из объектов. PowerShell предлагает два способа выполнения этого: один заключается в применении командлета, а другой при помощи построения сценария. В данной главе мы сосредоточимся на первом указанном методе, поскольку он самый простой. Вы всегда должны пытаться применять командлет, а не пробовать самостоятельно писать его. Второй подход мы сохраним для Главы 19, в которой мы погрузимся во встроенный язык программирования PowerShell.
Поскольку в этой главе мы обсуждаем процессы, в качестве своего образца мы обсудим конкретный командлет. Давайте рассмотрим его синтаксис:
Get-Help Get-Process -Full
Что выдаст нам всё... но пролистайте до раздела с названием "Id". Вы обнаружите, что в некоторых параметрах
указано, что они получают входные данные, но в скобках указано ByPropertyName
. Это
означает, что когда мы конвейером передаём некий объект в этот командлет, а у него имеется название свойства, например,
именуемое как Id
, данный командлет воспользуется следующим:
-Id <System.Int32[]>
Specifies one or more processes by process ID (PID). To specify multiple IDs, use commas to separate the IDs.
To find the PID of a process, type 'Get-Process'.
Required? true
Position? named
Default value None
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? false
-IncludeUserName <System.Management.Automation.SwitchParameter>
Indicates that the UserName value of the Process object is returned with results of the command.
Required? true
Position? named
Default value False
Accept pipeline input? False
Accept wildcard characters? false
Однако, что если мы просто хотим переслать конвейером список строк, которым выступают названия тех процессов, которые
мы бы желали создать? У нас бы не было такой возможности, потому как наш параметр Name
не поддерживает иной тип передачи конвейером, кроме как ByValue
. Пройдём далее и
попробуем это. Давайте взглянем на команду New-AzKeyVault
. Мы поместим свои значения в
некий массив и отправим их конвейером в свою команду New-AzKeyVault
:
@( "vaultInt1", "vaultProd1", "vaultInt2", "vaultProd2" ) | New-AzKeyVault
Это предоставит нам следующий менее- чем- идеальный для прочтения текст:
New-AzKeyVault: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
{New-AzKeyVault: Полученный на входе объект не может быть связан ни с какими параметрами для данной команды либо по причине того что эта команда не способна получать входные данные конвейером, либо эти входные данные и их свойства не соответствуют никакому из установленных параметров, принимаемых в качестве входных.}
Давайте рассмотрим подробнее то, как мы всё же можем достигать своей цели даже когда наш командлет не поддерживает то, что мы пытаемся сделать.
В данный момент нам придётся принять решение. Вполне вероятно, что вы запускаем свою команде неверно, поэтому нам придётся
решить желаем ли мы потратить много времени на решение этого. Также вполне вероятно, что New-AzKeyVault
просто не поддерживает то, что бы мы желали выполнить, и в этом случае мы бы затратили много времени чтобы попытаться исправить
нечто, чем мы не имеем возможности управлять.
Нам требуется создать текстовый файл со списком значений имён, которые мы бы желали создать. наш
vaultsToCreate.txt
выглядит так:
vaultInt1
vaultInt2
vaultProd1
vaultProd2
Наш обычный выбор для этого случая состоит в том, чтобы попробовать разные подходы. Мы намерены попросить у компьютера
(ладно, у оболочки) перенумеровать эти объекты (в нашем случае строки) по одной за раз, поскольку наша команда
New-AzKeyVault
принимает лишь по одному объекту за раз и исполнить
New-AzKeyVault
для этих объектов. Для этого мы воспользуемся своим командлетом
ForEach-Object
:
Get-Content -Path vaultsToCreate.txt | ForEach-Object { New-AzKeyVault -ResourceGroupName manning -Location 'UK South' -Name $_ }
Для четырёх созданных нами ресурсов мы получим четыре результата, которые выглядят так (здесь приводится лишь часть вывода, ибо получаемые результаты могут быть достаточно длинными):
Vault Name : vaultInt1
Resource Group Name : manning
Location : Australia Central
Resource ID : /subscriptions/*****/resourceGroups/manning/providers/Microsoft.KeyVault /vaults/vaultInt1
Vault URI : https://vaultint1.vault.azure.net/
Tenant ID : *********
SKU : Standard
Enabled For Deployment? : False
Enabled For Template Deployment? : False
Enabled For Disk Encryption? : False
Soft Delete Enabled? :
В соответствующей документации мы обнаруживаем, что когда мы получаем обратно подобный приводимому отклик, это означает успех, что подразумевает, что мы достигли своей цели. Однако давайте рассмотрим эту команду более подробно.
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Process {
New-AzKeyVault -ResourceGroupName manning -Location 'UK South' -Name $_
}
В данной команде много чего происходит. Первая строка должна иметь смысл: для выборки значений названий своих сейфов
(vaults), которые мы поместили в текстовый файл, мы пользуемся Get-Content
. Мы конвейером
отправляем эти объекты string
в свой командлет
ForEach-Object
:
-
Прежде всего, мы видим название своего командлета:
ForEach-Object
. -
Затем, мы применяем параметр
-Process
для задания блока сценария. Мы изначально не набрали название параметра-Process
, потому как это позиционный параметр. Однако этот блок сценария - всё что содержится внутри фигурных скобок - это значение для данного параметра-Process
. Мы пошли дальше и включили имя этого параметра при переформатировании своей команды для облегчения восприятия. -
ForEach-Object
исполняет свой блок сценария по разу для каждого из объектов, которые были конвейером отправлены вForEach-Object
. При каждом исполнении блока сценария, соответствующий следующий направляемый конвейером объект помещается в специальный заполнитель$_
, который мы видим передаваемым в качестве параметраName
вNew-AzKeyVault
.
{Прим. пер.: по- существу, здесь вместо объекта мы применяем структуру с признаком (trait), типичный современный подход, например, применяемый в Rust и поясняемый, скажем, в нашем переводе Ускоряем ваш Python при помощи Rust Максвелл Флиттон, декабрь 2021, (с) Packt Publishing.}
В своих предыдущих главах мы обсудили применение заданий PowerShell для параллельного запуска команд чтобы вы сохраняли
время. Для спрямления такой возможности сохранения времени PowerShell 7 предложил новый параметр в
ForEach-Object
: -Parallel
. С ним лучше
разобраться на неком примере с использованием повсеместно знаменитого командлета Measure-Command
,
который позволяет вам выполнять замеры вещей любого рода., однако мы будем применять его для определения времени длительности
исполнения блока сценария. Это выглядит следующим образом:
Measure-Command { <# The script we want to time #> }
Итак, давайте попробуем. Для первого раза мы испробуем нечто простое при помощи обычного
ForEach-Object
:
Get-Content -Path vaultsToCreate.txt | ForEach-Object -Process {
Write-Output $_
Start-Sleep 1
}
Всё что здесь требуется, так это вывести на печать каждую строку из этого файла и затем заснуть на одну секунду для
каждой строки. Если у нас есть в этом файле пять строк, скорее всего, вы сможете предвидеть сколько времени потребует это
исполнение, но давайте воспользуемся Measure-Command
:
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Process {
Write-Output $_
Start-Sleep 1
}
}
Когда мы выполним это, мы получим:
Days : 0
Hours : 0
Minutes : 0
Seconds : 5
Milliseconds : 244
Ticks : 52441549
TotalDays : 6.06962372685185E-05
TotalHours : 0.00145670969444444
TotalMinutes : 0.0874025816666667
TotalSeconds : 5.2441549
TotalMilliseconds : 5244.1549
Давайте конкретно рассмотрим значение Seconds
, равное
5
. Это имеет смысл, не так ли? Раз у нас имеются в нашем файле пять строк, и мы
обрабатываем по строке за раз, а при этом мы засыпаем на 1 секунду в каждой, мы ожидаем, что команда исполняется примерно
5 секунд.
Теперь давайте изменим ту же самую команду на применение вместо Process
параметра
Parallel
:
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Parallel {
Write-Output $_
Start-Sleep 1
}
}
Есть предположения? Давайте запустим:
Days : 0
Hours : 0
Minutes : 0
Seconds : 1
Milliseconds : 340
Ticks : 13405417
TotalDays : 1.55155289351852E-05
TotalHours : 0.000372372694444444
TotalMinutes : 0.0223423616666667
TotalSeconds : 1.3405417
TotalMilliseconds : 1340.5417
Секундочку! Это происходит по той причине, что Parallel
делает именно то, что
подразумевает его название - он запускает все блоки сценариев параллельно, а не последовательно. Поскольку у нас в файле
имеются пять элементов и мы исполняем все их параллельно, а также мы заставляем их все заснуть на 1 секунду, вся операция
занимает лишь около секунды. Это очень полезно для задач с длительным временем исполнения или в ситуациях, при которых у вас
имеется множество задач меньшего размера, которые вы бы хотели исполнять пакетно. Мы даже можем взять свой пример и
воспользоваться Parallel ForEache
:
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Parallel {
New-AzKeyVault -ResourceGroupName manning -Location 'UK South' -Name $_
}
Parallel
очень сильный параметр для Parallel
ForEach
, но в действительности он обладает некоторыми ограничениями, о которых вам надлежит знать. Для начала,
по умолчанию, Parallel ForEach
запустит параллельно лишь пять блоков сценариев. Это носит
название предела дросселирования (throttle limit) и может регулироваться при помощи
параметра ThrottleLimit
. Вернёмся обратно к тому файлу, которы мы применяли и убедимся что
в нём всего 10 строк. Получаемое отличие достаточно заметно:
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Process {
Write-Output $_
Start-Sleep 1
}
}
Без настройки значения предела дросселирования мы получаем 2 секунды:
Days : 0
Hours : 0
Minutes : 0
Seconds : 2
Milliseconds : 255
Ticks : 22554472
TotalDays : 2.6104712962963E-05
TotalHours : 0.000626513111111111
TotalMinutes : 0.0375907866666667
TotalSeconds : 2.2554472
TotalMilliseconds : 2255.4472
Однако, когда мы поднимем значение предела дросселирования до 10, мы получаем
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -ThrottleLimit 10 -Process {
Write-Output $_
Start-Sleep 1
}
}
наша команда завершается через 1 секунду!
Days : 0
Hours : 0
Minutes : 0
Seconds : 1
Milliseconds : 255
Ticks : 12553654
TotalDays : 1.45296921296296E-05
TotalHours : 0.000348712611111111
TotalMinutes : 0.0209227566666667
TotalSeconds : 1.2553654
TotalMilliseconds : 1255.3654
Parallel ForEach
это очень мощная функциональная возможность PowerShell. Вы
сбережёте много времени если будете пользоваться её преимуществом правильным способом.
Описанные в этой главе приёмы являются одними из самых сложных в PowerShell, и они часто вызывают наибольшие замешательство и разочарование. Давайте рассмотрим некоторые проблемы, с которыми обычно сталкиваются новички. Мы предложим некоторые альтернативные пояснения, которые помогут вам избегать те же самые проблемы.
Мы пользуемся термином пакетный командлет (batch cmdlet) или командлет действия (action cmdlet) для всякого командлета, который осуществляет некое действие для группы или собрания объектов за раз. Вместо того чтобы обладать инструкцией для вашего компьютера "пройтись по этому перечню вещей и выполнить это единственное действие для каждой из этих вещей", вы можете отправить в командлет всю группу целиком и сам командлет обработает их.
Microsoft в своих продуктах улучшает представительство таких командлетов, но они покрываю ещё пока не все 100% (и, скорее всего, оно не будет таковым на протяжении ещё многих лет из- за наличия множества сложных продуктов Microsoft). Но когда некий командлет имеется, мы предпочитаем пользоваться им. Тем не менее, прочие разработчики PowerShell предпочитают альтернативные способы, в зависимости от того, что они узнали первым и что они запомнили проще всего. Все последующие действия обладают одним и тем же результатом:
Get-Process -name *B* | Stop-Process # пакетный командлет
Get-Process -name *B* | ForEach-Object { $_.Kill()} # вызов ForEach-Object метода Kill()
Get-Process -Name *B* | ForEach-Object -Parallel { Stop-Process $_ } # вызов Stop-Process через ForEach-Object
Давайте рассмотрим как работает каждый из подходов
-
Пакетный командлет. Здесь для выборки всех процессов мы применяем
Get-Process
с неким B в названии и затем останавливаем их. -
ForEach-Object
вызывает метод Kill(). Этот подход аналогичен пакетному командлету, но вместо применения пакетного командлета мы через конвейер передаём значения процессов вForEach-Object
и просим для каждой службы выполнить Kill(). -
ForEach-Object
вызывает Stop-Process, применяя-Parallel
.
Чёрт возьми, имеется даже четвёртый подход - воспользоваться языком программирования сценариев PowerShell чтобы выполнить то же самое. В PowerShell вы обнаружите множество способов выполнить практически всё, причём ни один из них не является неправильным. Некоторые легче выучить, запомнить и повторить, нежели иные, поэтому мы сосредоточились на имеющихся в вашем распоряжении методах в том порядке, как мы это делали. Чем следует пользоваться? Это не имеет значения, поскольку не существует единственного верного способа. В конечном итоге, вы даже можете применять их сочетание, в зависимости от тех обстоятельств и возможностей, которые способна предложить вам сама оболочка для осуществления поставленной задачи.
Помните наш пример с Parallel ForEach
? Он выглядит следующим образом:
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -Parallel {
Write-Output $_
Start-Sleep 1
}
}
Теперь, допустим, vaultsToCreate.txt
обладает внутри себя 100 строками. Следует ли нам
установить значение ThrottleLimit
в 100
с тем,
чтобы наша операция завершилась за 1 секунду? Давайте попробуем:
Measure-Command {
Get-Content -Path vaultsToCreate.txt |
ForEach-Object -ThrottleLimit 100 -Parallel {
Write-Output $_
Start-Sleep 1
}
}
Это выдаёт нам результат в 3 секунды. Странно:
Days : 0
Hours : 0
Minutes : 0
Seconds : 3
Milliseconds : 525
Ticks : 35250040
TotalDays : 4.07986574074074E-05
TotalHours : 0.000979167777777778
TotalMinutes : 0.0587500666666667
TotalSeconds : 3.525004
TotalMilliseconds : 3525.004
Почему так медленно? Что ж, получается, что узким местом выступает ваша машина, которая способна выполнять только не
столь много вещей одновременно, прежде чем начнёт замедляться. Это похоже на Start-ThreadJob
,
который мы наблюдали в Главе 14. Один процесс способен выполнять лишь
столько вещей параллельно, прежде чем начнёт работать медленнее, чем их последовательное исполнение.
Это странная концепция, но представьте если бы вы одновременно работали над кучей задач. Вам придётся постоянно переключаться между этими задачами, чтобы одновременно выполнять их все. В некоторых ситуациях вы достигаете момента, когда вы были бы более действенны, если бы прежде чем приступать к новой задаче, вы бы просто дожидались пока не закончите с другими, которые уже выполняются. Обычно мы называем это "убывающей отдачей", имея в виду, что по мере того как вы пытаетесь всё больше выполнять одновременно, это становится менее правильным и даже способно отрицательно повлиять на результаты, если вы не будете осторожны.
Всегда помните, что передача в Get-Member
конвейером объектов извлекает методы:
Get-Process | Get-Member
Встроенная система подсказок PowerShell не документирует методы объектов. Например, когда вы получаете перечень для объекта
процесса, вы можете обнаружить, что присутствуют методы с названиями Kill
и
Start
:
TypeName: System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
BeginErrorReadLine Method void BeginErrorReadLine()
BeginOutputReadLine Method void BeginOutputReadLine()
CancelErrorRead Method void CancelErrorRead()
CancelOutputRead Method void CancelOutputRead()
Close Method void Close()
CloseMainWindow Method bool CloseMainWindow()
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
Kill Method void Kill(), void Kill(bool entireProcessTree)
Refresh Method void Refresh()
Start Method bool Start()
ToString Method string ToString()
WaitForExit Method void WaitForExit(), bool WaitForExit(int milliseconds)
WaitForInputIdle Method bool WaitForInputIdle(), bool WaitForInputIdle(int milliseconds)
Чтобы отыскать для них документацию, сосредоточьтесь на TypeName
, которым в данном
случае выступает System.Diagnostics.Process
. В механизме поиска отыщите это название
типа целиком и вы обычно проследует через официальную документацию разработчика для данного типа, что после этого приведёт
вас к необходимой документации для этого конкретного метода.
Командлет ForEach-Object
обладает сложной пунктуацией синтаксиса, а добавление в
некий метод собственного синтаксиса может создавать уродливую командную строку. Мы собрали некоторые советы для преодоления
умственных тупиков:
-
Попробуйте применять полное название командлета вместо его синонима
%
илиForEach
. Такое полное название может быть проще воспринимать. Если вы пользуетесь чужим примером, заменяйте псевдонимы полными названиями командлетов. -
Заключённый в фигурные скобки блок сценария выполняется по разу для каждого передаваемого конвейером в его командлет объекта.
-
Внутри конкретного блока сценария значение
$_
представляет текущий объект из его конвейера. -
Для работы с переданным в конвейере объектом целиком применяйте сам
$_
; для работы с индивидуальными методами или свойствами вслед за$_
разделяйте их точкой. -
За названием метода всегда следуют круглые скобки, даже когда метод не требует никаких параметров. Если параметры являются обязательными, они разделяются запятыми.
![]() | Замечание |
---|---|
Для данных лабораторных занятий вам требуется машина с PowerShell v7 или выше. |
Попробуйте ответить на следующие вопросы и выполнить предписанные задачи. Это важное лабораторное занятие, поскольку оно опирается на навыки, которые вы обрели во многих предыдущих главах, и вы должны продолжать применять и закреплять эти навыки по мере прохождения остающейся части данной книги.
-
Какой метод объекта
DirectoryInfo
(производимогоGet-ChildItem
) удалит этот каталог? -
Какой метод объекта
Process
(производимогоGet-Process
) прекратил бы этот процесс? -
Напишите три команды, которые можно было бы применять для удаления всех файлов и каталогов, которые обладают в своём названии
deleteme
, в предположении, что в своём названии обладают этим множество файлов и каталогов. -
Допустим, у вас имеется текстовый файл с названиями компьютеров, но вы хотите отображать все их в заглавном регистре. Каким бы выражением PowerShell вы бы воспользовались?
-
Отыщите методы, подобным образом:
Get-ChildItem | Get-Member -MemberType Method
Вы должны обнаружить метод
Delete()
. -
Отыщите методы, подобным образом:
get-process | Get-Member -MemberType Method
Вы должны обнаружить метод
Kill()
. Вы можете удостовериться проверив документацию MSDN для данного типа объекта процесса. Естественно, вам нет необходимости активировать этот метод, поскольку имеется командлет,Stop-Process
, который выполнит за вас ту же работу. -
Get-ChildItem *deleteme* | Remove-Item -Recurse -Force
Remove-Item *deleteme* -Recurse -Force
Get-ChildItem *deleteme* | foreach {$_.Delete()}
-
Get-content computers.txt | foreach {$_.ToUpper()}