From 5fc3818c8ac9ab86f94ca424b85df34fac23a0fe Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Fri, 3 Apr 2026 15:08:25 +0300 Subject: [PATCH 1/5] feat: Add MT.1123 - BitLocker encryption policy check (#intune) --- powershell/Maester.psd1 | 1 + .../Test-MtBitLockerFullDiskEncryption.md | 42 ++++ .../Test-MtBitLockerFullDiskEncryption.ps1 | 206 ++++++++++++++++++ .../Intune/Test-MtIntunePlatform.Tests.ps1 | 7 + tests/maester-config.json | 5 + .../version-2.0.0/tests/maester/MT.1123.md | 42 ++++ 6 files changed, 303 insertions(+) create mode 100644 powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.md create mode 100644 powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 create mode 100644 website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md diff --git a/powershell/Maester.psd1 b/powershell/Maester.psd1 index e2dc8200f..a1b06f41b 100644 --- a/powershell/Maester.psd1 +++ b/powershell/Maester.psd1 @@ -181,6 +181,7 @@ 'Test-MtAppleVolumePurchaseProgramToken', 'Test-MtCertificateConnectors', 'Test-MtFeatureUpdatePolicy', 'Test-MtIntuneDiagnosticSettings', 'Test-MtIntuneRbacGroupsProtected', + 'Test-MtBitLockerFullDiskEncryption', 'Test-MtMdmAuthority', 'Test-MtMobileThreatDefenseConnectors', 'Test-MtTenantCustomization', 'Test-MtWindowsDataProcessor', 'Test-MtXspmCriticalCredsOnDevicesWithNonCriticalAccounts', diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.md b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.md new file mode 100644 index 000000000..58a7e92d8 --- /dev/null +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.md @@ -0,0 +1,42 @@ +Ensure at least one Intune Disk Encryption policy enforces BitLocker with **full disk encryption type**. + +BitLocker Drive Encryption protects data on Windows devices by encrypting the disk. +However, BitLocker supports two encryption types that have very different security implications: + +- **Full disk encryption** — encrypts the entire drive including free space. This is the recommended and secure option. +- **Used space only encryption** — only encrypts sectors currently holding data. **This is dangerous on drives that previously contained unencrypted data**, because previously deleted files remain as raw data in unencrypted free space. This data can be recovered using commonly available data recovery software (e.g., Recuva, PhotoRec, or forensic imaging tools). NTFS marks deleted file sectors as "free" but does not zero them out — the original bytes stay on disk until overwritten by new data. + +**Bottom line:** If BitLocker is enabled with "Used space only" on a drive that already had data on it before encryption was turned on, that pre-existing deleted data is fully recoverable. Only "Full disk encryption" guarantees that the entire drive surface is protected. + +This test queries the Intune **Endpoint Security > Disk Encryption** policies via the `configurationPolicies` Graph API and inspects the BitLocker CSP settings to verify that **Enforce drive encryption type** is set to **Full encryption** for OS drives. + +#### Remediation action: + +1. Navigate to [Microsoft Intune admin center](https://intune.microsoft.com). +2. Go to **Endpoint security** > **Disk encryption**. +3. Click **+ Create policy**. +4. Set **Platform** to **Windows 10 and later** and **Profile** to **BitLocker**. +5. Enter a policy name (e.g., "BitLocker - Full Disk Encryption"). +6. Configure the following settings: + - **Require Device Encryption**: **Enabled** + - **Allow Warning For Other Disk Encryption**: **Disabled** (enables silent encryption) + - **Allow Standard User Encryption**: **Enabled** + - **Enforce drive encryption type on operating system drives**: **Enabled**, set to **Full encryption** + - **Enforce drive encryption type on fixed data drives**: **Enabled**, set to **Full encryption** + - **Choose drive encryption method and cipher strength**: **Enabled** + - OS drives: **XTS-AES 256-bit** + - Fixed data drives: **XTS-AES 256-bit** + - Removable data drives: **AES-CBC 256-bit** + - **Require additional authentication at startup**: **Enabled**, with **Require TPM** + - **Choose how BitLocker-protected OS drives can be recovered**: **Enabled**, with backup to Entra ID +7. Assign the policy to your device groups and click **Create**. + +#### Related links + +- [Microsoft Intune - Endpoint Security Disk Encryption](https://intune.microsoft.com/#view/Microsoft_Intune_Workflows/SecurityManagementMenu/~/diskEncryption) +- [Microsoft Learn - Encrypt devices with BitLocker in Intune](https://learn.microsoft.com/en-us/mem/intune/protect/encrypt-devices) +- [Microsoft Learn - BitLocker CSP reference](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp) +- [CIS Benchmark - Ensure BitLocker is enabled on all Windows devices](https://www.cisecurity.org/benchmark/microsoft_intune_for_windows) + + +%TestResult% diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 new file mode 100644 index 000000000..8e1171b06 --- /dev/null +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 @@ -0,0 +1,206 @@ +function Test-MtBitLockerFullDiskEncryption { + <# + .SYNOPSIS + Ensure at least one Intune Disk Encryption policy enforces BitLocker with full disk encryption type. + + .DESCRIPTION + Checks Intune Endpoint Security Disk Encryption policies (configurationPolicies API) for BitLocker + profiles that enforce full disk encryption rather than "Used space only" encryption. + + BitLocker supports two encryption types with very different security implications: + - "Full disk encryption" — encrypts the entire drive including free space. This is the secure option. + - "Used space only encryption" — only encrypts sectors currently holding data. Previously deleted + files that were written before encryption was enabled remain in unencrypted free space and can be + recovered using data recovery software (e.g., Recuva, PhotoRec, or forensic tools). This is because + NTFS marks sectors as free but does not zero them out — the raw data stays on disk until overwritten. + + This test queries the configurationPolicies Graph API (used by Endpoint Security > Disk Encryption) + which exposes the actual BitLocker CSP settings including: + - SystemDrivesEncryptionType (OS drive encryption type: full vs used space only) + - FixedDrivesEncryptionType (fixed drive encryption type: full vs used space only) + - RequireDeviceEncryption (require BitLocker encryption) + - EncryptionMethodByDriveType (cipher strength: XTS-AES 128/256, AES-CBC 128/256) + + The test passes only if at least one BitLocker Disk Encryption policy has the OS drive encryption + type set to "Full encryption". It fails if no policies exist, if encryption type is set to + "Used space only", or if the encryption type setting is not configured. + + .EXAMPLE + Test-MtBitLockerFullDiskEncryption + + Returns true if at least one Disk Encryption policy enforces full disk encryption for OS drives. + + .LINK + https://maester.dev/docs/commands/Test-MtBitLockerFullDiskEncryption + #> + [CmdletBinding()] + [OutputType([bool])] + param() + + if (-not (Get-MtLicenseInformation -Product Intune)) { + Add-MtTestResultDetail -SkippedBecause NotLicensedIntune + return $null + } + + try { + # Query only Endpoint Security Disk Encryption BitLocker policies (server-side filter) + $bitLockerPolicies = Invoke-MtGraphRequest -RelativeUri "deviceManagement/configurationPolicies?`$filter=templateReference/templateFamily eq 'endpointSecurityDiskEncryption'&`$select=id,name,description,templateReference" -ApiVersion beta + + if (-not $bitLockerPolicies -or $bitLockerPolicies.Count -eq 0) { + $testResultMarkdown = "No Endpoint Security Disk Encryption (BitLocker) policies found in Intune.`n`n" + $testResultMarkdown += "Create a BitLocker policy under **Endpoint Security > Disk Encryption** with " + $testResultMarkdown += "**Enforce drive encryption type** set to **Full encryption** for OS and fixed data drives." + Add-MtTestResultDetail -Result $testResultMarkdown + return $false + } + + # Setting definition IDs for BitLocker CSP settings + $osEncryptionTypeId = 'device_vendor_msft_bitlocker_systemdrivesencryptiontype' + $osEncryptionTypeDropdownId = 'device_vendor_msft_bitlocker_systemdrivesencryptiontype_osencryptiontypedropdown_name' + $fixedEncryptionTypeId = 'device_vendor_msft_bitlocker_fixeddrivesencryptiontype' + $fixedEncryptionTypeDropdownId = 'device_vendor_msft_bitlocker_fixeddrivesencryptiontype_fdeencryptiontypedropdown_name' + $requireEncryptionId = 'device_vendor_msft_bitlocker_requiredeviceencryption' + $encryptionMethodId = 'device_vendor_msft_bitlocker_encryptionmethodbydrivetype' + $osEncryptionMethodDropdownId = 'device_vendor_msft_bitlocker_encryptionmethodbydrivetype_encryptionmethodwithxtsosdropdown_name' + + # Encryption type value suffixes: _0 = Allow user to choose, _1 = Full encryption, _2 = Used Space Only + $encryptionTypeLabels = @{ + '_0' = 'Allow user to choose' + '_1' = 'Full encryption' + '_2' = 'Used Space Only' + } + + # Encryption method value suffixes: _3=AES-CBC 128, _4=AES-CBC 256, _6=XTS-AES 128, _7=XTS-AES 256 + $encryptionMethodLabels = @{ + '_3' = 'AES-CBC 128-bit' + '_4' = 'AES-CBC 256-bit' + '_6' = 'XTS-AES 128-bit' + '_7' = 'XTS-AES 256-bit' + } + + $policyResults = @() + $hasFullEncryption = $false + + foreach ($policy in $bitLockerPolicies) { + # Fetch settings for this policy with definitions expanded + $settingsUri = "deviceManagement/configurationPolicies('$($policy.id)')/settings?`$expand=settingDefinitions&`$top=1000" + $settingsResponse = Invoke-MtGraphRequest -RelativeUri $settingsUri -ApiVersion beta + + $policyDetail = @{ + Name = $policy.name + RequireEncryption = 'Not configured' + OsEncryptionType = 'Not configured' + FixedEncryptionType = 'Not configured' + OsEncryptionMethod = 'Not configured' + } + + foreach ($setting in $settingsResponse) { + $defId = $setting.settingInstance.settingDefinitionId + + # Check RequireDeviceEncryption + if ($defId -eq $requireEncryptionId) { + $val = $setting.settingInstance.choiceSettingValue.value + if ($val -like '*_1') { + $policyDetail.RequireEncryption = 'Enabled' + } else { + $policyDetail.RequireEncryption = 'Disabled' + } + } + + # Check OS drive encryption type (SystemDrivesEncryptionType) + if ($defId -eq $osEncryptionTypeId) { + $parentVal = $setting.settingInstance.choiceSettingValue.value + if ($parentVal -like '*_1') { + # Enabled — check the child dropdown for the actual encryption type + $children = $setting.settingInstance.choiceSettingValue.children + foreach ($child in $children) { + if ($child.settingDefinitionId -eq $osEncryptionTypeDropdownId) { + $dropdownVal = $child.choiceSettingValue.value + foreach ($suffix in $encryptionTypeLabels.Keys) { + if ($dropdownVal -like "*$suffix") { + $policyDetail.OsEncryptionType = $encryptionTypeLabels[$suffix] + if ($suffix -eq '_1') { $hasFullEncryption = $true } + } + } + } + } + } else { + $policyDetail.OsEncryptionType = 'Disabled' + } + } + + # Check Fixed drive encryption type (FixedDrivesEncryptionType) + if ($defId -eq $fixedEncryptionTypeId) { + $parentVal = $setting.settingInstance.choiceSettingValue.value + if ($parentVal -like '*_1') { + $children = $setting.settingInstance.choiceSettingValue.children + foreach ($child in $children) { + if ($child.settingDefinitionId -eq $fixedEncryptionTypeDropdownId) { + $dropdownVal = $child.choiceSettingValue.value + foreach ($suffix in $encryptionTypeLabels.Keys) { + if ($dropdownVal -like "*$suffix") { + $policyDetail.FixedEncryptionType = $encryptionTypeLabels[$suffix] + } + } + } + } + } else { + $policyDetail.FixedEncryptionType = 'Disabled' + } + } + + # Check encryption method (cipher strength) + if ($defId -eq $encryptionMethodId) { + $parentVal = $setting.settingInstance.choiceSettingValue.value + if ($parentVal -like '*_1') { + $children = $setting.settingInstance.choiceSettingValue.children + foreach ($child in $children) { + if ($child.settingDefinitionId -eq $osEncryptionMethodDropdownId) { + $methodVal = $child.choiceSettingValue.value + foreach ($suffix in $encryptionMethodLabels.Keys) { + if ($methodVal -like "*$suffix") { + $policyDetail.OsEncryptionMethod = $encryptionMethodLabels[$suffix] + } + } + } + } + } + } + } + + $policyResults += $policyDetail + } + + # Build result markdown + $testResultMarkdown = "Found $($bitLockerPolicies.Count) BitLocker Disk Encryption policy/policies in Intune.`n`n" + $testResultMarkdown += "| Policy | Require Encryption | OS Encryption Type | Fixed Encryption Type | OS Cipher |`n" + $testResultMarkdown += "| --- | --- | --- | --- | --- |`n" + foreach ($p in $policyResults) { + $testResultMarkdown += "| $($p.Name) | $($p.RequireEncryption) | $($p.OsEncryptionType) | $($p.FixedEncryptionType) | $($p.OsEncryptionMethod) |`n" + } + + if ($hasFullEncryption) { + $testResultMarkdown += "`n**Result:** At least one BitLocker policy enforces **Full encryption** for OS drives." + + # Warn if any policy uses Used Space Only + $usedSpaceOnly = $policyResults | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' } + if ($usedSpaceOnly) { + $testResultMarkdown += "`n`n> **Warning:** $($usedSpaceOnly.Count) policy/policies use **Used Space Only** encryption. " + $testResultMarkdown += "Previously deleted data remains recoverable from unencrypted free space on those devices." + } + + Add-MtTestResultDetail -Result $testResultMarkdown + return $true + } else { + $testResultMarkdown += "`n**Result:** No BitLocker policy enforces **Full encryption** for OS drives.`n`n" + $testResultMarkdown += "> **Risk:** If 'Used space only' encryption is configured (or encryption type is not enforced), " + $testResultMarkdown += "data written to the disk before BitLocker was enabled remains as raw unencrypted data in free space " + $testResultMarkdown += "and can be recovered using commonly available data recovery tools (Recuva, PhotoRec, forensic imaging)." + Add-MtTestResultDetail -Result $testResultMarkdown + return $false + } + } catch { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + return $null + } +} diff --git a/tests/Maester/Intune/Test-MtIntunePlatform.Tests.ps1 b/tests/Maester/Intune/Test-MtIntunePlatform.Tests.ps1 index 71a99bf15..0fa9fb966 100644 --- a/tests/Maester/Intune/Test-MtIntunePlatform.Tests.ps1 +++ b/tests/Maester/Intune/Test-MtIntunePlatform.Tests.ps1 @@ -61,4 +61,11 @@ Describe "Maester/Intune" -Tag "Maester", "Intune" { $result | Should -Be $true -Because "MDM Authority is set to Intune." } } + + It "MT.1123: Ensure BitLocker full disk encryption is configured" -Tag "MT.1123" { + $result = Test-MtBitLockerFullDiskEncryption + if ($null -ne $result) { + $result | Should -Be $true -Because "at least one Intune Endpoint Security Disk encryption policy enforces BitLocker full disk encryption." + } + } } diff --git a/tests/maester-config.json b/tests/maester-config.json index d0cf494e1..b2ac7909e 100644 --- a/tests/maester-config.json +++ b/tests/maester-config.json @@ -1349,6 +1349,11 @@ "Severity": "Medium", "Title": "AI agents should not have orphaned ownership" }, + { + "Id": "MT.1123", + "Severity": "High", + "Title": "Ensure BitLocker full disk encryption is configured via Intune" + }, { "Id": "ORCA.100", "Severity": "Medium", diff --git a/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md b/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md new file mode 100644 index 000000000..98d64d2b0 --- /dev/null +++ b/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md @@ -0,0 +1,42 @@ +--- +title: MT.1123 - Ensure BitLocker full disk encryption is configured via Intune +description: Checks if at least one Intune Endpoint Security Disk encryption policy enforces BitLocker full disk encryption for OS and fixed data drives. +slug: /tests/MT.1123 +sidebar_class_name: hidden +--- + +# Ensure BitLocker full disk encryption is configured via Intune + +## Description + +BitLocker Drive Encryption protects data on Windows devices, but the **encryption type** setting is critical: + +- **Full disk encryption** — encrypts the entire drive including free space. This is the secure option. +- **Used space only encryption** — only encrypts sectors currently holding data. Previously deleted files that existed before encryption was enabled remain as raw data in unencrypted free space and **can be recovered using data recovery software** (e.g., Recuva, PhotoRec, forensic imaging tools). This happens because NTFS marks deleted sectors as "free" but does not zero them — the original bytes persist on disk until overwritten. + +This test queries the `configurationPolicies` Graph API (the same API used by the Intune admin center's **Endpoint Security > Disk Encryption** blade). It reads the BitLocker CSP settings — specifically `SystemDrivesEncryptionType` and `FixedDrivesEncryptionType` — to verify that at least one Disk Encryption policy enforces **Full encryption** for OS drives. It also reports `RequireDeviceEncryption` status and cipher strength (`EncryptionMethodByDriveType`) for each policy. + +The test **passes** only if at least one BitLocker Disk Encryption policy has the OS drive encryption type set to "Full encryption". It **fails** if no policies exist, or if all policies use "Used space only" or "Allow user to choose". + +## How to fix + +1. Navigate to [Microsoft Intune admin center](https://intune.microsoft.com). +2. Go to **Endpoint security** > **Disk encryption**. +3. Click **Create Policy**. +4. Set **Platform** to **Windows 10 and later**. +5. Set **Profile** to **BitLocker**. +6. Under the BitLocker settings, configure: + - **Enforce drive encryption type on operating system drives**: **Full encryption** (`SystemDrivesEncryptionType`) + - **Enforce drive encryption type on fixed data drives**: **Full encryption** (`FixedDrivesEncryptionType`) + - **Require Device Encryption**: **Enabled** + - **Choose drive encryption method and cipher strength**: **Enabled** + - OS drives: **XTS-AES 256-bit** + - Fixed data drives: **XTS-AES 256-bit** + - Removable data drives: **AES-CBC 256-bit** +7. Assign the policy to your device groups. + +## Learn more + +- [Microsoft Intune - Endpoint Security Disk Encryption](https://intune.microsoft.com/#view/Microsoft_Intune_Workflows/SecurityManagementMenu/~/diskEncryption) +- [Microsoft Learn - Encrypt devices with BitLocker in Intune](https://learn.microsoft.com/en-us/mem/intune/protect/encrypt-devices) +- [Microsoft Learn - BitLocker CSP reference](https://learn.microsoft.com/en-us/windows/client-management/mdm/bitlocker-csp) From 83750b80f39e4da4d7459f2dac559b6763f4a134 Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Thu, 9 Apr 2026 14:46:00 +0300 Subject: [PATCH 2/5] Address Copilot review: array normalization, 401/403 handling, description fix - Wrap Invoke-MtGraphRequest and Where-Object results in @() to ensure .Count works for single-item results - Add explicit 401/403 handling in catch block to report NotAuthorized instead of generic Error - Fix MT.1123.md frontmatter description to match actual pass criteria (OS drives only) --- .../intune/Test-MtBitLockerFullDiskEncryption.ps1 | 14 +++++++++----- .../version-2.0.0/tests/maester/MT.1123.md | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 index 8e1171b06..7e6ca77cd 100644 --- a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 @@ -44,9 +44,9 @@ function Test-MtBitLockerFullDiskEncryption { try { # Query only Endpoint Security Disk Encryption BitLocker policies (server-side filter) - $bitLockerPolicies = Invoke-MtGraphRequest -RelativeUri "deviceManagement/configurationPolicies?`$filter=templateReference/templateFamily eq 'endpointSecurityDiskEncryption'&`$select=id,name,description,templateReference" -ApiVersion beta + $bitLockerPolicies = @(Invoke-MtGraphRequest -RelativeUri "deviceManagement/configurationPolicies?`$filter=templateReference/templateFamily eq 'endpointSecurityDiskEncryption'&`$select=id,name,description,templateReference" -ApiVersion beta) - if (-not $bitLockerPolicies -or $bitLockerPolicies.Count -eq 0) { + if ($bitLockerPolicies.Count -eq 0) { $testResultMarkdown = "No Endpoint Security Disk Encryption (BitLocker) policies found in Intune.`n`n" $testResultMarkdown += "Create a BitLocker policy under **Endpoint Security > Disk Encryption** with " $testResultMarkdown += "**Enforce drive encryption type** set to **Full encryption** for OS and fixed data drives." @@ -183,8 +183,8 @@ function Test-MtBitLockerFullDiskEncryption { $testResultMarkdown += "`n**Result:** At least one BitLocker policy enforces **Full encryption** for OS drives." # Warn if any policy uses Used Space Only - $usedSpaceOnly = $policyResults | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' } - if ($usedSpaceOnly) { + $usedSpaceOnly = @($policyResults | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' }) + if ($usedSpaceOnly.Count -gt 0) { $testResultMarkdown += "`n`n> **Warning:** $($usedSpaceOnly.Count) policy/policies use **Used Space Only** encryption. " $testResultMarkdown += "Previously deleted data remains recoverable from unencrypted free space on those devices." } @@ -200,7 +200,11 @@ function Test-MtBitLockerFullDiskEncryption { return $false } } catch { - Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -in @(401, 403)) { + Add-MtTestResultDetail -SkippedBecause NotAuthorized + } else { + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ + } return $null } } diff --git a/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md b/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md index 98d64d2b0..4d4deda91 100644 --- a/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md +++ b/website/versioned_docs/version-2.0.0/tests/maester/MT.1123.md @@ -1,6 +1,6 @@ --- title: MT.1123 - Ensure BitLocker full disk encryption is configured via Intune -description: Checks if at least one Intune Endpoint Security Disk encryption policy enforces BitLocker full disk encryption for OS and fixed data drives. +description: Checks if at least one Intune Endpoint Security Disk encryption policy enforces BitLocker full disk encryption for OS drives. slug: /tests/MT.1123 sidebar_class_name: hidden --- From f76920c3f6a226abd3322c3abea0fd5536931a9a Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Thu, 9 Apr 2026 15:43:17 +0300 Subject: [PATCH 3/5] Address Copilot review round 2: filter non-BitLocker policies, fix labels, harden code - Rename variable to diskEncryptionPolicies; filter to actual BitLocker policies via IsBitLockerPolicy flag based on presence of BitLocker CSP settings - Remove misleading 'Disabled' labels for OS/Fixed encryption type, keep default 'Not configured' to match Intune UI - Wrap settingsResponse in @() for array normalization consistency - Use .EndsWith() instead of -like for suffix matching to avoid greedy matches - Use Generic List instead of array concatenation for policyResults - Add separate empty-check for BitLocker policies after filtering out non-BitLocker templates --- .../Test-MtBitLockerFullDiskEncryption.ps1 | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 index 7e6ca77cd..5b8cebcb8 100644 --- a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 @@ -43,12 +43,14 @@ function Test-MtBitLockerFullDiskEncryption { } try { - # Query only Endpoint Security Disk Encryption BitLocker policies (server-side filter) - $bitLockerPolicies = @(Invoke-MtGraphRequest -RelativeUri "deviceManagement/configurationPolicies?`$filter=templateReference/templateFamily eq 'endpointSecurityDiskEncryption'&`$select=id,name,description,templateReference" -ApiVersion beta) - - if ($bitLockerPolicies.Count -eq 0) { - $testResultMarkdown = "No Endpoint Security Disk Encryption (BitLocker) policies found in Intune.`n`n" - $testResultMarkdown += "Create a BitLocker policy under **Endpoint Security > Disk Encryption** with " + # Query Endpoint Security Disk Encryption policies (server-side filter). + # This template family can include non-BitLocker templates (e.g., Personal Data Encryption); + # BitLocker-specific settings are evaluated per-policy below. + $diskEncryptionPolicies = @(Invoke-MtGraphRequest -RelativeUri "deviceManagement/configurationPolicies?`$filter=templateReference/templateFamily eq 'endpointSecurityDiskEncryption'&`$select=id,name,description,templateReference" -ApiVersion beta) + + if ($diskEncryptionPolicies.Count -eq 0) { + $testResultMarkdown = "No Endpoint Security Disk Encryption policies found in Intune.`n`n" + $testResultMarkdown += "Create a Disk Encryption policy under **Endpoint Security > Disk Encryption** with " $testResultMarkdown += "**Enforce drive encryption type** set to **Full encryption** for OS and fixed data drives." Add-MtTestResultDetail -Result $testResultMarkdown return $false @@ -78,13 +80,13 @@ function Test-MtBitLockerFullDiskEncryption { '_7' = 'XTS-AES 256-bit' } - $policyResults = @() + $policyResults = [System.Collections.Generic.List[hashtable]]::new() $hasFullEncryption = $false - foreach ($policy in $bitLockerPolicies) { + foreach ($policy in $diskEncryptionPolicies) { # Fetch settings for this policy with definitions expanded $settingsUri = "deviceManagement/configurationPolicies('$($policy.id)')/settings?`$expand=settingDefinitions&`$top=1000" - $settingsResponse = Invoke-MtGraphRequest -RelativeUri $settingsUri -ApiVersion beta + $settingsResponse = @(Invoke-MtGraphRequest -RelativeUri $settingsUri -ApiVersion beta) $policyDetail = @{ Name = $policy.name @@ -92,6 +94,7 @@ function Test-MtBitLockerFullDiskEncryption { OsEncryptionType = 'Not configured' FixedEncryptionType = 'Not configured' OsEncryptionMethod = 'Not configured' + IsBitLockerPolicy = $false } foreach ($setting in $settingsResponse) { @@ -99,6 +102,7 @@ function Test-MtBitLockerFullDiskEncryption { # Check RequireDeviceEncryption if ($defId -eq $requireEncryptionId) { + $policyDetail.IsBitLockerPolicy = $true $val = $setting.settingInstance.choiceSettingValue.value if ($val -like '*_1') { $policyDetail.RequireEncryption = 'Enabled' @@ -109,6 +113,7 @@ function Test-MtBitLockerFullDiskEncryption { # Check OS drive encryption type (SystemDrivesEncryptionType) if ($defId -eq $osEncryptionTypeId) { + $policyDetail.IsBitLockerPolicy = $true $parentVal = $setting.settingInstance.choiceSettingValue.value if ($parentVal -like '*_1') { # Enabled — check the child dropdown for the actual encryption type @@ -117,15 +122,13 @@ function Test-MtBitLockerFullDiskEncryption { if ($child.settingDefinitionId -eq $osEncryptionTypeDropdownId) { $dropdownVal = $child.choiceSettingValue.value foreach ($suffix in $encryptionTypeLabels.Keys) { - if ($dropdownVal -like "*$suffix") { + if ($dropdownVal.EndsWith($suffix)) { $policyDetail.OsEncryptionType = $encryptionTypeLabels[$suffix] if ($suffix -eq '_1') { $hasFullEncryption = $true } } } } } - } else { - $policyDetail.OsEncryptionType = 'Disabled' } } @@ -138,14 +141,12 @@ function Test-MtBitLockerFullDiskEncryption { if ($child.settingDefinitionId -eq $fixedEncryptionTypeDropdownId) { $dropdownVal = $child.choiceSettingValue.value foreach ($suffix in $encryptionTypeLabels.Keys) { - if ($dropdownVal -like "*$suffix") { + if ($dropdownVal.EndsWith($suffix)) { $policyDetail.FixedEncryptionType = $encryptionTypeLabels[$suffix] } } } } - } else { - $policyDetail.FixedEncryptionType = 'Disabled' } } @@ -158,7 +159,7 @@ function Test-MtBitLockerFullDiskEncryption { if ($child.settingDefinitionId -eq $osEncryptionMethodDropdownId) { $methodVal = $child.choiceSettingValue.value foreach ($suffix in $encryptionMethodLabels.Keys) { - if ($methodVal -like "*$suffix") { + if ($methodVal.EndsWith($suffix)) { $policyDetail.OsEncryptionMethod = $encryptionMethodLabels[$suffix] } } @@ -168,14 +169,25 @@ function Test-MtBitLockerFullDiskEncryption { } } - $policyResults += $policyDetail + $policyResults.Add($policyDetail) + } + + # Filter to only BitLocker policies (those with BitLocker CSP settings) + $bitLockerPolicies = @($policyResults | Where-Object { $_.IsBitLockerPolicy }) + + if ($bitLockerPolicies.Count -eq 0) { + $testResultMarkdown = "Found $($diskEncryptionPolicies.Count) Disk Encryption policy/policies in Intune, but none are BitLocker policies.`n`n" + $testResultMarkdown += "Create a BitLocker policy under **Endpoint Security > Disk Encryption** with " + $testResultMarkdown += "**Enforce drive encryption type** set to **Full encryption** for OS and fixed data drives." + Add-MtTestResultDetail -Result $testResultMarkdown + return $false } - # Build result markdown + # Build result markdown (only BitLocker policies) $testResultMarkdown = "Found $($bitLockerPolicies.Count) BitLocker Disk Encryption policy/policies in Intune.`n`n" $testResultMarkdown += "| Policy | Require Encryption | OS Encryption Type | Fixed Encryption Type | OS Cipher |`n" $testResultMarkdown += "| --- | --- | --- | --- | --- |`n" - foreach ($p in $policyResults) { + foreach ($p in $bitLockerPolicies) { $testResultMarkdown += "| $($p.Name) | $($p.RequireEncryption) | $($p.OsEncryptionType) | $($p.FixedEncryptionType) | $($p.OsEncryptionMethod) |`n" } @@ -183,7 +195,7 @@ function Test-MtBitLockerFullDiskEncryption { $testResultMarkdown += "`n**Result:** At least one BitLocker policy enforces **Full encryption** for OS drives." # Warn if any policy uses Used Space Only - $usedSpaceOnly = @($policyResults | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' }) + $usedSpaceOnly = @($bitLockerPolicies | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' }) if ($usedSpaceOnly.Count -gt 0) { $testResultMarkdown += "`n`n> **Warning:** $($usedSpaceOnly.Count) policy/policies use **Used Space Only** encryption. " $testResultMarkdown += "Previously deleted data remains recoverable from unencrypted free space on those devices." From 479ff0f3b3f4888c729e79a0d850c1b7e1ad26e9 Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Thu, 9 Apr 2026 16:13:37 +0300 Subject: [PATCH 4/5] Set IsBitLockerPolicy flag in all BitLocker CSP setting blocks - Add IsBitLockerPolicy = true in FixedDrivesEncryptionType and EncryptionMethodByDriveType blocks - Ensures policies configuring only these settings are not incorrectly filtered out --- .../maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 index 5b8cebcb8..905f2be66 100644 --- a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 @@ -134,6 +134,7 @@ function Test-MtBitLockerFullDiskEncryption { # Check Fixed drive encryption type (FixedDrivesEncryptionType) if ($defId -eq $fixedEncryptionTypeId) { + $policyDetail.IsBitLockerPolicy = $true $parentVal = $setting.settingInstance.choiceSettingValue.value if ($parentVal -like '*_1') { $children = $setting.settingInstance.choiceSettingValue.children @@ -152,6 +153,7 @@ function Test-MtBitLockerFullDiskEncryption { # Check encryption method (cipher strength) if ($defId -eq $encryptionMethodId) { + $policyDetail.IsBitLockerPolicy = $true $parentVal = $setting.settingInstance.choiceSettingValue.value if ($parentVal -like '*_1') { $children = $setting.settingInstance.choiceSettingValue.children From ef07579e6b6f636ceaf65236b8d432d4ef540a4c Mon Sep 17 00:00:00 2001 From: Ofir Gavish Date: Thu, 9 Apr 2026 16:41:13 +0300 Subject: [PATCH 5/5] Save as UTF-8 with BOM, replace em dashes with ASCII dashes - Re-save .ps1 file with UTF-8 BOM to match repo convention and avoid Windows PowerShell 5.1 decoding issues - Replace U+2014 em dashes with ASCII '--' in comment-based help --- .../intune/Test-MtBitLockerFullDiskEncryption.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 index 905f2be66..eb55a3c0e 100644 --- a/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 +++ b/powershell/public/maester/intune/Test-MtBitLockerFullDiskEncryption.ps1 @@ -1,4 +1,4 @@ -function Test-MtBitLockerFullDiskEncryption { +function Test-MtBitLockerFullDiskEncryption { <# .SYNOPSIS Ensure at least one Intune Disk Encryption policy enforces BitLocker with full disk encryption type. @@ -8,11 +8,11 @@ function Test-MtBitLockerFullDiskEncryption { profiles that enforce full disk encryption rather than "Used space only" encryption. BitLocker supports two encryption types with very different security implications: - - "Full disk encryption" — encrypts the entire drive including free space. This is the secure option. - - "Used space only encryption" — only encrypts sectors currently holding data. Previously deleted + - "Full disk encryption" -- encrypts the entire drive including free space. This is the secure option. + - "Used space only encryption" -- only encrypts sectors currently holding data. Previously deleted files that were written before encryption was enabled remain in unencrypted free space and can be recovered using data recovery software (e.g., Recuva, PhotoRec, or forensic tools). This is because - NTFS marks sectors as free but does not zero them out — the raw data stays on disk until overwritten. + NTFS marks sectors as free but does not zero them out -- the raw data stays on disk until overwritten. This test queries the configurationPolicies Graph API (used by Endpoint Security > Disk Encryption) which exposes the actual BitLocker CSP settings including: @@ -116,7 +116,7 @@ function Test-MtBitLockerFullDiskEncryption { $policyDetail.IsBitLockerPolicy = $true $parentVal = $setting.settingInstance.choiceSettingValue.value if ($parentVal -like '*_1') { - # Enabled — check the child dropdown for the actual encryption type + # Enabled -- check the child dropdown for the actual encryption type $children = $setting.settingInstance.choiceSettingValue.children foreach ($child in $children) { if ($child.settingDefinitionId -eq $osEncryptionTypeDropdownId) {