windowsweep 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-INTEGRATION-GUIDE.md +16 -12
- package/CHANGELOG.md +82 -0
- package/LICENSE +21 -21
- package/README.md +104 -69
- package/VERSION +1 -1
- package/lib/actions.ps1 +212 -212
- package/lib/config.ps1 +108 -99
- package/lib/constants.ps1 +103 -93
- package/lib/external.ps1 +87 -0
- package/lib/fs.ps1 +189 -180
- package/lib/log.ps1 +140 -140
- package/lib/safety.ps1 +467 -493
- package/lib/scan.ps1 +109 -100
- package/lib/ui.ps1 +275 -275
- package/modules/android_avd.ps1 +36 -36
- package/modules/app_caches.ps1 +58 -58
- package/modules/browsers.ps1 +33 -33
- package/modules/build_tools.ps1 +35 -35
- package/modules/crash_trap.ps1 +89 -89
- package/modules/disk_usage.ps1 +49 -49
- package/modules/docker.ps1 +93 -93
- package/modules/editors.ps1 +150 -150
- package/modules/globals_audit.ps1 +236 -236
- package/modules/health.ps1 +101 -101
- package/modules/installed_programs.ps1 +118 -118
- package/modules/menu.ps1 +63 -63
- package/modules/notify.ps1 +60 -60
- package/modules/orphaned_appdata.ps1 +189 -189
- package/modules/personal.ps1 +126 -126
- package/modules/pkg_managers.ps1 +73 -73
- package/modules/projects.ps1 +160 -160
- package/modules/release_helpers.ps1 +438 -438
- package/modules/reports.ps1 +212 -212
- package/modules/runner.ps1 +246 -239
- package/modules/self_test_contract.ps1 +195 -0
- package/modules/self_test_extra.ps1 +366 -360
- package/modules/startup_audit.ps1 +126 -126
- package/modules/system_admin.ps1 +220 -220
- package/modules/test_runners.ps1 +24 -24
- package/modules/walkthrough.ps1 +80 -79
- package/modules/windows_user.ps1 +118 -118
- package/package.json +3 -3
- package/windowsweep.ps1 +322 -316
package/lib/scan.ps1
CHANGED
|
@@ -1,100 +1,109 @@
|
|
|
1
|
-
# scan.ps1 - read-only scanners: the target table, --list-targets and the pre-scan.
|
|
2
|
-
# Every module contributes its targets through a Get-TargetsNN function; nothing here deletes.
|
|
3
|
-
|
|
4
|
-
function New-Target {
|
|
5
|
-
<# .SYNOPSIS Describe one path the tool can touch and how it is treated.
|
|
6
|
-
Kind: dir | file | glob (expands to dirs) | fileglob (expands to files) | cmd (informational)
|
|
7
|
-
Mode: prune (files idle >= Days) | clear (everything inside) | clear-old (entries idle >= Days) |
|
|
8
|
-
units (each child is a version/unit; idle gate, -KeepNewest per group) | file (remove the file)
|
|
9
|
-
Dev: $true = a developer cache: pruned by the idle gate in developer mode, cleared otherwise.
|
|
10
|
-
Guard: process names; the target is skipped while any of them runs. #>
|
|
11
|
-
param([int] $Section, [string] $Label, [string] $Path, [string] $Kind = 'dir', [string] $Mode = 'prune',
|
|
12
|
-
[int] $Days = 0, [bool] $Dev = $false, [string[]] $Guard = @(), [bool] $KeepNewest = $false,
|
|
13
|
-
[scriptblock] $GroupBy = $null, [string[]] $ExcludeNames = @(), [string] $Note = '')
|
|
14
|
-
return [pscustomobject]@{ Section = $Section; Label = $Label; Path = $Path; Kind = $Kind; Mode = $Mode; Days = $Days
|
|
15
|
-
Dev = $Dev; Guard = $Guard; KeepNewest = $KeepNewest; GroupBy = $GroupBy; ExcludeNames = $ExcludeNames; Note = $Note }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function Get-AllTargets {
|
|
19
|
-
<# .SYNOPSIS Collect every declared target from the loaded modules: one Get-TargetsNN per catalogue row.
|
|
20
|
-
Driven by WS_SECTIONS, never a literal range, so a new section is reachable the moment it is declared. #>
|
|
21
|
-
$all = @()
|
|
22
|
-
foreach ($s in $Script:WS_SECTIONS) {
|
|
23
|
-
$fn = 'Get-Targets{0:00}' -f $s.Id
|
|
24
|
-
if (Get-Command $fn -ErrorAction SilentlyContinue) { $all += @(& $fn) }
|
|
25
|
-
}
|
|
26
|
-
return $all
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function Resolve-TargetPaths {
|
|
30
|
-
<# .SYNOPSIS Expand a glob target into concrete existing paths; a plain path returns itself when present. #>
|
|
31
|
-
param([pscustomobject] $Target)
|
|
32
|
-
if ($Target.Kind -eq 'cmd') { return @() }
|
|
33
|
-
if ($Target.Kind -in 'chromium', 'firefox', 'electron', 'editor') { return @(Get-TargetCachePaths $Target) }
|
|
34
|
-
if ($Target.Kind -eq 'glob') {
|
|
35
|
-
try { return @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.DirectoryInfo] } | ForEach-Object { $_.FullName }) } catch { return @() }
|
|
36
|
-
}
|
|
37
|
-
if ($Target.Kind -eq 'fileglob') {
|
|
38
|
-
try { return @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.FileInfo] } | ForEach-Object { $_.FullName }) } catch { return @() }
|
|
39
|
-
}
|
|
40
|
-
if (Test-PathPresent $Target.Path) { return @((Get-FullPath $Target.Path)) }
|
|
41
|
-
return @()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function Show-ScanTable {
|
|
45
|
-
<# .SYNOPSIS Read-only table of every target with its current size. Returns total bytes found. #>
|
|
46
|
-
param([int[]] $Sections = @())
|
|
47
|
-
$total = [long]0
|
|
48
|
-
$targets = @(Get-AllTargets)
|
|
49
|
-
if ($Sections.Count -gt 0) { $targets = @($targets | Where-Object { $Sections -contains $_.Section }) }
|
|
50
|
-
$bySection = $targets | Group-Object Section | Sort-Object { [int]$_.Name }
|
|
51
|
-
foreach ($g in $bySection) {
|
|
52
|
-
$sec = Get-Section ([int]$g.Name)
|
|
53
|
-
Write-Section ("[{0:00}] {1}" -f $sec.Id, $sec.Title)
|
|
54
|
-
Write-UiLine (" {0,-46} {1,10} {2}" -f 'ITEM', 'SIZE', 'PATH') 'White'
|
|
55
|
-
$secTotal = [long]0
|
|
56
|
-
foreach ($t in $g.Group) {
|
|
57
|
-
if ($t.Kind -eq 'cmd') { Write-UiLine (" {0,-46} {1,10} {2}" -f $t.Label, '(cmd)', $t.Path) 'DarkGray'; continue }
|
|
58
|
-
$paths = @(Resolve-TargetPaths $t)
|
|
59
|
-
if ($paths.Count -eq 0) { Write-UiLine (" {0,-46} {1,10} {2}" -f $t.Label, 'absent', $t.Path) 'DarkGray'; continue }
|
|
60
|
-
$bytes = [long]0
|
|
61
|
-
foreach ($p in $paths) {
|
|
62
|
-
# Sized once per path: a second
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
foreach ($
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
1
|
+
# scan.ps1 - read-only scanners: the target table, --list-targets and the pre-scan.
|
|
2
|
+
# Every module contributes its targets through a Get-TargetsNN function; nothing here deletes.
|
|
3
|
+
|
|
4
|
+
function New-Target {
|
|
5
|
+
<# .SYNOPSIS Describe one path the tool can touch and how it is treated.
|
|
6
|
+
Kind: dir | file | glob (expands to dirs) | fileglob (expands to files) | cmd (informational)
|
|
7
|
+
Mode: prune (files idle >= Days) | clear (everything inside) | clear-old (entries idle >= Days) |
|
|
8
|
+
units (each child is a version/unit; idle gate, -KeepNewest per group) | file (remove the file)
|
|
9
|
+
Dev: $true = a developer cache: pruned by the idle gate in developer mode, cleared otherwise.
|
|
10
|
+
Guard: process names; the target is skipped while any of them runs. #>
|
|
11
|
+
param([int] $Section, [string] $Label, [string] $Path, [string] $Kind = 'dir', [string] $Mode = 'prune',
|
|
12
|
+
[int] $Days = 0, [bool] $Dev = $false, [string[]] $Guard = @(), [bool] $KeepNewest = $false,
|
|
13
|
+
[scriptblock] $GroupBy = $null, [string[]] $ExcludeNames = @(), [string] $Note = '')
|
|
14
|
+
return [pscustomobject]@{ Section = $Section; Label = $Label; Path = $Path; Kind = $Kind; Mode = $Mode; Days = $Days
|
|
15
|
+
Dev = $Dev; Guard = $Guard; KeepNewest = $KeepNewest; GroupBy = $GroupBy; ExcludeNames = $ExcludeNames; Note = $Note }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Get-AllTargets {
|
|
19
|
+
<# .SYNOPSIS Collect every declared target from the loaded modules: one Get-TargetsNN per catalogue row.
|
|
20
|
+
Driven by WS_SECTIONS, never a literal range, so a new section is reachable the moment it is declared. #>
|
|
21
|
+
$all = @()
|
|
22
|
+
foreach ($s in $Script:WS_SECTIONS) {
|
|
23
|
+
$fn = 'Get-Targets{0:00}' -f $s.Id
|
|
24
|
+
if (Get-Command $fn -ErrorAction SilentlyContinue) { $all += @(& $fn) }
|
|
25
|
+
}
|
|
26
|
+
return $all
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Resolve-TargetPaths {
|
|
30
|
+
<# .SYNOPSIS Expand a glob target into concrete existing paths; a plain path returns itself when present. #>
|
|
31
|
+
param([pscustomobject] $Target)
|
|
32
|
+
if ($Target.Kind -eq 'cmd') { return @() }
|
|
33
|
+
if ($Target.Kind -in 'chromium', 'firefox', 'electron', 'editor') { return @(Get-TargetCachePaths $Target) }
|
|
34
|
+
if ($Target.Kind -eq 'glob') {
|
|
35
|
+
try { return @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.DirectoryInfo] } | ForEach-Object { $_.FullName }) } catch { return @() }
|
|
36
|
+
}
|
|
37
|
+
if ($Target.Kind -eq 'fileglob') {
|
|
38
|
+
try { return @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.FileInfo] } | ForEach-Object { $_.FullName }) } catch { return @() }
|
|
39
|
+
}
|
|
40
|
+
if (Test-PathPresent $Target.Path) { return @((Get-FullPath $Target.Path)) }
|
|
41
|
+
return @()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Show-ScanTable {
|
|
45
|
+
<# .SYNOPSIS Read-only table of every target with its current size. Returns total bytes found. #>
|
|
46
|
+
param([int[]] $Sections = @())
|
|
47
|
+
$total = [long]0
|
|
48
|
+
$targets = @(Get-AllTargets)
|
|
49
|
+
if ($Sections.Count -gt 0) { $targets = @($targets | Where-Object { $Sections -contains $_.Section }) }
|
|
50
|
+
$bySection = $targets | Group-Object Section | Sort-Object { [int]$_.Name }
|
|
51
|
+
foreach ($g in $bySection) {
|
|
52
|
+
$sec = Get-Section ([int]$g.Name)
|
|
53
|
+
Write-Section ("[{0:00}] {1}" -f $sec.Id, $sec.Title)
|
|
54
|
+
Write-UiLine (" {0,-46} {1,10} {2}" -f 'ITEM', 'SIZE', 'PATH') 'White'
|
|
55
|
+
$secTotal = [long]0
|
|
56
|
+
foreach ($t in $g.Group) {
|
|
57
|
+
if ($t.Kind -eq 'cmd') { Write-UiLine (" {0,-46} {1,10} {2}" -f $t.Label, '(cmd)', $t.Path) 'DarkGray'; continue }
|
|
58
|
+
$paths = @(Resolve-TargetPaths $t)
|
|
59
|
+
if ($paths.Count -eq 0) { Write-UiLine (" {0,-46} {1,10} {2}" -f $t.Label, 'absent', $t.Path) 'DarkGray'; continue }
|
|
60
|
+
$bytes = [long]0
|
|
61
|
+
foreach ($p in $paths) {
|
|
62
|
+
# Sized once per path: a second pass for --json would walk every target twice. Under --json the walk
|
|
63
|
+
# is Get-DirectoryStats, which returns the newest timestamp out of the SAME enumeration that produces
|
|
64
|
+
# the byte count - so newest_write_utc costs no extra walk. Outside --json the faster robocopy path in
|
|
65
|
+
# Get-DirectoryBytes is kept, because a human --scan has no use for the timestamp.
|
|
66
|
+
$newest = $null
|
|
67
|
+
if ($Script:WS.JsonMode) {
|
|
68
|
+
$st = Get-DirectoryStats $p
|
|
69
|
+
$b = [long]$st.Bytes
|
|
70
|
+
$newest = Format-Utc8601 $st.Newest
|
|
71
|
+
} else {
|
|
72
|
+
$b = [long](Get-DirectoryBytes $p)
|
|
73
|
+
}
|
|
74
|
+
$bytes += $b
|
|
75
|
+
if ($Script:WS.JsonMode) { $Script:WS.ScanTargets += [ordered]@{ section = $t.Section; label = $t.Label; path = $p; bytes = $b; newest_write_utc = $newest } }
|
|
76
|
+
}
|
|
77
|
+
$secTotal += $bytes
|
|
78
|
+
$shown = $t.Path
|
|
79
|
+
if ($paths.Count -gt 1) { $shown = "$($t.Path) ($($paths.Count) matches)" }
|
|
80
|
+
Write-UiLine (" {0,-46} {1,10} {2}" -f $t.Label, (Format-Bytes $bytes), $shown) 'Gray'
|
|
81
|
+
}
|
|
82
|
+
Write-UiLine (" {0,-46} {1,10}" -f 'section total on disk', (Format-Bytes $secTotal)) 'Cyan'
|
|
83
|
+
$total += $secTotal
|
|
84
|
+
}
|
|
85
|
+
Write-UiLine '' 'Gray'
|
|
86
|
+
Write-Info ("Currently on disk across all listed targets: " + (Format-Bytes $total))
|
|
87
|
+
Write-Note 'These are sizes on disk, not what a run would delete: the idle gate keeps recently used files, and running apps are skipped.'
|
|
88
|
+
return $total
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Show-TargetList {
|
|
92
|
+
<# .SYNOPSIS --list-targets: every path the tool can touch, plus the protected lists. #>
|
|
93
|
+
Write-Box 'Cleanup targets' 'Every path this tool can touch, grouped by section (read-only)'
|
|
94
|
+
$targets = @(Get-AllTargets)
|
|
95
|
+
foreach ($g in ($targets | Group-Object Section | Sort-Object { [int]$_.Name })) {
|
|
96
|
+
$sec = Get-Section ([int]$g.Name)
|
|
97
|
+
Write-Section ("[{0:00}] {1}" -f $sec.Id, $sec.Title)
|
|
98
|
+
foreach ($t in $g.Group) {
|
|
99
|
+
$note = ''
|
|
100
|
+
if ($t.Note) { $note = " - $($t.Note)" }
|
|
101
|
+
Write-UiLine (" {0,-44} {1}{2}" -f $t.Label, $t.Path, $note) 'Gray'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
Write-Section 'PROTECTED - never deleted, no flag bypasses this'
|
|
105
|
+
foreach ($s in $Script:WS_PROTECT.Subtrees) { Write-UiLine " $($Script:WS.Glyph.bullet) $s" 'DarkGray' }
|
|
106
|
+
# ONE list, read here and by --list --json (Get-CatalogueJson). Printing one wording and publishing another
|
|
107
|
+
# is exactly the drift a machine-readable copy of a human list invites, so there is only ever one copy.
|
|
108
|
+
foreach ($c in $Script:WS_PROTECT_CATEGORIES) { Write-UiLine " $($Script:WS.Glyph.bullet) $c" 'DarkGray' }
|
|
109
|
+
}
|