PowerShell Functions to Invoke an Intune Remediation Script on Demand and View Remediation States

!! Updated 2023-07-13 – added additional versions of the functions using the Microsoft Graph PowerShell SDK (v2), as well as adding paging support to the original two functions.

Microsoft recently released a preview capability in Intune to run Remediations (formerly Proactive remediations – stop changing the name of things, Microsoft, really!!) on demand, which is a very cool feature. At the time of writing though, you can’t run a remediation against a co-managed device in the portal – the option is greyed out. You can still do this via MS Graph though.

You can also view Remediation script statuses in the portal as a preview feature, although I’ve found it to be hit and miss – the Remediations blade is there for some devices, and not for others.

I wrote a couple of PowerShell functions that let you invoke a Remediation on demand – whether co-managed or Intune-managed, and also view the Remediation statuses for a device.

The functions can be run against a single device, or multiple devices, and you only need to provide computer names. The function that invokes a Remediation will prompt you for which Remediation to run from those that are available to you. Simply select one and click OK.

The function will return no output if everything works successfully.

The function that displays the Remediations statuses can be piped to “Out-GridView” for an easy way to view the results, or you can filter for a particular workstation or Remediation using PowerShell.

A few things to note:

  • These capabilities are in preview and the Graph endpoints are in beta, so there’s no promise something won’t change in the future (and we all know how much Microsoft loves to change things!)
  • There are two versions of the functions – one using the Microsoft Graph PowerShell SDK (v2) and the other using the REST API and an access token obtained via the older Intune PowerShell SDK.
  • Certain delegated permissions are required, these are listed in each script
  • The MS documentation lists the DeviceManagementConfiguration.Read.All or DeviceManagementManagedDevices.Read.All delegated permissions as a requirement for the initiateOnDemandProactiveRemediation endpoint, however I believe this is incorrect and in fact the DeviceManagementManagedDevices.PrivilegedOperations.All permission is required.
  • It can take some minutes after invoking a Remediation on-demand for the results to be available.

Here are the functions. The first two are using the Microsoft Graph PowerShell SDK (v2), and the last two use the REST API directly but use the older Intune PowerShell SDK to get an access token – you can get this token any way you want, of course.

function Invoke-MgDeviceRemediationOnDemand {
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[string[]]
$Computername
)
# Requires the Microsoft Graph PowerShell SDK (Microsoft.Graph.*)
# MS Graph required permissions (delegated)
# DeviceManagementManagedDevices.Read.All
# DeviceManagementConfiguration.Read.All
# DeviceManagementManagedDevices.PrivilegedOperations.All
Begin
{
# Get NuGet
$provider = Get-PackageProvider NuGet -ErrorAction Ignore
if (-not $provider)
{
Write-Host "Installing provider NuGet…" -NoNewline
try
{
Find-PackageProvider -Name NuGet -ForceBootstrap -IncludeDependencies -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
$module = Import-Module Microsoft.Graph.DeviceManagement -PassThru -ErrorAction Ignore
if (-not $module)
{
Write-Host "Installing module Microsoft.Graph.DeviceManagement…" -NoNewline
try
{
Install-Module Microsoft.Graph.DeviceManagement -Scope CurrentUser -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
$module = Import-Module Microsoft.Graph.Beta.DeviceManagement -PassThru -ErrorAction Ignore
if (-not $module)
{
Write-Host "Installing module Microsoft.Graph.Beta.DeviceManagement…" -NoNewline
try
{
Install-Module Microsoft.Graph.Beta.DeviceManagement -Scope CurrentUser -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
try
{
$null = Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All","DeviceManagementConfiguration.Read.All","DeviceManagementManagedDevices.PrivilegedOperations.All" -ErrorAction Stop
}
catch
{
throw $_.Exception.Message
}
Function Invoke-IntuneOnDemandRemediation {
param($DeviceId,$RemediationId,$DeviceType)
if ($DeviceType -eq "CoManaged")
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/comanagedDevices('$DeviceID')/initiateOnDemandProactiveRemediation"
}
else
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceID')/initiateOnDemandProactiveRemediation"
}
$body = @{
"scriptPolicyId"="$RemediationID"
} | ConvertTo-Json
try
{
$Response = Invoke-MgGraphRequest -Uri $URL -Method POST -Body $Body -ErrorAction Stop
}
catch
{
$Response = $_.Exception.Message
}
return $Response
}
# Get list of remediations
try
{
$result = Get-MgBetaDeviceManagementDeviceHealthScript -Property displayName,version,description,publisher,id -All -ErrorAction Stop
}
catch
{
throw $_.Exception.Message
}
# Check we have an actual list
$Scripts = $Result | Select -Property DisplayName,Version,Description,Publisher,Id
if ($Scripts.Count -lt 1)
{
Write-Warning "No remediation scripts found"
break
}
# Prompt user to select a script
$script:SelectedScript = $Scripts | Sort -Property displayName | Out-GridView -Title "Select a remediation" -OutputMode Single
if ($null -eq $SelectedScript)
{
Write-Error "No remediation script selected"
break
}
}
Process
{
foreach ($Computer in $Computername)
{
# Find managed device in Graph
try
{
$Device = Get-MgDeviceManagementManagedDevice -Filter "deviceName eq '$Computer'" -ErrorAction Stop
}
catch
{
Write-Error $_.Exception.Message
continue
}
# Make sure only 1 result returned
if ($null -eq $Device)
{
Write-Error "Device not found"
continue
}
if ($Device.Count -gt 1)
{
Write-Error "Multiple devices found with the name '$Computer'. Device names must be unique."
continue
}
# Invoke the remediation
if ($Device.ManagementAgent -match "configurationManager")
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id -DeviceType "CoManaged"
}
else
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id
}
If ($null -ne $result)
{
# Try the other managament agent option
if ($Device.ManagementAgent -match "configurationManager")
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id
}
else
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id -DeviceType "CoManaged"
}
If ($null -ne $result)
{
Write-Error $result
continue
}
}
}
}
End
{
$null = Disconnect-Graph
}
}
# Example usage
Invoke-MgDeviceRemediationOnDemand -Computername "PC001","PC002"
Function Get-MgDeviceRemediationsStatus {
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[string[]]
$Computername
)
# Requires the Microsoft Graph PowerShell SDK (Microsoft.Graph.*)
# MS Graph required permissions (delegated)
# DeviceManagementManagedDevices.Read.All
# DeviceManagementConfiguration.Read.All
Begin
{
# Get NuGet
$provider = Get-PackageProvider NuGet -ErrorAction Ignore
if (-not $provider)
{
Write-Host "Installing provider NuGet…" -NoNewline
try
{
Find-PackageProvider -Name NuGet -ForceBootstrap -IncludeDependencies -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
$module = Import-Module Microsoft.Graph.DeviceManagement -PassThru -ErrorAction Ignore
if (-not $module)
{
Write-Host "Installing module Microsoft.Graph.DeviceManagement…" -NoNewline
try
{
Install-Module Microsoft.Graph.DeviceManagement -Scope CurrentUser -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
$module = Import-Module Microsoft.Graph.Beta.DeviceManagement -PassThru -ErrorAction Ignore
if (-not $module)
{
Write-Host "Installing module Microsoft.Graph.Beta.DeviceManagement…" -NoNewline
try
{
Install-Module Microsoft.Graph.Beta.DeviceManagement -Scope CurrentUser -Force -ErrorAction Stop -WarningAction SilentlyContinue
Write-Host "Success" -ForegroundColor Green
}
catch
{
Write-Host "Failed" -ForegroundColor Red
throw $_.Exception.Message
}
}
try
{
$null = Connect-MgGraph -Scopes "DeviceManagementManagedDevices.Read.All","DeviceManagementConfiguration.Read.All" -ErrorAction Stop
}
catch
{
throw $_.Exception.Message
}
}
Process
{
foreach ($Computer in $Computername)
{
# Find managed device in Graph
try
{
$Device = Get-MgDeviceManagementManagedDevice -Filter "deviceName eq '$Computer'" -ErrorAction Stop
}
catch
{
Write-Error $_.Exception.Message
continue
}
# Make sure only 1 result returned
if ($null -eq $Device)
{
Write-Error "Device not found"
continue
}
if ($Device.Count -gt 1)
{
Write-Error "Multiple devices found with the name '$Computer'. Device names must be unique."
continue
}
# Get the remediations status
try
{
$result = Get-MgBetaDeviceManagementManagedDeviceHealthScriptState -ManagedDeviceId $Device.id -All -ErrorAction Stop
}
catch
{
Write-Error $_.Exception.Message
continue
}
# Select only the desired properties
if ($result.Count -lt 1)
{
Write-Warning "No remediations status found for '$Computer'"
continue
}
$result = $result | Select -Property * -Unique
$Properties = @("DeviceName","PolicyName","UserName","LastStateUpdateDateTime","DetectionState","RemediationState","PreRemediationDetectionScriptOutput","PreRemediationDetectionScriptError","RemediationScriptError","PostRemediationDetectionScriptOutput","PostRemediationDetectionScriptError")
($result | Select -Property $Properties | Sort PolicyName)
}
}
End
{
$null = Disconnect-MgGraph
}
}
# Example usage
Get-MgDeviceRemediationsStatus -Computername "PC001","PC002" | Out-GridView
function Invoke-IntuneRemediationOnDemand {
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[string[]]
$Computername
)
# Requires the Intune PowerShell SDK (Microsoft.Graph.Intune)
# MS Graph required permissions (delegated):
# DeviceManagementManagedDevices.Read.All
# DeviceManagementConfiguration.Read.All
# DeviceManagementManagedDevices.PrivilegedOperations.All
Begin
{
$script:GraphToken = Connect-MSGraph -PassThru
$ProgressPreference = 'SilentlyContinue'
Function script:Invoke-LocalGraphRequest {
Param ($URL,$Headers,$Method,$Body)
try
{
If ($Method -eq "POST")
{
$WebRequest = Invoke-WebRequest -Uri $URL -Method $Method -Headers $Headers -Body $Body -ContentType "application/json" -UseBasicParsing
}
else
{
$WebRequest = Invoke-WebRequest -Uri $URL -Method $Method -Headers $Headers -UseBasicParsing
}
}
catch
{
$Response = $_
$WebRequest = [PSCustomObject]@{
Message = $response.Exception.Message
StatusCode = $response.Exception.Response.StatusCode
StatusDescription = $response.Exception.Response.StatusDescription
}
}
Return $WebRequest
}
Function Get-IntuneManagedDevices {
param($DeviceName)
$URL = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=deviceName eq '$DeviceName'"
$headers = @{'Authorization'="Bearer " + $GraphToken}
$GraphRequest = Invoke-LocalGraphRequest -URL $URL -Headers $headers -Method GET
return $GraphRequest
}
Function Get-IntuneDeviceHealthScripts {
param($URL)
If ($null -eq $URL)
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts"
}
$headers = @{'Authorization'="Bearer " + $GraphToken}
$GraphRequest = Invoke-LocalGraphRequest -URL $URL -Headers $headers -Method GET
return $GraphRequest
}
Function Invoke-IntuneOnDemandRemediation {
param($DeviceId,$RemediationId,$DeviceType)
if ($DeviceType -eq "CoManaged")
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/comanagedDevices('$DeviceID')/initiateOnDemandProactiveRemediation"
}
else
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceID')/initiateOnDemandProactiveRemediation"
}
$headers = @{'Authorization'="Bearer " + $GraphToken}
$body = @{
"scriptPolicyId"="$RemediationID"
} | ConvertTo-Json
$GraphRequest = Invoke-LocalGraphRequest -URL $URL -Headers $headers -Body $Body -Method POST
return $GraphRequest
}
# Get list of remediations
$result = Get-IntuneDeviceHealthScripts
if ($result.StatusCode -ne 200)
{
Write-Error $result
break
}
# Do paging
$Content = $result.Content | ConvertFrom-Json
$ScriptList = [System.Collections.Generic.List[Object]]::new()
$ScriptList.AddRange($Content.value)
If ($null -ne $Content.'@odata.nextLink')
{
do
{
$result = Get-IntuneDeviceHealthScripts -URL $Content.'@odata.nextLink'
if ($result.StatusCode -ne 200)
{
Write-Error $result
continue
}
$Content = $result.Content | ConvertFrom-Json
$ScriptList.AddRange($Content.value)
}
until ($null -eq $Content.'@odata.nextLink')
}
# Check we have an actual list
$Scripts = $ScriptList | Select -Property displayName,version,description,publisher,id
if ($Scripts.Count -lt 1)
{
Write-Warning "No remediation scripts found"
break
}
# Prompt user to select a script
$script:SelectedScript = $Scripts | Sort -Property displayName | Out-GridView -Title "Select a remediation" -OutputMode Single
if ($null -eq $SelectedScript)
{
Write-Error "No remediation script selected"
break
}
}
Process
{
foreach ($Computer in $Computername)
{
# Find managed device in Graph
$result = Get-IntuneManagedDevices -DeviceName "$Computer"
if ($result.StatusCode -ne 200)
{
Write-Error $result
continue
}
# Make sure only 1 result returned
$Device = $result.Content | ConvertFrom-Json | Select -ExpandProperty value
if ($null -eq $Device)
{
Write-Error "Device not found"
continue
}
if ($Device.Count -gt 1)
{
Write-Error "Multiple devices found with the name '$Computer'. Device names must be unique."
continue
}
# Invoke the remediation
if ($Device.managementAgent -match "configurationManager")
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id -DeviceType "CoManaged"
}
else
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id
}
If ($result.StatusCode -ne 204)
{
# Try the other managament agent option
if ($Device.managementAgent -match "configurationManager")
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id
}
else
{
$result = Invoke-IntuneOnDemandRemediation -DeviceID $Device.id -RemediationID $SelectedScript.id -DeviceType "CoManaged"
}
If ($result.StatusCode -ne 204)
{
Write-Error $result
continue
}
}
}
}
End
{
}
}
# Example usage
Invoke-IntuneRemediationOnDemand -Computername "PC001","PC002"
Function Get-IntuneDeviceRemediationsStatus {
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[string[]]
$Computername
)
# Requires the Intune PowerShell SDK (Microsoft.Graph.Intune)
# MS Graph required permissions (delegated)
# DeviceManagementManagedDevices.Read.All
# DeviceManagementConfiguration.Read.All
Begin
{
$script:GraphToken = Connect-MSGraph -PassThru
$ProgressPreference = 'SilentlyContinue'
Function script:Invoke-LocalGraphRequest {
Param ($URL,$Headers,$Method)
try
{
$WebRequest = Invoke-WebRequest -Uri $URL -Method $Method -Headers $Headers -UseBasicParsing
}
catch
{
$Response = $_
$WebRequest = [PSCustomObject]@{
Message = $response.Exception.Message
StatusCode = $response.Exception.Response.StatusCode
StatusDescription = $response.Exception.Response.StatusDescription
}
}
Return $WebRequest
}
Function Get-IntuneManagedDevices {
param($DeviceName)
$URL = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=deviceName eq '$DeviceName'"
$headers = @{'Authorization'="Bearer " + $GraphToken}
$GraphRequest = Invoke-LocalGraphRequest -URL $URL -Headers $headers -Method GET
return $GraphRequest
}
Function Get-IntuneSingleDeviceRemediationStatus
{
param($DeviceId,$URL)
If ($null -eq $URL)
{
$URL = "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceID')/deviceHealthScriptStates"
}
$headers = @{'Authorization'="Bearer " + $GraphToken}
$GraphRequest = Invoke-LocalGraphRequest -URL $URL -Headers $headers -Method GET
return $GraphRequest
}
}
Process
{
foreach ($Computer in $Computername)
{
# Find managed device in Graph
$result = Get-IntuneManagedDevices -DeviceName "$Computer"
if ($result.StatusCode -ne 200)
{
Write-Error $result
continue
}
# Make sure only 1 result returned
$Device = $result.Content | ConvertFrom-Json | Select -ExpandProperty value
if ($null -eq $Device)
{
Write-Error "Device not found"
continue
}
if ($Device.Count -gt 1)
{
Write-Error "Multiple devices found with the name '$Computer'. Device names must be unique."
continue
}
# Get the remediations status
$result = Get-IntuneSingleDeviceRemediationStatus -DeviceID $Device.id
if ($result.StatusCode -ne 200)
{
Write-Error $result
continue
}
# Do paging
$Content = $result.Content | ConvertFrom-Json
$Statuses = [System.Collections.Generic.List[Object]]::new()
$Statuses.AddRange($Content.value)
If ($null -ne $Content.'@odata.nextLink')
{
do
{
$result = Get-IntuneSingleDeviceRemediationStatus -URL $Content.'@odata.nextLink'
if ($result.StatusCode -ne 200)
{
Write-Error $result
continue
}
$Content = $result.Content | ConvertFrom-Json
$Statuses.AddRange($Content.value)
}
until ($null -eq $Content.'@odata.nextLink')
}
# Select only the desired properties
if ($Statuses.Count -lt 1)
{
Write-Warning "No remediations status found for '$Computer'"
continue
}
$Statuses = $Statuses | Select -Property * -Unique
$Properties = @("deviceName","policyName","userName","lastStateUpdateDateTime","detectionState","remediationState","preRemediationDetectionScriptOutput","preRemediationDetectionScriptError","remediationScriptError","postRemediationDetectionScriptOutput","postRemediationDetectionScriptError")
($Statuses | Select -Property $Properties | Sort policyName)
}
}
End
{
}
}
# Example usage
Get-IntuneDeviceRemediationsStatus -Computername "PC001","PC002" | Out-GridView