Uninstall-MakeMeAdminCLI.ps1

#Requires -Version 5.1
#Requires -RunAsAdministrator
<#
.SYNOPSIS
    Uninstalls the MakeMeAdminCLI module and service.

.DESCRIPTION
    This script performs a complete uninstallation of MakeMeAdminCLI:
    - Stops and removes the scheduled task service
    - Optionally removes active elevated users from the Administrators group
    - Removes the Windows Event Log source
    - Removes the state directory (unless -KeepConfig is specified)
    - Removes the module from the system-wide PowerShell modules directory

    This script must be run as Administrator.

.PARAMETER KeepConfig
    If specified, keeps the configuration and state files in ProgramData.
    Useful for reinstallation scenarios.

.PARAMETER Force
    Skips confirmation prompts for removing active users.

.EXAMPLE
    .\Uninstall-MakeMeAdminCLI.ps1

    Performs a standard uninstallation with prompts.

.EXAMPLE
    .\Uninstall-MakeMeAdminCLI.ps1 -KeepConfig

    Uninstalls but keeps the configuration for future reinstallation.

.EXAMPLE
    .\Uninstall-MakeMeAdminCLI.ps1 -Force

    Uninstalls without prompting for confirmation.

.NOTES
    Author: MakeMeAdminCLI
    Version: 1.0.0
#>


[CmdletBinding()]
param(
    [switch]$KeepConfig,
    [switch]$Force
)

$ErrorActionPreference = 'Stop'

#region Helper Functions

function Write-Status {
    param(
        [string]$Status,
        [string]$Message,
        [ConsoleColor]$Color = [ConsoleColor]::White
    )

    $statusColor = switch ($Status) {
        'OK' { [ConsoleColor]::Green }
        'WARN' { [ConsoleColor]::Yellow }
        'FAIL' { [ConsoleColor]::Red }
        'INFO' { [ConsoleColor]::Cyan }
        'SKIP' { [ConsoleColor]::DarkGray }
        default { [ConsoleColor]::White }
    }

    Write-Host "[" -NoNewline
    Write-Host $Status -ForegroundColor $statusColor -NoNewline
    Write-Host "] " -NoNewline
    Write-Host $Message -ForegroundColor $Color
}

function Test-IsAdministrator {
    $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object System.Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Get-ActiveElevatedUsers {
    param(
        [string]$StateFilePath
    )

    if (-not (Test-Path $StateFilePath)) {
        return @()
    }

    try {
        $state = Get-Content -Path $StateFilePath -Raw | ConvertFrom-Json
        if ($state.ActiveUsers) {
            return @($state.ActiveUsers)
        }
    }
    catch {
        Write-Verbose "Could not read state file: $($_.Exception.Message)"
    }

    return @()
}

function Remove-UserFromAdministrators {
    param(
        [string]$Username
    )

    try {
        # Get the local Administrators group by SID (language-independent)
        $adminGroupSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
        $adminGroup = $adminGroupSid.Translate([System.Security.Principal.NTAccount]).Value
        $localAdminGroup = [ADSI]"WinNT://./$($adminGroup.Split('\')[-1]),group"

        # Find and remove the user
        $members = @($localAdminGroup.Invoke("Members") | ForEach-Object {
            $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
        })

        $usernameShort = $Username -replace '^[^\\]+\\', ''

        if ($members -contains $usernameShort) {
            $localAdminGroup.Remove("WinNT://$Username")
            return @{ Success = $true; Message = "Removed from Administrators group" }
        }
        else {
            return @{ Success = $true; Message = "Not currently a member" }
        }
    }
    catch {
        return @{ Success = $false; Message = $_.Exception.Message }
    }
}

#endregion

#region Main Script

# Verify running as Administrator
if (-not (Test-IsAdministrator)) {
    Write-Host ""
    Write-Host "ERROR: This script must be run as Administrator." -ForegroundColor Red
    Write-Host ""
    Write-Host "Please run PowerShell as Administrator and try again." -ForegroundColor Yellow
    Write-Host ""
    exit 1
}

# Define paths
$ModuleName = "MakeMeAdminCLI"
$TargetModulePath = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\$ModuleName"
$StateDirectory = Join-Path $env:ProgramData $ModuleName
$StateFilePath = Join-Path $StateDirectory "state.json"
$TaskName = "MakeMeAdminCLI-Service"
$TaskPath = "\Microsoft\Windows\MakeMeAdminCLI\"
$EventLogSource = "MakeMeAdminCLI"

Write-Host ""
Write-Host "Uninstalling MakeMeAdminCLI..." -ForegroundColor Cyan
Write-Host "================================" -ForegroundColor Cyan
Write-Host ""

# Confirm uninstallation
if (-not $Force) {
    $response = Read-Host "Are you sure you want to uninstall MakeMeAdminCLI? (Y/N)"
    if ($response -notin @('Y', 'y', 'Yes', 'yes')) {
        Write-Host "Uninstallation cancelled." -ForegroundColor Yellow
        exit 0
    }
    Write-Host ""
}

# Step 1: Stop and remove the scheduled task
try {
    $existingTask = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue

    if ($existingTask) {
        # Stop the task if running
        if ($existingTask.State -eq 'Running') {
            Stop-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue
            Start-Sleep -Seconds 2
        }

        # Unregister the task
        Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$false

        # Try to remove the task folder (if empty)
        try {
            $schedule = New-Object -ComObject Schedule.Service
            $schedule.Connect()
            $folder = $schedule.GetFolder($TaskPath)
            $tasks = $folder.GetTasks(0)
            if ($tasks.Count -eq 0) {
                $parentPath = Split-Path $TaskPath -Parent
                if ($parentPath -eq '\') { $parentPath = '\' }
                $parentFolder = $schedule.GetFolder($parentPath)
                $folderName = Split-Path $TaskPath -Leaf
                $parentFolder.DeleteFolder($folderName, 0)
            }
        }
        catch {
            # Ignore errors removing the folder
        }

        Write-Status -Status "OK" -Message "Stopped and removed scheduled task"
    }
    else {
        Write-Status -Status "SKIP" -Message "Scheduled task not found"
    }
}
catch {
    Write-Status -Status "WARN" -Message "Could not remove scheduled task: $($_.Exception.Message)"
}

# Step 2: Remove active elevated users from Administrators group
try {
    $activeUsers = Get-ActiveElevatedUsers -StateFilePath $StateFilePath

    if ($activeUsers.Count -gt 0) {
        Write-Host ""
        Write-Host "Found $($activeUsers.Count) active elevated user(s):" -ForegroundColor Yellow

        foreach ($user in $activeUsers) {
            Write-Host " - $($user.Username) (expires: $($user.ExpiresAt))" -ForegroundColor Gray
        }

        Write-Host ""

        $removeUsers = $true
        if (-not $Force) {
            $response = Read-Host "Remove these users from the Administrators group? (Y/N)"
            $removeUsers = $response -in @('Y', 'y', 'Yes', 'yes')
        }

        if ($removeUsers) {
            foreach ($user in $activeUsers) {
                $result = Remove-UserFromAdministrators -Username $user.Username
                if ($result.Success) {
                    Write-Status -Status "OK" -Message "Removed '$($user.Username)' from Administrators"
                }
                else {
                    Write-Status -Status "WARN" -Message "Could not remove '$($user.Username)': $($result.Message)"
                }
            }
        }
        else {
            Write-Status -Status "SKIP" -Message "Keeping elevated users in Administrators group"
        }
    }
    else {
        Write-Status -Status "OK" -Message "No active elevated users to remove"
    }
}
catch {
    Write-Status -Status "WARN" -Message "Could not check active users: $($_.Exception.Message)"
}

# Step 3: Remove Event Log source
try {
    if ([System.Diagnostics.EventLog]::SourceExists($EventLogSource)) {
        [System.Diagnostics.EventLog]::DeleteEventSource($EventLogSource)
        Write-Status -Status "OK" -Message "Removed Event Log source '$EventLogSource'"
    }
    else {
        Write-Status -Status "SKIP" -Message "Event Log source not found"
    }
}
catch {
    Write-Status -Status "WARN" -Message "Could not remove Event Log source: $($_.Exception.Message)"
}

# Step 4: Remove state directory
if ($KeepConfig) {
    Write-Status -Status "SKIP" -Message "Keeping configuration at $StateDirectory"
}
else {
    try {
        if (Test-Path $StateDirectory) {
            Remove-Item -Path $StateDirectory -Recurse -Force
            Write-Status -Status "OK" -Message "Removed state directory at $StateDirectory"
        }
        else {
            Write-Status -Status "SKIP" -Message "State directory not found"
        }
    }
    catch {
        Write-Status -Status "WARN" -Message "Could not remove state directory: $($_.Exception.Message)"
    }
}

# Step 5: Remove module from Program Files
try {
    if (Test-Path $TargetModulePath) {
        Remove-Item -Path $TargetModulePath -Recurse -Force
        Write-Status -Status "OK" -Message "Removed module from $TargetModulePath"
    }
    else {
        Write-Status -Status "SKIP" -Message "Module not found at $TargetModulePath"
    }
}
catch {
    Write-Status -Status "WARN" -Message "Could not remove module: $($_.Exception.Message)"
}

# Uninstallation complete
Write-Host ""
Write-Host "Uninstallation complete." -ForegroundColor Green
Write-Host ""

if ($KeepConfig) {
    Write-Host "Configuration files were preserved at: $StateDirectory" -ForegroundColor Yellow
    Write-Host "To reinstall, run Install-MakeMeAdminCLI.ps1" -ForegroundColor Gray
}

Write-Host ""

#endregion