Starting from Sharepoint 2010 we used to work with managed metadata. But Sharepoint Online introduced new challenge: we have to provision managed metadata fields and term sets via client object model now. I searched for existing solutions first and they didn’t satisfy me. The most often solution I found was to hardcode term store id and provision managed metadata fields declaratively (with optional sugar like automatic replace of this id during publishing of wsp package). I don’t like this approach. What I needed is the same way which we use for regular on-premise Sharepoint installations:
- create term sets from xml file;
- provision managed metadata fields;
- bind fields to term sets.
I wrote PowerShell script which automates these tasks for Sharepoint Online using client object model. Let’s start from creating term sets in local site collection’s term store:
1: function Create-Term($ctx, $termSet, $label, $lcid)
2: {
3: $term = $termSet.CreateTerm($label, $lcid, [System.Guid]::NewGuid())
4: $ctx.ExecuteQuery()
5: }
6:
7: function Create-TermSet($ctx, $group, $termSetXml, $lcid)
8: {
9: Write-Host "Creating term set" $termSetXml.Name -foregroundcolor Green
10: $termSet = $group.CreateTermSet($termSetXml.Name, $termSetXml.Id, $lcid)
11: $ctx.ExecuteQuery()
12:
13: $termSetXml.Term | ForEach-Object { Create-Term $ctx $termSet $_.Name $lcid }
14: }
15:
16: function Get-TermStore($ctx)
17: {
18: Write-Host "Loading taxonomy session" -foregroundcolor Green
19: $session =
20: [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($ctx)
21: $session.UpdateCache();
22: $ctx.Load($session)
23: $ctx.ExecuteQuery()
24:
25: Write-Host "Loading term stores" -foregroundcolor Green
26: $termStores = $session.TermStores
27: $ctx.Load($termStores)
28: $ctx.ExecuteQuery()
29: $termStore = $termStores[0]
30: $ctx.Load($termStore)
31: Write-Host "Term store with the following id is loaded:"
32: $termStore.Id -foregroundcolor Green
33: return $termStore
34: }
35:
36: function Provision-TermSets($ctx, $xmlFilePath)
37: {
38: Write-Host "Load term sets from xml" -foregroundcolor Green
39: [xml]$xmlContent = (Get-Content $xmlFilePath)
40: if (-not $xmlContent)
41: {
42: Write-Host "Xml was not loaded successfully. Term sets won't be created"
43: -foregroundcolor Red
44: return
45: }
46:
47: $termStore = Get-TermStore $ctx
48:
49: Write-Host "Creating group" $xmlContent.Id -foregroundcolor Green
50: $groups = $termStore.Groups
51: $ctx.Load($groups)
52: $ctx.ExecuteQuery()
53:
54: $group = $groups | Where-Object {$_.Name -eq $xmlContent.Group.Name}
55: if ($group)
56: {
57: Write-Host "Group" $xmlContent.Group.Name
58: "already exists. If you want to recreate it, delete existing group first"
59: -foregroundcolor Yellow
60: return
61: }
62:
63: $group = $termStore.CreateGroup($xmlContent.Group.Name, $xmlContent.Group.Id)
64: $ctx.ExecuteQuery()
65: $xmlContent.Group.TermSet |
66: ForEach-Object { Create-TermSet $ctx $group $_ $termStore.DefaultLanguage }
67: }
The entry point is Provision-TermSets() method (line 36). Before to call it we need to define term sets in the xml. I used the following structure:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <Group Name="TestGroup" Id="...">
3: <TermSet Name="TermSet1" Id="...">
4: <Term Name="Term11" />
5: <Term Name="Term12" />
6: <Term Name="Term13" />
7: </TermSet>
8: <TermSet Name="TermSet2" Id="...">
9: <Term Name="Term21" />
10: <Term Name="Term22" />
11: <Term Name="Term23" />
12: </TermSet>
13: <TermSet Name="TermSet3" Id="...">
14: <Term Name="Term31" />
15: <Term Name="Term32" />
16: <Term Name="Term33" />
17: </TermSet>
18: </Group>
One thing we should notice here is that we explicitly define term set ids with names. It will help us when we will bind managed metadata fields to them (see below). Having term sets structure in xml file we can now create them using the following command:
1: $context = New-Object Microsoft.SharePoint.Client.ClientContext($siteURL)
2: $context.AuthenticationMode =
3: [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
4: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
5: $credentials =
6: New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username,
7: $securePassword)
8: $context.Credentials = $credentials
9:
10: Provision-TermSets $context $xmlPath
On lines 1-8 we prepare client context with user’s credentials and then call Provision-TermSets() function defined above with created context and path to xml file. After that we will have our term sets created in the term store.
Second step is to create managed metadata fields. We will do it declaratively, which is supported in sandbox solutions, so it will be the same as it would be for on-premise version. In our example we have 3 term sets, so lets provision 3 managed metadata fields:
1: <?xml version="1.0" encoding="utf-8"?>
2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
3: <Field Type="Note"
4: DisplayName="Field1TaxHTField"
5: MaxLength="255"
6: Group="Test"
7: ID="..."
8: StaticName="Field1TaxHTField"
9: Name="Field1TaxHTField"
10: Hidden="TRUE"
11: ShowInViewForms="FALSE"
12: Description="" />
13: <Field ID="..."
14: SourceID="http://schemas.microsoft.com/sharepoint/v3"
15: Type="TaxonomyFieldType"
16: DisplayName="Field1"
17: ShowField="Term1033"
18: Required="FALSE"
19: EnforceUniqueValues="FALSE"
20: Group="Test"
21: StaticName="Field1"
22: Name="Field1"
23: Hidden="FALSE"
24: Mult="FALSE">
25: <Default></Default>
26: <Customization>
27: <ArrayOfProperty>
28: <Property>
29: <Name>IsPathRendered</Name>
30: <Value xmlns:q7="http://www.w3.org/2001/XMLSchema"
31: p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
32: true
33: </Value>
34: </Property>
35: <Property>
36: <Name>TextField</Name>
37: <Value xmlns:q6="http://www.w3.org/2001/XMLSchema"
38: p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
39: {... - use id of note field defined above}
40: </Value>
41: </Property>
42: </ArrayOfProperty>
43: </Customization>
44: </Field>
45: <Field Type="Note"
46: DisplayName="Field2TaxHTField"
47: MaxLength="255"
48: Group="Test"
49: ID="..."
50: StaticName="Field2TaxHTField"
51: Name="Field2TaxHTField"
52: Hidden="TRUE"
53: ShowInViewForms="FALSE"
54: Description="" />
55: <Field ID="..."
56: SourceID="http://schemas.microsoft.com/sharepoint/v3"
57: Type="TaxonomyFieldType"
58: DisplayName="Field2"
59: ShowField="Term1033"
60: Required="FALSE"
61: EnforceUniqueValues="FALSE"
62: Group="Test"
63: StaticName="Field2"
64: Name="Field2"
65: Hidden="FALSE"
66: Mult="TRUE">
67: <Default></Default>
68: <Customization>
69: <ArrayOfProperty>
70: <Property>
71: <Name>IsPathRendered</Name>
72: <Value xmlns:q7="http://www.w3.org/2001/XMLSchema"
73: p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
74: true
75: </Value>
76: </Property>
77: <Property>
78: <Name>TextField</Name>
79: <Value xmlns:q6="http://www.w3.org/2001/XMLSchema"
80: p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
81: {...- use id of note field defined above}
82: </Value>
83: </Property>
84: </ArrayOfProperty>
85: </Customization>
86: </Field>
87: <Field Type="Note"
88: DisplayName="Field3TaxHTField"
89: MaxLength="255"
90: Group="Test"
91: ID="..."
92: StaticName="Field3TaxHTField"
93: Name="Field3TaxHTField"
94: Hidden="TRUE"
95: ShowInViewForms="FALSE"
96: Description="" />
97: <Field ID="..."
98: SourceID="http://schemas.microsoft.com/sharepoint/v3"
99: Type="TaxonomyFieldType"
100: DisplayName="Field3"
101: ShowField="Term1033"
102: Required="FALSE"
103: EnforceUniqueValues="FALSE"
104: Group="Test"
105: StaticName="Field3"
106: Name="Field3"
107: Hidden="FALSE"
108: Mult="TRUE">
109: <Default></Default>
110: <Customization>
111: <ArrayOfProperty>
112: <Property>
113: <Name>IsPathRendered</Name>
114: <Value xmlns:q7="http://www.w3.org/2001/XMLSchema"
115: p4:type="q7:boolean" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
116: false
117: </Value>
118: </Property>
119: <Property>
120: <Name>TextField</Name>
121: <Value xmlns:q6="http://www.w3.org/2001/XMLSchema"
122: p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">
123: {... - use id of note field defined above}
124: </Value>
125: </Property>
126: </ArrayOfProperty>
127: </Customization>
128: </Field>
129: </Elements>
As I wrote above this part is the same as for on-premise Sharepoint, so I won’t comment it.
The last step is to bind created managed metadata fields to the term sets. It can be done via the following PowerShell script:
1: function Bind-Managed-Metadata-Field($ctx, $termStoreId, $fieldId, $termSetId)
2: {
3: $rootWeb = $ctx.Web
4: $fields = $rootWeb.Fields
5: $ctx.Load($fields)
6: $ctx.ExecuteQuery()
7:
8: try
9: {
10: $field = $fields.GetById($fieldId)
11: }
12: catch
13: {
14: Write-Host "Field" $fieldId "not found in site columns collection."
15: "It won't be bound to the term set" -foregroundcolor red
16: return
17: }
18:
19: $taxField = [Microsoft.SharePoint.Client.ClientContext].GetMethod("CastTo").
20: MakeGenericMethod([Microsoft.SharePoint.Client.Taxonomy.TaxonomyField]).
21: Invoke($ctx, $field)
22: $taxField.SspId = $termStoreId
23: $taxField.TermSetId = $termSetId
24: $taxField.TargetTemplate = ""
25: $taxField.AnchorId = [System.Guid]::Empty
26: $taxField.UpdateAndPushChanges($true)
27: $ctx.ExecuteQuery()
28: Write-Host "Field" $fieldId "was successfully bound to termset"
29: $termSetId -foregroundcolor green
30: }
31:
32: function Bind-Managed-Metadata-Fields($ctx, $xmlFilePath)
33: {
34: Write-Host "Binding managed metadata fields to term sets"
35: -foregroundcolor green
36: [xml]$xmlContent = (Get-Content $xmlFilePath)
37: if (-not $xmlContent)
38: {
39: Write-Host "Xml was not loaded successfully. "
40: "Fields won't be bound to term sets" -foregroundcolor Red
41: return
42: }
43: $termStore = Get-TermStore $ctx
44: $groups = $termStore.Groups
45: $ctx.Load($groups)
46: $ctx.ExecuteQuery()
47: $group = $groups | Where-Object {$_.Name -eq $xmlContent.Group.Name}
48: if (-not $group)
49: {
50: Write-Host "Group" $xmlContent.Group.Name "not found. "
51: "Fields won't be bound to term sets" -foregroundcolor Red
52: return
53: }
54:
55: Bind-Managed-Metadata-Field $ctx $termStore.Id "{field1 id}" "{term set1 id}"
56: Bind-Managed-Metadata-Field $ctx $termStore.Id "{field2 id}" "{term set2 id}"
57: Bind-Managed-Metadata-Field $ctx $termStore.Id "{field2 id}" "{term set3 id}"
58: }
Method Bind-Managed-Metadata-Field() which is shown on lines 1-30 makes the actual binding. Its code is quite obvious, the only interesting thing is how to call clientContext.CastTo<TaxonomyField>() generic method in PowerShell. It is shown on lines 19-21. As we know ids of the term sets (see above) we may just specify these ids when call Bind-Managed-Metadata-Field() for our fields (lines 55-57). After that managed metadata fields will be bound to the term sets and you will be able to define values for these fields when create or update content.
As you can see for Sharepoint Online regular tasks are implemented in different way, but if you work with on-premise Sharepoint installations, there should not be a lot of problems to move to client object model. Hope that this information will help you in your work.
No comments:
Post a Comment