Глава 14. Многозадачность с фоновыми заданиями

Все всегда говорят вам про многозадачность, не так ли? Почему бы PowerShell не помочь вам в этом, выполняя одновременно более одного действия? Оказывается, именно это может делать PowerShell, в особенности для длительных задач, которые могут включать множество целевых компьютеров. Прежде чем погрузиться в эту главу, убедитесь что вы прочли Главу 13, потому как мы продвинем на шаг вперёд эти понятия удалённого взаимодействия.

[Предостережение]Будьте внимательны!

Мы будем применять большое число командлетов Az, что потребует активной подписки Azure. Это всего лишь примеры, которые мы выбрали для выделения.

Заставляем PowerShell выполнять множество вещей в одно время

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

Однако при своих функциональных возможностях фоновых заданий, PowerShell обладает возможностью помещать команду в отдельный фоновый поток или обосабливать фоновый процесс PowerShell. Это делает возможным такой команде исполняться в фоновом режиме, поскольку вы продолжаете применять свою оболочку для другой задачи. Вы должны принять такое решение перед исполнением соответствующей команды; после того как вы нажмёте Enter, вы не сможете принять решение о перемещении команд длительного исполнения в свой фоновый режим.

После того как команды находятся в фоновом режиме, PowerShell предоставляет механизмы для проверки их состояния, выборки всех результатов и тому подобного.

Сопоставление синхронного и асинхронного

Для начала давайте разберёмся с терминологией. Обычные команды PowerShell исполняет синхронно, то есть вы нажимаете Enter, а затем дожидаетесь завершения команды. Перемещение задания в фоновый режим делает возможным его выполнение асинхронно, что означает что вы можете продолжать применять свою оболочку для прочих задач по мере завершения команды. Давайте взглянем на некоторые важные различия между исполнением команд такими двумя способами:

  • Когда вы выполняете команду синхронно, вы можете откликаться на запросы на ввод. Когда вы можете запускать команды в фоновом режиме, нет никакой возможности увидеть запросы на ввод - по- существу, они остановят исполнение такой команды.

  • Когда что- то идёт не так, синхронные команды вырабатывают сообщения об ошибке. Фоновые команды производят ошибки, однако вы не можете увидеть их немедленно. Для их перехвата, если это необходимо, вам придётся выполнить определённые мероприятия (Глава 24 поясняет как выполнять это.)

  • Когда вы пропускаете необходимый параметр в синхронной команде, PowerShell имеет возможность выдать вам приглашение на недостающие сведения. Для фоновой команды вы этого не сможете сделать, а потому команда откажет.

  • Получаемые результаты синхронной команды начнут отображаться как только они станут доступными. Для команды в фоновом режиме вы дожидаетесь завершения исполнения команды и затем выполняете выборку кэшированных результатов.

Обычно вы исполняете команды в синхронном режиме для их проверки и чтобы получить их работающими как положено, а затем исполняете их в фоновом режиме только после того, как они полностью отлажены и работают ожидаемым образом. Мы следуем таким мерам чтобы гарантировать, что такая команда будет работать без проблем и что у неё будет больше шансов осуществления в фоновом режиме. Команды в фоновом режиме PowerShell именует заданиями (jobs). Вы можете создавать задания несколькими способами, а для управления ими вы можете применять различные команды.

[Замечание]Дополнительно

Технически говоря, те задания, которые мы обсуждаем в этой главе являются лишь несколькими видами заданий, с которыми вы можете сталкиваться. Задания являются неким моментом расширения для PowerShell, что означает, что для кого- то (либо в Microsoft, либо в качестве стороннего разработчика) имеется возможность создания иных вещей с названием заданий, которые выглядят и работают несколько иначе чем то, что мы описываем в данной главе. При расширении оболочки под различные цели вы можете сталкиваться с иными видами заданий. Мы хотим чтобы вы уяснили эту маленькую подробность и знали бы, что то, что вы изучаете в данной главе применимо лишь к естественным, обычным заданиям, которые поставляются с PowerShell.

Создание задания процесса

Самый первый рассматриваемый нами тип заданий, вероятно, самый простой: задание процесса. Это некая команда, которая запускается в ином процессе PowerShell в вашей машине в фоновом режиме.

Для запуска одного из таких заданий вы можете применять команду Start-Job. Параметр -ScriptBlock позволяет вам определять саму команду (или команды) для исполнения. PowerShell выставляет имена заданий по умолчанию (Job1, Job2 и т.д.), или же вы можете определять индивидуальное название задания при помощи параметра -Name . Вместо того чтобы определять блок сценария, вы можете задавать свой параметр -FilePath чтобы обладать заданием, исполняющим файл сценария, наполненный командами. Вот простой пример:


PS /Users/travisp/> start-job -scriptblock { gci }
Id   Name  PSJobTypeName  State    HasMoreData     Location    Command
--   ----  -------------  -----    -----------     --------    -------
1    Job1  BackgroundJob  Running  True            localhost   gci 
		

Данная команда создаёт свой объект задания и, как показывает наш предыдущий пример, это задание начинает исполняться немедленно. Такому заданию также выделяется последовательный номер идентификатора задания, который указан в этой таблице.

Данная команда также обладает параметром -WorkingDirectory, который позволяет вам изменять где в вашей файловой системе стартует ваше задание. По умолчанию, оно всегда стартует в вашем домашнем каталоге. Никогда не применяйте никаких предположений о путях к файлам из задания в фоновом режиме: применяйте абсолютные пути, чтобы гарантировать что вы можете ссылаться на любые файлы, которые могут потребоваться вашей команде задания, либо применяйте параметр -WorkingDirectory. Вот образец:


PS /Users/travisp/> start-job -scriptblock { gci } -WorkingDirectory /tmp
Id   Name  PSJobTypeName  State    HasMoreData     Location    Command
--   ----  -------------  -----    -----------     --------    -------
3    Job3  BackgroundJob  Running  True            localhost   gci 
		

Внимательные читатели обратят внимание, что самое первое созданное нами задание носит название Job1 и приданный ему идентификатор 1, Однако вторым заданием является Job3 с идентификатором 3. Оказывается, у каждого задания имеется по крайней мере одно дочернее задание (child job), и самому первому заданию (потомку Job1) придаются название Job2 и значение идентификатора 2. Позднее в этой главе мы вернёмся к дочерним заданиям.

Здесь нужно иметь в виду: хотя задания процессов выполняются локально, они требуют разрешения удалённого взаимодействия PowerShell, которое мы рассматривали в Главе 13.

Создание задания потока

Существует второй тип задания, который поставляется как часть PowerShell и о котором мы бы хотели поговорить. Он носит название задания потока (thread job). Вместо того чтобы запускать совершенно другой процесс PowerShell, задание потока раскручивается в другом потоке в том же самом процессе. Вот образец:


PS /Users/travisp/> start-threadjob -scriptblock { gci }
Id   Name  PSJobTypeName  State    HasMoreData     Location    Command
--   ----  -------------  -----    -----------     --------    -------
1    Job1  ThreadJob      Running  False           PowerShell  gci 
		

Ха, выглядит очень похожим на вывод нашего предыдущего задания? Всего лишь два отличия - значение PSJobTypeName которым выступает ThreadJob и значение Location, а именно PowerShell. Это сообщает нам что данное задание запущено внутри самого процесса, который мы применяем в данный момент, однако в другом потоке.

Поскольку накладные расходы раскрутки нового потока драматически быстрее нежели чем раскрутка нового процесса, задания потока фантастичны для краткосрочных сценариев и команд, которые вы бы желали запускать быстро и исполнять в фоновом режиме. И наоборот, вы можете пользоваться в своей машине заданиями процессов для сценариев с длительным временем исполнения.

[Предостережение]Будьте внимательны!

Хотя задания потоков стартуют быстрее, имейте в виду, что в одном процессе одновременно выполняться может лишь определённое число потоков, прежде чем он начнёт замедляться. PowerShell изготовлен с "пределом дросселирования" 10, чтобы помочь вам в том, чтобы не слишком застревать в PowerShell. Это означает, что одновременно могут исполняться только 10 заданий потоков. Если вы пожелаете увеличить этот предел, у вас есть такая возможность. Просто определите параметр -ThrottleLimit и передайте в нём новый предел, которым вы хотите пользоваться. В конце концов, когда вы будете запускать одновременно 50, 100, 200 потоков, вы начнёте замечать убывающую отдачу. Помните об этом.

Удалённая работа как задание

Давайте рассмотрим заключительную технологию, которую вы можете применять для создания нового задания: возможности удалённого взаимодействия PowerShell, которые мы изучали в Главе 13. Имеется существенное отличие: всякая команда, которую вы определяете в -scriptblock (или в -command, что является синонимом для того же самого параметра) будет передаваться параллельно во все заданные вами компьютеры. За раз может быть осуществлён контакт с 32 компьютерами (если только вы не изменили параметр -throttleLimit для разрешения большего или меньшего числа), поэтому когда вы определите более 32 названий компьютеров, запустятся только первые 32. Остальные будут запущены после того как первый набор начнёт завершаться и задание верхнего уровня покажет состояние выполненного после завершения во всех компьютерах.

В отличии от прочих двух способов запуска задания, данная технология потребует чтобы вы имели установленной PowerShell v6 или выше в каждом из целевых компьютеров и включённым в PowerShell всех целевых компьютеров удалённым взаимодействием поверх SSH. Поскольку такая команда физически выполняется в каждом из удалённых компьютеров, вы распределяете свою вычислительную нагрузку, что может способствовать улучшению производительности для сложных команд с длительным временем выполнения. Получаемый результат возвращается обратно в ваш компьютер и сохраняется в своём задании пока вы не будете готовы просмотреть его.

В нашем следующем примере вы также видите параметр -JobName, который позволяет вам задавать название задания, отличающееся от порождаемого по умолчанию:


PS C:\> invoke-command -command { get-process } -hostname (get-content .\allservers.txt ) -asjob -jobname MyRemoteJob
WARNING: column "Command" does not fit into the display and was removed.
Id              Name             State     HasMoreData     Location
--              ----             -----     -----------     --------
8               MyRemoteJob      Running   True            server-r2,lo...
		

Задания в природе

Мы хотим воспользоваться данным разделом чтобы показать некий пример модуля PowerShell, который выставляет свои собственные PSJobs, поэтому вы можете рассматривать его как шаблон в своём путешествии PowerShell. Давайте, например, возьмём команду New-AzVm:


PS /Users/travisp/> gcm New-AzVM -Syntax

New-AzVM -Name <string> -Credential <pscredential> [-ResourceGroupName
     <string>] [-Location <string>] [-Zone <string[]>] [-VirtualNetworkName
     <string>] [-AddressPrefix <string>] [-SubnetName <string>] [-
     SubnetAddressPrefix <string>] [-PublicIpAddressName <string>] [-
     DomainNameLabel <string>] [-AllocationMethod <string>] [-
     SecurityGroupName <string>] [-OpenPorts <int[]>] [-Image <string>] [-
     Size <string>] [-AvailabilitySetName <string>] [-SystemAssignedIdentity]
     [-UserAssignedIdentity <string>] [-AsJob] [-DataDiskSizeInGb <int[]>] [-
     EnableUltraSSD] [-ProximityPlacementGroup <string>] [-HostId <string>]
     [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm]
     [<CommonParameters>]

New-AzVM [-ResourceGroupName] <string> [-Location] <string> [-VM]
     <PSVirtualMachine> [[-Zone] <string[]>] [-DisableBginfoExtension] [-Tag
     <hashtable>] [-LicenseType <string>] [-AsJob] [-DefaultProfile
     <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>]

New-AzVM -Name <string> -DiskFile <string> [-ResourceGroupName <string>] [-
     Location <string>] [-VirtualNetworkName <string>] [-AddressPrefix
     <string>] [-SubnetName <string>] [-SubnetAddressPrefix <string>] [-
     PublicIpAddressName <string>] [-DomainNameLabel <string>] [-
     AllocationMethod <string>] [-SecurityGroupName <string>] [-OpenPorts
     <int[]>] [-Linux] [-Size <string>] [-AvailabilitySetName <string>] [-
     SystemAssignedIdentity] [-UserAssignedIdentity <string>] [-AsJob] [-
     DataDiskSizeInGb <int[]>] [-EnableUltraSSD] [-ProximityPlacementGroup
     <string>] [-HostId <string>] [-DefaultProfile <IAzureContextContainer>]
     [-WhatIf] [-Confirm] [<CommonParameters>]
		

Обратили внимание на знакомый параметр? -AsJob! Давайте посмотрим что делается в этой команде:


PS /Users/travisp/> Get-Help New-AzVM -Parameter AsJob

-AsJob <System.Management.Automation.SwitchParameter>
    Run cmdlet in the background and return a Job to track progress.

    Required?                    false
    Position?                    named
    Default value                False
    Accept pipeline input?       False
    Accept wildcard characters?  false
		

Этот параметр просит New-AzVM вернуть некое Job. Если мы выпалим этот командлет после того, как поместим имя пользователя и пароль для своей виртуальной машины, мы обнаружим, что получили обратно Job.


PS /Users/travisp/> New-AzVm -Name myawesomevm -Image UbuntuLTS -AsJob

cmdlet New-AzVM at command pipeline position 1
Supply values for the following parameters:
Credential
User: azureuser
Password for user azureuser: *********** 
Id Name              PSJobTypeName     State   HasMoreData Location  Command
-- ----              -------------     -----   ----------- --------  -------
8  Long Running O... AzureLongRunni... Running True        localhost New-AzVM
		

Что превращает это в настолько удивительное, так это то, что вы можете управлять этими заданиями так же, как и заданиями, которые возвращались Start-Job или Start-ThreadJob. Позднее вы увидите как мы обходимся с управлением заданиями, однако это некий пример того как может проявляться персонализация заданий. Ищите параметр -AsJob!

Получение результатов задания

Самая первая вещь, которую вы скорее всего пожелаете сделать после запуска задания, это проверить закончилось ли это задание. Командлет Get-Job выполняет выборку каждого задания, определённого вашей системой и отображает вам состояние каждого:


PS /Users/travisp/> get-job
Id Name            PSJobTypeName   State     HasMoreData  Location   Command
-- ----            -------------   -----     -----------  --------   -------
1  Job1            BackgroundJob   Completed True         localhost  gci
3  Job3            BackgroundJob   Completed True         localhost  gci
5  Job5            ThreadJob       Completed True         PowerShell gci
8  Job8            BackgroundJob   Completed True         server-r2, lo...
11 MyRemoteJob     BackgroundJob   Completed True         server-r2, lo...
13 Long Running O... AzureLongRunni... Running True       localhost  New-AzVM 
		

Вы также имеете возможность выполнить выборку определённого задания воспользовавшись его идентификатором или его названием. Мы предполагаем, что вы делаете это и отправляете конвейером в Format-List *, потому как вы собрали ценные сведения:


PS /Users/travisp/> get-job -id 1 | format-list *
State         : Completed
HasMoreData   : True
StatusMessage :
Location      : localhost
Command       : gci
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : e1ddde9e-81e7-4b18-93c4-4c1d2a5c372c
Id            : 1
Name          : Job1
ChildJobs     : {Job2}
PSBeginTime   : 12/12/2019 7:18:58 PM
PSEndTime     : 12/12/2019 7:18:58 PM
PSJobTypeName : BackgroundJob
Output        : {}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}
Information   : {} 
		

Испробуйте это сейчас Если вы следите за нами, имейте в виду, что ваши идентификаторы и названия заданий могут отличаться от наших. Чтобы сделать выборку своих идентификаторов и названий заданий, сосредоточьтесь на выводе Get-Job и подставьте их в своих примерах. Также имейте в виду, что Microsoft в последних нескольких версиях PowerShell Microsoft расширила свой объект задания, поэтому ваши результаты при просмотре всех свойств могут отличаться.

Свойство ChildJobs это один из самых сажных моментов в сведениях и мы рассмотрим его через мгновение. Для выборки полученных результатов из задания пользуйтесь Receive-Job, однако прежде чем вы исполните его, вам необходимо узнать несколько вещей:

  • Вам придётся определить то задание из которого вы желаете получать результаты. Вы можете делать это по идентификатору задания или по имени задания, либо получать задания при помощи Get-Job и отправлять конвейером их в Receive-Job.

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

  • Обычно, получение необходимых результатов из некого задания вычищает их из кэша вывода этого задания, поэтому вы не сможете получить их повторно. Для удержания копии этих результатов в памяти, определите -keep. Или же, когда вы желаете оставлять некую копию для работы с нею, вы можете выдавать получаемые результаты в некий файл CLIXML.

  • Результаты вашего задания могут быть распакованными объектами, которые вы изучали в Главе 13. Это моментальные снимки в тот момент времени, когда они были выработаны и они могут не обладать никакими методами, которые вы способны выполнять. Однако, если пожелаете, вы можете отправлять конвейером результаты своего задания непосредственно в такие командлеты как Sort-Object, Format-List, Export-CSV, ConvertTo-HTML, Out-File и тому подобные.

Вот некий образец:


PS /Users/travisp/> receive-job -id 1

Directory: /Users/travisp

Mode             LastWriteTime              Length Name
----             -------------              ------ ----
d----      11/24/2019 10:53 PM                     Code
d----      11/18/2019 11:23 PM                     Desktop
d----       9/15/2019  9:12 AM                     Documents
d----       12/8/2019 11:04 AM                     Downloads
d----       9/15/2019  7:07 PM                     Movies
d----       9/15/2019  9:12 AM                     Music
d----       9/15/2019  6:51 PM                     Pictures
d----       9/15/2019  9:12 AM                     Public 
		

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


PS /Users/travisp/> start-job -scriptblock { gci } 
		

Когда мы получили необходимые результаты от Job1, мы не определили -keep. Если мы попытаемся снова получить эти результаты, мы не получим ничего, потому как эти результаты более не кэшированы в своём задании:


PS /Users/travisp/> receive-job -id 1 
		

Вот как мы можем заставлять эти результаты оставаться кэшированными в памяти:


PS /Users/travisp/> receive-job -id 3 –keep

Directory: /Users/travisp

Mode             LastWriteTime              Length Name
----             -------------              ------ ----
d----            11/24/2019 10:53 PM               Code
d----            11/18/2019 11:23 PM               Desktop
d----             9/15/2019  9:12 AM               Documents
d----             12/8/2019 11:04 AM               Downloads
d----             9/15/2019  7:07 PM               Movies
d----             9/15/2019  9:12 AM               Music
d----             9/15/2019  6:51 PM               Pictures
d----             9/15/2019  9:12 AM               Public 
		

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


PS /Users/travisp> receive-job -name myremotejob | sort-object PSComputerName  | Format-Table -groupby PSComputerName
   PSComputerName: localhost
NPM(K)    PM(M)      WS(M) CPU(s)     Id ProcessName PSComputerName
------    -----      ----- ------     -- ----------- --------------
     0        0      56.92   0.70    484 pwsh        localhost
     0        0     369.20  70.17   1244 Code        localhost
     0        0      71.92   0.20   3492 pwsh        localhost
     0        0     288.96  15.31    476 iTerm2      localhost
		

Это было то задание, которое мы запустили при помощи Invoke-Command. Наш командлет был добавлен свойством PSComputerName, поэтому мы можем продолжать отслеживать какой объект возвращается от какого компьютера. Поскольку мы выполняем выборку получаемых результатов от своего задания верхнего уровня, они содержат определёнными все свои компьютеры, что позволяет этой команде сортировать их по значению названия компьютера и затем создавать некую индивидуальную группу таблиц для каждого компьютера. Get-Job к тому же способен поддерживать для вас сведения о том, для каих заданий остаются результаты:


PS /Users/travisp> get-job
Id Name            PSJobTypeName   State     HasMoreData Location   Command
-- ----            -------------   -----     ----------- --------   -------
1  Job1            BackgroundJob   Completed False       localhost  gci
3  Job3            BackgroundJob   Completed True        localhost  gci
5  Job5            ThreadJob       Completed True        PowerShell gci
8  Job8            BackgroundJob   Completed True        server-r2, lo...
11 MyRemoteJob     BackgroundJob   Completed False       server-r2, lo...
13 Long Running O... AzureLongRunni... Running True      localhost  New-AzVM
		

Значением колонки HasMoreData будет False, когда нет кэшированного вывода для этого задания. В случае Job1 и MyRemoteJob мы уже получили эти результаты и на данный момент не определяли -keep.

Работа с дочерними заданиями

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


PS /Users/travisp> get-job -id 1 | format-list *
State         : Completed
HasMoreData   : True
StatusMessage :
Location      : localhost
Command       : dir
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : e1ddde9e-81e7-4b18-93c4-4c1d2a5c372c
Id            : 1
Name          : Job1
ChildJobs     : {Job2}
PSBeginTime   : 12/27/2019 2:34:25 PM
PSEndTime     : 12/27/2019 2:34:29 PM
PSJobTypeName : BackgroundJob
Output        : {}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}
Information   : {}
		

Испробуйте это сейчас Не следуйте за этой частью, потому как если вы до сих пор следовали ею, вы уже получили результаты Job1. Если вы хотите это попробовать, запустите новое задание выполнив Start-Job -script { dir } и воспользовавшись идентификатором этого нового задания вместо того номера идентификатора 1, которым мы применили в своём примере.

Вы можете обнаружить, что Job1 обладает дочерним заданием, Job2. Вы можете получить его теперь непосредственно, ибо знаете его название:


PS /Users/travisp> get-job -name job2 | format-list *
State         : Completed
StatusMessage :
HasMoreData   : True
Location      : localhost
Runspace      : System.Management.Automation.RemoteRunspace
Debugger      : System.Management.Automation.RemotingJobDebugger
IsAsync       : True
Command       : dir
JobStateInfo  : Completed
Finished      : System.Threading.ManualResetEvent
InstanceId    : a21a91e7-549b-4be6-979d-2a896683313c
Id            : 2
Name          : Job2
ChildJobs     : {}
PSBeginTime   : 12/27/2019 2:34:25 PM
PSEndTime     : 12/27/2019 2:34:29 PM
PSJobTypeName :
Output        : {Applications, Code, Desktop, Documents, Downloads, Movies, Music...}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}
Information   : {} 
		

Порой задание обладает настолько большим числом дочерних заданий для перечисления в этой форме, что вы можете пожелать перечислять их несколько иначе, следующим образом:


PS /Users/travisp> get-job -id 1 | select-object -expand childjobs
Id Name            PSJobTypeName   State     HasMoreData  Location  Command
-- ----            -------------   -----     -----------  --------  -------
2  Job2                            Completed True         localhost gci
		

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

Команды для управления заданиями

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

  • Remove-Job - Он удаляет из памяти задание и весь вывод всё ещё кэшируемый им.

  • Stop-Job - Когда некое задание кажется подвисшим, данная команда прекращает его. Вы всё ещё имеете возможность получения каких- либо выработанных к данному моменту результатов.

  • Wait-Job - Это полезно когда некий сценарий намерен запустить задание или задания и вы хотите чтобы данный сценарий продолжался лишь тогда, когда исполнится такое задание. Данная команда принуждает вашу оболочку остановиться и ожидать выполнения своего задания (или заданий), а затем позволяет вашей оболочке продолжить исполнение.

Например, для удаления своего задания, от которого мы уже получили вывод, мы бы воспользовались следующей командой:


PS /Users/travisp> get-job | where { -not $_.HasMoreData } | remove-job
PS /Users/travisp> get-job
Id Name            PSJobTypeName   State     HasMoreData  Location   Command
-- ----            -------------   -----     -----------  --------   -------
3  Job3            BackgroundJob   Completed True         localhost  gci
5  Job5            ThreadJob       Completed True         PowerShell gci
8  Job8            BackgroundJob   Completed True         server-r2, lo...
13 Long Running O... AzureLongRunni... Completed True     localhost  New-AzVM
		

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


PS /Users/travisp> invoke-command -command { nothing } -hostname notonline -asjob -jobname ThisWillFail
Id Name            PSJobTypeName   State     HasMoreData  Location  Command
-- ----            -------------   -----     -----------  --------  -------
11 ThisWillFail    BackgroundJob   Failed    False        notonline nothing
		

В данном случае мы запустили задание с фиктивной командой и указали целью не существующий компьютер. Данное задание немедленно падает, что указано в его состоянии. Нам нет нужды применять на этот раз Stop-Job; это задание не исполняется. Однако мы можем получить список его дочерних заданий:


PS /Users/travisp> get-job -id 11 | format-list *
State         : Failed
HasMoreData   : False
StatusMessage :
Location      : notonline
Command       : nothing
JobStateInfo  : Failed
Finished      : System.Threading.ManualResetEvent
InstanceId    : d5f47bf7-53db-458d-8a08-07969305820e
Id            : 11
Name          : ThisWillFail
ChildJobs     : {Job12}
PSBeginTime   : 12/27/2019 2:45:12 PM
PSEndTime     : 12/27/2019 2:45:14 PM
PSJobTypeName : BackgroundJob
Output        : {}
Error         : {}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}
Information   : {}
		

Далее мы можем получить такое дочернее задание:


PS /Users/travisp> get-job -name job12

Id Name  PSJobTypeName   State     HasMoreData  Location  Command
-- ----  -------------   -----     -----------  --------  -------
12 Job12                 Failed    False        notonline nothing
		

Как вы можете наблюдать, для этого задания не было создано никакого вывода, а потому у вас нет никаких результатов для их выборки. Однако ошибки этого задания сохранены в полученных результатах и вы имеете возможность получить их воспользовавшись Receive-Job:


PS /Users/travisp> receive-job -name job12
OpenError: [notonline] The background process reported an error with the following message: The SSH client session has ended with error message: ssh: Could not resolve hostname notonline: nodename nor servname provided, or not known.
		

Текст полной ошибки намного длиннее; мы усекли его здесь для сохранения пространства. Вы заметите, что данная ошибка включает значение имени хоста, с которого пришла эта ошибка, [notonline]. Что произойдёт, если лишь один из компьютеров не может быть достигнут? Давайте попробуем:


PS /Users/travisp> invoke-command -command { nothing } -computer notonline,server-r2 -asjob -jobname ThisWilLFail
Id Name         PSJobTypeName   State    HasMoreData  Location        Command
-- ----         -------------   -----    ---------    --------        -------
13 ThisWillFail BackgroundJob   Running  True         notonline,lo... nothing
		

После короткого ожидания мы выполним следующее:


PS /Users/travisp> get-job 13
Id Name         PSJobTypeName    State   HasMoreData  Location        Command
-- ----         -------------    -----   -----------  --------        -------
13 ThisWillFail BackgroundJob    Failed  False        notonline,lo... nothing
		

Наше задание всё ещё отказавшее, но давайте взглянем на индивидуальные дочерние задания:


PS /Users/travisp> get-job -id 13 | select -expand childjobs
Id Name         PSJobTypeName    State   HasMoreData  Location        Command
-- ----         -------------    -----   -----------  --------        -------
14 Job14                         Failed  False        notonline       nothing
15 Job15                         Failed  False        localhost       nothing
		

ладно, они упали оба. Мы имеем ощущение, что мы знаем почему не работает Job14, однако что не верно с Job15?


PS /Users/travisp> receive-job -name job15
Receive-Job : The term 'nothing' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. 
		

А, всё верно, мы попросили его выполнить не существующую команду. Как вы можете видеть, каждое дочернее задание способно отказывать по различным причинам и PowerShell отслеживает каждую из них индивидуально.

Общие моменты путаницы

Задания обычно просты, однако мы встречали парней, которые делают одну вещь, которая вызывает путаницу. Не делайте этого:


PS /Users/travisp> invoke-command -command { Start-Job -scriptblock { dir } } -hostname Server-R2
		

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


PS /Users/travisp> start-job -scriptblock { invoke-command -command { dir } -hostname SERVER-R2 }
		

Это полностью избыточно; оставьте свой раздел Invoke-Command и воспользуйтесь его параметром -AsJob чтобы получить это исполняемым в фоновом режиме.

Менее сбивающими с толку, но также интересными являются те вопросы, которые новые пользователи часто задают о заданиях. Вероятно, наиболее важный из них: "можем ли мы наблюдать задания, запущенные кем- то ещё?" Правильный ответ - нет. Задания и потоковые задания целиком содержатся внутри самого процесса PowerShell и, хотя вы и можете обнаруживать что другой пользователь запускает PowerShell, вы не способны заглянуть вовнутрь этого процесса. Это похоже на любое иное приложение: например, вы можете наблюдать, что другой пользователь работает с Microsoft Word, но вы не можете видеть какие именно документы изменяет этот пользователь, потому как эти документы полностью заключены вовнутрь процесса Word.

Задания выполнятся лишь до тех пор, пока открыт ваш сеанс PowerShell. После его закрытия все определённые в нём задания исчезают. Задания не определены где- либо вне PowerShell, поэтому они зависят от того, что их процесс продолжает исполняться для поддержания себя.

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

Приводимые далее упражнения должны помочь вам разобраться с тем как работать с различными типами заданий и задач в PowerShell. Работая с этими упражнениями, не думайте что вам следует написать решение в одну строку. Иногда проще разбивать задачу на отдельные этапы.

  1. Создайте одноразовое потоковое задание для поиска всех текстовых файлов (*.txt) в вашей файловой системе. Любые задачи, которые могут потребовать длительного времени для своего завершения это отличные претенденты для задания.

  2. Вы осознали, что было бы полезным идентифицировать все текстовые файлы в некоторых из ваших серверов. Как бы вы могли исполнить ту ж самую команду из задачи 1 в некой группе удалённых компьютеров?

  3. Каким бы командлетом вы бы воспользовались для получения результатов задания и как бы вы сохранили эти результаты в своей очереди заданий?

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

  1. Start-ThreadJob {gci / -recurse –filter '*.txt'}

  2. Invoke-Command –scriptblock {gci / -recurse –filter *.txt} –computername (get-content computers.txt) -asjob

  3. Receive-Job –id 1 –keep

Естественно, вы бы могли воспользоваться тем идентификатором задания, который подходит вам, или же его именем задания.