Collaborate, Innovate, Automate

Compare Two Site Settings

This PnP PowerShell script compares the configuration settings between two SharePoint Online sites. This is essential for ensuring consistency across your SharePoint environment, identifying configuration drift, and maintaining standardized site configurations.

Purpose

Site settings comparison helps with:

Prerequisites

Site Settings Extraction Script

# SharePoint Site Configuration Comparison Script
            # This script exports detailed site settings to compare staging vs prod
            
            # Install PnP.PowerShell if not already installed
            # Install-Module -Name PnP.PowerShell -Force -AllowClobber
            
            param(
                [Parameter(Mandatory=$false)]
                [string]$SiteUrl = "https://tenantName.sharepoint.com/sites/SiteName/",
                
                [Parameter(Mandatory=$false)]
                [string]$OutputPath = "Output Path",
                
                [Parameter(Mandatory=$false)]
                [string]$ClientId = ""
            )
            
            Write-Host "Connecting to $SiteUrl..." -ForegroundColor Cyan
            Connect-PnPOnline -Url $SiteUrl -ClientId $ClientId -Interactive
            
            $siteName = $SiteUrl.Split('/')[-1]
            if ($siteName -eq "") {
                $siteName = $SiteUrl.Split('/')[-2]
            }
            $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $reportFile = Join-Path $OutputPath "$siteName`_config.json"
            
            Write-Host "Gathering site configuration..." -ForegroundColor Cyan
            
            $config = @{}
            
            # 1. Basic Site Information
            Write-Host "  - Basic site information" -ForegroundColor Gray
            $web = Get-PnPWeb -Includes Title, Url, Language, WebTemplate, Configuration, Created, LastItemModifiedDate
            $config.BasicInfo = @{
                Title = $web.Title
                Url = $web.Url
                Language = $web.Language
                WebTemplate = $web.WebTemplate
                Configuration = $web.Configuration
                Created = $web.Created
                LastModified = $web.LastItemModifiedDate
            }
            
            # 2. Site Features
            Write-Host "  - Site and web features" -ForegroundColor Gray
            $config.SiteFeatures = Get-PnPFeature -Scope Site | Select-Object DisplayName, DefinitionId | Sort-Object DisplayName
            $config.WebFeatures = Get-PnPFeature -Scope Web | Select-Object DisplayName, DefinitionId | Sort-Object DisplayName
            
            # 3. Search Settings
            Write-Host "  - Search settings" -ForegroundColor Gray
            try {
                $searchSettings = Get-PnPProperty -ClientObject $web -Property AllProperties
                $config.SearchSettings = @{
                    NoCrawl = $searchSettings.NoCrawl
                    SearchBoxInNavBar = $web.AllProperties["SearchBoxInNavBar"]
                    SearchScope = $web.AllProperties["SearchScope"]
                }
            } catch {
                $config.SearchSettings = "Error retrieving: $_"
            }
            
            # 4. Content Types (Site level)
            Write-Host "  - Content types" -ForegroundColor Gray
            $config.ContentTypes = Get-PnPContentType | Select-Object Name, Id, Group, Hidden | Sort-Object Name
            
            # 5. Site Pages Library Settings
            Write-Host "  - Site Pages library" -ForegroundColor Gray
            try {
                $sitePagesLib = Get-PnPList -Identity "Site Pages" -Includes EnableModeration, EnableVersioning, MajorVersionLimit, EnableMinorVersions, RootFolder, NoCrawl, Hidden
                $config.SitePagesLibrary = @{
                    Title = $sitePagesLib.Title
                    EnableModeration = $sitePagesLib.EnableModeration
                    EnableVersioning = $sitePagesLib.EnableVersioning
                    MajorVersionLimit = $sitePagesLib.MajorVersionLimit
                    EnableMinorVersions = $sitePagesLib.EnableMinorVersions
                    NoCrawl = $sitePagesLib.NoCrawl
                    Hidden = $sitePagesLib.Hidden
                    ItemCount = $sitePagesLib.ItemCount
                    ContentTypesEnabled = $sitePagesLib.ContentTypesEnabled
                }
                
                # Get content types enabled on Site Pages
                $sitePagesContentTypes = Get-PnPContentType -List "Site Pages" | Select-Object Name, Id
                $config.SitePagesContentTypes = $sitePagesContentTypes
            } catch {
                $config.SitePagesLibrary = "Error retrieving: $_"
            }
            
            # 6. Regional Settings
            Write-Host "  - Regional settings" -ForegroundColor Gray
            $regionalSettings = Get-PnPProperty -ClientObject $web -Property RegionalSettings
            $config.RegionalSettings = @{
                LocaleId = $regionalSettings.LocaleId
                TimeZone = $regionalSettings.TimeZone.Description
            }
            
            # 7. Site Collection App Catalog
            Write-Host "  - App catalog and apps" -ForegroundColor Gray
            try {
                $apps = Get-PnPApp
                $config.InstalledApps = $apps | Select-Object Title, Id, Deployed, AppCatalogVersion | Sort-Object Title
            } catch {
                $config.InstalledApps = "Error retrieving apps"
            }
            
            # 8. Navigation
            Write-Host "  - Navigation settings" -ForegroundColor Gray
            $config.Navigation = @{
                QuickLaunch = Get-PnPNavigationNode -Location QuickLaunch | Select-Object Title, Url, Id
                TopNavigationBar = Get-PnPNavigationNode -Location TopNavigationBar | Select-Object Title, Url, Id
            }
            
            # 9. Property Bag (Custom properties)
            Write-Host "  - Property bag values" -ForegroundColor Gray
            $propertyBag = Get-PnPPropertyBag
            $config.PropertyBag = $propertyBag
            
            # 10. Search Schema (Managed Properties relevant to pages)
            Write-Host "  - Search schema (managed properties)" -ForegroundColor Gray
            try {
                # This requires search admin permissions
                $config.SearchSchema = "Requires Search Admin - run separately"
            } catch {
                $config.SearchSchema = "Not accessible"
            }
            
            # 11. Site Permissions
            Write-Host "  - Site permissions" -ForegroundColor Gray
            $config.SiteGroups = Get-PnPGroup | Select-Object Title, Id, LoginName | Sort-Object Title
            
            # 12. Lists and Libraries Summary
            Write-Host "  - Lists and libraries" -ForegroundColor Gray
            $lists = Get-PnPList -Includes Title, ItemCount, BaseTemplate, Hidden, NoCrawl, EnableModeration
            $config.ListsAndLibraries = $lists | Where-Object { -not $_.Hidden } | Select-Object Title, ItemCount, BaseTemplate, NoCrawl, EnableModeration | Sort-Object Title
            
            # 13. Hub Site Association
            Write-Host "  - Hub site info" -ForegroundColor Gray
            try {
                $hubSite = Get-PnPHubSite -Identity $SiteUrl -ErrorAction SilentlyContinue
                if ($hubSite) {
                    $config.HubSite = @{
                        Title = $hubSite.Title
                        Id = $hubSite.Id
                        SiteUrl = $hubSite.SiteUrl
                    }
                } else {
                    $config.HubSite = "Not a hub site or not associated"
                }
            } catch {
                $config.HubSite = "Not a hub site or not associated"
            }
            
            # 14. Site Scripts and Site Designs Applied
            Write-Host "  - Site designs" -ForegroundColor Gray
            try {
                $siteDesigns = Get-PnPSiteDesign
                $config.SiteDesigns = $siteDesigns | Select-Object Title, Id
            } catch {
                $config.SiteDesigns = "Not accessible"
            }
            
            # 15. Indexing Status
            Write-Host "  - Indexing status" -ForegroundColor Gray
            $config.IndexingStatus = @{
                NoCrawl = $web.NoCrawl
                RequestReindex = "Check site settings manually"
            }
            
            # Export to JSON
            Write-Host "`nExporting configuration to $reportFile..." -ForegroundColor Cyan
            $config | ConvertTo-Json -Depth 10 | Out-File -FilePath $reportFile -Encoding UTF8
            
            Write-Host "Configuration exported successfully!" -ForegroundColor Green
            Write-Host "File location: $reportFile" -ForegroundColor Yellow
            
            # Disconnect
            Disconnect-PnPOnline
            
            Write-Host "`nTo compare two sites, run this script against both sites, then use the comparison script." -ForegroundColor Cyan

Settings Comparison Script

# Site Configuration Comparison Script
            # Compares two site configuration JSON files and highlights differences
            
            param(
                [Parameter(Mandatory=$false)]
                [string]$StagingConfigPath = "siteName_config.json",
                
                [Parameter(Mandatory=$false)]
                [string]$ProdConfigPath = "siteName2_config.json",
                
                [Parameter(Mandatory=$false)]
                [string]$OutputReportPath = "comparison_report.html"
            )
            
            Write-Host "Loading configurations..." -ForegroundColor Cyan
            $staging = Get-Content $StagingConfigPath | ConvertFrom-Json
            $prod = Get-Content $ProdConfigPath | ConvertFrom-Json
            
            $differences = @()
            $detailedSections = @{}
            
            function Compare-Objects {
                param($obj1, $obj2, $path)
                
                if ($obj1 -is [System.Management.Automation.PSCustomObject] -and $obj2 -is [System.Management.Automation.PSCustomObject]) {
                    $props1 = $obj1.PSObject.Properties.Name
                    $props2 = $obj2.PSObject.Properties.Name
                    
                    $allProps = $props1 + $props2 | Select-Object -Unique
                    
                    foreach ($prop in $allProps) {
                        $newPath = if ($path) { "$path.$prop" } else { $prop }
                        
                        if ($props1 -notcontains $prop) {
                            $script:differences += [PSCustomObject]@{
                                Property = $newPath
                                Issue = "Missing in Staging"
                                StagingValue = "N/A"
                                ProdValue = $obj2.$prop
                            }
                        }
                        elseif ($props2 -notcontains $prop) {
                            $script:differences += [PSCustomObject]@{
                                Property = $newPath
                                Issue = "Missing in Prod"
                                StagingValue = $obj1.$prop
                                ProdValue = "N/A"
                            }
                        }
                        else {
                            Compare-Objects -obj1 $obj1.$prop -obj2 $obj2.$prop -path $newPath
                        }
                    }
                }
                elseif ($obj1 -is [Array] -and $obj2 -is [Array]) {
                    if ($obj1.Count -ne $obj2.Count) {
                        $script:differences += [PSCustomObject]@{
                            Property = $path
                            Issue = "Array count mismatch"
                            StagingValue = "Count: $($obj1.Count)"
                            ProdValue = "Count: $($obj2.Count)"
                        }
                    }
                }
                else {
                    if ($obj1 -ne $obj2) {
                        # Ignore timestamp differences
                        if ($path -notmatch "Created|LastModified|timestamp") {
                            $script:differences += [PSCustomObject]@{
                                Property = $path
                                Issue = "Value mismatch"
                                StagingValue = $obj1
                                ProdValue = $obj2
                            }
                        }
                    }
                }
            }
            
            # Function to create detailed comparison tables for arrays
            function Get-DetailedArrayComparison {
                param($stagingArray, $prodArray, $sectionName, $keyProperty)
                
                $html = "$sectionName - Detailed Comparison"
                
                if ($stagingArray -and $prodArray) {
                    # Create comparison table
                    $html += @"
                    
            "@
                    
                    # Get all unique items
                    $allItems = @()
                    if ($stagingArray) {
                        $allItems += $stagingArray | ForEach-Object { $_.$keyProperty }
                    }
                    if ($prodArray) {
                        $allItems += $prodArray | ForEach-Object { $_.$keyProperty }
                    }
                    $allItems = $allItems | Select-Object -Unique | Sort-Object
                    
                    foreach ($item in $allItems) {
                        $inStaging = $stagingArray | Where-Object { $_.$keyProperty -eq $item }
                        $inProd = $prodArray | Where-Object { $_.$keyProperty -eq $item }
                        
                        $status = ""
                        $rowClass = ""
                        
                        if ($inStaging -and $inProd) {
                            $status = "✓ Present in both"
                            $rowClass = "info"
                        }
                        elseif ($inStaging -and -not $inProd) {
                            $status = "⚠ Missing in Prod"
                            $rowClass = "warning"
                        }
                        elseif (-not $inStaging -and $inProd) {
                            $status = "⚠ Missing in Staging"
                            $rowClass = "warning"
                        }
                        
                        # Get additional details if available
                        $stagingDetails = if ($inStaging) { 
                            ($inStaging.PSObject.Properties | Where-Object { $_.Name -ne $keyProperty } | ForEach-Object { "$($_.Name): $($_.Value)" }) -join ""
                        } else { "N/A" }
                        
                        $prodDetails = if ($inProd) { 
                            ($inProd.PSObject.Properties | Where-Object { $_.Name -ne $keyProperty } | ForEach-Object { "$($_.Name): $($_.Value)" }) -join ""
                        } else { "N/A" }
                        
                        $html += @"
                            
            "@
                    }
                    
                    $html += @"
                        
                        
                            
                                Item
                                In Staging
                                In Prod
                                Status
                            
                        
                        
                                $item
                                $stagingDetails
                                $prodDetails
                                $status
                            
                    
            "@
                }
                else {
                    $html += "No data available for comparison"
                }
                
                return $html
            }
            
            Write-Host "Comparing configurations..." -ForegroundColor Cyan
            
            # Compare each section
            $sections = @(
                "BasicInfo",
                "SiteFeatures",
                "WebFeatures",
                "SearchSettings",
                "ContentTypes",
                "SitePagesLibrary",
                "SitePagesContentTypes",
                "RegionalSettings",
                "InstalledApps",
                "Navigation",
                "PropertyBag",
                "SiteGroups",
                "ListsAndLibraries",
                "HubSite",
                "IndexingStatus"
            )
            
            foreach ($section in $sections) {
                Write-Host "  Comparing $section..." -ForegroundColor Gray
                if ($staging.$section -and $prod.$section) {
                    Compare-Objects -obj1 $staging.$section -obj2 $prod.$section -path $section
                }
                elseif (!$staging.$section -and $prod.$section) {
                    $differences += [PSCustomObject]@{
                        Property = $section
                        Issue = "Section missing in Staging"
                        StagingValue = "N/A"
                        ProdValue = "Present"
                    }
                }
                elseif ($staging.$section -and !$prod.$section) {
                    $differences += [PSCustomObject]@{
                        Property = $section
                        Issue = "Section missing in Prod"
                        StagingValue = "Present"
                        ProdValue = "N/A"
                    }
                }
            }
            
            # Generate detailed comparison sections
            Write-Host "  Generating detailed comparisons..." -ForegroundColor Gray
            $detailedSections["ContentTypes"] = Get-DetailedArrayComparison -stagingArray $staging.ContentTypes -prodArray $prod.ContentTypes -sectionName "Content Types" -keyProperty "Name"
            $detailedSections["SiteFeatures"] = Get-DetailedArrayComparison -stagingArray $staging.SiteFeatures -prodArray $prod.SiteFeatures -sectionName "Site Features" -keyProperty "DisplayName"
            $detailedSections["WebFeatures"] = Get-DetailedArrayComparison -stagingArray $staging.WebFeatures -prodArray $prod.WebFeatures -sectionName "Web Features" -keyProperty "DisplayName"
            $detailedSections["SitePagesContentTypes"] = Get-DetailedArrayComparison -stagingArray $staging.SitePagesContentTypes -prodArray $prod.SitePagesContentTypes -sectionName "Site Pages Content Types" -keyProperty "Name"
            $detailedSections["InstalledApps"] = Get-DetailedArrayComparison -stagingArray $staging.InstalledApps -prodArray $prod.InstalledApps -sectionName "Installed Apps" -keyProperty "Title"
            $detailedSections["ListsAndLibraries"] = Get-DetailedArrayComparison -stagingArray $staging.ListsAndLibraries -prodArray $prod.ListsAndLibraries -sectionName "Lists and Libraries" -keyProperty "Title"
            
            # Generate HTML Report
            Write-Host "`nGenerating comparison report..." -ForegroundColor Cyan
            
            $htmlReport = @"
            
            
            
                Site Configuration Comparison
                
                    body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; }
                    h1 { color: #0078d4; }
                    h2 { color: #0078d4; margin-top: 40px; border-bottom: 2px solid #0078d4; padding-bottom: 10px; }
                    h3 { color: #005a9e; margin-top: 30px; }
                    .summary { background: white; padding: 20px; border-radius: 8px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
                    table { width: 100%; border-collapse: collapse; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
                    th { background: #0078d4; color: white; padding: 12px; text-align: left; position: sticky; top: 0; }
                    td { padding: 10px; border-bottom: 1px solid #ddd; vertical-align: top; }
                    tr:hover { background: #f9f9f9; }
                    .critical { background: #fff3cd; }
                    .warning { background: #d1ecf1; }
                    .info { background: #d4edda; }
                    .section-header { background: #e7e7e7; font-weight: bold; }
                    .detail-section { margin: 30px 0; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
                    .toc { background: white; padding: 20px; border-radius: 8px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
                    .toc a { display: block; padding: 5px 0; color: #0078d4; text-decoration: none; }
                    .toc a:hover { text-decoration: underline; }
                
            
            
                SharePoint Site Configuration Comparison
                
                    Summary
                    Staging Config: $StagingConfigPath
                    Prod Config: $ProdConfigPath
                    Total Differences Found: $($differences.Count)
                    Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
                
                
                
                    Table of Contents
                    Differences Summary
                    Content Types Comparison
                    Site Pages Content Types Comparison
                    Site Features Comparison
                    Web Features Comparison
                    Installed Apps Comparison
                    Lists and Libraries Comparison
                    Property Bag Comparison
                
                
                Differences Summary
            "@
            
            if ($differences.Count -eq 0) {
                $htmlReport += "No differences found! Sites are configured identically."
            }
            else {
                $htmlReport += @"
                
            "@
                
                $currentSection = ""
                foreach ($diff in ($differences | Sort-Object Property)) {
                    $section = $diff.Property.Split('.')[0]
                    
                    if ($section -ne $currentSection) {
                        $htmlReport += ""
                        $currentSection = $section
                    }
                    
                    $rowClass = ""
                    if ($diff.Property -match "SearchSettings|NoCrawl|IndexingStatus") {
                        $rowClass = "critical"
                    }
                    elseif ($diff.Property -match "ContentType|Feature") {
                        $rowClass = "warning"
                    }
                    
                    $htmlReport += @"
                        
            "@
                }
                
                $htmlReport += @"
                    
                    
                        
                            Property
                            Issue
                            Staging Value
                            Prod Value
                        
                    
                    $section
                            $($diff.Property)
                            $($diff.Issue)
                            $($diff.StagingValue)
                            $($diff.ProdValue)
                        
                
            "@
            }
            
            # Add detailed sections
            $htmlReport += @"
                Detailed Comparisons
                
                
                    $($detailedSections["ContentTypes"])
                
                
                
                    $($detailedSections["SitePagesContentTypes"])
                
                
                
                    $($detailedSections["SiteFeatures"])
                
                
                
                    $($detailedSections["WebFeatures"])
                
                
                
                    $($detailedSections["InstalledApps"])
                
                
                
                    $($detailedSections["ListsAndLibraries"])
                
            "@
            
            # Add Property Bag detailed comparison
            $htmlReport += @"
                
                    Property Bag - Detailed Comparison
                    
            "@
            
            if ($staging.PropertyBag -and $prod.PropertyBag) {
                $allKeys = @()
                $allKeys += $staging.PropertyBag.PSObject.Properties.Name
                $allKeys += $prod.PropertyBag.PSObject.Properties.Name
                $allKeys = $allKeys | Select-Object -Unique | Sort-Object
                
                foreach ($key in $allKeys) {
                    $stagingVal = $staging.PropertyBag.$key
                    $prodVal = $prod.PropertyBag.$key
                    
                    $status = ""
                    $rowClass = ""
                    
                    if ($stagingVal -and $prodVal) {
                        if ($stagingVal -eq $prodVal) {
                            $status = "✓ Same"
                            $rowClass = "info"
                        }
                        else {
                            $status = "⚠ Different"
                            $rowClass = "warning"
                        }
                    }
                    elseif ($stagingVal -and -not $prodVal) {
                        $status = "⚠ Missing in Prod"
                        $rowClass = "warning"
                    }
                    elseif (-not $stagingVal -and $prodVal) {
                        $status = "⚠ Missing in Staging"
                        $rowClass = "warning"
                    }
                    
                    # Highlight search-related properties
                    if ($key -match "search|crawl|index|scope" -and $status -ne "✓ Same") {
                        $rowClass = "critical"
                    }
                    
                    $htmlReport += @"
                        
            "@
                }
            }
            
            $htmlReport += @"
                        
                        
                            
                                Property Key
                                Staging Value
                                Prod Value
                                Status
                            
                        
                        
                            $key
                            $($stagingVal)
                            $($prodVal)
                            $status
                        
                    
                
            "@
            
            $htmlReport += @"
                
                    Key Areas to Check:
                    
                        Search Settings: Check NoCrawl, SearchScope settings
                        Content Types: Verify ContentTypeId values match
                        Site Features: Ensure all features are activated
                        Site Pages Library: Check NoCrawl and moderation settings
                        Indexing Status: Verify site is being crawled
                        Property Bag: Look for search-related custom properties
                    
                
            
            
            "@
            
            # Save HTML report
            $htmlReport | Out-File -FilePath $OutputReportPath -Encoding UTF8
            
            # Also save CSV for easy filtering
            $csvPath = $OutputReportPath -replace '\.html$', '.csv'
            $differences | Export-Csv -Path $csvPath -NoTypeInformation
            
            Write-Host "`nComparison complete!" -ForegroundColor Green
            Write-Host "HTML Report: $OutputReportPath" -ForegroundColor Yellow
            Write-Host "CSV Report: $csvPath" -ForegroundColor Yellow
            Write-Host "`nTotal differences found: $($differences.Count)" -ForegroundColor Cyan
            
            # Display critical differences in console
            $criticalDiffs = $differences | Where-Object { $_.Property -match "SearchSettings|NoCrawl|IndexingStatus|ContentType" }
            if ($criticalDiffs) {
                Write-Host "`nCRITICAL DIFFERENCES (may affect search):" -ForegroundColor Red
                $criticalDiffs | Format-Table -AutoSize
            }
            
            # Open the report
            Start-Process $OutputReportPath

Usage Notes

What Gets Compared