Public/Invoke-Argus.ps1

function Invoke-Argus {
    <#
    .SYNOPSIS
        Runs the ARGUS governance checks and writes the report.

    .DESCRIPTION
        Runs the selected read-only governance checks against the connected Microsoft 365 tenant,
        computes the weighted Governance Score and writes the outputs:
        - a self-contained HTML report (dashboard, per-check findings and remediation);
        - a JSON results file (machine-readable, re-renderable with Export-ArgusReport);
        - optionally one CSV per failed check with the full findings list.

        Checks excluded by -Area, -Id, -ExcludeId or -Tag are reported as NotRun. Checks whose
        prerequisites (service connection, scope, licence) are not met are reported as Skipped with
        the reason. A check that throws is reported as Error and the run continues.

        Call Connect-Argus first.

    .PARAMETER Area
        Run only checks of these areas.

    .PARAMETER Id
        Run only these check IDs. Wildcards are supported (for example ARG.ENT.*).

    .PARAMETER ExcludeId
        Do not run these check IDs. Wildcards are supported.

    .PARAMETER Tag
        Run only checks with at least one of these tags.

    .PARAMETER ConfigPath
        Path of a configuration file created with New-ArgusConfig. Values are merged over the defaults.

    .PARAMETER OutputPath
        Folder where the report files are written. Created if missing. Defaults to ./output.

    .PARAMETER OutputFormat
        Output formats to write. Valid values: Html, Json, Csv. Defaults to Html and Json.

    .PARAMETER NoBrowser
        Do not open the HTML report in the default browser at the end of the run.

    .PARAMETER NoBanner
        Do not print the ARGUS banner (name, version, author, copyright) at the start of the run.

    .PARAMETER PassThru
        Return the results object.

    .PARAMETER CustomCheckPath
        Additional folder(s) containing custom check files to run alongside the built-in catalogue.

    .PARAMETER MaxFindingsInHtml
        Maximum number of findings rows embedded in the HTML report per check. The full list is always
        available in the JSON and CSV outputs. Defaults to 500.

    .EXAMPLE
        Connect-Argus; Invoke-Argus

        Runs the full catalogue and opens the HTML report.

    .EXAMPLE
        Invoke-Argus -Area EntraID, Licensing -OutputFormat Html, Json, Csv -NoBrowser

        Runs the Entra ID and Licensing checks and writes HTML, JSON and CSV files without opening a browser.

    .EXAMPLE
        $result = Invoke-Argus -ConfigPath ./contoso.config.json -ExcludeId ARG.SPO.002 -PassThru -NoBrowser

        Runs with a custom configuration, skips one check and returns the results object.

    .OUTPUTS
        None by default. With -PassThru, a PSCustomObject containing run metadata, score and per-check results.

    .LINK
        Connect-Argus

    .LINK
        Get-ArgusCheck

    .LINK
        Export-ArgusReport
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter()]
        [ValidateSet('EntraID', 'Applications', 'Licensing', 'GroupsTeams', 'SharePoint', 'Exchange', 'Copilot')]
        [string[]] $Area,

        [Parameter()]
        [SupportsWildcards()]
        [string[]] $Id,

        [Parameter()]
        [SupportsWildcards()]
        [string[]] $ExcludeId,

        [Parameter()]
        [string[]] $Tag,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $ConfigPath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $OutputPath = (Join-Path -Path (Get-Location) -ChildPath 'output'),

        [Parameter()]
        [ValidateSet('Html', 'Json', 'Csv')]
        [string[]] $OutputFormat = @('Html', 'Json'),

        [Parameter()]
        [switch] $NoBrowser,

        [Parameter()]
        [switch] $NoBanner,

        [Parameter()]
        [switch] $PassThru,

        [Parameter()]
        [string[]] $CustomCheckPath,

        [Parameter()]
        [ValidateRange(0, 100000)]
        [int] $MaxFindingsInHtml = 500
    )

    if (-not $NoBanner) { Write-ArgusBanner }
    $startedAt = [datetimeoffset]::Now

    $config = Get-ArgusConfig -Path $ConfigPath
    if ($PSBoundParameters.ContainsKey('MaxFindingsInHtml')) {
        $config.Report.MaxFindingsInHtml = $MaxFindingsInHtml
    }

    $catalog = @(Get-ArgusCheckCatalog -CustomCheckPath $CustomCheckPath)
    $environment = Get-ArgusEnvironment
    if ('Graph' -notin $environment.ConnectedServices) {
        Write-Warning 'Not connected to Microsoft Graph. Run Connect-Argus first; checks that need Graph will be Skipped.'
    }

    $filter = @{}
    foreach ($name in 'Area', 'Id', 'ExcludeId', 'Tag') {
        if ($PSBoundParameters.ContainsKey($name)) { $filter[$name] = $PSBoundParameters[$name] }
    }

    $script:ArgusDataCache = @{}
    $script:ArgusDataSetNotes = @{}
    $results = @(Invoke-ArgusCheckRunner -Check $catalog -Config $config -Environment $environment -Now $startedAt.UtcDateTime @filter)
    $score = Get-ArgusScore -Result $results -Scoring $config.Scoring
    $finishedAt = [datetimeoffset]::Now

    $document = ConvertTo-ArgusResultDocument -Result $results -Score $score -Environment $environment -Config $config `
        -StartedAt $startedAt -FinishedAt $finishedAt -Filter $filter -ConfigPath $ConfigPath

    # Outputs
    $null = New-Item -Path $OutputPath -ItemType Directory -Force
    $tenantLabel = $environment.TenantDefaultDomain ?? $environment.TenantName ?? 'tenant'
    $slug = (($tenantLabel -split '\.')[0] -replace '[^A-Za-z0-9-]', '-').Trim('-')
    if (-not $slug) { $slug = 'tenant' }
    $baseName = 'ARGUS-Report-{0}-{1}' -f $slug, $startedAt.ToString('yyyyMMdd-HHmm')

    $files = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
    if ('Json' -in $OutputFormat) {
        $files.Add((Export-ArgusResultJson -Document $document -Path (Join-Path $OutputPath "$baseName.json")))
    }
    if ('Csv' -in $OutputFormat) {
        foreach ($result in $results | Where-Object { $_.Status -eq 'Failed' -and @($_.Findings).Count -gt 0 }) {
            $files.Add((Export-ArgusFindingCsv -Result $result -Path (Join-Path $OutputPath "$baseName-$($result.Id).csv")))
        }
    }
    if ('Html' -in $OutputFormat) {
        $htmlPath = [System.IO.Path]::GetFullPath((Join-Path $OutputPath "$baseName.html"), (Get-Location).ProviderPath)
        $json = $document | ConvertTo-Json -Depth 10
        $html = ConvertTo-ArgusHtml -Json $json -MaxFindingsInHtml $config.Report.MaxFindingsInHtml -LogoPath $config.Report.LogoPath
        [System.IO.File]::WriteAllText($htmlPath, $html, [System.Text.UTF8Encoding]::new($false))
        $files.Add((Get-Item -LiteralPath $htmlPath))
        if (-not $NoBrowser) { Open-ArgusFile -Path $htmlPath }
    }

    Write-ArgusRunSummary -Document $document -File $files

    if ($PassThru) { $document }
}