Cancelling Workflows in Sharepoint Using Powershell

Originally this script was built to audit a library for problematic workflows. I re-purposed it to also decare records which weren’t previously declared. Modify it to serve your purpose.

Add-PSSnapin Microsoft.SharePoint.PowerShell

function Write-Success {
    process { Write-Host $_ -ForegroundColor Green }
}

$config = (Get-Content config.json) -join "`n" | ConvertFrom-Json
$SPWeb = Get-SPWeb $config.SPSite;

$listName = "MyLibraryWithProblem";

$hitlist = @();
$count = $SPWeb.Lists[$listName].items.count;
$i=1;
foreach($item in $SPWeb.Lists[$listName].items) { # | select -First 200) {
    $target = @{};
    $target.ID = $item.ID;
    $target.IsRecord = [Microsoft.Office.RecordsManagement.RecordsRepository.Records]::IsRecord($item);
    $target.Title = $item["Title"];
    $target.Created = $item["Created"].ToString("s");
# $item;
    if($item.Workflows.count -gt 0) {
        $workflows = @();
        foreach($workflow in $item.Workflows) {
            $wfinstance = @{};
            $wfinstance.InstanceId = $workflow.InstanceId;
            $wfinstance.Modified = $workflow.Modified.ToString("s");
            $wfinstance.StatusText = $workflow.StatusText;
            $wfinstance.IsCompleted = $workflow.IsCompleted;
            $wfinstance.StatusUrl = $workflow.StatusUrl;
            $workflows += $wfinstance;
        }
        $target.Workflows = $workflows;
    }
#determine if we should ignore this entry
    $ignore = $true;
    try {
        if($target.Workflows.count -gt 0) {
            foreach($wf in $target.Workflows) {
                if($wf.IsCompleted -ne $true) {
                    $ignore = $false;
                }
                if($wf.StatusText -ne "Approved") {
#$item.Workflows;
                    $ignore = $false;
                }
                #not sure why but my system shows a typo for cancelled state
                if($wf.StatusText -eq "Canceled") {
                    $ignore = $true;
                }
            }
        }
    } catch {};
    if(!$target.IsRecord) {
        $ignore = $false;
    }
    if($item["Created"] -gt (get-date).AddDays(-14)) {
        $ignore = $true;
    }
    if(!$ignore) {
        $hitlist += $target;
    }
    $i++;
    Write-Progress -Activity "Collecting data" -status $target.Title -percentComplete ($i / $count*100);
}

#$hitlist | ConvertTo-Json -Depth 10

#from this list let's either:
# decare as record when it's not 
# and cancel a workflow when it's still running
# noting that we are only interested in the items created past 14 days ago
$count = $hitlist.count;
$i=0;
foreach($target in $hitlist) {
    $i++;
    Write-Progress -Activity "Working through list" -status $target.Title -percentComplete ($i / $count*100);
    if(!$target.IsRecord) {
        $item = $SPWeb.Lists[$listName].items.GetItemById($target.ID);
        Write-Output "Declaring as record for $($target.Title)" | Write-Success;
        if($item.File.CheckOutStatus -ne "None") {
            Write-Output "  Checking in file first because it is checked out" | Write-Success;
            $item.File.CheckIn("Automatic CheckIn. (Administrator)");
        }
        [Microsoft.Office.RecordsManagement.RecordsRepository.Records]::DeclareItemAsRecord($item);
    }
    foreach($wf in $target.Workflows) {
        if(!$wf.IsCompleted) {
            $item = $SPWeb.Lists[$listName].items.GetItemById($target.ID);
            foreach($wfi in $item.Workflows) {
                if($wfi.InstanceId -eq $wf.InstanceId) {
                    Write-Output "Cancelling workflow for $($target.Title)" | Write-Success;
                    [Microsoft.SharePoint.Workflow.SPWorkflowManager]::CancelWorkflow($wfi);
                }
            }
        }
    }
}