PowerShell sample - Verify Microsoft Entra Global Secure Access configuration backup compliance
In brief
Verify that your Global Secure Access configuration backup runbook ran successfully. Send an alert when the runbook fails or misses a scheduled run.
Documentation change
new file mode 100644
title: PowerShell sample - Verify Microsoft Entra Global Secure Access configuration backup compliance description: "Verify that your Global Secure Access configuration backup runbook ran successfully. Send an alert when the runbook fails or misses a scheduled run." ms.topic: sample ms.date: 06/17/2026 ms.reviewer: tdetzner ai-usage: ai-assisted
Verify Microsoft Entra Global Secure Access configuration backup compliance
This script checks recent Azure Automation jobs for your Global Secure Access configuration backup runbook. It returns a compliance summary and can send an alert email when the backup job fails or no job ran during the expected schedule.
The script dot-sources _GsaOpsHelpers.ps1, so place the helper file in the same folder before you run this sample.
Prerequisites
- PowerShell 7.0 or later.
- Install the modules listed in the script's
.NOTESblock. - Use only permissions and roles that your organization has approved for the task.
Script
<#
.SYNOPSIS
Monitors GSA configuration backup job results and alerts on failures.
.DESCRIPTION
Queries Azure Automation for the status of GSA backup runbook jobs over a
configurable lookback window. If any jobs failed or no jobs ran during the
expected schedule, sends an alert email via Microsoft Graph.
Run this script daily as a secondary watchdog runbook in the same (or a
different) Automation Account.
.PARAMETER AutomationAccountName
Name of the Azure Automation Account running the backup runbooks.
.PARAMETER ResourceGroupName
Resource group containing the Automation Account.
.PARAMETER BackupRunbookName
Name of the runbook that performs GSA configuration backups.
.PARAMETER LookbackHours
Number of hours to look back for completed jobs. Default: 26 (covers a
daily schedule with 2-hour buffer).
.PARAMETER AlertRecipient
Email address to receive failure alerts.
.PARAMETER SenderId
UserId or UPN of the mailbox used to send alert emails.
.EXAMPLE
.\Test-GsaBackupCompliance.ps1 -AutomationAccountName "gsa-automation" -ResourceGroupName "gsa-ops-rg" -BackupRunbookName "Export-GsaConfiguration" -AlertRecipient "[email protected]" -SenderId "[email protected]"
.NOTES
Required permissions: Azure — Automation Job Operator on the Automation Account; Graph — Mail.Send
Minimum module versions: Az.Accounts 2.x, Az.Automation 1.x, Microsoft.Graph.Authentication 2.x, Microsoft.Graph.Users.Actions 2.x
Dot-sources scripts/automation/_GsaOpsHelpers.ps1 for shared auth and email helpers.
Author: GSA Operations
#>
[CmdletBinding()]
[OutputType([pscustomobject])]
param(
[Parameter(Mandatory)]
[string]$AutomationAccountName,
[Parameter(Mandatory)]
[string]$ResourceGroupName,
[Parameter(Mandatory)]
[string]$BackupRunbookName,
[int]$LookbackHours = 26,
[string]$AlertRecipient,
[string]$SenderId,
[string]$TenantId,
[string]$SubscriptionId,
[switch]$SkipEmail
)
$ErrorActionPreference = 'Stop'
# Load shared helpers (Connect-GsaRuntime, Send-GsaAlertEmail)
. "$PSScriptRoot\_GsaOpsHelpers.ps1"
# Ensure Az and Graph contexts are available (throws on failure)
Connect-GsaRuntime -Service Both -TenantId $TenantId -SubscriptionId $SubscriptionId
# Query recent backup jobs
$cutoff = (Get-Date).AddHours(-$LookbackHours)
try {
$jobs = Get-AzAutomationJob `
-AutomationAccountName $AutomationAccountName `
-ResourceGroupName $ResourceGroupName `
-RunbookName $BackupRunbookName `
-ErrorAction Stop |
Where-Object { $_.CreationTime -ge $cutoff }
} catch {
Write-Error "Failed to query Automation jobs. Error: $_"
return
}
# Evaluate results
$failedJobs = $jobs | Where-Object { $_.Status -eq 'Failed' }
$noJobsRan = $jobs.Count -eq 0
if (-not $noJobsRan -and $failedJobs.Count -eq 0) {
Write-Verbose "All $($jobs.Count) backup job(s) in the last $LookbackHours hours completed successfully."
[PSCustomObject]@{
Status = "Compliant"
CheckedAt = (Get-Date)
TotalJobs = $jobs.Count
FailedJobs = 0
}
return
}
# Build alert details
$alertReason = if ($noJobsRan) {
"No backup jobs ran in the last $LookbackHours hours. The scheduled backup may be misconfigured or the Automation Account may be stopped."
} else {
"$($failedJobs.Count) of $($jobs.Count) backup job(s) failed in the last $LookbackHours hours."
}
$jobDetails = ""
if ($failedJobs.Count -gt 0) {
$tableRows = ($failedJobs | ForEach-Object {
"<tr><td>$($_.JobId)</td><td>$($_.Status)</td><td>$($_.CreationTime.ToString('yyyy-MM-dd HH:mm'))</td><td>$($_.Exception)</td></tr>"
}) -join "`n"
$jobDetails = @"
<table border="1" cellpadding="5" cellspacing="0">
<tr><th>Job ID</th><th>Status</th><th>Started</th><th>Error</th></tr>
$tableRows
</table>
"@
}
$emailBody = @"
<h2>GSA Backup Compliance Alert</h2>
<p>$alertReason</p>
$jobDetails
<p><strong>Action required:</strong></p>
<ol>
<li>Check the Automation Account <strong>$AutomationAccountName</strong> in resource group <strong>$ResourceGroupName</strong>.</li>
<li>Review the failed job output for error details.</li>
<li>Run the backup manually: <code>Start-AzAutomationRunbook -Name '$BackupRunbookName' -AutomationAccountName '$AutomationAccountName' -ResourceGroupName '$ResourceGroupName'</code></li>
<li>Verify the backup file was created in your backup storage location.</li>
<li>Fix the root cause (expired credentials, API throttling, storage quota) and confirm the next scheduled run succeeds.</li>
</ol>
"@
if ($SkipEmail) {
Write-Verbose "Skipping alert email (-SkipEmail specified)."
} elseif (-not $SenderId -or -not $AlertRecipient) {
Write-Warning "Skipping alert email — -SenderId and -AlertRecipient are required when -SkipEmail is not set."
} else {
try {
Send-GsaAlertEmail `
-SenderId $SenderId `
-Recipient $AlertRecipient `
-Subject "GSA Backup Alert — $alertReason" `
-HtmlBody $emailBody
} catch {
Write-Warning "Alert email failed (non-fatal): $_"
}
}
# Return summary
[PSCustomObject]@{
Status = "NonCompliant"
CheckedAt = (Get-Date)
TotalJobs = $jobs.Count
FailedJobs = $failedJobs.Count
Reason = $alertReason
}
Related content
diff --git a/docs/global-secure-access/scripts/powershell-test-backup-compliance.md b/docs/global-secure-access/scripts/powershell-test-backup-compliance.md new file mode 100644 index 00000000000..e03f60ec3ed --- /dev/null +++ b/docs/global-secure-access/scripts/powershell-test-backup-compliance.md @@ -0,0 +1,183 @@ +--- +title: PowerShell sample - Verify Microsoft Entra Global Secure Access configuration backup compliance +description: "Verify that your Global Secure Access configuration backup runbook ran successfully. Send an alert when the runbook fails or misses a scheduled run." +ms.topic: sample +ms.date: 06/17/2026 +ms.reviewer: tdetzner +ai-usage: ai-assisted +--- + +# Verify Microsoft Entra Global Secure Access configuration backup compliance + +This script checks recent Azure Automation jobs for your Global Secure Access configuration backup runbook. It returns a compliance summary and can send an alert email when the backup job fails or no job ran during the expected schedule. + +The script dot-sources `_GsaOpsHelpers.ps1`, so place the helper file in the same folder before you run this sample. + +## Prerequisites + +- PowerShell 7.0 or later. +- Install the modules listed in the script's `.NOTES` block. +- Use only permissions and roles that your organization has approved for the task. + +## Script + +```powershell +<# +.SYNOPSIS + Monitors GSA configuration backup job results and alerts on failures. +.DESCRIPTION + Queries Azure Automation for the status of GSA backup runbook jobs over a + configurable lookback window. If any jobs failed or no jobs ran during the + expected schedule, sends an alert email via Microsoft Graph. + + Run this script daily as a secondary watchdog runbook in the same (or a + different) Automation Account. +.PARAMETER AutomationAccountName + Name of the Azure Automation Account running the backup runbooks. +.PARAMETER ResourceGroupName + Resource group containing the Automation Account. +.PARAMETER BackupRunbookName + Name of the runbook that performs GSA configuration backups. +.PARAMETER LookbackHours + Number of hours to look back for completed jobs. Default: 26 (covers a + daily schedule with 2-hour buffer). +.PARAMETER AlertRecipient + Email address to receive failure alerts. +.PARAMETER SenderId + UserId or UPN of the mailbox used to send alert emails. +.EXAMPLE + .\Test-GsaBackupCompliance.ps1 -AutomationAccountName "gsa-automation" -ResourceGroupName "gsa-ops-rg" -BackupRunbookName "Export-GsaConfiguration" -AlertRecipient "[email protected]" -SenderId "[email protected]" +.NOTES + Required permissions: Azure — Automation Job Operator on the Automation Account; Graph — Mail.Send + Minimum module versions: Az.Accounts 2.x, Az.Automation 1.x, Microsoft.Graph.Authentication 2.x, Microsoft.Graph.Users.Actions 2.x + Dot-sources scripts/automation/_GsaOpsHelpers.ps1 for shared auth and email helpers. + Author: GSA Operations +#> + +[CmdletBinding()] +[OutputType([pscustomobject])] +param( + [Parameter(Mandatory)] + [string]$AutomationAccountName, + + [Parameter(Mandatory)] + [string]$ResourceGroupName, + + [Parameter(Mandatory)] + [string]$BackupRunbookName, + + [int]$LookbackHours = 26, + + [string]$AlertRecipient, + + [string]$SenderId, + + [string]$TenantId, + + [string]$SubscriptionId, + + [switch]$SkipEmail +) + +$ErrorActionPreference = 'Stop' + +# Load shared helpers (Connect-GsaRuntime, Send-GsaAlertEmail) +. "$PSScriptRoot\_GsaOpsHelpers.ps1" + +# Ensure Az and Graph contexts are available (throws on failure) +Connect-GsaRuntime -Service Both -TenantId $TenantId -SubscriptionId $SubscriptionId + +# Query recent backup jobs +$cutoff = (Get-Date).AddHours(-$LookbackHours) + +try { + $jobs = Get-AzAutomationJob ` + -AutomationAccountName $AutomationAccountName ` + -ResourceGroupName $ResourceGroupName ` + -RunbookName $BackupRunbookName ` + -ErrorAction Stop | + Where-Object { $_.CreationTime -ge $cutoff } +} catch { + Write-Error "Failed to query Automation jobs. Error: $_" + return +} + +# Evaluate results +$failedJobs = $jobs | Where-Object { $_.Status -eq 'Failed' } +$noJobsRan = $jobs.Count -eq 0 + +if (-not $noJobsRan -and $failedJobs.Count -eq 0) { + Write-Verbose "All $($jobs.Count) backup job(s) in the last $LookbackHours hours completed successfully." + [PSCustomObject]@{ + Status = "Compliant" + CheckedAt = (Get-Date) + TotalJobs = $jobs.Count + FailedJobs = 0 + } + return +} + +# Build alert details +$alertReason = if ($noJobsRan) { + "No backup jobs ran in the last $LookbackHours hours. The scheduled backup may be misconfigured or the Automation Account may be stopped." +} else { + "$($failedJobs.Count) of $($jobs.Count) backup job(s) failed in the last $LookbackHours hours." +} + +$jobDetails = "" +if ($failedJobs.Count -gt 0) { + $tableRows = ($failedJobs | ForEach-Object { + "<tr><td>$($_.JobId)</td><td>$($_.Status)</td><td>$($_.CreationTime.ToString('yyyy-MM-dd HH:mm'))</td><td>$($_.Exception)</td></tr>" + }) -join "`n" + $jobDetails = @" +<table border="1" cellpadding="5" cellspacing="0"> +<tr><th>Job ID</th><th>Status</th><th>Started</th><th>Error</th></tr> +$tableRows +</table> +"@ +} + +$emailBody = @" +<h2>GSA Backup Compliance Alert</h2> +<p>$alertReason</p> +$jobDetails +<p><strong>Action required:</strong></p> +<ol> +<li>Check the Automation Account <strong>$AutomationAccountName</strong> in resource group <strong>$ResourceGroupName</strong>.</li> +<li>Review the failed job output for error details.</li> +<li>Run the backup manually: <code>Start-AzAutomationRunbook -Name '$BackupRunbookName' -AutomationAccountName '$AutomationAccountName' -ResourceGroupName '$ResourceGroupName'</code></li> +<li>Verify the backup file was created in your backup storage location.</li> +<li>Fix the root cause (expired credentials, API throttling, storage quota) and confirm the next scheduled run succeeds.</li> +</ol> +"@ + +if ($SkipEmail) { + Write-Verbose "Skipping alert email (-SkipEmail specified)." +} elseif (-not $SenderId -or -not $AlertRecipient) { + Write-Warning "Skipping alert email — -SenderId and -AlertRecipient are required when -SkipEmail is not set." +} else { + try { + Send-GsaAlertEmail ` + -SenderId $SenderId ` + -Recipient $AlertRecipient ` + -Subject "GSA Backup Alert — $alertReason" ` + -HtmlBody $emailBody + } catch { + Write-Warning "Alert email failed (non-fatal): $_" + } +} + +# Return summary +[PSCustomObject]@{ + Status = "NonCompliant" + CheckedAt = (Get-Date) + TotalJobs = $jobs.Count + FailedJobs = $failedJobs.Count + Reason = $alertReason +} +``` + +## Related content + +- [Global Secure Access PowerShell samples](../powershell-samples.md) +- [Microsoft Entra Global Secure Access operations guide](../overview-operations.md)