PowerShell – Script to uninstall Adobe Acrobat Reader

Just another Tech site

PowerShell – Script to uninstall Adobe Acrobat Reader

Script to stop process, verify installation version, uninstall, log for 32/64bit version.

 

<#
.SYNOPSIS
  Detects and silently uninstalls Adobe Acrobat Reader (32-bit or 64-bit), stops processes/services, verifies removal, and logs to C:\logs\<date>\uninstall.log

.NOTES
  - Designed for PowerShell 5.1 on Windows.
  - Run as Administrator.
  - Use at your own risk; test in a safe environment before wide deployment.
#>

# -------------------------
# Elevation check
# -------------------------
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "This script must be run as Administrator." -ForegroundColor Red
    exit 1
}

# -------------------------
# Logging helpers
# -------------------------
function Get-LogPath {
    $date = (Get-Date).ToString('yyyy-MM-dd')
    $dir = Join-Path -Path 'C:\logs' -ChildPath $date
    if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null }
    return Join-Path -Path $dir -ChildPath 'uninstall.log'
}

$LogFile = Get-LogPath

function Log {
    param([string]$Message, [string]$Level = "INFO")
    $ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
    $line = "$ts [$Level] $Message"
    $line | Out-File -FilePath $LogFile -Encoding UTF8 -Append
    Write-Host $line
}

# -------------------------
# Utility: normalize uninstall string to executable + args
# -------------------------
function Parse-UninstallString {
    param([string]$UninstallString)

    # Trim quotes and whitespace
    $s = $UninstallString.Trim()
    # If starts with "MsiExec.exe" or "msiexec", return msiexec and args
    if ($s -match '(?i)msiexec') {
        # ensure /qn for silent uninstall
        # If uninstall string contains /I or /X with a product code, keep product code and use /qn
        # Example: MsiExec.exe /I{GUID} or MsiExec.exe /X{GUID}
        $exe = "msiexec.exe"
        # Replace /I with /X to uninstall if necessary
        $args = $s -replace '(?i)msiexec.exe','' -replace '(?i)/I','/X'
        if ($args -notmatch '(?i)/qn') { $args += " /qn /norestart" }
        return @{Exe=$exe; Args=$args.Trim()}
    }

    # If uninstall string is quoted path to an exe with arguments
    if ($s -match '^\s*"(.*?)"\s*(.*)$') {
        $exePath = $matches[1]
        $rest = $matches[2].Trim()
        # Common Adobe silent switches: /sAll /silent /s /S /qn for MSI
        if ($rest -notmatch '(?i)/sAll|/silent|/s|/S|/qn') {
            # try /sAll first
            $rest = ($rest + " /sAll").Trim()
        }
        return @{Exe=$exePath; Args=$rest}
    }

    # If uninstall string is unquoted path
    if ($s -match '^\s*([^\s]+)\s*(.*)$') {
        $exePath = $matches[1]
        $rest = $matches[2].Trim()
        if ($rest -notmatch '(?i)/sAll|/silent|/s|/S|/qn') {
            $rest = ($rest + " /sAll").Trim()
        }
        return @{Exe=$exePath; Args=$rest}
    }

    # Fallback: return raw string as command to run via cmd /c
    return @{Exe="cmd.exe"; Args="/c $s"}
}

# -------------------------
# Detect installed Acrobat Reader entries
# -------------------------
function Get-AcrobatInstallInfo {
    <#
    Returns array of objects:
      @{ DisplayName=; UninstallString=; InstallLocation=; Publisher=; Version=; Architecture=; RegistryKey= }
    #>
    $results = @()

    # Registry hives to search
    $regPaths = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
        "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    )

    foreach ($path in $regPaths) {
        try {
            $keys = Get-ChildItem -Path $path -ErrorAction SilentlyContinue
            foreach ($k in $keys) {
                try {
                    $props = Get-ItemProperty -Path $k.PSPath -ErrorAction SilentlyContinue
                    if ($null -eq $props) { continue }
                    $display = $props.DisplayName
                    if ($display -and $display -match 'Adobe Acrobat Reader|Acrobat Reader') {
                        $obj = [PSCustomObject]@{
                            DisplayName     = $props.DisplayName
                            UninstallString = $props.UninstallString
                            InstallLocation = $props.InstallLocation
                            Publisher       = $props.Publisher
                            Version         = $props.DisplayVersion
                            RegistryKey     = $k.PSPath
                            Architecture    = if ($path -like '*WOW6432Node*') { '32-bit' } else { '64-bit' }
                        }
                        $results += $obj
                    }
                } catch { continue }
            }
        } catch { continue }
    }

    # Also check common install folders for Acrobat Reader executables as a fallback
    $possiblePaths = @(
        "$env:ProgramFiles\Adobe\Acrobat Reader*",
        "$env:ProgramFiles(x86)\Adobe\Acrobat Reader*"
    )
    foreach ($p in $possiblePaths) {
        foreach ($match in Get-ChildItem -Path $p -Directory -ErrorAction SilentlyContinue) {
            # If not already in results, add a fallback entry
            if (-not ($results | Where-Object { $_.InstallLocation -and ($_.InstallLocation -eq $match.FullName) })) {
                $exe32 = Join-Path $match.FullName 'Reader\AcroRd32.exe'
                $exe64 = Join-Path $match.FullName 'Acrobat\Acrobat.exe'
                if (Test-Path $exe32 -or Test-Path $exe64) {
                    $arch = if ($match.FullName -like "*Program Files (x86)*") { '32-bit' } else { '64-bit' }
                    $results += [PSCustomObject]@{
                        DisplayName     = "Adobe Acrobat Reader (detected folder)"
                        UninstallString = $null
                        InstallLocation = $match.FullName
                        Publisher       = "Adobe"
                        Version         = $null
                        RegistryKey     = $null
                        Architecture    = $arch
                    }
                }
            }
        }
    }

    return $results
}

# -------------------------
# Stop processes and services related to Acrobat Reader
# -------------------------
function Stop-AcrobatProcessesAndServices {
    param([switch]$Force)

    Log "Stopping Acrobat Reader related processes and services..." "INFO"

    # Common process names
    $procNames = @('AcroRd32','Acrobat','AdobeARM','AdobeUpdateService','AdobeCollabSync','RdrCEF') 

    foreach ($pname in $procNames) {
        try {
            $procs = Get-Process -Name $pname -ErrorAction SilentlyContinue
            foreach ($pr in $procs) {
                try {
                    Log "Stopping process $($pr.ProcessName) (Id $($pr.Id))" "INFO"
                    Stop-Process -Id $pr.Id -Force:$Force -ErrorAction Stop
                    Log "Stopped process Id $($pr.Id)" "INFO"
                } catch {
                    Log "Failed to stop process $($pr.ProcessName) Id $($pr.Id): $($_.Exception.Message)" "WARN"
                }
            }
        } catch {}
    }

    # Stop services that look like Adobe services
    try {
        $svcCandidates = Get-Service | Where-Object { $_.Name -match 'Adobe|ARM|AdobeUpdate' -or $_.DisplayName -match 'Adobe' } -ErrorAction SilentlyContinue
        foreach ($svc in $svcCandidates) {
            try {
                if ($svc.Status -ne 'Stopped') {
                    Log "Stopping service $($svc.Name) ($($svc.DisplayName))" "INFO"
                    Stop-Service -Name $svc.Name -Force -ErrorAction Stop
                    Log "Stopped service $($svc.Name)" "INFO"
                }
            } catch {
                Log "Failed to stop service $($svc.Name): $($_.Exception.Message)" "WARN"
            }
        }
    } catch {
        Log "Service enumeration failed: $($_.Exception.Message)" "WARN"
    }
}

# -------------------------
# Uninstall function
# -------------------------
function Uninstall-Acrobat {
    param(
        [Parameter(Mandatory=$true)][PSCustomObject]$InstallInfo
    )

    Log "Preparing to uninstall: $($InstallInfo.DisplayName) (Arch: $($InstallInfo.Architecture))" "INFO"

    if (-not $InstallInfo.UninstallString) {
        Log "No uninstall string found in registry for this entry. Attempting to remove folder if present." "WARN"
        if ($InstallInfo.InstallLocation -and (Test-Path $InstallInfo.InstallLocation)) {
            try {
                Log "Attempting to remove folder: $($InstallInfo.InstallLocation)" "INFO"
                Remove-Item -Path $InstallInfo.InstallLocation -Recurse -Force -ErrorAction Stop
                Log "Removed folder $($InstallInfo.InstallLocation)" "INFO"
            } catch {
                Log "Failed to remove folder $($InstallInfo.InstallLocation): $($_.Exception.Message)" "ERROR"
            }
        } else {
            Log "No install location available to remove." "WARN"
        }
        return
    }

    $parsed = Parse-UninstallString -UninstallString $InstallInfo.UninstallString
    $exe = $parsed.Exe
    $args = $parsed.Args

    Log "Uninstall command: `"$exe`" $args" "INFO"

    try {
        # If exe is msiexec, run directly
        $startInfo = @{
            FilePath = $exe
            ArgumentList = $args
            Wait = $true
            NoNewWindow = $true
            ErrorAction = 'Stop'
        }
        Log "Starting uninstall process..." "INFO"
        Start-Process @startInfo
        Log "Uninstall process completed for $($InstallInfo.DisplayName)" "INFO"
    } catch {
        Log "Uninstall process failed: $($_.Exception.Message)" "ERROR"
    }
}

# -------------------------
# Verify uninstalled and folder removed
# -------------------------
function Verify-Uninstalled {
    param([PSCustomObject]$InstallInfo)

    $name = $InstallInfo.DisplayName
    Log "Verifying uninstall for: $name" "INFO"

    # Check registry again for matching display name
    $stillInstalled = $false
    try {
        $all = Get-AcrobatInstallInfo
        if ($all | Where-Object { $_.DisplayName -and $_.DisplayName -eq $name }) {
            $stillInstalled = $true
        }
    } catch {
        Log "Failed to re-check registry: $($_.Exception.Message)" "WARN"
    }

    if ($stillInstalled) {
        Log "Product still present in registry: $name" "ERROR"
    } else {
        Log "Product not found in registry: $name" "INFO"
    }

    # Check install folder
    if ($InstallInfo.InstallLocation) {
        if (Test-Path $InstallInfo.InstallLocation) {
            Log "Install folder still exists: $($InstallInfo.InstallLocation)" "ERROR"
        } else {
            Log "Install folder removed: $($InstallInfo.InstallLocation)" "INFO"
        }
    } else {
        Log "No install folder recorded for this entry." "INFO"
    }
}

# -------------------------
# Main flow
# -------------------------
Log "=== Adobe Acrobat Reader Uninstall Script started ===" "INFO"

$found = Get-AcrobatInstallInfo
if (-not $found -or $found.Count -eq 0) {
    Log "No Adobe Acrobat Reader installations detected on this machine." "INFO"
    Log "=== Script finished ===" "INFO"
    exit 0
}

# Process each detected installation
foreach ($inst in $found) {
    Log "Detected: $($inst.DisplayName) | Version: $($inst.Version) | Arch: $($inst.Architecture) | InstallLocation: $($inst.InstallLocation)" "INFO"

    # Stop processes/services
    Stop-AcrobatProcessesAndServices -Force

    # Wait briefly for processes to terminate
    Start-Sleep -Seconds 2

    # Attempt uninstall
    Uninstall-Acrobat -InstallInfo $inst

    # Wait for uninstall to complete and for processes to settle
    Start-Sleep -Seconds 5

    # Verify
    Verify-Uninstalled -InstallInfo $inst
}

Log "=== Completed processing all detected Acrobat Reader installations ===" "INFO"