Microsoft Edge Cleanup and Reinstall Script
This page documents a robust PowerShell script used to completely remove an application (like Microsoft Edge) and reinstall it cleanly. It's especially useful when traditional uninstallation or update processes fail, leaving broken traces behind.
đź“„ About
This script helps remove:
-
Registry entries
-
Scheduled tasks
-
Background services
-
Running processes
-
Residual folders and files
đź”§ Configuration Tips
Finding Configurations:
-
Search online for:
-
Uninstall GUIDs
-
File paths
-
Registry keys
-
-
Define:
-
$regNameToMatch -
$knownUninstallers -
$knownPathsToDelete, etc.
-
-
Repeat if needed: leftover services or background tasks can require multiple runs.
🛠️ Script
###
### About
###
# Application Cleanup and Reinstall
# What is this?
# This PowerShell script helps remove most traces of an installed application
# so you can reinstall it cleanly. It targets leftover files, folders, and registry
# entries that can block updates or fresh installs. It was originally built to
# remove problematic versions of Microsoft Edge that failed to update.
# How to find configurations?
# 1. Search online for uninstall GUIDs, file paths, and registry keys related to
# your application. Official docs or forums often list these details.
# 2. Use RegShot (on GitHub) to capture "before" and "after" system snapshots.
# Compare them to see which files and registry entries the installer added.
# 3. In the script, specify top-level folders, file patterns, and registry keys you
# want removed. It will also scan the usual uninstall locations by name.
# 4. If the first run doesn’t catch everything, repeat the process. Look for
# background processes, services, or custom registry keys not covered initially.
# Having issues?
# Please email me with detailed information:
# • RegShot comparison report
# • Script output or action log
# • List of running processes
# • Your script configurations
# The more data you provide, the faster we can diagnose and fix the problem.
######################################################################################################################
######################################################################################################################
###
### Configurations
###
######################################################################################################################
$regNameToMatch = "Microsoft Edge"
# This setting tells the script what application name to look for when scanning standard uninstall registry
# locations. It uses a "match" operation against the Name, DisplayName, and ProductName properties in those
# registry keys. Use a clear, unique identifier for your app so you don’t accidentally remove other software.
# Example usage
# $regNameToMatch = "Microsoft Edge"
# This will match any registry entries whose Name, DisplayName, or ProductName contains "Microsoft Edge"
######################################################################################################################
$knownUninstallers = @('"C:\Program Files (x86)\Microsoft\Edge\Application\*\Installer\setup.exe" --uninstall --system-level --force-uninstall')
# Define known uninstaller paths Add any known uninstaller executable locations to this array. You can use PowerShell
# wildcards (\*) to match varying folder names. If the path includes spaces, wrap it in single quotes on the outside
# and double quotes for the executable path.
# Example usage
# $knownUninstallers = @(
# '"C:\Program Files (x86)\Microsoft\Edge\Application\*\Installer\setup.exe" --uninstall --system-level --force-uninstall'
# )
######################################################################################################################
$scheduledTasksNameToRemove = "Edge"
# Configure scheduled task removalThis array specifies a name pattern to search for in Scheduled Tasks.The script
# uses a "like" operator, adding wildcards (*) before and afterthe name you provide to match any part of the task name.
# Example usage
# $scheduledTasksNameToRemove = "Edge"
# This will match tasks containing "Edge" anywhere in their names.
######################################################################################################################
$processNamesToStop = @("msedge","msedgewebview2","widget","msiexec","MicrosoftEdgeUpdate")
# Define processes to stop List any running process names that should be terminated at the start of cleanup.
# You can include wildcards (*) for partial matches.
# Example usage
# $processNamesToStop = @("msedge","msedgewebview2","widget","msiexec","MicrosoftEdgeUpdate")
# This will stop any processes matching those names before uninstall steps begin.
######################################################################################################################
$knownPathsToDelete = @(
"HKLM:\SOFTWARE\Microsoft\EdgeUpdate",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate",
"HKLM:\SOFTWARE\Microsoft\Edge",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Edge",
"C:\Program Files (x86)\Microsoft\Edge",
"C:\ProgramData\Microsoft\EdgeUpdate"
)
# Define known paths to delete. Specify any registry keys or file paths to remove during cleanup.
# Use PowerShell paths for registry (e.g., HKLM:...) and standard file paths. Wildcards (*) can be used.
# Example usage
# $knownPathsToDelete = @(
# "HKLM:\SOFTWARE\Microsoft\EdgeUpdate",
# "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate",
# "C:\Program Files (x86)\Microsoft\Edge"
# )
######################################################################################################################
$knownsServicesToDelete = @()
# Define services to delete List any service names to remove during cleanup. The script uses sc.exe to delete
# matching services. Wildcards (*) are supported.
# Example usage
# $knownServicesToDelete = @("*Soup*")
# This will delete any service whose name contains "Soup"
######################################################################################################################
$installerStrings = @("msiexec.exe /i MicrosoftEdgeEnterpriseX64.msi /qn")
# Define install commands for reinstallation If you plan to reinstall the application, list the full install
# commands here. Include the executable and all arguments. Installer files must reside in the script’s working
# directory, so you can call them by name without a full path.
# Example usage
# $installerStrings = @("msiexec.exe /i MicrosoftEdgeEnterpriseX64.msi /qn")
######################################################################################################################
$executableTimeoutSeconds = 300
# Configure executable timeout This setting defines the maximum time (in seconds) the script will wait for any
# executable to finish. Keep this value as low as possible to avoid hanging on installers that launch interactive GUIs.
# Example usage
# $executableTimeoutSeconds = 300
# This sets a 5-minute timeout for each executable call.
######################################################################################################################
######################################################################################################################
###
### Main function can be modified to change the order of operation or additional rounds
###
######################################################################################################################
function Main {
SetLog("Starting Cleanup")
Stop-AppProcessesByName($processNamesToStop)
Remove-ScheduledActions($scheduledTasksNameToRemove)
Remove-KnownServices($knownsServicesToDelete)
Invoke-KnownUninstallStrings($knownUninstallers)
$cleanupPaths = Find-AllRegistryLocations($regNameToMatch)
$GUIDs = Get-GUIDFromRegPath($cleanupPaths)
$cleanupPaths = $cleanupPaths + (Expand-GUIDPaths($GUIDs))
Invoke-MSIUninstall($GUIDs)
Invoke-UninstallStrings($cleanupPaths)
Remove-CleanupItems($knownPathsToDelete)
Remove-CleanupItems($cleanupPaths)
Invoke-InstallStrings($installerStrings)
SetLog("Cleanup complete")
}
######################################################################################################################
######################################################################################################################
###
### Working function below this point. If you find modifications are needed, please
### reach out for assistance or share your changes. This will allow others to avoid
### and benefit from the issues you encountered. [email protected]
###
######################################################################################################################
function SetLog($logline){
Write-Host ("[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) + $logline)
}
function Remove-KnownServices($services){
if($services.count -eq 0){
return
}
SetLog("Removing Services")
foreach($service in $services){
$svcs = Get-Service $service
SetLog("Found $($svcs.count) service(s) using $service")
#looping through if multiple services are found
foreach($svc in $svcs){
$thisName = $svc.Name
if($svc.status -eq "Running"){
SetLog("Service $thisName is running. Attempting to stop before removal.")
$svc | Stop-Service -ErrorAction SilentlyContinue
}
SetLog("Attempting to remove: $thisName")
sc.exe delete ($thisName)
if((Get-Service $thisName).count -eq 0){
SetLog("Service removed")
} else {
SetLog("Failed to remove service")
}
}
}
}
function Invoke-KnownUninstallStrings($strings){
Invoke-ExecutableStrArr -arr $strings -msg "Starting known uninstall strings"
}
function Invoke-ExecutableStrArr{
param(
[array]$arr,
[string]$msg
)
if($arr.count -eq 0){
return
}
SetLog($msg)
foreach($string in $arr){
$stringParts = Expand-ExecutableString($string)
Invoke-Executable @($stringParts.path,$stringParts.args)
}
}
function Invoke-InstallStrings($installers){
Invoke-ExecutableStrArr -arr $installers -msg "Starting installer(s)"
}
function Stop-AppProcessesByName($procNames){
SetLog("Stopping Application Processes")
$procNames = ConvertTo-Array $procNames
foreach($procName in $procNames){
SetLog("Looking for processes with name $procName")
$attempt = 0
do{
if($attempt -gt 0 ){
SetLog("Attempt number $($attempt+1) to stop processes for $procName")
}
$procs = Get-Process | Where-Object {$_.Name -match $procName}
foreach($proc in $procs){
try{
SetLog("Stopping process $($proc.processname) PID $($proc.id)")
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
}catch{
SetLog("ERROR stopping process: $($Error[0].Exception.Message)")
SetLog("ERROR NAME: $($error[0].Exception.GetType().FullName)")
}
}
Start-Sleep 1
$attempt++
}while(Get-Process | Where-Object {$_.Name -match $procName})
}
}
function Invoke-MSIUninstall($GUIDs){
if($GUIDs.count -eq 0){
return
}
SetLog("Uninstall by GUID")
foreach($GUID in $GUIDs){
Invoke-Executable @("msiexec.exe","/x $GUID /q")
}
}
function Invoke-UninstallStrings($cleanupObj){
foreach($path in $cleanupObj){
$targetStrings = New-Object System.Collections.ArrayList
foreach($props in (Get-ItemProperty $path)){
$propKeys = $props.PSObject.Properties.Name
#Locate any well know uninstall string locations
if($propKeys -contains "UninstallString"){
$targetStrings.add($props.UninstallString) | Out-Null
}
if($propKeys -contains "QuietUninstallString"){
$targetStrings.add($props.QuietUninstallString) | Out-Null
}
if($targetStrings.count -eq 0){
SetLog("Unhandled potential props: $($propKeys -join ", ")")
}
}
foreach($targetString in $targetStrings){
if($targetString -match "MsiExec"){
if($targetString -match ".*({.*}).*"){
Invoke-MSIUninstall @($Matches[1])
}else{
Invoke-Executable @("msiexec.exe","/X $($targetString) /q")
}
} else {
#Regex to extract the executable and arguments
$exeStrings = Expand-ExecutableString($targetString)
if($exeStrings){
Invoke-Executable @($exeStrings['Path'],$exeStrings['Args'])
} else {
SetLog("Unable to process uninstall string: $($targetString)")
}
}
}
}
}
function Expand-ExecutableString($str){
$pattern = '(?:"(?<Path>[A-Za-z]:\\[^"\\]+(?:\\[^"\\]+)*)"|(?<Path>[A-Za-z]:\\\S+(?:\\\S+)*)|(?<Path>[\w\.-]+\.exe))(?:\s+(?<Args>.*))?'
if($str -match $pattern){
return [PSCustomObject]@{
Path = $Matches['Path']
Args = $Matches['Args']
}
}
return $false
}
function Expand-GUIDPaths($GUIDs){
$results = New-Object System.Collections.ArrayList
foreach($GUID in $GUIDs){
$productCode = Convert-GUIDToMsiProductCode($GUID)
$productCodeRegistryPath = (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$productCode").PSPath
$results.add(($productCodeRegistryPath))|Out-Null
$results + (Find-AllRegistryLocations($GUID)) + (Find-AllRegistryLocations($productCode))
}
return $results
}
function Get-GUIDFromRegPath($pathsObj){
$results = New-Object System.Collections.ArrayList
foreach($path in $pathsObj){
if($path -match ".*({.*}).*"){
$results.add($Matches[1])|Out-Null
}
}
if($results.Count -gt 0){
return $results
} else {
return $null
}
}
Function Find-AllRegistryLocations($appName){
SetLog("Locating all application registry values for $appName")
$results = New-Object System.Collections.ArrayList
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\Classes\Installer\Products",
"HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Folders"
)
#To work with the asterisk path needed for HKU an additional loop is required
foreach($path in @("Registry::HKEY_USERS\*\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall","Registry::HKEY_USERS\*\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall")){
Get-ChildItem $path | ForEach-Object {$paths += $_.PSPath}
}
$valuesOfInterest = @("Name","DisplayName","ProductName")
foreach ($path in $paths){
if (Test-Path $path){
$regHive = Get-ChildItem -Path $path
foreach($regKey in $regHive){
$thisPSPath = $regKey.PSPath
if($thisPSPath -match $appName){
SetLog("FOUND: $thisPSPath")
$results.Add($thisPSPath)|Out-Null
continue
}
foreach($value in $valuesOfInterest){
try{
$foundValue = Get-ItemPropertyValue -Path $thisPSPath -Name $value
if($foundValue -match $appName){
$results.Add($thisPSPath)|Out-Null
}
}catch [System.Management.Automation.PSArgumentException]{
#Nothing to do - This reg did not have one of the values
}catch{
#Log it for later review
SetLog("ERROR: $($Error[0].Exception.Message)")
SetLog("ERROR NAME: $($error[0].Exception.GetType().FullName)")
}
}
}
}
}
return $results | Select-Object -Unique
}
function Convert-GUIDToMsiProductCode {
param([string]$Guid)
$ProductIDChars = [regex]::replace($GUID, "[^a-zA-Z0-9]", "")
$RearrangedCharIndex = 7,6,5,4,3,2,1,0,11,10,9,8,15,14,13,12,17,16,19,18,21,20,23,22,25,24,27,26,29,28,31,30
Return -join ($RearrangedCharIndex | ForEach-Object{$ProductIDChars[$_]})
}
function ConvertTo-Array($in){
if(-not($in -is [array])){
return @($in)
}
return $in
}
function Invoke-Executable($un){
SetLog("Expanding: $un")
try{
$thisPaths = Get-Item $un[0] -ErrorAction SilentlyContinue
}catch{
#nothing to do. catch to silence errors
}
if($thisPaths.count -eq 0 -and -not($un[0] -match "\*")){
$thisPaths = @{}
$thisPaths.FullName = $un[0]
}
if($thisPaths.count -eq 0){
SetLog("No executable found")
return
}
#Looping through to handle variable paths returning multiple
foreach($thisPath in $thisPaths){
$exePath = $thisPath.fullname
if($null -eq $exePath -or $exePath -eq ""){
continue
}
SetLog("Starting command: $exePath $($un[1])")
if($un[1]){
$thisPoc = Start-Process $exePath -ArgumentList ($un[1]) -PassThru -ErrorAction SilentlyContinue
} else {
$thisPoc = Start-Process $exePath -PassThru -ErrorAction SilentlyContinue
}
if ($thisPoc){
try{
Wait-Process -Id $thisPoc.Id -Timeout $executableTimeoutSeconds
}catch{
SetLog("ERROR: executable exceeded timed out (max: $executableTimeoutSeconds)")
Stop-Process -Id $thisPoc.Id -Force
}
} else {
SetLog("Failed to start executable process")
}
SetLog("ExitCode: $($thisPoc.ExitCode)")
}
}
function Remove-ScheduledActions($name){
SetLog("Checking for scheduled tasks with task name like: $name")
$tasks = Get-ScheduledTask | Where-Object { $_.TaskName -like "*$name*" }
if ($tasks) {
foreach ($task in $tasks) {
SetLog("Removing scheduled task: $($task.TaskName)")
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
}
} else {
SetLog("No scheduled tasks found with task name like: $name")
}
}
function Remove-CleanupItems($paths){
if($paths.count -eq 0){
return
}
SetLog("Removing items by path")
$paths = ConvertTo-Array $paths
foreach($path in $paths){
if(Test-Path $path){
SetLog("Found and removing item: $path")
Remove-Item -Path $path -Recurse -Force -Erroraction SilentlyContinue
SetLog("Item removed: $(-not(Test-Path $path))")
} else {
SetLog("Item found during scan no longer exists: $path")
}
}
}
Main
📝 Notes
-
Requires Admin privileges
-
Designed to run in a clean-up and reinstall loop
-
Highly configurable — edit
$regNameToMatchand related variables per app -
Consider creating backups before aggressive cleanups