Skip to content

Commit e273d80

Browse files
committed
feat: windots refactor, pester test suite, and CI pipeline
- Convert Windots modules from .psm1 to .ps1 with dot-sourcing, remove Export-ModuleMember blocks - Add Copy-ConfigFiles helper for DRY config installation across all dotfile installers - Add Test-IsExcluded helper in Organizer.ps1 to centralize exclusion logic - Add Assert-AdminOrElevate, Initialize-Logging, Invoke-MenuLoop utilities in Common.ps1 - Add smart install prompts for Oh My Posh and FastFetch (detect missing → install/skip/cancel) - Add Pester test suite with 5 test files covering core functions, configs, and script syntax - Add Pester CI job with NUnit XML artifact upload, rename workflow to "CI" - Migrate Organizer.ps1 from Write-Host to Write-Log throughout - Use shared Set-RegistryValue in Windots.Customization.ps1 instead of inline Set-ItemProperty - Switch Steam Millennium install from piped irm|iex to direct .exe download - Expand README tools grid with DefendNot, RemoveWindowsAI, and Sparkle - Rewrite Windots README with structured tables, remove emojis - Fix Expand-StartFolders path resolution using $PSScriptRoot directly
1 parent 7302c2a commit e273d80

19 files changed

Lines changed: 720 additions & 292 deletions

.github/workflows/lint.yml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
name: PSScriptAnalyzer
1+
name: CI
22

33
on:
44
push:
55
branches: [main]
6-
paths: ['**.ps1', '**.psm1', '**.psd1']
6+
paths: ['**.ps1', '**.psm1', '**.psd1', 'config/**', 'tests/**']
77
pull_request:
88
branches: [main]
9-
paths: ['**.ps1', '**.psm1', '**.psd1']
9+
paths: ['**.ps1', '**.psm1', '**.psd1', 'config/**', 'tests/**']
1010

1111
jobs:
1212
lint:
@@ -43,3 +43,34 @@ jobs:
4343
} else {
4444
Write-Host "No issues found." -ForegroundColor Green
4545
}
46+
47+
test:
48+
name: Run Pester Tests
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v4
52+
53+
- name: Install Pester
54+
shell: pwsh
55+
run: |
56+
Set-PSRepository PSGallery -InstallationPolicy Trusted
57+
Install-Module -Name Pester -Force -Scope CurrentUser -MinimumVersion 5.5.0
58+
59+
- name: Run Pester Tests
60+
shell: pwsh
61+
run: |
62+
$config = New-PesterConfiguration
63+
$config.Run.Path = './tests'
64+
$config.Run.Exit = $true
65+
$config.Output.Verbosity = 'Detailed'
66+
$config.TestResult.Enabled = $true
67+
$config.TestResult.OutputFormat = 'NUnitXml'
68+
$config.TestResult.OutputPath = './testresults.xml'
69+
Invoke-Pester -Configuration $config
70+
71+
- name: Upload Test Results
72+
uses: actions/upload-artifact@v4
73+
if: always()
74+
with:
75+
name: test-results
76+
path: ./testresults.xml

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ and this project uses [Calendar Versioning](https://calver.org/) (YY.M format).
77

88
## [26.3] - 2026-03-16
99

10+
### March 16 — Windots refactor, Pester tests, and CI pipeline
11+
12+
- Added `Copy-ConfigFiles` helper in `Windots.Configs.ps1` — reusable config-copy-with-logging for all dotfile installers
13+
- Added `Test-IsExcluded` function in `Organizer.ps1` — centralizes file/folder exclusion logic
14+
- Added `Assert-AdminOrElevate`, `Initialize-Logging`, `Invoke-MenuLoop` utilities in `Common.ps1`
15+
- Added smart install prompts for Oh My Posh and FastFetch — detect missing tools and offer install/skip/cancel
16+
- Added Pester test suite (`tests/`) — Common.ps1 functions, config validation, ExternalLauncher params, module exports, script syntax
17+
- Added Pester CI job in `.github/workflows/lint.yml` with NUnit XML artifact upload
18+
- Added DefendNot, RemoveWindowsAI, and Sparkle to README tools grid
19+
- Changed Windots modules from `.psm1` to `.ps1` — dot-sourced instead of `Import-Module`, all `Export-ModuleMember` blocks removed
20+
- Changed `Organizer.ps1` migrated from `Write-Host` to `Write-Log` throughout
21+
- Changed `ExternalLauncher.ps1` header from `Write-Header` to `Write-Log`
22+
- Changed `Windots.Customization.ps1` uses shared `Set-RegistryValue` instead of `Set-ItemProperty` with inline try/catch
23+
- Changed `Set-OtherVSCConfig` delegates to `Set-VSCodeConfig` instead of duplicating copy logic
24+
- Changed `Set-VSCodeConfig` validates target path before copying
25+
- Changed Steam Millennium install from piped `irm | iex` to direct `.exe` download
26+
- Changed CI workflow renamed from "PSScriptAnalyzer" to "CI"; path triggers expanded to include `config/**` and `tests/**`
27+
- Changed README tools grid expanded to 8 entries (two rows of 4 at 25% width)
28+
- Changed Windots README rewritten — emoji-free headings, structured tables for Configs/Apps/Customization
29+
- Changed main menu box width normalized to 59 characters
30+
- Changed `Write-Log` catch block now surfaces warning instead of silently swallowing errors
31+
- Fixed `Expand-StartFolders` path resolution — uses `$PSScriptRoot` directly instead of double parent traversal
32+
1033
### March 16 — Bundle overhaul, return-to-menu navigation, and documentation rewrite
1134

1235
- Added `Invoke-ReturnToMenu` in `Common.ps1` — reads saved launch directory from temp file and re-launches `simplify11.ps1` in the same window

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,27 +142,44 @@ irm https://raw.githubusercontent.com/emylfy/simplify11/main/scripts/install.ps1
142142

143143
<table>
144144
<tr>
145-
<td align="center" width="20%">
145+
<td align="center" width="25%">
146146
<img src="https://github.com/ChrisTitusTech/winutil/blob/main/docs/assets/favicon.png?raw=true" width="60px" alt="WinUtil Logo"><br/>
147147
<b><a href="https://github.com/ChrisTitusTech/winutil" title="Visit WinUtil on GitHub">WinUtil</a></b><br/>
148148
<sub>Install programs, tweaks, fixes and updates</sub>
149149
</td>
150-
<td align="center" width="20%">
150+
<td align="center" width="25%">
151151
<img src="https://raw.githubusercontent.com/flick9000/winscript/refs/heads/main/app/public/logo.svg" width="60px" alt="WinScript Logo"><br/>
152152
<b><a href="https://github.com/flick9000/winscript" title="Visit WinScript on GitHub">WinScript</a></b><br/>
153153
<sub>Build custom setup scripts</sub>
154154
</td>
155-
<td align="center" width="20%">
155+
<td align="center" width="25%">
156156
<img src="https://github.com/Greedeks/GTweak/blob/main/Assets/GTweak.png" width="60px" alt="GTweak Logo"><br/>
157157
<b><a href="https://github.com/Greedeks/GTweak" title="Visit GTweak on GitHub">GTweak</a></b><br/>
158158
<sub>Tweaking tool and debloater</sub>
159159
</td>
160-
<td align="center" width="20%">
160+
<td align="center" width="25%">
161+
<img src="https://github.com/Parcoil/Sparkle/blob/v2/resources/sparklelogo.png?raw=true" width="60px" alt="Sparkle Logo"><br/>
162+
<b><a href="https://github.com/Parcoil/Sparkle" title="Visit Sparkle on GitHub">Sparkle</a></b><br/>
163+
<sub>Windows package manager</sub>
164+
</td>
165+
</tr>
166+
<tr>
167+
<td align="center" width="25%">
168+
<img src="https://i.imgur.com/F9gWA92.png" width="60px" alt="DefendNot Logo"><br/>
169+
<b><a href="https://github.com/es3n1n/defendnot" title="Visit DefendNot on GitHub">DefendNot</a></b><br/>
170+
<sub>Disable Windows Defender</sub>
171+
</td>
172+
<td align="center" width="25%">
173+
<img src="https://github.com/zoicware/RemoveWindowsAI/assets/118035521/33efb033-c935-416c-977d-777bb69a3737" width="60px" alt="RemoveWindowsAI Logo"><br/>
174+
<b><a href="https://github.com/zoicware/RemoveWindowsAI" title="Visit RemoveWindowsAI on GitHub">RemoveWindowsAI</a></b><br/>
175+
<sub>Remove Copilot & Recall</sub>
176+
</td>
177+
<td align="center" width="25%">
161178
<img src="https://raw.githubusercontent.com/undergroundwires/privacy.sexy/refs/heads/master/img/logo.svg" width="60px" alt="Privacy.sexy Logo"><br/>
162179
<b><a href="https://github.com/undergroundwires/privacy.sexy" title="Visit Privacy.sexy on GitHub">Privacy.sexy</a></b><br/>
163180
<sub>Security enhancement</sub>
164181
</td>
165-
<td align="center" width="20%">
182+
<td align="center" width="25%">
166183
<img src="https://raw.githubusercontent.com/marticliment/UniGetUI/refs/heads/main/media/icon.svg" width="60px" alt="UniGetUI Logo"><br/>
167184
<b><a href="https://github.com/marticliment/UniGetUI" title="Visit UniGetUI on GitHub">UniGetUI</a></b><br/>
168185
<sub>Discover, install and update packages</sub>

modules/tools/ExternalLauncher.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if (-not $tool) {
2424

2525
$Host.UI.RawUI.WindowTitle = "$($tool.name) Launcher"
2626

27-
Write-Header -Text "Launching $($tool.name)..."
27+
Write-Log -Message "Launching $($tool.name)..." -Level INFO
2828

2929
try {
3030
switch ($tool.type) {

modules/windots/Organizer.ps1

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,23 @@ foreach ($item in $excludeList) {
4141
$null = $excludeHash.Add($item)
4242
}
4343

44+
function Test-IsExcluded {
45+
param(
46+
[string]$BaseName,
47+
[string]$Name
48+
)
49+
if ($excludeHash.Contains($Name) -or $excludeHash.Contains($BaseName) -or $BaseName -like "Uninstall*") {
50+
return $true
51+
}
52+
foreach ($term in $excludeList) {
53+
if ($BaseName -like "*$term*") { return $true }
54+
}
55+
return $false
56+
}
57+
4458
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -and
4559
(Test-Path -Path $targetPaths[1])) {
46-
Write-Host "`nAdministrator rights required for system directory!`n" -ForegroundColor Red
60+
Write-Log -Message "Administrator rights required for system directory!" -Level ERROR
4761

4862
$adminLaunchPath = Join-Path -Path $PSScriptRoot -ChildPath "..\..\scripts\AdminLaunch.ps1"
4963
. $adminLaunchPath
@@ -55,81 +69,69 @@ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti
5569
try {
5670
$dirsToBackup = $targetPaths | Where-Object { Test-Path $_ }
5771
if ($dirsToBackup) {
58-
Write-Host "`nCreating backup to: $backupPath" -ForegroundColor Cyan
72+
Write-Log -Message "Creating backup to: $backupPath" -Level INFO
5973
Compress-Archive -Path $dirsToBackup -DestinationPath $backupPath -CompressionLevel Fastest
6074

6175
if (Test-Path $backupPath) {
62-
Write-Host "Backup successful. Size: $('{0:N2} MB' -f ((Get-Item $backupPath).Length/1MB))" -ForegroundColor Green
63-
Write-Host "Press Enter to continue" -ForegroundColor DarkCyan
76+
Write-Log -Message "Backup successful. Size: $('{0:N2} MB' -f ((Get-Item $backupPath).Length/1MB))" -Level SUCCESS
77+
Write-Log -Message "Press Enter to continue" -Level INFO
6478
$null = Read-Host
6579
}
6680
else {
67-
Write-Host "Backup failed! Aborting operation." -ForegroundColor Red
81+
Write-Log -Message "Backup failed! Aborting operation." -Level ERROR
6882
exit
6983
}
7084
}
7185
}
7286
catch {
73-
Write-Host "Backup error: $_" -ForegroundColor Red
87+
Write-Log -Message "Backup error: $_" -Level ERROR
7488
exit
7589
}
7690

7791
foreach ($targetDir in $targetPaths) {
7892
if (-not (Test-Path -Path $targetDir)) {
79-
Write-Host "`nSkipping missing directory: $targetDir" -ForegroundColor Yellow
93+
Write-Log -Message "Skipping missing directory: $targetDir" -Level WARNING
8094
continue
8195
}
8296

83-
Write-Host "`nProcessing directory: $targetDir" -ForegroundColor Cyan
97+
Write-Log -Message "Processing directory: $targetDir" -Level INFO
8498

8599
$subFolders = Get-ChildItem -Path $targetDir -Directory | Where-Object {
86100
$_.Name -notmatch $excludeRegex -and
87101
-not $excludeHash.Contains($_.Name) -and
88-
-not (Get-ChildItem -Path $_.FullName -Recurse -ErrorAction SilentlyContinue |
89-
Where-Object {
90-
$file = $_
91-
$excludeHash.Contains($_.Name) -or
92-
$excludeHash.Contains($_.BaseName) -or
93-
($excludeList | Where-Object { $file.BaseName -like "*$_*" }) -or
94-
$_.BaseName -like "Uninstall*"
95-
})
102+
-not (Get-ChildItem -Path $_.FullName -Recurse -ErrorAction SilentlyContinue |
103+
Where-Object { Test-IsExcluded -BaseName $_.BaseName -Name $_.Name })
96104
}
97105

98106
foreach ($folder in $subFolders) {
99107
$folderName = $folder.Name
100108
$folderFullPath = $folder.FullName
101109

102110
$files = Get-ChildItem -Path $folderFullPath -File -Recurse -ErrorAction SilentlyContinue |
103-
Where-Object {
104-
$file = $_
105-
-not ($excludeList | Where-Object { $file.BaseName -like "*$_*" }) -and
106-
-not $excludeHash.Contains($_.Name) -and
107-
-not $excludeHash.Contains($_.BaseName) -and
108-
-not ($_.BaseName -like "Uninstall*")
109-
}
111+
Where-Object { -not (Test-IsExcluded -BaseName $_.BaseName -Name $_.Name) }
110112

111113
if (-not $files) {
112-
Write-Host "`nNo movable files in: $folderName" -ForegroundColor DarkGray
114+
Write-Log -Message "No movable files in: $folderName" -Level SKIP
113115
continue
114116
}
115117

116-
Write-Host "`nFolder: $folderName" -ForegroundColor Cyan
117-
Write-Host "Contains these files:" -ForegroundColor White
118+
Write-Log -Message "Folder: $folderName" -Level INFO
119+
Write-Log -Message "Contains these files:" -Level INFO
118120
$files | ForEach-Object {
119-
Write-Host " - $($_.Name)" -ForegroundColor Gray
121+
Write-Log -Message " - $($_.Name)" -Level INFO
120122
}
121123

122124
do {
123125
$response = Read-Host "`nMove $($files.Count) files from '$folderName'? (Y/N/Q)"
124126
$response = $response.Trim().ToUpper()
125127
if ($response -eq 'Q') {
126-
Write-Host "Operation cancelled by user." -ForegroundColor Yellow
128+
Write-Log -Message "Operation cancelled by user." -Level WARNING
127129
exit
128130
}
129131
} until ($response -match '^[YN]$')
130132

131133
if ($response -eq 'N') {
132-
Write-Host "Skipping folder: $folderName" -ForegroundColor Yellow
134+
Write-Log -Message "Skipping folder: $folderName" -Level SKIP
133135
continue
134136
}
135137

@@ -140,7 +142,7 @@ foreach ($targetDir in $targetPaths) {
140142
try {
141143
$destination = Join-Path -Path $targetDir -ChildPath $file.Name
142144
if (Test-Path $destination) {
143-
Write-Host "Replacing existing: $($file.Name)" -ForegroundColor DarkYellow
145+
Write-Log -Message "Replacing existing: $($file.Name)" -Level WARNING
144146
}
145147
Move-Item -Path $file.FullName -Destination $targetDir -Force -ErrorAction Stop
146148
$movedCount++
@@ -151,24 +153,24 @@ foreach ($targetDir in $targetPaths) {
151153
}
152154

153155
if ($errors.Count -gt 0) {
154-
Write-Host "`nCompleted with $($errors.Count) errors:" -ForegroundColor Red
155-
$errors | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
156+
Write-Log -Message "Completed with $($errors.Count) errors:" -Level ERROR
157+
$errors | ForEach-Object { Write-Log -Message " $_" -Level ERROR }
156158
}
157159
else {
158-
Write-Host "`nSuccessfully moved $movedCount files" -ForegroundColor Green
160+
Write-Log -Message "Successfully moved $movedCount files" -Level SUCCESS
159161
}
160162

161163
try {
162164
$remainingItems = Get-ChildItem -Path $folderFullPath -Recurse -Force -ErrorAction SilentlyContinue
163165
if (-not $remainingItems) {
164166
Remove-Item -Path $folderFullPath -Recurse -Force -ErrorAction Stop
165-
Write-Host "Cleaned empty folder: $folderName" -ForegroundColor DarkGray
167+
Write-Log -Message "Cleaned empty folder: $folderName" -Level INFO
166168
}
167169
}
168170
catch {
169-
Write-Host "Error cleaning folder: $_" -ForegroundColor Red
171+
Write-Log -Message "Error cleaning folder: $_" -Level ERROR
170172
}
171173
}
172174
}
173175

174-
Write-Host "`nOperation completed! Backup saved to: $backupPath`n" -ForegroundColor Green
176+
Write-Log -Message "Operation completed! Backup saved to: $backupPath" -Level SUCCESS

0 commit comments

Comments
 (0)