<# .SYNOPSIS Script CRIPTIONScript 05 - Group Inventory Report READ-ONLY script to inventory SharePoint groups across site collections. Captures group membership, owners, and permission levels. .PARAMETER WebAppUrl Target Web Application URL .PARAMETER OutputCsv Full output file path .PARAMETER SiteCollectionUrl (Optional) Limit to a single site collection .PARAMETER NoPrompt Skip confirmation prompt #> [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 05 - GROUP INVENTORY REPORT" -ForegroundColor Cyan Write-Host "READ-ONLY - NO CHANGES WILL BE MADE" -ForegroundColor Green # ----------------------------- # 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 full OutputCsv path (e.g., C:\Temp\GroupInventory.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)_Summary_$timestamp.csv" $logPath = Join-Path $outDir "$($baseName)_RunLog_$timestamp.txt" $errorPath = Join-Path $outDir "$($baseName)_Errors_$timestamp.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 SharePoint groups..." -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($UserCount, $Permission) if ($Permission -match "Full Control") { return "High" } if ($UserCount -eq 0) { 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 "Review Full Control group membership before migration." } "Medium" { return "Validate empty group usage. Consider cleanup." } "Low" { return "Group configuration acceptable." } } } # ----------------------------- # Main Execution # ----------------------------- $results = New-Object System.Collections.Generic.List[object] $sites = Get-Sites Log "Resolved $($sites.Count) site collection(s)." foreach ($site in $sites) { try { Log "Scanning Site Collection: $($site.Url)" foreach ($web in $site.AllWebs) { try { foreach ($group in $web.SiteGroups) { $users = $group.Users | ForEach-Object { $_.LoginName } -join ";" $userCount = $group.Users.Count $owner = $group.Owner # Permission roles $permissions = ($web.RoleAssignments | Where-Object { $_.Member.Name -eq $group.Name } | ForEach-Object { $_.RoleDefinitionBindings.Name }) -join ";" $risk = Get-RiskLevel -UserCount $userCount -Permission $permissions $results.Add([pscustomobject]@{ SiteCollectionUrl = $site.Url WebUrl = $web.Url GroupName = $group.Name Owner = $owner UserCount = $userCount Users = $users PermissionLevels = $permissions RiskLevel = $risk Score = Get-Score $risk Category = "GroupInventory" ActionRecommendation = Get-Recommendation $risk }) | Out-Null } } catch { Add-Error $web.Url $_.Exception.Message } finally { $web.Dispose() } } } catch { Add-Error $site.Url $_.Exception.Message } finally { $site.Dispose() } } # ----------------------------- # Export Reports # ----------------------------- $results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8 $results | Group-Object RiskLevel | ForEach-Object { [pscustomobject]@{ RiskLevel = $_.Name Count = $_.Count } } | 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