Nerd in Training
Nerd in Training

Reputation: 2220

Run Executable from Powershell script with parameters

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

Answers (5)

mklement0
mklement0

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:

      • directly in the caller's console
      • synchronously (i.e., PowerShell waits until the application has exited)
      • with the application's standard streams (stdin, stdout, stderr) connected to PowerShell's output streams, which allows providing input via the pipeline, and capturing and redirecting output.
    • 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

Sergio Di Fiore
Sergio Di Fiore

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

Nerd in Training
Nerd in Training

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

Knuckle-Dragger
Knuckle-Dragger

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

Keith Hill
Keith Hill

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

Related Questions