Для удаленного запуска команд или PowerShell скриптов на одном или нескольких удаленных компьютерах можно использовать командлет Invoke-Command.
Настройка WinRM для PowerShell Remoting
Для установки связи с удаленным компьютерами командлет Invoke-Command использует сессии PowerShell Remoting. Технология удаленного управления и запуска команд PowerShell Remoting основана на протоколе WS-Management (реализуется через службу WinRM, Windows Remote Management). Для связи с удаленным компьютерами используются протокол HTTP (порт
TCP/5985
) или HTTPS (порт
TCP/5986
). По умолчанию используется протокол HTTP, но даже этот трафик шифруется с помощью ключа AES-256 (впрочем, есть угроза атак man-in-the middle). Возможна аутентификация через Kerberos (в домене) или NTLM.
Чтобы установить сессию (PSSession) с удаленным компьютерам, на нем нужно включить службу WinRM и разрешить удаленные подключения:
Проверьте, что на клиенте запущена служба WinRM и слушатель службы (WinRM listener):
Get-Service -Name "*WinRM*" | fl
WinRM enumerate winrm/config/listener
В данном случае служба WinRM не настроена. Чтобы включить ее и разрешить удаленные подключения через WinRM, выполните команду:
winrm quickconfig
или
Enable-PSRemoting -Force
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.
Теперь проверьте возможно ли удаленное подключение к компьютеру через PSRemoting:
Test-WsMan compname1
Enable-PSRemoting -SkipNetworkProfileCheck
Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP' -RemoteAddress Any
Если компьютеры не в домене (используется рабочая группа), или вы обращаетесь к компьютерам через по IP адресам, в этом случае для аутентификации вместо Kerberos использоваться протокол NTLM. Чтобы разрешить NTLM аутентификацию через WimRM, нужно на компьютере (с которого вы будете устанавливать подключения) добавить имя/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
Выполнение удаленных команд через Invoke-Command
Чтобы выполнить одну команду на удаленном компьютере, нужно указать имя компьютера (
-ComputerName
) и саму команду в блоке
{ScriptBlock}
:
Invoke-Command -ComputerName dc01 -ScriptBlock {$PSVersionTable.PSVersion}
Командлет отправит указанную команду на удаленный компьютер и выведет результат в консоль (результат возвращается в виде объекта PowerShell). В данном случае мы проверили состояние службы Windows Update на удаленном компьютере.
CTRL+C
.Командлет Invoke-Command выполняет команды от имени пользователя, под которым запущена консоль PowerShell. Чтобы выполнить команду от другого пользователя, используется параметр
-Credential
:
$cred = Get-Credential
Invoke-Command -ComputerName comp-buh2 -Credential $cred -ScriptBlock {Get-NetAdapter}
Эта PowerShell команда выведет список сетевых интерфейсов на удаленном компьютере:
- Право на удаленное подключение через PSRemoting есть у членов группы локальных администраторов и Remote Management Users.
- Если в отправляемой на удаленный компьютер команде есть обращение к удаленному ресурсу (например папке сетевой папке), при аутентфикации на втором ресурсе произойдёт ошибка. Эта проблема называется double hop (двойной прыжок) и связана с ограничениями безопасности Windows, запрещающими передачу учетных данных на третий ресурс.
Если на компьютере нужно выполнить несколько последовательных команд, их нужно разделить точкой с запятой в блоке ScriptBlock. Следующая команда выведет текущий часовой пояс и изменит его на другой:
Invoke-Command -Computername dc01 -ScriptBlock {Get-TimeZone| select DisplayName;Set-TimeZone -Name "Astrakhan Standard Time"}
Командлет Invoke-Command позволяет запускать не только отдельные команды, но и скрипты PowerShell. Для этого нужно указать путь к локальному PS1 файлу со скриптом в параметре
-FilePath
:
Invoke-Command -ComputerName Server01 -FilePath c:\PS\Scripts\GetComputerInfo.ps1
- При таком способе закуска не нужно вручную копировать ваш PowerShell скрипт на удаленные компьютеры
- Настройки политики выполнения скриптов PowerShell игнорируются
Если нужно передать значение локальной переменной из скрипта в блок команд Invoke-Command, используется конструкцию
$using
:
$localVar = Get-Date
Invoke-Command -ComputerName Server01 -ScriptBlock {
"Date = $using:localVar"
}
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 есть ограничение на максимальное количество одновременных сессий с компьютерами. По умолчанию она равно 32 (определяется параметром ThrottleLimit. Если вам нужно выполнить команду одновременно более чем на 32 компьютерах (например, на 128), используйте параметр
–ThrottleLimit 128
.
Для запуска команд на удаленных компьютерах через Invoke-Command в фоновом режиме используется специальный атрибут
–AsJob
. В этом случае результат выполнения команды не возвращается в консоль. Чтобы получить результаты нужно использовать командлет
Receive-Job
.
Постоянные сессии в PowerShell Remoting
Если вам часто нужно выполнять команды на удаленном компьютер, можно создать постоянную PSRemoting сессию с удаленным компьютером. В этом случае командлету Invoke-Command не нужно будет каждый раз инициировать удаленное подключение.
Создадим постоянную (persistent) сессию с удаленным одним или несколькими компьютерами:
$s = New-PSSession -ComputerName Server01, Server02
Теперь, чтобы выполнить команду в созданном сеансе PSSession, используйте параметр -Session:
Invoke-Command -Session $s -ScriptBlock { Get-ComputerInfo|select OsLastBootUpTime }
Чтобы закрыть удаленную сессию, нужно закрыть консоль PowerShell или выполнить:
Remove-PSSession $s
я бы еще добавил, что по умолчанию права на подключение через 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 потока.