Public/Get-CwSnapshot.ps1

function Get-CwSnapshot {
    <#
    .SYNOPSIS
        Creates the canonical JSON snapshot from the local collectors, or loads
        previously saved snapshots from disk.
    .DESCRIPTION
        Collect mode (default): runs Get-CwCertInventory and optionally
        Test-CwCrlHealth (-IncludeCrl), Get-CwCaHealth (-IncludeCA) and
        Test-CwEndpoint (-ProbeEndpoints) on the local machine and returns one
        normalized snapshot object - the canonical artifact every report and
        diff derives from.
 
        Load mode: -Path (+ -Latest / -Skip) reads snapshot JSON files back.
        '-Latest -Skip 1' returns the second-newest snapshot, which makes it
        the natural baseline for trend reports.
    .EXAMPLE
        Get-CwSnapshot -IncludeCrl -IncludeCA
    .EXAMPLE
        Get-CwSnapshot -Latest -Path .\snapshots -Skip 1
    #>

    [CmdletBinding(DefaultParameterSetName = 'Collect')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(ParameterSetName = 'Collect')]
        [switch]$IncludeCrl,

        [Parameter(ParameterSetName = 'Collect')]
        [switch]$IncludeCA,

        [Parameter(ParameterSetName = 'Collect')]
        [string[]]$ProbeEndpoints,

        [Parameter(ParameterSetName = 'Collect')]
        [string]$Tenant,

        [Parameter(ParameterSetName = 'Collect')]
        [int]$WarnDays = 30,

        [Parameter(ParameterSetName = 'Collect')]
        [int]$CritDays = 7,

        [Parameter(ParameterSetName = 'Collect')]
        [double]$CrlWarnHours = 24,

        [Parameter(ParameterSetName = 'Collect')]
        [double]$CrlCritHours = 0,

        [Parameter(Mandatory, ParameterSetName = 'Load')]
        [string]$Path,

        [Parameter(ParameterSetName = 'Load')]
        [switch]$Latest,

        [Parameter(ParameterSetName = 'Load')]
        [int]$Skip = 0
    )

    if ($PSCmdlet.ParameterSetName -eq 'Load') {
        # First run friendly: a missing snapshot directory/file simply yields
        # nothing (e.g. -Baseline (Get-CwSnapshot -Latest ...) before any scan
        # has ever been persisted) instead of throwing.
        if (-not (Test-Path -Path $Path)) {
            Write-Verbose "Snapshot path '$Path' not found - nothing to load."
            return
        }
        $files = if (Test-Path -Path $Path -PathType Container) {
            Get-ChildItem -Path $Path -Filter '*.json' -File | Sort-Object LastWriteTime -Descending
        }
        else {
            Get-Item -Path $Path
        }
        if ($Latest) {
            $files = $files | Select-Object -Skip $Skip -First 1
        }
        foreach ($file in $files) {
            Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
        }
        return
    }

    # --- collect mode --------------------------------------------------------
    $certs = @(Get-CwCertInventory -WarnDays $WarnDays -CritDays $CritDays)

    $crls = @()
    if ($IncludeCrl) {
        # The CRLs that matter for THIS host are the ones referenced by its own
        # certificates' CDP extensions - that is exactly where relying parties
        # go for revocation checks of these certs. No CDPs -> nothing to check
        # (a workstation with only cloud-issued certs stays quiet).
        $cdpUrls = @($certs | ForEach-Object { $_.CdpUrls } | Where-Object { $_ } | Sort-Object -Unique)
        if ($cdpUrls.Count -gt 0) {
            $crls = @(Test-CwCrlHealth -CrlUrl $cdpUrls -WarnHours $CrlWarnHours -CritHours $CrlCritHours -ErrorAction SilentlyContinue)
        }
        else {
            Write-Verbose 'No CDP URLs found in local certificates - no CRLs to check.'
        }
    }

    $caHealth = $null
    if ($IncludeCA) {
        $caHealth = Get-CwCaHealth
    }

    $endpoints = @()
    if ($ProbeEndpoints) {
        $endpoints = @(Test-CwEndpoint -Uri $ProbeEndpoints -WarnDays $WarnDays -CritDays $CritDays)
    }

    $toolVersion = if ($MyInvocation.MyCommand.Module) { $MyInvocation.MyCommand.Module.Version.ToString() } else { '1.1.0' }

    [pscustomobject]@{
        PSTypeName    = 'CertWatchtower.Snapshot'
        SchemaVersion = '1.0'
        ToolVersion   = $toolVersion
        Host          = $env:COMPUTERNAME
        Tenant        = $Tenant
        Timestamp     = (Get-Date).ToString('o')
        Thresholds    = [pscustomobject]@{
            WarnDays = $WarnDays; CritDays = $CritDays
            CrlWarnHours = $CrlWarnHours; CrlCritHours = $CrlCritHours
        }
        Certificates  = $certs
        Crls          = $crls
        CaHealth      = $caHealth
        Endpoints     = $endpoints
    }
}