How can a Windows PowerShell script pass its parameters through to another script invocation?
Solution 1
This is probably going to look a little odd, but:
param(
[String] $stringArg,
[Int32] $int32Arg
)
if ([IntPtr]::Size -gt 4)
{
[String] $scriptPath = $MyInvocation.MyCommand.Path;
$params = @()
$psboundparameters.keys |
foreach {
$params += "-$($_)"
$params += $psboundparameters.$_
}
$sb = [scriptblock]::Create(@"
&'$scriptpath' $params
"@)
[Object] $job = Start-Job -scriptblock $sb -RunAs32
$job | Wait-Job | Receive-Job;
}
else
{
Get-Date
}
Solution 2
You could re-launch your script programmatically as a 32 bit process like this:
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[String] $stringArg,
[parameter(Mandatory=$true)]
[Int32] $int32Arg
)
if ([IntPtr]::Size -ne 4) {
$p = $PSBoundParameters
$32bitPs = 'C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe'
$myArgs = @($MyInvocation.MyCommand.Path)
$myArgs += ( $p.Keys | % { ('-' + $_), $p.Item($_) } )
Start-Process -NoNewWindow -FilePath $32bitPs -ArgumentList $myArgs
exit
}
Write-Host "Hi I'm a 32 bit process, my args are:"
$PSBoundParameters
# Do 32 bit stuff here...
Read-Host "Press enter to quit"
Comments
-
Lance U. Matthews almost 2 years
In short, I'm just looking for the PowerShell equivalent of how a batch file can call a script with the same parameters...
"%~dpnx0" %*
...where
"%~dpnx0"
expands to the absolute path of the script and%*
is expanded to the list of parameters. Is there an easy way to replicate%*
? Or, at least, a way that works?I have a PowerShell script that uses the
System.Data.OleDb
namespace to read data from anExcel
workbook. Because there is no 64-bit implementation of the Microsoft Jet provider, the script needs to be run from a 32-bit PowerShell session. Rather than simply having the script fail with an error message if it's run from a 64-bit session, I'd like to have the 64-bit session invoke the script in and retrieve the results from a 32-bit session. I found that this can be done using theStart-Job
cmdlet with the-RunAs32
switch, however I'm having trouble providing a value for the-ArgumentList
parameter.I came up with the following to search for whichever script parameters have values and build a command line out of them:
function GetValueText([Object] $value) { [String] $text = if ($value -eq $null) { '$null'; } elseif ($value -is [String]) { "'$value'"; } elseif ($value -is [Array]) { '@({0})' -f (($value | ForEach-Object { GetValueText $_ }) -join ', '); } else { "$value"; } return $text; } if ([IntPtr]::Size -gt 4) { [String] $scriptPath = $MyInvocation.MyCommand.Path; [String[]] $parameters = @( $MyInvocation.MyCommand.Parameters.Keys ` | ForEach-Object { [Object] $parameterValue = Get-Variable -Name $_ -ValueOnly; if ($parameterValue -ne $null) { [String] $parameterValueText = GetValueText $parameterValue; '-{0}' -f $_; $parameterValueText; } } ); [Object] $job = Start-Job -FilePath $scriptPath -RunAs32 -ArgumentList $parameters; [Object[]] $data = $job | Wait-Job | Receive-Job; $data; } else { # Retrieve data... }
When it gets to the
Start-Job
line it generates an error with message"Cannot convert value "-Argument1" to type "System.Int32[]""
.-Argument1
is the script's first parameter and is of type[Int32[]]
, so does this mean that-ArgumentList
only works with positional and not named parameters?I've also tried simplifying it to...
param( [String] $stringArg, [Int32] $int32Arg ) $PSBoundParameters; if ([IntPtr]::Size -gt 4) { [String] $scriptPath = $MyInvocation.MyCommand.Path; [Object] $job = Start-Job -FilePath $scriptPath -RunAs32 -ArgumentList @PSBoundParameters; $job | Wait-Job | Receive-Job; } else { Get-Date; }
...but when I run
.\Test.ps1 'string' 12345
from a 64-bit session, it displays...Key Value --- ----- stringArg string int32Arg 12345 Start-Job : Missing an argument for parameter 'ArgumentList'. Specify a parameter of type 'System.Object[]' and try again. At X:\Test.ps1:11 char:72 + [Object] $job = Start-Job -FilePath $scriptPath -RunAs32 -ArgumentList <<<< @PSBoundParameters; + CategoryInfo : InvalidArgument: (:) [Start-Job], ParameterBindingException + FullyQualifiedErrorId : MissingArgument,Microsoft.PowerShell.Commands.StartJobCommand
...so
@PSBoundParameters
seems to evaluate to$null
. I'm not sure why this isn't working or what else to try. -
Lance U. Matthews about 12 yearsThanks for the response. That fails if the script path has spaces in it, but I was able to fix it by passing
"& '$scriptpath' $(&{$args} @psboundparameters)"
to[scriptblock]::Create()
. After making that change, the script works when called with no parameters, but if I run.\Test.ps1 'string' 12345
, it fails atReceive-Job
withCannot process argument transformation on parameter 'int32Arg'. Cannot convert value "string" to type "System.Int32". Error: "Input string was not in a correct format."
Can you explain what this is doing?&{$args}
, in particular, is confusing me. -
mjolinor about 12 yearsThat will allow you to pass named parameters. Splatting a hash table produces a set of "-parameter value" strings from the key/value pairs of the hash table. But you can't evaluate a splat by itself. &{$args} provides a script block to accept the splat that will echo back the "-parameter value" strings and then they will be included in the script block that is passed to the job.
-
mjolinor about 12 yearsI cannot reproduce your results. On my system (W7 x64) it returns the date, and running get-job shows that the created job is still there, with a status of "Completed'.
-
Lance U. Matthews about 12 yearsI am also on 64-bit Windows 7. I tried your updated script and
.\Test.ps1
,.\Test.ps1 123
,.\Test.ps1 123 456
,.\Test.ps1 123 456 foo
, and.\Test.ps1 -int32Arg 123
all work..\Test.ps1 foo
,.\Test.ps1 foo 123
,.\Test.ps1 -stringArg foo
and anything else where the first two parameters cannot be converted to an integer fail with the errorCannot process argument transformation on parameter 'int32Arg'. Cannot convert value "foo" to type "System.Int32".
I don't understand why it's trying to match those tokens up with the second positional parameter. -
mjolinor about 12 yearsI do not understand that either. Running that script as: PS C:\scripts\test\test scripts> ./test.ps1 'string' 12345 or PS C:\scripts\test\test scripts> ./test.ps1 -stringarg 'string' -int32arg 12345 both return the date, as expected on my system. Note - that's from a ps console, not ISE.
-
Lance U. Matthews about 12 yearsI did have to edit your script because
@"
and"@
need to appear at the very beginning of the lines, but I can't imagine that'd introduce any problems. I write my scripts with PowerGUI, but have been testing this from a console session just because PowerGUI sometimes introduces some little differences. Starting the session withpowershell.exe -NoProfile
doesn't make any difference. I'm using PowerShell 2.0, not the 3.0 preview or anything. Hmmm. -
mjolinor about 12 yearsI am runnin the 3.0 preview. Let me do some more testing.
-
mjolinor about 12 yearsOK. There is a difference between the way &{$args} @hash evaluates between V2 and V3. In V3 the keys will be prepended with a '-' (which I would expect). I made a change to the code that does work, but I don't really like it because it's now broken in V3. I may need to go back and look at using an array instead.
-
mjolinor about 12 yearsOK, changed it to an array splat, and that seems to work in V2 and in V3. Sorry for leading you down that rabbit hole. Maybe somebody else will have a better solution.
-
Lance U. Matthews about 12 yearsThis is the way I originally thought of doing it and would work fine for a script that is purely writing to the console, files, etc. without yielding any data to the pipeline, but I realized that any data that is retrieved will be stuck in the 32-bit instance of the script with no (easy?) way to return it to the parent 64-bit instance. If you add
Get-Date
to the end of the script you'll see it in the pipeline when run directly from a 32-bit session, but from a 64-bit session it gets lost. I'd like the 64-bit handling to be transparent such that I can still pipe this script to cmdlets. -
Andy Arismendi about 12 yearsIf you have to mix the two then relaunching the whole process won't work so we'll and creating a background job would be better. If everything works as a 32 bit script you could just run all the code that way.
-
Lance U. Matthews about 12 yearsNo problem. I did notice one time that the command line that was being built didn't have any hyphens before the parameter names, so I guess that explains why it wasn't matching up the parameters to the correct positions. Your updated code works perfectly, though. I just needed to change the second line of the
foreach
to$params += GetValueText $psboundparameters.$_
(using the function defined in my question) so it works properly with my array parameters. Thanks for your help and for sticking with this.