Public/Uninstall-MakeMeAdminService.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Uninstalls the MakeMeAdminCLI background service. .DESCRIPTION The Uninstall-MakeMeAdminService cmdlet removes all service components that were configured by Install-MakeMeAdminService. .NOTES Author: MakeMeAdminCLI Version: 1.1.0 #> function Uninstall-MakeMeAdminService { <# .SYNOPSIS Uninstalls the MakeMeAdminCLI background service. .DESCRIPTION Removes the service components configured by Install-MakeMeAdminService: - Stops and unregisters the MakeMeAdminCLI-Service scheduled task - Removes the Task Scheduler folder if empty - Optionally removes active elevated users from the Administrators group - Removes the Windows Event Log source - Removes the state directory (config.json, state.json, removal scripts) This cmdlet does NOT remove the module itself from Program Files. Use Uninstall-Module MakeMeAdminCLI for that. This cmdlet requires administrator privileges. .PARAMETER KeepConfig If specified, preserves the configuration and state directory at $env:ProgramData\MakeMeAdminCLI\. Useful when reinstalling. .PARAMETER RemoveActiveUsers If specified, removes any currently elevated users from the local Administrators group before uninstalling. Without this switch, active elevated users retain their admin rights. .PARAMETER Force Skips the confirmation prompt. .OUTPUTS PSCustomObject with uninstallation status for each component: - ScheduledTaskStopped: Boolean - ScheduledTaskRemoved: Boolean - TaskFolderRemoved: Boolean - ActiveUsersRemoved: Boolean or 'Skipped' - EventLogSourceRemoved: Boolean - StateDirectoryRemoved: Boolean or 'Kept' - OverallSuccess: Boolean .EXAMPLE Uninstall-MakeMeAdminService Uninstalls the service with confirmation prompt. .EXAMPLE Uninstall-MakeMeAdminService -Force Uninstalls the service without prompting for confirmation. .EXAMPLE Uninstall-MakeMeAdminService -KeepConfig Uninstalls the service but preserves config and state files. .EXAMPLE Uninstall-MakeMeAdminService -RemoveActiveUsers -Force Uninstalls and removes any currently elevated users from the Administrators group. .LINK Install-MakeMeAdminService Test-MakeMeAdminService #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param( [switch]$KeepConfig, [switch]$RemoveActiveUsers, [switch]$Force ) $ErrorActionPreference = 'Stop' # --- Constants --- $ModuleName = 'MakeMeAdminCLI' $TaskName = 'MakeMeAdminCLI-Service' $TaskPath = '\Microsoft\Windows\MakeMeAdminCLI\' $EventLogSource = 'MakeMeAdminCLI' $StateDirectory = Join-Path $env:ProgramData $ModuleName $StateFilePath = Join-Path $StateDirectory 'state.json' # --- Result tracker --- $result = [ordered]@{ ScheduledTaskStopped = $false ScheduledTaskRemoved = $false TaskFolderRemoved = $false ActiveUsersRemoved = 'Skipped' EventLogSourceRemoved = $false StateDirectoryRemoved = 'Kept' OverallSuccess = $false } #region Elevation Check if (-not (Test-IsElevated)) { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( [System.UnauthorizedAccessException]::new( 'Uninstall-MakeMeAdminService must be run from an elevated (Administrator) PowerShell session.' ), 'ElevationRequired', [System.Management.Automation.ErrorCategory]::PermissionDenied, $null ) ) return } #endregion # If -Force was specified, override ConfirmPreference so ShouldProcess passes if ($Force) { $ConfirmPreference = 'None' } if (-not $PSCmdlet.ShouldProcess('MakeMeAdminCLI service', 'Uninstall')) { return } #region 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') { Write-Verbose 'Stopping scheduled task...' Stop-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 $result.ScheduledTaskStopped = $true } else { Write-Verbose 'Scheduled task was not running' $result.ScheduledTaskStopped = $true } # Unregister the task Write-Verbose 'Unregistering scheduled task...' Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$false $result.ScheduledTaskRemoved = $true Write-Verbose 'Scheduled task removed' } else { Write-Verbose 'Scheduled task not found (already removed)' $result.ScheduledTaskStopped = $true $result.ScheduledTaskRemoved = $true } } catch { Write-Warning "Could not remove scheduled task: $($_.Exception.Message)" } #endregion #region Step 2: Remove task folder if empty try { $schedule = New-Object -ComObject Schedule.Service $schedule.Connect() try { $folder = $schedule.GetFolder($TaskPath) $tasks = $folder.GetTasks(0) if ($tasks.Count -eq 0) { $parentPath = Split-Path $TaskPath.TrimEnd('\') -Parent if ([string]::IsNullOrEmpty($parentPath) -or $parentPath -eq '\') { $parentPath = '\' } $parentFolder = $schedule.GetFolder($parentPath) $folderName = Split-Path $TaskPath.TrimEnd('\') -Leaf $parentFolder.DeleteFolder($folderName, 0) $result.TaskFolderRemoved = $true Write-Verbose "Removed empty task folder: $TaskPath" } else { Write-Verbose "Task folder not empty ($($tasks.Count) remaining tasks), leaving in place" $result.TaskFolderRemoved = $false } } catch { # Folder may not exist Write-Verbose "Task folder not found or already removed" $result.TaskFolderRemoved = $true } } catch { Write-Verbose "Could not check task folder: $($_.Exception.Message)" } #endregion #region Step 3: Remove active elevated users (optional) if ($RemoveActiveUsers) { try { $activeUsers = @() if (Test-Path $StateFilePath) { try { $state = Get-Content -Path $StateFilePath -Raw | ConvertFrom-Json if ($state.ActiveUsers) { $activeUsers = @($state.ActiveUsers) } } catch { Write-Verbose "Could not read state file: $($_.Exception.Message)" } } if ($activeUsers.Count -gt 0) { Write-Verbose "Found $($activeUsers.Count) active elevated user(s)" # 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" foreach ($user in $activeUsers) { try { $members = @($localAdminGroup.Invoke('Members') | ForEach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) }) $usernameShort = $user.Username -replace '^[^\\]+\\', '' if ($members -contains $usernameShort) { $localAdminGroup.Remove("WinNT://$($user.Username)") Write-Verbose "Removed '$($user.Username)' from Administrators group" } else { Write-Verbose "'$($user.Username)' is not currently in Administrators group" } } catch { Write-Warning "Could not remove '$($user.Username)' from Administrators: $($_.Exception.Message)" } } $result.ActiveUsersRemoved = $true } else { Write-Verbose 'No active elevated users to remove' $result.ActiveUsersRemoved = $true } } catch { Write-Warning "Could not process active users: $($_.Exception.Message)" $result.ActiveUsersRemoved = $false } } #endregion #region Step 4: Remove Event Log source try { if ([System.Diagnostics.EventLog]::SourceExists($EventLogSource)) { [System.Diagnostics.EventLog]::DeleteEventSource($EventLogSource) Write-Verbose "Removed Event Log source '$EventLogSource'" $result.EventLogSourceRemoved = $true } else { Write-Verbose "Event Log source '$EventLogSource' not found" $result.EventLogSourceRemoved = $true } } catch { Write-Warning "Could not remove Event Log source: $($_.Exception.Message)" } #endregion #region Step 5: Remove state directory if ($KeepConfig) { Write-Verbose "Keeping state directory at $StateDirectory (-KeepConfig specified)" $result.StateDirectoryRemoved = 'Kept' } else { try { if (Test-Path $StateDirectory) { Remove-Item -Path $StateDirectory -Recurse -Force Write-Verbose "Removed state directory: $StateDirectory" $result.StateDirectoryRemoved = $true } else { Write-Verbose 'State directory not found (already removed)' $result.StateDirectoryRemoved = $true } } catch { Write-Warning "Could not remove state directory: $($_.Exception.Message)" $result.StateDirectoryRemoved = $false } } #endregion # Determine overall success $result.OverallSuccess = $result.ScheduledTaskRemoved -and $result.EventLogSourceRemoved return [PSCustomObject]$result } # Export the function Export-ModuleMember -Function 'Uninstall-MakeMeAdminService' |