В этой статье мы рассмотрим особенности использования командлета Invoke-Command для удаленного выполнения команд и скриптов. Возможно запускать команды удаленно на одном компьютере, или параллельно на множестве компьютерах в вашей сети. Командлет Invoke-Command использует возможности удаленного управления, заложенные в PowerShell Remoting. PowerShell Remoting позволяет удаленно подключаться к PowerShell сессиям на компьютерах через службу WinRM (Windows Remote Management) через протокол Web Services for Management (WS-Management). Этот сервис дает возможность принимать команды Powershell и устанавливать сеансы.
Настройка WinRM для PowerShell Remoting
Для связи между компьютерами в PowerShell Remoting используется протокол HTTP (порт TCP/5985) или HTTPS (порт TCP/5986). По умолчанию используется протокол HTTP, но даже этот трафик шифруется с помощью ключа AES-256 (впрочем, есть угроза атак man-in-the middle). Возможна аутентификация через Kerberos (в домене) или NTLM.
На удаленных компьютерах, к которым вы планируете подключаться должен быть запущена служба WinRM. Проверить это можно так:
Get-Service -Name "*WinRM*" | fl
Если служба не запущена, запустите ее:
Enable-PSRemoting
WinRM has been updated to receive requests. WinRM service started. WinRM is already set up for remote management on this computer.
Данная команда запустит службу WinRM (установит автоматический запуск), выставит настройки winrm по-умолчанию и добавит исключение в Windows Firewall. Команда Enable-PSRemoting –Force включает WinRM без запроса пользователя.
Теперь к компьютеру можно подключиться удаленно через PowerShell Remoting.
Set-WSManQuickConfig : ... WinRM firewall exception will not work since one of the network connection types on this machine is set to Public. Change the network connection type to either Domain or Private and try again.
Вам нужно изменить тип сети на частную (private), или использовать команду:
Enable-PSRemoting –SkipNetworkProfileCheck.
Также нужно включить правило Window Defender Firewall, которое разрешает доступ к WinRM в общедоступных сетях. Вы можете включить правило брандмауэра с помощью GPO или PowerShell:
Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -RemoteAddress Any
Чтобы проверить подключение к удаленному компьютер через PowerShell Remoting используется команда:
Test-WsMan compname1
Если у вас нет домена, или вы обращаетесь к компьютерам через PowerShell Remoting по IP адресам, в этом случае используется для аутентификации используется протокол NTLM. При использовании NTLM, при выполнении команду Invoke-Command появится ошибка:
[192.168.1.201] Connecting to remote server 192.168.1.201 failed with the following error message : The WinRM client cannot process the request. Default authentication may be used with an IP address under the following conditions: thetransport is HTTPS or the destination is in the TrustedHosts list, and explicit credentials are provided. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. + FullyQualifiedErrorId : CannotUseIPAddress,PSSessionStateBroken
Для корректной работы NTLM аутентификации, на компьютере, с которого вы будете устанавливать подключения нужно выполнить дополнительные действия: выпустить SSL сертификат и исопльзовать его для шифрования HTTPS трафика winrm, или добавить имя/IP адрес хоста в доверенные:
Set-Item wsman:\localhost\Client\TrustedHosts -value 192.168.1.201
Либо можно разрешить подключение ко все компьютерам (не рекомендуется, т.к. один из главных недостатков NTLM – он не осуществляет проверку подлинности).
Set-Item wsman:\localhost\Client\TrustedHosts -value *
Аналогичные настройки нужно сделать на удаленных хостах.
Чтобы вывести список доверенных хостов, выполните команду:
Get-Item WSMan:\localhost\Client\TrustedHosts
Чтобы применить изменения, перезапустите службу WinRM:
Restart-Service WinRM
Удаленное выполнение PowerShell с помощью Invoke-Command
Командлет Invoke-Command позволяет выполнить команду на одном или нескольких удаленных компьютерах.
Например, для запуска одиночной команды на удаленном компьютере можно использовать такую команду:
Invoke-Command -ComputerName dc01 -ScriptBlock {$PSVersionTable.PSVersion}
Эта команда выведет в вашу консоль значение версии PowerShell, установленной на удаленном компьютере, имя которого указано в параметре
-ComputerName
. В блоке
-ScriptBlock {[cmdlet]}
указывается команда, которую нужно запусть на удаленном компьютере.
По-умолчанию команда, посланная через Invoke-Command выполняется на удалённом компьютере от текущего пользователя. Если нужно выполнить команду от имени другого пользователя, сначала нужно запросить учетные данные пользователя и сохранить их в переменную:
$cred = Get-Credential
Invoke-Command -ComputerName comp-buh2 -Credential $cred -ScriptBlock {Get-NetAdapter}
Эта PowerShell команда выведет список сетевых интерфейсов на удаленном компьютере:
Можно задать несколько команд в блоке ScriptBlock, их нужно разделить точкой с запятой. Например следующая команда выведет текущий часовой пояс и изменит его на другой:
Invoke-Command -Computername dc01 -ScriptBlock {Get-TimeZone| select DisplayName;Set-TimeZone -Name "Astrakhan Standard Time”}
Invoke-Command позволяет выполнять не только отдельные команды, но и запускать скрипты PowerShell. Для этого используется аргумент
-FilePath
(вместо –ScriptBlock). При этом вы указываете путь к локальному PS1 файлу скрипта на вашем компьютере (вам не нужно копировать файл скрипт на удаленный компьютер):
Invoke-Command -ComputerName Server01 -FilePath c:\PS\Scripts\GetComputerInfo.ps1
Используем Invoke-Command для параллельного запуска команд на нескольких компьютерах
Командлет Invoke-Command можно использовать для параллельного выполнения команд на нескольких удаленных компьютерах.
В самом просто случае имена компьютеров, на которых нужно выполнить команды указываются через запятую:
Invoke-Command server1, server2, server3 -ScriptBlock {get-date}
Список компьютеров можно поместить в переменную (массив):
$servers = @(″server1″,″server2″,″server3″)
Invoke-Command -ScriptBlock { get-date} -ComputerName $servers
Или получить из текстового файла:
Invoke-Command -ScriptBlock {Restart-Service spooler} -ComputerName(Get-Content c:\ps\servers.txt)
Также можно получить список компьютеров в ADс помощью командлета Get-ADComputer из модуля AD PowerShell:
Чтобы выполнить команду на всех Windows Server в домене, исопльзуйте такой код:
$computers = (Get-ADComputer -Filter 'operatingsystem -like "*Windows server*" -and enabled -eq "true"').Name
Invoke-Command -ComputerName $computers -ScriptBlock {get-date} -ErrorAction SilentlyContinue
Если компьютер выключен, или недоступен, благодаря параметру SilentlyContinue скрипт не будет остановлен и продолжит выполнение на других компьютерах.
Чтобы понять с какого компьютера получены результаты, нужно использовать специальную переменную окружения PSComputerName.
$results = Invoke-Command server1, server2, server3 -ScriptBlock {get-date}
$results | Select-Object PSComputerName, DateTime
При запуске команды через Invoke-Command на нескольких компьютерах она выполняется параллельно. В Invoke-Command есть ограничение на максимальное количество компьютеров, которыми можно управлять одновременно (ограничение на количество одновременных PSSession). Оно определяется параметром ThrottleLimit (по умолчанию 32). Если вам нужно выполнить команду одновременно более чем на 32 компьютерах (например, на 128), используйте параметр –ThrottleLimit 128 (но это вызывает повышенную нагрузку на ваш компьютер).
Для запуска команд на удаленных компьютерах через Invoke-Command в фоновом режиме используется специальный атрибут
–AsJob
. В этом случае результат выполнения команды не возвращается в консоль. Чтобы получить результаты нужно использовать командлет
Receive-Job
.
Если вы хотите запускать команды на удаленном компьютере интерактивно, используйте командлет Enter-PSSession.
я бы еще добавил, что по умолчанию права на подключение через Invoke-Command есть у членов группы администраторов (ну это и так понятно) и у группы «Remote Management Users».
А также, что есть возможность указывать сервера не через параметр -ComputerName, а через параметр -Session. Для которого мы указываем сессию, созданную через New-PSSession. Может пригодиться для тех, кто пользуется данным командлетом, чтобы не приходилось заново указывать сервера.
Ну и не забывать, что Invoke-Command не возвращает нам методы для объекта. И поэтому при необходимости их надо вызывать в самом ScriptBlock. Вот пример: если сделать так Invoke-Command -ComputerName Server1 -ScriptBlock {gwmi win32_operatingsystem} | GM То мы не увидим метод reboot. А если Get-Member выполнить в ScriptBlock, то метод будет доступен нам. Поэтому его надо вызывать так Invoke-Command -ComputerName Server1 -ScriptBlock {gwmi win32_operatingsystem | % Reboot}
А так все супер.
Повтор слова «используется».
исПОльзовать
Пожалуйста, замените косые кавычки на прямые. Лишний пробел в { get-date}.
исПОльзуйте
А как выполнить скрипт на удаленных ПК но последовательно, т.е. сначала на первом ПК и только после завершения на первом ПК выполнить этот скрипт на втором ПК?
Что-то типа такого?
$servers = @("server1","server2","server3")
foreach ($server in $servers)
{
Invoke-Command -ComputerName $server -ScriptBlock {get-date}
}
при таком варианте на каждое последовательное подключение запрашивается логин пароль в Get-Credential, а есть вариант с вводом логин пароль один раз или подстановка логин/пароль в переменной?
Запросить креды до запуска цикла:
$cred = Get-Credential
Внутри foreach использовать креды из переменной для Invoke-Command:
foreach ($server in $servers)
{
Invoke-Command -ComputerName $server -ScriptBlock {get-date} -Credential $cred
}
Благодарю!
Вдруг кому из начинающих пригодится, последовательные перебор списка серверов при выполнении команд powershell на питон:
import subprocess
cred = «Get-Credential»
servers = ‘@(«server1″,»server2″)’
commands = ‘{(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /1gb}’ #memory capacity
script = f»$cred = {cred}; foreach ($server in {servers}){{Invoke-Command -ComputerName $server -ScriptBlock {commands} -Credential $cred}}»
commandline_options = [«Powershell.exe», ‘-ExecutionPolicy’, ‘Unrestricted’, script] # INITIALIZING COMMAND
result = subprocess.run(commandline_options, stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines = True, check=True)
print(«returncode «,result.returncode) # PRINT THE RETURN CODE FROM POWERSHELL SCRIPT
print(«stdout «,result.stdout) # PRINT THE STANDARD OUTPUT FROM POWERSHELL SCRIPT
print(«stderr «,result.stderr) # PRINT THE STANDARD ERROR FROM POWERSHELL SCRIPT
кавычки выше форматнулись
import subprocess
cred = "Get-Credential"
servers = '@("server1","server2")'
commands = '{(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /1gb}' #memory capacity
script = f"$cred = {cred}; foreach ($server in {servers}){{Invoke-Command -ComputerName $server -ScriptBlock {commands} -Credential $cred}}"
commandline_options = ["Powershell.exe", "-ExecutionPolicy", "Unrestricted", script] # INITIALIZING COMMAND
result = subprocess.run(commandline_options, stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines = True, check=True)
print("returncode ",result.returncode) # PRINT THE RETURN CODE FROM POWERSHELL SCRIPT
print("stdout ",result.stdout) # PRINT THE STANDARD OUTPUT FROM POWERSHELL SCRIPT
print("stderr ",result.stderr) # PRINT THE STANDARD ERROR FROM POWERSHELL SCRIPT
Добрый день! А как через shell отправить набор командлет в powershell ? 🙂 например: Shell(«powershell.exe командлета1 командлета2 командлета3», appwinstyle.normalfocus, true)
Имеет в виду програмный запуск через c#, vb и прочее?
Попробуйте что-то в виде:
Shell("c:\windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command {Get-EventLog -LogName security;secondcommand;thirdcommand}", AppWinStyle.NormalFocus)
Здравствуйте,
пробовал сделать архив через 7-Zip на удаленной машине, указал:
-ScriptBlock { "C:\Program Files\7-Zip\7z.exe a -r создатьАрхив чтоАрхивировать" }
и ничего не произошло, что не так? На машинах я доменный админ своего бранча. Кстати Winrc тоже не помог.
Кавычки лишние.
Доброго дня!
Как сделать так чтоб на запрашивал аутентификации в домене?
Invoke-Command -ComputerName serv1.domen.ru -Credential $cred -ScriptBlock {Get-NetAdapter}
После выскакивает окно для авторизации.
Можно ли эту авторизацию в самой команде прописать?
Если компьютер в домене и вы вошли под доменным пользователем, можно использовать текущую учетную запись для удаленного подключения и пропустить запрос учетных данных. Просто уберите секцию -Credential $cred:
Invoke-Command -ComputerName serv1.domen.ru -ScriptBlock {Get-NetAdapter}
Здравствуйте!
Как нужно использовать параметр SilentlyContinue ?
Можете привести пример ?
SilentlyContinue позволяет не прерываться на ошибку и если нужно можно потом обработать ее, вернув в результаттах инфу об ошибке.
В статье есть пример использования при выполнеии Invoke-Command на несколько компьютеров
Спасибо за ответ!
Подскажите как лучше сделать чтобы в скрипте, в котором реализовано удалённое подключение к нескольким ПК и сбор необходимой информации, добавить имя пользователя(компы не в домене, есть список кто за каким компом закреплён в файле .csv в котором два поля: name и user). На выходе должно получиться, к примеру: comp1 (ivanov), memory size =8Gb… comp2 (petrov), …
И ещё вывести в конце: comp4 (sidorov) — не в сети.
По умолчанию переменные, которые определены за пределами Invoke-Command нельзя использовать внутри блока команд.
Если нужно передать локальную переменную в блок команд Invoke-Command, можно использовать конструкцию
$using
:Например:
$filename = "test.zip"
Invoke-Command -ComputerName winserver -ScriptBlock {
Get-FileHash E:\test\$Using:filename -Algorithm SHA1
}
Здравствуйте!
Скрипт зависает на стадии выполнения Invoke-Command -ComputerName Computer -ScriptBlock {мой скрипт} на нескольких компьютерах в сети. На остальных корректно.
Подскажите, пожалуйста, как сделать, чтобы Invoke-Command пыталась выполнить скрипт на удалённом ПК только 10 минут, а потом закрывалась.
Поднапрягся и сам додумался, как это сделать 🙂
$set = New-PSSessionOption -IdleTimeout 300000 #задаёт таймаут завершения работы удалённого выполнения команды, если компьютер не отвечает (в миллисекундах)
$Rezultat = Invoke-Command -ComputerName $PCName -SessionOption $set -EA 4 -ScriptBlock {мой скрипт}
Отлично! По идее можно еще сначала проверять доступность компьютера
if ((Test-WsMan compname1 -ErrorAction ignore)) {do invoke-command}
А зачем Invoke-command использовать цикл foreach, если этот командлент сам перебирает имена компов находящихся в $servers ?
foreach ($server in $servers)
{
Invoke-Command -ComputerName $server -ScriptBlock {get-date} -Credential $cred
}
Да, можно без цикла. Но в вопросе была задача выполнить команду последовательно.Для этого как раз цикл foreach
Invoke-Command по умолчанию запускается параллельно в 32 потока.