Public/Get-UTCMAvailableSnapshot.ps1

function Get-UTCMAvailableSnapshot {
    <#
    .SYNOPSIS
        Lists available UTCM snapshot jobs from the tenant.
 
    .DESCRIPTION
        Queries GET /beta/admin/configurationManagement/configurationSnapshotJobs with
        optional server-side OData filtering ($filter, $select) and client-side post-filters.
        Returns snapshot job metadata sorted by createdDateTime descending.
 
    .PARAMETER AsJson
        Return results as a JSON string instead of objects.
 
    .PARAMETER Status
        Filter to specific job status(es): notStarted, running, succeeded, failed, partiallySuccessful.
 
    .PARAMETER OnlyCompleted
        Convenience switch — equivalent to -Status succeeded,partiallySuccessful.
 
    .PARAMETER Since
        Only return jobs created on or after this datetime (UTC).
 
    .PARAMETER Until
        Only return jobs created before this datetime (UTC).
 
    .PARAMETER Top
        Limit to the N most recent jobs (client-side).
 
    .PARAMETER IncludeDetails
        Include resourceLocation and errorDetails in the server-side $select.
 
    .PARAMETER Select
        Custom field names for server-side $select (overrides -IncludeDetails).
 
    .PARAMETER DownloadableOnly
        Keep only jobs that have a non-empty resourceLocation.
 
    .EXAMPLE
        Get-UTCMAvailableSnapshot
 
    .EXAMPLE
        Get-UTCMAvailableSnapshot -OnlyCompleted -DownloadableOnly -Top 5
 
    .EXAMPLE
        Get-UTCMAvailableSnapshot -Since (Get-Date).AddDays(-7) -AsJson
    #>

    [CmdletBinding()]
    param(
        # Original switch kept for compatibility
        [switch] $AsJson,

        # New: filter to specific status(es); auto-validated
        [ValidateSet('notStarted','running','succeeded','failed','partiallySuccessful')]
        [string[]] $Status,

        # New: convenience — equivalent to -Status succeeded,partiallySuccessful (unless -Status is explicitly provided)
        [switch] $OnlyCompleted,

        # New: time window filters (UTC ISO 8601 in server-side OData $filter)
        [datetime] $Since,
        [datetime] $Until,

        # New: return at most N newest items (client-side)
        [int] $Top = 0,

        # New: include extra fields (resourceLocation, errorDetails) in server-side $select
        [switch] $IncludeDetails,

        # New: custom $select (comma-separated field names); overrides -IncludeDetails if provided
        [string[]] $Select,

        # New: keep only jobs that expose a downloadable resourceLocation (client-side filter).
        # If needed, this automatically adds resourceLocation to $select.
        [switch] $DownloadableOnly
    )

    # Ensure a Graph token is available
    if (Get-Command -Name Ensure-GraphConnection -ErrorAction SilentlyContinue) {
        Ensure-GraphConnection
    }

    # -------------------------------
    # Build $select
    # -------------------------------
    $selectList = @()
    if ($Select -and $Select.Count -gt 0) {
        $selectList = $Select
    } else {
        if ($IncludeDetails -or $DownloadableOnly) {
            $selectList = @('id','displayName','createdDateTime','status','resourceLocation','errorDetails','createdBy','resources','tenantId')
        } else {
            $selectList = @('id','displayName','createdDateTime','status','createdBy','resources','tenantId')
        }
    }

    # -------------------------------
    # Build $filter (OData) for server-side filtering
    # -------------------------------
    $filters = @()

    if ($Since) {
        # ISO 8601 "o" (round-trip) format
        $filters += "createdDateTime ge " + $Since.ToString("o")
    }
    if ($Until) {
        $filters += "createdDateTime le " + $Until.ToString("o")
    }

    # -OnlyCompleted implies succeeded/partiallySuccessful if -Status not explicitly provided
    if ($OnlyCompleted -and -not $Status) {
        $Status = @('succeeded','partiallySuccessful')
    }
    if ($Status) {
        if ($Status.Count -eq 1) {
            $filters += "status eq '$($Status[0])'"
        } else {
            $filters += '(' + (($Status | ForEach-Object { "status eq '$_'" }) -join ' or ') + ')'
        }
    }

    # Assemble querystring
    $qs = @()
    if ($selectList.Count -gt 0) {
        $qs += "`$select=" + [System.Uri]::EscapeDataString(($selectList -join ','))
    }
    if ($filters.Count -gt 0) {
        $qs += "`$filter=" + [System.Uri]::EscapeDataString(($filters -join ' and '))
    }

    $uri = $script:SnapshotJobsUri
    if ($qs.Count -gt 0) {
        $uri = $uri + '?' + ($qs -join '&')
    }

    # -------------------------------
    # Page through all results
    # -------------------------------
    $all = @()
    do {
        $page = Invoke-GraphRequestWithRetry -Method 'GET' -Uri $uri
        if ($page.value) { $all += $page.value }
        $uri = $null
        if ($page -is [System.Collections.IDictionary]) {
            if ($page.ContainsKey('@odata.nextLink')) { $uri = $page['@odata.nextLink'] }
        } elseif ($page.PSObject.Properties.Match('@odata.nextLink').Count) {
            $uri = $page.'@odata.nextLink'
        }
    } while ($uri)

    # -------------------------------
    # Client-side shaping
    # -------------------------------

    # Sort newest first
    $sorted = $all | Sort-Object createdDateTime -Descending

    # Keep only jobs that have a download URL if requested
    if ($DownloadableOnly) {
        # Ensure resourceLocation exists in projection; if not, we still filter client-side defensively
        $sorted = $sorted | Where-Object { $_.resourceLocation -and -not [string]::IsNullOrWhiteSpace($_.resourceLocation) }
    }

    # Apply Top if provided
    if ($Top -gt 0) {
        $sorted = $sorted | Select-Object -First $Top
    }

    # Output formatting
    if ($AsJson) {
        return ($sorted | ConvertTo-Json -Depth 10)
    }

    # If the caller provided a custom -Select, return the objects as-is to honor the projection
    if ($Select) {
        return $sorted
    }

    # Otherwise, present a friendly view
    if ($IncludeDetails -or $DownloadableOnly) {
        return ($sorted | Select-Object id, displayName, createdDateTime, status, resourceLocation, errorDetails, createdBy, resources, tenantId)
    } else {
        return ($sorted | Select-Object id, displayName, createdDateTime, status, createdBy, resources, tenantId)
    }
}