Locate Workflows referencing missing Lists or Subsites

I was asked recently if it was possible to locate workflows in a SharePoint environment that were referencing lists/sites that no longer existed. This script is the result of a bit of brainstorming and tinkering with a colleague of mine.

Running this script against a Site Collection in SharePoint will provide you with a collection of Guids that the script was unable to match up to a site or list. Additionally, the script will provide you with the URL to the workflow referencing the Guid.

To use the script, load it into PowerShell ISE or a PS1 file and execute it. Once the script is loaded it can be run by typing the following:

Scan-Site -url "http://contoso.com" -workflowType "Site" | FT  

Note: The workflowType argument can be set to List or Site depending on the type of workflow you are looking for.

Add-PSSnapin microsoft.sharepoint.powershell

function Scan-Site  
{
    param([string]$url, [string]$workflowType)

    function Get-WFType($XmlDocument)
    {
        return $XmlDocument.SelectSingleNode('/WorkflowConfig/Template/@Category')
    }

    function New-XmlDocument()
    {
        return New-Object System.Xml.XmlDocument
    }

    function New-HashSet($type)
    {
        return New-Object "system.collections.generic.HashSet[$($type)]"
    }

    function New-List($type)
    {
        return New-Object "system.collections.generic.List[$($type)]"
    }

    function New-ReportItem()
    {
        param([string]$Guid, [string]$WorkflowUrl)

        $obj = New-Object PSObject

        Add-Member -InputObject $obj -MemberType NoteProperty -Name Guid -Value $Guid

        Add-Member -InputObject $obj -MemberType NoteProperty -Name WorkflowUrl -Value $WorkflowUrl

        return $obj

    }

    function Get-GUIDs($string)
    {
        return [Regex]::Matches($string, "(\{)(\w\w\w\w\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w\w\w\w\w\w\w\w\w)(\})")
    }

    function Populate-DataSet($xmlDocument)
    {
        foreach($id in Get-Guids($XmlDocument.InnerXml))
        {
            $item = $set.Add(@($id.Value, $folder.Url))
        }
    }

    function Get-WFGuids()
    {
        $set = New-List("string[]")

        foreach($folder in $($(Get-SPSite $url).RootWeb.GetFolder("Workflows").SubFolders))
        {
            if($folder.Files.Count -eq 0)
            {
                continue
            }

            $xomlDoc = New-XmlDocument
            $xomlDoc.Load($($folder.Files | where {$_.Url -match '(.*\.xoml)(?!\.)'}).OpenBinaryStream())

            $xmlDoc = New-XmlDocument
            $xmlDoc.Load($($folder.Files | where {$_.Url -match '.*\.wfconfig\.xml'}).OpenBinaryStream())

            if($(Get-WFType($XmlDoc)).Value -ne $($workflowType))
            {
                continue
            }

            Populate-DataSet($xomlDoc)

            Populate-DataSet($xmlDoc)
        }

        return $set
    }

    $set = Get-WFGuids

    $result = New-HashSet("PSObject")

    function Validate-Lists($item)
    {
        try
        {
            if($(Get-SPSite $url).RootWeb.Lists.GetList([Guid]::Parse($item[0]), $false) -ne $null)
            {
                continue
            }
        }
        catch [Exception]
        {
            if($_.Exception.Message -Match "List does not exist")
            {
                $tmp = $result.Add($(New-ReportItem -Guid $item[0] -WorkflowUrl $item[1]))
            }
            else
            {
                $_
            }
        }
    }

    function Validate-Sites($item)
    {
        try
        {
            $site = new-object Microsoft.SharePoint.SPSite([Guid]::Parse($item[0]))

            if($($site) -ne $null)
            {
                continue
            }
        }
        catch [Exception]
        {
            if($_.Exception.Message -Match "(The site with the id )(\w\w\w\w\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w)(\-)(\w\w\w\w\w\w\w\w\w\w\w\w)( could not be found\.)")
            {
                $tmp = $result.Add($(New-ReportItem -Guid $item[0] -WorkflowUrl $item[1]))
            }
            else
            {
                $_
            }
        }
    }

    function Validate-Webs($item)
    {

        if($($(new-object Microsoft.SharePoint.SPSite($url)).AllWebs[[Guid]::Parse($item[0])]) -ne $null)
        {
            continue
        }
        else
        {
            $tmp = $result.Add($(New-ReportItem -Guid $item[0] -WorkflowUrl $item[1]))
        }
    }

    foreach($item in $set)
    {
        Validate-Lists($item)
        Validate-Sites($item)
        Validate-Webs($item)
    }

    $dedupe = @{}

    foreach($item in $result)
    {
        $hash = $($item.Guid + $item.WorkflowUrl)

        if($dedupe.Contains($hash))
        {
        }
        else
        {
            $dedupe.Add($hash, $item)
        }        
    }

    return $dedupe.Values
}