14

I'm trying to set a public property in an InstallShield installer with a value containing space. While running the MSI installer, I'm using below command on PowerShell prompt. Since the value contains a space so I used double quotes to pass the value

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

It breaks the command as the argument value C:\new folder\data.txt has a space in the string new folder. It results in showing up below error prompt of msiexec:

enter image description here

It suggests that arguments passed to the msiexec command has some problem.

But if I execute the same command on Windows default command prompt then it runs fine:

enter image description here

Few other options that I've tried to make things work on PowerShell prompt are as below:

  1. Using single quote in place of double quotes
  2. Using a back tick (`) character before space in the argument as per this answer.
RBT
  • 24,161
  • 21
  • 159
  • 240

3 Answers3

13

Try with this

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

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

Marko Tica
  • 391
  • 4
  • 11
  • 2
    Horrible! Not in my dreams I would have been able to imagine that. Thanks a ton for helping. I got some more details [here](https://serverfault.com/questions/47811/what-is-the-literal-escape-character-in-powershell) for reference. – RBT Jun 12 '17 at 12:25
8

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Cool! `msiexec --% -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"` also works. In fact this is a more cleaner solution than trying that magical back tick character as escape sequence. – RBT Jun 12 '17 at 14:38
  • 1
    @RBT: Indeed: because in your case you don't need PowerShell to interpret the command line at all, `--%` is simplest. – mklement0 Jun 12 '17 at 14:39
  • 1
    True. In fact, in reality I've got several command line arguments for `msiexec` which can have spaces in them.I showed only one command line argument just to ensure simplicity in my post. When there are several command-line arguments with a possibility of space in them, applying back tick escape sequence would become a nightmare. – RBT Jun 12 '17 at 14:51
  • 1
    If you are stuck with PowerShell 2.0 on older systems, you can use a command-line tool such as `showargs.exe` (see [Windows IT Pro - Running Executables in PowerShell](http://windowsitpro.com/powershell/running-executables-powershell)) to get a look at the _actual_ command line that PowerShell passes to an executable. – Bill_Stewart Jun 12 '17 at 15:32
  • 2
    This answer solved my problem too. I was executing msiexec from Powershell with a path as a msi property. --% solved my problem. Thanks. – Divyarajsinh Jadeja Dec 01 '17 at 07:35
  • Wow I never knew that. I wish one place had every single detail of powershell lol. `echo hi | find --% "hi"` – js2010 May 09 '19 at 21:08
  • @js2010 obviously there's a single place for that: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.1 – phuclv May 13 '21 at 04:16
  • @phuclv, the conceptual help topic you link to only explains PowerShell literals _in the context of native PowerShell commands_, so it doesn't apply to the issue at hand, which is about how PowerShell _invisibly rebuilds the command line behind the scenes when calling external programs_ on Windows (of necessity, to only use `"..."` quoting). Please see my update, which provides more background (including the link to said topic, among others), a `cmd /c` alternative, and a link to the `Native` module whose `ie` function shields users from all behind-the-scenes re-quoting problems. – mklement0 May 13 '21 at 13:47
1

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.

Ragavan Rajan
  • 4,171
  • 1
  • 25
  • 43
Kyle74
  • 31
  • 6
  • Adding a link to the [Windows Installer PowerShell Module](https://stackoverflow.com/a/53436779/129130) by [Heath Stewart](https://blogs.msdn.microsoft.com/heaths/). – Stein Åsmul Jul 24 '19 at 20:47