If you manage Microsoft Intune, you've likely faced this frustrating scenario: You need to find all policies that configure a specific setting - maybe "location services", "wallpaper", or "trusted certificates" - but Intune's portal only lets you search by policy name.
When you have dozens or hundreds of policies, manually clicking through each one to check if it contains a particular setting is a massive time sink. What if a security audit asks "Which policies configure location settings?" or you need to track down all policies that deploy certificates? You're stuck clicking through every single policy.
I needed a way to search inside the actual policy configurations, not just their names. The Intune portal doesn't offer this, so I built a PowerShell script that uses the Microsoft Graph API to solve this problem.
The Challenge: It's Not Just About Names
My first attempt seemed straightforward - query the Graph API for policies and search through them. But I quickly discovered that searching policy names wasn't enough. A policy named "Device-TrustedCertificate" doesn't tell you it might contain location settings. The actual settings and their values are buried deep in the policy object structure.
Here's what a typical Device Configuration Profile looks like when retrieved via Graph API:
{
"displayName": "Device-TrustedCertificate",
"@odata.type": "#microsoft.graph.windows81TrustedRootCertificate",
"trustedRootCertificate": "MIIFkDCCA3igAwIBAgIQBZo...",
"destinationStore": "computerCertStoreRoot",
"certFileName": "bearissuersrv1.cer.cer"
}
The word "certificate" appears in the odata.type and properties, but not necessarily in the display name. My search needed to dig into the entire object structure.
The Solution: Deep Object Searching
The core of my solution converts each policy object to JSON and searches through the entire structure:
function Search-InPolicyContent {
param(
[object]$PolicyObject,
[string]$SearchTerm,
[string]$PolicyName = "Unknown"
)
$json = $PolicyObject | ConvertTo-Json -Depth 100
# Case-insensitive search for whole words only
# Using negative lookbehind/lookahead for better word boundary detection
$pattern = "(?i)(?<![a-zA-Z0-9])" + [regex]::Escape($SearchTerm) + "(?![a-zA-Z0-9])"
$matches = [regex]::Matches($json, $pattern)
if ($matches.Count -gt 0) {
Write-Host " → Found '$SearchTerm' $($matches.Count) time(s)" -ForegroundColor DarkYellow
# Show context for debugging
$firstMatch = $matches[0]
$startIndex = [Math]::Max(0, $firstMatch.Index - 50)
$length = [Math]::Min(150, $json.Length - $startIndex)
$context = $json.Substring($startIndex, $length) -replace '\s+', ' ' -replace '[\\"]', ''
Write-Host " → Context: ...$context..." -ForegroundColor DarkGray
return $true
}
return $false
}
Script Decisions
- Whole Word Matching: When searching for "location", I don't want to match "dislocation" or "lockscreen". The regex pattern
(?<![a-zA-Z0-9])keyword(?![a-zA-Z0-9])ensures we only match complete words. - Case Insensitive: Settings might be "Location", "location", or "LOCATION". The
(?i)flag handles this. - Deep JSON Conversion: Using -Depth 100 ensures even deeply nested settings are captured in the search.
- Context Display: Showing where the match was found helps verify the results are relevant.
Searching Different Policy Types
Intune has multiple policy types, each with different Graph API endpoints and structures:
Device Configuration Profiles
These are the traditional configuration profiles:
$deviceConfigUri = "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations"
$deviceConfigs = Invoke-GraphRequest -Uri $deviceConfigUri -AccessToken $accessToken
Configuration Policies (Settings Catalog)
The newer Settings Catalog policies require a different endpoint:
$configPolicyUri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies"
$configPolicies = Invoke-GraphRequest -Uri $configPolicyUri -AccessToken $accessToken
# For each policy, get its detailed settings
foreach ($policy in $configPolicies.value) {
$settingsUri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($policy.id)')/settings"
$settings = Invoke-GraphRequest -Uri $settingsUri -AccessToken $accessToken
# ... search through settings
}
Compliance Policies and Administrative Templates
Each policy type has its own endpoint and structure, but the search approach remains consistent - retrieve the full object and search through its JSON representation.
Getting Assignment Information
Once I find matching policies, I need to know which groups they're assigned to. This requires additional API calls:
function Get-PolicyAssignments {
param(
[string]$PolicyId,
[string]$PolicyType,
[string]$AccessToken
)
$assignmentUri = switch ($PolicyType) {
"deviceConfigurations" {
"https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations('$PolicyId')/assignments"
}
"configurationPolicies" {
"https://graph.microsoft.com/v1.0/deviceManagement/configurationPolicies('$PolicyId')/assignments"
}
# ... other policy types
}
$assignments = Invoke-GraphRequest -Uri $assignmentUri -AccessToken $accessToken
# Resolve group names from IDs
foreach ($assignment in $assignments.value) {
if ($assignment.target.'@odata.type' -eq "#microsoft.graph.groupAssignmentTarget") {
$groupUri = "https://graph.microsoft.com/v1.0/groups/$($assignment.target.groupId)"
$group = Invoke-GraphRequest -Uri $groupUri -AccessToken $accessToken
# Return group name and assignment type
}
}
}
API Permissions Required
The script requires an Azure AD App Registration with the following Microsoft Graph API permissions:
| Permission | Type | Purpose |
|---|---|---|
DeviceManagementConfiguration.Read.All |
Application | Read Intune device configuration policies |
Group.Read.All |
Application | Resolve group names from group IDs |
Setting Up the App Registration:
- Go to Azure Portal → Azure Active Directory → App Registrations
- Create a new registration
- Under API Permissions, add the above Microsoft Graph permissions
- Grant admin consent for your tenant
- Create a client secret under Certificates & Secrets
- Note your Tenant ID, Application (Client) ID, and Client Secret
Using the Script
Configuration
Update these variables at the top of the script with your values:
$TenantId = "YOUR-TENANT-ID-HERE"
$ClientId = "YOUR-CLIENT-ID-HERE"
$ClientSecret = "YOUR-CLIENT-SECRET-HERE"
Running the Script
# Search for policies containing "location"
.\Search-IntunePolicy.ps1 -Keyword "location"
# Search for policies containing "certificate"
.\Search-IntunePolicy.ps1 -Keyword "certificate"
# Search for policies containing "wallpaper"
.\Search-IntunePolicy.ps1 -Keyword "wallpaper"
Script : Search-IntunePolicy.ps1
# Intune Policy Search Script
# Searches through Intune policies for keywords in their configurations
# ============================================
# PARAMETERS - Pass keyword when running script
# ============================================
param(
[Parameter(Mandatory=$true)]
[string]$Keyword
)
# ============================================
# CONFIGURATION - Update these values
# ============================================
$TenantId = "YOUR-TENANT-ID-HERE" # Example: "12345678-1234-1234-1234-123456789012"
$ClientId = "YOUR-CLIENT-ID-HERE" # Example: "87654321-4321-4321-4321-210987654321"
$ClientSecret = "YOUR-CLIENT-SECRET-HERE" # Example: "your-secret-value-here"
# ============================================
# SCRIPT BEGINS - No need to modify below
# ============================================
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " Intune Policy Search Script" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Validate configuration
if ($TenantId -eq "YOUR-TENANT-ID-HERE" -or
$ClientId -eq "YOUR-CLIENT-ID-HERE" -or
$ClientSecret -eq "YOUR-CLIENT-SECRET-HERE") {
Write-Host "`nERROR: Please update the configuration variables in the script!" -ForegroundColor Red
Write-Host "You need to set:" -ForegroundColor Yellow
Write-Host " - `$TenantId" -ForegroundColor Yellow
Write-Host " - `$ClientId" -ForegroundColor Yellow
Write-Host " - `$ClientSecret" -ForegroundColor Yellow
Write-Host ""
exit 1
}
Write-Host "`nSearch Parameters:" -ForegroundColor Yellow
Write-Host " Keyword: '$Keyword'" -ForegroundColor White
Write-Host ""
# Install required module if not present
if (!(Get-Module -ListAvailable -Name "MSAL.PS")) {
Write-Host "Installing MSAL.PS module..." -ForegroundColor Yellow
Install-Module -Name MSAL.PS -Force -AllowClobber -Scope CurrentUser
}
Import-Module MSAL.PS
function Get-GraphAccessToken {
param(
[string]$TenantId,
[string]$ClientId,
[string]$ClientSecret
)
$authority = "https://login.microsoftonline.com/$TenantId"
$scope = "https://graph.microsoft.com/.default"
$secureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
try {
$token = Get-MsalToken -ClientId $ClientId `
-ClientSecret $secureSecret `
-TenantId $TenantId `
-Authority $authority `
-Scopes $scope
return $token.AccessToken
}
catch {
Write-Error "Failed to authenticate: $_"
return $null
}
}
function Invoke-GraphRequest {
param(
[string]$Uri,
[string]$AccessToken,
[string]$Method = "GET"
)
$headers = @{
"Authorization" = "Bearer $AccessToken"
"Content-Type" = "application/json"
}
try {
$response = Invoke-RestMethod -Uri $Uri -Headers $headers -Method $Method
return $response
}
catch {
$errorDetails = $_.ErrorDetails.Message
if ($errorDetails) {
Write-Warning "Graph API Error: $errorDetails"
} else {
Write-Warning "Error calling Graph API: $_"
}
Write-Warning "Failed URI: $Uri"
return $null
}
}
function Search-InPolicyContent {
param(
[object]$PolicyObject,
[string]$SearchTerm,
[string]$PolicyName = "Unknown"
)
$json = $PolicyObject | ConvertTo-Json -Depth 100
# Case-insensitive search for the whole word
# Using lookahead and lookbehind for better word boundary detection
# This handles cases where \b doesn't work properly with certain characters
$pattern = "(?i)(?<![a-zA-Z0-9])" + [regex]::Escape($SearchTerm) + "(?![a-zA-Z0-9])"
$matches = [regex]::Matches($json, $pattern)
if ($matches.Count -gt 0) {
# Debug: Show where the match was found
Write-Host " → Found '$SearchTerm' $($matches.Count) time(s)" -ForegroundColor DarkYellow
# Show context of first match for debugging
$firstMatch = $matches[0]
$startIndex = [Math]::Max(0, $firstMatch.Index - 50)
$length = [Math]::Min(150, $json.Length - $startIndex)
$context = $json.Substring($startIndex, $length) -replace '\s+', ' ' -replace '[\\"]', ''
Write-Host " → Context: ...$context..." -ForegroundColor DarkGray
return $true
}
return $false
}
function Get-PolicyAssignments {
param(
[string]$PolicyId,
[string]$PolicyType,
[string]$AccessToken
)
$assignmentUri = switch ($PolicyType) {
"deviceConfigurations" {
"https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations('$PolicyId')/assignments"
}
"configurationPolicies" {
"https://graph.microsoft.com/v1.0/deviceManagement/configurationPolicies('$PolicyId')/assignments"
}
"compliancePolicies" {
"https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies('$PolicyId')/assignments"
}
"groupPolicyConfigurations" {
"https://graph.microsoft.com/v1.0/deviceManagement/groupPolicyConfigurations('$PolicyId')/assignments"
}
default { $null }
}
if ($assignmentUri) {
$assignments = Invoke-GraphRequest -Uri $assignmentUri -AccessToken $accessToken
$assignmentDetails = @()
foreach ($assignment in $assignments.value) {
$target = $assignment.target
$assignmentInfo = @{
Type = $target.'@odata.type'
GroupId = $null
GroupName = "N/A"
}
# Handle different assignment types
switch ($target.'@odata.type') {
"#microsoft.graph.groupAssignmentTarget" {
$assignmentInfo.GroupId = $target.groupId
# Get group name
$groupUri = "https://graph.microsoft.com/v1.0/groups/$($target.groupId)"
$group = Invoke-GraphRequest -Uri $groupUri -AccessToken $accessToken
$assignmentInfo.GroupName = if ($group) { $group.displayName } else { "Unknown Group" }
$assignmentInfo.Type = "Group"
}
"#microsoft.graph.allLicensedUsersAssignmentTarget" {
$assignmentInfo.GroupName = "All Users"
$assignmentInfo.Type = "All Users"
}
"#microsoft.graph.allDevicesAssignmentTarget" {
$assignmentInfo.GroupName = "All Devices"
$assignmentInfo.Type = "All Devices"
}
"#microsoft.graph.exclusionGroupAssignmentTarget" {
$assignmentInfo.GroupId = $target.groupId
$groupUri = "https://graph.microsoft.com/v1.0/groups/$($target.groupId)"
$group = Invoke-GraphRequest -Uri $groupUri -AccessToken $accessToken
$assignmentInfo.GroupName = if ($group) { "Excluded: $($group.displayName)" } else { "Excluded: Unknown Group" }
$assignmentInfo.Type = "Exclusion"
}
}
$assignmentDetails += $assignmentInfo
}
return $assignmentDetails
}
return @()
}
# Main script execution
Write-Host "Authenticating with Azure AD..." -ForegroundColor Green
$accessToken = Get-GraphAccessToken -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret
if (!$accessToken) {
Write-Error "Failed to obtain access token. Please check your credentials."
exit 1
}
Write-Host "Authentication successful!" -ForegroundColor Green
Write-Host ""
$results = @()
$searchStartTime = Get-Date
# Search Device Configuration Profiles
Write-Host "Searching Device Configuration Profiles..." -ForegroundColor Green
$deviceConfigUri = "https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations"
$deviceConfigs = Invoke-GraphRequest -Uri $deviceConfigUri -AccessToken $accessToken
if ($deviceConfigs) {
Write-Host " Checking $($deviceConfigs.value.Count) policies..." -ForegroundColor Gray
foreach ($config in $deviceConfigs.value) {
if (Search-InPolicyContent -PolicyObject $config -SearchTerm $Keyword -PolicyName $config.displayName) {
Write-Host " ✓ Found match in: $($config.displayName)" -ForegroundColor Yellow
$assignments = Get-PolicyAssignments -PolicyId $config.id -PolicyType "deviceConfigurations" -AccessToken $accessToken
$results += [PSCustomObject]@{
PolicyType = "Device Configuration"
PolicyName = $config.displayName
PolicyId = $config.id
Platform = $config.'@odata.type' -replace '#microsoft.graph.', ''
Assignments = $assignments
}
}
}
} else {
Write-Host " ⚠ No policies retrieved or error occurred" -ForegroundColor Red
}
# Search Configuration Policies (Settings Catalog)
Write-Host "Searching Configuration Policies (Settings Catalog)..." -ForegroundColor Green
$configPolicyUri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies"
$configPolicies = Invoke-GraphRequest -Uri $configPolicyUri -AccessToken $accessToken
if ($configPolicies) {
Write-Host " Checking $($configPolicies.value.Count) policies..." -ForegroundColor Gray
foreach ($policy in $configPolicies.value) {
# Get detailed settings for this policy
$settingsUri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($policy.id)')/settings"
$settings = Invoke-GraphRequest -Uri $settingsUri -AccessToken $accessToken
$policyData = @{
Policy = $policy
Settings = $settings
}
if (Search-InPolicyContent -PolicyObject $policyData -SearchTerm $Keyword -PolicyName $policy.name) {
Write-Host " ✓ Found match in: $($policy.name)" -ForegroundColor Yellow
$assignments = Get-PolicyAssignments -PolicyId $policy.id -PolicyType "configurationPolicies" -AccessToken $accessToken
$results += [PSCustomObject]@{
PolicyType = "Configuration Policy (Settings Catalog)"
PolicyName = $policy.name
PolicyId = $policy.id
Platform = $policy.platforms -join ", "
Assignments = $assignments
}
}
}
}
# Search Compliance Policies
Write-Host "Searching Compliance Policies..." -ForegroundColor Green
$complianceUri = "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies"
$compliancePolicies = Invoke-GraphRequest -Uri $complianceUri -AccessToken $accessToken
if ($compliancePolicies) {
Write-Host " Checking $($compliancePolicies.value.Count) policies..." -ForegroundColor Gray
foreach ($policy in $compliancePolicies.value) {
if (Search-InPolicyContent -PolicyObject $policy -SearchTerm $Keyword -PolicyName $policy.displayName) {
Write-Host " ✓ Found match in: $($policy.displayName)" -ForegroundColor Yellow
$assignments = Get-PolicyAssignments -PolicyId $policy.id -PolicyType "compliancePolicies" -AccessToken $accessToken
$results += [PSCustomObject]@{
PolicyType = "Compliance Policy"
PolicyName = $policy.displayName
PolicyId = $policy.id
Platform = $policy.'@odata.type' -replace '#microsoft.graph.', ''
Assignments = $assignments
}
}
}
}
# Search Group Policy Configurations (Administrative Templates)
Write-Host "Searching Administrative Templates..." -ForegroundColor Green
$groupPolicyUri = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=definitionValues"
$groupPolicies = Invoke-GraphRequest -Uri $groupPolicyUri -AccessToken $accessToken
if ($groupPolicies) {
Write-Host " Checking $($groupPolicies.value.Count) policies..." -ForegroundColor Gray
foreach ($policy in $groupPolicies.value) {
if (Search-InPolicyContent -PolicyObject $policy -SearchTerm $Keyword -PolicyName $policy.displayName) {
Write-Host " ✓ Found match in: $($policy.displayName)" -ForegroundColor Yellow
$assignments = Get-PolicyAssignments -PolicyId $policy.id -PolicyType "groupPolicyConfigurations" -AccessToken $accessToken
$results += [PSCustomObject]@{
PolicyType = "Administrative Template"
PolicyName = $policy.displayName
PolicyId = $policy.id
Platform = "Windows"
Assignments = $assignments
}
}
}
}
$searchEndTime = Get-Date
$searchDuration = $searchEndTime - $searchStartTime
# Display results
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " SEARCH RESULTS" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Search completed in: $($searchDuration.TotalSeconds) seconds" -ForegroundColor Gray
Write-Host "Found $($results.Count) policies containing '$Keyword'`n" -ForegroundColor Green
if ($results.Count -eq 0) {
Write-Host "No policies found containing the keyword '$Keyword'" -ForegroundColor Yellow
}
else {
foreach ($result in $results) {
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host "Policy Name: " -NoNewline -ForegroundColor Yellow
Write-Host $result.PolicyName -ForegroundColor White
Write-Host "Policy Type: " -NoNewline -ForegroundColor Yellow
Write-Host $result.PolicyType -ForegroundColor Gray
Write-Host "Platform: " -NoNewline -ForegroundColor Yellow
Write-Host $result.Platform -ForegroundColor Gray
Write-Host "Policy ID: " -NoNewline -ForegroundColor Yellow
Write-Host $result.PolicyId -ForegroundColor DarkGray
Write-Host "Assigned to:" -ForegroundColor Yellow
if ($result.Assignments.Count -eq 0) {
Write-Host " • No assignments" -ForegroundColor DarkGray
}
else {
foreach ($assignment in $result.Assignments) {
$icon = switch ($assignment.Type) {
"Group" { "►" }
"All Users" { "◉" }
"All Devices" { "◈" }
"Exclusion" { "✗" }
default { "•" }
}
Write-Host " $icon $($assignment.GroupName) [$($assignment.Type)]" -ForegroundColor Cyan
}
}
Write-Host ""
}
}
# Export to CSV
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$exportPath = "$PSScriptRoot\IntuneSearchResults_${Keyword}_${timestamp}.csv"
$exportData = @()
foreach ($result in $results) {
foreach ($assignment in $result.Assignments) {
$exportData += [PSCustomObject]@{
SearchKeyword = $Keyword
PolicyName = $result.PolicyName
PolicyType = $result.PolicyType
Platform = $result.Platform
PolicyId = $result.PolicyId
AssignedTo = $assignment.GroupName
AssignmentType = $assignment.Type
GroupId = $assignment.GroupId
}
}
if ($result.Assignments.Count -eq 0) {
$exportData += [PSCustomObject]@{
SearchKeyword = $Keyword
PolicyName = $result.PolicyName
PolicyType = $result.PolicyType
Platform = $result.Platform
PolicyId = $result.PolicyId
AssignedTo = "Not Assigned"
AssignmentType = "None"
GroupId = ""
}
}
}
if ($exportData.Count -gt 0) {
$exportData | Export-Csv -Path $exportPath -NoTypeInformation
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
Write-Host "`n✓ Results exported to:" -ForegroundColor Green
Write-Host " $exportPath" -ForegroundColor Cyan
}
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " Search Complete!" -ForegroundColor Green
This script has saved me countless hours when tracking down policy settings in Intune. Whether it's for security audits, troubleshooting, or just understanding which policies configure specific settings, being able to search inside policy content rather than just by name is invaluable.