$VerbosePreference = "SilentlyContinue" function Build-PolicyManagerOmaUri { param($subKey, $valueName) if ($subKey -notmatch 'Uefi|Accounts|WindowsDefenderApplicationGuard|WindowsLicensing') { return "./Device/Vendor/MSFT/Policy/Config/$subKey/$valueName" } elseif ($subKey -match 'Uefi') { return "./Device/Vendor/MSFT/Uefi/$subKey/$valueName" } elseif ($subKey -match 'Accounts') { return "./Device/Vendor/MSFT/Accounts/Domain/$valueName" } elseif ($subKey -match 'WindowsDefenderApplicationGuard') { return "./Device/Vendor/MSFT/WindowsDefenderApplicationGuard/$valueName" } elseif ($subKey -match 'WindowsLicensing') { return "./Device/Vendor/MSFT/WindowsLicensing/$valueName" } } function Convert-OmaUriToDefinitionId { param ($omaUri) return ($omaUri -replace '^\./Device/Vendor/MSFT/', 'device_vendor_msft_') -replace '/', '_' | ForEach-Object { $_.ToLower().Trim() } } function Ensure-GraphModule { if (-not (Get-Command Invoke-MgGraphRequest -ErrorAction SilentlyContinue)) { Write-Host "Microsoft.Graph module missing. Attempting to install..." -ForegroundColor Yellow try { Install-Module Microsoft.Graph -Force -ErrorAction Stop } catch { Write-Host "Failed to install Microsoft.Graph." -ForegroundColor Red return $false } } Import-Module Microsoft.Graph -ErrorAction SilentlyContinue return $true } function Get-MatchedOmaUris { $rebootRequiredPath = 'HKLM:\SOFTWARE\Microsoft\Provisioning\SyncML\RebootRequiredURIs' $policyManagerPath = 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device' Write-Host "`n[1] Checking for RebootRequired URI matches abd the NodeCache..." -ForegroundColor Cyan $rebootRequiredURIs = Get-ItemProperty -Path $rebootRequiredPath | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name $matches = @() # SPECIAL HANDLING: ComputerName CSP via NodeCache $nodeCachePath = "HKLM:\SOFTWARE\Microsoft\Provisioning\NodeCache\CSP\Device\MS DM Server\Nodes" if ($rebootRequiredURIs -contains './Device/Vendor/MSFT/Accounts/Domain/ComputerName') { $allNodes = Get-ChildItem -Path $nodeCachePath -ErrorAction SilentlyContinue foreach ($node in $allNodes) { $props = Get-ItemProperty -Path $node.PSPath -ErrorAction SilentlyContinue if ($props.NodeUri -eq "./DevDetail/Ext/Microsoft/DeviceName" -and $props.ExpectedValue) { Write-Host "[INFO] DeviceName CSP reboot URI detected via NodeCache:" -ForegroundColor Yellow #Write-Host " - NodeUri: $($props.NodeUri) | ExpectedValue: $($props.ExpectedValue) | Key: $($node.PSChildName)" $matches += "./Device/Vendor/MSFT/Accounts/Domain/ComputerName" } } } # REGULAR POLICYMANAGER KEYS Get-ChildItem -Path $policyManagerPath -Recurse | ForEach-Object { $subKey = $_.PSChildName try { $vals = Get-ItemProperty -Path $_.PSPath | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name foreach ($val in $vals) { $cleanVal = $val -replace '_(WinningProvider|ProviderSet)$','' $omaUri = Build-PolicyManagerOmaUri -subKey $subKey -valueName $cleanVal if ($rebootRequiredURIs -contains $omaUri) { $matches += $omaUri } } } catch {} } return $matches | Sort-Object -Unique } function Fetch-IntuneAssignedPoliciesInMemory { param ([string]$ApiVersion = "Beta") $uriBase = "https://graph.microsoft.com/$ApiVersion" if (-not (Get-MgContext)) { Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All" -ContextScope Process } $allConfigs = @() $allCatalogs = @() $deviceConfigurations = Invoke-MgGraphRequest -Uri "$uriBase/deviceManagement/deviceConfigurations" $allDeviceConfigs = @($deviceConfigurations.value) while ($deviceConfigurations.'@odata.nextLink') { $deviceConfigurations = Invoke-MgGraphRequest -Uri $deviceConfigurations.'@odata.nextLink' $allDeviceConfigs += $deviceConfigurations.value } foreach ($policy in $allDeviceConfigs) { $assignments = Invoke-MgGraphRequest -Uri "$uriBase/deviceManagement/deviceConfigurations/$($policy.id)/assignments" if ($assignments.value.Count -eq 0) { continue } $policy | Add-Member -NotePropertyName "assignments" -NotePropertyValue $assignments.value $allConfigs += $policy } $catalogPolicies = Invoke-MgGraphRequest -Uri "$uriBase/deviceManagement/configurationPolicies" $allCatalogs = @($catalogPolicies.value) while ($catalogPolicies.'@odata.nextLink') { $catalogPolicies = Invoke-MgGraphRequest -Uri $catalogPolicies.'@odata.nextLink' $allCatalogs += $catalogPolicies.value } foreach ($catalog in $allCatalogs) { $assignments = Invoke-MgGraphRequest -Uri "$uriBase/deviceManagement/configurationPolicies/$($catalog.id)/assignments" if ($assignments.value.Count -eq 0) { continue } $settings = Invoke-MgGraphRequest -Uri "$uriBase/deviceManagement/configurationPolicies/$($catalog.id)/settings" $catalog | Add-Member -NotePropertyName "settings" -NotePropertyValue $settings.value $catalog | Add-Member -NotePropertyName "assignments" -NotePropertyValue $assignments.value } return @{ DeviceConfig = $allConfigs; SettingsCatalog = $allCatalogs } } function Search-InMemoryPolicies { param ( [string[]]$MatchedOmaUris, [array]$Policies ) $defIds = $MatchedOmaUris | ForEach-Object { Convert-OmaUriToDefinitionId $_ } $hits = @() foreach ($policy in $Policies) { $defs = @() $policyId = $policy.id $policyName = $policy.displayName if (-not $policyName) { $policyName = $policy.name } if ($policy.settings) { $defs += $policy.settings.settingInstance.settingDefinitionId } if ($policy.omaSettings) { $defs += $policy.omaSettings.omaUri } $defs = $defs | Where-Object { $_ } | ForEach-Object { $_.ToLower().Trim() } foreach ($def in $defIds) { if ($defs -contains $def) { $hits += [pscustomobject]@{ IntunePolicyId = $policyId SettingName = $def -replace '^device_vendor_msft_policy_config_', '' IntunePolicyName = $policyName } } } # Special: ComputerName CSP (OMA-URI) direct match if ($policy.omaSettings) { foreach ($oma in $policy.omaSettings) { if ($oma.omaUri -eq "./Device/Vendor/MSFT/Accounts/Domain/ComputerName") { Write-Host "[INFO] Found ComputerName OMA-URI in policy: $($policyName) ($($policyId))" -ForegroundColor Yellow $hits += [pscustomobject]@{ IntunePolicyId = $policyId SettingName = "computername" IntunePolicyName = $policyName } } } } } return $hits | Sort-Object IntunePolicyId, SettingName, IntunePolicyName -Unique } function Get-SystemEventLogForReboot { $rebootEvents = Get-WinEvent -LogName 'System' | Where-Object { $_.Id -eq 1074 } foreach ($event in $rebootEvents) { $eventMessage = $event.Message -replace "\s+", " " $eventMessage = $eventMessage.Trim().ToLower() $eventTime = $event.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss") if ($eventMessage -match "cloudexperiencehost") { Write-Host "`nReboot event detected in the System Event Log (Event ID 1074). $eventTime" -ForegroundColor Green } } } function Get-DeviceSetupRebootEvent { $rebootEvents = Get-WinEvent -LogName 'Microsoft-Windows-Shell-Core/Operational' | Where-Object { $_.Id -eq 62407 } foreach ($event in $rebootEvents) { $eventMessage = $event.Message -replace "\s+", " " $eventMessage = $eventMessage.Trim().ToLower() $eventTime = $event.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss") if ($eventMessage -match "bootstrapstatus: reboot required by subcategory devicesetup.rebootcoalescing") { Write-Host "`nDevice Setup reboot event detected in Shell-Core Event Log (Event ID 62407). $eventTime" -ForegroundColor Green } } } # Custom truncate for table output function Trunc { param($s, $n) if ($null -eq $s) { return "" } elseif ($s.Length -le $n) { $s } else { $s.Substring(0, $n-3) + "..." } } function Invoke-IntuneMemoryAudit { if (-not (Ensure-GraphModule)) { return } $matched = Get-MatchedOmaUris if ($matched.Count -eq 0) { Write-Host "`nNo matching URIs found in PolicyManager." -ForegroundColor Red return } Write-Host "`n[2] Check Event Logs for Reboots:`n" -ForegroundColor Cyan Get-SystemEventLogForReboot Get-DeviceSetupRebootEvent Write-Host "`n[3] Matched URIs:`n" -ForegroundColor Cyan $matched | ForEach-Object { Write-Host " - $_" } Write-Host "`n[4] Fetching assigned policies from Intune..." -ForegroundColor Cyan $policies = Fetch-IntuneAssignedPoliciesInMemory Write-Host "`n[5] Scanning in-memory policy data..." -ForegroundColor Cyan $hits1 = @(Search-InMemoryPolicies -MatchedOmaUris $matched -Policies $policies.DeviceConfig) $hits2 = @(Search-InMemoryPolicies -MatchedOmaUris $matched -Policies $policies.SettingsCatalog) $allHits = @() if ($hits1) { $allHits += $hits1 } if ($hits2) { $allHits += $hits2 } # --- Special logic for ManagePreviewBuilds/WUfB/Insider ring foreach ($defId in $matched | ForEach-Object { Convert-OmaUriToDefinitionId $_ }) { if ($defId -eq "device_vendor_msft_policy_config_update_managepreviewbuilds") { foreach ($container in @($policies.DeviceConfig, $policies.SettingsCatalog)) { foreach ($policy in $container) { if ($policy.'@odata.type' -eq "#microsoft.graph.windowsUpdateForBusinessConfiguration") { $val = $policy.businessReadyUpdatesOnly if ($val -and $val.ToLower() -eq "windowsinsiderbuildrelease") { $allHits += [pscustomobject]@{ IntunePolicyId = $policy.id SettingName = "managepreviewbuilds" IntunePolicyName = $policy.displayName } Write-Host "`n[INFO] Detected ManagePreviewBuilds (via businessReadyUpdatesOnly) in:" -ForegroundColor Yellow Write-Host " - Name: $($policy.displayName)" Write-Host " - ID: $($policy.id)" } } } } } } $allHits = $allHits | Sort-Object IntunePolicyId, SettingName, IntunePolicyName -Unique if ($allHits.Count -gt 0) { Write-Host "`nMatched definitions:`n" -ForegroundColor Green $col1 = 36; $col2 = 22; $col3 = 30 "{0,-$col1} {1,-$col2} {2,-$col3}" -f "IntunePolicyId", "SettingName", "IntunePolicyName" | Write-Host "{0,-$col1} {1,-$col2} {2,-$col3}" -f ("-"*($col1-1)), ("-"*($col2-1)), ("-"*($col3-1)) | Write-Host $allHits | ForEach-Object { "{0,-$col1} {1,-$col2} {2,-$col3}" -f (Trunc $_.IntunePolicyId $col1), (Trunc $_.SettingName $col2), (Trunc $_.IntunePolicyName $col3) | Write-Host } # Prompt and export to CSV $export = Read-Host "`nExport all results to CSV in C:\Temp\intune-memory-audit.csv? (Y/N)" if ($export -match '^[Yy]') { $csvPath = "C:\Temp\intune-memory-audit.csv" if (-not (Test-Path "C:\Temp")) { New-Item -ItemType Directory -Path "C:\Temp" | Out-Null } try { $allHits | Select-Object IntunePolicyId, SettingName, IntunePolicyName | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 Write-Host "`n[INFO] Results exported to $csvPath" -ForegroundColor Cyan } catch { Write-Host "[ERROR] Could not write to CSV: $_" -ForegroundColor Red } } } else { Write-Host "`nNo matching definitions found." -ForegroundColor Red } } Invoke-IntuneMemoryAudit