<# .SYNOPSIS Clears stuck print jobs by stopping the Print Spooler, deleting its queued job files, and starting it again. .DESCRIPTION Reads the real spool folder from the registry (it isn't always C:\Windows\System32\spool\PRINTERS), stops the Spooler service, removes the .SPL/.SHD job files, and restarts the service. With -OlderThanMinutes it leaves recent jobs alone and only clears the old, wedged ones. The spooler is started again even if the cleanup fails partway. Supports -WhatIf. .PARAMETER OlderThanMinutes Only delete job files older than this. 0 (the default) clears the whole queue. .PARAMETER TimeoutSeconds How long to wait for the service to stop or start before giving up. Default: 30. .EXAMPLE .\Clear-PrintSpooler.ps1 -WhatIf .EXAMPLE .\Clear-PrintSpooler.ps1 -OlderThanMinutes 30 #> [CmdletBinding(SupportsShouldProcess)] param( [ValidateRange(0, 10080)] [int]$OlderThanMinutes = 0, [ValidateRange(5, 300)] [int]$TimeoutSeconds = 30 ) $ErrorActionPreference = 'Stop' $printKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Print\Printers' $spoolDir = (Get-ItemProperty -Path $printKey -Name DefaultSpoolDirectory -ErrorAction SilentlyContinue).DefaultSpoolDirectory if (-not $spoolDir) { $spoolDir = Join-Path $env:SystemRoot 'System32\spool\PRINTERS' } Write-Verbose "Spool folder: $spoolDir" $cutoff = (Get-Date).AddMinutes(-$OlderThanMinutes) $jobFiles = @(Get-ChildItem -LiteralPath $spoolDir -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.SPL', '.SHD' -and ($OlderThanMinutes -eq 0 -or $_.LastWriteTime -lt $cutoff) }) $result = [pscustomobject]@{ ComputerName = $env:COMPUTERNAME SpoolFolder = $spoolDir FilesFound = $jobFiles.Count FilesRemoved = 0 SpoolerState = (Get-Service -Name Spooler).Status.ToString() Error = $null } if ($jobFiles.Count -eq 0) { Write-Verbose 'No matching job files. Nothing to clear.' return $result } if (-not $PSCmdlet.ShouldProcess("$($jobFiles.Count) job file(s) in $spoolDir", 'Stop Spooler, delete, restart')) { return $result } $spooler = Get-Service -Name Spooler $wasRunning = $spooler.Status -eq 'Running' # Remember which dependent services were running, so they can be brought back too. $dependents = @($spooler.DependentServices | Where-Object Status -eq 'Running') try { if ($wasRunning) { # -Force also stops anything that depends on the spooler (fax, some vendor print services). Stop-Service -InputObject $spooler -Force -Confirm:$false $spooler.WaitForStatus('Stopped', [timespan]::FromSeconds($TimeoutSeconds)) } foreach ($file in $jobFiles) { try { Remove-Item -LiteralPath $file.FullName -Force -Confirm:$false $result.FilesRemoved++ } catch { Write-Warning "Couldn't delete $($file.Name): $($_.Exception.Message)" } } } catch { $result.Error = $_.Exception.Message } finally { # Whatever happened above, don't leave the machine without a spooler. if ($wasRunning) { try { Start-Service -InputObject $spooler -Confirm:$false $spooler.WaitForStatus('Running', [timespan]::FromSeconds($TimeoutSeconds)) foreach ($svc in $dependents) { Start-Service -InputObject $svc -Confirm:$false -ErrorAction Continue } } catch { $result.Error = "Spooler didn't come back up: $($_.Exception.Message)" } } $spooler.Refresh() $result.SpoolerState = $spooler.Status.ToString() } $result