fix(cleanup-windows): ASCII-only damit Windows-PowerShell 5.1 parsen kann

Windows-PowerShell 5.1 liest .ps1 ohne UTF-8 BOM als Windows-1252;
em-dashes (-), Pfeile (->), Box-Zeichen, Emojis (OK/FAIL/WARN) wurden
als Mojibake interpretiert ("â€"" fuer "-") und sprengten den Parser.

Loesung: alle Sonderzeichen durch ASCII-Aequivalente ersetzt:
  - "-" / "->" statt em-dash / Pfeil
  - "===" statt Box-Linien
  - "[OK]" / "[FAIL]" / "[WARN]" statt Emojis
Funktioniert jetzt zuverlaessig auf jeder Windows-PS-Version ohne BOM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck 2026-04-24 16:01:56 +02:00
parent 607a4c9ff8
commit 60c5cb7e59
2 changed files with 43 additions and 38 deletions

View File

@ -1,7 +1,7 @@
@echo off @echo off
REM ════════════════════════════════════════════════════════════════ REM ================================================================
REM ARIA Cleanup-Wrapper fuer Windows REM ARIA - Cleanup-Wrapper fuer Windows
REM ════════════════════════════════════════════════════════════════ REM ================================================================
REM Ruft cleanup-windows.ps1 mit ExecutionPolicy Bypass auf. REM Ruft cleanup-windows.ps1 mit ExecutionPolicy Bypass auf.
REM Funktioniert auch wenn Windows .ps1 direkt nicht startet. REM Funktioniert auch wenn Windows .ps1 direkt nicht startet.
REM REM
@ -11,6 +11,6 @@ REM cleanup-windows.bat stefan -SkipPrune
REM REM
REM Doppelklick funktioniert NICHT (braucht Username als Param). REM Doppelklick funktioniert NICHT (braucht Username als Param).
REM Per Konsole aufrufen. REM Per Konsole aufrufen.
REM ════════════════════════════════════════════════════════════════ REM ================================================================
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0cleanup-windows.ps1" %* powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0cleanup-windows.ps1" %*

View File

@ -1,6 +1,6 @@
# ════════════════════════════════════════════════════════════════ # ================================================================
# ARIA Windows / WSL2 / Docker Desktop VHDX Cleanup # ARIA - Windows / WSL2 / Docker Desktop VHDX Cleanup
# ════════════════════════════════════════════════════════════════ # ================================================================
# #
# Findet alle WSL2 + Docker Desktop ext4.vhdx Files unter # Findet alle WSL2 + Docker Desktop ext4.vhdx Files unter
# C:\Users\<USER>\AppData\Local\... und kompaktiert sie via diskpart. # C:\Users\<USER>\AppData\Local\... und kompaktiert sie via diskpart.
@ -8,10 +8,11 @@
# Containern geloescht hast (z.B. nach `docker system prune`), # Containern geloescht hast (z.B. nach `docker system prune`),
# der aber von der VHDX bisher nicht freigegeben wurde. # der aber von der VHDX bisher nicht freigegeben wurde.
# #
# Nutzung (PowerShell als ADMIN): # Nutzung (PowerShell als ADMIN, oder via cleanup-windows.bat):
# .\cleanup-windows.ps1 stefan # .\cleanup-windows.ps1 stefan
# .\cleanup-windows.ps1 -User stefan # .\cleanup-windows.ps1 -User stefan
# .\cleanup-windows.ps1 -User stefan -SkipPrune # nur compacten # .\cleanup-windows.ps1 -User stefan -SkipPrune # nur compacten
# .\cleanup-windows.ps1 -User stefan -PruneOnly # nur prune
# #
# Was passiert: # Was passiert:
# 1. Erst (optional): docker system prune + builder prune in WSL2 # 1. Erst (optional): docker system prune + builder prune in WSL2
@ -19,7 +20,11 @@
# 3. Alle gefundenen .vhdx Files mit diskpart compact vdisk shrinken # 3. Alle gefundenen .vhdx Files mit diskpart compact vdisk shrinken
# #
# Hinweis: diskpart braucht KEINE Hyper-V Tools (anders als Optimize-VHD). # Hinweis: diskpart braucht KEINE Hyper-V Tools (anders als Optimize-VHD).
# ════════════════════════════════════════════════════════════════ #
# ASCII-only damit Windows-PowerShell 5.1 das File ohne BOM korrekt
# parsen kann (UTF-8-Sonderzeichen wuerden sonst als Windows-1252
# fehlinterpretiert).
# ================================================================
[CmdletBinding()] [CmdletBinding()]
param( param(
@ -27,27 +32,27 @@ param(
HelpMessage="Dein Windows-Benutzername (z.B. stefan)")] HelpMessage="Dein Windows-Benutzername (z.B. stefan)")]
[string]$User, [string]$User,
[Parameter(HelpMessage="Docker prune ueberspringen nur compacten")] [Parameter(HelpMessage="Docker prune ueberspringen - nur compacten")]
[switch]$SkipPrune, [switch]$SkipPrune,
[Parameter(HelpMessage="Docker prune NUR machen, dann beenden")] [Parameter(HelpMessage="Docker prune NUR machen, dann beenden")]
[switch]$PruneOnly [switch]$PruneOnly
) )
# Defensive: Process-Scope ExecutionPolicy auf Bypass verhindert dass # Defensive: Process-Scope ExecutionPolicy auf Bypass - verhindert dass
# Untersaetze (z.B. Module) blockiert werden. Harmless wenn Parent schon # Untersaetze (z.B. Module) blockiert werden. Harmless wenn Parent schon
# Bypass aufgerufen hat. # Bypass aufgerufen hat.
try { Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force | Out-Null } catch {} try { Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force | Out-Null } catch {}
# Admin-Check + Self-Elevation # Admin-Check + Self-Elevation
# Wenn nicht als Admin gestartet einmal neu starten als Admin, mit # Wenn nicht als Admin gestartet -> einmal neu starten als Admin, mit
# ExecutionPolicy Bypass + den Original-Argumenten. User muss nur einmal # ExecutionPolicy Bypass + den Original-Argumenten. User muss nur einmal
# UAC-Prompt bestaetigen. # UAC-Prompt bestaetigen.
$isAdmin = ([Security.Principal.WindowsPrincipal] ` $isAdmin = ([Security.Principal.WindowsPrincipal] `
[Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator) [Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) { if (-not $isAdmin) {
Write-Host " Starte neu als Administrator (mit ExecutionPolicy Bypass)..." -ForegroundColor Yellow Write-Host "-> Starte neu als Administrator (mit ExecutionPolicy Bypass)..." -ForegroundColor Yellow
$myPath = $MyInvocation.MyCommand.Path $myPath = $MyInvocation.MyCommand.Path
$forwardArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$myPath`"") $forwardArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$myPath`"")
if ($User) { $forwardArgs += @("-User", $User) } if ($User) { $forwardArgs += @("-User", $User) }
@ -56,8 +61,8 @@ if (-not $isAdmin) {
try { try {
Start-Process powershell.exe -Verb RunAs -ArgumentList $forwardArgs Start-Process powershell.exe -Verb RunAs -ArgumentList $forwardArgs
} catch { } catch {
Write-Host " UAC-Elevation abgebrochen oder fehlgeschlagen." -ForegroundColor Red Write-Host "[FAIL] UAC-Elevation abgebrochen oder fehlgeschlagen." -ForegroundColor Red
Write-Host " Rechtsklick auf PowerShell → 'Als Administrator ausfuehren'" -ForegroundColor Yellow Write-Host " Rechtsklick auf PowerShell -> 'Als Administrator ausfuehren'" -ForegroundColor Yellow
exit 1 exit 1
} }
exit 0 exit 0
@ -65,17 +70,17 @@ if (-not $isAdmin) {
$basePath = "C:\Users\$User\AppData\Local" $basePath = "C:\Users\$User\AppData\Local"
if (-not (Test-Path $basePath)) { if (-not (Test-Path $basePath)) {
Write-Host " Pfad existiert nicht: $basePath" -ForegroundColor Red Write-Host "[FAIL] Pfad existiert nicht: $basePath" -ForegroundColor Red
Write-Host " Pruefe den Benutzernamen." -ForegroundColor Yellow Write-Host " Pruefe den Benutzernamen." -ForegroundColor Yellow
exit 1 exit 1
} }
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan
Write-Host " ARIA Cleanup fuer User: $User" -ForegroundColor Cyan Write-Host " ARIA Cleanup fuer User: $User" -ForegroundColor Cyan
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan
Write-Host "" Write-Host ""
# ── 1. Docker prune (in WSL2) ────────────────────────────────── # -- 1. Docker prune (in WSL2) -----------------------------------
if (-not $SkipPrune) { if (-not $SkipPrune) {
Write-Host "[1/3] Docker Cleanup in WSL2..." -ForegroundColor Yellow Write-Host "[1/3] Docker Cleanup in WSL2..." -ForegroundColor Yellow
Write-Host " docker system prune -a --volumes -f" -ForegroundColor Gray Write-Host " docker system prune -a --volumes -f" -ForegroundColor Gray
@ -83,26 +88,26 @@ if (-not $SkipPrune) {
Write-Host "" Write-Host ""
try { try {
wsl -e bash -c "docker system prune -a --volumes -f && docker builder prune -a -f" wsl -e bash -c "docker system prune -a --volumes -f && docker builder prune -a -f"
Write-Host " fertig" -ForegroundColor Green Write-Host " [OK] fertig" -ForegroundColor Green
} catch { } catch {
Write-Host " ⚠️ Docker prune fehlgeschlagen (vielleicht laeuft Docker Desktop nicht?)" -ForegroundColor Yellow Write-Host " [WARN] Docker prune fehlgeschlagen (vielleicht laeuft Docker Desktop nicht?)" -ForegroundColor Yellow
Write-Host " $_" -ForegroundColor Gray Write-Host " $_" -ForegroundColor Gray
} }
Write-Host "" Write-Host ""
if ($PruneOnly) { if ($PruneOnly) {
Write-Host "─PruneOnly gesetzt — fertig." -ForegroundColor Cyan Write-Host "PruneOnly gesetzt - fertig." -ForegroundColor Cyan
exit 0 exit 0
} }
} }
# ── 2. WSL2 shutdown ────────────────────────────────────────── # -- 2. WSL2 shutdown --------------------------------------------
Write-Host "[2/3] WSL2 herunterfahren..." -ForegroundColor Yellow Write-Host "[2/3] WSL2 herunterfahren..." -ForegroundColor Yellow
wsl --shutdown wsl --shutdown
Start-Sleep -Seconds 3 Start-Sleep -Seconds 3
Write-Host " fertig" -ForegroundColor Green Write-Host " [OK] fertig" -ForegroundColor Green
Write-Host "" Write-Host ""
# ── 3. VHDX-Files finden + compacten ────────────────────────── # -- 3. VHDX-Files finden + compacten ----------------------------
Write-Host "[3/3] VHDX-Files suchen + compacten..." -ForegroundColor Yellow Write-Host "[3/3] VHDX-Files suchen + compacten..." -ForegroundColor Yellow
Write-Host "" Write-Host ""
@ -126,7 +131,7 @@ Write-Host ""
$totalBefore = ($vhdxFiles | Measure-Object Length -Sum).Sum $totalBefore = ($vhdxFiles | Measure-Object Length -Sum).Sum
foreach ($f in $vhdxFiles) { foreach ($f in $vhdxFiles) {
Write-Host " Compact: $($f.FullName)" -ForegroundColor White Write-Host "-> Compact: $($f.FullName)" -ForegroundColor White
$sizeBefore = [math]::Round($f.Length / 1GB, 2) $sizeBefore = [math]::Round($f.Length / 1GB, 2)
# Temporaeres diskpart-Script schreiben # Temporaeres diskpart-Script schreiben
@ -141,26 +146,26 @@ exit
try { try {
$output = & diskpart /s $tmp 2>&1 $output = & diskpart /s $tmp 2>&1
# Datei neu lesen Length ist gecacht # Datei neu lesen - Length ist gecacht
$newFile = Get-Item $f.FullName $newFile = Get-Item $f.FullName
$sizeAfter = [math]::Round($newFile.Length / 1GB, 2) $sizeAfter = [math]::Round($newFile.Length / 1GB, 2)
$saved = [math]::Round($sizeBefore - $sizeAfter, 2) $saved = [math]::Round($sizeBefore - $sizeAfter, 2)
if ($saved -gt 0) { if ($saved -gt 0) {
Write-Host (" ✅ {0} GB → {1} GB (gespart: {2} GB)" -f $sizeBefore, $sizeAfter, $saved) -ForegroundColor Green Write-Host (" [OK] {0} GB -> {1} GB (gespart: {2} GB)" -f $sizeBefore, $sizeAfter, $saved) -ForegroundColor Green
} else { } else {
Write-Host (" ─ {0} GB → {1} GB (nichts zu holen — File war schon optimal)" -f $sizeBefore, $sizeAfter) -ForegroundColor DarkGray Write-Host (" -- {0} GB -> {1} GB (nichts zu holen - File war schon optimal)" -f $sizeBefore, $sizeAfter) -ForegroundColor DarkGray
} }
} catch { } catch {
Write-Host " Fehler: $_" -ForegroundColor Red Write-Host " [FAIL] Fehler: $_" -ForegroundColor Red
Write-Host " diskpart-Output:" -ForegroundColor DarkGray Write-Host " diskpart-Output:" -ForegroundColor DarkGray
$output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } $output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
} finally { } finally {
Remove-Item $tmp -ErrorAction SilentlyContinue Remove-Item $tmp -ErrorAction SilentlyContinue
} }
Write-Host "" Write-Host ""
} }
# ── Zusammenfassung ───────────────────────────────────────── # -- Zusammenfassung ---------------------------------------------
$vhdxFilesAfter = @() $vhdxFilesAfter = @()
$vhdxFilesAfter += Get-ChildItem -Path "$basePath\Docker" -Recurse -Filter "*.vhdx" -ErrorAction SilentlyContinue $vhdxFilesAfter += Get-ChildItem -Path "$basePath\Docker" -Recurse -Filter "*.vhdx" -ErrorAction SilentlyContinue
$vhdxFilesAfter += Get-ChildItem -Path "$basePath\Packages" -Recurse -Filter "ext4.vhdx" -ErrorAction SilentlyContinue $vhdxFilesAfter += Get-ChildItem -Path "$basePath\Packages" -Recurse -Filter "ext4.vhdx" -ErrorAction SilentlyContinue
@ -169,11 +174,11 @@ $totalAfter = ($vhdxFilesAfter | Measure-Object Length -Sum).Sum
$savedTotal = [math]::Round(($totalBefore - $totalAfter) / 1GB, 2) $savedTotal = [math]::Round(($totalBefore - $totalAfter) / 1GB, 2)
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan
Write-Host (" Gesamt: {0} GB {1} GB (gespart: {2} GB)" -f ` Write-Host (" Gesamt: {0} GB -> {1} GB (gespart: {2} GB)" -f `
[math]::Round($totalBefore / 1GB, 2), [math]::Round($totalBefore / 1GB, 2),
[math]::Round($totalAfter / 1GB, 2), [math]::Round($totalAfter / 1GB, 2),
$savedTotal) -ForegroundColor Cyan $savedTotal) -ForegroundColor Cyan
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan
Write-Host "" Write-Host ""
Write-Host "Fertig. Docker Desktop / WSL2 starten ja von alleine wieder beim naechsten Aufruf." -ForegroundColor Green Write-Host "Fertig. Docker Desktop / WSL2 starten ja von alleine wieder beim naechsten Aufruf." -ForegroundColor Green