I needed to move document sets in SharePoint Online
- from one library to another in the same site
- where the document sets use a custom content type (not the OOTB document set content type)
- while maintaining the version history of files contained within (move, not copy)
The Nintex Office 365 send document set to repository action only works if your document sets use the OOTB document set content type, so that left me with building a custom solution. Vadim Tabakman’s blog post about moving documents with version history got me rolling. My solution includes moving the document set and uses the modern REST API. In the interest of sharing knowledge, here’s how I did it.
The workflow runs on the document set. First, the grouped general process steps. Note: the workflow is nearly 50 actions, so I’ve attached a PDF of all the actions in the workflow, expanded for detail, rather than pasting images into this post.
Details of the first action set, Get files in the source doc set:
- Set Workflow Status: Getting files in doc set
- A Build Dictionary action sets up the headers for the HTTP Request to follow
- Key=Accept Type=Text Value=application/json;odata=verbose
- Output to dicGetFilesRequestHeaders
- A Call HTTP Web Service action queries the source document set for all files contained within
- Address: {Workflow Context:Current site URL}/_api/web/lists/getByTitle('{Workflow Context:List Name}')/Items('{Current Item:ID}')/Folder/Files
- Request Type: GET
- Request Headers: dicGetFilesRequestHeaders
- Request Content: empty
- Response Content: dicGetFilesResponseContent
- Response Headers: empty
- Response Status Code: txtGetFilesResponseCode
- A Run If checks if txtGetFilesResponseCode is not “OK”
If it’s some other value, the request failed
- Set Workflow Status: Error occurred
- Send myself an email that contains the name of the current item, the Response Status Code, and the Response Content values so I know what happened
- Terminate the workflow
Otherwise, the request will have returned some JSON (see the attachment named HTTP Request Content XML for more detail), now stored in dicGetFilesResponseContent. Note: the example in the attached file is for only one file in a document set; I copied it before I put multiple files in the document set.
Since document sets will often contain more than one file, I need to count the number of files so I can loop through them.
- A Get an Item from a Dictionary action removes the outer shell of the Response Content and stores the “results” portion
- Dictionary: dicGetFilesResponseContent
- Item name or path: d/results
- Output: dicGetFilesArray
- A Count Items in a Dictionary action counts the elements in dicGetFilesArray and stores that value in intSourceFilesCount
- A Loop N Times action uses intSourceFilesCount as its repeat count to process the files
Inside the loop…
- A Get an Item from a Dictionary action gets the file name at the current index (intLoopIndex, which is initialized as zero)
- Dictionary: dicGetFilesResponseContent
- Item name or path: d/results/({Variable:intLoopIndex})/Name
- Output: txtFileName
- I want to ensure the workflow doesn’t fail because someone is editing the file. A Call HTTP Web Service action gets the LockedByUser property of the file.
- Address: {Workflow Context:Current site URL}/_api/web/GetFileByServerRelativePath(decodedurl='{Current Item:Server Relative URL}/{Variable:txtFileName}')/LockedByUser
- Request Type: GET
- Request Headers: dicGetFilesRequestHeaders
- Request Content: empty
- Response Content: dicFilePropertyResponseContent
- Response Headers: empty
- Response Status Code: txtFilePropertyResponseCode (not being used; just there in case I need it)
- A Get an Item from a Dictionary action gets the user name value of the LockedByUser property
- Dictionary: dicFilePropertyResponseContent
- Item name or path: d/Title
- Output: txtLockedBy
- A Run If checks to see if txtLockedBy is not empty
If a name is present, the file is locked for editing.
- Log a message indicating the file name and user to the workflow history
- Set Workflow Status: Cancelled – file locked
- Terminate workflow
Otherwise…
- I also want to ensure the workflow doesn’t fail because someone has a file checked out. A Call HTTP Web Service action gets the CheckedOutByUser property of the file
- Address: {Workflow Context:Current site URL}/_api/web/GetFileByServerRelativePath(decodedurl='{Current Item:Server Relative URL}/{Variable:txtFileName}')/CheckedOutByUser
- Request Type: GET
- Request Headers: dicGetFilesRequestHeaders
- Request Content: empty
- Response Content: dicFilePropertyResponseContent
- Response Headers: empty
- Response Status Code: txtFilePropertyResponseCode (not being used; just there in case I need it)
- A Get an Item from a Dictionary action gets the user name of the CheckedOutByUser property
- Dictionary: dicFilePropertyResponseContent
- Item name or path: d/Title
- Output: txtCheckedOutBy
- A Run If checks to see if txtCheckedOutBy is not empty
If a name is present, the file is checked out.
- Log a message indicating the file name and user to the workflow history
- Set Workflow Status: Cancelled – file checked out
- Terminate workflow
Otherwise…
- An Add Item to Collection action adds txtFileName to colGetFilesNames at the current loop index
- Log the file name to the history list, for reference, if the workflow suspends for any reason
- Increment the loop index
- End of loop; start again
Details of the second action set, Copy doc set to target library:
Technically, a document set can’t be moved, but a copy can be made with the same metadata.
- Set Workflow Status: Copying doc set
- An Office 365 Create List Item or Document Set action creates a new document set in the target library. Note: the steps in this action set assume that the target library uses the same document set content type and metadata as the source library)
- Destination site URL: {Workflow Context:Current site URL}
- Connection: a List & Library connection (Nintex help page for connections)
- List or Document Library: My Target Library
- Folder: empty (unless applicable)
- Fields (select Builder radio button)
- Field: Content Type
- Type: ContentTypeId
- Value: Enter the content type ID for the document set content type in the target library. The trick to getting this value is to navigate to the Library Settings page for the target library. Once you’re there, under Content Types, click the name of the document set content type. On the settings page for the list content type, put your cursor in the address bar and drag it all the way to the right end. Highlight the long string that follows &ctype= and copy it. Paste that into the Nintex action.
- Field: Name
- Type: Text
- Value: {Current Item:Name}
- Created item ID: txtNewDocSetId
- Created item URL: empty
- Field: Content Type
- An Update List Item action sets the new document set’s metadata to match that of the source document set
- Target List: My Target Library
- Where: ID equals workflow variable txtNewDocSetId
- List Item Properties: whatever suits you
- A Set Workflow Variable action sets txtNewDocSetUrl to the server relative URL of the new document set. Set up an Advanced Lookup like this:
Details of the third action set, Move files to target doc set:
- Set Workflow Status: Moving files
To move the files, I first need to get the security digest so SharePoint doesn’t think something malicious is going on
- A Build Dictionary action sets up the headers for the HTTP Request to follow
- Key=Accept Type=Text Value=application/json;odata=verbose
- Key=Content-Length Type=Text Value=0
- Output to dicGetApicontextinfoRequestHeaders
- A Call HTTP Web Service action gets the API context info from the site
- Address: {Workflow Context:Current site URL}_api/contextinfo
- Request Type: POST
- Request Headers: dicGetApicontextinfoRequestHeaders
- Request Content: empty
- Response Content: dicGetApicontextinfoResponseContent
- Response Headers: empty
- Response Status Code: dicGetApicontextinfoResponseCode (not being used; just there in case I need it)
- A Get an Item from a Dictionary action gets the security digest value from the HTTP request
- Dictionary: dicGetApicontextinfoResponseContent
- Item name or path: d/GetContextWebInformation/FormDigestValue
- Output: txtSecurityDigest
- A Set Workflow Variable action resets intLoopIndex to zero
- A Build Dictionary action sets up the headers for the HTTP Request to that will move files inside the coming loop
- Key=Accept Type=Text Value=application/json;odata=verbose
- Key= X-RequestDigest Type=Text Value= {Variable:txtSecurityDigest}
- Output to dicMoveFilesRequestHeaders
- A Loop N Times action uses intSourceFilesCount as its repeat count to process the files found earlier
Inside the loop…
- A Get an Item from a Dictionary action gets the file name at the current index
- Dictionary: dicGetFilesResponseContent
- Item name or path: d/results/({Variable:intLoopIndex})/Name
- Output: txtFileName
- Log the name of the file to the workflow history
- A Call HTTP Web Service action moves the file to the target document set
- Address: {Workflow Context:Current site URL}/_api/web/GetFileByServerRelativeUrl('{Current Item:Server Relative URL}/{Variable:txtFileName}')/MoveTo(newurl='/{Variable:txtNewDocSetUrl}/{Variable:txtFileName}',flags=1)
- Request Type: POST
- Request Headers: dicMoveFilesRequestHeaders
- Request Content: empty
- Response Content: dicMoveFilesResponseContent
- Response Headers: empty
- Response Status Code: dicMoveFilesResponseCode
- A Run If checks if dicMoveFilesResponseCode is not “OK”
If it’s some other value, the request failed
- Set Workflow Status: Error occurred
- Send myself an email that contains the name of the current item, the Response Status Code, and the Response Content values so I know what happened and can check the source and target document sets
- Terminate the workflow
Otherwise…
- Increment the loop index
- End of loop; start again
When all the files have been moved…
- An Office 365 delete items action deletes the now empty source document set. I chose this action because it moves the deleted document set to the Recycle Bin. I like having the opportunity to restore it if someone thinks my workflow didn’t move contents of a document set. Nintex’s Delete Item action removes an item permanently.
- Destination site URL: {Workflow Context:Current site URL}
- Connection: your connection
- List name: {Workflow Context:List Name}
- Items to delete (select Query builder)
- Check either of the two checkboxes as you see fit
- Filters:
- Delete items only when the following is true
- Delete the items when column: ID
- Is equal to: {Current Item:ID}
- All matched items deleted: bolAllDeleted (not being used; just there in case)
That’s it. Hope you find this helpful.