Uninstalling Agent via PowerShell
How to use PowerShell to uninstall the AutoElevate agent
Table of Contents
Overview
Occasionally, the AutoElevate Agent may not install cleanly, and manual removal is required when the standard uninstall process fails. The Uninstall-AutoElevate.ps1 PowerShell script removes the agent silently, cleans up anything the uninstall leaves behind, and writes a detailed log of every step.
The script is built to run unattended from your RMM as SYSTEM, and it returns standard exit codes your RMM can report on. You can also run it by hand from an elevated PowerShell window.
Download the PowerShell Script
What the Script Does
The script works through these steps in order. Running the MSI uninstall before deleting files and registry keys gives Windows Installer the best chance to remove the agent cleanly.
- Switches to 64-bit PowerShell if needed. Some RMM agents launch 32-bit PowerShell, which redirects registry and Program Files paths. The script relaunches itself in 64-bit PowerShell so it finds the right locations.
-
Checks for administrative rights. If the script isn't running as SYSTEM or an administrator, it logs an error and exits with code
1. -
Stops the agent. It disables the
AutoElevateAgentservice so it can't restart, stops it (force-killing the service process if a normal stop fails within 30 seconds), and stops any other process running from the AutoElevate install folder. -
Runs the MSI uninstall. It finds the AutoElevate product code in the Windows installed-programs registry and runs
msiexec /x {ProductCode} /qn /norestartwith verbose MSI logging. -
Cleans up leftovers. It removes the
AutoElevateAgentservice registration, theC:\Program Files\AutoElevateandC:\Program Files (x86)\AutoElevatefolders, and theHKLM\SOFTWARE\AutoElevateandHKLM\SOFTWARE\WOW6432Node\AutoElevateregistry keys. - Retries the MSI uninstall if it failed. If the first uninstall failed, the script runs it a second time after cleanup to clear the AutoElevate entry from Installed apps (Add/Remove Programs).
- Last resort, if the retry also fails. The script removes the Windows Installer registration for the AutoElevate product code only, so AutoElevate no longer appears in Installed apps and a clean reinstall is possible. Nothing else in the Windows Installer database is touched.
- Verifies and reports. The script checks that the service, the installed-programs entry, the folders and the registry keys are all gone, logs anything that remains, and exits with an RMM-friendly exit code.
Log Files
All logs are written to the Windows system temp folder, normally C:\Windows\Temp:
-
AutoElevateUninstall.log— the script's own log. Each run is appended with a separator line, the computer name, and the account it ran as. -
AutoElevateUninstall_MSI_Attempt1.log— the verbose Windows Installer log for the first uninstall. -
AutoElevateUninstall_MSI_Attempt2.log— the verbose Windows Installer log for the retry (only created if the first uninstall failed).
The script's output is also written to the console, so most RMMs capture it in the job or script output.
Exit Codes
-
0— AutoElevate was removed, or wasn't installed. -
3010— AutoElevate was removed, but a reboot is required to finish (for example, files were in use). -
1— The script failed or AutoElevate was not fully removed. Check the log.
Running the Script
Run as Administrator. Ensure you run the PowerShell script with administrative privileges — either as SYSTEM from your RMM or from an elevated PowerShell window.
RMM Deployment
- Download Uninstall-AutoElevate.ps1 using the button above.
- Add it to your RMM as a PowerShell script set to run as SYSTEM. The script takes no parameters.
- Run it against the target device(s).
- Treat exit code
0as success and3010as success with a reboot needed. If your RMM supports it, reboot devices that return3010.
Run Manually on a Windows Device
- Download Uninstall-AutoElevate.ps1 to the device. Use the download button — don't copy the script text from this page into a file (see Troubleshooting).
- Open PowerShell with Run as administrator.
- Change to the folder where you saved the script, then unblock it and run it:
Unblock-File .\Uninstall-AutoElevate.ps1 powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Uninstall-AutoElevate.ps1 - When it finishes, the last line shows the exit code. Review
C:\Windows\Temp\AutoElevateUninstall.logfor details.
Best Practices
- Try the standard uninstall first. This script is for agents that didn't install or uninstall cleanly.
- Test on one device before a broad rollout, and confirm the exit code and log look as expected.
- Reboot devices that return 3010, then re-run the script to confirm nothing remains.
-
Keep the logs from
C:\Windows\Tempwhen a device returns exit code1. Attach them if you open a support ticket.
Troubleshooting
"Unexpected token" or "Unexpected attribute 'CmdletBinding'" errors
PowerShell couldn't read the file as a valid script, usually because extra text was pasted into it when it was copied from a web page or chat. Delete the file, download a fresh copy using the download button, and run it again.
"Running scripts is disabled on this system"
The device's PowerShell execution policy is blocking the script. Run Unblock-File on the downloaded file and start it with -ExecutionPolicy Bypass, as shown in Run Manually on a Windows Device. Many RMMs already bypass the execution policy when they run scripts.
Exit code 1: "This script must run as SYSTEM or an administrator"
The script wasn't elevated. Run it from your RMM as SYSTEM, or open PowerShell with Run as administrator.
Exit code 3010, or items still listed as "Remaining"
Some files or the service were still in use and Windows needs a reboot to finish removing them. Reboot the device and run the script again; it should then return 0.
The MSI uninstall failed
The log will show msiexec failed with exit code …. Open the matching AutoElevateUninstall_MSI_Attempt*.log in C:\Windows\Temp and search for Return value 3 to find the step that failed. The script retries automatically and, if the retry also fails, removes the AutoElevate Installed apps entry so the device can be reinstalled cleanly.
Security Notes
- The script needs SYSTEM or administrator rights because it stops services and changes protected areas of the registry and Program Files.
- It deletes the
HKLM\SOFTWARE\AutoElevateregistry keys, which hold the agent's configuration. - Once the agent is removed, the device is no longer protected by AutoElevate.
- The last-resort cleanup only removes Windows Installer entries tied to the AutoElevate product code. It doesn't change any other installed software.
The Script
The full script is shown below for review. To run it, use the download button above rather than copying this text.
# Copyright (c) 2026 CyberFOX, LLC
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the names of CyberFOX, AutoElevate nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL CyberFOX, LLC. BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<#
.SYNOPSIS
Uninstalls the AutoElevate agent silently and logs every step.
.DESCRIPTION
Designed to run non-interactively from an RMM as SYSTEM (or from an elevated
PowerShell session). Order of operations:
1. Relaunch in 64-bit PowerShell if started from a 32-bit host.
2. Verify administrative rights.
3. Stop the AutoElevateAgent service and any process running from the
install folder.
4. Run the MSI uninstall (msiexec /x {ProductCode} /qn /norestart) with a
verbose MSI log.
5. Sweep leftovers: service registration, install folders, registry keys.
6. If the MSI uninstall failed, re-run it after the sweep to clear the
Installed apps entry. If it fails again, remove the Windows Installer
registration for the AutoElevate product code only.
7. Verify nothing is left behind and return an RMM-friendly exit code.
Logs (all in the system temp folder, normally C:\Windows\Temp):
AutoElevateUninstall.log - this script's log
AutoElevateUninstall_MSI_Attempt1.log - verbose msiexec log, first pass
AutoElevateUninstall_MSI_Attempt2.log - verbose msiexec log, retry (if needed)
.NOTES
Exit codes:
0 AutoElevate removed (or was not installed)
3010 Removed, but a reboot is required to finish (files in use)
1 Failed - see the log
Compatible with Windows PowerShell 5.1 and PowerShell 7.
#>
[CmdletBinding()]
param()
#region ---- Configuration -------------------------------------------------------
$SoftwareDisplayName = 'AutoElevate'
$ServiceName = 'AutoElevateAgent'
$TempDir = Join-Path $env:SystemRoot 'Temp'
$LogFile = Join-Path $TempDir 'AutoElevateUninstall.log'
$MsiLogFile = Join-Path $TempDir 'AutoElevateUninstall_MSI.log'
$InstallFolders = @($env:ProgramFiles, ${env:ProgramFiles(x86)}) |
Where-Object { $_ } |
ForEach-Object { Join-Path $_ 'AutoElevate' } |
Select-Object -Unique
$RegistryKeys = @(
'HKLM:\SOFTWARE\AutoElevate'
'HKLM:\SOFTWARE\WOW6432Node\AutoElevate'
)
$UninstallRoots = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
# msiexec results treated as success
$MsiSuccessCodes = @{
0 = 'Success'
1605 = 'Product not installed'
1641 = 'Success - reboot initiated'
3010 = 'Success - reboot required'
}
#endregion
#region ---- 64-bit relaunch -----------------------------------------------------
# Some RMM agents launch 32-bit PowerShell, which redirects registry and
# Program Files paths. Relaunch in native 64-bit PowerShell when needed.
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
$nativePS = Join-Path $env:SystemRoot 'SysNative\WindowsPowerShell\v1.0\powershell.exe'
if (Test-Path $nativePS) {
& $nativePS -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
}
#endregion
#region ---- Logging -------------------------------------------------------------
function Write-Log {
param(
[Parameter(Mandatory)][string]$Message,
[ValidateSet('INFO', 'WARN', 'ERROR')][string]$Level = 'INFO'
)
$line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
Write-Host $line
try { Add-Content -Path $LogFile -Value $line -Encoding UTF8 -ErrorAction Stop } catch { }
}
#endregion
#region ---- Functions -----------------------------------------------------------
function Test-IsAdmin {
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-AEUninstallEntry {
# Reads the Uninstall registry keys instead of Win32_Product, which is slow
# and triggers an MSI consistency check (and possible repair) of every
# installed product.
foreach ($root in $UninstallRoots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem -Path $root -ErrorAction SilentlyContinue |
Get-ItemProperty -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -eq $SoftwareDisplayName }
}
}
function Stop-AEAgent {
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $svc) {
Write-Log "Service '$ServiceName' not found; nothing to stop."
}
else {
# Disable first so the service can't be restarted by recovery options
# while the uninstall runs.
try { Set-Service -Name $ServiceName -StartupType Disabled -ErrorAction Stop } catch {
Write-Log "Could not disable '$ServiceName': $($_.Exception.Message)" 'WARN'
}
if ($svc.Status -ne 'Stopped') {
Write-Log "Stopping service '$ServiceName'..."
try {
Stop-Service -Name $ServiceName -Force -ErrorAction Stop
$svc.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(30))
Write-Log "Service '$ServiceName' stopped."
}
catch {
Write-Log "Graceful stop failed ($($_.Exception.Message)); killing service process." 'WARN'
$svcPid = (Get-CimInstance -ClassName Win32_Service -Filter "Name='$ServiceName'").ProcessId
if ($svcPid -and $svcPid -ne 0) {
Stop-Process -Id $svcPid -Force -ErrorAction SilentlyContinue
Write-Log "Killed service process (PID $svcPid)."
}
}
}
else {
Write-Log "Service '$ServiceName' already stopped."
}
}
# Kill anything else (tray/UI helpers) still running from the install folder
foreach ($folder in $InstallFolders) {
Get-Process -ErrorAction SilentlyContinue |
Where-Object { $_.Path -and $_.Path -like "$folder\*" } |
ForEach-Object {
Write-Log "Stopping process $($_.ProcessName) (PID $($_.Id))."
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
}
}
function Invoke-AEMsiUninstall {
# Runs msiexec /x for every AutoElevate MSI entry in installed programs.
# Returns: @{ Found = <bool>; Reboot = <bool>; Failed = <product codes that failed> }
param([int]$Attempt = 1)
$result = @{ Found = $false; Reboot = $false; Failed = @() }
$entries = @(Get-AEUninstallEntry)
if ($entries.Count -eq 0) {
Write-Log "[Attempt $Attempt] '$SoftwareDisplayName' not found in installed programs; nothing to uninstall."
return $result
}
$result.Found = $true
foreach ($entry in $entries) {
$productCode = $entry.PSChildName
if ($productCode -notmatch '^\{[0-9A-Fa-f\-]{36}\}$') {
Write-Log "[Attempt $Attempt] Uninstall entry '$productCode' is not an MSI product code; skipping." 'WARN'
continue
}
$msiLog = $MsiLogFile -replace '\.log$', "_Attempt$Attempt.log"
Write-Log "[Attempt $Attempt] Uninstalling '$($entry.DisplayName)' version $($entry.DisplayVersion) ($productCode)..."
$msiArgs = "/x $productCode /qn /norestart /l*v `"$msiLog`""
$proc = Start-Process -FilePath "$env:SystemRoot\System32\msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru -WindowStyle Hidden
$code = $proc.ExitCode
if ($MsiSuccessCodes.ContainsKey($code)) {
Write-Log "[Attempt $Attempt] msiexec exit code $code ($($MsiSuccessCodes[$code]))."
if ($code -in 1641, 3010) { $result.Reboot = $true }
}
else {
Write-Log "[Attempt $Attempt] msiexec failed with exit code $code. See $msiLog." 'ERROR'
$result.Failed += $productCode
}
}
return $result
}
function ConvertTo-PackedGuid {
# Converts {12345678-ABCD-EFGH-IJKL-MNOPQRSTUVWX} to the "packed" form
# Windows Installer uses for its registry keys.
param([Parameter(Mandatory)][string]$ProductCode)
$g = $ProductCode.Trim('{}').Replace('-', '').ToUpper()
$rev = { param($s) -join ($s.ToCharArray()[($s.Length - 1)..0]) }
$swapPairs = { param($s) -join (0..($s.Length / 2 - 1) | ForEach-Object { $s[$_ * 2 + 1]; $s[$_ * 2] }) }
return (& $rev $g.Substring(0, 8)) + (& $rev $g.Substring(8, 4)) + (& $rev $g.Substring(12, 4)) +
(& $swapPairs $g.Substring(16, 16))
}
function Remove-AEProductRegistration {
# Last resort when msiexec still fails after cleanup: remove the Windows
# Installer registration for the AutoElevate product code(s) only, so the
# entry disappears from Installed apps and a clean reinstall is possible.
param([Parameter(Mandatory)][string[]]$ProductCodes)
foreach ($productCode in $ProductCodes) {
$packed = ConvertTo-PackedGuid -ProductCode $productCode
Write-Log "Removing Windows Installer registration for $productCode (packed $packed)." 'WARN'
$keys = @(
"HKLM:\SOFTWARE\Classes\Installer\Products\$packed"
"HKLM:\SOFTWARE\Classes\Installer\Features\$packed"
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$packed"
) + ($UninstallRoots | ForEach-Object { Join-Path $_ $productCode })
foreach ($key in $keys) {
if (Test-Path -LiteralPath $key) {
try {
Remove-Item -LiteralPath $key -Recurse -Force -ErrorAction Stop
Write-Log "Deleted '$key'."
}
catch {
Write-Log "Could not delete '$key': $($_.Exception.Message)" 'ERROR'
}
}
}
# Remove the product from its UpgradeCode mapping so a reinstall isn't
# treated as an upgrade of a product that no longer exists.
$upgradeRoot = 'HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes'
if (Test-Path $upgradeRoot) {
Get-ChildItem -Path $upgradeRoot -ErrorAction SilentlyContinue | ForEach-Object {
$props = $_.GetValueNames()
if ($props -contains $packed) {
Remove-ItemProperty -LiteralPath $_.PSPath -Name $packed -Force -ErrorAction SilentlyContinue
Write-Log "Removed $packed from UpgradeCode '$($_.PSChildName)'."
if (@((Get-Item -LiteralPath $_.PSPath).GetValueNames() | Where-Object { $_ }).Count -eq 0) {
Remove-Item -LiteralPath $_.PSPath -Force -ErrorAction SilentlyContinue
Write-Log "Deleted empty UpgradeCode key '$($_.PSChildName)'."
}
}
}
}
}
}
function Remove-AELeftovers {
# Service registration
if (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue) {
Write-Log "Service '$ServiceName' still registered; deleting."
$null = & sc.exe delete $ServiceName 2>&1
if ($LASTEXITCODE -eq 0) { Write-Log "Service '$ServiceName' deleted." }
else { Write-Log "sc.exe delete returned $LASTEXITCODE (service may be marked for deletion until reboot)." 'WARN' }
}
# Install folders
foreach ($folder in $InstallFolders) {
if (Test-Path -LiteralPath $folder) {
try {
Remove-Item -LiteralPath $folder -Recurse -Force -ErrorAction Stop
Write-Log "Deleted folder '$folder'."
}
catch {
Write-Log "Could not fully delete '$folder': $($_.Exception.Message)" 'WARN'
}
}
}
# Registry keys
foreach ($key in $RegistryKeys) {
if (Test-Path -LiteralPath $key) {
try {
Remove-Item -LiteralPath $key -Recurse -Force -ErrorAction Stop
Write-Log "Deleted registry key '$key'."
}
catch {
Write-Log "Could not delete '$key': $($_.Exception.Message)" 'WARN'
}
}
}
}
function Get-AERemnants {
$remnants = @()
if (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue) { $remnants += "Service: $ServiceName" }
if (@(Get-AEUninstallEntry).Count -gt 0) { $remnants += "Installed programs entry: $SoftwareDisplayName" }
foreach ($f in $InstallFolders) { if (Test-Path -LiteralPath $f) { $remnants += "Folder: $f" } }
foreach ($k in $RegistryKeys) { if (Test-Path -LiteralPath $k) { $remnants += "Registry: $k" } }
return $remnants
}
#endregion
#region ---- Main ----------------------------------------------------------------
$ErrorActionPreference = 'Stop'
$exitCode = 0
try {
Write-Log ('=' * 70)
Write-Log "AutoElevate uninstall started on $env:COMPUTERNAME as $([Security.Principal.WindowsIdentity]::GetCurrent().Name)."
Write-Log "Log file: $LogFile | MSI logs: $($MsiLogFile -replace '\.log$', '_Attempt*.log')"
if (-not (Test-IsAdmin)) {
Write-Log 'This script must run as SYSTEM or an administrator.' 'ERROR'
exit 1
}
Stop-AEAgent
# Attempt 1: normal MSI uninstall
$attempt1 = Invoke-AEMsiUninstall -Attempt 1
$rebootRequired = $attempt1.Reboot
# Always sweep leftovers (service, folders, AutoElevate registry keys)
Remove-AELeftovers
if ($attempt1.Failed.Count -gt 0) {
# Attempt 2: re-run the MSI uninstall now that the service, files and
# registry keys are gone, to clear the Installed apps entry.
Write-Log 'MSI uninstall failed; leftovers cleaned. Re-running MSI uninstall to clear the Installed apps entry.' 'WARN'
Stop-AEAgent
$attempt2 = Invoke-AEMsiUninstall -Attempt 2
if ($attempt2.Reboot) { $rebootRequired = $true }
# Attempt 2 can recreate files or keys before failing; sweep again.
Remove-AELeftovers
if ($attempt2.Failed.Count -gt 0) {
# Last resort: remove the Windows Installer registration directly.
Write-Log 'MSI uninstall failed again. Removing the product registration so it no longer appears in Installed apps.' 'WARN'
Remove-AEProductRegistration -ProductCodes $attempt2.Failed
}
}
$remnants = @(Get-AERemnants)
if ($remnants.Count -eq 0) {
if ($rebootRequired) {
Write-Log 'AutoElevate removed. A reboot is required to complete the uninstall.' 'WARN'
$exitCode = 3010
}
else {
Write-Log 'AutoElevate removed successfully.'
}
}
else {
foreach ($r in $remnants) { Write-Log "Remaining: $r" 'WARN' }
if ($rebootRequired) {
Write-Log 'Items remain but a reboot is pending; reboot and re-run to confirm.' 'WARN'
$exitCode = 3010
}
else {
Write-Log 'AutoElevate was not fully removed.' 'ERROR'
$exitCode = 1
}
}
}
catch {
Write-Log "Unhandled error: $($_.Exception.Message) (line $($_.InvocationInfo.ScriptLineNumber))" 'ERROR'
$exitCode = 1
}
Write-Log "Finished with exit code $exitCode."
exit $exitCode
#endregion