Templates/Plugins/Invoke-PluginPostRunReport.ps1

# Local JSON Run Report Plugin — IDBridge PostRun plugin template
<#
Shipped with the IDBridge module and copied to <RootPath>\Plugins by New-IDBridgeConfig.
Works out of the box — just set Enabled = $true on its descriptor in IDBridgeConfig.psd1.

Writes a run-summary JSON (RunSummary-<timestamp>.json) to <RootPath>\Exports after every
run, including failed and ReadOnly runs. Demonstrates the PostRun contract: the run's
outcome, counts, and change lists arrive on the -RunResult parameter (see docs/plugins.md
for the schema); config and the run's log lines come from the existing accessors
Get-IDBridgeConfig and Get-IDBridgeLogs.

The summary carries counts, the run's Warn/Error log lines, and the Applied write journal —
one line per attempted write (PersonID + UPN/group + outcome), the full "what did the run
do" record. No full user records, but the journal and log lines do contain IDs and
addresses — think before shipping the file off the box. RunResult.SourceData and the change
lists carry the complete records if your report needs more (SecureStrings are already
scrubbed to $null).
#>



function Invoke-PluginPostRunReport {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [pscustomobject]$RunResult
    )

    #region Import Configuration
    try { $IDConfig = Get-IDBridgeConfig } catch { Throw $_ }
    #endregion Import Configuration

    #Warn/Error lines from the in-memory log buffer - the closest thing to "what actually
    #failed" (per-user write errors are logged, not collected in the change lists).
    $problemLogs = @((Get-IDBridgeLogs) | Where-Object { $_.Level -in @('Warn', 'Error') })

    $summary = [ordered]@{
        SchemaVersion    = $RunResult.SchemaVersion
        ModuleVersion    = $RunResult.ModuleVersion
        Success          = $RunResult.Success
        Error            = if ($RunResult.RunError) { "$($RunResult.RunError)" } else { $null }
        RunStart         = $RunResult.RunStart.ToString("yyyy-MM-dd HH:mm:ss")
        RunEnd           = $RunResult.RunEnd.ToString("yyyy-MM-dd HH:mm:ss")
        DurationSeconds  = $RunResult.DurationSeconds
        ReadOnly         = $RunResult.ReadOnly
        TestRun          = $RunResult.TestRun
        AppliedCounts    = $RunResult.Counts   # actual applied outcomes - zeros in ReadOnly
        Applied          = [ordered]@{          # per-write results (RunResult.Applied)
            Succeeded = @($RunResult.Applied | Where-Object { $_.Success }).Count
            Failed    = @($RunResult.Applied | Where-Object { -not $_.Success }).Count
            Failures  = @($RunResult.Applied | Where-Object { -not $_.Success } | ForEach-Object {
                "$($_.Directory) $($_.Action) $($_.PersonID) ($($_.Target)): $($_.Error)"
            })
            #The full audit journal: one line per attempted write, in order. This is the
            #"what exactly did the run do" record - on a big onboarding day it can run to
            #thousands of lines; trim or filter here if your report only needs failures.
            Writes    = @($RunResult.Applied | ForEach-Object {
                "$($_.Timestamp) $($_.Directory) $($_.Action) $($_.PersonID) ($($_.Target)): $(if ($_.Success) { 'OK' } else { "FAILED - $($_.Error)" })"
            })
        }
        Proposed         = [ordered]@{          # computed change lists, sizes only
            AD = [ordered]@{
                Enabled     = $RunResult.AD.Enabled
                Create      = @($RunResult.AD.UsersToCreate).Count
                Update      = @($RunResult.AD.UsersToUpdate.UpdateList).Where({ $null -ne $_ }).Count
                Rename      = @($RunResult.AD.UsersToUpdate.RenameList).Where({ $null -ne $_ }).Count
                Move        = @($RunResult.AD.UsersToUpdate.MoveList).Where({ $null -ne $_ }).Count
                Deactivate  = @($RunResult.AD.UsersToDeactivate).Count
                GroupAdd    = @($RunResult.AD.GroupsToUpdate.Add.Groups).Where({ $null -ne $_ }).Count
                GroupRemove = @($RunResult.AD.GroupsToUpdate.Remove.Groups).Where({ $null -ne $_ }).Count
                OrgUnits    = @($RunResult.AD.OrgUnitsToCreate).Count
            }
            Google = [ordered]@{
                Enabled     = $RunResult.Google.Enabled
                Create      = @($RunResult.Google.UsersToCreate).Count
                Update      = @($RunResult.Google.UsersToUpdate).Count
                Deactivate  = @($RunResult.Google.UsersToDeactivate).Count
                GroupAdd    = @($RunResult.Google.GroupsToUpdate.Add.Groups).Where({ $null -ne $_ }).Count
                GroupRemove = @($RunResult.Google.GroupsToUpdate.Remove.Groups).Where({ $null -ne $_ }).Count
                OrgUnits    = @($RunResult.Google.OrgUnitsToCreate).Count
            }
        }
        ThresholdResults = @($RunResult.ThresholdResults)
        Warnings         = @($problemLogs | ForEach-Object { "$($_.Timestamp) $($_.Level): $($_.Message)" })
    }

    $reportPath = "$($IDConfig.Paths.ExportsRoot)\RunSummary-$($RunResult.RunEnd.ToString("yyyy-MM-dd-HH.mm.ss")).json"
    $summary | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath -Force

    Write-Log -Message "Plugin: Invoke-PluginPostRunReport wrote run summary to $reportPath"
}