如何从另一个PS会话启动新的PowerShell会话,并使用splatted参数

时间:2018-01-21 16:30:12

标签: powershell powershell-v6.0

目标:

是否能够测试是否安装了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-Process CmdLet中传递相同的内容失败了,因为我猜测参数哈希表没有被评估:

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 }

下一步?

我想我不得不以某种方式传递参数作为参数,以便可以对它们进行评估和splatted,但是,语法使我望而却步。我甚至不确定Start-Process是否是最好使用的CmdLet,而是我应该查看其他内容,例如Invoke-Command,还是完全不同的东西?

将这个CmdLet的结果返回到原始shell会很棒,但此刻,它只需要一些功能。

3 个答案:

答案 0 :(得分:3)

注意:原则上,此答案中的技术不仅可以应用于从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.PSVersion在PowerShell Core中的类型为[System.Management.Automation.SemanticVersion],这是Windows PowerShell自v5.1起不可用的类型(在Windows PowerShell中,相同属性的类型为{{ 1}})。

无法传递[System.Version]实例的解决方法:

[PSCredential]

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

通常,有许多.NET类型反序列化无法忠实地重新创建,并且在这种情况下创建{em}模拟原始类型实例的pwsh -c { $SplatParms = $Args[0]; $SplatParams.Credential = [pscredential] $SplatParams.Credential; Invoke-RestMethod @SplatParms } -args $SplatParms 个实例,其中(通常是隐藏的){{ 1}}反映前缀为[PSCustomObject]

的原始类型名称的属性

从Windows PowerShell 5.1 / PowerShell Core 6.0.0开始,.pstypenamesDeserialized.)的实例也会出现这种情况,这会阻止他们在目标会话中使用直接 - 见this GitHub issue

幸运的是,只需将反序列化的对象强制转换回[pscredential]似乎可以正常工作

答案 1 :(得分: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。结果作为对象返回。根据mklement0的回答,Scriptblocks可能需要通过-ArgumentList传递的局部变量。

$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接收一个块并知道如何容纳。

答案 2 :(得分:-1)

使用启动过程立即闪现是不合适的,不得不对此进行研究,无论如何,我的反应是构建一系列参数,而不是使用它们进行尝试。