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

4

4

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
Fakery answered 3/12, 2015 at 21:28 Comment(2)
$b=[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([Management.Automation.PSSerializer]::Serialize($a))) $c=[Management.Automation.PSSerializer]::Deserialize([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b)))Contemn
Excellent approach, PetSerAl !!! I'll try this out soon.Fakery
A
6

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.

Autolysis answered 3/12, 2015 at 22:25 Comment(5)
Since I'll be passing it into start-process, newlines may muck it up, but I can probably try it both ways. Thank you for the explanation. I'll report back once I try this out later this morning.Fakery
@KirkLiemohn There is a powershell command line option to accept command input Base64 encoded. So you can use that to avoid escaping issues, including with newline.Zoology
@user2460798 true, but then he can't specify a file to run (if that was the plan). He'd have to generate code that embeds the XML object into the code he already wanted to run, and then base64 encode the whole thing. Also that must be unicode (as opposed to UTF8), which doubles the size of the base64 encoded string, making it more likely to hit the command line limit.Autolysis
I can't give you all the correct answer, but all were helpful. I gave it to briantist, but if @PetSerAl had just used an answer instead of a comment, I would have give it to him/her because I did end up using the base 64 encoding on top of the serialization.Fakery
@KirkLiemohn I typically wait for a user who posts an answer as a comment to post it as an answer, or leave a comment saying I'll retract mine if they post one later. PetSerAl's solutions are usually correct but often not posted as an answer so I've gotten used to it by now and just go with it.Autolysis
B
1

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.

Bangup answered 3/12, 2015 at 23:12 Comment(3)
If this starts another process in another window this should work well for me. I may try it out as well. Everyone has given me some good options. I'll report back later today.Fakery
Based on a quick test, this feature won't work if you start powershell in another window.Zoology
OK. Thanks for the quick test. I went with start-process and the base 64 encoding + serialization originally recommended by @PetSerAl. Very similar to what you have, except you are calling "PowerShell" vs. start-process. I suspect that would have worked just fine.Fakery
A
1

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
Apposite answered 20/2, 2016 at 5:34 Comment(0)
P
0

Using this ConvertTo-Expression cmdlet:

Parent script:

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

Child script:

$Expression = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($credSerial))
$Cred = Invoke-Expression $Expression   # or safer: &([ScriptBlock]::Create($Expression))
Please answered 19/10, 2019 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.