Загрузка данных
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Admin-side cleanup of games, launchers and their leftovers on a shared Windows PC.
.DESCRIPTION
DRY RUN BY DEFAULT: without -Apply the script only reports what it would do.
With -Apply every deletion is still confirmed (y/N) unless -Force is given.
What it covers
1. Stops running game/launcher processes
2. Removes Store (UWP) games and Xbox apps for all users
3. Uninstalls installed launchers and games (registry uninstall entries)
4. Third-party game services (launchers, anti-cheat)
5. Autostart: Run keys (machine + every processed profile), Startup folders, scheduled tasks
6. Game folders: drive roots, Program Files, ProgramData, shared folders (C:\Users\Public),
Steam libraries on any drive, game data and installers inside user profiles
7. Desktop / Start Menu shortcuts that point to games
8. Game Bar / Game DVR off by policy
9. REPORT ONLY (nothing deleted): big folders and big files for manual review
Profiles of DISABLED local accounts are skipped by default, so the old account stays intact.
Use -KeepProfiles to protect more profiles (by profile folder name or account name),
or -IncludeDisabledProfiles to clean disabled accounts too.
A log is written next to the script (or to %TEMP%).
.EXAMPLE
# 1) look first (changes nothing)
.\cleanup-games.ps1
# 2) real run, asks before each deletion
.\cleanup-games.ps1 -Apply
# 3) real run without questions
.\cleanup-games.ps1 -Apply -Force
# protect an extra profile and add your own patterns (regex)
.\cleanup-games.ps1 -Apply -KeepProfiles "Admin" -ExtraPatterns "MyGame","Foobar"
Run in Windows PowerShell 5.1 as Administrator:
powershell -ExecutionPolicy Bypass -File .\cleanup-games.ps1
#>
[CmdletBinding()]
param(
[switch]$Apply,
[switch]$Force,
[string[]]$KeepProfiles = @(),
[switch]$IncludeDisabledProfiles,
[string[]]$ExtraPatterns = @(),
[int]$BigFileGB = 1
)
$ErrorActionPreference = 'Continue'
$DryRun = -not $Apply
# =====================================================================
# EDITABLE LISTS
# =====================================================================
# Regex (case-insensitive) matched against program names, shortcut names/targets,
# autostart entries, scheduled tasks, service names, files in shared folders
$GameAppPatterns = @(
'\bSteam', 'Epic ?Games', 'Battle\.net', 'Blizzard', '\bEA app', '\bEA Desktop', 'Electronic Arts',
'^Origin$', 'OriginSetup|OriginThinSetup', 'Ubisoft', 'Uplay', 'GOG ?GALAXY', '\bRiot',
'VALORANT', 'League of Legends', 'Minecraft', 'Roblox', 'Rockstar', 'Wargaming', '\bLesta',
'World of (Tanks|Warships)', 'HoYoverse', 'Genshin', 'Honkai', 'Overwolf', 'VK ?Play',
'Game ?Center', 'Xbox', '^itch$', 'Playnite', 'Discord'
) + $ExtraPatterns
# Exact service names (besides pattern matches) that belong to anti-cheat / launchers
$GameServiceNames = '^(vgc|vgk|BEService|EasyAntiCheat.*|BattlEye.*|EpicOnlineServices)$'
# UWP / Store packages to remove (wildcards allowed)
$AppxPatterns = @(
'Microsoft.Xbox*', 'Microsoft.GamingApp', 'Microsoft.MicrosoftSolitaireCollection',
'king.com.*', 'Microsoft.Minecraft*', '*Roblox*'
)
# Processes to stop before cleanup (Get-Process names, wildcards allowed)
$GameProcesses = @(
'steam', 'steamwebhelper', 'EpicGamesLauncher', 'Battle.net', 'RiotClientServices',
'RiotClientUx', 'Origin', 'EADesktop', 'UbisoftConnect', 'upc', 'GalaxyClient', 'Discord',
'Overwolf', 'Roblox*', 'Minecraft*', 'GameCenter', 'VKPlay*'
)
# Folder names looked for in drive roots, Program Files, Program Files (x86), ProgramData
$GameFolderNames = @(
'Steam', 'SteamLibrary', 'Epic Games', 'Battle.net', 'Riot Games', 'Origin', 'Origin Games',
'EA Games', 'Electronic Arts', 'Ubisoft', 'GOG Games', 'GOG Galaxy', 'XboxGames',
'Games', 'Wargaming.net', 'Lesta', 'Blizzard Entertainment', 'Rockstar Games', 'VK Play'
)
$ProgramDataExtra = @('Epic')
# Game folders inside every processed user profile
$GameProfileSubdirs = @(
'Documents\My Games', 'Saved Games', 'AppData\Roaming\.minecraft',
'AppData\Roaming\Battle.net', 'AppData\Roaming\discord', 'AppData\Roaming\Wargaming.net',
'AppData\Local\Steam', 'AppData\Local\EpicGamesLauncher', 'AppData\Local\Battle.net',
'AppData\Local\Riot Games', 'AppData\Local\Roblox', 'AppData\Local\Discord',
'AppData\Local\Ubisoft Game Launcher', 'AppData\Local\Origin',
'AppData\Local\Electronic Arts', 'AppData\Local\VK Play',
'AppData\Local\Programs\Discord'
)
# Top-level folders ignored by the "big folders" report
$ReportSkipTop = @(
'Windows', 'Program Files', 'Program Files (x86)', 'ProgramData', 'Users', 'Recovery',
'PerfLogs', '$Recycle.Bin', 'System Volume Information', 'Documents and Settings',
'$WinREAgent', 'Config.Msi', 'MSOCache'
)
# =====================================================================
# LOG + HELPERS
# =====================================================================
$logDir = if ($PSScriptRoot) { $PSScriptRoot } else { $env:TEMP }
$logFile = Join-Path $logDir ("cleanup-games-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date))
try { Start-Transcript -Path $logFile -Force | Out-Null } catch { }
$script:Stats = @{ Done = 0; Failed = 0; Skipped = 0 }
$script:Seen = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
$pub = Join-Path $env:SystemDrive 'Users\Public'
function Step-Header {
param([string]$Text)
Write-Host "`n== $Text ==" -ForegroundColor Cyan
}
function Do-Action {
param([string]$Text, [scriptblock]$Action)
if ($DryRun) {
Write-Host "[dry-run] $Text" -ForegroundColor Yellow
return
}
Write-Host " -> $Text"
try {
& $Action
$script:Stats.Done++
}
catch {
$script:Stats.Failed++
Write-Warning "Failed: $Text :: $($_.Exception.Message)"
}
}
function Confirm-Item {
param([string]$Message)
if ($DryRun -or $Force) { return $true }
$a = Read-Host "$Message [y/N]"
if ($a -match '^(y|yes)$') { return $true }
$script:Stats.Skipped++
return $false
}
function Test-GameText {
param([string]$Text)
if ([string]::IsNullOrWhiteSpace($Text)) { return $false }
foreach ($p in $GameAppPatterns) {
if ($Text -match $p) { return $true }
}
return $false
}
$ProtectedExact = @(
$env:SystemDrive, "$env:SystemDrive\Users", $env:ProgramFiles, ${env:ProgramFiles(x86)},
$env:ProgramData, $env:windir
) | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\') }
function Test-SafePath {
param([string]$Path)
$full = $Path.TrimEnd('\')
if ($full -match '^[A-Za-z]:$') { return $false }
if ($ProtectedExact -contains $full) { return $false }
if ($full.StartsWith($env:windir + '\', [StringComparison]::OrdinalIgnoreCase)) { return $false }
return $true
}
function Get-SizeGB {
param([string]$Path)
try {
$item = Get-Item -LiteralPath $Path -Force -ErrorAction Stop
if (-not $item.PSIsContainer) { return [math]::Round($item.Length / 1GB, 2) }
$sum = (Get-ChildItem -LiteralPath $Path -Recurse -Force -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum).Sum
if (-not $sum) { $sum = 0 }
return [math]::Round($sum / 1GB, 2)
}
catch { return 0 }
}
function Remove-GameItem {
param([string]$Path)
if (-not (Test-SafePath $Path)) { Write-Warning "Protected path skipped: $Path"; return }
$size = Get-SizeGB $Path
if ($DryRun) {
Write-Host ("[dry-run] Delete: {0} ({1} GB)" -f $Path, $size) -ForegroundColor Yellow
return
}
if (Confirm-Item ("Delete '{0}' ({1} GB)?" -f $Path, $size)) {
Write-Host " -> Deleting $Path"
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue
if (Test-Path -LiteralPath $Path) {
$script:Stats.Failed++
Write-Warning "Not fully deleted (in use? reboot and rerun): $Path"
}
else { $script:Stats.Done++ }
}
}
$script:Wsh = New-Object -ComObject WScript.Shell
function Test-ShortcutGame {
param([System.IO.FileInfo]$File)
if (Test-GameText $File.BaseName) { return $true }
try {
if ($File.Extension -ieq '.lnk') {
$s = $script:Wsh.CreateShortcut($File.FullName)
return (Test-GameText ("{0} {1}" -f $s.TargetPath, $s.Arguments))
}
if ($File.Extension -ieq '.url') {
return (Test-GameText (Get-Content -LiteralPath $File.FullName -Raw -ErrorAction Stop))
}
}
catch { }
return $false
}
function Remove-GameFile {
param([System.IO.FileInfo]$File, [string]$What)
if (-not $script:Seen.Add($File.FullName)) { return }
if ($DryRun) {
Write-Host "[dry-run] $What : $($File.FullName)" -ForegroundColor Yellow
return
}
if (Confirm-Item "$What '$($File.FullName)'?") {
$p = $File.FullName
Do-Action "$What $p" { Remove-Item -LiteralPath $p -Force -ErrorAction Stop }
}
}
function Clear-RunKey {
param([string]$Key)
if (-not (Test-Path -LiteralPath $Key)) { return }
$skip = 'PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider'
$props = (Get-ItemProperty -LiteralPath $Key).PSObject.Properties |
Where-Object { $skip -notcontains $_.Name }
foreach ($pr in $props) {
if (Test-GameText ("{0} {1}" -f $pr.Name, $pr.Value)) {
$name = $pr.Name
Do-Action "Remove autostart '$name' from $Key" {
Remove-ItemProperty -LiteralPath $Key -Name $name -Force -ErrorAction Stop
}
}
}
}
function Use-UserHive {
# runs $Body with the registry root of the user's hive (loads NTUSER.DAT if the user is not logged in)
param($Profile, [scriptblock]$Body)
$sid = $Profile.Sid
$tmp = $null
if (Test-Path -LiteralPath "Registry::HKEY_USERS\$sid") {
$root = "Registry::HKEY_USERS\$sid"
}
else {
$nt = Join-Path $Profile.Path 'NTUSER.DAT'
if (-not (Test-Path -LiteralPath $nt)) { return }
$tmp = 'CleanupTmp_' + $sid.Replace('-', '_')
& reg.exe load "HKU\$tmp" $nt | Out-Null
if ($LASTEXITCODE -ne 0) { Write-Warning "Could not load registry hive of $($Profile.Path)"; return }
$root = "Registry::HKEY_USERS\$tmp"
}
try { & $Body $root }
finally {
if ($tmp) {
[gc]::Collect(); [gc]::WaitForPendingFinalizers()
& reg.exe unload "HKU\$tmp" | Out-Null
}
}
}
function Uninstall-Program {
param($App)
$cmd = if ($App.QuietUninstallString) { $App.QuietUninstallString } else { $App.UninstallString }
if (-not $cmd) { Write-Warning "No uninstall command for '$($App.DisplayName)' - remove manually"; return }
if ($cmd -match 'msiexec') {
$cmd = $cmd -replace '/I', '/X'
if ($cmd -notmatch '/q') { $cmd += ' /qn /norestart' }
}
elseif (-not $App.QuietUninstallString) {
$cmd += ' /S' # silent flag for NSIS-style uninstallers; ignored by others
}
Start-Process -FilePath 'cmd.exe' -ArgumentList "/c `"$cmd`"" -Wait -WindowStyle Hidden
}
function Get-CleanupProfiles {
$disabled = @(Get-LocalUser -ErrorAction SilentlyContinue |
Where-Object { -not $_.Enabled } | ForEach-Object { $_.SID.Value })
foreach ($p in (Get-CimInstance Win32_UserProfile)) {
if ($p.Special -or -not $p.LocalPath) { continue }
$folder = Split-Path $p.LocalPath -Leaf
$acct = ''
try {
$acct = (New-Object System.Security.Principal.SecurityIdentifier($p.SID)).Translate(
[System.Security.Principal.NTAccount]).Value.Split('\')[-1]
}
catch { }
$why = ''
if (($KeepProfiles -contains $folder) -or ($acct -and ($KeepProfiles -contains $acct))) {
$why = 'in -KeepProfiles'
}
elseif ((-not $IncludeDisabledProfiles) -and ($disabled -contains $p.SID)) {
$why = 'disabled account'
}
[pscustomobject]@{ Path = $p.LocalPath; Sid = $p.SID; Account = $acct; SkipReason = $why }
}
}
# =====================================================================
# START
# =====================================================================
if ($DryRun) {
Write-Host "DRY RUN: nothing will be changed. Add -Apply to really clean." -ForegroundColor Yellow
}
else {
Write-Host "APPLY mode: deletions are real." -ForegroundColor Red
}
$Profiles = @(Get-CleanupProfiles)
$Targets = @($Profiles | Where-Object { -not $_.SkipReason })
Write-Host "`nProfiles:"
foreach ($p in $Profiles) {
if ($p.SkipReason) {
Write-Host (" SKIP {0} ({1})" -f $p.Path, $p.SkipReason) -ForegroundColor DarkGray
}
else {
Write-Host (" clean {0}" -f $p.Path)
}
}
$fixed = @(Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | ForEach-Object { $_.DeviceID })
# ---------------------------------------------------------------------
Step-Header '1. Processes'
foreach ($n in $GameProcesses) {
foreach ($proc in (Get-Process -Name $n -ErrorAction SilentlyContinue)) {
$id = $proc.Id
Do-Action "Stop process $($proc.Name) (PID $id)" { Stop-Process -Id $id -Force -ErrorAction Stop }
}
}
# ---------------------------------------------------------------------
Step-Header '2. Store (UWP) games'
foreach ($pattern in $AppxPatterns) {
foreach ($pkg in (Get-AppxPackage -AllUsers -Name $pattern -ErrorAction SilentlyContinue)) {
$full = $pkg.PackageFullName
Do-Action "Remove UWP package $($pkg.Name)" {
Remove-AppxPackage -Package $full -AllUsers -ErrorAction Stop
}
}
foreach ($prov in (Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like $pattern })) {
$pn = $prov.PackageName
Do-Action "Remove provisioned package $($prov.DisplayName)" {
Remove-AppxProvisionedPackage -Online -PackageName $pn -ErrorAction Stop | Out-Null
}
}
}
# ---------------------------------------------------------------------
Step-Header '3. Installed launchers and games'
$uninstallKeys = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$apps = @(foreach ($a in (Get-ItemProperty $uninstallKeys -ErrorAction SilentlyContinue)) {
if ($a.DisplayName -and (Test-GameText $a.DisplayName)) { $a }
})
if ($apps.Count -eq 0) { Write-Host ' nothing found' }
foreach ($app in $apps) {
if ($DryRun) {
Write-Host "[dry-run] Uninstall program: $($app.DisplayName)" -ForegroundColor Yellow
}
elseif (Confirm-Item "Uninstall '$($app.DisplayName)'?") {
Write-Host " -> Uninstalling $($app.DisplayName)"
try { Uninstall-Program -App $app; $script:Stats.Done++ }
catch { $script:Stats.Failed++; Write-Warning $_.Exception.Message }
}
}
# ---------------------------------------------------------------------
Step-Header '4. Third-party game services'
foreach ($svc in (Get-CimInstance Win32_Service)) {
$path = "$($svc.PathName)"
if ($path -match '(?i)\\Windows\\') { continue } # leave Windows' own services alone
if ((Test-GameText $svc.Name) -or (Test-GameText $svc.DisplayName) -or ($svc.Name -match $GameServiceNames)) {
$name = $svc.Name
if (Confirm-Item "Remove service '$name' ($($svc.DisplayName))?") {
Do-Action "Stop and delete service $name" {
Stop-Service -Name $name -Force -ErrorAction SilentlyContinue
& sc.exe delete $name | Out-Null
}
}
}
}
# ---------------------------------------------------------------------
Step-Header '5. Autostart'
Write-Host '-- Run keys (machine)'
Clear-RunKey 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
Clear-RunKey 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run'
Write-Host '-- Run keys (profiles)'
foreach ($t in $Targets) {
Use-UserHive -Profile $t -Body {
param($root)
Clear-RunKey "$root\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
}
}
Write-Host '-- Startup folders'
$startupDirs = @("$env:ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp")
foreach ($t in $Targets) {
$startupDirs += "$($t.Path)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
}
foreach ($dir in $startupDirs) {
if (-not (Test-Path -LiteralPath $dir)) { continue }
foreach ($f in (Get-ChildItem -LiteralPath $dir -Force -File -ErrorAction SilentlyContinue)) {
if (Test-ShortcutGame $f) { Remove-GameFile -File $f -What 'Remove startup item' }
}
}
Write-Host '-- Scheduled tasks'
foreach ($task in (Get-ScheduledTask -ErrorAction SilentlyContinue)) {
if ($task.TaskPath -like '\Microsoft\*') { continue }
if (Test-GameText $task.TaskName) {
$tn = $task.TaskName; $tp = $task.TaskPath
if (Confirm-Item "Remove scheduled task '$tp$tn'?") {
Do-Action "Remove scheduled task $tp$tn" {
Unregister-ScheduledTask -TaskName $tn -TaskPath $tp -Confirm:$false -ErrorAction Stop
}
}
}
}
# ---------------------------------------------------------------------
Step-Header '6. Game folders and files'
$cand = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase)
foreach ($d in $fixed) {
foreach ($n in $GameFolderNames) {
foreach ($base in @("$d\", "$d\Program Files\", "$d\Program Files (x86)\")) {
$c = Join-Path $base $n
if (Test-Path -LiteralPath $c) { [void]$cand.Add($c) }
}
}
# Steam libraries anywhere on the drive (depth-limited)
Get-ChildItem -LiteralPath "$d\" -Directory -Filter 'steamapps' -Recurse -Depth 3 -Force -ErrorAction SilentlyContinue |
Where-Object { -not $_.FullName.StartsWith($env:windir, [StringComparison]::OrdinalIgnoreCase) } |
ForEach-Object { [void]$cand.Add($_.FullName) }
}
foreach ($n in ($GameFolderNames + $ProgramDataExtra)) {
$c = Join-Path $env:ProgramData $n
if (Test-Path -LiteralPath $c) { [void]$cand.Add($c) }
}
# shared folder C:\Users\Public: game-related items by name
$pubGames = Join-Path $pub 'Games'
if (Test-Path -LiteralPath $pubGames) { [void]$cand.Add($pubGames) }
foreach ($sub in @('', 'Documents', 'Downloads', 'Music', 'Pictures', 'Videos')) {
$dir = if ($sub) { Join-Path $pub $sub } else { $pub }
if (-not (Test-Path -LiteralPath $dir)) { continue }
$depthArgs = if ($sub) { @{ Recurse = $true; Depth = 2 } } else { @{} }
Get-ChildItem -LiteralPath $dir -Force -ErrorAction SilentlyContinue @depthArgs |
Where-Object { (Test-GameText $_.Name) -and $_.Name -ne 'Desktop' } |
ForEach-Object { [void]$cand.Add($_.FullName) }
}
# processed profiles: game data + installers/archives in Downloads
foreach ($t in $Targets) {
foreach ($sub in $GameProfileSubdirs) {
$c = Join-Path $t.Path $sub
if (Test-Path -LiteralPath $c) { [void]$cand.Add($c) }
}
$dl = Join-Path $t.Path 'Downloads'
if (Test-Path -LiteralPath $dl) {
Get-ChildItem -LiteralPath $dl -Recurse -Depth 2 -Force -File -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -match '^\.(exe|msi|iso|zip|rar|7z)$' -and (Test-GameText $_.Name) } |
ForEach-Object { [void]$cand.Add($_.FullName) }
}
}
if ($cand.Count -eq 0) { Write-Host ' nothing found' }
$handled = New-Object System.Collections.Generic.List[string]
foreach ($c in ($cand | Sort-Object { $_.Length })) {
$isChild = $false
foreach ($h in $handled) {
if ($c.StartsWith($h.TrimEnd('\') + '\', [StringComparison]::OrdinalIgnoreCase)) { $isChild = $true; break }
}
if ($isChild) { continue }
if (-not (Test-Path -LiteralPath $c)) { continue }
$handled.Add($c)
Remove-GameItem -Path $c
}
# ---------------------------------------------------------------------
Step-Header '7. Shortcuts to games'
$shortcutRoots = @("$pub\Desktop", "$env:ProgramData\Microsoft\Windows\Start Menu\Programs")
foreach ($t in $Targets) {
$shortcutRoots += "$($t.Path)\Desktop"
$shortcutRoots += "$($t.Path)\OneDrive\Desktop"
$shortcutRoots += "$($t.Path)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
}
foreach ($r in $shortcutRoots) {
if (-not (Test-Path -LiteralPath $r)) { continue }
foreach ($f in (Get-ChildItem -LiteralPath $r -Recurse -Force -File -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -match '^\.(lnk|url)$' })) {
if (Test-ShortcutGame $f) { Remove-GameFile -File $f -What 'Remove shortcut' }
}
}
# ---------------------------------------------------------------------
Step-Header '8. Game Bar / Game DVR'
Do-Action 'Disable Game Bar / Game DVR via policy' {
$k = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR'
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
New-ItemProperty -Path $k -Name 'AllowGameDVR' -Value 0 -PropertyType DWord -Force | Out-Null
}
# ---------------------------------------------------------------------
Step-Header '9. REPORT (nothing is deleted here): review by hand'
Write-Host '-- Top-level folders on fixed drives (system folders hidden), GB'
$rows = foreach ($d in $fixed) {
Get-ChildItem -LiteralPath "$d\" -Directory -Force -ErrorAction SilentlyContinue |
Where-Object { $ReportSkipTop -notcontains $_.Name } |
ForEach-Object { [pscustomobject]@{ GB = (Get-SizeGB $_.FullName); Folder = $_.FullName } }
}
if ($rows) { $rows | Sort-Object GB -Descending | Format-Table -AutoSize | Out-String | Write-Host }
else { Write-Host ' none' }
Write-Host "-- Files bigger than $BigFileGB GB (non-system drives, Public, processed profiles)"
$scanRoots = @($pub) + @($Targets | ForEach-Object { $_.Path })
foreach ($d in $fixed) { if ($d -ine $env:SystemDrive) { $scanRoots += "$d\" } }
$big = foreach ($root in $scanRoots) {
if (-not (Test-Path -LiteralPath $root)) { continue }
Get-ChildItem -LiteralPath $root -Recurse -Force -File -ErrorAction SilentlyContinue |
Where-Object { $_.Length -gt ($BigFileGB * 1GB) } |
ForEach-Object { [pscustomobject]@{ GB = [math]::Round($_.Length / 1GB, 1); File = $_.FullName } }
}
if ($big) { $big | Sort-Object GB -Descending | Select-Object -First 30 | Format-Table -AutoSize | Out-String | Write-Host }
else { Write-Host ' none' }
# =====================================================================
# SUMMARY
# =====================================================================
Write-Host "`nSummary: done=$($script:Stats.Done) failed=$($script:Stats.Failed) skipped=$($script:Stats.Skipped)" -ForegroundColor Cyan
if ($DryRun) { Write-Host 'This was a dry run. Run again with -Apply to clean.' -ForegroundColor Yellow }
else {
Write-Host 'Reboot, then run the script once more (without -Apply): the lists should be empty.'
}
Write-Host "Log: $logFile"
try { Stop-Transcript | Out-Null } catch { }