Hey Guys,
This is one I’ve been thinking about how best to write for a bit. It took some consideration on how to handle the 64-bit scenario because the Automox Agent is a 32-bit process. The ScriptBlock method I used here worked wonders–and can be re-purposed for anything that needs a 64-bit shell.
So all you have to do here is define $appName in both blocks (make sure they match!). If it finds any matches, the Evaluation returns as Non-Compliant (since uninstalled is the desired state). It uses similar detection in the Remediation to find the applications “UninstallString” which can be used to run an uninstall–but adding parameters/arguments for silent execution. Since EXE arguments are non-standard, you may need to customize that bit, but /S works for most of them.
There are a ton of comments inside to make it clear what’s happening, but feel free to ask any questions that might come up.
Evaluation
<#
.SYNOPSIS
Check for presence of specified application on the target device
.DESCRIPTION
Read 32-bit and 64-bit registry to find matching applications
Exits with 0 for compliance, 1 for Non-Compliance.
Non-Compliant devices will run Remediation Code at the Policy's next scheduled date.
.NOTES
A scriptblock is used to workaround the limitations of 32-bit powershell.exe.
This allows us to redirect the operations to a 64-bit powershell.exe and read
the 64-bit registry without .NET workarounds.
.LINK
http://www.automox.com
#>
# The ScriptBlock method used here is to allow a 32-bit agent process
# to access the 64-bit registry on 64-bit Windows. This is necessary if the application
# isn't known to be 32-bit only.
$scriptblock = {
#Define Registry Location for the 64-bit and 32-bit Uninstall keys
$uninstReg = @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall')
# Define the App Name to look for
# Look at a machine with the application installed unless you're sure the formatting of the name/version
# Specifically the DisplayName. This is what you see in Add/Remove Programs. This doesn't have to be exact.
# Default behavior uses -match which is essentially "DisplayName contains VLC"
##################
$appName = 'Steam'
##################
# Get all entries that match our criteria. DisplayName matches $appname
$installed = @(Get-ChildItem $uninstReg -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { ($_.DisplayName -match $appName) })
# If any matches were present, $installed will be populated. If none, then $installed is NULL and this IF statement will be false.
# The return value here is what the ScriptBlock will send back to us after we run it.
# 1 for Non-Compliant, 0 for Compliant
if ($installed) {
return 1
} else { return 0 }
}
$exitCode = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
Exit $exitCode
Remediation
<#
.SYNOPSIS
Uninstall matching applications from the target device.
.DESCRIPTION
Read 32-bit and 64-bit registry to determine the UninstallString for
each matching application. Then use the UninstallString to uninstall
the matching applications.
Exits with 0 for Success, 1 for Failure.
.NOTES
A scriptblock is used to workaround the limitations of 32-bit powershell.exe.
This allows us to redirect the operations to a 64-bit powershell.exe and read
the 64-bit registry without .NET workarounds.
.LINK
http://www.automox.com
#>
# BIG CAVEAT HERE
# If your application uses an EXE instead of "msiexec" in it's
# UninstallString, the SILENT argument isn't standardized.
# It's also possible that a reboot suppressing argument can be important
# You may need to look that up in the vendor's documentation.
# Change this in the ArgumentList below on this line
# $process = Start-Process $uninstString -ArgumentList '/S /NORESTART'
# The current ArgumentList is specific to Steam which uses /S for silent
# If your application doesn't have a standard UninstallString, you can
# define it directly, but that is rare.
$scriptblock = {
#Define Registry Location for Uninstall keys
$uninstReg = @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall')
# Look at a machine with the application installed unless you're sure the formatting of the name/version
##################
$appName = 'Steam'
##################
# Get all entries that match our criteria. DisplayName matches $appname, DisplayVersion less than current
$installed = @(Get-ChildItem $uninstReg -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object { ($_.DisplayName -match $appName) })
# Initialize an array to store the uninstalled app information
$uninstalled = @()
# Start a loop in-case you get more than one match, uninstall each.
foreach ($version in $installed) {
#For every version found, run the uninstall string
$uninstString = $version.UninstallString
#If exe run as written + silent argument, if msiexec run as msi using the name of the reg key as the msi guid.
if ($uninstString -match 'msiexec') {
$process = Start-Process msiexec.exe -ArgumentList "/x $($version.PSChildName) /qn REBOOT=ReallySuppress" -Wait -PassThru
} else {
$process = Start-Process $uninstString -ArgumentList '/S' -Wait -PassThru
}
# Check exit code for success/fail
# Using 3 "-eq" statements becuase older PowerShell doesn't support "-in"
# If unsuccessful, don't add to uninstalled list.
if ( ($process.ExitCode -eq '0') -or ($process.ExitCode -eq '1641') -or ($process.ExitCode -eq '3010') ) {
$uninstalled += $version.PSPath
}
}
return $uninstalled
}
$uninstalledApps = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
# Use Write-Output so you can see a result in the Activity Log
Write-Output "$uninstalledApps"
if ($uninstalledApps) {
Exit 0
} else { Exit 1 }
Edited to add success/fail check on the uninstall process.