如何从另一个 PS 会话启动新的 PowerShell 会话,并运行带有 splatted 参数的 CmdLet

Swi*_*ter 3 powershell powershell-v6.0

目标:

是为了能够测试以查看是否安装了 PowerShell v6(可以),如果是,则为某些 CmdLet 调用该 shell。这将在 PowerShell v5.1 中运行的脚本中调用。我无法完全转移到 v6,因为还有其他依赖项在此环境中尚不可用,但是,v6 对某些 CmdLet 进行了重大优化,从而将操作改进了 200 多倍(具体而言,Invoke-WebRequest调用将导致下载一个大文件 - 在 v5.1 中,一个 4GB 的文件需要 1 小时以上才能下载,在 v6 中,使用同一子网上的相同机器,这将需要大约 30 秒。

附加点:

但是,我还建立了一组动态参数,用于插入 CmdLets 参数列表。例如,构建的参数列表将类似于:

$SplatParms = @{
    Method = "Get"
    Uri = $resource
    Credential = $Creds
    Body = (ConvertTo-Json $data)
    ContentType = "application/json"
}
Run Code Online (Sandbox Code Playgroud)

并且运行 CmdLet 通常会按预期工作:

Invoke-RestMethod @SplatParms
Run Code Online (Sandbox Code Playgroud)

已经尝试过的:

在过去的几天里,我查看了这个论坛和其他地方的各种帖子。我们可以创建一个简单的脚本块,它可以被调用并按预期工作:

$ConsoleCommand = { Invoke-RestMethod @SplatParms }

& $ConsoleCommand
Run Code Online (Sandbox Code Playgroud)

但是,尝试在Start-ProcessCmdLet 中传递相同的东西失败了,因为我猜参数哈希表没有被评估:

Start-Process pwsh -ArgumentList "-NoExit","-Command  &{$ConsoleCommand}" -wait
Run Code Online (Sandbox Code Playgroud)

结果是:

Invoke-RestMethod : Cannot validate argument on parameter 'Uri'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:1 char:22
+ &{ Invoke-RestMethod @SplatParms }
Run Code Online (Sandbox Code Playgroud)

接下来在哪里?

我想我必须以某种方式将参数作为参数传递,以便可以对它们进行评估和处理,但是,语法使我无法理解。我什至不确定是否Start-Process是最好的 CmdLet,但我应该看看其他东西,比如Invoke-Command,还是完全不同的东西?

将这个 CmdLet 的结果返回到原始 shell 会很棒,但目前,它只会采取一些功能。

mkl*_*nt0 5

注意:原则上,此答案中的技术不仅可以应用于从 Windows PowerShell 到 PowerShell Core 的调用,还可以应用于相反的方向,以及在 Windows 和 Unix 上相同PowerShell 版本的实例之间。

你不需要Start-Process; 您可以使用脚本块直接调用pwsh

pwsh -c { $SplatParms = $Args[0]; Invoke-RestMethod @SplatParms } -args $SplatParms
Run Code Online (Sandbox Code Playgroud)

请注意需要将哈希表作为参数传递,而不是作为脚本块的一部分。

  • 不幸的是,从 Windows PowerShell 5.1 / PowerShell Core 6.0.0 开始,[PSCredential]这种方式传递实例存在问题- 请参见底部解决方法

这将同步执行,甚至在控制台中打印脚本块的输出。

警告是,捕捉这样的输出-要么将其分配给变量或重定向到一个文件-如果可以返回所不具备的呼叫会话类型的实例失败

作为一个次优的解决方法,你可以使用-o Text-OutputFormat Text的感谢,PetSerAl捕获输出为文本,正是因为它会打印到控制台 (运行pwsh -h看所有选项)。

输出默认以序列化的 CLIXML 格式返回,调用 PowerShell 会话将其反序列化回对象。如果无法识别序列化对象的类型,则会发生错误。

一个简单的例子(从Windows PowerShell执行):

# This FAILS, but you can add `-o text` to capture the output as text.
WinPS> $output = pwsh -c { $PSVersionTable.PSVersion } # !! FAILS
pwsh : Cannot process the XML from the 'Output' stream of 'C:\Program Files\PowerShell\6.0.0\pwsh.exe': 
SemanticVersion XML tag is not recognized. Line 1, position 82.
...
Run Code Online (Sandbox Code Playgroud)

这将失败,因为PowerShell Core 中$PSVersionTable.PSVersion的类型[System.Management.Automation.SemanticVersion]是 PowerShell Core,这是自 v5.1 起在 Windows PowerShell 中不可用的类型(在 Windows PowerShell 中,相同属性的类型为[System.Version])。


无法传递[PSCredential]实例的解决方法:

pwsh -c { 
          $SplatParms = $Args[0];
          $SplatParams.Credential = [pscredential] $SplatParams.Credential;
          Invoke-RestMethod @SplatParms
        } -args $SplatParms
Run Code Online (Sandbox Code Playgroud)

使用脚本块从 PowerShell 中调用另一个 PowerShell 实例涉及 CLIXML 格式的对象的序列化和反序列化,也用于 PowerShell 远程处理。

一般来说,有许多.NET类型的反序列化不能忠实地再现,在这种情况下,创建[PSCustomObject]该实例模拟原始类型的实例,用(通常隐藏).pstypenames属性反映前缀原来的类型名称Deserialized.

从 Windows PowerShell 5.1 / PowerShell Core 6.0.0 开始,[pscredential]( [System.Management.Automation.PSCredential]) 的实例也会发生这种情况,这会阻止它们在目标会话中直接使用 - 请参阅此 GitHub 问题

然而,幸运的是,简单地将反序列化的对象转换回[pscredential]似乎可以工作