Skip to content

Control 4.7: Microsoft 365 Copilot Data Governance - PowerShell Setup

This playbook provides PowerShell automation guidance for Control 4.7.


Prerequisites

# Install required modules
Install-Module -Name Microsoft.Graph -Scope CurrentUser
Install-Module -Name Microsoft.Online.SharePoint.PowerShell -Force

Connect to Services

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.Read.All", "Organization.Read.All", "Application.Read.All"

# Connect to SharePoint Online
Connect-SPOService -Url "https://tenant-admin.sharepoint.com"  # Replace 'tenant' with your tenant name

Parameter Status

RestrictContentOrgWideSearch is the current GA parameter for site-level search/Copilot restrictions. Copilot-specific parameters are in preview and subject to change.

Inventory Copilot Licenses

# Step 1: Find Copilot SKU IDs from tenant subscriptions
$copilotSkus = Get-MgSubscribedSku -All | Where-Object {
    $_.SkuPartNumber -match 'Copilot'
} | Select-Object -ExpandProperty SkuId

if ($copilotSkus.Count -eq 0) {
    Write-Warning "No Copilot SKUs found in tenant subscriptions"
} else {
    Write-Host "Found $($copilotSkus.Count) Copilot SKU(s)"
}

# Step 2: Filter users assigned those SKUs
$licensedUsers = Get-MgUser -Filter "assignedLicenses/any()" -All |
    Where-Object {
        $_.AssignedLicenses | Where-Object {
            $_.SkuId -in $copilotSkus
        }
    }

Write-Host "Found $($licensedUsers.Count) users with Copilot licenses"

# Export for documentation
$licensedUsers | Select-Object DisplayName, UserPrincipalName, Id |
    Export-Csv -Path "Copilot-Licensed-Users.csv" -NoTypeInformation

Audit Content Exclusions

# Get all sites with Copilot exclusion status
$allSites = Get-SPOSite -Limit All | Where-Object { $_.Template -notlike "*SPSPERS*" }

$siteAudit = $allSites | ForEach-Object {
    [PSCustomObject]@{
        Url = $_.Url
        Title = $_.Title
        RestrictedFromCopilot = $_.RestrictContentOrgWideSearch
        SensitivityLabel = $_.SensitivityLabel
        StorageUsedGB = [math]::Round($_.StorageUsageCurrent / 1024, 2)
    }
}

$siteAudit | Export-Csv -Path "Site-Copilot-Status.csv" -NoTypeInformation

# Count summary
$excludedCount = ($siteAudit | Where-Object { $_.RestrictedFromCopilot -eq $true }).Count
$includedCount = ($siteAudit | Where-Object { $_.RestrictedFromCopilot -eq $false }).Count

Write-Host "Sites excluded from Copilot: $excludedCount"
Write-Host "Sites accessible by Copilot: $includedCount"

Exclude Sensitive Sites

# Exclude sensitive sites from Copilot
# Replace 'tenant' with your tenant name and update site URLs to match your environment
$sensitiveSites = @(
    "https://tenant.sharepoint.com/sites/ExecutiveCompensation",
    "https://tenant.sharepoint.com/sites/MergerTarget",
    "https://tenant.sharepoint.com/sites/LegalHold",
    "https://tenant.sharepoint.com/sites/ComplianceInvestigations"
)

foreach ($siteUrl in $sensitiveSites) {
    Set-SPOSite -Identity $siteUrl -RestrictContentOrgWideSearch $true
    Write-Host "Excluded from Copilot: $siteUrl" -ForegroundColor Yellow
}

# Verify exclusions
foreach ($siteUrl in $sensitiveSites) {
    $site = Get-SPOSite -Identity $siteUrl
    Write-Host "$siteUrl - RCD Enabled: $($site.RestrictContentOrgWideSearch)"
}

Audit Integrated Apps and Plugins

# List integrated apps and plugins
$apps = Get-MgServicePrincipal -All |
    Where-Object { $_.Tags -contains "WindowsAzureActiveDirectoryIntegratedApp" }

$appAudit = $apps | ForEach-Object {
    [PSCustomObject]@{
        DisplayName = $_.DisplayName
        Publisher = $_.PublisherName
        AppId = $_.AppId
        CreatedDate = $_.CreatedDateTime
        SignInAudience = $_.SignInAudience
    }
}

$appAudit | Export-Csv -Path "Integrated-Apps-Inventory.csv" -NoTypeInformation
Write-Host "Found $($appAudit.Count) integrated apps"

Generate Governance Recommendations

# Identify potentially sensitive sites not excluded
$sensitivePatterns = @("executive", "legal", "hr", "confidential", "board", "merger", "acquisition")

$potentialSensitive = $siteAudit | Where-Object {
    $url = $_.Url.ToLower()
    $title = $_.Title.ToLower()
    ($sensitivePatterns | Where-Object { $url -like "*$_*" -or $title -like "*$_*" }) -and
    $_.RestrictedFromCopilot -ne $true
}

if ($potentialSensitive.Count -gt 0) {
    Write-Host "`nWARNING: $($potentialSensitive.Count) potentially sensitive sites not excluded!" -ForegroundColor Red
    $potentialSensitive | Select-Object Url, Title | Format-Table
} else {
    Write-Host "`nNo sensitive sites found that need exclusion" -ForegroundColor Green
}

Graph Connector ACL Validation

# Review Graph Connector connections
Connect-MgGraph -Scopes "ExternalConnection.Read.All"

# Get external connections (Graph Connectors)
$connections = Get-MgExternalConnection

foreach ($conn in $connections) {
    Write-Host "`nConnection: $($conn.Name)" -ForegroundColor Cyan

    # Get connection operations/status
    $status = Get-MgExternalConnectionOperation -ExternalConnectionId $conn.Id -All |
        Sort-Object -Property StartedDateTime -Descending |
        Select-Object -First 5

    foreach ($op in $status) {
        $statusColor = switch ($op.Status) {
            "completed" { "Green" }
            "inProgress" { "Yellow" }
            "failed" { "Red" }
            default { "White" }
        }
        Write-Host "  $($op.StartedDateTime): $($op.Status) - $($op.Type)" -ForegroundColor $statusColor
    }
}

Complete Configuration Script

<#
.SYNOPSIS
    Configures Control 4.7 - Microsoft 365 Copilot Data Governance

.DESCRIPTION
    This script configures Copilot data governance:
    1. Inventories Copilot licenses
    2. Audits content exclusions
    3. Excludes sensitive sites from Copilot
    4. Generates governance recommendations

.PARAMETER AdminUrl
    SharePoint Admin Center URL

.PARAMETER SensitiveSites
    Array of site URLs to exclude from Copilot

.EXAMPLE
    .\Configure-Control-4.7.ps1 -AdminUrl "https://contoso-admin.sharepoint.com"

.NOTES
    Last Updated: January 2026
    Related Control: Control 4.7 - Microsoft 365 Copilot Data Governance
#>

param(
    [Parameter(Mandatory=$true)]
    [string]$AdminUrl,

    [Parameter(Mandatory=$false)]
    [string[]]$SensitiveSites = @()
)

try {
    # Connect to Microsoft Graph
    Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
    Connect-MgGraph -Scopes "User.Read.All", "Organization.Read.All", "Application.Read.All"

    # Connect to SharePoint Online
    Write-Host "Connecting to SharePoint Online..." -ForegroundColor Cyan
    Connect-SPOService -Url $AdminUrl

    Write-Host "Configuring Control 4.7 Copilot Data Governance" -ForegroundColor Cyan

    # Audit content exclusions
    Write-Host "`nAuditing Copilot content exclusions..." -ForegroundColor Yellow
    $allSites = Get-SPOSite -Limit All | Where-Object { $_.Template -notlike "*SPSPERS*" }

    $siteAudit = $allSites | ForEach-Object {
        [PSCustomObject]@{
            Url = $_.Url
            Title = $_.Title
            RestrictedFromCopilot = $_.RestrictContentOrgWideSearch
            SensitivityLabel = $_.SensitivityLabel
        }
    }

    $siteAudit | Export-Csv -Path "Site-Copilot-Status.csv" -NoTypeInformation

    $excludedCount = ($siteAudit | Where-Object { $_.RestrictedFromCopilot -eq $true }).Count
    $includedCount = ($siteAudit | Where-Object { $_.RestrictedFromCopilot -eq $false }).Count

    Write-Host "  Sites excluded from Copilot: $excludedCount" -ForegroundColor Green
    Write-Host "  Sites accessible by Copilot: $includedCount" -ForegroundColor Yellow

    # Exclude specified sensitive sites
    if ($SensitiveSites.Count -gt 0) {
        Write-Host "`nExcluding sensitive sites from Copilot..." -ForegroundColor Yellow
        foreach ($siteUrl in $SensitiveSites) {
            try {
                Set-SPOSite -Identity $siteUrl -RestrictContentOrgWideSearch $true
                Write-Host "  [DONE] Excluded: $siteUrl" -ForegroundColor Green
            }
            catch {
                Write-Host "  [FAIL] $siteUrl: $($_.Exception.Message)" -ForegroundColor Red
            }
        }
    }

    # Check for potentially sensitive sites not excluded
    Write-Host "`nGenerating governance recommendations..." -ForegroundColor Yellow
    $sensitivePatterns = @("executive", "legal", "hr", "confidential", "board", "merger", "acquisition")

    $potentialSensitive = $siteAudit | Where-Object {
        $url = $_.Url.ToLower()
        $title = $_.Title.ToLower()
        ($sensitivePatterns | Where-Object { $url -like "*$_*" -or $title -like "*$_*" }) -and
        $_.RestrictedFromCopilot -ne $true
    }

    if ($potentialSensitive.Count -gt 0) {
        Write-Host "  [WARN] $($potentialSensitive.Count) potentially sensitive sites not excluded" -ForegroundColor Yellow
        $potentialSensitive | Select-Object Url, Title | Format-Table
    } else {
        Write-Host "  [PASS] No sensitive sites need exclusion" -ForegroundColor Green
    }

    Write-Host "`nCopilot Governance Summary:" -ForegroundColor Cyan
    Write-Host "  Total sites: $($allSites.Count)" -ForegroundColor Green
    Write-Host "  Excluded from Copilot: $excludedCount" -ForegroundColor Green
    Write-Host "  Recommendations: $($potentialSensitive.Count)" -ForegroundColor $(if ($potentialSensitive.Count -gt 0) { "Yellow" } else { "Green" })

    Write-Host "`n[PASS] Control 4.7 configuration completed successfully" -ForegroundColor Green
}
catch {
    Write-Host "[FAIL] Error: $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "[INFO] Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Yellow
    exit 1
}
finally {
    # Cleanup connections
    Disconnect-MgGraph -ErrorAction SilentlyContinue
    Disconnect-SPOService -ErrorAction SilentlyContinue
}

Back to Control 4.7 | Portal Walkthrough | Verification Testing | Troubleshooting


Updated: January 2026 | Version: v1.2