Add proactive remediation workload - #41
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new “Proactive Remediations” workload to IntuneHydrationKit, bundling six Windows remediation templates and integrating them into the existing settings/CLI/TUI orchestration and pre-flight access probing.
Changes:
- Introduces
Import-IntuneRemediationplus remediation template loading/fingerprinting/body/description helpers. - Adds a shared
Sync-IntuneDeviceHealthScriptsynchronizer and refactors WinGet proactive remediation sync to use it. - Wires the new workload into orchestration, settings schema/example, scope calculation, access probes, and expands Pester coverage for the new behavior.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Tests/Public/Test-IntunePrerequisites.Tests.ps1 | Adds coverage ensuring device health script probing is not duplicated when both WinGet apps and remediations are selected. |
| Tests/Public/Invoke-IntuneHydration.Tests.ps1 | Verifies -Remediations switch and orchestrator calls into remediation import. |
| Tests/Public/Import-IntuneRemediation.Tests.ps1 | New test suite for remediation create/update/delete/WhatIf and assignment-safe behavior. |
| Tests/Private/Sync-IntuneWinGetProactiveRemediation.Tests.ps1 | Expands tests for stricter ownership requirements and ambiguous-owned matching behavior. |
| Tests/Private/Resolve-HydrationWorkloadPlan.Tests.ps1 | Updates workload catalog expectations to include remediations. |
| Tests/Private/Get-HydrationRemediationTemplates.Tests.ps1 | New tests validating remediation template loading, path safety, and bundled catalog parsing. |
| Tests/Private/Get-HydrationGraphScopes.Tests.ps1 | Ensures remediation selection adds the expected Graph scopes. |
| Templates/Remediations/Windows/DiskPressureCleanup/Detect-DiskPressureCleanup.ps1 | Adds bundled remediation detection script (disk pressure). |
| Templates/Remediations/Windows/DiskPressureCleanup/Remediate-DiskPressureCleanup.ps1 | Adds bundled remediation script (disk pressure cleanup). |
| Templates/Remediations/Windows/DiskPressureCleanup/metadata.json | Adds remediation template metadata (disk pressure cleanup). |
| Templates/Remediations/Windows/DeviceHealthReporting/Detect-DeviceHealth.ps1 | Adds bundled detection-only template for device health reporting. |
| Templates/Remediations/Windows/DeviceHealthReporting/metadata.json | Adds detection-only template metadata (device health reporting). |
| Templates/Remediations/Windows/AutomaticTimeZone/Detect-AutomaticTimeZone.ps1 | Adds detection script for automatic time zone service state. |
| Templates/Remediations/Windows/AutomaticTimeZone/Remediate-AutomaticTimeZone.ps1 | Adds remediation script for automatic time zone service. |
| Templates/Remediations/Windows/AutomaticTimeZone/metadata.json | Adds remediation template metadata (automatic time zone). |
| Templates/Remediations/Windows/BitLockerRecoveryKeyEscrow/Detect-BitLockerRecoveryKeyEscrow.ps1 | Adds detection script for BitLocker key escrow retry. |
| Templates/Remediations/Windows/BitLockerRecoveryKeyEscrow/Remediate-BitLockerRecoveryKeyEscrow.ps1 | Adds remediation script for BitLocker key escrow retry. |
| Templates/Remediations/Windows/BitLockerRecoveryKeyEscrow/metadata.json | Adds remediation template metadata (BitLocker escrow). |
| Templates/Remediations/Windows/DefenderSignatureFreshness/Detect-DefenderSignatureFreshness.ps1 | Adds detection script for Defender signature age. |
| Templates/Remediations/Windows/DefenderSignatureFreshness/Remediate-DefenderSignatureFreshness.ps1 | Adds remediation script for Defender signature refresh. |
| Templates/Remediations/Windows/DefenderSignatureFreshness/metadata.json | Adds remediation template metadata (Defender signature freshness). |
| Templates/Remediations/Windows/WindowsRecoveryEnvironment/Detect-WindowsRecoveryEnvironment.ps1 | Adds detection script for WinRE disabled state. |
| Templates/Remediations/Windows/WindowsRecoveryEnvironment/Remediate-WindowsRecoveryEnvironment.ps1 | Adds remediation script to enable WinRE when safely repairable. |
| Templates/Remediations/Windows/WindowsRecoveryEnvironment/metadata.json | Adds remediation template metadata (WinRE health). |
| settings.schema.json | Adds imports.remediations setting. |
| settings.example.json | Documents default remediations import selection. |
| Public/Orchestration/Invoke-IntuneHydration.ps1 | Integrates remediation workload execution into the main orchestrator. |
| Public/Imports/Import-IntuneRemediation.ps1 | New public entry point to import/delete bundled remediation templates. |
| Private/WinGet/Sync-IntuneWinGetProactiveRemediation.ps1 | Refactors WinGet remediation sync to use shared device health script synchronizer. |
| Private/Tui/Get-HydrationTuiImportOption.ps1 | Adds a TUI import option for proactive remediations. |
| Private/Remediations/New-HydrationRemediationDescription.ps1 | Builds standardized remediation description/ownership metadata. |
| Private/Remediations/New-HydrationRemediationBody.ps1 | Constructs the Graph body payload for device health scripts from templates. |
| Private/Remediations/Get-HydrationRemediationTemplates.ps1 | Loads/validates remediation template metadata and resolves safe script paths. |
| Private/Remediations/Get-HydrationRemediationFingerprint.ps1 | Generates a deterministic fingerprint for remediation template updates. |
| Private/DeviceHealthScripts/Sync-IntuneDeviceHealthScript.ps1 | New shared synchronizer handling create/update/delete with strict ownership and assignment safety. |
| Private/DeviceHealthScripts/ConvertFrom-HydrationDeviceHealthScriptDescription.ps1 | Parses newline-delimited metadata from device health script descriptions. |
| Private/Configuration/Resolve-HydrationExecutionSettings.ps1 | Adds -Remediations switch integration into execution settings resolution. |
| Private/Configuration/Get-HydrationWorkloadCatalog.ps1 | Adds remediation workload catalog entry (platform-scoped to Windows). |
| Private/Auth/Get-HydrationGraphWorkloadAccessProbe.ps1 | Consolidates device health script access probing when either remediation workload is enabled. |
| Private/Auth/Get-HydrationGraphScopes.ps1 | Adds remediation-required scopes to scope resolution. |
| Invoke-IntuneHydration.ps1 | Exposes -Remediations switch in the wrapper script help/params. |
| IntuneHydrationKit.psm1 | Exports the new public function in module loader list. |
| IntuneHydrationKit.psd1 | Exports the new public function in module manifest. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 43 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
Templates/Remediations/Windows/AutomaticTimeZone/Remediate-AutomaticTimeZone.ps1:5
- The remediation can fail silently:
Get-CimInstance -ErrorAction Stopcan throw (unhandled), andStart-Service -ErrorAction SilentlyContinuesuppresses failures while the script still exits 0. That can report success even when the service is still disabled/not running. Catch CIM/service-start failures and return a non-zero exit code when remediation doesn't actually succeed.
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name='tzautoupdate'" -ErrorAction Stop
if ($service.StartMode -eq 'Disabled') {
$process = Start-Process -FilePath "$env:SystemRoot\System32\sc.exe" -ArgumentList 'config', 'tzautoupdate', 'start=', 'demand' -Wait -PassThru -NoNewWindow
if ($process.ExitCode -ne 0) {
Write-Error "Failed to enable the automatic time zone service. ExitCode=$($process.ExitCode)"
Templates/Remediations/Windows/AutomaticTimeZone/Detect-AutomaticTimeZone.ps1:8
Get-CimInstance -ErrorAction Stopcan throw, andWin32_Servicequeries can also return$null(service missing/unavailable). As written, the script can terminate with an unhandled error and Intune will treat the run as a script failure rather than a clean skip/compliance result. Consider catching CIM failures and handling missing service as a safe skip (exit 0).
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name='tzautoupdate'" -ErrorAction Stop
if ($service.StartMode -eq 'Disabled') {
Write-Output 'Automatic time zone service is disabled.'
exit 1
}
Templates/Remediations/Windows/DiskPressureCleanup/Detect-DiskPressureCleanup.ps1:19
Get-CimInstancecan fail or return$null(WMI/CIM issues, unexpected SystemDrive), and the script then dereferences$systemDrive.FreeSpace, which will terminate the detection script with an unhandled error. Since detection scripts should reliably return 0/1, handle CIM errors/null results as a safe skip (exit 0) instead of crashing.
$systemDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($env:SystemDrive)'"
$eligiblePaths = @($env:TEMP, (Join-Path -Path $env:WINDIR -ChildPath 'Temp')) | Select-Object -Unique
$eligibleSize = 0L
foreach ($path in $eligiblePaths) {
Templates/Remediations/Windows/DiskPressureCleanup/Remediate-DiskPressureCleanup.ps1:37
- The remediation script dereferences
$systemDrive.FreeSpacewithout validating that the CIM query succeeded. IfGet-CimInstancefails/returns$null, the script will terminate with an unhandled error and remediation will be reported as failed. Handle CIM failures/null results and return a clear error/skip instead of crashing.
$systemDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($env:SystemDrive)'"
$freeSpaceGB = [math]::Round($systemDrive.FreeSpace / 1GB, 2)
$removedMB = [math]::Round($removedBytes / 1MB, 2)
Write-Output "Disk cleanup completed: RemovedMB=$removedMB; FreeSpaceGB=$freeSpaceGB; TargetFreeSpaceGB=$minimumFreeSpaceGB"
|
@copilot review the current head |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 43 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
Private/Remediations/New-HydrationRemediationDescription.ps1:19
- The description hard-codes
Assignments: none, but assignment state can change after import. That line will become stale/misleading and also gets parsed as metadata byConvertFrom-HydrationDeviceHealthScriptDescription. Consider omitting assignment state from the persisted description and relying on the runtime/assignmentscheck instead.
'Imported from Proactive Remediation Pack'
"RemediationTemplateId: $($Template.TemplateId)"
"RemediationFingerprint: $Fingerprint"
'Assignments: none'
) -join "`n"
Reviewed What I checked:
No changes required. |
Type of Change
Issue
N/A
Description
Adds six bundled, unassigned Windows Proactive Remediation templates and the
Import-IntuneRemediationworkload, with CLI, settings, TUI, pre-flight, scope, and orchestration integration.The shared device-health-script synchronizer preserves exact ownership boundaries, detects assignment state before updating, and fails closed for assigned remediation packages.
-WhatIfperforms the same read-only assignment check, so its plan matches a live run.Validation
./build.ps1 -Task CI— passed (1,121 tests discovered; PSScriptAnalyzer: 0 errors)git diff --checkChecklist