feat(bloatware): toggleable Outlook/Snipping removal, keep Snipping by default
All checks were successful
release / build-and-release (push) Successful in 40s

Add three GUI feature toggles to the bloatware step:
- standardBloatware (default on)  - the bulk AppX/capability/feature list
- removeNewOutlook   (default on)  - new Outlook for Windows (Microsoft.OutlookForWindows)
- removeSnippingTool (default OFF) - Snipping Tool across all three lists

Each toggle is independent via Test-RemovalAllowed in 01-bloatware.ps1.
Snipping Tool (ScreenSketch + legacy capability/feature) is now kept by
default as a common productivity tool, like Calculator. Classic Outlook from
M365 is a Win32 app and was never touched; only the bundled new Outlook is.

Also fix a latent bug: the Go Config struct had no Bloatware field, so the
GUI's runtime-config regeneration silently dropped bloatware.keepPackages.
Added the field so the keep-list survives to the script.

Docs: SPEC.md, CHANGELOG.md, web/data/descriptions.json, web/spec/index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
X9 Dev 2026-06-03 16:27:34 +02:00
parent d02411adfd
commit fc35450eb0
8 changed files with 98 additions and 3 deletions

View file

@ -8,7 +8,24 @@ Builds are continuous: every push to `main` produces a signed `xetup.exe` publis
## [Unreleased] ## [Unreleased]
_Nothing yet._ ### Added
- **Bloatware feature toggles** (01): the bloatware step now exposes three GUI checkboxes -
`standardBloatware` (default on, the bulk AppX/capability/feature list), `removeNewOutlook`
(default on, the new Outlook for Windows app `Microsoft.OutlookForWindows`) and
`removeSnippingTool` (default OFF). Each toggle is independent, so a technician can spare
Outlook or remove the Snipping Tool without affecting the rest.
### Changed
- **Snipping Tool now kept by default** (01): `Microsoft.ScreenSketch` (the modern Snipping Tool
app) plus the legacy capability and optional feature are no longer removed unless
`removeSnippingTool` is checked - it is a commonly used productivity tool, like Calculator.
Classic Outlook from M365 was never removed (it is a Win32 app, not an AppX package); only the
bundled new Outlook is, and that is now toggleable.
### Fixed
- **`bloatware.keepPackages` was dropped at runtime**: the Go `Config` struct had no `Bloatware`
field, so the GUI's runtime-config regeneration silently discarded `keepPackages`. Added the
field so the keep-list survives and is honored by `01-bloatware.ps1`.
## [0.8] - 2026-06-02 ## [0.8] - 2026-06-02

11
SPEC.md
View file

@ -63,6 +63,15 @@ Removes ~35 AppX packages (Cortana, Copilot, Teams, Xbox, Skype, News, etc.),
~14 Windows Capabilities (Fax, IE, WordPad, etc.), and Optional Features ~14 Windows Capabilities (Fax, IE, WordPad, etc.), and Optional Features
(PowerShell 2.0, Recall). Calculator intentionally kept. (PowerShell 2.0, Recall). Calculator intentionally kept.
Three GUI feature toggles gate removal:
- `standardBloatware` (default on) - the bulk list above.
- `removeNewOutlook` (default on) - the new Outlook for Windows app
(`Microsoft.OutlookForWindows`). Classic Outlook from M365 is a Win32 app and
is never touched.
- `removeSnippingTool` (default OFF) - Snipping Tool across all three lists
(ScreenSketch app + legacy capability + legacy feature). Kept by default as a
common productivity tool, like Calculator.
--- ---
## Step 02 - Software installation ## Step 02 - Software installation
@ -184,7 +193,7 @@ properties (logging the raw objects printed "System.__ComObject").
"activation": { "productKey": "", "kmsServer": "" }, "activation": { "productKey": "", "kmsServer": "" },
"software": { "install": [{ "name": "...", "wingetId": "..." }] }, "software": { "install": [{ "name": "...", "wingetId": "..." }] },
"steps": { "adminAccount": true, ... }, "steps": { "adminAccount": true, ... },
"features": { "software": { "wingetInstalls": true, "pdfDefault": true, "ateraAgent": true }, ... }, "features": { "bloatware": { "standardBloatware": true, "removeNewOutlook": true, "removeSnippingTool": false }, "software": { "wingetInstalls": true, ... }, ... },
"bloatware": { "keepPackages": ["Microsoft.WindowsCalculator"] } "bloatware": { "keepPackages": ["Microsoft.WindowsCalculator"] }
} }
``` ```

View file

@ -33,6 +33,11 @@
"windowsUpdate": true "windowsUpdate": true
}, },
"features": { "features": {
"bloatware": {
"standardBloatware": true,
"removeNewOutlook": true,
"removeSnippingTool": false
},
"software": { "software": {
"wingetInstalls": true, "wingetInstalls": true,
"pdfDefault": true, "pdfDefault": true,

View file

@ -14,6 +14,7 @@ type Config struct {
Software Software `json:"software"` Software Software `json:"software"`
Steps map[string]bool `json:"steps"` Steps map[string]bool `json:"steps"`
Features Features `json:"features"` Features Features `json:"features"`
Bloatware Bloatware `json:"bloatware"`
} }
type Deployment struct { type Deployment struct {
@ -41,6 +42,12 @@ type Software struct {
Install []SoftwareItem `json:"install"` Install []SoftwareItem `json:"install"`
} }
// Bloatware holds bloatware-removal config. KeepPackages lists AppX package
// names that must never be removed, on top of the always-kept defaults.
type Bloatware struct {
KeepPackages []string `json:"keepPackages"`
}
// Features holds per-step, per-feature toggle flags. // Features holds per-step, per-feature toggle flags.
// Keys: stepID -> featureID -> enabled. // Keys: stepID -> featureID -> enabled.
// A missing key defaults to true (feature enabled). // A missing key defaults to true (feature enabled).
@ -80,6 +87,11 @@ func DefaultConfig() Config {
"windowsUpdate": true, "windowsUpdate": true,
}, },
Features: Features{ Features: Features{
"bloatware": {
"standardBloatware": true,
"removeNewOutlook": true,
"removeSnippingTool": false,
},
"software": { "software": {
"wingetInstalls": true, "wingetInstalls": true,
"pdfDefault": true, "pdfDefault": true,
@ -102,6 +114,9 @@ func DefaultConfig() Config {
"bios": true, "bios": true,
}, },
}, },
Bloatware: Bloatware{
KeepPackages: []string{"Microsoft.WindowsCalculator"},
},
} }
} }

View file

@ -58,6 +58,11 @@ type Feature struct {
// have no sub-features and are controlled at the step level only. // have no sub-features and are controlled at the step level only.
func StepFeatures() map[string][]Feature { func StepFeatures() map[string][]Feature {
return map[string][]Feature{ return map[string][]Feature{
"bloatware": {
{ID: "standardBloatware", Label: "Standardni bloatware (AppX, capabilities, features)"},
{ID: "removeNewOutlook", Label: "Novy Outlook for Windows"},
{ID: "removeSnippingTool", Label: "Vystrizky / Snipping Tool"},
},
"software": { "software": {
{ID: "wingetInstalls", Label: "Instalace SW ze seznamu (winget)"}, {ID: "wingetInstalls", Label: "Instalace SW ze seznamu (winget)"},
{ID: "pdfDefault", Label: "Adobe Reader jako vychozi PDF"}, {ID: "pdfDefault", Label: "Adobe Reader jako vychozi PDF"},

View file

@ -12,6 +12,7 @@
zachovano-microsoft-windowscalculator: Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store. zachovano-microsoft-windowscalculator: Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store.
windows-capabilities-fax-ie-openssh-wmp-: Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist. windows-capabilities-fax-ie-openssh-wmp-: Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist.
windows-optional-features-ps-2-0-mediapl: Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component. windows-optional-features-ps-2-0-mediapl: Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component.
feature-toggles: Three GUI feature flags gate removal. standardBloatware (default on) covers the bulk list. removeNewOutlook (default on) controls Microsoft.OutlookForWindows; classic Outlook from M365 is a Win32 app and is never touched. removeSnippingTool (default OFF) controls Snipping Tool across all three lists (ScreenSketch app + legacy capability + legacy feature) - kept by default as a common productivity tool, like Calculator.
#> #>
param( param(
[string]$ConfigPath, [string]$ConfigPath,
@ -21,6 +22,33 @@ param(
. "$PSScriptRoot\common.ps1" . "$PSScriptRoot\common.ps1"
$Config = Load-Config $ConfigPath $Config = Load-Config $ConfigPath
# -----------------------------------------------------------------------
# Feature flags (see CLAUDE.md features system)
# standardBloatware - removes the bulk AppX/capability/feature list
# removeNewOutlook - new Outlook for Windows (Microsoft.OutlookForWindows)
# removeSnippingTool - Snipping Tool, spans all three lists; default OFF
# Outlook and Snipping items are gated by their own flag, independent of the
# standard flag, so each GUI checkbox does exactly what it says.
# -----------------------------------------------------------------------
$DoStandard = Get-Feature $Config "bloatware" "standardBloatware" $true
$DoOutlook = Get-Feature $Config "bloatware" "removeNewOutlook" $true
$DoSnipping = Get-Feature $Config "bloatware" "removeSnippingTool" $false
# Snipping Tool appears as an AppX package (ScreenSketch, the modern app),
# a legacy capability, and a legacy optional feature.
$SnippingItems = @(
"Microsoft.ScreenSketch"
"Microsoft.Windows.SnippingTool"
"Microsoft-SnippingTool"
)
function Test-RemovalAllowed {
param([string]$Name)
if ($Name -eq "Microsoft.OutlookForWindows") { return $DoOutlook }
if ($SnippingItems -contains $Name) { return $DoSnipping }
return $DoStandard
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# 1a - AppX packages # 1a - AppX packages
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -89,6 +117,10 @@ foreach ($pkg in $AppxToRemove) {
Write-Log " KEEP $pkg" -Level INFO Write-Log " KEEP $pkg" -Level INFO
continue continue
} }
if (-not (Test-RemovalAllowed $pkg)) {
Write-Log " KEEP (feature off): $pkg" -Level INFO
continue
}
# Installed packages (current user + all users) # Installed packages (current user + all users)
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue $installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
@ -145,6 +177,10 @@ Write-Log "1b - Removing Windows Capabilities" -Level STEP
$installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue $installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue
foreach ($cap in $CapabilitiesToRemove) { foreach ($cap in $CapabilitiesToRemove) {
if (-not (Test-RemovalAllowed $cap)) {
Write-Log " KEEP (feature off): $cap" -Level INFO
continue
}
# Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0) # Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0)
$matches = $installedCaps | Where-Object { $matches = $installedCaps | Where-Object {
$_.Name -like "$cap*" -and $_.State -eq "Installed" $_.Name -like "$cap*" -and $_.State -eq "Installed"
@ -177,6 +213,10 @@ $FeaturesToDisable = @(
Write-Log "1c - Disabling Windows Optional Features" -Level STEP Write-Log "1c - Disabling Windows Optional Features" -Level STEP
foreach ($feat in $FeaturesToDisable) { foreach ($feat in $FeaturesToDisable) {
if (-not (Test-RemovalAllowed $feat)) {
Write-Log " KEEP (feature off): $feat" -Level INFO
continue
}
$feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue $feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue
if ($feature -and $feature.State -eq "Enabled") { if ($feature -and $feature.State -eq "Enabled") {
try { try {

View file

@ -18,7 +18,8 @@
"appx-balicky-odstraneni-pro-vsechny-uziv": "Uses Remove-AppxPackage -AllUsers and Remove-AppxProvisionedPackage. The provisioned removal prevents apps from reinstalling for new user profiles. Covers ~35 apps including Cortana, Copilot, Teams personal, Xbox, Skype, News, Weather, Maps.", "appx-balicky-odstraneni-pro-vsechny-uziv": "Uses Remove-AppxPackage -AllUsers and Remove-AppxProvisionedPackage. The provisioned removal prevents apps from reinstalling for new user profiles. Covers ~35 apps including Cortana, Copilot, Teams personal, Xbox, Skype, News, Weather, Maps.",
"zachovano-microsoft-windowscalculator": "Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store.", "zachovano-microsoft-windowscalculator": "Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store.",
"windows-capabilities-fax-ie-openssh-wmp-": "Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist.", "windows-capabilities-fax-ie-openssh-wmp-": "Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist.",
"windows-optional-features-ps-2-0-mediapl": "Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component." "windows-optional-features-ps-2-0-mediapl": "Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component.",
"feature-toggles": "Three GUI feature flags gate removal. standardBloatware (default on) covers the bulk list. removeNewOutlook (default on) controls Microsoft.OutlookForWindows; classic Outlook from M365 is a Win32 app and is never touched. removeSnippingTool (default OFF) controls Snipping Tool across all three lists (ScreenSketch app + legacy capability + legacy feature) - kept by default as a common productivity tool, like Calculator."
} }
}, },
"02-software": { "02-software": {

View file

@ -580,6 +580,9 @@
<tr class="flag-done"><td>Zachovano: Microsoft.WindowsCalculator</td><td>Zamerny vyjimek</td></tr> <tr class="flag-done"><td>Zachovano: Microsoft.WindowsCalculator</td><td>Zamerny vyjimek</td></tr>
<tr class="flag-done"><td>Windows Capabilities (Fax, IE, OpenSSH, WMP, WordPad, …)</td><td>Remove-WindowsCapability</td></tr> <tr class="flag-done"><td>Windows Capabilities (Fax, IE, OpenSSH, WMP, WordPad, …)</td><td>Remove-WindowsCapability</td></tr>
<tr class="flag-done"><td>Windows Optional Features (PS 2.0, MediaPlayback, Recall, …)</td><td>Disable-WindowsOptionalFeature</td></tr> <tr class="flag-done"><td>Windows Optional Features (PS 2.0, MediaPlayback, Recall, …)</td><td>Disable-WindowsOptionalFeature</td></tr>
<tr class="flag-done"><td>GUI prepinace: standardBloatware, removeNewOutlook, removeSnippingTool</td><td>Kazdy krok lze v GUI samostatne zaskrtnout/odskrtnout</td></tr>
<tr class="flag-done"><td>Novy Outlook for Windows (Microsoft.OutlookForWindows)</td><td>Default odebran; klasicky Outlook z M365 (Win32) se nedotyka</td></tr>
<tr class="flag-done"><td>Zachovano ve vychozim stavu: Vystrizky / Snipping Tool</td><td>ScreenSketch + legacy capability/feature; default OFF (jako Kalkulacka)</td></tr>
</table> </table>
</div> </div>
<div class="step-footer"> <div class="step-footer">