Command line arguments for msiexec break on powershell if they contain space

Try with this

msiexec -i "myinstaller.msi" MYDIRPATH=`"C:\new folder\data.txt`"

The escape character in PowerShell is the grave-accent(`).


Note:

  • This answer addresses direct, but asynchronous invocation of msiexec from PowerShell, as in the question. If you want synchronous invocation, use Start-Process with the -Wait switch, as shown in Kyle 74's helpful answer, which also avoids the quoting problems by passing the arguments as a single string with embedded quoting.

  • Additionally, if you add the -PassThru switch, you can obtain a process-information object that allows you to query msiexec's exit code later:

# Invoke msiexec and wait for its completion, then
# return a process-info object that contains msiexec's exit code.
$process = Start-Process -Wait -PassThru msiexec '-i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"'
$process.ExitCode
  • Note: There's a simple trick that can make even direct invocation of msiexec synchronous: pipe the call to a cmdlet, such as Wait-Process
    (msiexec ... | Wait-Process) - see this answer for more information.

To complement Marko Tica's helpful answer:

Calling external programs in PowerShell is notoriously difficult, because PowerShell, after having done its own parsing first, of necessity rebuilds the command line that is actually invoked behind the scenes in terms of quoting, and it's far from obvious what rules are applied.

  • Note:
    • While the re-quoting PowerShell performs behind the scenes in this particular case is defensible (see bottom section), it isn't what msiexec.exe requires.
    • Up to at least PowerShell 7.1, some of the re-quoting is downright broken, and the problems, along with a potential upcoming (partial) fix, are summarized in this answer.
    • Marko Tica's workaround relies on this broken behavior, and with the for now experimental feature that attempts to fix the broken behavior (PSNativeCommandArgumentPassing, available since Core 7.2.0-preview.5), the workaround would break. Sadly, it looks like then simply omitting the workaround won't work either, because it was decided not to include accommodations for the special quoting requirements of high-profile CLIs such as msiexec - see GitHub issue #15143.

To help with this problem, PSv3+ offers --%, the stop-parsing symbol, which is the perfect fit here, given that the command line contains no references to PowerShell variables or expressions: --% passes the rest of the command line as-is to the external utility, save for potential expansion of %...%-style environment variables:

# Everything after --% is passed as-is.
msiexec --% -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"

If you do need to include the values of PowerShell variables or expressions in your msiexec call, the safest option is to call via cmd /c with a single argument containing the entire command line; for quoting convenience, the following example uses an expandable here-string (see the bottom section of this answer for an overview of PowerShell's string literals).

$myPath = 'C:\new folder\data.txt'

# Let cmd.exe invoke msiexec, with the quoting as specified.
cmd /c @"
msiexec --% -i "myinstaller.msi" MYDIRPATH="$myPath"
"@

If you don't mind installing a third-party module, the ie function from the Native module (Install-Module Native) obviates the need for any workarounds: it fixes problems with arguments that have embedded " chars. as well as empty-string arguments and contains important accommodations for high-profile CLIs such as msiexec on Windows, and will continue to work as expected even with the PSNativeCommandArgumentPassing feature in effect:

# `ie` takes care of all necessary behind-the-scenes re-quoting.
ie msiexec -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"

As for what you tried:

PowerShell translated
MYDIRPATH="C:\new folder\data.txt" into
"MYDIRPATH=C:\new folder\data.txt" behind the scenes - note how the entire token is now enclosed in "...".

Arguably, these two forms should be considered equivalent by msiexec, but all bets are off in the anarchic world of Windows command-line argument parsing.


This is the best way to install a program in general with Powershell.

Here's an example from my own script:

start-process "c:\temp\SQLClient\sqlncli (x64).msi" -argumentlist "/qn IACCEPTSQLNCLILICENSETERMS=YES" -wait

Use Start-Process "Path\to\file\file.msi or .exe" -argumentlist (Parameters) "-qn or whatever" -wait.

Now -wait is important, if you have a script with a slew of programs being installed, the wait command, like piping to Out-Null, will force Powershell to wait until the program finishes installing before continuing forward.