Wednesday, 18 February 2015

Programming eDiscovery in SharePoint Server 2013

Recently I needed to get a grip on how to work with the eDiscovery tools in SharePoint 2013 from the server-side object model. There's not much information out there on how to do this (and some of the information out there is plain wrong), so I built a proof-of-concept console app to work through the key features, including:

  • Programmatically retrieving an eDiscovery case.
  • Creating a new source.
  • Creating a custodian.
  • Creating a new source.
  • Creating a new eDiscovery set.
  • Using queries and exports.
  • Applying in-place holds to eDiscovery sets.

I'll keep the explanation to a minimum, as I'm hoping the code largely speaks for itself. I'll assume you know the basic concepts of eDiscovery in SharePoint 2013, including how to work with cases, sources, sets, queries, and exports through the UI. To use the code, you'll need assembly references to Microsoft.SharePoint.dll and Microsoft.Office.Policy.dll. All the eDiscovery classes you need are in the Microsoft.Office.Server.Discovery namespace.

Our first task is to retrieve a case. An eDiscovery case is an individual site (SPWeb) within your eDiscovery Center site collection. Individual eDiscovery cases are represented by the Case class. The Case class provides the entry point for all eDiscovery operations in code. To retrieve a case, you need to call the Case constructor and pass the corresponding SPWeb instance as a parameter:

using (SPSite site = new SPSite("http://sp2013/sites/ediscovery/"))
using (SPWeb webCase = site.OpenWeb("case1"))
{
Case myCase = new Case(webCase);

Next, let's use the Case instance to create a new custodian. Essentially all this does is create a new list item in the Custodians list in the case web. Later, you'll see how we can assign custodians to sources to indicate who's responsible for the source:

//Create a custodian
Custodian newCustodian = myCase.CreateCustodian();
newCustodian.Name = "Legal Eagle";
newCustodian.LoginName = "JASON\\bob"; // i.e. domain username
newCustodian.Update();

Next let's create a new source. Essentially, SharePoint 2013 eDiscovery sources are either SharePoint webs or Exchange mailboxes. In both cases, the source is represented by the Source class. To create a new source from a SharePoint web, you call the Case.CreateLocation method. To create a new source from an Exchange mailbox, you call the Case.CreateMailbox method. I'm going to focus on creating a source from a SharePoint web. Once you've created the new Source instance, if you want it to work properly, you need to set three key properties:
  • Set the Source.WebId property to the ID of the SPWeb for which you want to define a source.
  • Set the Source.SiteId property to the ID of the SPSite containing the web for which you want to define a source.
  • Set the Source.FederationId property to the ID of the search result source (e.g. "Local SharePoint Results") from which you want to retrieve content.
Getting the ID of webs and sites is straightforward. Getting the ID of the search result source is a little more complex (credit). The code looks like this:


Guid targetSiteId;
Guid targetWebId;
Guid resultSourceId;

// Get the site ID and the web ID for the source
using(SPSite targetSite = new SPSite("http://team.jason.net/"))
{
targetSiteId = targetSite.ID;
var targetWeb = targetSite.RootWeb;
targetWebId = targetWeb.ID;
}

// Get a result source ID from the search service application
SearchQueryAndSiteSettingsServiceProxy searchSettingsProxy =
SPFarm.Local.ServiceProxies
    .GetValue<SearchQueryAndSiteSettingsServiceProxy>();
SearchServiceApplicationProxy searchProxy =
searchSettingsProxy.ApplicationProxies
    .GetValue<SearchServiceApplicationProxy>("Search Service Application");
    SearchObjectOwner serviceApplicationOwner = new      
    SearchObjectOwner(SearchObjectLevel.Ssa);
SourceRecord serviceApplicationResultSource = searchProxy.GetResultSourceByName("Local SharePoint Results", serviceApplicationOwner);
resultSourceID = serviceApplicationResultSource.Id;

// Create the new eDiscovery source
Microsoft.Office.Server.Discovery.Source newSource = myCase.CreateLocation();
newSource.Name = "Team Site";
newSource.DisplayId = "http://team.jason.net";
newSource.FederationId = resultSourceID;
newSource.SiteId = targetSiteId;
newSource.WebId = targetWebId;
newSource.AddCustodian(newCustodian);
newSource.Update();

Now that we've created a source, we can use it in eDiscovery sets or queries. In the object model, eDiscovery sets are represented by the SourceGroup class. Let's create a new one and add our source to it:

// Create a new eDiscovery Set
SourceGroup newSet = myCase.CreateSourceGroup();
newSet.Name = "Team Site Leaflets";
newSet.AddSource(newSource);
newSet.DateStartFilter = new DateTime(2015, 1, 1);
newSet.Query = "Leaflet";

newSet.Update();

If you want to apply an in-place hold to the set, you simply set the SourceGroup.Preserve property to true and then call the Update method:

// Put the set on hold
newSet.Preserve = true;

newSet.Update();

Note that this requests an in-place hold. The hold won't actually take effect until the eDiscovery In-Place Hold Processing timer job runs.

Next, let's look at how to create a query. In the object model, queries are represented by the SavedSearch class:

// Create a new query based on the discovery set we created earlier
SavedSearch newQuery = myCase.CreateSavedSearch();
newQuery.Name = "Coded Query";
newQuery.SourceManagement = SourceManagementType.SourceGroup;
newQuery.SourceGroupIds.Add(1) // SPListItem ID of the discovery set
newQuery.Deduplication = true;
newQuery.SPFilters = "Created<=2/15/2015";
newQuery.Update();

When you create a query through the UI, you can choose whether to include eDiscovery sets (each consisting of one or more sources with filter criteria) or sources (no filter criteria). In the object model, you specify query scope by setting the SavedSearch.SourceManagement property to one of the following SourceManagementType enumeration values:

  • SourceManagementType.AllContent. This corresponds to the All case content option in the UI. The query contains all the sources defined in the case, with any eDiscovery set filters applied.
  • SourceManagementType.SourceGroup. This corresponds to the Select eDiscovery sets option in the UI. The query contains the sources defined in the discovery sets you select, with any discovery set filters applied.
  • SourceManagementType.Source. This corresponds to the Select sources option in the UI. The query contains the sources you select, and no discovery set filters are applied.
Once you've configured this property, you can either add sources or discovery sets to the query by adding integer IDs to the SourceIds or SourceGroupIds collections respectively. In this example, I've specified the SourceManagementType.SourceGroup option and then added the integer ID of my discovery set to the SourceGroupIds collection.

The SavedSearch class also allows you to specify string filters and refiners for the query. You can use the SPFilters and SPRefinements properties to specify filters and refiners for SharePoint sources, and you can use the EXFilters and EXRefinements properties to specify filters and refiners for Exchange mailboxes. A word of warning, however - the syntax required for the refinement properties is not user friendly. For example, if you create a query with refiners that match a file extension of "txt" and an author of "Administrator", the SPRefinements property ends up looking like this:

[{"n":"Author","t":["Administrator"],"o":"and","k":false,"m":null},"n":"FileExtension","t":["\"??747874\""],"o":"OR","k":false,"m":{"\"??747874\"":"txt"}}]

So it's probably fair to say that our ability to create query refiners in code is limited.

Finally, let's take a look at how to create an export in code. When you create an export, you're essentially just adding an item to the Exports list in the case web. However, the Export class does expose a ResultLink property that enables you to get the exported and packaged discovery content.

Export myExport = myCase.CreateExport(true, true, true);
myExport.Name = "Coded Export";
myExport.AddSearch(newQuery);
Console.WriteLine(String.Format("Download link: {0}", myExport.ResultLink));
}

That's it - I think that pretty much covers all the key bits of using SharePoint eDiscovery in code. Hope it helps.

Sunday, 4 January 2015

Custom Workflow Activity for Setting Managed Metadata Field Values

In this post I'll show you how to build a custom workflow activity in Visual Studio that can update managed metadata field values in a SharePoint 2013 list or library. This is the final part of a three-part series on working with managed metadata fields in workflows:
  • Getting and Setting Managed Metadata Fields in SharePoint 2013 Workflows. In this post, I introduce the scenario, explain why you can't use built-in list actions to work with managed metadata fields, and provide a conceptual overview of the custom workflow activities approach.
  • Custom Workflow Activity for Getting Managed Metadata Field Values. In this post, I'll walk you through how to build a custom workflow activity in Visual Studio that gets managed metadata field values from a list item.
  • Custom Workflow Activity for Setting Managed Metadata Field Values (this post). In this post, I'll walk you through how to build a workflow activity that sets managed metadata field values on a list item.
I've said it before, but it's worth repeating - you can use these custom workflow activities in any SharePoint Designer list workflows, including on Office 365 sites - custom workflow activities in SharePoint 2013 are entirely declarative, so deploying to SharePoint Online is not a problem.

This series of posts tackles the scenario where you want to extract a managed metadata field value from an item in one list (previous post), and then apply that value to a managed metadata field in another list (this post). The main constraint is that the managed metadata fields in the source list and the destination list must both use the same term set. 

Arguments and Variables

If you've been following the series of posts you'll be familiar with the scenario and the concepts, so let's assume we've created a brand new custom workflow activity named Set MMS Field Value in Visual Studio and jump straight into defining arguments and variables. First the arguments:














I want to be able to use this activity update a managed metadata field on any SharePoint list or library, so the first piece of information we need is an identifier for the target list or library (selectedList). Next, we need to know which list item to update. List items are commonly identified using either a GUID identifier (listItemGuid) or an integer identifier (listItemIdIn) - I've defined arguments for both so the workflow designer can use either approach to identify the target list item. Next, we need to know the name of the managed metadata field in the target list item (mmsFieldName). Finally, we need the two property values that uniquely identify our managed metadata term (termGuid and termLabelInteger).

Now the variables:













We'll use listItemId to store the integer identifier for the target list item. The emptyGuid variable is just an empty GUID that we'll use for comparison purposes, and the remaining variables (metadataDV, propertiesDV and fieldValueDV) are DynamicValue properties that we'll use to progressively build the correct JSON structure to update a managed metadata field.

Activity Design

The workflow activity consists of seven child activities that correspond to three main tasks:







































  1. Get the integer identifier of the target list item. (If the workflow designer has provided an integer identifier, use it directly. Alternatively, if the workflow designer has provided a GUID identifier, use the GUID to look up the integer identifier.)
  2. Build up the JSON payload we must provide in order to update the specified managed metadata field.
  3. Update the specified managed metadata field on the specified list item.
Let's take a closer look at these three high-level tasks.

Task 1 - Get an integer identifier for the target list item

Our first task is to get an integer identifier for the target list item. Remember that we're giving the workflow designer two options: he or she can provide either a GUID identifier or an integer identifier to specify the target list item. To cater for both scenarios, we use an If activity. If the list item GUID is equal to an empty GUID, we can assume the workflow designer has used an integer identifier to specify the target list item. In this case, we use an Assign activity to set the the listItemId variable to the value of the listItemIdIn argument. If not, we use a LookupSPListItemId activity to look up the integer identifier using the specified GUID and then set the listItemId variable accordingly.

























Task 2 - Build a JSON payload for the target managed metadata field

Our next task is to build a JSON payload for the target managed metadata field. The payload must take the following format, where <Field name> is the name of the target managed metadata field, <Term label integer> is the term label integer of the managed metadata term, and <Term GUID> is the term GUID of the managed metadata term:


{"<Field name>":{
   "__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},
   "Label":"<Term label integer>",
   "TermGuid":"<Term GUID>",
   "WssId":-1 }}


To take advantage of the built-in child activities in Visual Studio, we need to create our JSON payload using DynamicValue structures. Because of the nested nature of this payload, we need to build the structure progressively from the inside out. First we use a BuildDynamicValue activity to build the contents of the innermost braces (the value of the __metadata property):





















Next, we use a BuildDynamicValue activity to build the value of the middle set of braces (the value of the <Field name> property):





















Notice how we set the __metadata key to the metadataDV value we created in the previous step, thereby creating a nested DynamicValue instance.

Finally, we use a CreateDynamicValue activity to build the value of the outer set of braces:














Note: We use a CreateDynamicValue activity rather than a BuildDynamicValue activity in this task because it allows us to set the dictionary key (PropertyName) to a variable value (mmsFieldName in this case). The BuildDynamicValue activity only allows you to type static string text for the dictionary key (Path). That wouldn't work in this scenario as we don't know the name of the target managed metadata field at compile time.

Task 3 - Update the target list item

Now that we've identified our target list item and build our JSON payload, all that remains is to perform the update operation on the list item. We can use the built-in UpdateListItem activity to do this:















The Actions File

The next stage is to build the actions (.actions4) file for the workflow activity, to specify how our activity should behave when we add it in SharePoint Designer. My actions file looks like this:


















I won't go into more detail on the structure of the actions file right now, as there's nothing out of the ordinary in it and I don't want to get too repetitive. When you deploy the activity and use it in SharePoint Designer, it looks like this:















In this case, I'm using my Get MMS Field Value activity to get the term GUID and the term label integer from a managed metadata field named Customer in the current list item. I'm then using the Set MMS Field Value activity to set the value of the Client field on a list item in the Clients list to the same term. Because the source Customer field and the destination Client field both use the same term set, the workflow is able to copy the value across as desired.

Custom Workflow Activity for Getting Managed Metadata Field Values

In this post I'll show you how to build a custom workflow activity in Visual Studio that gets managed metadata field values from a SharePoint 2013 list or library. You can use the workflow activity in any SharePoint Designer list workflows, including on Office 365 sites - custom workflow activities in SharePoint 2013 are entirely declarative, so deploying to SharePoint Online is not a problem.

This is the second of a three-part series on working with managed metadata fields in workflows:
I'll assume you know the basics of how to build and deploy custom workflow activities - if you need a bit more detail on the deployment side of things, have a read through my first post on custom workflow activities. For now, let's just say I've created a new custom workflow activity named Get MMS Field Value in Visual Studio.

Arguments and Variables

I'll start with a quick run through of the arguments and variables I've used in the activity. First the arguments:











The activity will get the value of a managed metadata field from the current list item, so we only need the caller to provide one argument value - the name of the field (mmsFieldName). We want to return two values to the caller: the term GUID (termGuidOut) and the term label integer (labelOut).

Now the variables:












We'll use listItemFields to store a JSON representation of the list item. The DynamicValue type is perfect for storing and manipulating JSON data. We'll use fieldPathTermGuid and fieldPathLabel to build the XPath expressions we need in order to isolate and extract the term GUID and the term label from the list item JSON.

Activity Design

The workflow activity consists of seven child activities:




  1. Get the current list item.
  2. Build an XPath expression to find the TermGuid property of the specified managed metadata field value.
  3. Build an XPath expression to find the Label property of the specified managed metadata field value.
  4. Add a try-catch block so we can catch any errors when we parse the list item.
  5. Within the try block, use the XPath expression we created earlier to get the TermGuid property from the managed metadata field value.
  6. Within the try block, use the XPath expression we created earlier to get the Label property from the managed metadata field value.
  7. Within the catch block, catch invalid operation exceptions and log the details to the workflow history list. (Invalid operation exceptions occur if the specified field does not exist or is not a managed metadata field.)

Let's walk through each of these in turn.


Step 1 - Get the current list item


The first task is to retrieve the list item from which you want to extract the managed metadata field:



I've used a LookupSPListItem activity to do this. The activity returns the JSON representation of the list item as a DynamicValue instance, which I've assigned to the listItemFields variable.


Step 2 - Build an XPath expression to find the TermGuid property


Now we've got the JSON representation of the list item, we need to figure out how to extract the properties we need. The only effective way to do this is to use the Visual Studio debugger (or a web debugger such as Fiddler) to take a look at the raw JSON data. In my development environment, it looks something like this:

{"d":{"results":[{"__metadata":{"id":"Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)","uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)","etag":"\"59\"","type":"SP.Data.CustomersListItem"},"FirstUniqueAncestorSecurableObject":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FirstUniqueAncestorSecurableObject"}},"RoleAssignments":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/RoleAssignments"}},"AttachmentFiles":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/AttachmentFiles"}},"ContentType":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/ContentType"}},"FieldValuesAsHtml":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsHtml"}},"FieldValuesAsText":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesAsText"}},"FieldValuesForEdit":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/FieldValuesForEdit"}},"File":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/File"}},"Folder":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/Folder"}},"ParentList":{"__deferred":{"uri":"http:\/\/team.jason.net\/_api\/Web\/Lists(guid'77190d4b-c6a4-4e15-ac04-d3124492ca88')\/Items(5)\/ParentList"}},"FileSystemObjectType":0,"Id":5,"ContentTypeId":"0x0100B48DCF9FDA5C9B4BACDCC7DF84D111480007A91266528ED647BA59883DA1C209A8","Title":"Test5","Customer":{"__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},"Label":"1","TermGuid":"47081a2a-c78d-495c-bea9-e1ba8522e881","WssId":1},"TempCol":null,"MMS_x0020_Test_x0020_2":null,"Test_x0020_Create_x0020_List_x00":{"__metadata":{"type":"SP.FieldUrlValue"},"Description":"Stage 1","Url":"http:\/\/team.jason.net\/_layouts\/15\/wrkstat.aspx?List=77190d4b-c6a4-4e15-ac04-d3124492ca88&WorkflowInstanceName=96478011-ec99-469a-a209-c8b1170d0dc1"},"ID":5,"Modified":"2014-10-15T16:49:42Z","Created":"2014-10-08T13:55:57Z","AuthorId":1,"EditorId":1,"OData__UIVersionString":"1.0","Attachments":false,"GUID":"1e222378-6ebc-421f-b87f-f5374d9b8566"}]}}

I've highlighted the bits we're interested in. In this case, we can figure out that the XPath expression to get to the TermGuid property is as follows:

d/results(0)/Customer/TermGuid

Note that the results property actually contains an array of one result, so we use results(0) to get the first object in the array.

We can use an Assign activity to create our XPath expression and assign it to the fieldPathTermGuid variable. If we replace Customer (the MMS field name in this example) with a placeholder for our mmsFieldName variable, the activity looks like this:

















Remember that we're not using the XPath expression at this stage - we're just building an XPath expression from the workflow variables to use in a later task.


Step 3 - Build an XPath expression to find the Label property


We can use the same approach to build an XPath expression that retrieves the Label property - only the last node of the XPath expression is different. In this case we assign the XPath expression to our fieldPathLabel variable: 

















Step 4 - Add a try-catch block

When we come to actually parse the list item, there's quite a lot that could go wrong. If the list item doesn't contain the specified field name, or the specified field is not a managed metadata field, the XPath expressions will fail and the workflow will throw an InvalidOperationException. As such, we want to build the parsing logic within a TryCatch activity:





















Here you can see that we attempt to get the salient managed metadata field properties within a Try block, and we look for an InvalidOperationException in the Catch block. We'll look more closely at the activities within the Try and Catch blocks in the next step. For now, notice that the activities within the Try and Catch blocks are wrapped in Sequence activities. In a production scenario you will probably want to implement a more comprehensive error handling strategy, but for this proof-of-concept scenario I'm mainly interested in catching the common invalid operation exceptions.

Steps 5 and 6 - Get the TermGuid and Label property values

Within our Try block, we can use generic GetDynamicValueProperty<T> activities to get the TermGuid and Label property values:
























In each case:

  • The Source property is the JSON representation of our list item.
  • The PropertyName property is the XPath expression that finds our TermGuid or Label property.
  • The Result property is the output argument that exposes each property to SharePoint Designer workflows.

Step 7 - Catch invalid operation exceptions

Within our InvalidOperationException catch block, all I'm doing at this stage is writing a (hopefully) helpful message to the workflow history list:





The Actions File

The next stage is to build the actions (.actions4) file for the workflow activity. The actions file defines the sentence that appears in SharePoint Designer when you add the custom activity to a workflow, together with the arguments (inputs and outputs) for the custom activity. I'll assume you know the basics of actions files - if not, check out my first post on custom workflow activities. In my case, the actions file looks like this:















As you can see from the Sentence attribute in the RuleDesigner element, we require the workflow designer to specify the name of the managed metadata field, and we return the term GUID and the term label integer value. Note that in the parameter definition for the mmsFieldName argument, we specify a DesignerType of FieldNames. This enables the workflow designer to select the name of the managed metadata field from a list of all the fields in the current list item.

In the SharePoint Designer workflow designer, the custom activity looks like this:

















In this case, I've created a really simple SharePoint Designer workflow to test my custom activity. I use the custom activity to get the term GUID and term label integer properties from a managed metadata field named Customer, and I write the values to the workflow history list.

In the next post, I'll look at the other half of the problem - using these managed metadata field properties to update a managed metadata field in another list.

Getting and Setting Managed Metadata Fields in SharePoint 2013 Workflows

In many workflow scenarios, you'll want to get a field value from a list item in one list and apply that value to a list item on another list. With most field types, you can do this easily using workflow variables and built-in list actions in SharePoint Designer 2013. However, it's widely acknowledged that working with managed metadata fields in SharePoint workflows is a bit of a nightmare. To get around the problem, I built some custom workflow activities to get and set managed metadata fields in SharePoint Designer workflows.

This is the first of a three-part series on working with managed metadata fields in workflows:


Problem overview


Managed metadata fields are similar in structure to lookup fields, with complex values of type TaxonomyFieldValue. If you use the REST API to get or set the value of a managed metadata field named Customer, the JSON payload looks something like this:

{"Customer":{
   "__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},
   "Label":"n",
   "TermGuid":"nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn",
   "WssId":n }}

You can see that the managed metadata field consists of three property values, where TermGuid uniquely identifies the term, Label represents the display name for the term, and WssId is the ID of the list item that stores the term locally in the TaxonomyHiddenList list.
Unfortunately, the built-in list actions in SharePoint Designer 2013 (Create List Item, Update List Item, Set Field in Current Item) will only allow you to set a managed metadata field to a string value. When you try to update a managed metadata field value, SharePoint is expecting a complex type in the format you can see above. However, any value you supply through a built-in list action in SharePoint Designer gets wrapped in quote marks and is treated as a string literal. Behind the scenes, SharePoint throws an InvalidClientQuery exception when it receives the request from your workflow, with the following message:

An unexpected 'PrimitiveValue' node was found when reading from the JSON reader. A 'StartObject' node was expected.


Distractions


You may have read that you can specify string values for managed metadata fields using the format <WssId>;#<Label>|<TermGuid>. That may have worked in SharePoint 2010 workflows, but it doesn't work in SharePoint Designer 2013 where workflow activities are built on the client web services. The error is clear - SharePoint is expecting a complex object, and it complains when the workflow manager sends it a string. It's also worth noting that the built-in list actions in SharePoint Designer don't expose hidden taxonomy fields.

Solution overview


To update the value of a managed metadata field, your workflow needs to send a request that looks something like this:

POST http://team.jason.net/_api/web/lists/getbyid(guid'fe31bb2a-8737-4085-be2e-70368115c688')/Items(29) HTTP/1.1
If-Match: *
X-HTTP-Method: MERGE
Accept: application/json; odata=verbose
Content-Type: application/json; odata=verbose
...

{"Customer":{"__metadata":{"type":"SP.Taxonomy.TaxonomyFieldValue"},"Label":"1","TermGuid":"47081a2a-c78d-495c-bea9-e1ba8522e881","WssId":"-1"},"__metadata":{"type":"SP.Data.ClientsListItem"}}

You need to plug four values into the JSON payload:
  • The name of the managed metadata field (Customer in this example). 
  • The Label value. This can either be the string-based term label or an integer-based lookup value. If you used the REST API to get the term details from an existing list item, you'll have an integer value.
  • The TermGuid value. 
  • The WssId value. You can safely set this value to -1. SharePoint will resolve the term correctly from the Label and TermGuid values.
You could do this with a Call HTTP Web Service activity in SharePoint Designer, but structuring the data would be ugly and laborious. A more elegant solution is to create a couple of custom activities in Visual Studio that you can reuse in multiple SharePoint Designer workflows. In part 2 I'll show you how to create a custom activity to get managed metadata field values, and in part 3 I'll show you how to create a custom activity to set managed metadata field values.

Monday, 17 November 2014

Site Management Workflow Activities - Sample Solution Now Available

Earlier this year, I published a series of blog posts on custom workflow activities for SharePoint 2013 and Office 365. Among other things, I covered how to build workflow activities to:

  • Create sites.
  • Set site permissions and list permissions.
  • Create groups and set group owners.
  • Break permissions inheritance.
  • Set the associated owners, members and visitors groups on a site.
Since then, several people have asked me to make my source solution available - so here it is:


Disclaimers:
  • It's proof-of-concept code, not production-ready code. 
  • For use as a learning aid in a test environment only.
  • Please read the corresponding blog posts before you play around with the custom activities - they'll make much more sense once you've read the explanations.
To build and test the solution, you'll need a test environment with a local SharePoint 2013 installation, SharePoint Designer 2013, and Visual Studio 2012 with Office Developer Tools or Visual Studio 2013.

When you build the solution, Visual Studio creates a .wsp package. You can add this to the solutions gallery on any SharePoint site (including Office 365) - workflow activities in SharePoint 2013 are entirely declarative, so you don't have to worry about resource points and other restrictions. The solution deploys a site-scoped feature named Site Management Workflow Activities. Once you've activated this feature, the custom activities will be available when you open the site in SharePoint Designer.

Final note: To run these activities, your workflow service needs to be running with app permissions and it needs full control rights over the entire site collection (as you'd expect - you're asking it to create sites, create groups, set permissions, and so on). For guidance, see my first post in the series and the MSDN article Create a workflow with elevated permissions by using the SharePoint 2013 Workflow platform.

Friday, 17 October 2014

Can't find Business Data Web Parts? Check your permissions.

Just a quick note on an issue we encountered today. Users with the Contribute permission level can, as you'd expect, edit wiki pages in SharePoint 2013. As part of the editing process, users can add various web parts to the wiki page. Today we were temporarily stumped when a user with Contribute permissions wanted to add a Visio Web Access web part to a wiki page. It turns out that this web part, along with most business data web parts, is only available when the user has the Add and Customize Pages permission (found in the Design permission level).

With the Contribute permission level, the insert web part options looked like this:



















Whereas with the Design permission level, the insert web part options looked like this:
















Conclusion - if your users are struggling to add business data web parts to wiki pages, it might be a permissions issue.

Friday, 3 October 2014

Server-side activities have been updated

If you use SharePoint Designer 2013 to build workflows, there's a fair chance you'll have come across the following error message:
Server-side activities have been updated. You need to restart SharePoint Designer to use the updated version of activities.
 Needless to say, restarting SharePoint Designer rarely makes the error go away. The usual advice is:

Approach 1: Clear the cache folders
See for example How to Clear Your SharePoint Designer 2010/2013 Cache. If you've just deployed some custom workflow activities to your site, this will probably solve your problem (and you should clear the cache folders every time you deploy custom activities). If the error occurs spontaneously, this approach often won't help.

Approach 2: Reinstall SharePoint Designer
This might work if you've got a preview version of SharePoint Designer installed. If not, it's unlikely to help. It didn't work for me, and it didn't work for countless others on the forums.

Approach 3: Install SharePoint Designer on another machine
This one kind of annoys me... it usually works, but it's hardly a practical solution to the problem.

In my case, having tried approach 1 and 2 and having established that the problem was client-specific (a colleague was able to connect to the same sites and create workflows just fine), I did some digging around to see what else could be causing the problem.

Short Answer


(If you're working on a Windows client rather than a SharePoint server)

Check your Programs list for Workflow Manager Client 1.0. If it's there, uninstall it. The Workflow Manager Client needs to run on every SharePoint server in a farm when you pair the farm with a Workflow Manager deployment. In my case, I'm using SharePoint Designer on my laptop to build workflows on an Office 365 site, so there was no reason for the Workflow Manager Client to be there. (I'm not even sure how it got there - maybe bundled with Visual Studio.)

In my case, once I'd uninstalled Workflow Manager Client, I was able to create and edit workflows in SharePoint Designer without errors.

Long Answer ("Show your work")


I started by getting a colleague, John Devaney, to run SharePoint Designer and connect to the site. Once we'd established he was able to create and edit workflows, we compared the contents of our cache folders. The contents of the WebsiteCache folder (%USERPROFILE%\AppData\Local\Microsoft\WebsiteCache) showed some key differences. When you drill down into the version folder for a particular site, mine looked like this:


























Whereas John's looked like this:




















We'd both started with empty cache folders. However, John's SharePoint Designer instance downloaded, generated, or copied in a handful of assemblies and culture definitions (the NLP files), whereas mine failed to do so. 

I experimented with copying these files across to my own cache folder. The key file turned out to be the Microsoft.SharePoint.WorkflowServices.Activities.Proxy.dll assembly. If I copied this assembly across to my cache folder, I was able to create and edit workflows. Without it, I got the Server-side activities have been updated error.

Next, I had a closer look at how SharePoint Designer goes about getting these assemblies. I used Fiddler to look at the web traffic that SharePoint Designer generates when I try to create a new workflow. It calls various client-side object model (CSOM) methods, including two methods in the WorkflowDeploymentService class:
  • GetDesignerActions. According to MSDN, this method "returns a list of valid Workflow Manager Client 1.0 actions for the specified server". As you'd expect, the response body contains a bunch of SharePoint Designer actions in .actions4 (XML) format.
  • GetActivitySignatures. This method takes a single DateTime argument named lastChanged, which suggests a connection to SharePoint Designer caching. Again, as you'd expect, the response body contains a set of activity definitions in XAML format.
The fact that these methods interact with Workflow Manager Client 1.0 on the SharePoint server got me thinking, so I checked the Programs list on my laptop. The working theory is that information on the local instance of Workflow Manager Client could conflict with the information returned by the server, and somehow prevent SharePoint Designer from generating the proxy assemblies. Regardless, uninstalling the local Workflow Manager Client instance solved the problem for me.

Seeing as it's not an easy issue to replicate, I'd be interested to hear whether this fix works for other people.