<# SCRIPT 03 - SITE COLLECTION SAFE TO RUN IN PRODUCTIONSCRIPT 03 - SITE COLLECTION ADMINS REPORT #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$WebAppUrl, [Parameter(Mandatory = $true)] [string]$OutputCsv, [string]$SiteCollectionUrl, [switch]$NoPrompt ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" Write-Host "SCRIPT 03 - SITE COLLECTION ADMINS REPORT" -ForegroundColor Cyan Write-Host "READ-ONLY - NO CHANGES WILL BE MADE" -ForegroundColor Green Write-Host "" #-------------------------------------------------- # Load SharePoint Snap-in #-------------------------------------------------- try { if (-not (Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue)) { Add-PSSnapin "Microsoft.SharePoint.PowerShell" } } catch { throw "Run in SharePoint Management Shell. Error: $($_.Exception.Message)" } #-------------------------------------------------- # Output Setup #-------------------------------------------------- $outDir = Split-Path -Path $OutputCsv -Parent if ([string]::IsNullOrWhiteSpace($outDir)) { throw "Provide a full OutputCsv path. Example: C:\Temp\SiteCollectionAdmins.csv" } if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null } $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") $baseName = [System.IO.Path]::GetFileNameWithoutExtension($OutputCsv) $summaryPath = Join-Path $outDir "$baseName`_$timestamp`_Summary.csv" $logPath = Join-Path $outDir "$baseName`_$timestamp`_RunLog.txt" $errorPath = Join-Path $outDir "$baseName`_$timestamp`_Errors.csv" #-------------------------------------------------- # Logging #-------------------------------------------------- $log = New-Object System.Collections.Generic.List[string] $errors = New-Object System.Collections.Generic.List[object] function Log { param($msg) $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg $log.Add($line) | Out-Null Write-Host $line } function Add-Error { param($scope, $msg) $errors.Add([pscustomobject]@{ Timestamp = Get-Date Scope = $scope Message = $msg }) | Out-Null } #-------------------------------------------------- # Prompt #-------------------------------------------------- if (-not $NoPrompt) { Write-Host "This script inventories Site Collection Administrators" -ForegroundColor Yellow if ((Read-Host "Type YES to continue") -ne "YES") { return } } #-------------------------------------------------- # Resolve Sites #-------------------------------------------------- function Get-Sites { if ($SiteCollectionUrl) { return @(Get-SPSite -Identity $SiteCollectionUrl) } return @(Get-SPSite -WebApplication $WebAppUrl -Limit All) } #-------------------------------------------------- # Risk Model #-------------------------------------------------- function Get-RiskLevel { param($IsPrimary, $TotalAdmins) if ($TotalAdmins -gt 5) { return "High" } if ($IsPrimary -eq $false) { return "Medium" } return "Low" } function Get-Score { param($risk) switch ($risk) { "High" { return 30 } "Medium" { return 60 } "Low" { return 90 } default { return 50 } } } function Get-Recommendation { param($risk) switch ($risk) { "High" { return "Too many site collection admins. Reduce before migration." } "Medium" { return "Validate admin assignments. Ensure least privilege model." } "Low" { return "Admin configuration acceptable." } } } #-------------------------------------------------- # Main Execution #-------------------------------------------------- $results = New-Object System.Collections.Generic.List[object] try { $sites = Get-Sites Log "Resolved $($sites.Count) site collections" } catch { Add-Error $WebAppUrl $_.Exception.Message throw } foreach ($site in $sites) { Log "Scanning Site Collection: $($site.Url)" try { $primaryAdmin = $site.Owner $secondaryAdmin = $site.SecondaryContact $admins = $site.RootWeb.SiteAdministrators # Key method 【1-2309db】 $adminCount = $admins.Count foreach ($admin in $admins) { $isPrimary = ($admin.LoginName -eq $primaryAdmin.LoginName) $risk = Get-RiskLevel -IsPrimary $isPrimary -TotalAdmins $adminCount $results.Add([pscustomobject]@{ SiteCollectionUrl = $site.Url AdminName = $admin.DisplayName LoginName = $admin.LoginName Email = $admin.Email IsPrimaryAdmin = $isPrimary TotalAdmins = $adminCount RiskLevel = $risk Score = Get-Score $risk Category = "SiteCollectionAdmins" ActionRecommendation = Get-Recommendation $risk }) | Out-Null } $site.Dispose() } catch { Add-Error $site.Url $_.Exception.Message } } #-------------------------------------------------- # Export Reports #-------------------------------------------------- $results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8 $results | Group-Object SiteCollectionUrl | ForEach-Object { [pscustomobject]@{ SiteCollectionUrl = $_.Name AdminCount = ($_.Group | Select-Object -First 1).TotalAdmins } } | Export-Csv -Path $summaryPath -NoTypeInformation -Encoding UTF8 $log | Set-Content $logPath if ($errors.Count -gt 0) { $errors | Export-Csv -Path $errorPath -NoTypeInformation -Encoding UTF8 Write-Host "ERROR REPORT: $errorPath" -ForegroundColor Yellow } Write-Host "DETAIL REPORT: $OutputCsv" -ForegroundColor Green Write-Host "SUMMARY REPORT: $summaryPath" -ForegroundColor Green Write-Host "RUN LOG: $logPath" -ForegroundColor Green Write-Host "Complete." -ForegroundColor Green