Initial commit: PrintCleaner tool with GUI, Admin check, and custom icon
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Binaries
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.syso
|
||||||
|
|
||||||
|
# Temporary Files
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
PrintCleaner_Temp.ps1
|
||||||
|
|
||||||
|
# OS specific
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
BIN
3022251.png
Normal file
BIN
3022251.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
20
Launch_PrintCleaner.bat
Normal file
20
Launch_PrintCleaner.bat
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
:: Check for permissions
|
||||||
|
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||||
|
if '%errorlevel%' NEQ '0' (
|
||||||
|
echo Requesting administrative privileges...
|
||||||
|
goto UACPrompt
|
||||||
|
) else ( goto gotAdmin )
|
||||||
|
|
||||||
|
:UACPrompt
|
||||||
|
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
|
||||||
|
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
|
||||||
|
"%temp%\getadmin.vbs"
|
||||||
|
exit /B
|
||||||
|
|
||||||
|
:gotAdmin
|
||||||
|
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
|
||||||
|
pushd "%~dp0"
|
||||||
|
|
||||||
|
:: Launch the PowerShell script
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\PrintCleaner.ps1"
|
||||||
19
PrintCleaner.manifest
Normal file
19
PrintCleaner.manifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="PrintCleaner.App"
|
||||||
|
version="1.0.0.0"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel
|
||||||
|
level="requireAdministrator"
|
||||||
|
uiAccess="false"
|
||||||
|
/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
245
PrintCleaner.ps1
Normal file
245
PrintCleaner.ps1
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
PrintCleaner - A Terminal Utility to clean up printers and print queues.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script provides a text-based interface to:
|
||||||
|
1. Remove all printers and drivers (preserving Microsoft defaults).
|
||||||
|
2. Clear the Windows Print Spooler queue.
|
||||||
|
Requires Administrator privileges.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
File Name : PrintCleaner.ps1
|
||||||
|
Author : Gemini CLI
|
||||||
|
Prerequisite : Run as Administrator
|
||||||
|
#>
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
$Host.UI.RawUI.WindowTitle = "PrintCleaner"
|
||||||
|
$global:running = $true
|
||||||
|
$menuOptions = @("List Installed Printers", "Clean Print Queue", "Remove All Printers & Drivers", "Exit")
|
||||||
|
$selectionIndex = 0
|
||||||
|
|
||||||
|
# --- Helper Functions ---
|
||||||
|
|
||||||
|
function Test-Administrator {
|
||||||
|
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$principal = [Security.Principal.WindowsPrincipal]$currentUser
|
||||||
|
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-Header {
|
||||||
|
Clear-Host
|
||||||
|
Write-Host " ____ _ _ ____ _ " -ForegroundColor Cyan
|
||||||
|
Write-Host " | _ \ _ __(_)_ __ | |_/ ___| | ___ __ _ _ __ ___ _ __ " -ForegroundColor Cyan
|
||||||
|
Write-Host " | |_) | '__| | '_ \| __| | | |/ _ \/ _' | '_ \ / _ \ '__|" -ForegroundColor Cyan
|
||||||
|
Write-Host " | __/| | | | | | | |_| |___| | __/ (_| | | | | __/ | " -ForegroundColor Cyan
|
||||||
|
Write-Host " |_| |_| |_|_| |_|\__|\____|_|\___|\__,_|_| |_|\___|_| " -ForegroundColor Cyan
|
||||||
|
Write-Host " " -ForegroundColor Cyan
|
||||||
|
Write-Host "Use UP/DOWN arrows to navigate, ENTER to select." -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-Menu {
|
||||||
|
param (
|
||||||
|
[int]$SelectedIndex
|
||||||
|
)
|
||||||
|
Show-Header
|
||||||
|
for ($i = 0; $i -lt $menuOptions.Count; $i++) {
|
||||||
|
if ($i -eq $SelectedIndex) {
|
||||||
|
Write-Host " > $($menuOptions[$i])" -ForegroundColor Green -BackgroundColor DarkGray
|
||||||
|
} else {
|
||||||
|
Write-Host " $($menuOptions[$i])" -ForegroundColor White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-Key {
|
||||||
|
Write-Host "`nPress ENTER to return to menu..." -ForegroundColor DarkGray
|
||||||
|
while ($true) {
|
||||||
|
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
|
if ($key.VirtualKeyCode -eq 13) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Action Functions ---
|
||||||
|
|
||||||
|
function Invoke-ListPrinters {
|
||||||
|
Show-Header
|
||||||
|
Write-Host "INSTALLED PRINTERS" -ForegroundColor Cyan
|
||||||
|
Write-Host "------------------" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
try {
|
||||||
|
$printers = Get-Printer -ErrorAction SilentlyContinue | Sort-Object Name
|
||||||
|
|
||||||
|
if ($printers) {
|
||||||
|
$printers | Format-Table -Property Name, DriverName, PortName -AutoSize | Out-String | Write-Host -ForegroundColor White
|
||||||
|
} else {
|
||||||
|
Write-Host "No printers found." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "[ERROR] Could not list printers: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
Wait-Key
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CleanQueue {
|
||||||
|
Show-Header
|
||||||
|
Write-Host "Cleaning Print Queue..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Host "[-] Stopping Print Spooler service..."
|
||||||
|
Stop-Service -Name "Spooler" -Force -ErrorAction Stop
|
||||||
|
|
||||||
|
$spoolPath = "$env:SystemRoot\System32\spool\PRINTERS\*"
|
||||||
|
Write-Host "[-] Deleting spool files in $spoolPath..."
|
||||||
|
Remove-Item -Path $spoolPath -Force -Recurse -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Host "[-] Restarting Print Spooler service..."
|
||||||
|
Start-Service -Name "Spooler" -ErrorAction Stop
|
||||||
|
|
||||||
|
Write-Host "`n[SUCCESS] Print queue cleared successfully." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "`n[ERROR] Failed to clean queue: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
Wait-Key
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-RemovePrinters {
|
||||||
|
Show-Header
|
||||||
|
Write-Host "WARNING: This will remove ALL printers and drivers (except MS defaults)." -ForegroundColor Red
|
||||||
|
Write-Host "Are you sure? (Y/N): " -NoNewline -ForegroundColor Yellow
|
||||||
|
$confirm = Read-Host
|
||||||
|
if ($confirm -notmatch "^[Yy]$") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nStarting Cleanup..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$removedPrinters = @()
|
||||||
|
$removedDrivers = @()
|
||||||
|
|
||||||
|
# 1. Remove Printers
|
||||||
|
try {
|
||||||
|
$printers = Get-Printer -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
$_.Name -ne "Microsoft Print to PDF" -and
|
||||||
|
$_.Name -ne "Microsoft XPS Document Writer" -and
|
||||||
|
$_.Name -ne "OneNote for Windows 10"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($printers) {
|
||||||
|
foreach ($p in $printers) {
|
||||||
|
Write-Host "[-] Removing Printer: $($p.Name)"
|
||||||
|
try {
|
||||||
|
Remove-Printer -Name $p.Name -ErrorAction Stop
|
||||||
|
$removedPrinters += $p.Name
|
||||||
|
} catch {
|
||||||
|
Write-Host " [!] Failed to remove $($p.Name): $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "[-] No printers found to remove." -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "[!] Error accessing printers: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Remove Drivers
|
||||||
|
# Note: Drivers can only be removed if not in use.
|
||||||
|
# Sometimes requires restarting spooler or system to fully release.
|
||||||
|
Write-Host "`nAttempting to remove unused drivers..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$drivers = Get-PrinterDriver -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
$_.Name -notmatch "Microsoft" -and
|
||||||
|
$_.Provider -ne "Microsoft"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($drivers) {
|
||||||
|
foreach ($d in $drivers) {
|
||||||
|
Write-Host "[-] Removing Driver: $($d.Name)"
|
||||||
|
try {
|
||||||
|
Remove-PrinterDriver -Name $d.Name -ErrorAction Stop
|
||||||
|
$removedDrivers += $d.Name
|
||||||
|
} catch {
|
||||||
|
Write-Host " [!] Locked or in use (skipping): $($d.Name)" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "[-] No third-party drivers found." -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "[!] Error accessing drivers: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Summary Report ---
|
||||||
|
Show-Header
|
||||||
|
Write-Host "SUMMARY REPORT" -ForegroundColor Cyan
|
||||||
|
Write-Host "--------------" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if ($removedPrinters.Count -gt 0) {
|
||||||
|
Write-Host "`nRemoved Printers:" -ForegroundColor Green
|
||||||
|
foreach ($p in $removedPrinters) {
|
||||||
|
Write-Host " [+] $p" -ForegroundColor White
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "`nNo printers were removed." -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($removedDrivers.Count -gt 0) {
|
||||||
|
Write-Host "`nRemoved Drivers:" -ForegroundColor Green
|
||||||
|
foreach ($d in $removedDrivers) {
|
||||||
|
Write-Host " [+] $d" -ForegroundColor White
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "`nNo drivers were removed." -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
Wait-Key
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main Loop ---
|
||||||
|
|
||||||
|
if (-not (Test-Administrator)) {
|
||||||
|
Write-Host "ERROR: This script requires Administrator privileges." -ForegroundColor Red
|
||||||
|
Write-Host "Please right-click and 'Run with PowerShell' as Administrator."
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Hide cursor for cleaner look
|
||||||
|
$originalCursorSize = $Host.UI.RawUI.CursorSize
|
||||||
|
$Host.UI.RawUI.CursorSize = 0
|
||||||
|
} catch {
|
||||||
|
# Ignore if console doesn't support cursor hiding
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($global:running) {
|
||||||
|
Show-Menu -SelectedIndex $selectionIndex
|
||||||
|
|
||||||
|
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
|
|
||||||
|
switch ($key.VirtualKeyCode) {
|
||||||
|
38 { # Up Arrow
|
||||||
|
if ($selectionIndex -gt 0) { $selectionIndex-- }
|
||||||
|
}
|
||||||
|
40 { # Down Arrow
|
||||||
|
if ($selectionIndex -lt ($menuOptions.Count - 1)) { $selectionIndex++ }
|
||||||
|
}
|
||||||
|
13 { # Enter
|
||||||
|
switch ($selectionIndex) {
|
||||||
|
0 { Invoke-ListPrinters }
|
||||||
|
1 { Invoke-CleanQueue }
|
||||||
|
2 { Invoke-RemovePrinters }
|
||||||
|
3 { $global:running = $false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
27 { # Escape
|
||||||
|
$global:running = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Clear-Host
|
||||||
|
try { $Host.UI.RawUI.CursorSize = 25 } catch {} # Restore cursor
|
||||||
|
Write-Host "Exiting PrintCleaner. Goodbye!" -ForegroundColor Cyan
|
||||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module printcleaner
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.11
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/akavel/rsrc v0.10.2 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
)
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
|
||||||
|
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
102
main.go
Normal file
102
main.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed PrintCleaner.ps1
|
||||||
|
var scriptContent []byte
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if !amIAdmin() {
|
||||||
|
runMeElevated()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary file for the script
|
||||||
|
tempDir := os.TempDir()
|
||||||
|
tempFile := filepath.Join(tempDir, "PrintCleaner_Temp.ps1")
|
||||||
|
|
||||||
|
err := os.WriteFile(tempFile, scriptContent, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating temp file: %v\n", err)
|
||||||
|
fmt.Println("Press Enter to exit...")
|
||||||
|
fmt.Scanln()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFile)
|
||||||
|
|
||||||
|
// Launch PowerShell with the script
|
||||||
|
cmd := exec.Command("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", tempFile)
|
||||||
|
|
||||||
|
// Connect IO
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
// Only show error if it wasn't just a clean exit from the script
|
||||||
|
// (The script manages its own UI pauses)
|
||||||
|
fmt.Printf("Program exited: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func amIAdmin() bool {
|
||||||
|
var sid *windows.SID
|
||||||
|
|
||||||
|
// Although this looks huge, we are just creating the SID for the Builtin Administrators group.
|
||||||
|
// WinBuiltinAdministratorsSid = 26
|
||||||
|
err := windows.AllocateAndInitializeSid(
|
||||||
|
&windows.SECURITY_NT_AUTHORITY,
|
||||||
|
2,
|
||||||
|
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||||
|
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
&sid,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer windows.FreeSid(sid)
|
||||||
|
|
||||||
|
// This token check is the standard way to see if the current process has the admin token.
|
||||||
|
token := windows.Token(0)
|
||||||
|
member, err := token.IsMember(sid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMeElevated() {
|
||||||
|
verb := "runas"
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
args := strings.Join(os.Args[1:], " ")
|
||||||
|
|
||||||
|
verbPtr, _ := syscall.UTF16PtrFromString(verb)
|
||||||
|
exePtr, _ := syscall.UTF16PtrFromString(exe)
|
||||||
|
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
|
||||||
|
argsPtr, _ := syscall.UTF16PtrFromString(args)
|
||||||
|
|
||||||
|
var showCmd int32 = 1 // SW_NORMAL
|
||||||
|
|
||||||
|
// Use ShellExecute to relaunch with "runas" (Admin)
|
||||||
|
err := windows.ShellExecute(0, verbPtr, exePtr, argsPtr, cwdPtr, showCmd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error requesting Administrator privileges:", err)
|
||||||
|
fmt.Println("Press Enter to exit...")
|
||||||
|
fmt.Scanln()
|
||||||
|
}
|
||||||
|
// Exit the current non-admin process
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user