Public/New-UTCMSnapshot.ps1
|
function New-UTCMSnapshot { <# .SYNOPSIS Creates a new UTCM configuration snapshot. .DESCRIPTION Submits a createSnapshot request to the UTCM API and polls until the job reaches a terminal status (succeeded, failed, or partiallySuccessful). Resources can be specified explicitly or via a JSON-backed preset. DisplayName is auto-sanitized to meet the API constraint (letters, numbers, and spaces only). .PARAMETER PollingIntervalSeconds Seconds between status polls while the job runs. Default: 10. .PARAMETER Resources Explicit UTCM resource identifiers. Overrides -Preset. .PARAMETER Preset Named preset from Presets/resource-presets.json. Default: TenantCore. .PARAMETER DisplayName Friendly snapshot name (alphanumeric + spaces only; special characters stripped). .PARAMETER Description Snapshot description. Default: "Baseline snapshot". .OUTPUTS The completed snapshot job object (id, status, resourceLocation). .EXAMPLE New-UTCMSnapshot .EXAMPLE New-UTCMSnapshot -Preset ExchangeCore -DisplayName "Exchange Baseline" .EXAMPLE New-UTCMSnapshot -Resources 'microsoft.exchange.sharedmailbox','microsoft.exchange.transportrule' #> [CmdletBinding(DefaultParameterSetName = 'Preset', SupportsShouldProcess = $true)] param( # How often to poll the job status [ValidateRange(5,300)] [int] $PollingIntervalSeconds = 10, # Supply explicit UTCM resource identifiers (overrides -Preset) [Parameter(ParameterSetName = 'Explicit')] [string[]] $Resources, # Or pick a JSON-backed preset (see Presets\resource-presets.json) [Parameter(ParameterSetName = 'Preset')] [string] $Preset = 'TenantCore', # Friendly metadata [string] $DisplayName = $("UTCM Snapshot " + (Get-Date -Format 'yyyyMMdd HHmm')), [string] $Description = "Baseline snapshot" ) # Ensure we have a valid Graph token (module helper, if present) if (Get-Command -Name Ensure-GraphConnection -ErrorAction SilentlyContinue) { Ensure-GraphConnection } # Resolve resources via JSON presets or explicit list, then warn (non-blocking) on unknown types $effectiveResources = Resolve-UTCMResources -Resources $Resources -Preset $Preset Test-UTCMResourceTypes -Resources $effectiveResources | Out-Null # UTCM API allows only letters, digits, and spaces in displayName $DisplayName = ($DisplayName -replace '[^a-zA-Z0-9 ]', ' ') -replace '\s+', ' ' $DisplayName = $DisplayName.Trim() # Build the request body – UTCM action REQUIRES 'resources' $body = @{ displayName = $DisplayName description = $Description resources = $effectiveResources } if (Get-Command -Name Write-Log -ErrorAction SilentlyContinue) { Write-Log -Message ("Starting UTCM snapshot: {0} (resources={1})" -f $DisplayName, ($effectiveResources -join ', ')) -Color Cyan } if (-not $PSCmdlet.ShouldProcess($DisplayName, "Create UTCM snapshot")) { return } # --- Start the snapshot job (UTCM action endpoint) --- # Correct endpoint: POST /beta/admin/configurationManagement/configurationSnapshots/createSnapshot $job = Invoke-GraphRequestWithRetry -Method 'POST' -Uri $script:CreateSnapshotActionUri -Body $body # --- Poll until terminal status (notStarted|running -> succeeded|failed|partiallySuccessful) --- do { Start-Sleep -Seconds $PollingIntervalSeconds # Job read endpoint: GET /beta/admin/configurationManagement/configurationSnapshotJobs/{id} $status = Invoke-GraphRequestWithRetry -Method 'GET' -Uri "$($script:SnapshotJobsUri)/$($job.id)" if (Get-Command -Name Write-Log -ErrorAction SilentlyContinue) { Write-Log -Message "Snapshot status: $($status.status)" -Color Gray } } while ($status.status -in @('notStarted','running')) # --- Terminal evaluation --- if ($status.status -notin @('succeeded','partiallySuccessful')) { # Extract error details — Invoke-MgGraphRequest returns a hashtable, # so try both hashtable key access and PSObject property access. $errorInfo = @() try { $ed = $null if ($status -is [System.Collections.IDictionary] -and $status.ContainsKey('errorDetails')) { $ed = $status['errorDetails'] } elseif ($status.PSObject.Properties.Name -contains 'errorDetails') { $ed = $status.errorDetails } if ($ed -and $ed.Count -gt 0) { $errorInfo += "errorDetails: $($ed -join '; ')" } } catch { <# ignore #> } # Dump the full job status so we can diagnose server-side failures $statusDump = try { $status | ConvertTo-Json -Depth 5 -Compress } catch { $status.ToString() } Write-Warning "Full snapshot job response: $statusDump" throw ("Snapshot job failed with status '{0}'. {1}" -f $status.status, ($errorInfo -join ' | ')) } if (Get-Command -Name Write-Log -ErrorAction SilentlyContinue) { Write-Log -Message ("Snapshot completed with status '{0}'. ResourceLocation={1}" -f $status.status, $status.resourceLocation) -Color Green } # Return the final job (contains id, status, and resourceLocation) return $status } |