docs: Update README and documentation for lxs multi-tool across Linux and Windows platforms.
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<#
|
||||
LXS - Disk Cleanup (Windows)
|
||||
Description: Reclaim disk space (temp files, Windows Update cache, recycle
|
||||
bin, Prefetch, error reports, WinSxS component store).
|
||||
Repo: https://git.hyko.cx/hykocx/lxs
|
||||
|
||||
Usage: cleanup.ps1 [-Yes] [-NoUpdateCache] [-NoRecycleBin] [-NoPrefetch]
|
||||
[-NoComponentCleanup] [-Help]
|
||||
#>
|
||||
|
||||
# Load LXS common library (colors, UI helpers, spinner, loggers, guards).
|
||||
# Prefers the sibling ..\lib\common.ps1 (repo checkout or installed layout) and
|
||||
# only hits the network when this script is run standalone.
|
||||
$LxsRawBase = if ($env:LXS_RAW_BASE) { $env:LXS_RAW_BASE } else { 'https://git.hyko.cx/hykocx/lxs/raw/branch/main' }
|
||||
if (-not $env:LXS_RAW_PLATFORM_BASE) { $env:LXS_RAW_PLATFORM_BASE = "$LxsRawBase/windows" }
|
||||
$LxsLibPath = if ($PSScriptRoot) { Join-Path $PSScriptRoot '..\lib\common.ps1' } else { $null }
|
||||
if ($LxsLibPath -and (Test-Path $LxsLibPath)) {
|
||||
. $LxsLibPath
|
||||
} else {
|
||||
try {
|
||||
$LxsLibSource = Invoke-RestMethod -Uri "$env:LXS_RAW_PLATFORM_BASE/lib/common.ps1" -UseBasicParsing -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Error 'Failed to fetch lib/common.ps1'
|
||||
exit 1
|
||||
}
|
||||
. ([scriptblock]::Create($LxsLibSource))
|
||||
}
|
||||
|
||||
$env:LXS_LOG_FILE = Join-Path (Get-LxsTempDir) 'lxs_cleanup.log'
|
||||
|
||||
$LxsOpts = Read-LxsFlags -Arguments $args -Known @(
|
||||
'-Yes', '-y', '-NoUpdateCache', '-NoRecycleBin', '-NoPrefetch',
|
||||
'-NoComponentCleanup', '-Help', '-h'
|
||||
)
|
||||
if (Show-LxsUnknownFlags $LxsOpts) { exit 1 }
|
||||
$Yes = Test-LxsFlag $LxsOpts @('-Yes', '-y')
|
||||
$NoUpdateCache = Test-LxsFlag $LxsOpts @('-NoUpdateCache')
|
||||
$NoRecycleBin = Test-LxsFlag $LxsOpts @('-NoRecycleBin')
|
||||
$NoPrefetch = Test-LxsFlag $LxsOpts @('-NoPrefetch')
|
||||
$NoComponentCleanup = Test-LxsFlag $LxsOpts @('-NoComponentCleanup')
|
||||
$Help = Test-LxsFlag $LxsOpts @('-Help', '-h')
|
||||
|
||||
if ($Help) {
|
||||
Write-Host @'
|
||||
Usage: cleanup.ps1 [options]
|
||||
|
||||
Options:
|
||||
-Yes Skip the confirmation prompt
|
||||
-NoUpdateCache Skip the Windows Update download cache
|
||||
-NoRecycleBin Skip emptying the recycle bin
|
||||
-NoPrefetch Skip the Prefetch folder
|
||||
-NoComponentCleanup Skip DISM /StartComponentCleanup (the slow one)
|
||||
-Help Show this help
|
||||
'@
|
||||
exit 0
|
||||
}
|
||||
|
||||
function Get-LxsFreeBytes {
|
||||
try { return (New-Object System.IO.DriveInfo "$env:SystemDrive\").AvailableFreeSpace } catch { return 0 }
|
||||
}
|
||||
|
||||
function Format-LxsSize {
|
||||
param([double]$Bytes)
|
||||
if ($Bytes -ge 1GB) { return ('{0:N2} GB' -f ($Bytes / 1GB)) }
|
||||
if ($Bytes -ge 1MB) { return ('{0:N1} MB' -f ($Bytes / 1MB)) }
|
||||
return ('{0:N0} KB' -f ($Bytes / 1KB))
|
||||
}
|
||||
|
||||
function Get-LxsFolderSize {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path $Path)) { return 0 }
|
||||
try {
|
||||
$sum = (Get-ChildItem -Path $Path -Recurse -Force -File -ErrorAction SilentlyContinue |
|
||||
Measure-Object -Property Length -Sum).Sum
|
||||
if ($null -eq $sum) { return 0 }
|
||||
return $sum
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
# Delete the *contents* of a folder, never the folder itself. Files locked by a
|
||||
# running process are skipped silently — that is expected on a live system.
|
||||
function Clear-LxsFolderContents {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][string]$Label
|
||||
)
|
||||
if (-not (Test-Path $Path)) {
|
||||
Write-Host "$($script:Gray) $Label — not present, skipped$($script:NC)"
|
||||
return 0
|
||||
}
|
||||
$before = Get-LxsFolderSize -Path $Path
|
||||
$skipped = 0
|
||||
Get-ChildItem -Path $Path -Force -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
try {
|
||||
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Stop
|
||||
} catch {
|
||||
$skipped++
|
||||
}
|
||||
}
|
||||
$after = Get-LxsFolderSize -Path $Path
|
||||
$freed = [math]::Max(0, $before - $after)
|
||||
$suffix = if ($skipped -gt 0) { " ($skipped item(s) in use, skipped)" } else { '' }
|
||||
Write-LxsOk "$Label — $(Format-LxsSize $freed) reclaimed$suffix"
|
||||
return $freed
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Plan
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
if (-not (Assert-LxsWindows)) { exit 1 }
|
||||
|
||||
$isAdmin = Test-LxsAdmin
|
||||
$doUpdateCache = (-not $NoUpdateCache) -and $isAdmin
|
||||
$doPrefetch = (-not $NoPrefetch) -and $isAdmin
|
||||
$doComponent = (-not $NoComponentCleanup) -and $isAdmin
|
||||
|
||||
Clear-Host
|
||||
Show-LxsBoxTop -Title 'DISK CLEANUP'
|
||||
Write-Host ''
|
||||
Write-Host 'The following locations will be cleaned:'
|
||||
Write-Host ' - User temp folder'
|
||||
if ($isAdmin) { Write-Host ' - System temp folder (C:\Windows\Temp)' }
|
||||
if (-not $NoRecycleBin) { Write-Host ' - Recycle bin (all drives)' }
|
||||
if ($doUpdateCache) { Write-Host ' - Windows Update download cache' }
|
||||
if ($doPrefetch) { Write-Host ' - Prefetch folder' }
|
||||
if ($isAdmin) { Write-Host ' - Windows Error Reporting queue and Delivery Optimization cache' }
|
||||
if ($doComponent) { Write-Host ' - WinSxS component store (DISM /StartComponentCleanup) - slow' }
|
||||
Write-Host ''
|
||||
|
||||
if (-not $isAdmin) {
|
||||
Write-LxsWarn 'Not running as administrator: system-wide locations are skipped.'
|
||||
Write-Host "$($script:Gray) Re-run from an elevated terminal to clean them.$($script:NC)"
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
Show-LxsSeparator
|
||||
Write-Host ''
|
||||
if (-not (Confirm-LxsAction -Question 'Proceed with the cleanup?' -DefaultYes -AssumeYes:$Yes)) {
|
||||
Write-LxsInfo 'Cancelled.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Run
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
$freeBefore = Get-LxsFreeBytes
|
||||
Write-Host ''
|
||||
Write-LxsInfo "Free space before: $(Format-LxsSize $freeBefore)"
|
||||
Write-Host ''
|
||||
|
||||
Clear-LxsFolderContents -Path (Get-LxsTempDir) -Label 'User temp' | Out-Null
|
||||
|
||||
if ($isAdmin) {
|
||||
Clear-LxsFolderContents -Path (Join-Path $env:SystemRoot 'Temp') -Label 'System temp' | Out-Null
|
||||
Clear-LxsFolderContents -Path (Join-Path $env:ProgramData 'Microsoft\Windows\WER\ReportQueue') -Label 'Error reports' | Out-Null
|
||||
Clear-LxsFolderContents -Path (Join-Path $env:SystemRoot 'SoftwareDistribution\DeliveryOptimization') -Label 'Delivery Optimization' | Out-Null
|
||||
}
|
||||
|
||||
if ($doPrefetch) {
|
||||
Clear-LxsFolderContents -Path (Join-Path $env:SystemRoot 'Prefetch') -Label 'Prefetch' | Out-Null
|
||||
}
|
||||
|
||||
if (-not $NoRecycleBin) {
|
||||
try {
|
||||
Clear-RecycleBin -Force -ErrorAction Stop
|
||||
Write-LxsOk 'Recycle bin emptied'
|
||||
} catch {
|
||||
# Clear-RecycleBin throws when the bin is already empty.
|
||||
Write-Host "$($script:Gray) Recycle bin — already empty or not accessible$($script:NC)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($doUpdateCache) {
|
||||
# The download cache can only be cleared with the update services stopped;
|
||||
# they are restarted afterwards whether or not the deletion succeeded.
|
||||
$services = @('wuauserv', 'bits')
|
||||
$stopped = @()
|
||||
foreach ($svc in $services) {
|
||||
try {
|
||||
$s = Get-Service -Name $svc -ErrorAction Stop
|
||||
if ($s.Status -eq 'Running') {
|
||||
Stop-Service -Name $svc -Force -ErrorAction Stop
|
||||
$stopped += $svc
|
||||
}
|
||||
} catch {
|
||||
Write-LxsWarn "Could not stop $svc — the update cache may be partially locked."
|
||||
}
|
||||
}
|
||||
try {
|
||||
Clear-LxsFolderContents -Path (Join-Path $env:SystemRoot 'SoftwareDistribution\Download') -Label 'Windows Update cache' | Out-Null
|
||||
} finally {
|
||||
foreach ($svc in $stopped) {
|
||||
try { Start-Service -Name $svc -ErrorAction Stop } catch { Write-LxsWarn "Failed to restart $svc — start it manually." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($doComponent) {
|
||||
Write-Host ''
|
||||
Write-LxsInfo 'Running DISM component cleanup (several minutes, no output until done)...'
|
||||
$ok = Invoke-LxsSpinner -Message 'DISM /StartComponentCleanup' -FilePath 'dism.exe' `
|
||||
-ArgumentList @('/Online', '/Cleanup-Image', '/StartComponentCleanup')
|
||||
if (-not $ok) { Write-LxsWarn 'Component cleanup did not complete; see the log above.' }
|
||||
}
|
||||
|
||||
# Windows.old is reported, never auto-deleted: removing it forfeits the
|
||||
# ability to roll back a feature update.
|
||||
$windowsOld = Join-Path $env:SystemDrive 'Windows.old'
|
||||
if (Test-Path $windowsOld) {
|
||||
Write-Host ''
|
||||
Write-LxsWarn "$windowsOld exists ($(Format-LxsSize (Get-LxsFolderSize -Path $windowsOld)))."
|
||||
Write-Host "$($script:Gray) It holds your previous Windows install. Removing it blocks rollback;$($script:NC)"
|
||||
Write-Host "$($script:Gray) use Settings > System > Storage > Temporary files if you want it gone.$($script:NC)"
|
||||
}
|
||||
|
||||
$freeAfter = Get-LxsFreeBytes
|
||||
Write-Host ''
|
||||
Show-LxsSeparator
|
||||
Write-Host ''
|
||||
Write-LxsOk "Free space after: $(Format-LxsSize $freeAfter)"
|
||||
Write-LxsOk "Reclaimed: $(Format-LxsSize ([math]::Max(0, $freeAfter - $freeBefore)))"
|
||||
Write-Host ''
|
||||
exit 0
|
||||
Reference in New Issue
Block a user