Issue with path not being treated as encapsulated when calling cmd /C

71 views Asked by At

I'm having an issue where it seems my encapsulated path is not being treated as encapsulted. I have tried a few different things, to no avail. I'm wondering if anyone has seen this before?

Function Remove-App-EXE-SILENT-QUOTES([String]$appName)
{
    $appCheck = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object {$_.DisplayName -eq $appName } | Select-Object -Property DisplayName,UninstallString
    if($appCheck -ne $null){
        Write-host "Uninstalling "$appCheck.DisplayName
        $uninst="`"" + ($appCheck.UninstallString) + "`""
        Write-Host $uninst
        cmd /C $uninst
    }
    else{
        Write-Host "$appName is not installed on this computer"
    }
}
Remove-App-EXE-SILENT-QUOTES "DELLOSD"

output is

PS C:\Program Files> C:\Users\HOL-OP05\Documents\RemoveDellOSD.ps1
Uninstalling  DELLOSD
"C:\Program Files (x86)\InstallShield Installation Information\{50033799-D5D3-4759-9ABD-589AD04F33C1}\setup.exe -runfromtemp -l0x0009 -removeonly"
cmd : 'C:\Program' is not recognized as an internal or external command,
At C:\Users\HOL-OP05\Documents\RemoveDellOSD.ps1:8 char:9
+         cmd /C $uninst
+         ~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: ('C:\Program' is...ternal command,:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
operable program or batch file.
1

There are 1 answers

0
mklement0 On
  • Your attempt to enclose $appCheck.UninstallString in embedded "..." is unnecessary (and happens to make no difference in this case)

  • PowerShell, when it constructs the process command line ultimately used behind the scenes, will automatically enclose the value of $appCheck.UninstallString in "...", if that value contains spaces[1], such as in the case at hand.

  • Therefore, it should normally be enough to invoke the uninstallation command string as follows:

     # Works, but only with well-formed uninstallation command strings.
     cmd /C $appCheck.UninstallString
    
  • As Compo points out, your uninstallation command string is malformed: For it to work as intended when interpreted by cmd.exe, the path of the executable inside the command must be enclosed in embedded "...", because it contains spaces; that is, the verbatim content of $appCheck.UninstallString would have to be (note how only the setup.exe file path is enclosed in "..."):

    "C:\Program Files (x86)\InstallShield Installation Information\{50033799-D5D3-4759-9ABD-589AD04F33C1}\setup.exe" -runfromtemp -l0x0009 -removeonly
    
  • In other words: The root cause is that whatever installation application created the UninstallString registry value in key HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall on installation did so in a broken manner - see below for a potential workaround.


An automated workaround workaround for such broken UninstallString strings is possible, assuming that the executable path in the string explicitly uses the .exe filename extension:

# Add missing double quotes around an embedded executable path with spaces.
$uninst = $appCheck.UninstallString -replace '^([^"].+? .+?\.exe)(?!$)', '"$1"'
# ...
cmd /C $uninst
  • The above adds missing double quotes around an embedded executable path with spaces, and leaves already well-formed command-line strings alone.

  • This is achieved via the regex-based -replace operator.

Here is self-contained sample code:

# Sample uninstall command-line strings.
$uninstallCommandLines = @(
  # !! BROKEN
  'C:\Program Files (x86)\InstallShield Installation Information\{50033799-D5D3-4759-9ABD-589AD04F33C1}\setup.exe -runfromtemp -l0x0009 -removeonly'
  # OK, because there is only an executable path, no arguments.
  'C:\Program Files (x86)\Some Directory\Foo.exe'
  # OK, because the executable path contains no spalces.
  'C:\SomePath\WithoutSpaces\Bar.exe -baz'
  # OK, because the executable path with spaces is already double-quoted.
  '"C:\Some Path\With Spaces\Qux.exe" -quux'
)

# Process each and fix on demand.
$uninstallCommandLines | ForEach-Object {
  $original = $_
  # Fix it, if necessary; the result can be used with cmd /C
  $fixed = $original -replace '^([^"].+? .+?\.exe)(?!$)', '"$1"'
  [pscustomobject] @{
    Original = $original
    PotentiallyFixed = $fixed
    ActuallyFixed = $original -ne $fixed
  }
} | Format-List

Output:

Original         : C:\Program Files (x86)\InstallShield Installation Information\{50033799-D5D3-4759-9ABD-589AD04F33C1}\setup.exe -runfromtemp -l0x0009 -removeonly
PotentiallyFixed : "C:\Program Files (x86)\InstallShield Installation Information\{50033799-D5D3-4759-9ABD-589AD04F33C1}\setup.exe" -runfromtemp -l0x0009 -removeonly
ActuallyFixed    : True

Original         : C:\Program Files (x86)\Some Directory\Foo.exe
PotentiallyFixed : C:\Program Files (x86)\Some Directory\Foo.exe
ActuallyFixed    : False

Original         : C:\SomePath\WithoutSpaces\Bar.exe -baz
PotentiallyFixed : C:\SomePath\WithoutSpaces\Bar.exe -baz
ActuallyFixed    : False

Original         : "C:\Some Path\With Spaces\Qux.exe" -quux
PotentiallyFixed : "C:\Some Path\With Spaces\Qux.exe" -quux
ActuallyFixed    : False

[1] It actually neglects to escape " characters that are embedded in such a value, but that is where broken worlds meet: cmd /c actually requires there to be no escaping. When targeting other console applications, however, PowerShell's broken behavior continues to cause problems in Windows PowerShell and caused problems in PowerShell (Core) 7 up to v7.2.x - see this answer for background information.