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), andMicrosoft.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:
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