The argument is null or empty: Start-Process error when invoking msiexec.exe for uninstallation

130 views Asked by At
Get-Process | ? {$_.ProcessName -eq "DellCommandUpdate"} | Stop-Process -Force

$Params = @(
    "/qn"
    "/norestart"
)

Start-Process "msiexec.exe" -ArgumentList $Params -Wait

I'm trying to uninstall Dell Command Update from a few laptops with older versions. I'm getting this error, below?

screenshot

2

There are 2 answers

2
Tanaka Saito On BEST ANSWER

Msiexec.exe requires the path to the program you want to install or uninstall.
You also need the /x flag to specify that it is an uninstall, otherwise you're going to run it as an install. Try this:

$MSIExec = "msiexec.exe"
$Params = @(
    "/qn",
    "/norestart",
    "/x",
    "<Path to package.msi>"
)
& "$MSIExec" $Params

I'm inclined to consider this a duplicate question to the more generic "How do I run executable files with arguments in Powershell" question which has a similar answere here: https://stackoverflow.com/a/22247408/1618897

But, considering this is a new account, and you clearly showed what you've done, I think this is still valid.

EDIT: This answer runs asynchronously, see mklement0's answer for a 1:1 solution for a synchronous solution (which matches this question directly).

0
mklement0 On
  • Your screenshot is not consistent with your code, because it implies that you passed (a variable containing) either $null, or an empty array (@()) to the -ArgumentList parameter of Start-Process

  • Your code as shown would technically work - in the sense that Start-Process would succeed in launching msiexec.exe, but - as Tanaka Saito's answer notes - the arguments passed to msiexec.exe's do not form a complete command, due to the absence of an /x (uninstallation) argument specifying the target .msi file.

  • Adding this missing information to $Parameters would make your Start-Process call work:[1]

    $Params = @(
      '/qn'
      '/norestart'
      '/x'
      'someInstaller.msi' # specify the .msi file here
    )
    
  • However, there is a better way to invoke msiexec.exe from PowerShell, namely in a way that:

    • is syntactically simpler, due to using direct invocation.

    • automatically reflects the installer's exit code in the automatic $LASTEXITCODE variable (with Start-Process, you'd have to add -PassThru in order to receive and capture a process-information object whose .ExitCode property you'd have to query).

       # Executes synchronously due to the `| Write-Output` trick,
       # reflects the exit code in $LASTEXITCODE.
       msiexec /qn /norestart /x someInstaller.msi | Write-Output
      
    • Note:

      • Piping to Write-Output is a simple way to ensure synchronous execution of GUI-subsystem applications such as msiexec.exe: involving the pipeline this way makes PowerShell wait for the application's termination and also reports its exit code.

      • There are cases where passing property values to msiexec.exe from PowerShell causes problems, namely when a value contains spaces and therefore requires partial quoting (e.g. FOO="bar baz"). In that case, you can use the following alternative, via cmd.exe /c:

         # Executes synchronously,
         # reflects the exit code in $LASTEXITCODE.
         # '...' encloses the entire msiexec command line
         # Use "..." if you need string interpolation, and `" for embedded "
         cmd /c 'msiexec /qn /norestart /x someInstaller.msi'
        
      • Finally, note that Start-Process -Wait - unlike direct invocation / invocation via cmd.exe /c - technically waits for the entire child process tree to terminate, which can cause apparent hangs if an installer happens to asynchronously launch a child process that is designed to outlive the installer process (msiexec.exe) itself - see this post for an example.


[1] However, due to a long-standing bug, it is ultimately better to encode all arguments in a single string, because it makes the situational need for embedded double-quoting explicit - see this answer.