Public/Connect-CopilotGovernance.ps1

function Connect-CopilotGovernance {
    <#
    .SYNOPSIS
        Connects to SharePoint Online for the Copilot Governance cmdlets.

    .DESCRIPTION
        A thin wrapper over Connect-PnPOnline so every cmdlet in the toolkit shares
        one authentication entry point. Read-only usage: the toolkit never changes
        anything unless you explicitly ask a future -Remediate switch to.

        Two authentication modes:

          Delegated (default): interactive sign-in. The scan runs as the signed-in
          user, so coverage is bounded by the sites that user can reach. A tenant-wide
          scan will only read sites where the user is a member or a site collection
          administrator. This is fine for a single site or a small set.

          App-only (certificate): pass -Tenant and -Thumbprint (or -CertificatePath)
          for an Entra app registration that has the SharePoint Sites.FullControl.All
          Application permission with admin consent. App-only reads every site
          regardless of who can access it, which is what a full-tenant assessment
          needs. PnP.PowerShell does app-only with a certificate, not a client secret
          (client-secret app-only relies on Azure ACS, which Microsoft is retiring).

        Tip: Register-PnPEntraIDApp creates the app registration and generates the
        certificate for you in one step.

    .PARAMETER Url
        The site or tenant-admin URL to connect to. Use the tenant-admin URL
        (e.g. https://contoso-admin.sharepoint.com) for -AllSites scans.

    .PARAMETER ClientId
        Your Entra app registration (client) id.

    .PARAMETER Tenant
        App-only: the tenant name, e.g. contoso.onmicrosoft.com.

    .PARAMETER Thumbprint
        App-only: thumbprint of a certificate installed in the current user's
        certificate store, whose public key is uploaded to the app registration.

    .PARAMETER CertificatePath
        App-only: path to a .pfx certificate file (alternative to -Thumbprint).

    .PARAMETER CertificatePassword
        App-only: SecureString password for the .pfx, if it has one.

    .EXAMPLE
        Connect-CopilotGovernance -Url "https://contoso-admin.sharepoint.com" -ClientId "<guid>"
        Delegated sign-in.

    .EXAMPLE
        Connect-CopilotGovernance -Url "https://contoso-admin.sharepoint.com" -ClientId "<guid>" -Tenant "contoso.onmicrosoft.com" -Thumbprint "<cert-thumbprint>"
        App-only sign-in for a full-tenant scan.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Delegated')]
    param(
        [Parameter(Mandatory)][string]$Url,
        [Parameter(Mandatory)][string]$ClientId,

        [Parameter(ParameterSetName = 'AppOnlyThumbprint', Mandatory)]
        [Parameter(ParameterSetName = 'AppOnlyCert', Mandatory)]
        [string]$Tenant,

        [Parameter(ParameterSetName = 'AppOnlyThumbprint', Mandatory)]
        [string]$Thumbprint,

        [Parameter(ParameterSetName = 'AppOnlyCert', Mandatory)]
        [string]$CertificatePath,

        [Parameter(ParameterSetName = 'AppOnlyCert')]
        [securestring]$CertificatePassword
    )

    if (-not (Get-Module -ListAvailable -Name PnP.PowerShell)) {
        throw "PnP.PowerShell is required. Run: Install-Module PnP.PowerShell -Scope CurrentUser"
    }

    $appOnly = $PSCmdlet.ParameterSetName -like 'AppOnly*'
    if ($appOnly) {
        $connect = @{ Url = $Url; ClientId = $ClientId; Tenant = $Tenant }
        if ($Thumbprint) { $connect.Thumbprint = $Thumbprint }
        else {
            $connect.CertificatePath = $CertificatePath
            if ($CertificatePassword) { $connect.CertificatePassword = $CertificatePassword }
        }
        Connect-PnPOnline @connect
    }
    else {
        Connect-PnPOnline -Url $Url -Interactive -ClientId $ClientId
    }

    # Remember how we connected so per-site reconnects during an -AllSites scan use the
    # same method (see Connect-CGSite). Without this, a tenant-wide scan reconnects
    # interactively per site and coverage collapses to the signed-in user's access.
    $script:CGAuth = @{
        ClientId            = $ClientId
        Tenant              = $Tenant
        Thumbprint          = $Thumbprint
        CertificatePath     = $CertificatePath
        CertificatePassword = $CertificatePassword
        AppOnly             = [bool]$appOnly
    }
    Write-Verbose ("Connected to {0} ({1})" -f $Url, $(if ($appOnly) { 'app-only' } else { 'delegated' }))
}