I am trying to execute the following command through powershell, in a script invoked by an Advanced Installer generated installation program. The problem is that when the script executes, it chokes on a call to MSIEXEC.exe. More specifically, it puts up a windows dialog of the msiexec help screen.
Ok so maybe it doesn't like the way advanced installer is executing it. So I take the actual line that is causing problems:
msiexec.exe /q /i 'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\' ADDLOCAL='all'
And when I execute this directly in powershell, I still get the same stupid help screen. I have tried every conceivable variation of this command line:
In all cases, I get the damnable "help" dialog. The only thing that appears to make a difference is if I leave off the INSTALLLOCATION and ADDLOCAL options. (These are apparently used as per "Unattended Installation part 2" found here: https://docs.mongodb.com/tutorials/install-mongodb-on-windows/). In that case it just exits quietly without installing anything.
I'm honestly at my wits' end having been beating my head against the wall on this all afternoon.
By the way, the reason I'm installing mongo in such an absurd way is I need a method of having a single-install system for my company's product. It depends on Mongo, and we have to have it run as a server and use authentication, so I have to have scripts to create the admin and database user and put it into authenticated mode. Since I needed to know where mongo was installed (to execute mongod.exe and mongo.exe) I need to query the user first for the location, then pass on the install location to the mongo installer. If I'm completely off the rails here please let me know that there's a better way. Thanks
EDITED: I forgot to mention I wrote my complete powershell script and tested it before trying to execute it through advanced installer. The script worked until I tried to run it through the installer. Strange that I still can't execute the command though manually now.
Update:
The next section still applies to direct msiexec
invocations from PowerShell.
A simpler solution is to call msiexec
via cmd.exe /c
, as it gives you more direct control over the resulting process command line and its quoting, and the invocation is synchronous (blocking) by default and even reports the exit code via the automatic $LASTEXITCODE
variable:
# Executes synchronously and reports the exit code via $LASTEXITCODE
cmd /c 'msiexec.exe /q /i "C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi" INSTALLLOCATION="C:\Program Files\MongoDB\Server\3.4\" ADDLOCAL="all"'
Inside the overall '...'
(verbatim) string passed to cmd /c
, you must only use "..."
quoting, which cmd.exe
- unlike PowerShell - will pass through as-is to msiexec
.
To embed PowerShell variable values, use an overall "..."
(expandable) string and escape the embedded "
as `"
(or ""
).
This approach notably also works when you need to pass property values with spaces which require partial quoting, e.g. FOO="bar baz"
(FOO=`"bar baz`"
inside "..."
quoting); if you used direct invocation, PowerShell would reformat this to "FOO=bar baz"
behind the scenes, which msiexec.exe
doesn't recognize.
To make the call asynchronous (return control to PowerShell right after launching msiexec
, before it completes, at the expense of not learning its exit code), use cmd /c 'start "" msiexec ...'
.
It seems that in order to pass paths with embedded spaces to msiexec
, you must use explicit embedded "..."
quoting around them.
In your case, this means that instead of passing
INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\'
, you must pass INSTALLLOCATION='"C:\Program Files\MongoDB\Server\3.4\\"'
[1]
Note the embedded "..."
and the extra \
at the end of the path to ensure that \"
alone isn't mistaken for an escaped "
by msiexec
(though it may work without the extra \
too).
To put it all together:
# See v7.3+ caveat below.
msiexec.exe /q /i `
'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' `
INSTALLLOCATION='"C:\Program Files\MongoDB\Server\3.4\\"' ADDLOCAL='all'
Note:
... | Write-Output
, which also causes msiexec
's exit code to be reflected in $LASTEXITCODE
.Caveat:
This embedded-quoting technique relies on longstanding, but broken PowerShell behavior - see this answer; this behavior was fixed in PowerShell (Core) v7.3 (with selective exceptions on Windows), so in v7.3+, you must first set $PSNativeCommandArgumentPassing
= 'Legacy'
; by contrast, the
--%
approach shown below continues to work as-is.
A workaround-free, future-proof method is to use the PSv3+ ie
helper function from the Native
module (in PSv5+, install with Install-Module Native
from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; that is, simply prepending ie
to your original command would be enough:
# No workarounds needed with the 'ie' function from the 'Native' module.
ie msiexec.exe /q /i 'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\' ADDLOCAL='all'
The alternative is to stick with the original quoting and use --%
, the stop-parsing symbol, but note that this means that you cannot use PowerShell variables in all subsequent arguments (however, you could define environment variables - e.g. $env:foo = ...
- and then reference them with cmd.exe syntax - e.g.
%foo%`):
msiexec.exe /q /i `
'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' `
--% INSTALLLOCATION="C:\Program Files\MongoDB\Server\3.4\\" ADDLOCAL='all'
Note that msiexec
, despite having a CLI (command-line interface), is a GUI-subsystem application, so it runs asynchronously by default; if you want to run it synchronously, preferably use the ... | Write-Output
trick mentioned above; alternatively, use
Start-Process -Wait
:
$msiArgs = '/q /i "C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi" INSTALLLOCATION="C:\Program Files\MongoDB\Server\3.4\\" ADDLOCAL=all'
$ps = Start-Process -PassThru -Wait msiexec -ArgumentList $msiArgs
# $ps.ExitCode contains msiexec's exit code.
Note that the argument-list string, $msiArgs
, is used as-is by Start-Process
as part of the command line used to invoke the target program (msiexec
), which means:
only (embedded) double-quoting must be used.
"..."
with embedded "
escaped as `"
to embed PowerShell variables and expressions in the string.conversely, however, no workaround for partially quoted arguments is needed.
Even though Start-Process
technically supports passing the arguments individually, as an array, this is best avoided due to a longstanding bug - see GitHub issue #5576.
[1] The reason that INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\'
doesn't work is that PowerShell transforms the argument by "..."
-quoting it as a whole, which msiexec
doesn't recognize; specifically, what is passed to msiexec
in this case is:
"INSTALLLOCATION=C:\Program Files\MongoDB\Server\3.4\"