Public/Show-TCMDrift.ps1
|
function Show-TCMDrift { <# .SYNOPSIS Single command to check your tenant for drift — console, HTML report, or Maester. .DESCRIPTION The daily command for TCM monitoring. Checks all monitors for active drift and presents results in the format you choose: • No switch: Console summary (quick check) • -Report: HTML report with admin portal links • -Maester: Sync to Maester + run drift tests • -CompareBaseline: Also detect new/deleted resources (uses quota) .PARAMETER Report Generate an HTML drift report with admin portal deep links. .PARAMETER Maester Sync drift to Maester format and run Invoke-Maester on the drift folder. .PARAMETER CompareBaseline Also detect new/deleted resources not tracked by the monitor. Uses a snapshot (quota impact) but results are cached for 1 hour. .PARAMETER MonitorId Check a specific monitor. If omitted, checks all monitors. .PARAMETER Notify Send a notification when drift is detected. Currently supports 'Teams'. .PARAMETER WebhookUrl Teams incoming webhook URL. Required when -Notify Teams is specified. Store in a variable or secret manager — not in scripts. .PARAMETER PassThru Return the drift objects for pipeline processing. .EXAMPLE # Quick console check Show-TCMDrift .EXAMPLE # Full HTML report Show-TCMDrift -Report .EXAMPLE # Maester integration with baseline comparison Show-TCMDrift -Maester -CompareBaseline .EXAMPLE # Send Teams notification when drift is found Show-TCMDrift -Notify Teams -WebhookUrl $env:EASYTCM_WEBHOOK_URL .EXAMPLE # Pipeline: get drifted resource details Show-TCMDrift -PassThru | Where-Object { $_.DriftedPropertyCount -gt 0 } #> [CmdletBinding()] param( [switch]$Report, [switch]$Maester, [switch]$CompareBaseline, [ValidateSet('Teams')] [string]$Notify, [string]$WebhookUrl, [string]$MonitorId, [switch]$PassThru ) # Resolve webhook URL from parameter or environment variable if ($Notify -and -not $WebhookUrl) { $WebhookUrl = $env:EASYTCM_WEBHOOK_URL } if ($Notify -and -not $WebhookUrl) { throw 'WebhookUrl is required when -Notify is specified. Use -WebhookUrl or set $env:EASYTCM_WEBHOOK_URL.' } # ── Resolve mode ──────────────────────────────────────────────── # Fetch active drifts once (reused by all modes) $driftParams = @{ Status = 'active' } if ($MonitorId) { $driftParams.MonitorId = $MonitorId } $activeDrifts = @(Get-TCMDrift @driftParams) # In -Report/-Maester modes, show a brief console summary so the user isn't blind if (($Report -or $Maester) -and $activeDrifts.Count -gt 0) { Write-Host '' Write-Host " ⚠️ $($activeDrifts.Count) active drift(s) detected" -ForegroundColor Yellow $grouped = $activeDrifts | Group-Object -Property ResourceType foreach ($group in $grouped) { $shortType = ($group.Name -split '\.')[-1] Write-Host " • $shortType ($($group.Count))" -ForegroundColor Yellow } Write-Host '' } if ($Report) { # HTML report mode $reportParams = @{} if ($MonitorId) { $reportParams.MonitorId = $MonitorId } if ($CompareBaseline) { $reportParams.CompareBaseline = $true } $result = Export-TCMDriftReport @reportParams # Send notification if requested if ($Notify -eq 'Teams') { $monitors = if ($MonitorId) { @(Get-TCMMonitor -Id $MonitorId) } else { @(Get-TCMMonitor) } foreach ($mon in $monitors) { $monDrifts = @($activeDrifts | Where-Object { $_.MonitorId -eq $mon.Id }) Send-TCMNotification -Drifts $monDrifts -Monitor $mon -WebhookUrl $WebhookUrl } } if ($PassThru) { return $result } return } if ($Maester) { # Maester sync + run mode $syncParams = @{} if ($MonitorId) { $syncParams.MonitorId = $MonitorId } if ($CompareBaseline) { $syncParams.CompareBaseline = $true } Sync-TCMDriftToMaester @syncParams # Resolve drift folder $driftPath = if ($env:MAESTER_TESTS_PATH) { Join-Path $env:MAESTER_TESTS_PATH 'Drift' } else { './tests/Maester/Drift' } if (Test-Path $driftPath) { Write-Host '' Invoke-Maester -Path $driftPath } # Send notification if requested if ($Notify -eq 'Teams') { $monitors = if ($MonitorId) { @(Get-TCMMonitor -Id $MonitorId) } else { @(Get-TCMMonitor) } foreach ($mon in $monitors) { $monDrifts = @($activeDrifts | Where-Object { $_.MonitorId -eq $mon.Id }) Send-TCMNotification -Drifts $monDrifts -Monitor $mon -WebhookUrl $WebhookUrl } } return } # ── Console summary mode (default) ───────────────────────────── Write-Host '' Write-Host '🔍 Checking for configuration drift...' -ForegroundColor Cyan Write-Host '' $drifts = $activeDrifts $monitors = if ($MonitorId) { @(Get-TCMMonitor -Id $MonitorId) } else { @(Get-TCMMonitor) } if ($drifts.Count -eq 0) { Write-Host " ✅ No active drift across $($monitors.Count) monitor(s)." -ForegroundColor Green Write-Host " TCM checks every 6 hours automatically." -ForegroundColor DarkGray } else { Write-Host " ⚠️ $($drifts.Count) active drift(s) detected!" -ForegroundColor Yellow Write-Host '' # Group by resource type $grouped = $drifts | Group-Object -Property ResourceType foreach ($group in $grouped) { $shortType = ($group.Name -split '\.')[-1] Write-Host " $shortType ($($group.Count)):" -ForegroundColor White foreach ($d in $group.Group) { $propCount = $d.DriftedPropertyCount Write-Host " • $($d.ResourceDisplay) — $propCount changed propert$(if ($propCount -eq 1) {'y'} else {'ies'})" -ForegroundColor Yellow if ($d.DriftedProperties) { foreach ($dp in $d.DriftedProperties | Select-Object -First 3) { Write-Host " $($dp.propertyName): $($dp.baselineValue) → $($dp.currentValue)" -ForegroundColor DarkGray } if ($d.DriftedProperties.Count -gt 3) { Write-Host " ... and $($d.DriftedProperties.Count - 3) more" -ForegroundColor DarkGray } } } Write-Host '' } } # Optional: baseline comparison $comparison = $null if ($CompareBaseline) { Write-Host ' 🔗 Checking for untracked resources...' -ForegroundColor Cyan $compareParams = @{} if ($MonitorId) { $compareParams.MonitorId = $MonitorId } $comparison = Compare-TCMBaseline @compareParams if ($comparison.HasDrift) { Write-Host " ⚠️ $($comparison.NewCount) new, $($comparison.DeletedCount) deleted resource(s) not in baseline." -ForegroundColor Yellow Write-Host " Run Update-TCMBaseline to adopt approved changes." -ForegroundColor DarkGray } else { Write-Host " ✅ All resources are tracked. Baseline is up to date." -ForegroundColor Green } Write-Host '' } # ── Notification ──────────────────────────────────────────────── if ($Notify -eq 'Teams') { foreach ($mon in $monitors) { $monDrifts = if ($MonitorId) { $drifts } else { @($drifts | Where-Object { $_.MonitorId -eq $mon.Id }) } Send-TCMNotification -Drifts $monDrifts -Monitor $mon -WebhookUrl $WebhookUrl -CompareResult $comparison } } # Hints if (-not $Report -and -not $Maester -and -not $Notify) { Write-Host ' Commands:' -ForegroundColor DarkGray Write-Host ' Show-TCMDrift -Report # detailed HTML report' -ForegroundColor DarkGray Write-Host ' Show-TCMDrift -Maester # Maester test results' -ForegroundColor DarkGray Write-Host ' Show-TCMDrift -CompareBaseline # find untracked resources' -ForegroundColor DarkGray if ($drifts.Count -gt 0) { Write-Host ' Update-TCMBaseline # accept current state as new baseline' -ForegroundColor DarkGray } Write-Host '' } if ($PassThru) { $drifts } } |