<# .SYNOPSIS SharePoint Production Toolkit - Script 09 - Workflow Inventory Report .DES.DESCRIPTION Optional. Limit to one site collection .PARAMETER NoPrompt Skip confirmation #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$WebAppUrl, [Parameter(Mandatory=$true)] [string]$OutputCsv, [string]$SiteCollectionUrl, [switch]$NoPrompt ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" Write-Host "" Write-Host "SCRIPT 09 - WORKFLOW INVENTORY REPORT" -ForegroundColor Cyan Write-Host "READ-ONLY. NO CHANGES ARE 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 $OutputCsv -Parent if ([string]::IsNullOrWhiteSpace($outDir)) { throw "Provide full OutputCsv path. Example: C:\Temp\WorkflowInventory.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 ("{0}_{1}_Summary_ByType.csv" -f $baseName,$timestamp) $logPath = Join-Path $outDir ("{0}_{1}_RunLog.txt" -f $baseName,$timestamp) $errorPath = Join-Path $outDir ("{0}_{1}_Errors.csv" -f $baseName,$timestamp) # -------------------------------------------------- # Logging + Errors # -------------------------------------------------- $errors = New-Object System.Collections.Generic.List[object] $log = @() function Log { param($msg) $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg $log += $line Write-Host $line } function Add-Error { param($scope,$msg) $errors.Add([pscustomobject]@{ Timestamp = Get-Date Scope = $scope Message = $msg }) | Out-Null } if (-not $NoPrompt) { Write-Host "This script inventories workflows. No changes are made." -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 (Migration impact) # -------------------------------------------------- function Get-RiskLevel { param($RunningInstances, $WorkflowType) if ($RunningInstances -gt 0) { return "High" } if ($WorkflowType -eq "SiteWorkflow") { return "Medium" } return "Low" } function Get-Score { param($risk) switch ($risk) { "High" {30} "Medium" {60} "Low" {90} default {50} } } function Get-Recommendation { param($risk) switch ($risk) { "High" { "Active workflow. Validate migration strategy or rebuild in Power Automate." } "Medium" { "Review workflow design. Confirm if still required post-migration." } "Low" { "Legacy workflow. Consider retirement or modernization." } } } # -------------------------------------------------- # Main Scan # -------------------------------------------------- $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) { try { Log "Scanning site: $($site.Url)" foreach ($web in $site.AllWebs) { try { # ------------------------- # SITE WORKFLOWS # ------------------------- foreach ($wf in $web.WorkflowAssociations) { $risk = Get-RiskLevel -RunningInstances $wf.RunningInstances -WorkflowType "SiteWorkflow" $results.Add([pscustomobject]@{ SiteCollectionUrl = $site.Url WebUrl = $web.Url ScopeType = "SiteWorkflow" ListTitle = "" WorkflowName = $wf.Name RunningInstances = $wf.RunningInstances Created = $wf.Created Modified = $wf.Modified TaskList = $wf.TaskListTitle HistoryList = $wf.HistoryListTitle RiskLevel = $risk Score = Get-Score $risk Category = "WorkflowInventory" ActionRecommendation = Get-Recommendation $risk }) | Out-Null } # ------------------------- # LIST WORKFLOWS # ------------------------- foreach ($list in $web.Lists) { foreach ($wf in $list.WorkflowAssociations) { $risk = Get-RiskLevel -RunningInstances $wf.RunningInstances -WorkflowType "ListWorkflow" $results.Add([pscustomobject]@{ SiteCollectionUrl = $site.Url WebUrl = $web.Url ScopeType = "ListWorkflow" ListTitle = $list.Title WorkflowName = $wf.Name RunningInstances = $wf.RunningInstances Created = $wf.Created Modified = $wf.Modified TaskList = $wf.TaskListTitle HistoryList = $wf.HistoryListTitle RiskLevel = $risk Score = Get-Score $risk Category = "WorkflowInventory" ActionRecommendation = Get-Recommendation $risk }) | Out-Null } } } catch { Add-Error $web.Url $_.Exception.Message } finally { try { $web.Dispose() } catch {} } } $site.Dispose() } catch { Add-Error $site.Url $_.Exception.Message } } # -------------------------------------------------- # Export Reports # -------------------------------------------------- $results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8 $results | Group-Object ScopeType | ForEach-Object { [pscustomobject]@{ WorkflowType = $_.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 "" 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 READ-ONLY. Scans workflows across: - Lists / Libraries (List workflows) - Site (Site workflows) Used for: - Migration planning (SP 2019 -> SE) - Workflow modernization (Power Automate readiness) - Governance and cleanup .PARAMETER WebAppUrl Target SharePoint Web Application URL .PARAMETER OutputCsv Full path to output CSV