Public/Invoke-CopilotReadinessAssessment.ps1

function Invoke-CopilotReadinessAssessment {
    <#
    .SYNOPSIS
        Runs the full Copilot readiness assessment (Everyone/EEEU grants +
        oversharing links) and produces a branded HTML scorecard.

    .DESCRIPTION
        Composes Get-EveryoneAccess and Get-OversharedContent over a single site or
        the whole tenant, scores the exposure (0-100, higher = safer), writes a
        self-contained HTML scorecard, and returns a summary object. Read-only.

        Score = 100 - min(100, anonymousLinks*8 + everyoneGrants*4 + orgLinks*2),
        so anonymous ("Anyone") links weigh heaviest.

    .PARAMETER Site
        A single site collection URL to assess.

    .PARAMETER AllSites
        Assess the whole tenant (admin connection + -ClientId).

    .PARAMETER IncludeRootSite
        Include the tenant root site during -AllSites (large; off by default).

    .PARAMETER ClientId
        Your PnP Entra app client id (needed for -AllSites).

    .PARAMETER OutputHtml
        Path for the HTML scorecard (default .\CopilotReadiness.html).

    .PARAMETER Show
        Open the scorecard in the default browser when done.

    .EXAMPLE
        Invoke-CopilotReadinessAssessment -AllSites -ClientId $cid -Show
    #>

    [CmdletBinding(DefaultParameterSetName = 'Site')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Site', Position = 0)]
        [string]$Site,

        [Parameter(Mandatory, ParameterSetName = 'AllSites')]
        [switch]$AllSites,

        [switch]$IncludeRootSite,

        [string]$ClientId,

        [string]$OutputHtml = (Join-Path (Get-Location) 'CopilotReadiness.html'),

        [switch]$Show
    )

    if ($AllSites -and -not $ClientId) { throw "-AllSites requires -ClientId." }

    Write-Host "Assessing Everyone/EEEU grants..." -ForegroundColor Cyan
    $everyone = if ($AllSites) {
        Get-EveryoneAccess -AllSites -ClientId $ClientId -IncludeRootSite:$IncludeRootSite
    } else {
        Get-EveryoneAccess -Site $Site
    }
    $covEveryone = $script:CGCoverage

    Write-Host "Assessing oversharing links..." -ForegroundColor Cyan
    $shares = if ($AllSites) {
        Get-OversharedContent -AllSites -ClientId $ClientId -IncludeRootSite:$IncludeRootSite
    } else {
        Get-OversharedContent -Site $Site
    }
    $covShares = $script:CGCoverage

    # --- score ---
    $anon = @($shares   | Where-Object { $_.LinkClass -eq 'Anonymous' }).Count
    $org  = @($shares   | Where-Object { $_.LinkClass -eq 'Organization' }).Count
    $eeeu = @($everyone).Count
    $sc    = Get-CGReadinessScore -AnonymousLinks $anon -EveryoneGrants $eeeu -OrganizationLinks $org
    $score = $sc.Score
    $band  = $sc.Band

    $sitesAtRisk = @(@($everyone.Site) + @($shares.Site) | Where-Object { $_ } | Select-Object -Unique).Count

    # Coverage: union of sites either scan could not read (blind spots).
    $skippedSites = @(@($covEveryone.Skipped) + @($covShares.Skipped) | Where-Object { $_ } | Select-Object -Unique)
    $totalSites   = [Math]::Max([int]$covEveryone.Total, [int]$covShares.Total)
    $scannedSites = $totalSites - $skippedSites.Count

    $summary = @{
        Score        = $score
        Band         = $band
        Anon         = $anon
        Org          = $org
        Eeeu         = $eeeu
        SitesAtRisk  = $sitesAtRisk
        Total        = $totalSites
        Scanned      = $scannedSites
        Skipped      = $skippedSites.Count
        SkippedSites = $skippedSites
        ScopeLabel   = if ($AllSites) { 'Whole tenant' } else { $Site }
        Generated    = (Get-Date).ToString('yyyy-MM-dd HH:mm')
    }

    New-CGScorecardHtml -Everyone $everyone -Shares $shares -Summary $summary -Path $OutputHtml
    Write-Host ""
    Write-Host (" Readiness score: {0}/100 ({1})" -f $score, $band) -ForegroundColor $(if ($score -ge 80) { 'Green' } elseif ($score -ge 50) { 'Yellow' } else { 'Red' })
    Write-Host (" Scorecard: {0}" -f $OutputHtml) -ForegroundColor Cyan
    if ($skippedSites.Count -gt 0) {
        Write-Host (" Coverage: scanned {0} of {1} sites; {2} could not be read - the score may understate exposure." -f $scannedSites, $totalSites, $skippedSites.Count) -ForegroundColor DarkYellow
    }

    if ($Show) { Invoke-Item $OutputHtml }

    [pscustomobject]@{
        Score             = $score
        Band              = $band
        AnonymousLinks    = $anon
        OrganizationLinks = $org
        EveryoneGrants    = $eeeu
        SitesAtRisk       = $sitesAtRisk
        SitesScanned      = $scannedSites
        SitesSkipped      = $skippedSites.Count
        SkippedSites      = $skippedSites
        Report            = $OutputHtml
        EveryoneAccess    = $everyone
        OversharedContent = $shares
    }
}