Public/Export-CaBaseline.ps1

function Export-CaBaseline {
    <#
    .EXTERNALHELP CaOutcome-Help.xml
    .SYNOPSIS
        Records a set of outcomes as a baseline that can be committed and compared against later
    #>


    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('CaOutcome.Baseline')]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNull()]
        [object[]]$Outcome,

        [Parameter()]
        [string]$Path,

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$PassThru
    )

    begin {
        $collected = New-Object System.Collections.Generic.List[object]
    }

    process {
        foreach ($item in $Outcome) {
            if ($null -ne $item) { $collected.Add($item) }
        }
    }

    end {
        $failed = @($collected | Where-Object {
            $_.PSObject.Properties['Failed'] -and $_.Failed
        })

        if ($failed.Count -gt 0) {
            $names = ($failed | ForEach-Object { $_.Scenario }) -join ', '
            if (-not $Force) {
                throw ("$($failed.Count) scenario(s) failed and would be missing from this " +
                    "baseline: $names. A later comparison would report them as removed. Fix " +
                    'the run, or pass -Force if you accept a partial baseline.')
            }
            Write-Warning "Writing a partial baseline; these scenarios failed and are omitted: $names"
        }

        $usable = @($collected | Where-Object {
            -not ($_.PSObject.Properties['Failed'] -and $_.Failed)
        })

        $scenarios = [ordered]@{}
        foreach ($item in ($usable | Sort-Object -Property Scenario)) {
            $name = [string]$item.Scenario
            if ([string]::IsNullOrWhiteSpace($name)) {
                throw ('An outcome has no Scenario name. A baseline is keyed by name, so ' +
                    'every outcome needs one - pass -ScenarioName to ConvertTo-CaOutcome.')
            }
            if ($scenarios.Contains($name)) {
                throw "Two outcomes share the scenario name '$name'. Names must be unique."
            }

            $scenarios[$name] = [ordered]@{
                current            = ConvertTo-CaBaselineEntry -EffectiveControl $item.Current
                projected          = ConvertTo-CaBaselineEntry -EffectiveControl $item.Projected
                reportOnlyApplying = [int]$item.ReportOnlyApplying
            }
        }

        $baseline = [PSCustomObject]@{
            PSTypeName    = 'CaOutcome.Baseline'
            schemaVersion = 1
            scenarios     = $scenarios
        }

        if (-not $Path) {
            return $baseline
        }

        $target = "$($scenarios.Count) scenario(s) to $Path"
        if ($PSCmdlet.ShouldProcess($target, 'Write Conditional Access outcome baseline')) {
            $json = $baseline | Select-Object schemaVersion, scenarios | ConvertTo-Json -Depth 20
            Set-Content -Path $Path -Value $json -Encoding utf8
        }

        if ($PassThru) { return $baseline }
    }
}