Convenient way to get Intune Remediation Scripts assignments and info
So I was trying to find the Intune remediation scripts assignments to perform a cleanup and wanted to avoid the manual check of every single script — going into each one, checking its assignments, filters, and schedules one by one. If you have ever been there, you know how tedious that gets, especially when you have dozens of scripts deployed across different groups.
That is exactly why I created a simple PowerShell script to do this automatically.
Instead of clicking through the Intune portal endlessly, this script pulls all your Intune Remediation Scripts (deviceHealthScripts) and their full assignment details in one shot — outputs it to the console, a grid view, and a CSV you can actually work with.
Table of Contents
What Are Intune Remediation Scripts?
If you are not fully familiar, Remediation Scripts are pairs of PowerShell scripts in Microsoft Intune — a detection script and a remediation script — used to proactively find and fix issues on managed Windows devices.
They live under:
Intune > Devices > Scripts and remediations > Remediation
Over time, environments tend to accumulate a lot of these — some assigned, some not, some scheduled daily, some hourly — and keeping track of all of it manually is a nightmare.
What Does This Script Do?
The script connects to Microsoft Graph (beta endpoint) and for each remediation script in your tenant it collects:
- Script name, ID, description, publisher, and version
- Created and last modified date
- Assignment target type (specific group, all devices, all users, etc.)
- Group name and ID (resolved automatically from the group ID)
- Assignment filter type and filter ID
- Run schedule details (daily, hourly, weekly — with interval, time, and UTC info)
Everything gets neatly assembled into a report that you can:
- View directly in the console
- Open in Out-GridView for quick filtering
- Export to a CSV file for documentation or cleanup work

Requirements
Before running the script, make sure you have:
- PowerShell 5.1+ or PowerShell 7+
- Microsoft Graph PowerShell SDK installed:
Install-Module Microsoft.Graph #-Scope CurrentUser
- The following delegated Graph permissions (you will be prompted to consent on first run):
DeviceManagementConfiguration.Read.AllDeviceManagementManagedDevices.Read.AllGroup.Read.All
The Script
At the top of the script you will find a settings block — adjust these to fit your needs:
$GridView = $true # Set to $true to open Out-GridView $CSV = $true # Set to $true to export CSV $CsvPath = "C:\TEMP\Intune_RemediationScripts_Assignments_Report.csv"
Here is the full script (you can also find it in my GitHub page here):
#-----------------------------------------
# Default settings (edit as needed)
#-----------------------------------------
$GridView = $true
$CSV = $true
$CsvPath = "C:\TEMP\Intune_RemediationScripts_Assignments_Report.csv"
#-----------------------------------------
# Script purpose
#-----------------------------------------
# This script retrieves all Intune Remediation scripts assignments (deviceHealthScripts) and their data:
# - Assignment target type (group/all devices/all users/etc.)
# - Group name and ID (when applicable)
# - Assignment filter details
# - Schedule type and frequency/timing summary
#
# Output can be viewed in console, Out-GridView, and/or CSV.
Write-Host "=====================================================" -ForegroundColor Cyan
Write-Host " Intune Remediation Scripts Assignment Report" -ForegroundColor Cyan
Write-Host "=====================================================" -ForegroundColor Cyan
Write-Host "[$(Get-Date -Format 'u')] Starting script..." -ForegroundColor Yellow
#-----------------------------------------
# Connect to Microsoft Graph
#-----------------------------------------
Write-Host "[$(Get-Date -Format 'u')] Checking Microsoft Graph connection..." -ForegroundColor Yellow
try {
if (-not (Get-MgContext)) {
Write-Host "[$(Get-Date -Format 'u')] No active Graph session found. Connecting..." -ForegroundColor Yellow
Connect-MgGraph -Scopes @(
"DeviceManagementConfiguration.Read.All",
"DeviceManagementManagedDevices.Read.All",
"Group.Read.All"
) | Out-Null
Write-Host "[$(Get-Date -Format 'u')] Connected to Microsoft Graph." -ForegroundColor Green
}
else {
$ctx = Get-MgContext
Write-Host "[$(Get-Date -Format 'u')] Already connected to Graph as $($ctx.Account)." -ForegroundColor Green
}
}
catch {
Write-Error "Failed to connect to Microsoft Graph. $_"
return
}
#-----------------------------------------
# Helper: Resolve AAD group display name safely
#-----------------------------------------
function Get-GroupDisplayNameSafe {
param(
[Parameter(Mandatory = $true)][string]$GroupId
)
try {
$group = Get-MgGroup -GroupId $GroupId -ErrorAction Stop
return $group.DisplayName
}
catch {
return "Unknown group ($GroupId)"
}
}
#-----------------------------------------
# Helper: Convert assignment runSchedule into readable text
#-----------------------------------------
function Get-ScheduleSummary {
param(
[Parameter(Mandatory = $false)]$RunSchedule
)
if (-not $RunSchedule) { return "Not specified" }
$odataType = $RunSchedule.'@odata.type'
switch -Regex ($odataType) {
"deviceHealthScriptDailySchedule" {
$time = if ($RunSchedule.time) { $RunSchedule.time } else { "N/A" }
$interval = if ($RunSchedule.interval) { $RunSchedule.interval } else { "1" }
$useUtc = if ($null -ne $RunSchedule.useUtc) { $RunSchedule.useUtc } else { "N/A" }
return "Daily | Every $interval day(s) | Time: $time | UTC: $useUtc"
}
"deviceHealthScriptHourlySchedule" {
$interval = if ($RunSchedule.interval) { $RunSchedule.interval } else { "1" }
$useUtc = if ($null -ne $RunSchedule.useUtc) { $RunSchedule.useUtc } else { "N/A" }
return "Hourly | Every $interval hour(s) | UTC: $useUtc"
}
"deviceHealthScriptWeeklySchedule" {
$interval = if ($RunSchedule.interval) { $RunSchedule.interval } else { "1" }
$time = if ($RunSchedule.time) { $RunSchedule.time } else { "N/A" }
$day = if ($RunSchedule.dayOfWeek) { $RunSchedule.dayOfWeek } else { "N/A" }
$useUtc = if ($null -ne $RunSchedule.useUtc) { $RunSchedule.useUtc } else { "N/A" }
return "Weekly | Every $interval week(s) | Day: $day | Time: $time | UTC: $useUtc"
}
default {
return "Other schedule type ($odataType): $($RunSchedule | ConvertTo-Json -Compress -Depth 10)"
}
}
}
#-----------------------------------------
# Main: Retrieve remediation scripts
#-----------------------------------------
$baseUrl = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts"
Write-Host "[$(Get-Date -Format 'u')] Retrieving remediation scripts from Intune..." -ForegroundColor Yellow
try {
$scriptsResponse = Invoke-MgGraphRequest -Uri $baseUrl -Method GET
}
catch {
Write-Error "Failed to retrieve remediation scripts. $_"
return
}
if (-not $scriptsResponse.value) {
Write-Host "[$(Get-Date -Format 'u')] No remediation scripts found." -ForegroundColor Yellow
return
}
$totalScripts = $scriptsResponse.value.Count
Write-Host "[$(Get-Date -Format 'u')] Found $totalScripts remediation script(s)." -ForegroundColor Green
$report = New-Object System.Collections.Generic.List[object]
$scriptCounter = 0
foreach ($script in $scriptsResponse.value) {
$scriptCounter++
$scriptId = $script.id
$scriptName = $script.displayName
$scriptDescription = $script.description
Write-Host "[$(Get-Date -Format 'u')] [$scriptCounter/$totalScripts] Checking script: '$scriptName'" -ForegroundColor Cyan
$scriptAssignmentsUrl = "$baseUrl/$scriptId/assignments"
try {
$assignmentsResponse = Invoke-MgGraphRequest -Uri $scriptAssignmentsUrl -Method GET
$assignments = $assignmentsResponse.value
}
catch {
Write-Warning "Could not get assignments for script '$scriptName' ($scriptId)."
$assignments = @()
}
if (-not $assignments -or $assignments.Count -eq 0) {
Write-Host "[$(Get-Date -Format 'u')] -> No assignments found." -ForegroundColor DarkYellow
$report.Add([PSCustomObject]@{
ScriptName = $scriptName
ScriptId = $scriptId
Description = $scriptDescription
Publisher = $script.publisher
Version = $script.version
CreatedDateTime = $script.createdDateTime
LastModifiedDateTime = $script.lastModifiedDateTime
AssignmentId = ""
AssignmentTargetType = "Unassigned"
AssignmentGroupId = ""
AssignmentGroupName = ""
AssignmentIntent = ""
AssignmentFilterType = ""
AssignmentFilterId = ""
ScheduleType = ""
ScheduleSummary = "Not assigned"
})
continue
}
Write-Host "[$(Get-Date -Format 'u')] -> Found $($assignments.Count) assignment(s)." -ForegroundColor Green
foreach ($a in $assignments) {
$target = $a.target
$targetType = $target.'@odata.type'
$groupId = ""
$groupName = ""
$intent = ""
$filterType = ""
$filterId = ""
if ($target.deviceAndAppManagementAssignmentFilterType) {
$filterType = $target.deviceAndAppManagementAssignmentFilterType
}
if ($target.deviceAndAppManagementAssignmentFilterId) {
$filterId = $target.deviceAndAppManagementAssignmentFilterId
}
if ($targetType -match "groupAssignmentTarget") {
$groupId = $target.groupId
if ($groupId) { $groupName = Get-GroupDisplayNameSafe -GroupId $groupId }
}
if ($target.intent) { $intent = $target.intent }
$scheduleType = ""
$scheduleSummary = "Not specified"
if ($a.runSchedule) {
$scheduleType = $a.runSchedule.'@odata.type'
$scheduleSummary = Get-ScheduleSummary -RunSchedule $a.runSchedule
}
$report.Add([PSCustomObject]@{
ScriptName = $scriptName
ScriptId = $scriptId
Description = $scriptDescription
Publisher = $script.publisher
Version = $script.version
CreatedDateTime = $script.createdDateTime
LastModifiedDateTime = $script.lastModifiedDateTime
AssignmentId = $a.id
AssignmentTargetType = $targetType
AssignmentGroupId = $groupId
AssignmentGroupName = $groupName
AssignmentIntent = $intent
AssignmentFilterType = $filterType
AssignmentFilterId = $filterId
ScheduleType = $scheduleType
ScheduleSummary = $scheduleSummary
})
}
}
#-----------------------------------------
# Output section
#-----------------------------------------
Write-Host "[$(Get-Date -Format 'u')] Preparing final report output..." -ForegroundColor Yellow
$reportSorted = $report | Sort-Object ScriptName, AssignmentGroupName
$reportSorted | Format-Table -AutoSize
Write-Host "[$(Get-Date -Format 'u')] Total output rows: $($reportSorted.Count)" -ForegroundColor Green
if ($GridView) {
Write-Host "[$(Get-Date -Format 'u')] Opening Out-GridView..." -ForegroundColor Yellow
$reportSorted | Out-GridView -Title "Intune Remediation Scripts Assignments"
}
if ($CSV) {
$csvFolder = Split-Path -Path $CsvPath -Parent
if (-not (Test-Path -Path $csvFolder)) {
Write-Host "[$(Get-Date -Format 'u')] CSV folder does not exist. Creating: $csvFolder" -ForegroundColor Yellow
New-Item -Path $csvFolder -ItemType Directory -Force | Out-Null
}
Write-Host "[$(Get-Date -Format 'u')] Exporting CSV to: $CsvPath" -ForegroundColor Yellow
$reportSorted | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
Write-Host "[$(Get-Date -Format 'u')] CSV export complete." -ForegroundColor Green
}
Write-Host "[$(Get-Date -Format 'u')] Script finished successfully." -ForegroundColor Cyan
Breaking Down the Key Parts
1. Settings Block
At the very top you control the output behaviour. Toggle $GridView and $CSV on or off, and set your own export path. No need to touch anything else for basic use.
2. Graph Connection
The script checks if you already have an active Graph session before prompting for login. If you are already connected from a previous session in the same PowerShell window, it reuses that connection and tells you which account is active — no unnecessary re-authentication.
3. Group Name Resolution
Assignments in Graph often return just a Group ID. The helper function Get-GroupDisplayNameSafe resolves that ID to a human-readable display name automatically. If the group cannot be found (deleted groups, permission issues), it falls back gracefully with "Unknown group (ID)" rather than throwing an error.
4. Schedule Summary
The Get-ScheduleSummary function translates the raw Graph schedule object into a clean readable string like:
Daily | Every 1 day(s) | Time: 08:00:00 | UTC: True Weekly | Every 1 week(s) | Day: monday | Time: 07:00:00 | UTC: False
Much easier to read in a spreadsheet than raw JSON.
5. Unassigned Scripts Are Included
Scripts with no assignments are still included in the report with AssignmentTargetType = "Unassigned". This is the most useful part for cleanup — you can instantly spot orphaned scripts that are deployed to nobody and can be safely removed.
Sample CSV Output Columns
| Column | Description |
|---|---|
| ScriptName | Display name of the remediation script |
| ScriptId | Unique GUID of the script |
| Description | Script description from Intune |
| Publisher | Publisher field |
| Version | Script version |
| CreatedDateTime | When the script was created |
| LastModifiedDateTime | Last modification timestamp |
| AssignmentTargetType | groupAssignmentTarget, allDevicesAssignmentTarget, etc. |
| AssignmentGroupName | Resolved AAD group display name |
| AssignmentGroupId | AAD group GUID |
| AssignmentFilterType | Include / Exclude / None |
| AssignmentFilterId | GUID of the assignment filter |
| ScheduleType | Raw schedule odata type |
| ScheduleSummary | Human-readable schedule summary |
Tips for Using the Report
For cleanup: Filter the CSV on AssignmentTargetType = "Unassigned" — those are your candidates for deletion or review.
For auditing: Sort by ScriptName and check whether groups, filters, and schedules are consistent and intentional across your environment.
For documentation: The CSV gives you a ready-made inventory of all your remediation scripts and their full deployment scope — handy for change management records or onboarding new colleagues.
Frequently Asked Questions
Does this script make any changes to Intune?
No. It is fully read-only. It only calls GET endpoints on Microsoft Graph — nothing is modified, created, or deleted.
Why does it use the beta endpoint?
The deviceHealthScripts resource with full assignment and schedule details is only available on the Graph beta API. The v1.0 endpoint does not expose all the scheduling properties needed for this report.
What if I have more than 100 scripts?
The current version fetches the first page of results. If you have a large tenant with more than 100 remediation scripts, you may want to add pagination support using the @odata.nextLink property returned in the Graph response.
Can I run this without admin rights?
You need at least read permissions in Intune and the ability to read group names in Azure AD. A Global Reader or Intune Service Administrator role will work fine.
Wrapping Up
Intune Remediation Scripts are powerful, but managing them at scale without tooling is genuinely painful. This script turns a tedious manual audit into a one-click report — saving you time and giving you a clear picture of what is deployed, to whom, and on what schedule.
If you found this useful, feel free to share it or drop a comment below. And if you extend it — for example adding pagination or filtering by publisher — I would love to hear what you built on top of it.
Happy automating!
References and Documentation
Other Interesting Posts
