Given the following UninstallString value in the registry:
MsiExec.exe /X{guid}
Get-AppInstallLocation should not return "MsiExec.exe ", but ignore this value (as it does not contain path information) and proceed to check subsequent locations (Program Files, App Paths etc.).
Get-AppInstallLocation returns "MsiExec.exe ", which is wrong on many levels:
Enhance registry value parsing code so that it
cinst -y paint.net
The paint.net package passes the bogus value obtained from Get-AppInstallLocation to Register-Application, which throws an error and the entire package installation fails.
Also reported on Disqus: https://chocolatey.org/packages/paint.net#comment-3558201902
Hm.... works on my machine, and the value IS tested:
> import-module C:\ProgramData\chocolatey\helpers\chocolateyInstaller.psm1
> Get-AppInstallLocation 'Paint.NET*' -Verbose
VERBOSE: Trying local and machine (x32 & x64) Uninstall keys
VERBOSE: Retrieving all uninstall registry keys
VERBOSE: Trying Uninstall key property 'InstallLocation'
VERBOSE: Trying Uninstall key property 'UninstallString'
VERBOSE: Trying Uninstall key property 'DisplayIcon'
VERBOSE: Trying Program Files with 2 levels depth: C:\Program Files C:\Program Files\*\* C:\Program Files (x86) C:\Program Files (x86)\*\*
C:\Program Files\paint.net
Try cd'ing to $Env:SystemRoot\System32:
PS C:\> Import-Module C:\ProgramData\chocolatey\helpers\chocolateyInstaller.psm1
PS C:\> Get-AppInstallLocation 'Paint.NET*' -Verbose -OutVariable x | % { "[$_]" }
VERBOSE: Trying local and machine (x32 & x64) Uninstall keys
VERBOSE: Retrieving all uninstall registry keys
VERBOSE: Trying Uninstall key property 'InstallLocation'
VERBOSE: Trying Uninstall key property 'UninstallString'
VERBOSE: Trying Uninstall key property 'DisplayIcon'
VERBOSE: Trying Program Files with 2 levels depth: C:\Program Files C:\Program Files\*\* C:\Program Files (x86)
C:\Program Files (x86)\*\*
[C:\Program Files\paint.net]
PS C:\> Test-Path $x
True
PS C:\> cd $Env:SystemRoot\System32
PS C:\WINDOWS\System32> Get-AppInstallLocation 'Paint.NET*' -Verbose -OutVariable x | % { "[$_]" }
VERBOSE: Trying local and machine (x32 & x64) Uninstall keys
VERBOSE: Retrieving all uninstall registry keys
VERBOSE: Trying Uninstall key property 'InstallLocation'
VERBOSE: Trying Uninstall key property 'UninstallString'
[MsiExec.exe ]
PS C:\WINDOWS\System32> Test-Path $x
True
Relative paths can be detected and rejected by calling e.g. [IO.Path]::IsPathRooted($path).
Apart from that, a simple Test-Path is not sufficient, because it does not check whether the object is a directory. It would require something like:
function Test-Directory($path)
{
$i = Get-Item -Path $path -ErrorAction SilentlyContinue
return $i -ne $null -and $i.PSIsContainer
}
There is also the matter of UninstallStrings with arguments containing path separators, which the current simplistic parsing method will not handle correctly (the trailing space is a symptom of it):
PS C:\> $location = 'MsiExec.exe /X{F10AAD91-58DF-44EC-A647-810197141667}'
PS C:\> $location.Replace('"', '') | Split-Path | % { "[$_]" }
[MsiExec.exe ]
PS C:\> $location = '"C:\Program Files (x86)\Steam\steam.exe" steam://uninstall/7760'
PS C:\> $location.Replace('"', '') | Split-Path | % { "[$_]" }
[C:\Program Files (x86)\Steam\steam.exe steam:\\uninstall]
PS C:\> $location = 'C:\Windows\IsUninst.exe -fE:\Gry\XComCE\Uninst.isu'
PS C:\> $location.Replace('"', '') | Split-Path | % { "[$_]" }
[C:\Windows\IsUninst.exe -fE:\Gry\XComCE]
https://github.com/chocolatey/chocolatey-coreteampackages/commit/555925cb5f0d3ea3ded52b9b22fb8779feae105f is enough to fix this problem with Paint.Net.
There is also the matter of UninstallStrings with arguments containing path separators, which the current simplistic parsing method will not handle correctly (the trailing space is a symptom of it).
This was made only to handle quoted single path.
I made this parsing function instead, that should return unquoted 'first argument'. It utilizes powershell parsing of arguments with some special posh chars replaced prior to iex and returned later:
if (!(is_dir $location)) { $location = parse $location; if (is_dir $location) { return $location }}
function parse($s) {
$escape = "``()@;'&|><{}[]"
[char[]]$escape | % {$i=0} { $i++; $s = $s.Replace($_, [char]$i ) }
try { $s = iex ". { `$args[0] } $s" } catch { return $s }
[char[]]$s | % {$r=''} { $a=[byte]$_; $r += if ( $a -le $escape.Length) { $escape[$a-1] } else {$_} }
$r
}
If nobody has objections I can make it into module.
@majkinetor if it's compatible with POSHv2, then there is no objection from me
I am hesitant to introduce any change to the extension since it can affect big number of packages.. The new parsing method even if better can change behavior of existing packages so all would need to be tested for it.
Since this is not actually an improvement to any existing package and I don't have time to be purist, I will just leave it be for now. If anybody else wants to give it a go feel free to do so.