Remove-SnapAgent.psm1

function Remove-SnapAgent {
    <#
    .SYNOPSIS
        Completely removes Blackpoint SNAP Agent and ZTAC components from a Windows system.
 
    .DESCRIPTION
        This function removes all traces of Blackpoint SNAP Agent and ZTAC software including:
        - Running processes
        - Windows services
        - MSI installations
        - Program files and directories
        - Registry keys and entries
         
        The function performs a thorough cleanup by dynamically discovering installed versions
        and removing them via MSI uninstall, followed by manual cleanup of remaining artifacts.
 
    .EXAMPLE
        Remove-SnapAgent
         
        Performs a complete removal of SNAP Agent and ZTAC components.
 
    .NOTES
        Requires Administrator privileges to execute.
        Author: Dailen Gunter
         
    .LINK
        https://github.com/DailenG/Remove-SnapAgent
    #>

    
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param()

    #Requires -RunAsAdministrator

    $snapProcesses = @("SnapAgent", "snap", "snapw", "ztac")

    $snapFolders = @(
        "$env:ProgramFiles\Blackpoint\SNAP Agent",
        "$env:ProgramFiles(x86)\Blackpoint\SNAP Agent"
    )

    $ztacFolders = @(
        "$env:ProgramFiles\Blackpoint\ZTAC",
        "$env:ProgramFiles(x86)\Blackpoint\ZTAC"
    )

    $allServices = @("Snap", "SnapAgent", "ZTAC", "ZtacFltr")
    $criticalErrors = @()
    $rebootRequired = $false

    # Stop any running processes
    Write-Host "Stopping any SNAP or ZTAC processes..." -ForegroundColor Green
    Get-Process $snapProcesses -ErrorAction SilentlyContinue | Stop-Process -Force
    Start-Sleep -Seconds 2

    # Stop and remove all SNAP/ZTAC services
    Write-Host "Stopping and removing SNAP/ZTAC services..." -ForegroundColor Green
    foreach ($serviceName in $allServices) {
        $svc = Get-Service $serviceName -ErrorAction SilentlyContinue
        if ($svc) {
            try {
                if ($svc.Status -ne 'Stopped') {
                    Write-Host "Attempting to stop service: $serviceName" -ForegroundColor Yellow
                    Stop-Service $serviceName -Force -ErrorAction Stop
                    Start-Sleep -Seconds 2
                    
                    # Verify service actually stopped
                    $svc = Get-Service $serviceName -ErrorAction SilentlyContinue
                    if ($svc -and $svc.Status -ne 'Stopped') {
                        $errorMsg = "CRITICAL: Service '$serviceName' failed to stop (Status: $($svc.Status)). Manual intervention or reboot required."
                        $criticalErrors += $errorMsg
                        Write-Error $errorMsg
                        $rebootRequired = $true
                        continue
                    }
                    Write-Host "Stopped service: $serviceName" -ForegroundColor Green
                }
                
                # Use sc.exe to delete the service
                $scResult = sc.exe delete $serviceName 2>&1
                if ($LASTEXITCODE -eq 0) {
                    Write-Host "Removed service: $serviceName" -ForegroundColor Green
                } else {
                    $errorMsg = "Failed to delete service '$serviceName': $scResult"
                    Write-Warning $errorMsg
                    if ($scResult -match "marked for deletion|pending") {
                        $rebootRequired = $true
                    }
                }
            } catch {
                $errorMsg = "Failed to stop/remove service: $serviceName. Error: $_"
                $criticalErrors += $errorMsg
                Write-Warning $errorMsg
                $rebootRequired = $true
            }
        }
    }

    # Uninstall using MSI for all SnapAgent versions
    Write-Host "Uninstalling all SnapAgent versions via MSI..." -ForegroundColor Yellow
    $snapPackages = Get-Package -Name "SnapAgent" -ErrorAction SilentlyContinue
    if ($snapPackages) {
        $pkgCount = 0
        $totalPkgs = @($snapPackages).Count
        foreach ($pkg in $snapPackages) {
            $pkgCount++
            Write-Progress -Activity "Uninstalling SnapAgent packages" -Status "Processing $($pkg.Name) $($pkg.Version) ($pkgCount of $totalPkgs)" -PercentComplete (($pkgCount / $totalPkgs) * 100)
            Write-Host "Found SnapAgent version $($pkg.Version) - Uninstalling..." -ForegroundColor Yellow
            try {
                # Try using Uninstall-Package first
                Uninstall-Package -Name $pkg.Name -RequiredVersion $pkg.Version -Force -ErrorAction Stop
                Write-Host "Uninstalled SnapAgent $($pkg.Version)" -ForegroundColor Green
            } catch {
                Write-Host "Uninstall-Package failed for version $($pkg.Version) (expected, will try msiexec)" -ForegroundColor DarkGray
            }
        }
        Write-Progress -Activity "Uninstalling SnapAgent packages" -Completed
    }

    # Dynamically find and uninstall using msiexec with all SnapAgent GUIDs
    Write-Host "Searching for SnapAgent installations in registry..." -ForegroundColor Yellow
    $uninstallPaths = @(
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
        "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
    )

    $snapProducts = @()
    foreach ($path in $uninstallPaths) {
        $products = Get-ItemProperty $path -ErrorAction SilentlyContinue | 
            Where-Object { $_.DisplayName -like "*Snap*" -or $_.Publisher -like "*Blackpoint*" }
        $snapProducts += $products
    }

    if ($snapProducts) {
        foreach ($product in $snapProducts) {
            $guid = $product.PSChildName
            $name = $product.DisplayName
            $version = $product.DisplayVersion
            
            Write-Host "Found: $name $version (GUID: $guid)" -ForegroundColor Yellow
            Write-Host "Uninstalling via msiexec..." -ForegroundColor Yellow
            
            try {
                # Use msiexec for silent uninstall
                $uninstallArgs = "/x $guid /qn /norestart"
                $process = Start-Process "msiexec.exe" -ArgumentList $uninstallArgs -Wait -PassThru -NoNewWindow
                
                if ($process.ExitCode -eq 0) {
                    Write-Host "Successfully uninstalled: $name $version" -ForegroundColor Green
                } else {
                    Write-Warning "msiexec returned exit code $($process.ExitCode) for $name $version"
                }
            } catch {
                Write-Warning "Failed to uninstall $name $version via msiexec: $_"
            }
        }
    }

    # Uninstall ZTAC packages
    Write-Host "Removing any existing versions of ZTAC..." -ForegroundColor Green
    try {
        Uninstall-Package -Name "ZTAC" -AllVersions -Force -ErrorAction Stop
        Write-Host "Uninstalled ZTAC packages" -ForegroundColor Green
    } catch {
        Write-Host "No ZTAC packages found or uninstall failed (not critical)" -ForegroundColor DarkGray
    }

    # Remove folders
    Write-Host "Removing program directories..." -ForegroundColor Green
    foreach ($folder in $snapFolders + $ztacFolders) {
        if (Test-Path $folder) {
            try {
                Remove-Item -Path $folder -Recurse -Force
                Write-Host "Removed folder: $folder" -ForegroundColor Green
            } catch {
                Write-Warning "Failed to remove folder: $folder. Error: $_"
            }
        }
    }

    # Clean up registry keys
    Write-Host "Cleaning up registry keys..." -ForegroundColor Green

    # Build list of all discovered product GUIDs plus hardcoded known keys
    $registryKeys = @(
        "HKLM:\SOFTWARE\Classes\Installer\Features\0E1D3F0C2B974FA4AA0418F12B055384",
        "HKLM:\SOFTWARE\Classes\Installer\Products\0E1D3F0C2B974FA4AA0418F12B055384",
        "HKLM:\SOFTWARE\Classes\Installer\Products\0E1D3F0C2B974FA4AA0418F12B055384\SourceList",
        "HKLM:\SOFTWARE\Classes\Installer\Products\0E1D3F0C2B974FA4AA0418F12B055384\SourceList\Media",
        "HKLM:\SOFTWARE\Classes\Installer\Products\0E1D3F0C2B974FA4AA0418F12B055384\SourceList\Net",
        "HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes\7CF0653F8B24F2647B3A70510A96BEE6",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\7CF0653F8B24F2647B3A70510A96BEE6",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\08C8C87010175A141912F6695F06EB95",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\5E3D36BBC4ADCA749AC6CC3774478B04",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\74A044CACC826754BB48542EA5681E4C",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A3129D8FE202CCF47B233E82C70367D2",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A73F059633BC8314597EE7F81A662796",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C0016A60CBED93E41900FCBD4BC10AB4",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\DB4ABEA1DA4832048BCCF78860ADA944",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\F1AB931B4E8A02A4F8E5F828409E4DD1",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\F81ECEA5C9A7CA3409D05D38A602B11C",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\0E1D3F0C2B974FA4AA0418F12B055384",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\0E1D3F0C2B974FA4AA0418F12B055384\Features",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\0E1D3F0C2B974FA4AA0418F12B055384\InstallProperties",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\0E1D3F0C2B974FA4AA0418F12B055384\Patches",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\0E1D3F0C2B974FA4AA0418F12B055384\Usage",
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{C0F3D1E0-79B2-4AF4-AA40-811FB2503548}",
        "HKLM:\SYSTEM\CurrentControlSet\Services\ZTAC",
        "HKLM:\SYSTEM\CurrentControlSet\Services\ZtacFltr"
    )

    # Add registry keys for any discovered product GUIDs
    if ($snapProducts) {
        foreach ($product in $snapProducts) {
            $guid = $product.PSChildName
            $registryKeys += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$guid"
            $registryKeys += "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$guid"
        }
    }

    $i = 0
    foreach ($key in $registryKeys) {
        Write-Progress -Activity "Cleaning up registry keys" -Status "Processing key $($i + 1) of $($registryKeys.Count)" -PercentComplete (($i / $registryKeys.Count) * 100)

        if (Test-Path $key) {
            try {
                # Use a job with timeout to prevent hanging
                $job = Start-Job -ScriptBlock {
                    param($regKey)
                    Remove-Item -Path $regKey -Recurse -Force -ErrorAction Stop
                } -ArgumentList $key
                
                # Wait up to 10 seconds for registry deletion
                $completed = Wait-Job $job -Timeout 10
                
                if ($completed) {
                    Receive-Job $job -ErrorAction SilentlyContinue
                    Remove-Job $job
                    Write-Host "Removed registry key: $key" -ForegroundColor Green
                } else {
                    Stop-Job $job
                    Remove-Job $job
                    $errorMsg = "TIMEOUT: Registry key deletion hung after 10 seconds: $key - This may indicate the service is still running or a reboot is needed."
                    $criticalErrors += $errorMsg
                    Write-Error $errorMsg
                    $rebootRequired = $true
                }
            } catch {
                $errorMsg = "Failed to remove registry key: $key. Error: $_"
                Write-Warning $errorMsg
                if ($_.Exception.Message -match "in use|cannot access") {
                    $rebootRequired = $true
                }
            }
        }
        $i++
    }
    Write-Progress -Activity "Cleaning up registry keys" -Completed -Status "All registry keys processed."

    # Final verification
    Write-Host "`nVerifying cleanup..." -ForegroundColor Cyan
    $remainingPackages = Get-Package -Name "*Snap*" -ErrorAction SilentlyContinue
    if ($remainingPackages) {
        Write-Warning "The following packages are still installed:"
        $remainingPackages | Format-Table Name, Version, ProviderName
    } else {
        Write-Host "No SnapAgent packages found - cleanup successful!" -ForegroundColor Green
    }

    # Check for remaining folders
    $remainingFolders = $snapFolders + $ztacFolders | Where-Object { Test-Path $_ }
    if ($remainingFolders) {
        Write-Warning "The following folders still exist:"
        $remainingFolders | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
        $rebootRequired = $true
    } else {
        Write-Host "All program folders removed successfully!" -ForegroundColor Green
    }

    # Check for remaining services
    $remainingServices = Get-Service $allServices -ErrorAction SilentlyContinue | Where-Object { $_.Status -ne 'Stopped' }
    if ($remainingServices) {
        Write-Error "CRITICAL: The following services are still running:"
        $remainingServices | ForEach-Object { 
            Write-Host " $($_.Name) - Status: $($_.Status)" -ForegroundColor Red 
            $criticalErrors += "Service still running: $($_.Name)"
        }
        $rebootRequired = $true
    }

    Write-Host "`n========================================" -ForegroundColor Cyan
    if ($criticalErrors.Count -gt 0) {
        Write-Host "CLEANUP COMPLETED WITH ERRORS" -ForegroundColor Red
        Write-Host "========================================" -ForegroundColor Cyan
        Write-Host "`nCritical errors encountered:" -ForegroundColor Red
        $criticalErrors | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
        
        if ($rebootRequired) {
            Write-Host "`n⚠️ A SYSTEM REBOOT IS REQUIRED to complete the removal." -ForegroundColor Yellow -BackgroundColor DarkRed
            Write-Host "Some services or files are locked and can only be removed after restart." -ForegroundColor Yellow
        }
        
        throw "Remove-SnapAgent encountered critical errors. See above for details."
    } else {
        Write-Host "CLEANUP COMPLETED SUCCESSFULLY" -ForegroundColor Green
        Write-Host "========================================" -ForegroundColor Cyan
        
        if ($rebootRequired) {
            Write-Host "`n⚠️ A system reboot is recommended to complete the removal." -ForegroundColor Yellow
        }
    }
}

# Export the function
Export-ModuleMember -Function Remove-SnapAgent