如何从另一个 PS 会话启动新的 PowerShell 会话,并使用拆分参数运行 CmdLet



目标:

是能够测试以查看是否安装了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"
}

正常运行 CmdLet 将按预期工作:

Invoke-RestMethod @SplatParms

已经尝试过的内容:

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

$ConsoleCommand = { Invoke-RestMethod @SplatParms }
& $ConsoleCommand

但是,尝试在 CmdLetStart-Process传递相同的内容失败,因为我想没有评估参数哈希表:

Start-Process pwsh -ArgumentList "-NoExit","-Command  &{$ConsoleCommand}" -wait

结果:

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 }

下一步在哪里?

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

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

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

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

pwsh -c { $SplatParms = $Args[0]; Invoke-RestMethod @SplatParms } -args $SplatParms

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

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

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

需要注意的是如果返回调用会话中不可用的类型实例,则捕获此类输出(通过分配给变量或重定向到文件)可能会失败

作为次优解决方法,您可以使用-o Text(-OutputFormat Text)感谢PetSerAl将输出捕获为文本就像打印到控制台一样(运行pwsh -h以查看所有选项)。

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

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

# 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 FilesPowerShell6.0.0pwsh.exe': 
SemanticVersion XML tag is not recognized. Line 1, position 82.
...

此操作失败,因为$PSVersionTable.PSVersion在PowerShell Core中属于[System.Management.Automation.SemanticVersion]类型,而从v5.1开始,这种类型在Windows PowerShell中不可用(在Windows PowerShell中,同一属性的类型为[System.Version])。


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

pwsh -c { 
$SplatParms = $Args[0];
$SplatParams.Credential = [pscredential] $SplatParams.Credential;
Invoke-RestMethod @SplatParms
} -args $SplatParms

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

通常,有许多 .NET 类型无法持久地重新创建,在这种情况下,会创建模拟原始类型实例的[PSCustomObject]实例,其中(通常隐藏的).pstypenames属性反映以Deserialized.为前缀的原始类型名称

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

但是,幸运的是,简单地将反序列化的对象转换回[pscredential]似乎有效

尝试在5.1 会话中创建针对 6.0 的新 PSSession。

在安装 Powershell 核心 6.0 并运行Enable-PSRemoting后,为 6.0 创建了一个新的 PSSessionConfiguration:

PS > Get-PSSessionConfiguration

Name          : microsoft.powershell
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITYINTERACTIVE AccessAllowed, BUILTINAdministrators AccessAllowed, BUILTINRemote Management Users AccessAllowed
Name          : microsoft.powershell.workflow
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : BUILTINAdministrators AccessAllowed, BUILTINRemote Management Users AccessAllowed
Name          : microsoft.powershell32
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITYINTERACTIVE AccessAllowed, BUILTINAdministrators AccessAllowed, BUILTINRemote Management Users AccessAllowed
Name          : PowerShell.v6.0.0
PSVersion     : 6.0
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITYINTERACTIVE AccessAllowed, BUILTINAdministrators AccessAllowed, BUILTINRemote Management Users AccessAllowed

在父脚本中,使用 6.0 配置名称创建新会话PowerShell.v6.0.0并将其传递给所需的任何后续Invoke-Command。结果作为对象返回。脚本块可能需要通过-ArgumentList传递的局部变量,每个 mklement0 的答案。

$ps6sess = New-PSSession -ComputerName localhost -ConfigurationName 'PowerShell.v6.0.0'
$results = Invoke-Command -Session $ps60sess -ScriptBlock {Param($splatthis) Invoke-WebRequest @splatthis} -ArgumentList $SplatParms

了解会话在调用命令调用之间持续存在也可能很有用。例如,您创建的任何新变量都可以在该会话的后续调用中访问:

PS > Invoke-Command -Session $ps60sess -ScriptBlock {$something = 'zers'}
PS > Invoke-Command -Session $ps60sess -ScriptBlock {write-host $something }
zers

PSCredential传递的问题似乎不是这种方法的问题:

$ps6sess = New-PSSession -ComputerName localhost -ConfigurationName 'PowerShell.v6.0.0'
$credential = Get-Credential -UserName 'TestUser'
$IRestArgs = @{
Method='GET'
URI = 'https://httpbin.org'
Credential = $credential
}
$IRestBlock = {Param($splatval) Invoke-RestMethod @splatval}
Invoke-Command -Session $ps6sess -ScriptBlock $IRestBlock -ArgumentList $IRestArgs
# no error
pwsh -c { 
Param ($SplatParms)
#$SplatParams.Credential = [pscredential] $SplatParams.Credential;
Invoke-RestMethod @SplatParms
} -args $IRestArgs
# error - pwsh : cannot process argument transformation on 
#   parameter 'Credential. username

也许在 ps6 会话中知道它正在接收来自 ps5.1 的块并知道如何适应。

使用启动过程立即刷新是不合适的,必须为此进行研究,无论如何,我的反应是构建一个参数数组来使用而不是拼接它们进行尝试。

相关内容

  • 没有找到相关文章

最新更新