Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions scripts/spo-export-sites-with-custom-script-enabled/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# SharePoint Online - Export Sites with Custom Script Enabled

## Professional Summary

This PowerShell script connects to the SharePoint Online Admin Center using certificate-based authentication and audits all SharePoint Online site collections to identify sites where Custom Script is enabled. The results are exported to a timestamped CSV report containing key site metadata, including site name, URL, owner, template, creation date, and execution metadata for audit purposes.

The script is designed for Microsoft 365 administrators who require a tenant-wide inventory of sites that permit custom scripting, enabling governance, security, and compliance reviews.

## Why it matters

Custom Script allows users to add or modify JavaScript, master pages, and other customizations within SharePoint Online. While this capability is required for some legacy business solutions, it also increases the potential attack surface and may bypass modern SharePoint development practices.

Many organizations intentionally disable Custom Script across their tenant to improve security, standardization, and supportability. However, exceptions are often granted for legacy applications, migration projects, or business-critical solutions.

Maintaining an accurate inventory of sites where Custom Script is enabled helps administrators:

- Identify legacy or non-standard SharePoint implementations.
- Validate compliance with organizational governance policies.
- Support security assessments and audit activities.
- Plan modernization initiatives by locating sites that rely on classic customization techniques.
- Reduce operational risk by reviewing sites that permit unsupported or legacy customizations.

## Prerequisites

- PowerShell 7.x or Windows PowerShell 5.1
- PnP.PowerShell module installed
- SharePoint Administrator or Global Administrator permissions
- Microsoft Entra ID App Registration configured for certificate-based authentication
- Certificate installed on the execution host
- Appropriate Microsoft Graph and SharePoint application permissions granted

# [PnP PowerShell](#tab/pnpps)

```powershell
#==========================================
# Configuration
#==========================================

$ClientID = "xxxxxxxxxxxxxxxx"
$ThumbPrint = "xxxxxxxxxxxxxxxx"
$Tenant = "contoso.onmicrosoft.com"

$AdminUrl = "https://contoso-admin.sharepoint.com"

#==========================================
# Audit Metadata
#==========================================

$RunBy = $env:USERNAME
$RunDate = Get-Date
$ReportID = [Guid]::NewGuid()

#==========================================
# Output
#==========================================

$OutputFolder = "C:\Temp\Report"
$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$OutputFile = Join-Path $OutputFolder "CustomScriptEnabledSites_$TimeStamp.csv"

if (!(Test-Path $OutputFolder)) {
New-Item -Path $OutputFolder -ItemType Directory -Force | Out-Null
}

#==========================================
# Connect
#==========================================

try {
Connect-PnPOnline -Url $AdminUrl -ClientId $ClientID -Thumbprint $ThumbPrint -Tenant $Tenant
}
catch {
Write-Host "Connection failed: $($_.Exception.Message)" -ForegroundColor Red
exit
}

#==========================================
# Retrieve Sites
#==========================================

Write-Host "Retrieving tenant sites..." -ForegroundColor Cyan

$Sites = Get-PnPTenantSite -Detailed
$Results = [System.Collections.Generic.List[object]]::new()

foreach ($Site in $Sites) {

# Custom Script Enabled = 1
if ($Site.DenyAddAndCustomizePages -eq 1) {

$Results.Add([PSCustomObject]@{
SiteName = $Site.Title
SiteUrl = $Site.Url
Owner = $Site.Owner
Template = $Site.Template
CreatedDate = $Site.CreationDate

CustomScriptEnabled = $true

RunBy = $RunBy
RunDate = $RunDate
ReportID = $ReportID
})
}
}

#==========================================
# Export
#==========================================

$Results |
Sort-Object SiteName |
Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8

#==========================================
# Summary
#==========================================

Write-Host ""
Write-Host "============================================" -ForegroundColor Cyan
Write-Host "Custom Script Enabled Sites Report Complete"
Write-Host "============================================" -ForegroundColor Cyan

Write-Host "Sites Scanned : $($Sites.Count)"
Write-Host "Custom Script Enabled Sites : $($Results.Count)"
Write-Host "Report ID : $ReportID"
Write-Host "Run By : $RunBy"
Write-Host "Run Date : $RunDate"
Write-Host ""
Write-Host "Report saved to:"
Write-Host $OutputFile -ForegroundColor Green

Disconnect-PnPOnline


```

## Authentication

The script authenticates using certificate-based application authentication, making it suitable for:

- Scheduled Tasks
- Azure Automation
- Azure Functions
- Unattended administrative execution
- Secure enterprise automation

No interactive sign-in is required.

## Usage

1. Configure the following variables:
- Client ID
- Certificate Thumbprint
- Tenant Name
- SharePoint Admin Center URL
2. Execute the script.
3. Review the generated CSV report located in the configured output directory.

## Output

### CSV Report Location

C:\Temp\Report\

### Report Contents

The script generates a CSV report containing:

| Column | Description |
| ------------------- | ------------------------------ |
| SiteName | SharePoint site title |
| SiteUrl | Site collection URL |
| Owner | Primary site owner |
| Template | Site template |
| CreatedDate | Site creation date |
| CustomScriptEnabled | Indicates Custom Script status |
| RunBy | User executing the script |
| RunDate | Script execution date/time |
| ReportID | Unique execution identifier |

## Notes

- The report is read-only and makes no changes to the Microsoft 365 tenant.
- Hidden, redirected, and special-purpose SharePoint sites may also be returned depending on tenant configuration.
- Ensure the executing identity has sufficient permissions to enumerate all SharePoint Online site collections.
- Verify that the script checks DenyAddAndCustomizePages -eq 0 when reporting sites with Custom Script enabled.

## Contributors

|Author(s)|
|-----------|
|[Josiah Opiyo](https://github.com/ojopiyo)|

*Built with a focus on automation, governance, least privilege, and clean Microsoft 365 tenants-helping M365 admins gain visibility and reduce operational risk.*

## Version history

|Version|Date|Comments|
|-------|----|--------|
|1.0|August 02, 2026|Initial release|

[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]