Skip to content

Control 4.1: SharePoint Information Access Governance (IAG) — PowerShell Setup

Read the FSI PowerShell baseline first

Before running any command in this playbook, read the PowerShell Authoring Baseline for FSI Implementations. It is the canonical source for module version pinning, sovereign-cloud (GCC / GCC High / DoD) endpoints, mutation safety (-WhatIf / SupportsShouldProcess), Dataverse compatibility, and SHA-256 evidence emission. Snippets below show abbreviated patterns; the baseline is authoritative.

Automation guidance for Control 4.1. Uses Microsoft.Online.SharePoint.PowerShell (SPO Management Shell), PnP.PowerShell (DAG and site context), and Microsoft.Graph (group resolution / audit log).


Module Prerequisites

# Pin known-good versions; confirm in your release notes
Install-Module Microsoft.Online.SharePoint.PowerShell -MinimumVersion 16.0.25814.12000 -Scope CurrentUser -Force
Install-Module PnP.PowerShell                          -MinimumVersion 2.12.0           -Scope CurrentUser -Force
Install-Module Microsoft.Graph                         -MinimumVersion 2.25.0           -Scope CurrentUser -Force

Import-Module Microsoft.Online.SharePoint.PowerShell
Import-Module PnP.PowerShell
Import-Module Microsoft.Graph.Groups

Sovereign clouds: Use the appropriate -Region / endpoint for GCC, GCC High, and DoD per the baseline. Do not hard-code *.sharepoint.com.


Connect

$AdminUrl = 'https://contoso-admin.sharepoint.com'   # Replace; use *.sharepoint.us for GCC High / DoD
Connect-SPOService -Url $AdminUrl
Connect-MgGraph -Scopes 'Group.Read.All','AuditLog.Read.All' -NoWelcome

Verify connection:

Get-SPOTenant | Select-Object SignInAccelerationDomain, SharingCapability

1. Inventory: Identify RCD/RAC Posture Across the Tenant

$AllSites = Get-SPOSite -Limit All -IncludePersonalSite:$false

$Inventory = $AllSites | Select-Object `
    Url, Title, Owner,
    RestrictContentOrgWideSearch,
    RestrictedAccessControl,
    @{ N = 'RACGroupCount'; E = { ($_.RestrictedAccessControlGroups -split ',' | Where-Object { $_ }).Count } },
    SensitivityLabel, SharingCapability, ConditionalAccessPolicy, LastContentModifiedDate

$ReportPath = Join-Path -Path (Get-Location) -ChildPath ("IAG-Inventory-{0:yyyyMMdd}.csv" -f (Get-Date))
$Inventory | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8

# Emit SHA-256 evidence hash (per baseline)
$Hash = (Get-FileHash -Path $ReportPath -Algorithm SHA256).Hash
Write-Host ("Inventory: {0} (SHA-256 {1})" -f $ReportPath, $Hash)

2. Enable RCD on a Single Site (Idempotent, -WhatIf Aware)

function Set-IagRcd {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory)] [string] $SiteUrl,
        [Parameter(Mandatory)] [bool]   $Enable,
        [Parameter(Mandatory)] [string] $Justification
    )

    try {
        $site = Get-SPOSite -Identity $SiteUrl -ErrorAction Stop
    } catch {
        Write-Error "Site not found: $SiteUrl ($($_.Exception.Message))"
        return
    }

    if ($site.RestrictContentOrgWideSearch -eq $Enable) {
        Write-Host "[SKIP] $SiteUrl already RCD=$Enable"
        return
    }

    if ($PSCmdlet.ShouldProcess($SiteUrl, "Set RestrictContentOrgWideSearch=$Enable")) {
        Set-SPOSite -Identity $SiteUrl -RestrictContentOrgWideSearch $Enable -ErrorAction Stop
        Write-Host "[OK]   $SiteUrl RCD=$Enable :: $Justification"
    }
}

# Examples
Set-IagRcd -SiteUrl 'https://contoso.sharepoint.com/sites/FinanceConfidential' `
           -Enable $true -Justification 'Zone 3 SOX-scoped, ticket CHG-1042' -WhatIf

The function is idempotent (no-op when desired state already holds) and supports -WhatIf for dry runs — required by the baseline.


3. Bulk Enable RCD from a Governance Manifest

Manifest CSV columns: Url, Zone, Justification, ChangeTicket.

$Manifest = Import-Csv -Path '.\rcd-manifest.csv'

$Results = foreach ($row in $Manifest) {
    try {
        Set-IagRcd -SiteUrl $row.Url -Enable $true -Justification "$($row.Zone) :: $($row.Justification) ($($row.ChangeTicket))"
        [pscustomobject]@{ Url = $row.Url; Status = 'OK'; Error = $null }
    } catch {
        [pscustomobject]@{ Url = $row.Url; Status = 'FAIL'; Error = $_.Exception.Message }
    }
}

$ResultsPath = Join-Path -Path (Get-Location) -ChildPath ("RCD-BulkApply-{0:yyyyMMddHHmm}.csv" -f (Get-Date))
$Results | Export-Csv -Path $ResultsPath -NoTypeInformation -Encoding UTF8
$Hash = (Get-FileHash $ResultsPath -Algorithm SHA256).Hash
Write-Host ("Bulk apply log: {0} (SHA-256 {1})" -f $ResultsPath, $Hash)

4. Configure Restricted SharePoint Search (RSS) Allow-List

Short-term posture

Treat RSS as a transitional control. Document an exit plan to RCD + Purview.

# Enable tenant-level RSS
Set-SPOTenant -EnableRestrictedAccessControl $false   # Reserved
Set-SPOTenant -EnableRestrictedSearchAllList $true

# Add sites (current documented limit: 100; verify on Microsoft Learn)
$AllowList = @(
    'https://contoso.sharepoint.com/sites/CopilotPilot',
    'https://contoso.sharepoint.com/sites/HRPolicies'
)

foreach ($url in $AllowList) {
    try {
        Add-SPOTenantRestrictedSearchAllowedList -SiteUrl $url -ErrorAction Stop
        Write-Host "[OK] Added $url to RSS allow-list"
    } catch {
        Write-Warning "Add failed for $url :: $($_.Exception.Message)"
    }
}

# Inspect current RSS state
Get-SPOTenant | Select-Object EnableRestrictedSearchAllList
Get-SPOTenantRestrictedSearchAllowedList

5. Configure Restricted Access Control (RAC) per Site

function Resolve-EntraGroupId {
    param([Parameter(Mandatory)][string]$DisplayName)
    $g = Get-MgGroup -Filter "displayName eq '$DisplayName'" -ConsistencyLevel eventual -ErrorAction Stop
    if (-not $g) { throw "Group not found: $DisplayName" }
    if ($g.Count -gt 1) { throw "Ambiguous group name: $DisplayName" }
    return $g.Id
}

function Set-IagRac {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory)] [string]   $SiteUrl,
        [Parameter(Mandatory)] [string[]] $GroupDisplayNames,   # max 10
        [Parameter(Mandatory)] [string]   $Justification
    )

    if ($GroupDisplayNames.Count -gt 10) {
        throw "RAC supports a maximum of 10 security groups per site."
    }

    $groupIds = $GroupDisplayNames | ForEach-Object { Resolve-EntraGroupId -DisplayName $_ }

    if ($PSCmdlet.ShouldProcess($SiteUrl, "Enable RAC with $($groupIds.Count) group(s)")) {
        Set-SPOSite -Identity $SiteUrl -RestrictedAccessControl $true -ErrorAction Stop
        Set-SPOSite -Identity $SiteUrl -RestrictedAccessControlGroups ($groupIds -join ',') -ErrorAction Stop
        Write-Host "[OK] RAC applied :: $SiteUrl :: $Justification"
    }
}

# Example: M&A deal room
Set-IagRac -SiteUrl 'https://contoso.sharepoint.com/sites/MandA-ProjectAlpha' `
           -GroupDisplayNames @('MandA-DealTeam-Alpha','Compliance-Surveillance') `
           -Justification 'MNPI ethical wall, ticket CHG-1099' -WhatIf

Delegate RAC Management to Site Admins

Set-SPOTenant -DelegateRestrictedAccessControlManagement $true
Get-SPOTenant | Select-Object DelegateRestrictedAccessControlManagement

When delegated, site admins must supply a justification on each policy update — captured to the unified audit log.


6. Generate RAC Insights

# Generates an admin report on RAC-protected sites and their access patterns
Start-SPORestrictedAccessForSitesInsights
# Retrieve results from SharePoint admin center > Reports > Restricted access control insights

7. Pull Audit Evidence (Microsoft Graph)

# Search the unified audit log for IAG operations (last 30 days)
$Start = (Get-Date).AddDays(-30).ToString('o')
$End   = (Get-Date).ToString('o')

$ops = @('SiteRestrictedFromOrgSearch','RestrictedAccessControlPolicyUpdated')

$events = foreach ($op in $ops) {
    Search-UnifiedAuditLog -StartDate $Start -EndDate $End -Operations $op -ResultSize 5000
}

$AuditPath = Join-Path -Path (Get-Location) -ChildPath ("IAG-Audit-{0:yyyyMMdd}.csv" -f (Get-Date))
$events | Export-Csv -Path $AuditPath -NoTypeInformation -Encoding UTF8
$Hash = (Get-FileHash $AuditPath -Algorithm SHA256).Hash
Write-Host ("Audit evidence: {0} (SHA-256 {1})" -f $AuditPath, $Hash)

Search-UnifiedAuditLog requires Connect-IPPSSession (Exchange Online Management module).


Cleanup

Disconnect-SPOService -ErrorAction SilentlyContinue
Disconnect-MgGraph    -ErrorAction SilentlyContinue

Error-Handling Patterns

Scenario Pattern
Site not found try/catch on Get-SPOSite -ErrorAction Stop; record FAIL row in results CSV
Throttling (429) Wrap mutating cmdlets with exponential backoff (per baseline)
Ambiguous group Resolve-EntraGroupId throws; halt and surface to operator
Stale module Pin -MinimumVersion; CI fails the script if older module is loaded
Sovereign cloud Use Connect-SPOService -Region and Connect-MgGraph -Environment per baseline

Back to Control 4.1 | Portal Walkthrough | Verification & Testing | Troubleshooting


Updated: April 2026 | Version: v1.4.0