目标:
是能够测试以查看是否安装了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 的块并知道如何适应。
使用启动过程立即刷新是不合适的,必须为此进行研究,无论如何,我的反应是构建一个参数数组来使用而不是拼接它们进行尝试。