Reputation: 2220
So I have a powershell script that is supposed to run an executable with an argument to pass to set which method I want to run, and I need to pass a parameter, which is a directory to a config file. So this is what I have
Start-Process -FilePath "C:\Program Files\MSBuild\test.exe" -ArgumentList /genmsi/f $MySourceDirectory\src\Deployment\Installations.xml
/f is the shortname and file is the long name for my attribute... I get an error in powershell telling me that a positional parameter cannot be found for /f or /file.
Any thoughts?
Upvotes: 53
Views: 250518
Reputation: 437062
Many years later, let me attempt a systematic overview.
External programs are best invoked directly in PowerShell, as in any other shell, i.e. by their name or path, followed by a space-separated list of arguments:
Purely for syntactic reasons, PowerShell situationally requires the command line to start with &
, its call operator, namely whenever the program name or path is quoted and/or contains variable references:
# & is needed, because the program path is enclosed in "..."
# It's safe to always use &
# Pass the arguments directly, separated with spaces.
& "C:\Program Files\MSBuild\test.exe" /genmsi /f $MySourceDirectory\src\Deployment\Installations.xml
Note that it's easy to construct arguments programmatically in PowerShell: store them individually in an array, and pass that array as an argument, which makes PowerShell pass each element as a separate argument:[1]
$passThruArgs = @(
'/genmsi',
'/f',
"$MySourceDirectory\src\Deployment\Installations.xml"
)
& "C:\Program Files\MSBuild\test.exe" $passThruArgs
With direct invocation, console applications are then executed:
Additionally, PowerShell automatically records the application's process exit code in the automatic $LASTEXITCODE
variable
While GUI applications (e.g., Notepad.exe
) are invoked using the same syntax, they are executed:
asynchronously (PowerShell does not wait for them to exit)
therefore without populating $LASTEXITCODE
almost always without stream integration - unless a given GUI application goes out of its way to connect to the caller's console.
Note: Not many GUI applications report meaningful process exit codes (a notable exception is msiexec.exe
), and even fewer connect to the caller's console, but there's a simple trick that makes calls to GUI applications synchronous, potentially enabling stream integration, and causes the process exit code to be recorded in $LASTEXITCODE
: pipe to Write-Output
; e.g.:
# Piping to Write-Output makes the call synchronous and records
# the process exit code in `$LASTEXITCODE`
Notepad foo.txt | Write-Output
Use Start-Process
only in special circumstances:
Among the legitimate reasons to use it are wanting to run an application asynchronously, in a new window, with elevation (as administrator) or as a different user.
If you use it to invoke a console application as the current user, you forgo all the benefits of direct invocation: synchronous execution in the caller's console, stream integration, recording of the exit code in $LASTEXITCODE
.
See this answer and GitHub docs issue #6239 for detailed guidance on when its use is and isn't appropriate.
Avoid use of Invoke-Expression
:
It should generally be avoided, because it can be a security risk and because better solutions are usually available: see this answer.
Except in rare circumstances, don't use it to invoke an external program or PowerShell script / command - direct invocation is the superior alternative; see this answer.
As for what you tried:
It is implied by Keith Hill's helpful answer, but let me spell it out:
You had a syntax problem: the -ArgumentList
parameter of Start-Process
accepts an array of individual arguments, which must therefore be separated with ,
By contrast, because you separated /genmsi/f
from $MySourceDirectory\src\Deployment\Installations.xml
with a space, you passed an extra, unnamed argument, which Start-Process
didn't recognize.
While -ArgumentList /genmsi, /f, $MySourceDirectory\src\Deployment\Installations.xml
therefore would work - assuming that the value of $MySourceDirectory
doesn't include spaces, it is indeed better to encode all arguments in a single string passed to -ArgumentList
, as shown in Keith's answer:
Doing so makes it obvious when embedded quoting is required around individual arguments, which PowerShell unfortunately does not do for you when passing arguments as individual array elements, due to a long-standing bug that won't be fixed so as not to jeopardize backward compatibility - see GitHub issue #5576.
With embedded quoting for robustness, it should therefore be:
-ArgumentList "/genmsi /f `"$MySourceDirectory\src\Deployment\Installations.xml`""
[1] This technique is called splatting, and while splatting when used with PowerShell commands requires use of @
rather than $
to reference the variable containing the arguments, this isn't strictly necessary when calling external programs.
Upvotes: 1
Reputation: 81
Just adding an example that worked fine for me:
$sqldb = [string]($sqldir) + '\bin\MySQLInstanceConfig.exe'
$myarg = '-i ConnectionUsage=DSS Port=3311 ServiceName=MySQL RootPassword= ' + $rootpw
Start-Process $sqldb -ArgumentList $myarg
Upvotes: 8
Reputation: 2220
I was able to get this to work by using the Invoke-Expression
cmdlet.
Invoke-Expression "& `"$scriptPath`" test -r $number -b $testNumber -f $FileVersion -a $ApplicationID"
Upvotes: 34
Reputation: 7046
Here is an alternative method for doing multiple args. I use it when the arguments are too long for a one liner.
$app = 'C:\Program Files\MSBuild\test.exe'
$arg1 = '/genmsi'
$arg2 = '/f'
$arg3 = '$MySourceDirectory\src\Deployment\Installations.xml'
& $app $arg1 $arg2 $arg3
Upvotes: 30
Reputation: 201592
Try quoting the argument list:
Start-Process -FilePath "C:\Program Files\MSBuild\test.exe" -ArgumentList "/genmsi/f $MySourceDirectory\src\Deployment\Installations.xml"
You can also provide the argument list as an array (comma separated args) but using a string is usually easier.
Upvotes: 65