PowerShell Invoke-Command: 一个全面的指南

IT专业人士很少只在本地计算机上工作。使用PowerShell的Invoke-Command cmdlet,我们不必这样做!该cmdlet允许我们无缝地编写代码,就好像我们在本地计算机上工作一样。

通过使用PowerShell远程功能,Invoke-Command cmdlet是一个常用的PowerShell cmdlet,允许用户在PSSession中执行代码。这个PSSession可以是之前使用New-PSSession cmdlet创建的,也可以快速创建和销毁一个临时会话。

相关: PowerShell Remoting:终极指南

将Invoke-Command视为PowerShell的psexec。尽管它们的实现方式不同,但概念是相同的。获取一小段代码或命令并在远程计算机上“本地”运行。

但要使Invoke-Command正常工作,您必须在远程计算机上启用并可用PowerShell Remoting。默认情况下,所有Windows Server 2012 R2或更高版本的机器都已启用它,并具有适当的防火墙例外。如果不幸的是,您仍在使用Server 2008机器,有多种设置Remoting的方法,但一个简单的方法是在远程机器上运行winrm quickconfigEnable-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 "Installer path is: $InstallerFilePath" }

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

请注意,$InstallerFilePath 的值为空。该变量未展开,因为未传递到远程计算机。要将本地定义的变量传递到远程脚本块,我们有两个选项;我们可以在脚本块内使用$using:前缀变量名,或者可以使用Invoke-Command 参数ArgumentList。让我们看看这两者。

ArgumentList 参数

将本地变量传递到远程脚本块的一种方法是使用Invoke-CommandArgumentList参数。该参数允许您将本地变量传递给参数,并在脚本块中用占位符替换本地变量引用。

将本地变量传递给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 }

PowerShell $using结构要简单得多,但如果你有机会学习Pester,你会发现ArgumentList会是你的好朋友。

Invoke-Command和New-PSSession

从技术上讲,本文仅涉及Invoke-Command,但为了演示其用途,我们需要简要介绍一下New-PSSession命令。回想一下,我之前提到过Invoke-Command可以使用“临时”命令或使用现有会话。

在本文中,我们一直在远程计算机上运行“临时”命令。我们一直在启动一个新会话、运行代码并关闭它。这对于一次性的情况来说没问题,但在您对同一台计算机执行数十个命令时就不太合适了。在这种情况下,最好是预先创建一个新的PSSession,并通过New-PSSession来重用现有的PSSession。

在运行任何命令之前,您首先需要使用New-PSSession创建一个 PSSession。我们可以通过简单地运行$session = New-PSSession -ComputerName WEBSRV1来实现这一点。这将在服务器上创建一个远程会话,并在我的本地计算机上创建对该会话的引用。在这一点上,我可以用Session替换我的ComputerName引用,并将Session指向我保存的$session变量。

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

当运行时,您会注意到性能更快,因为会话已经建立。然而,完成后,使用Remove-PSSession移除打开的会话是很重要的。

总结

Invoke-Command PowerShell 命令是最常见和强大的命令之一。我个人几乎使用所有命令中的最多之一。它的易用性和在远程计算机上运行任何代码的能力非常强大,是我建议从头到尾学习的一个命令!

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