有没有一种方法可以将可序列化对象传递给带有启动进程的 PowerShell 脚本?

Is there a way to pass serializable objects to a PowerShell script with start-process?

我知道 PowerShell 作业,但我想使用 start-process 并将可序列化对象传递给新的 powershell 进程。有什么办法吗?

似乎使用 start-process 你必须提供一个字符串参数列表,这对我来说是行不通的。我正在尝试从一个进程获取 PSCredential 到另一个进程(或 SecureString,我将采用其中一个)。也许这绕过了安全。


更新 - 添加我在看到其他人的帮助后使用的解决方案(使用来自@PetSerAl 的解决方案)

我写了两个测试脚本:父脚本和子脚本。父脚本调用子脚本。

父脚本:

$securePassword = ConvertTo-SecureString "testpassword" -AsPlainText -Force
$cred = New-Object PSCredential("testuser", $securePassword)
$credSerial = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([Management.Automation.PSSerializer]::Serialize($cred)))

$psFile = "C:\repos\Test\PowerShell Scripts\KirkTestChild.ps1"
$p1 = "-someparam ""this is a test!!!"""
$p2 = "-cred ""$credSerial"""

$proc = Start-Process PowerShell.exe -PassThru:$true -Argument "-File ""$($psFile)""", $p1, $p2
Write-Host "ID" $proc.Id
Write-Host "Has Exited" $proc.HasExited

Start-Sleep -Seconds 15
Write-Host "Has Exited" $proc.HasExited

子脚本:

Param(
    $someParam,
    $cred
)

Write-Host "someParam: $($someParam)"
Write-Host "cred (raw): $($cred)"
$realCred=[Management.Automation.PSSerializer]::Deserialize([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($cred)))
Write-Host "cred user: $($realCred.UserName)"
Write-Host "start"
Start-Sleep 5
Write-Host "ending"
Start-Sleep 5

是的。正如 PetSerAl 在评论中所写,您可以使用 PSSerializer class 来处理:

$ser = [System.Management.Automation.PSSerializer]::Serialize($credential)

$cred = [System.Management.Automation.PSSerializer]::Deserialize($ser)

虽然我不知道您的进程是否理解 CliXML 格式;您没有指定要启动的进程。

由于这会产生多行的 XML,因此将其作为参数传递给进程可能无法正常工作,这就是为什么 PetSerAl 将代码包含到 Base64 中,对结果 XML.

您也可以通过 STDIN 而不是 Base64 编码传递 XML,这也确保您不会意外达到命令行大小限制。

您可以将脚本块作为参数从 parent PS 进程传递到 child PS 进程。 child 进程执行脚本的结果被序列化并返回给 parent。 (运行 powershell -? 以获得有关此功能的帮助)。因此,如果您想要将凭证移动的方向是从 child 到 parent 那么这是一个替代方案,或者如果您想要在两个方向上移动数据,那么您可以将之前的答案与这种方法结合起来. (OP没有说哪个方向)。

编辑

我假设 OP 想要启动另一个 Powershell 进程 - 因为他说 "new Powershell process"。他想将 PSCredential 从 parent 传递到 child,反之亦然。我猜是前者。基于 PetSerAl 的解决方案,他可以将 cred 序列化为 CliXML。它将有新的线路。除了可能导致将参数传递给 Powershell 时出现问题外,如果换行符位于值的中间,还会导致反序列化失败。因此,为避免这些问题,可以对整个脚本进行 base64 编码并通过 -EncodedCommand 参数传递。它看起来像这样:

$xmlStr = [management.automation.psserializer]::Serialize($cred)
$scriptStr = "$liveCred = " +
  "[management.automation.psserializer]::Deserialize('$xmlstr');" +
  "# PS statements to do something with $liveCred"
$inB64 = [Convert]::ToBase64String( [Text.Encoding]::UTF8.GetBytes($scriptStr))
powershell -EncodedCommand $inB64

如果 OP 需要从 child PS 中的 运行 脚本返回一些东西,并且想使用此答案前面提到的脚本块功能,我不知道可以使用这种方法,因为该功能与 -Command 参数有关。相反,需要删除换行符,转义问题可能会发挥作用。

这是一个使用 ScriptBlock 而不是脚本文件的变体。请参阅 Greg Bray at Powershell Start-Process to start Powershell session and pass local variables 的 post。

$securePassword = ConvertTo-SecureString 'testpassword' -AsPlainText -Force
$cred = New-Object PSCredential( 'testuser', $securePassword )

$scriptBlockOuter = {
    $sb = {
        Param(
           [Parameter( Mandatory, Position = 0 )]
           [String] $someParam,
           [Parameter( Mandatory, Position = 1 )]
           [String] $credSerial
        )
        $cred = [System.Management.Automation.PSSerializer]::Deserialize( [System.Text.Encoding]::UTF8.GetString( [System.Convert]::FromBase64String( $credSerial )))
        Write-Host "someParam: $someParam"
        Write-Host "cred user: $($cred.UserName)"
        Write-Host 'start'
        Start-Sleep 5
        Write-Host 'ending'
        Start-Sleep 5
    }
}

$p1 = 'this is a test!!!'
$credSerial = [System.Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes( [System.Management.Automation.PSSerializer]::Serialize( $cred )))

$proc = Start-Process PowerShell -PassThru -ArgumentList '-Command', $scriptBlockOuter, '& $sb', '-someParam', "'$p1'", '-credSerial', "'$credSerial'"
Write-Host 'ID' $proc.Id
Write-Host 'Has Exited' $proc.HasExited

Start-Sleep -Seconds 15
Write-Host 'Has Exited' $proc.HasExited

使用此 ConvertTo-Expression cmdlet:

父脚本:

$Expression = $Cred | ConvertTo-Expression
$credSerial = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Expression))

子脚本:

$Expression = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($credSerial))
$Cred = Invoke-Expression $Expression   # or safer: &([ScriptBlock]::Create($Expression))