Recently was working on a small project that ended up requiring me to start SharePoint 2013 workflows with PowerShell scripts. This turned out to be a necessity because of a few things. I was finishing off some development recently on a smaller project that required a significant amount of JavaScript injection code. To be terribly honest, had I known at the time it would have taken as much as it did, I would have rewritten it as a full Add-In, but that’s a story for another day. To make a long story short because of the way the form had been written by another developer we were saving attachments to the list manually from the form (not allowing SharePoint to do it for us). Because we had workflows running on item added, we were having save conflicts because the items could not be added until after the form data was initially inserted into the list. Basically getting a kind of race condition between the workflows and the list item update.
My first thought was to simply disable the workflows from starting automatically and instead initiating them from the form using JSOM. This however wasn’t going to work because while the code I wrote worked perfectly fine (I’ll blog on that later), I forgot the solution also had a mobile app feeding into it. The mobile app wasn’t able to use the same model as I could and SharePoint 2013 workflows is one of the few places REST needs to be vastly improved. In theory it can be done simply by using the ExecuteProcess endpoint and enter the necessary information in the body of the transmission. However, I had never done this and didn’t have a lot of time left to figure it out (I’ll post something here if I get around to working it out). So that left a PowerShell Script and scheduled tasks.
Start SharePoint 2013 Workflows with PowerShell
So now let’s get to why you’re here. How to initiate SharePoint 2013 workflows from PowerShell. The gist of my solution was this:
- Build a search query for every item created in the past 10 minutes (scheduled task will run every 5 min so there will be plenty of overlap).
- Loop through each item returned and check to see if the workflows have run based on the workflow status field created.
- If no workflow for that item, initiate the workflow.
Search Query
The query is pretty straight forward:
############################################################################################# #Function Name: BuildItemsCreatedMinutesInPastQuery # #Parameters: $minutesInPast - Number of minutes to subtract from current time # # # #Return Value: SPQuery object # #Purpose: Creates an SPQuery object that will check against the Created Date subracting the # # number of minutes passed in to the function # ############################################################################################# function BuildItemsCreatedMinutesInPastQuery([Int]$minutesInPast) { $setMinInPast = $minutesInPast * -1; $currentTime = (Get-Date).AddMinutes($setMinInPast); $formatTime = $currentTime.ToString('s'); $spQuery = New-Object Microsoft.SharePoint.SPQuery; $spQuery.Query = "<Where> <Geq> <FieldRef Name='Created' /> <Value IncludeTimeValue='TRUE' Type='DateTime'>$formatTime</Value> </Geq> </Where>" return $spQuery; }
The script only takes in the number of minutes you want to look into the past. You get the current date and time, back it up the number of minutes you want, convert it to ISO 8601 format and build it into your query to find any items later than that time. If you aren’t up on CAML query, the code basically translates to “Get all items, where the field ‘Created’ (of type DateTime) is greater than or equal (<geq>) to the date I want in ISO 8601 format.
Initiate the Workflow
When initiating the workflow there are a couple of steps you need to do first:
- Connect to the SharePoint Workflow Services and create a Workflow Services Manager object
- Connect to the subscription service
- Get the workflow subscription you are looking for (searching an enumerated list using the workflow name)
- You are checking all the workflow associations (definitions) within the site and grabbing the one you are looking for
- Build an empty dictionary. This is used to hold the parameters of the workflow if there was an initiate form gathering information.
- Creating an object of the workflow instance service and initiating the workflow against the item in question.
#build the WF Manager and Subscription Services objects to manipulate the WFs in the site $spWFMgr = New-Object Microsoft.SharePoint.WorkflowServices.WorkflowServicesManager($spWeb); $spWFSubSvc = $spWFMgr.GetWorkflowSubscriptionService(); #try to find the WF to initiate $spWorkflow = $spWFSubSvc.EnumerateSubscriptions() | ?{$_.Name -eq $wfName} if($spWorkflow) { #connect to the Instance service, build empty parameter dictionary and initiate the WF. $spWFInstanceSvc = $spWFMgr.GetWorkflowInstanceService(); $wfParams = New-Object 'system.collections.generic.dictionary[string,object]' $wfGUID = $spWFInstanceSvc.StartWorkflowOnListItem($spWorkflow, $spListItem.ID, $wfParams); } else { $returnMessage = ("Workflow: {0} not found" -f $wfName); }
That’s it. These few lines, find the workflow you need, create an instance of it against the item you wish and then start the workflow.
You next step is to build it all together and then toss it into a scheduled task to run every 5 minutes or however often you need it to run.
Thanks for reading!
Comments