Skip to content

Instantly share code, notes, and snippets.

@mbrownnycnyc
Last active March 30, 2020 16:50
Show Gist options
  • Save mbrownnycnyc/6511499 to your computer and use it in GitHub Desktop.
Save mbrownnycnyc/6511499 to your computer and use it in GitHub Desktop.
Still needs to be polished to do less work (proc arch conditional): for use with chocolatey packaging and to be included as a help within a warmUp template for use with chocolateyUnininstall.ps1... GetValueFromRegistryThruWMI modified version of http://gallery.technet.microsoft.com/scriptcenter/6062bbfc-53bf-4f92-994d-08f18c8324c0
$global:debug = $false
$global:debugverbose = $false
Function GetUninstallString([string]$computername, $displayname)
{
try {
#first, we'll grab a collection of key names below HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 64 bit
#we'll search for the DisplayName...
#if found, we'll query for the UninstallString and we'll stop looking
write-host "Getting UninstallString. This may take a moment..." -backgroundcolor "DarkYellow"
write-host "Enumerating 64-bit HKLM keys (or 32-bit keys if the host process is 32-bit)..." -backgroundcolor "DarkYellow"
$keynames = GetValueFromRegistryThruWMI "enumkey" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ""
foreach ( $key in $keynames) {
$dname = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
if ( $dname -ne $false -and $dname.contains($displayname) ) {
$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
return $uninstallstring
}
}
#then, we'll grab a collection of key names below HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 32 bit
#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
write-host "Enumerating 32-bit HKLM keys..." -backgroundcolor "DarkYellow"
write-host "Don't you just wish you had installed that 64-bit version now? C'mon! Quantum computing is almost here!" -backgroundcolor "DarkYellow"
$keynames = GetValueFromRegistryThruWMI "enumkey" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ""
foreach ( $key in $keynames) {
$dname = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
if ( $dname -ne $false -and $dname.contains($displayname) ) {
$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
return $uninstallstring
}
}
#then, we'll grab a collection of key names below HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 64 bit
#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
write-host "Enumerating 64-bit HKCU keys (or 32-bit keys if the host process is 32-bit)" -backgroundcolor "DarkYellow"
write-host "What great times we're having, huh?" -backgroundcolor "DarkYellow"
$keynames = GetValueFromRegistryThruWMI "enumkey" "64" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ""
foreach ( $key in $keynames) {
$dname = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
if ( $dname -ne $false -and $dname.contains($displayname) ) {
$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "64" "localhost" "HKcU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
return $uninstallstring
}
}
#then, we'll grab a collection of key names below HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall for 32 bit
#we'll search for the DisplayName... if found, we'll query for the UninstallString and we'll stop looking
write-host "Enumerating 32-bit HKCU keys " -backgroundcolor "DarkYellow"
write-host "The pain is almost over." -backgroundcolor "DarkYellow"
$keynames = GetValueFromRegistryThruWMI "enumkey" "32" "localhost" "HKCU" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ""
foreach ( $key in $keynames) {
$dname = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "DisplayName"
if ( $dname -ne $false -and $dname.contains($displayname) ) {
$uninstallstring = GetValueFromRegistryThruWMI "GetStringValue" "32" "localhost" "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$key" "UninstallString"
write-host "Got it... $uninstallstring" -backgroundcolor "darkgreen"
write-host "Passing it to the chocolatey gods for sacrifice..." -backgroundcolor "darkgreen"
return $uninstallstring
}
}
throw "Couldn't find $displayname as a DIsplayName or couldn't find the DisplayName's UninstallString below Uninstall."
}
catch {
return $error[0]
}
}
Function GetValueFromRegistryThruWMI([string]$stdregprovmethod, $providerarchtoget, [string]$computername, $regtree, $regkey, $value)
{
try{
$stdregprovmethod = $stdregprovmethod.tostring().tolower()
#these are all the available methods for stdregprov: http://msdn.microsoft.com/en-us/library/aa393664%28v=vs.85%29.aspx
$supportedstdregprovmethods = 'enumkey','enumvalues','getstringvalue'
#$allstdregprovmethods = 'checkaccess','createkey','deletekey','deletevalue','enumkey','enumvalues','getbinaryvalue','getdwordvalue','getexpandedstringvalue','getmultistringvalue','getstringvalue','setbinaryvalue','setdwordvalue','setexpandedstringvalue','setmultistringvalue','setstringvalue','#these are not supported in windows version 5.x.x (<=2003/xp)','getqwordvalue','getsecuritydescriptor','setqwordvalue','setsecuritydescriptor'
if ( $supportedstdregprovmethods -notcontains $stdregprovmethod ) {
write-host You have provided a StrRegProv Method that is not supported yet.
write-host enumkey will be used instead.
$stdregprovmethod = 'enumkey'
}
else {
}
switch ($regtree)
{
#constant uints for registry trees
HKCR { $regtree = "&h80000000" }
HKCU { $regtree = "&h80000001" }
HKLM { $regtree = "&h80000002" }
HKU { $regtree = "&h80000003" }
#more obscure trees: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724836%28v=vs.85%29.aspx
HKPerformanceData { $regtree = "&h80000004" } # http://msdn.microsoft.com/en-us/library/windows/desktop/aa373219%28v=vs.85%29.aspx
HKCurrentConfig { $regtree = "&h80000005" }
HKCULocalSettings { $regtree = "&h80000007" }
default { $regtree = "&h80000002" }
}
$objNamedValueSet = New-Object -COM "WbemScripting.SWbemNamedValueSet"
if ( $providerarchtoget -eq 32 ) {
if ( [IntPtr]::size -eq 8 -and (test-path env:\PROCESSOR_ARCHITEW6432) ) {
if ($debugverbose) { write-host process arch is 64-bit, querying 32-bit provider as requested. }
$objNamedValueSet.Add("__ProviderArchitecture", 32) | Out-Null
}
elseif ( [IntPtr]::size -eq 4 ) {
if ($debugverbose) { write-host process arch is 32-bit, querying 32-bit provider. }
$objNamedValueSet.Add("__ProviderArchitecture", 32) | Out-Null
}
}
elseif ( $providerarchtoget -eq 64 ) {
if ( [IntPtr]::size -eq 8 -and (test-path env:\PROCESSOR_ARCHITEW6432) ) {
if ($debugverbose) { write-host process arch is 64-bit, querying 64-bit provider as requested. }
$objNamedValueSet.Add("__ProviderArchitecture", 64) | Out-Null
}
elseif ( [IntPtr]::size -eq 4 ) {
if ($debugverbose) { write-host process arch is 32-bit, querying 64-bit provider. }
$objNamedValueSet.Add("__ProviderArchitecture", 64) | Out-Null
}
}
else {
if ($debug) { write-host You have requested an architecture that is not handled. Please provide "32" or "64" as your first parameter. }
return
}
$objLocator = New-Object -COM "Wbemscripting.SWbemLocator"
$objServices = $objLocator.ConnectServer($computername,"root\default","","","","","",$objNamedValueSet)
$objStdRegProv = $objServices.Get("StdRegProv")
$Inparams = ($objStdRegProv.Methods_ | where {$_.name -eq $stdregprovmethod}).InParameters.SpawnInstance_()
if ($stdregprovmethod -eq "getstringvalue") {
if ($debugverbose) { write-host getting string value $regtree $regkey : $value }
($Inparams.Properties_ | where {$_.name -eq "Hdefkey"}).Value = $regtree
($Inparams.Properties_ | where {$_.name -eq "Ssubkeyname"}).Value = $regkey
($Inparams.Properties_ | where {$_.name -eq "Svaluename"}).Value = $value
$Outparams = $objStdRegProv.ExecMethod_("GetStringValue", $Inparams, "", $objNamedValueSet)
if (($Outparams.Properties_ | where {$_.name -eq "ReturnValue"}).Value -eq 0) {
$result = ($Outparams.Properties_ | where {$_.name -eq "sValue"}).Value
if ($debugverbose) { write-host '$result of GetStringValue: ' $result }
return $result
}
else{
if ($debugverbose) { write-host '$result of GetStringValue: ' $false }
return $false
}
}
elseif ($stdregprovmethod -eq "enumkey") {
if ($debug) { write-host enuming keys below $regtree : $regkey }
($Inparams.Properties_ | where {$_.name -eq "Hdefkey"}).Value = $regtree
($Inparams.Properties_ | where {$_.name -eq "Ssubkeyname"}).Value = $regkey
$Outparams = $objStdRegProv.ExecMethod_("EnumKey", $Inparams, "", $objNamedValueSet)
if (($Outparams.Properties_ | where {$_.name -eq "ReturnValue"}).Value -eq 0) {
$result = ($Outparams.Properties_ | where {$_.name -eq "sNames"}).Value
if ($debugverbose) { write-host '$result of EnumKey: ' $result }
return $result
}
else{
if ($debugverbose) { write-host '$result of EnumKey: ' $false }
return $false
}
}
}
catch {
$error[0]
}
}
$packageName = 'desktoprestore'
$fileType = 'MSI'
$silentArgs = '/x','/quiet', '/passive' #in this case, this must be an array instead of a string. We will expand during execution.
#not ready as of September 9th, 2013:
#Uninstall-ChocolateyPackage $packageName $fileType $silentArgs $filePath
$filePath = GetUninstallString localhost "Desktop Restore"
#We must parse the returned UninstallString and modify it, consider $fileType and $silentArgs
#let's take the example:
#$filePath = "MsiExec.exe /I{228CEA74-6DD1-40B9-B95F-77273F4316B5}"
#or this
#$filePath = "MsiExec.exe /xribbit11-.3adf.msi"
if ( $fileType -eq "MSI" ) {
#With MSIs, some UninstallString REG_SZs will contain the `/I{Product GUID}` which brings up the installer configuration screen for the installed product.
#There are three scenarios for an UninstallString that we are concerned with that will trigger an uninstall:
# `/x[Product GUID]`
# `/x[product.msi]`
# any arbitrary string that doesn't contain /i or /x (could be an exe)
#our primary step will be to isolate the Product GUID:
# (by the way, we get the product guid by querying the registry, but why would we perform more costly work again...
# We already queried the registry to deal with the condition that we aren't dealing with an MSI.)
# We will find the Product GUID by searching for the [0-9][a-z][a-Z] between '{' and '}' which is empirically stable.
if ( $filePath -match '{.*}' ) { $productGUID = $matches[0].tolower() }
#even if this $productGUID winds up being "", we can still search for other things:
#If the UninstallString contains '/x$productGUID'
# or if it contains
# '/x', followed by any character, followed by '.msi', followed by an end of line character, followed by whitespace, followed by any character.
# and
# any character, followed by any invalid character, followed by any character
# or if it doesn't contain '/x' or '/i'
#http://msdn.microsoft.com/en-us/library/aa365247.aspx
if ( $filePath.tolower() -match "/x$productGUID" -or (
$filePath.tolower() -match "/x.*.msi[$\s]*" -and
$filePath.tolower() -notmatch ".*[" + [regex]::Escape([System.IO.Path]::InvalidPathChars -join "") + "].*"
) -or (
-not $filePath.tolower() -match "/x" -or
-not $filePath.tolower() -match "/i"
)
) {
#then we can just run it as is. It contains the one of the uninstall commands ("/x$productGUID" or "/xproduct.msi") or it could be an EXE or something else [see pdfcreator for an example of this instance].
if ($debug) { write-host running "$filePath"}
(Start-Process -FilePath "$filePath" -Wait -Passthru).ExitCode
}
else {
#We'll gather up the switches, then we'll verify each as okay (only allow $silentArgs, except "/x" which requires more things)
# a switch is defined as any string that starts with a '/' that is followed by '[a-zA-Z]' then followed by parameters
# this excludes the /log switch as it can contain a parameter... maybe later...
# we can exclude the `/x` switch from this check as it was already handled in this conditional
$filePath -split "/" | foreach-object {
if ( $_ -ne "x") {
if ($silentArgs -match [regex]::Escape( $_.trim() ) ) {
[string[]]$matchedsilentargs += $_
}
}
}
foreach ($silentArg in $silentArgs) {
if ( $silentArg -ne "/x") {
[string[]]$matchedsilentargs += $silentArg
}
}
#Now, let's add /x$productGUID to $silentArgs. It wasn't already there, or the original condition (above) would have covered it.
#we might still only be dealing with "/x" $silentArgs
$silentArgs = "/x$productGUID " + ($matchedsilentargs)
#now we can execute it with the $silentArgs
if ($debug) { write-host running "msiexec.exe " $silentArgs }
(Start-Process -FilePath "msiexec.exe" -ArgumentList "$silentArgs" -Wait -Passthru).ExitCode
}
}
else {
#file type isn't MSI
#We'll gather up the switches, then we'll verify each as okay (only allow $silentArgs)
# a switch is defined as any string that starts with a '/' that is followed by '[a-zA-Z]' then followed by parameters
# this excludes any switch that takes in a parameter... maybe later...
$filePath -split "/" | foreach-object {
if ( $silentArgs -match [regex]::Escape( $_.trim() ) ) {
if ($debug) { write-host "adding to the array " $_ }
[string[]]$matchedsilentargs += $_
}
}
$silentArgs = ([string[]]$matchedsilentargs -join " /")
write-host running "$filePath" $silentArgs
(Start-Process -FilePath "msiexec.exe" -ArgumentList "$silentArgs" -Wait -Passthru).ExitCode
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment