3

目标:

是为了能够测试是否安装了 PowerShell v6(没问题),如果是,则为某些 CmdLets 调用该 shell。这将在 PowerShell v5.1 中运行的脚本中调用。我无法完全转换到 v6,因为在此环境中还有其他依赖项尚无法工作,但是,v6 对某些 CmdLets 提供了显着优化,导致操作改进超过 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

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

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 会很棒,但目前,它只需要一些功能。

4

3 回答 3

5

注意:原则上,此答案中的技术不仅可以应用于从 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 会话将其反序列化回对象。如果无法识别序列化对象的类型,则会发生错误。

一个简单的示例(从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.
...

这失败了,因为$PSVersionTable.PSVersionis 属于[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

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

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

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

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

于 2018-01-21T17:24:56.720 回答
0

尝试在 5.1 会话中针对 6.0 创建一个 New-PSSession。

安装 powershell core 6.0 并运行后Enable-PSRemoting,为 6.0 创建了一个新的 PSSessionConfiguration:

PS > Get-PSSessionConfiguration


Name          : microsoft.powershell
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell.workflow
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : microsoft.powershell32
PSVersion     : 5.1
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : PowerShell.v6.0.0
PSVersion     : 6.0
StartupScript : 
RunAsUser     : 
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote 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

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

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 的块并且知道如何适应。

于 2018-01-21T18:24:01.080 回答
-1

使用 start-process 立即闪烁是不合适的,必须对此进行研究,无论如何,我的反应是构建一个参数数组以使用而不是 splat 尝试。

于 2018-01-23T09:29:55.927 回答