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

10,429

Solution 1

Yes. As PetSerAl wrote in a comment, you can use the PSSerializer class to handle that:

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

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

I don't know if your process will understand the CliXML format though; you don't specify what process you're starting.

Since this will produce XML which is multi-line, it may not work to pass this to a process as a parameter which is why PetSerAl is including the code to Base64 encode the resulting XML.

You might also be able to pass the XML over STDIN instead of Base64 encoding, which also ensures that you won't accidentally hit the command line size limit.

Solution 2

You can pass a scriptblock as an argument from a parent PS process to a child PS process. The result of the child process executing the script is serialized and returned to the parent. (Run powershell -? to get help on this feature). So if the direction you want to move the credential is from child to parent then this is an alternative, or if you want to move data in both directions, then you could combine the previous answers with this approach. (The OP doesn't say which direction).

EDIT

I'm assuming OP wants to start another Powershell process - because he said "new Powershell process". And he wants to pass a PSCredential from either the parent to the child or vice versa. I'd guess the former. Based on PetSerAl's solution he could serialize the cred as CliXML. It would have new lines. Besides potentially causing problems with passing the arguments to Powershell, the newlines will cause the Deserialize to fail if they are in the middle of a value. So to avoid those problems the whole script can be base64 encoded and passed via the -EncodedCommand parameter. It'll look something like this:

$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

If OP needs something back from the script that ran in the child PS, and wanted to use the scriptblock feature mentioned earlier in this answer, I don't he could use this approach, because that feature is related to the -Command parameter. Rather newlines would need to be removed and escaping concerns might come into play.

Solution 3

Here's a variation that uses a ScriptBlock instead of script files. See the post by Greg Bray at Powershell Start-Process to start Powershell session and pass local variables.

$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
Share:
10,429
Kirk Liemohn
Author by

Kirk Liemohn

I focus on SharePoint, Office 365, and Azure development and integration. I have a history of integrating products with SharePoint. My focus lately has been around Office 365 and Azure.

Updated on June 15, 2022

Comments

  • Kirk Liemohn
    Kirk Liemohn almost 2 years

    I know about PowerShell jobs, but I want to use start-process and pass a serializable object to the new powershell process. Is there any way to do this?

    It seems that using start-process you have to provide a string argument list which won't cut it for me. I'm trying to get a PSCredential from one process to another (or a SecureString, I'll take either one). Maybe this circumvents security.


    UPDATE - adding the solution I used after seeing help from others (using solution from @PetSerAl)

    I wrote two test scripts: a parent script and a child script. The parent script calls the child script.

    Parent Script:

    $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
    

    Child Script:

    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