Are SharePoint Designer Workflows Using Custom Features or Solutions (iLoveSharePoint)

Needed to audit a farm to see if a CodePlex solution was being used in SharePoint Designer workflows.  In my case, I needed to see where the iLove SharePoint  solution was being used. The script below is only targeted at one web and is looking for word “ILoveSharePoint” in the XML.

 


if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
	Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
	{

		$resultsarray =@()
		#output file name
		$fileName = "C:\ilsp-" + $(Get-Date -Format "yyyyMMddHHmmss") + ".csv"
		#name of the feature we are looking for
		$wFeatureName = "ILoveSharePoint"

		Function GetFiles($folder)
 { 
			foreach($file in $folder.Files)
			{
				if($file.Name.Split('.')[-1] -eq "xoml")
				{
					$web2 = Get-SPWeb $file.Web.Url
					$wFile = $web2.GetFileOrFolderObject($web2.URL +"/"+ $file.URL)

					if ($wFile.Exists -eq "True")
					{
						$wXml = (New-Object System.Text.UTF8Encoding).GetString($wFile.OpenBinary());
						$nsDetail = $wXml.OuterXml.ToLower()
						
						$wFeatureName = $wFeatureName.ToLower()
							
						if($nsDetail -Like "*$wFeatureName*")
						{
							$outFolder = $folder -replace "Workflows/",""

							$outObject = new-object PSObject
							$outObject | add-member -membertype NoteProperty -name "URL" -Value $web2.Url
							$outObject | add-member -membertype NoteProperty -name "Workflow" -Value $outFolder
							$outObject | add-member -membertype NoteProperty -name "Created By" -Value $wFile.Author
							$outObject | add-member -membertype NoteProperty -name "Created Date" -Value $wFile.TimeCreated
							$outObject | add-member -membertype NoteProperty -name "Modified By" -Value $wFile.ModifiedBy
							$outObject | add-member -membertype NoteProperty -name "Modified Date" -Value $wFile.TimeLastModified

							$global:resultsarray += $outObject
						}
					} 
				} 
			}

			# Use recursion to loop through all subfolders.
			foreach ($subFolder in $folder.SubFolders)
			{
				GetFiles($Subfolder)
			}
		}

		$WebApplications = Get-SPWebApplication

		foreach($webApp in $WebApplications)
		{
			foreach($site in $webApp.Sites)
			{
				if ((Get-SPSite $site.url -ErrorAction SilentlyContinue) -ne $null) 
				{
					foreach($web in $site.AllWebs)
					{
						if ((Get-SPWeb $web.url -ErrorAction SilentlyContinue) -ne $null) 
						{
							$list1 = $web.Lists.TryGetList("Workflows")
							if($list1 -ne $null)
							{
								GetFiles($list1.RootFolder)
							}
						}
					}
				}
			}
		}

		#output file
		$resultsarray | Export-csv $fileName -notypeinformation

	}
)

The workflow could not update the item, possibly because one or more columns for the item require a different type of information.

SharePoint Designer workflow error:
The workflow could not update the item, possibly because one or more columns for the item require a different type of information.

If you look at the workflow history nothing of value is there to clue you into what field is broken.

To track down what field is broken, I added a Pause action to my Step. Then I added a Log message before each action. Doing this helped track down the broken field.

The workflow could not update the item

Once you get the workflow working again, remember to clean up the added Log actions.

In my case, the workflow was trying to update a Person / Group field with an employee that no longer worked at the company.

SharePoint Workflow Not Starting

For one reason or another, SharePoint Designer Workflows are not always starting when an item is added to a library.  This happens with document libraries and InfoPath form libraries.  I’ve seen this happen with SharePoint 2007 and SharePoint 2010.

What I’ve created is a way to identify, monitor, and start workflows.

Create two lists:

Workflow Monitor

Fields:

Site URL, List Name, Workflow Name

Workflow Monitor Logging

Fields:

Site URL, List Name, Workflow Name, List Item ID

All of the field types are the default single line of text.

Workflow Monitor will be populated with the site url, list name, and workflow name of the workflow you want to monitor.

Place the script in a folder on one of your SharePoint server. Then setup a scheduled task to run the script as needed.


<#
 loop though workflow monitor list
 get workflow item
 query associated list by created date between yesterday and today AND workflow field is empty
 if item is returned, start workflow on item.
 log item that was not started
 if there are any errors send email to DL SharePoint
#>

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

$cSite = Get-SPWeb "http://sharepointed.com"
$cList = $cSite.Lists["Workflow Monitor"]
$cListLog = $cSite.Lists["Workflow Monitor Logging"]
$errorCount = 0
$errorString = ""

foreach($config in $cList.Items)
{
	try
	{
		$fSite = Get-SPWeb $config["Site URL"].ToString()
		$wfManager = $fSite.Site.WorkFlowManager
		$fList = $fSite.Lists[$config["List Name"].ToString()]
		$fWFfield = $fList.Fields[$config["Workflow Name"].ToString()].InternalName

		#Get the list workflow
		$wfAssoc = $fList.WorkflowAssociations.GetAssociationByName($config["Workflow Name"].ToString(),"en-US")
		$wfData = $wfAssoc.AssociationData

		$sQuery = New-Object Microsoft.SharePoint.SPQuery 

		#Get all item that were created in the past day and a workflow has not ran.
		$caml = '<Where><And><IsNull><FieldRef Name="' + $fWFfield + '" /></IsNull><Geq><FieldRef Name="Created" /><Value Type="DateTime"><Today OffsetDays="-1" /></Value></Geq></And></Where>'
		$sQuery.Query = $caml
		$fItems = $fList.GetItems($sQuery)

		foreach($lItem in $fItems)
		{
			#Start workflow
			$wf = $wfManager.StartWorkFlow($lItem,$wfAssoc,$wfData,$true)

			#Create Log entry
			$newLogItem = $cListLog.Items.Add()
			$newLogItem["Site URL"] = $config["Site URL"]
			$newLogItem["List Name"] = $config["List Name"]
			$newLogItem["List Item ID"] = $lItem["ID"]
			$newLogItem["Workflow Name"] = $config["Workflow Name"]
			$newLogItem.Update()
		} 
	}
	Catch
	{
		#string.format
		$errorMessage = $_.Exception.Message.ToString()
		$errorString += $config["ID"].ToString() + " " + $errorMessage + " --- "
		$errorCount++
	}
}

#If there are any errors send email 
if($errorCount -gt 0)
{
	$errorString = $errorString.TrimEnd(" --- ")
	
	$emailSubject = "Workflow Montior Script Error"
	$emailBody = "Error running the Workflow Monitor script. <br><br> <b>Error: </b>"
	$emailBody += "$errorString <br><br>"
	$emailBody += "<a href=$cList.URL.ToString()>Workflow Monitor List </a>" 
	$emailsmtpServer = "mail.doman.net"
	$emailTo = "you@sharepointed.com"
	$emailFrom = "alerts@sharepointed.com"

	Send-MailMessage -From $emailFrom -To $emailTo -Subject $emailSubject -BodyAsHtml $emailBody -SmtpServer $emailsmtpServer
}

This script will inventory your entire farm and output workflows that have failed to start int the past 59 days. *You can adjust the 59 day setting, but my farm is setup to truncate workflow history every 60 days.*


$contentWebAppServices = (Get-SPFarm).services |
? {$_.typename -eq "Microsoft SharePoint Foundation Web Application"}

$stringBuilder = New-Object System.Text.StringBuilder
$list = New-Object System.Collections.Generic.List[System.String]
$counter = 0

foreach($webApp in $contentWebAppServices.WebApplications)
{
	$webApp = Get-SPWebApplication $webApp.Url

	if($webApp -ne $null)
	{
		foreach($siteColl in $webApp.Sites)
		{
			if($siteColl -ne $null)
			{
				foreach($subWeb in $siteColl.AllWebs)
				{
					if($subWeb -ne $null)
					{
						foreach($list in $subWeb.Lists)
						{
							foreach($wf in $list.WorkflowAssociations)
							{
								if ($wf.Name -notlike "*Previous Version*")
								{
									$subWeb.Site.WorkflowManager
									$wfManager = $subWeb.Site.WorkFlowManager
									$fWFfield = $list.Fields[$wf.Name.ToString()].InternalName

									#Get the list workflow
									$wfAssoc = $list.WorkflowAssociations.GetAssociationByName($wf.Name,"en-US")
									$wfData = $wfAssoc.AssociationData

									if($wfAssoc.AutoStartCreate -eq $true)
									{
										$counter++
										
										$sQuery = New-Object Microsoft.SharePoint.SPQuery 

										#Get all item that were created in the past day and a workflow has not ran.
										$caml = '<Where><And><IsNull><FieldRef Name="' + $fWFfield + '" /></IsNull><Geq><FieldRef Name="Created" /><Value Type="DateTime"><Today OffsetDays="-59" /></Value></Geq></And></Where>'
										$sQuery.Query = $caml
										$fItems = $list.GetItems($sQuery) 
										
										$null = $stringBuilder.Append($subWeb.URL)
										$null = $stringBuilder.Append(",")
										$null = $stringBuilder.Append($list.Title)
										$null = $stringBuilder.Append(",")
										$null = $stringBuilder.Append($wf.Name.ToString())
										$null = $stringBuilder.Append("`r`n")
									
									}
								}
							}

						}

						$subWeb.Dispose()
					}
				}
				$siteColl.Dispose()
			}
		}
	} 
}

if($counter -gt 0)
{
	out-file -filepath C:\WorkflowOutput.csv -inputobject $stringBuilder.ToString()
}

Why Wait for Document to be Unlocked by Document Editor Sucks

If you are using the SharePoint Designer action wait for document to be unlocked by document editor, you may be aware of it’s shortcomings.  Like what?  Well, if you submit a form or document to the library, this action fires.  If the form/document happens to be a little slow getting to the library, the workflow will pause for another 5 minutes!  ¡No bueno! 

What I did to get around this was, write a little inline PowerShell script as my first action in the Workflow.  The script loops every 5 seconds to see if the form/document has been unlocked.  5 seconds vs 5 minutes, there is a large difference.

How? If you don’t already have the iLove SharePoint Workflow Actions installed, do it! The actions can be downloaded here: http://ilovesharepoint.codeplex.com/ .  (read the documentation)

Once installed, modify your Worflow or create a new one. I replaced the lame Wait for Document to be Unlocked by Document Editor with a Execute PowerShell action. The PowerShell code is below.


if(-not(Get-PSSnapin | where { $_.Name -eq "Microsoft.SharePoint.PowerShell"}))
{
 Add-PSSnapin Microsoft.SharePoint.PowerShell;
}

$site = Get-SPweb "http://sharepointed.com/sites/taco/"
$mList=$site.Lists["The Library"]
$item = $mList.GetItemById([<strong><span style="text-decoration: underline;">%Current Item:ID%</span></strong>])

$Lock = $item.File.LockType

while($Lock -ne "None")
 {
 start-sleep -s 5
 $Lock = $item.File.LockType
 }

Please notice the line $item = $mList.GetItemById([%Current Item:ID%]), Current Item: ID is the ID of the item that started the workflow.  This is key to Workflow action working!

*update*

It’s also a good idea to modify your default view and other views to only show workflow items that have a completed status.  How?  Edit the view, scroll down to the Filter section, select the column that has your workflows name in it.  The next drop down should be ‘is equal to‘ and the text box, input the number five 5.  5 represents the Completed workflow status.

SharePoint Completed Workflow Status

SharePoint Completed Workflow Status

Code blocks are not allowed in this file

I was trying to enable code in an aspx page in SharePoint Designer, with no luck!

(warning, search google for reason not to do this.)

Appears my problem was related to not including the SubFolders option in the web.config file update.

<script><!–mce:0–></script>
<form id="form1"> Current server time is <% =GetTime()%>. </form>

This is the contents of my aspx page in SharePoint Designer (2010).

What web.config file did I update?

C:\inetpub\wwwroot\wss\VirtualDirectories\80

Do I need to run an IISreset?

Nope, I’ve tested this and there is no need for an IISreset.