Passing a function to a PowerShell subprocess (without making a terrible, terrible mess)
This took a bit of experimenting. I’m still not completely certain that this is the most reasonable way to go about this, but I’ve already lost a few hours of my life to it, so oh well. It works.
I had a script, we’ll call it Charles.
Charles has functions.
I need to run some of Charles’s functions in a subprocess, so I can be sure all of Charles’s son’s handles are closed and collected by the GC.
If I don’t do this, things will go horribly wrong and none of Charles’s bodily functions will, well, function.
Charles is being difficult.
When I try feeding Charles an escaped string, Charles spits it right back out into my terminal (with a large helping of red error messages), which makes me sad.
When I try feeding Charles my functions, Charles makes me sad, yet again, by eviscerating my functions and eating random characters.
Then, I had a revelation. Maybe Charles doesn’t have an appetite for base64 strings?
I settled on parsing the functions with Get-Command, reassembling them into a string, then encoding the string (and the command I would like my subprocess to do) to base64 and feeding THAT to my powershell.exe
call via the -EncodedCommand
argument.
This works!
You could scrape from the script file itself (pull the path to the file from $MyInvocation
, use regular expressions to filter for functions) but that is quite nasty.
Not that this isn’t nasty. But this is a little less nasty.
You could also access the functions with namespace variable notation (e.g., ${function:Get-Coffee}
, to pull Get-Coffee
right on out of the function:
PSDrive). This does effectively the same thing as Get-Command, but I feel it’s a little less clear.
Here is a trimmed down example of what I wound up doing:
# select the functions from our script that are needed in the subprocess
$FunctionNames = @(
'Load-UserHives',
'Set-RegistryKey',
'Set-HKUKeyInner'
)
# parse them from memory, pull names in array out of Command bank
$FunctionBlocks = foreach ($Name in $FunctionNames) {
$Function = Get-Command $Name -CommandType Function
if ($Function) {
# then reassemble them into valid PowerShell stored as a string
"function $($Function.Name) {$($Function.Definition)}`n"
}
}
# include our function definitions in a new script, then do what we need to
$SubprocessScript = @"
$($FunctionBlocks -join "`n`n")
Set-HKUKeyInner -SetDefault:`$$($SetDefault) -Verbose:$($null -ne $VerbosePreference)
"@
# encode the new script so it doesn't need to have awful escaping magic applied
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($SubprocessScript))
# send the assembled script off to Charles's young son for usage
powershell.exe -NoProfile -EncodedCommand $Encoded
Anyway. That was fun! On to the next one.