~/2026/06/17/windows-11-a-debloat-and-hardening-baseline-for-new-deployments.md
Windows 11: A Debloat and Hardening Baseline for New Deployments
--- author: Tom Lasswell date: read: 8 min in: [engineering] tags: [windows-11, debloat, gpo, intune] ---
$ grep -n '^#' post.md
Every new Windows 11 deployment starts from the same place: an image or a fresh OOBE install carrying a set of consumer-oriented defaults that have no business on a managed business endpoint. Widgets, a taskbar full of pinned consumer apps, Start suggestions, and a handful of provisioned apps nobody in the fleet asked for. None of it is individually a big deal, but treated as a checklist instead of an afterthought, it turns into a baseline that's consistent across every machine that ships, which matters more than any single setting on its own.
This post is the checklist I use, with the scripts behind it. The companion script posts go deeper on the two debloat paths: debloating a golden image before Sysprep and stripping apps from a deployment image.
Decide what "debloat" actually means before touching anything
The word gets used loosely enough to mean anything from removing a game bar overlay to disabling Windows Update entirely, and the second interpretation is how debloat scripts turn into support tickets six months later. My working definition is narrower: remove provisioned consumer apps that serve no business purpose, turn off consumer experiences that fight the management stack, and leave anything that's arguably still useful alone: Snipping Tool, Terminal, Calculator, Notepad, Quick Assist if your help desk uses it. A debloat pass that quietly disables Windows Update, removes the Store's ability to service inbox apps, or rips out Defender components because a blog post said so is a hardening regression wearing a hardening costume.
In practice that's three settings groups:
- Provisioned apps. News, Weather, Solitaire, the Xbox overlays, Clipchamp, Feedback Hub.
- Consumer experiences. The "Turn off Microsoft consumer experiences" and "Do not show Windows tips" policies under Computer Configuration > Administrative Templates > Windows Components > Cloud Content. Both are honored only on Enterprise and Education; the Experience Policy CSP lists Pro as not applicable.
- Widgets. "Allow widgets" under Windows Components > Widgets (registry
HKLM\SOFTWARE\Policies\Microsoft\Dsh, valueAllowNewsAndInterests= 0). This one does apply to Pro.
Use the policy for app removal if your edition allows it
For a long time the only way to remove inbox apps was a script: Remove-AppxPackage for existing users, Remove-AppxProvisionedPackage for future ones, run in the image or at provisioning. That still works, and it's the only option on Pro, but on Windows 11, version 24H2 or later, Enterprise and Education there's now a policy: Remove default Microsoft Store packages from the system (Group Policy, under Windows Components > App Package Deployment) or ApplicationManagement/RemoveDefaultMicrosoftStorePackages in the Policy CSP.
What makes it better than a script is the lifecycle. Removal runs at OOBE, at the first sign-in after an OS upgrade, and at the first sign-in after the policy changes, and an app stays blocked from reinstalling (including from the Store) while it's selected. A feature update that would have brought an app back on a scripted device doesn't on a policy-managed one. The documented limits are worth knowing before you commit: it's device-scoped only, multi-session environments aren't supported, existing profiles only lose the app at their next sign-in, and deselecting an app doesn't reinstall it. The strip-apps post has the full OMA-URI payload and the AppXDeploymentServer event IDs (606, 614, 762) for verifying it.
Start from Microsoft's security baseline, not a blank page
Microsoft's own guidance is to implement a broadly known, well-tested configuration, such as the Microsoft security baselines, rather than building one yourself. The baselines ship in the Security Compliance Toolkit as GPO backups you can import into GPMC, and as MDM security baselines in Intune. They're designed for organizations where standard users don't have admin rights, and they only enforce a setting when it mitigates a current threat without causing worse operational problems. That design principle is exactly what you want from a baseline: it saves you arguing about three thousand Group Policy settings.
The baseline covers a lot, but I still verify four things explicitly on a fresh device, because they're the ones that most often end up wrong in practice: disk encryption, Defender's cloud protection, attack surface reduction rules, and Credential Guard.
The four hardening settings I check on every device
BitLocker with a backed-up recovery key. The BitLocker cmdlet reference describes the pattern: add a recovery password protector, back it up, then enable BitLocker with the TPM protector. Enabling without an escrowed recovery password is how a firmware update turns into a data-loss event. XTS-AES-128 is the default; I specify XTS-AES-256 and skip hardware encryption, which Microsoft's reference advises against (security advisory ADV180028).
Defender cloud-delivered protection and sample submission. Cloud protection is on by default, but earlier policy can have turned it off (the Set-MpPreference reference still lists MAPSReporting as defaulting to Disabled), so I set it explicitly: MAPSReporting to Advanced, and SubmitSamplesConsent to SendSafeSamples. Several ASR rules (obfuscated scripts, ransomware protection, prevalence-based blocking) require cloud-delivered protection to work at all.
ASR rules. Microsoft groups three rules as "standard protection" that you can typically enable in Block mode without an audit period: block abuse of exploited vulnerable signed drivers, block credential stealing from LSASS, and block persistence through WMI event subscription. Everything else goes into Audit first. One nuance from the reference: if LSA protection or Credential Guard is on, the LSASS rule adds nothing and shows as not applicable. And if you use Configuration Manager, test the WMI persistence rule in Audit first, since the ConfigMgr client relies heavily on WMI.
Credential Guard. On Windows 11 22H2 and later, Credential Guard is enabled by default on Enterprise and Education devices that meet the hardware and license requirements, unless it was explicitly disabled before the upgrade. That "unless" is why I check rather than assume. Microsoft also says to enable it before domain join or before the first domain user signs in, so it belongs in provisioning, not in a post-deployment fix. It isn't available on Pro.
Here's the local script I run in a provisioning step (an Autopilot platform script, a task sequence step, or by hand on a lab device). It's deliberately explicit so you can read exactly what it does:
<#
.SYNOPSIS
Applies the local hardening defaults for a new Windows 11 device.
.DESCRIPTION
Adds and escrows a BitLocker recovery password, enables BitLocker on the OS drive
with the TPM protector (XTS-AES-256, used space only), sets Defender cloud
protection and sample submission, puts the three standard-protection ASR rules in
Block and a set of other rules in Audit, and reports Credential Guard status.
Policy from Intune, ConfigMgr or GPO overrides these local settings, which is the
intended end state.
.PARAMETER KeyBackup
Where to escrow the BitLocker recovery password: AD or Entra.
.EXAMPLE
.\Set-EndpointHardening.ps1 -KeyBackup Entra
.NOTES
Author : Thomas Lasswell (https://www.techcolumnist.com)
Version : 1.0 (2026-06-17)
Requires: Windows 11 Pro/Enterprise/Education, elevated Windows PowerShell 5.1,
BitLocker and Defender modules (in box). ASR rules also need
Microsoft Defender Antivirus as the primary antivirus, in Active mode.
#>
#Requires -RunAsAdministrator
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateSet("AD", "Entra")]
[string]$KeyBackup
)
$osDrive = $env:SystemDrive
# BitLocker: recovery password first, escrow it, then the TPM protector.
$volume = Get-BitLockerVolume -MountPoint $osDrive
if ($volume.VolumeStatus -eq "FullyDecrypted") {
$recovery = $volume.KeyProtector | Where-Object { $_.KeyProtectorType -eq "RecoveryPassword" } | Select-Object -First 1
if (-not $recovery) {
Add-BitLockerKeyProtector -MountPoint $osDrive -RecoveryPasswordProtector | Out-Null
$recovery = (Get-BitLockerVolume -MountPoint $osDrive).KeyProtector |
Where-Object { $_.KeyProtectorType -eq "RecoveryPassword" } | Select-Object -First 1
}
if ($KeyBackup -eq "Entra") {
BackupToAAD-BitLockerKeyProtector -MountPoint $osDrive -KeyProtectorId $recovery.KeyProtectorId | Out-Null
} else {
Backup-BitLockerKeyProtector -MountPoint $osDrive -KeyProtectorId $recovery.KeyProtectorId | Out-Null
}
Enable-BitLocker -MountPoint $osDrive -TpmProtector -EncryptionMethod XtsAes256 -UsedSpaceOnly | Out-Null
Write-Host "BitLocker enabled on $osDrive; recovery password escrowed to $KeyBackup."
} else {
Write-Host "BitLocker already $($volume.VolumeStatus) on $osDrive; leaving it alone."
}
# Defender cloud-delivered protection and automatic safe-sample submission.
Set-MpPreference -MAPSReporting Advanced -SubmitSamplesConsent SendSafeSamples -PUAProtection Enabled
# ASR: standard protection rules in Block.
$blockRules = @(
# Block abuse of exploited vulnerable signed drivers
"56a863a9-875e-4185-98a7-b882c64b5ce5",
# Block credential stealing from LSASS
"9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2",
# Block persistence through WMI event subscription
"e6db77e5-3df2-4cf1-b95a-636979351e5b"
)
# ASR: other rules in Audit until the event data says they're safe to block.
$auditRules = @(
# Block executable content from email client and webmail
"be9ba2d9-53ea-4cdc-84e5-9b1eeee46550",
# Block all Office applications from creating child processes
"d4f940ab-401b-4efc-aadc-ad5f3c50688a",
# Block Office applications from creating executable content
"3b576869-a4ec-4529-8536-b80a7769e899",
# Block Office applications from injecting code into other processes
"75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84",
# Block JavaScript or VBScript from launching downloaded executable content
"d3e037e1-3eb8-44c8-a917-57927947596d",
# Block execution of potentially obfuscated scripts
"5beb7efe-fd9a-4556-801d-275e5ffc04cc",
# Block Win32 API calls from Office macros
"92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b",
# Use advanced protection against ransomware
"c1db55ab-c21a-4637-bb3f-a12568109d35"
)
Add-MpPreference -AttackSurfaceReductionRules_Ids $blockRules -AttackSurfaceReductionRules_Actions (@("Enabled") * $blockRules.Count)
Add-MpPreference -AttackSurfaceReductionRules_Ids $auditRules -AttackSurfaceReductionRules_Actions (@("AuditMode") * $auditRules.Count)
# Credential Guard: report only; enable it through policy before domain join.
$deviceGuard = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace "root\Microsoft\Windows\DeviceGuard"
$cgRunning = @($deviceGuard.SecurityServicesRunning) -contains 1
Write-Host "Credential Guard running: $cgRunning"
Set-MpPreference has the lowest precedence of any ASR configuration method. Group Policy and MDM overwrite it at startup. That's fine: the script gives a lab or pilot device a sane state on day one, and the real baseline lives in policy.
Bake it into provisioning, not into a post-deployment script
The version of this baseline that actually holds up over time lives in the provisioning pipeline: an Autopilot profile with the app removal policy, security baseline and endpoint security profiles assigned to device groups, or GPOs linked to the workstation OU. Not a PowerShell script run once by hand on day one. A script run once drifts the moment someone reimages a machine outside the documented path or restores from an older image during a break-fix. A baseline enforced through the same mechanism that provisions every machine self-heals on the next sync, which is the difference between a policy and a one-time favor to your future self.
For Autopilot specifically, Microsoft recommends configuring the Enrollment Status Page to block until device configuration completes so the app removal policy lands before the user reaches the desktop. If it arrives late, apps can appear until the following sign-in.
Measure what the baseline actually enforces
The debloat half is easy to verify (a provisioned app is either gone or it isn't), but the hardening half needs an audit trail. Pull BitLocker status, Defender configuration, ASR rule state and Credential Guard across the fleet on a schedule, not just at image-build time. A setting that's correct in the base image but gets overridden by a conflicting GPO six months later is functionally the same as never having set it, and the only way to catch that kind of regression is to keep checking.
This is the audit I schedule weekly. It fans out over PowerShell remoting, so it needs WinRM on the targets and an account in the Administrators group (or Remote Management Users with a suitably configured endpoint):
<#
.SYNOPSIS
Reports BitLocker, Defender, ASR, Credential Guard and provisioned-app state across
a list of Windows 11 computers.
.PARAMETER ComputerName
Computers to audit.
.PARAMETER OutputPath
CSV to write. Defaults to .\baseline-audit.csv.
.EXAMPLE
.\Get-BaselineCompliance.ps1 -ComputerName (Get-Content .\workstations.txt)
.NOTES
Author : Thomas Lasswell (https://www.techcolumnist.com)
Version : 1.0 (2026-06-17)
Requires: PowerShell remoting on targets; admin rights on targets
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string[]]$ComputerName,
[string]$OutputPath = ".\baseline-audit.csv"
)
$auditScript = {
$os = Get-BitLockerVolume -MountPoint $env:SystemDrive
$status = Get-MpComputerStatus
$prefs = Get-MpPreference
$deviceGuard = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace "root\Microsoft\Windows\DeviceGuard"
# Pair each ASR rule GUID with its action (0 off, 1 block, 2 audit, 6 warn).
$asr = @()
for ($i = 0; $i -lt @($prefs.AttackSurfaceReductionRules_Ids).Count; $i++) {
$asr += "$($prefs.AttackSurfaceReductionRules_Ids[$i])=$($prefs.AttackSurfaceReductionRules_Actions[$i])"
}
$consumerApps = @(Get-AppxProvisionedPackage -Online | Where-Object {
$_.DisplayName -match "BingNews|BingWeather|SolitaireCollection|GamingApp|Clipchamp"
})
[pscustomobject]@{
ComputerName = $env:COMPUTERNAME
BitLockerStatus = "$($os.VolumeStatus)"
BitLockerProtection = "$($os.ProtectionStatus)"
EncryptionMethod = "$($os.EncryptionMethod)"
HasRecoveryPassword = [bool]($os.KeyProtector | Where-Object { $_.KeyProtectorType -eq "RecoveryPassword" })
DefenderService = $status.AMServiceEnabled
RealTimeProtection = $status.RealTimeProtectionEnabled
SignaturesUpdated = $status.AntivirusSignatureLastUpdated
MAPSReporting = $prefs.MAPSReporting
AsrRules = ($asr -join ";")
CredentialGuard = @($deviceGuard.SecurityServicesRunning) -contains 1
ConsumerAppsRemaining = $consumerApps.Count
}
}
$results = Invoke-Command -ComputerName $ComputerName -ScriptBlock $auditScript -ErrorAction SilentlyContinue -ErrorVariable remoteErrors
$results | Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName |
Export-Csv -Path $OutputPath -NoTypeInformation
Write-Host "Audited $(@($results).Count) of $($ComputerName.Count) computers; results in $OutputPath"
foreach ($err in $remoteErrors) {
Write-Warning $err.Exception.Message
}
Sample row from a device that drifted:
ComputerName : WKS-0142
BitLockerStatus : FullyEncrypted
BitLockerProtection : Off
EncryptionMethod : XtsAes256
HasRecoveryPassword : True
DefenderService : True
RealTimeProtection : True
SignaturesUpdated : 6/15/2026 3:12:44 AM
MAPSReporting : 2
AsrRules : 56a863a9-875e-4185-98a7-b882c64b5ce5=1;9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2=1;...
CredentialGuard : False
ConsumerAppsRemaining : 0
BitLockerProtection : Off on a fully encrypted volume means the keys are not currently protected, most often because protection was suspended (for example with Suspend-BitLocker ahead of a firmware update) and never resumed; Resume-BitLocker turns it back on. CredentialGuard : False on an Enterprise device is worth checking against msinfo32 and the WinInit events 13 through 17 in the System log that Microsoft documents for Credential Guard.
If you'd rather not run anything, the same questions can be answered by the Intune security baseline and endpoint security reports. I still like having the CSV, because it's the same shape for every device regardless of which management channel it's in, and it feeds straight into the discovery inventory.