I have a PowerShell script that needs to execute a second script in a new PowerShell instance, passing in two objects. The problem that happens is that the objects get converted to strings containing the object type in the second script. Here are two sample scripts that illustrate what I'm trying to do. I've tried with Start-Process and with Invoke-Expression. I've also tried splatting the arguments, but that doesn't work at all - nothing is passed.
Script 1:
$hash1 = @{
"key1" = "val1"
"key2" = "val2"
}
$hash2 = @{
"key3" = "val3"
"key4" = "val4"
}
$type1 = $hash1.GetType()
$type2 = $hash2.GetType()
Write-Host "Hash1 type: $type1"
Write-Host "Hash2 type: $type2"
$scriptPath = Split-Path -parent $MyInvocation.MyCommand.Definition
$method = "Start-Process"
Start-Process -FilePath PowerShell "-noExit -command $scriptPath\script2.ps1 -hash1 $hash1 -hash2 $hash2 -method $method"
$method = "Invoke-Expression"
$command = "$scriptPath\script2.ps1 -hash1 $hash1 -hash2 $hash2 -method $method"
Invoke-Expression "cmd /c Start PowerShell -noExit -Command { $command }"
Script 2:
param(
[PSObject]$hash1,
[PSObject]$hash2,
[string]$method
)
$type1 = $hash1.GetType()
$type2 = $hash2.GetType()
Write-Host "Method: $method"
Write-Host "Hash1 type: $type1"
Write-Host "Hash2 type: $type2"
Here's the output from each call:
Method: Start-Process
Hash1 type: string
Hash2 type: string
Method: Invoke-Expression
Hash1 type: string
Hash2 type: string
Here's some background on why I'm trying to do it this way:
I have a script that reads an XML file containing ACL information for multiple directories on a filesystem. I need to set the permissions on each directory, but each one takes time to propagate to all child items. I want to call the second script asynchronously for each directory to reduce runtime. I need each instance of the second script to have its own window so the user can review each one for errors or other messages.
Can anyone help please?
Here is a simple setup using Runspace
, this allows you to run async code with no serialization and no need for weird invocations of your script via Start-Process
.
I'll use only one hashtable
for this simple example, to keep it as short as possible.
script1.ps1
$hash1 = @{
key1 = 'hello'
key2 = 'world'
}
$scriptPath = Join-Path $PSScriptRoot -ChildPath script2.ps1
$action = {
param($path, $hash1, $method)
& $path -hash1 $hash1 -method $method
}
$params = @{
path = $scriptPath
hash1 = $hash1
method = 'Runspace'
}
$iss = [initialsessionstate]::CreateDefault2()
$rs = [runspacefactory]::CreateRunspace($Host, $iss)
$rs.Open()
$ps = [powershell]::Create().AddScript($action).AddParameters($params)
$ps.Runspace = $rs
$async = $ps.BeginInvoke()
# Do my thing in this script while the other instance runs
# ...
# ...
# Receive results from the instance, this is stdout (Success stream)
$output = $ps.EndInvoke($async)
$output # => hello world
# Non-terminating errors are here
$ps.Streams.Error
# Dipose the instance and the runspace when done
$ps, $rs | ForEach-Object Dispose
script2.ps1
param(
[PSObject] $hash1,
[string] $method
)
Write-Host "Method: $method"
Write-Host "Hash1 type: $($hash1.GetType())"
'Standard Output: ' + $hash1['key1'] + ' ' + $hash1['key2']
Write-Warning "A Warning"
Write-Error "An Error!"
0..10 | ForEach-Object {
Write-Progress -Activity 'Testing Progress' -PercentComplete ($_ * 10)
Start-Sleep -Milliseconds 200
}
Worth noting that, since the Runspace was initialized targeting the CreateRunspace(PSHost, InitialSessionState)
overload and we're passing the automatic variable $Host
as the PSHost
argument, the host is now associated with the new Runspace, this means that all Streams will go directly to our console with the exception of the Error Stream where all non-terminating errors will go and the Success Stream which we can capture after our call to .EndInvoke(IAsyncResult)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With