PowerShell Invoke-Command: Полное руководство

IT-специалисты редко работают только на нашем локальном компьютере. Используя командлет Invoke-Command в PowerShell, нам не придется это делать! Этот командлет позволяет нам без проблем писать код, как если бы мы работали на нашем локальном компьютере.

Используя функцию PowerShell Remoting, командлет Invoke-Command является часто используемым командлетом PowerShell, который позволяет пользователю выполнять код внутри PSSession. Этот PSSession может быть создан заранее с помощью командлета New-PSSession, или он может быстро создавать и уничтожать временную сессию.

Связано: PowerShell Remoting: Полное руководство

Подумайте о Invoke-Command как о PowerShell psexec. Хотя они реализованы по-разному, концепция одинакова. Возьмите небольшой код или команду и запустите его “локально” на удаленном компьютере.

Однако для работы Invoke-Command необходимо иметь включенное и доступное на удаленном компьютере PowerShell Remoting. По умолчанию все машины Windows Server 2012 R2 или более поздних версий имеют это включено, а также соответствующие исключения в брандмауэре. Если у вас все еще есть машины с Server 2008, есть несколько способов настройки Remoting, но простой способ – это запустить winrm quickconfig или Enable-PSRemoting на удаленной машине.

Чтобы продемонстрировать, как работает Invoke-Command с “ад-хок командой”, то есть такой, которая не требует создания нового PSSession, предположим, что у вас есть удаленный компьютер, присоединенный к домену Windows Server 2012 R2 или более поздней версии. Вещи становятся немного запутанными, когда работаем с компьютерами в рабочей группе. Я открою консоль PowerShell, наберу Invoke-Command и нажму Enter.

PS> Invoke-Command
cmdlet Invoke-Command at command pipeline position 1
Supply values for the following parameters:
ScriptBlock:

I’m immediately asked to provide a scriptblock. The scriptblock is the code that we’re going to run on the remote computer.

Чтобы доказать, что код внутри скриптблока выполняется на удаленном компьютере, просто выполним команду hostname. Эта команда вернет имя хоста компьютера, на котором она выполняется. Запуск hostname на моем локальном компьютере возвращает его имя.

PS> hostname
MACWINVM

Теперь давайте передадим скриптблок с тем же кодом внутри скриптблока в Invoke-Command. Прежде чем мы сделаем это, мы забыли обязательный параметр: ComputerName. Мы должны указать Invoke-Command, на каком удаленном компьютере выполнять эту команду.

PS> Invoke-Command -ScriptBlock { hostname } -ComputerName WEBSRV1 WEBSRV1

Обратите внимание, что вывод hostname теперь является именем удаленного компьютера WEBSRV1. Вы выполнили некоторый код на WEBSRV1. Выполнение простого кода внутри скриптблока и передача его на один удаленный компьютер является самым простым применением Invoke-Command, но он может делать гораздо больше.

Передача локальных переменных в удаленные скриптблоки.

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

Например, предположим, у вас есть функция с параметром имени компьютера и пути к файлу. Цель этой функции – запустить установщик программного обеспечения на удаленном компьютере. Вы можете передать имя компьютера и “локальный” путь к установщику, который уже находится на удаленном компьютере.

Следующая функция кажется разумной, верно? Давайте запустим ее.

function Install-Stuff {
    param(
    	[Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$InstallerFilePath
	)
    Invoke-Command -ComputerName $ComputerName -ScriptBlock { & $InstallerFilePath }
}

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadExpression
+ PSComputerName        : websrv1

Она завершается неясным сообщением об ошибке из-за моего использования оператора амперсанд. Код был правильным, но он не сработал из-за того, что $InstallerFilePath был пустым, несмотря на то, что вы передали значение с параметром функции. Мы можем проверить это, заменив амперсанд на Write-Host.

Новая строка функции: Invoke-Command -ComputerName $ComputerName -ScriptBlock { Write-Host "Путь установщика: $InstallerFilePath" }

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
Installer path is:
PS>

Обратите внимание, что значение $InstallerFilePath равно нулю. Переменная не была развернута, потому что она не была передана на удаленную машину. Чтобы передать локально определенные переменные в удаленный блок сценария, у нас есть два варианта; мы можем предварить имя переменной с $using: внутри блока сценария, или мы можем использовать параметр ArgumentList команды Invoke-Command. Давайте рассмотрим оба варианта.

Параметр ArgumentList

Один из способов передать локальные переменные в удаленный блок сценария – использовать параметр ArgumentList команды Invoke-Command. Этот параметр позволяет передавать локальные переменные в параметр и заменять ссылки на локальные переменные в блоке сценария плейсхолдерами.

Передача локальных переменных в параметр ArgumentList легка.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $InstallerFilePath } -ArgumentList $InstallerFilePath

То, что сбивает с толку некоторых людей, – это структурирование переменных внутри блока сценария. Вместо использования { & $InstallerPath }, мы должны изменить его на { & $args[0] } или {param($foo) & $foo }. Оба варианта работают одинаково, но какой из них следует использовать?

Параметр ArgumentList представляет собой коллекцию объектов. Коллекции объектов позволяют передавать один или несколько объектов за один раз. В данном случае я передаю только один.

При выполнении командлета Invoke-Command этот набор берется и внедряется в скриптблок, фактически превращая его в массив с именем $args. Помните, что $args -eq ArgumentList. На этом этапе вы ссылаетесь на каждый индекс набора так же, как на элемент массива. В нашем случае у нас был только один элемент в наборе ($InstallerFilePath), который “переводится” как $args[0], что означает первый индекс в этом наборе. Однако, если бы у вас было больше элементов, вы бы ссылались на них $args[1], $args[2] и так далее.

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

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { param($foo) & $foo } -ArgumentList $InstallerFilePath

В данном случае элементы в коллекции ArgumentList “сопоставляются” с определенными параметрами по порядку. Имена параметров не имеют значения; важен порядок. Invoke-Command возьмет первый элемент в коллекции ArgumentList, найдет первый параметр и сопоставит эти значения, сделает то же самое для второго, третьего и так далее.

Конструкция $Using

Конструкция $using – еще один популярный способ передачи локальных переменных в удаленный блок сценария. Эта конструкция позволяет повторно использовать существующие локальные переменные, просто предварив имя переменной символом $using:. Нет необходимости беспокоиться о коллекции $args или добавлении блока параметров.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $using:InstallerFilePath }

Конструкция $using в PowerShell намного проще, но если вы когда-нибудь начнете изучать Pester, вы увидите, что ArgumentList будет вашим другом.

Invoke-Command и New-PSSession

Технически, в этом посте речь идет только о Invoke-Command, но чтобы продемонстрировать его полезность, нам нужно кратко упомянуть команду New-PSSession. Напомню, что ранее я упомянул, что Invoke-Command может использовать “ад-хок” команды или использовать существующие сеансы.

На протяжении этого поста мы только что выполняли “ад-хок” команды на удаленных компьютерах. Мы создавали новую сессию, выполняли код и разрушали ее. Это подходит для единичных случаев, но не так хорошо для ситуации, когда вам нужно выполнить десятки команд на одном и том же компьютере. В этом случае лучше повторно использовать существующую PSSession, создав ее заранее с помощью New-PSSession.

Перед выполнением любых команд вам нужно создать PSSession с помощью New-PSSession. Это можно сделать, просто запустив $session = New-PSSession -ComputerName WEBSRV1. Это создает удаленную сессию на сервере, а также ссылку на эту сессию на моем локальном компьютере. На этом этапе я могу заменить ссылки ComputerName на Session и указать Session на сохраненную переменную $session.

Invoke-Command -Session $session -ScriptBlock { & $using:InstallerFilePath }

При запуске вы заметите, что производительность выше, потому что сеанс уже создан. Однако после завершения важно удалить открытую сессию с помощью Remove-PSSession.

Сводка

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

Source:
https://adamtheautomator.com/invoke-command/