|
| 1 | +function Test-MtBitLockerFullDiskEncryption { |
| 2 | + <# |
| 3 | + .SYNOPSIS |
| 4 | + Ensure at least one Intune Disk Encryption policy enforces BitLocker with full disk encryption type. |
| 5 | +
|
| 6 | + .DESCRIPTION |
| 7 | + Checks Intune Endpoint Security Disk Encryption policies (configurationPolicies API) for BitLocker |
| 8 | + profiles that enforce full disk encryption rather than "Used space only" encryption. |
| 9 | +
|
| 10 | + BitLocker supports two encryption types with very different security implications: |
| 11 | + - "Full disk encryption" — encrypts the entire drive including free space. This is the secure option. |
| 12 | + - "Used space only encryption" — only encrypts sectors currently holding data. Previously deleted |
| 13 | + files that were written before encryption was enabled remain in unencrypted free space and can be |
| 14 | + recovered using data recovery software (e.g., Recuva, PhotoRec, or forensic tools). This is because |
| 15 | + NTFS marks sectors as free but does not zero them out — the raw data stays on disk until overwritten. |
| 16 | +
|
| 17 | + This test queries the configurationPolicies Graph API (used by Endpoint Security > Disk Encryption) |
| 18 | + which exposes the actual BitLocker CSP settings including: |
| 19 | + - SystemDrivesEncryptionType (OS drive encryption type: full vs used space only) |
| 20 | + - FixedDrivesEncryptionType (fixed drive encryption type: full vs used space only) |
| 21 | + - RequireDeviceEncryption (require BitLocker encryption) |
| 22 | + - EncryptionMethodByDriveType (cipher strength: XTS-AES 128/256, AES-CBC 128/256) |
| 23 | +
|
| 24 | + The test passes only if at least one BitLocker Disk Encryption policy has the OS drive encryption |
| 25 | + type set to "Full encryption". It fails if no policies exist, if encryption type is set to |
| 26 | + "Used space only", or if the encryption type setting is not configured. |
| 27 | +
|
| 28 | + .EXAMPLE |
| 29 | + Test-MtBitLockerFullDiskEncryption |
| 30 | +
|
| 31 | + Returns true if at least one Disk Encryption policy enforces full disk encryption for OS drives. |
| 32 | +
|
| 33 | + .LINK |
| 34 | + https://maester.dev/docs/commands/Test-MtBitLockerFullDiskEncryption |
| 35 | + #> |
| 36 | + [CmdletBinding()] |
| 37 | + [OutputType([bool])] |
| 38 | + param() |
| 39 | + |
| 40 | + if (-not (Get-MtLicenseInformation -Product Intune)) { |
| 41 | + Add-MtTestResultDetail -SkippedBecause NotLicensedIntune |
| 42 | + return $null |
| 43 | + } |
| 44 | + |
| 45 | + try { |
| 46 | + # Query Endpoint Security Disk Encryption policies (configurationPolicies API) |
| 47 | + $policies = Invoke-MtGraphRequest -RelativeUri 'deviceManagement/configurationPolicies' -ApiVersion beta |
| 48 | + |
| 49 | + # Filter to BitLocker Disk Encryption policies by template family |
| 50 | + $bitLockerPolicies = $policies | Where-Object { |
| 51 | + $_.templateReference.templateFamily -eq 'endpointSecurityDiskEncryption' -and |
| 52 | + $_.templateReference.templateDisplayName -eq 'BitLocker' |
| 53 | + } |
| 54 | + |
| 55 | + if (-not $bitLockerPolicies -or $bitLockerPolicies.Count -eq 0) { |
| 56 | + $testResultMarkdown = "No Endpoint Security Disk Encryption (BitLocker) policies found in Intune.`n`n" |
| 57 | + $testResultMarkdown += "Create a BitLocker policy under **Endpoint Security > Disk Encryption** with " |
| 58 | + $testResultMarkdown += "**Enforce drive encryption type** set to **Full encryption** for OS and fixed data drives." |
| 59 | + Add-MtTestResultDetail -Result $testResultMarkdown |
| 60 | + return $false |
| 61 | + } |
| 62 | + |
| 63 | + # Setting definition IDs for BitLocker CSP settings |
| 64 | + $osEncryptionTypeId = 'device_vendor_msft_bitlocker_systemdrivesencryptiontype' |
| 65 | + $osEncryptionTypeDropdownId = 'device_vendor_msft_bitlocker_systemdrivesencryptiontype_osencryptiontypedropdown_name' |
| 66 | + $fixedEncryptionTypeId = 'device_vendor_msft_bitlocker_fixeddrivesencryptiontype' |
| 67 | + $fixedEncryptionTypeDropdownId = 'device_vendor_msft_bitlocker_fixeddrivesencryptiontype_fdeencryptiontypedropdown_name' |
| 68 | + $requireEncryptionId = 'device_vendor_msft_bitlocker_requiredeviceencryption' |
| 69 | + $encryptionMethodId = 'device_vendor_msft_bitlocker_encryptionmethodbydrivetype' |
| 70 | + $osEncryptionMethodDropdownId = 'device_vendor_msft_bitlocker_encryptionmethodbydrivetype_encryptionmethodwithxtsosdropdown_name' |
| 71 | + |
| 72 | + # Encryption type value suffixes: _0 = Allow user to choose, _1 = Full encryption, _2 = Used Space Only |
| 73 | + $encryptionTypeLabels = @{ |
| 74 | + '_0' = 'Allow user to choose' |
| 75 | + '_1' = 'Full encryption' |
| 76 | + '_2' = 'Used Space Only' |
| 77 | + } |
| 78 | + |
| 79 | + # Encryption method value suffixes: _3=AES-CBC 128, _4=AES-CBC 256, _6=XTS-AES 128, _7=XTS-AES 256 |
| 80 | + $encryptionMethodLabels = @{ |
| 81 | + '_3' = 'AES-CBC 128-bit' |
| 82 | + '_4' = 'AES-CBC 256-bit' |
| 83 | + '_6' = 'XTS-AES 128-bit' |
| 84 | + '_7' = 'XTS-AES 256-bit' |
| 85 | + } |
| 86 | + |
| 87 | + $policyResults = @() |
| 88 | + $hasFullEncryption = $false |
| 89 | + |
| 90 | + foreach ($policy in $bitLockerPolicies) { |
| 91 | + # Fetch settings for this policy with definitions expanded |
| 92 | + $settingsUri = "deviceManagement/configurationPolicies('$($policy.id)')/settings?`$expand=settingDefinitions&top=1000" |
| 93 | + $settingsResponse = Invoke-MtGraphRequest -RelativeUri $settingsUri -ApiVersion beta |
| 94 | + |
| 95 | + $policyDetail = @{ |
| 96 | + Name = $policy.name |
| 97 | + RequireEncryption = 'Not configured' |
| 98 | + OsEncryptionType = 'Not configured' |
| 99 | + FixedEncryptionType = 'Not configured' |
| 100 | + OsEncryptionMethod = 'Not configured' |
| 101 | + } |
| 102 | + |
| 103 | + foreach ($setting in $settingsResponse) { |
| 104 | + $defId = $setting.settingInstance.settingDefinitionId |
| 105 | + |
| 106 | + # Check RequireDeviceEncryption |
| 107 | + if ($defId -eq $requireEncryptionId) { |
| 108 | + $val = $setting.settingInstance.choiceSettingValue.value |
| 109 | + if ($val -like '*_1') { |
| 110 | + $policyDetail.RequireEncryption = 'Enabled' |
| 111 | + } else { |
| 112 | + $policyDetail.RequireEncryption = 'Disabled' |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + # Check OS drive encryption type (SystemDrivesEncryptionType) |
| 117 | + if ($defId -eq $osEncryptionTypeId) { |
| 118 | + $parentVal = $setting.settingInstance.choiceSettingValue.value |
| 119 | + if ($parentVal -like '*_1') { |
| 120 | + # Enabled — check the child dropdown for the actual encryption type |
| 121 | + $children = $setting.settingInstance.choiceSettingValue.children |
| 122 | + foreach ($child in $children) { |
| 123 | + if ($child.settingDefinitionId -eq $osEncryptionTypeDropdownId) { |
| 124 | + $dropdownVal = $child.choiceSettingValue.value |
| 125 | + foreach ($suffix in $encryptionTypeLabels.Keys) { |
| 126 | + if ($dropdownVal -like "*$suffix") { |
| 127 | + $policyDetail.OsEncryptionType = $encryptionTypeLabels[$suffix] |
| 128 | + if ($suffix -eq '_1') { $hasFullEncryption = $true } |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + } else { |
| 134 | + $policyDetail.OsEncryptionType = 'Disabled' |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + # Check Fixed drive encryption type (FixedDrivesEncryptionType) |
| 139 | + if ($defId -eq $fixedEncryptionTypeId) { |
| 140 | + $parentVal = $setting.settingInstance.choiceSettingValue.value |
| 141 | + if ($parentVal -like '*_1') { |
| 142 | + $children = $setting.settingInstance.choiceSettingValue.children |
| 143 | + foreach ($child in $children) { |
| 144 | + if ($child.settingDefinitionId -eq $fixedEncryptionTypeDropdownId) { |
| 145 | + $dropdownVal = $child.choiceSettingValue.value |
| 146 | + foreach ($suffix in $encryptionTypeLabels.Keys) { |
| 147 | + if ($dropdownVal -like "*$suffix") { |
| 148 | + $policyDetail.FixedEncryptionType = $encryptionTypeLabels[$suffix] |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + } else { |
| 154 | + $policyDetail.FixedEncryptionType = 'Disabled' |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + # Check encryption method (cipher strength) |
| 159 | + if ($defId -eq $encryptionMethodId) { |
| 160 | + $parentVal = $setting.settingInstance.choiceSettingValue.value |
| 161 | + if ($parentVal -like '*_1') { |
| 162 | + $children = $setting.settingInstance.choiceSettingValue.children |
| 163 | + foreach ($child in $children) { |
| 164 | + if ($child.settingDefinitionId -eq $osEncryptionMethodDropdownId) { |
| 165 | + $methodVal = $child.choiceSettingValue.value |
| 166 | + foreach ($suffix in $encryptionMethodLabels.Keys) { |
| 167 | + if ($methodVal -like "*$suffix") { |
| 168 | + $policyDetail.OsEncryptionMethod = $encryptionMethodLabels[$suffix] |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + $policyResults += $policyDetail |
| 178 | + } |
| 179 | + |
| 180 | + # Build result markdown |
| 181 | + $testResultMarkdown = "Found $($bitLockerPolicies.Count) BitLocker Disk Encryption policy/policies in Intune.`n`n" |
| 182 | + $testResultMarkdown += "| Policy | Require Encryption | OS Encryption Type | Fixed Encryption Type | OS Cipher |`n" |
| 183 | + $testResultMarkdown += "| --- | --- | --- | --- | --- |`n" |
| 184 | + foreach ($p in $policyResults) { |
| 185 | + $testResultMarkdown += "| $($p.Name) | $($p.RequireEncryption) | $($p.OsEncryptionType) | $($p.FixedEncryptionType) | $($p.OsEncryptionMethod) |`n" |
| 186 | + } |
| 187 | + |
| 188 | + if ($hasFullEncryption) { |
| 189 | + $testResultMarkdown += "`n**Result:** At least one BitLocker policy enforces **Full encryption** for OS drives." |
| 190 | + |
| 191 | + # Warn if any policy uses Used Space Only |
| 192 | + $usedSpaceOnly = $policyResults | Where-Object { $_.OsEncryptionType -eq 'Used Space Only' } |
| 193 | + if ($usedSpaceOnly) { |
| 194 | + $testResultMarkdown += "`n`n> **Warning:** $($usedSpaceOnly.Count) policy/policies use **Used Space Only** encryption. " |
| 195 | + $testResultMarkdown += "Previously deleted data remains recoverable from unencrypted free space on those devices." |
| 196 | + } |
| 197 | + |
| 198 | + Add-MtTestResultDetail -Result $testResultMarkdown |
| 199 | + return $true |
| 200 | + } else { |
| 201 | + $testResultMarkdown += "`n**Result:** No BitLocker policy enforces **Full encryption** for OS drives.`n`n" |
| 202 | + $testResultMarkdown += "> **Risk:** If 'Used space only' encryption is configured (or encryption type is not enforced), " |
| 203 | + $testResultMarkdown += "data written to the disk before BitLocker was enabled remains as raw unencrypted data in free space " |
| 204 | + $testResultMarkdown += "and can be recovered using commonly available data recovery tools (Recuva, PhotoRec, forensic imaging)." |
| 205 | + Add-MtTestResultDetail -Result $testResultMarkdown |
| 206 | + return $false |
| 207 | + } |
| 208 | + } catch { |
| 209 | + Add-MtTestResultDetail -SkippedBecause Error -SkippedError $_ |
| 210 | + return $null |
| 211 | + } |
| 212 | +} |
0 commit comments