Hardening my Intune build: baselines, Defender, BitLocker and the bits that broke

8 August 2026 · Luke Gillmore-White

My first Autopilot post covered the basics: registering devices, deployment profiles, the Enrollment Status Page, compliance and Conditional Access. This one is what came after. I wiped my own daily-driver laptop and rebuilt it purely through Autopilot, with the goal of locking it down the way I would a client fleet. It was also a way to find out where the documentation and reality disagree.

The tenant runs Microsoft 365 Business Premium with the Defender Suite add-on. That adds Defender for Endpoint P2, Entra ID P2, and Defender for Office 365 P2.

Structure before settings

Before changing any security settings, I made sure every policy targets the same thing and is named consistently:

Assignment group:  dynamic device group for every Autopilot-registered device
Rule:              (device.devicePhysicalIDs -any (_ -contains "[ZTDId]"))

Naming:            WIN-SEC-*   security baselines
                   WIN-CFG-*   configuration / experience
                   WIN-UPD-*   update rings

A dynamic group built on the Autopilot ZTDId means any device registered with Autopilot picks up the full build automatically. Nobody has to remember to add it to a group.

I also export the whole tenant’s Intune configuration to a Git repo with IntuneCD, using an app registration with delegated Graph permissions. Every change I make in the portal then has a diff and a history behind it, which matters far more once things start breaking.

Three baselines, not one

The first thing I deployed was the Defender for Endpoint security baseline. It’s a broad policy: BitLocker, attack surface reduction rules, firewall, antivirus, SmartScreen and DMA protection all in one.

Once the device was onboarded to Defender for Endpoint P2, Defender Vulnerability Management came up with 35 security recommendations for it, and most of them were things I thought were already covered. They weren’t, because the Defender baseline isn’t the Windows baseline. I’d only deployed one of them. The fix was to add:

WIN-SEC-Baseline-25H2      Windows 11 security baseline
WIN-SEC-Baseline-M365Apps  Microsoft 365 Apps security baseline

Layering baselines means they can conflict. The Windows and Defender baselines disagreed on Allow Local Policy Merge and Allow Local IPsec Policy Merge, so I set both to the same value in each. Intune reports a conflict whenever two policies disagree, even if one value would have won anyway, so it’s cleaner to resolve them at the source.

Defender for Endpoint, and the licence that silently loses

Onboarding is straightforward: an EDR policy using the connector’s onboarding package, EDR in block mode, and a separate Windows Security Experience policy to turn on tamper protection.

What wasn’t straightforward was this. Advanced hunting was missing every Device* table, even though MDE P2 was licensed. The tenant also held a Defender for Business licence, and in a mixed-licensing tenant Defender for Business wins by default. Every device was being treated as Business-tier. The fix is in Defender portal → Settings → Endpoints → Licenses → Manage subscription settings, where you switch the tenant to Defender for Endpoint Plan 2. Nothing warns you about this.

BitLocker: 128-bit, and read-only USB sticks

After the rebuild the drive came up encrypted with XTS-AES 128, not 256. My first assumption was that Windows’ automatic device encryption had started before the policy arrived. It hadn’t. The baseline itself was set to 128-bit, and I’d misread the enum values when checking the raw JSON. I set it to XTS-AES 256 for OS and fixed drives and AES-CBC 256 for removable drives, then decrypted and re-encrypted so the new method took effect.

The second issue appeared weeks later: unencrypted USB sticks were mounting read-only. The cause is the BitLocker setting Deny write access to removable drives not protected by BitLocker. That’s sensible for a fleet, but it gets in the way when you regularly move files with plain USB sticks. Two things made it slow to track down:

  • The setting is delivered through the BitLocker CSP, so it doesn’t appear under the usual HKLM\SOFTWARE\Policies\Microsoft\FVE key. The real value lives under HKLM\SOFTWARE\Microsoft\PolicyManager\providers\<id>\default\Device\BitLocker.
  • Both baselines set it, so it had to be turned off in both. And the portal displays the Group Policy friendly name rather than the CSP name (RemovableDrivesRequireEncryption), which makes it harder to search for.

Compliance and Conditional Access

The compliance policy now requires Secure Boot, encryption, firewall, antispyware, real-time protection, up-to-date signatures and a minimum OS build, with a one-day grace period.

That minimum build caught me out once. The install media was 24H2 while the device had been on 25H2 before the wipe, so it came back non-compliant until I lowered the requirement.

My new Conditional Access policies, requiring a compliant device, MFA for all users and MFA for device join, are deliberately still in report-only. They stay that way until I have proper break-glass accounts. Switching on “require compliant device” without an excluded emergency account is how you lock yourself out of your own tenant.

Passwordless

Sign-in is Windows Hello for Business (PIN minimum 6, with security key sign-in allowed) plus a YubiKey registered as a passkey. In the authentication methods policy, passkeys, Authenticator and Temporary Access Pass are enabled, while SMS and voice are switched off entirely.

Apps: why every Win32 app failed

Several apps I needed (Tailscale, Git, 7-Zip, Python, PowerToys) aren’t in the Intune Store catalogue. So I built one generic Win32 package that wraps winget: an install, uninstall and detect script that each take a package ID. It’s uploaded once per app with a different ID.

On the rebuild, every one of those apps failed. Two separate problems were stacked on top of each other:

  1. Finding winget under SYSTEM. Resolving winget.exe by browsing WindowsApps fails without the right permissions. Asking the App Installer package where it lives works reliably:
$appInstaller = Get-AppxPackage -AllUsers Microsoft.DesktopAppInstaller |
    Sort-Object Version -Descending | Select-Object -First 1
$winget = Join-Path $appInstaller.InstallLocation 'winget.exe'
  1. The msstore source. winget’s Microsoft Store source was throwing a certificate error. That left winget unable to work out which source a package came from, so it refused to install. Intune reported this as a generic 0x80070001. Pinning the source fixed it:
& $winget install --id $AppId --source winget --silent `
    --accept-package-agreements --accept-source-agreements --scope machine

One app still wouldn’t go through winget, because the vendor’s download server returned 403 partway through. So OpenVPN Connect is packaged straight from its MSI instead.

There was one more surprise, from my own hardening. The ASR rule Block executable files unless they meet a prevalence, age or trusted list criterion blocked pip.exe and IntuneCD’s own executable. I now use python -m pip, and IntuneCD has a targeted exclusion.

Updates and the rest

WIN-UPD-Ring1:  quality deferral 2 days, feature deferral 0
                deadlines 2 days (quality) / 5 days (feature), 1-day grace
WIN-CFG-Experience:  taskbar layout, no Spotlight, no widgets,
                     no web results in search, no consumer features

One honest exception to best practice: on this device the signed-in user is a local administrator. That’s my own choice for my own laptop, and it’s set in the Autopilot profile, so it’s explicit rather than something that crept in. It’s not how I’d configure a client device.

Result

The laptop now rebuilds from nothing into a hardened, compliant, fully patched machine with every app installed, purely from Autopilot and with no manual steps. The whole configuration lives in Git. More valuable than the end state, though, is the list of things that went wrong on the way: the mixed-licensing default, BitLocker settings that don’t live where the documentation suggests, and winget’s source disambiguation. Those are exactly the kinds of problems that cost hours on a real client rollout, and now I’ve already hit them.