In the previous post I talked about how provisioning Managed Metadata fields is tricky for Office 365 (since developers cannot use server-side code to “hook-up” the field to the Term Store, like we can on-premises). I talked about the way we approach this, and in this post I’ll show the Visual Studio/MSBuild customisation we use to build WSPs specific to an Office 365 tenancy. Here’s how the articles relate to each other:
- Provisioning Managed Metadata fields in Office 365 – dealing with multiple environments
- Provisioning Managed Metadata fields in Office 365 – building WSP packages for a specific environment/tenancy [this article]
Using MSBuild/custom Project Configurations to build packages per Office 365 tenancy
Here’s what I came up with – it took quite a few long nights in the murky hall-of-mirrors world of “batching” in MSBuild, roughly 5 million discarded approaches and several new gray hairs. But finally, it works great for us:
We now have some custom project configurations which can be selected in Visual Studio:
- “CandCVM” – Content and Code (name of my employer) virtual machine. Used for when you are developing against your local VM rather than the cloud
- “DevTenancy” – the Office 365 tenancy we are using for DEV purposes
- “TestTenancy” – the Office 365 tenancy we are using for TEST purposes
Those are all the environments we need to worry about for now, but of course others will be needed eventually.
We also have an XML file in the VS project, which defines the Managed Metadata IDs for each environment – since we now only have to worry about the SspId, that’s the only value the file contains:
Once the project (or solution) configuration has been switched, we just build/publish the WSP to the filesystem as usual:
..and as you’d expect, we get the WSP(s) which will work against that environment:
What has happened, and how?
As the WSPs were being packaged, some custom MSBuild executed. What this does is:
- Finds the SspId_Replacements.xml file
- Reads the SspId value for the appropriate environment
- Performs a find/replace across all the of the XML files about to go into the package(s), and replaces the SspId value with the one read from the ‘replacements’ file
- Although it seems inefficient to look for an SspId and try to replace across *all* XML files (and it is), we do need to ensure that we catch:
- Managed Metadata field declarations (i.e. in elements.xml files)
- The section in schema.xml files (for list definitions) where the fields are duplicatedI did look at trying to constrain the search beyond just the .xml extension, but found this difficult in MSBuild. I was also mindful of the fact that a developer could choose to have a Feature elements file NOT named elements.xml 🙂 Fortunately the performance hit for us is negligible – the overall penalty in packaging for using this stuff (compared to a plain Debug or Release build) seems to be around 1-3 seconds – perfectly tolerable right now.
- The replacement is XML-based (using XPath rather than string manipulation), so if you have XML comments nearby, this won’t trip up the replacement.
Benefit: integrating with Continuous Integration/automated build
One reason I wanted to implement in MSBuild is because I knew that it would be painless to use in a CI process. When implementing the build in TFS, we select the solutions/projects to build, and then define any custom Configurations we wish to use there also (i.e. in addition to developer machines). In our case, our CI builds deploy to our test tenancy:
We then make sure our build definition is using this Configuration (instead of Debug or Release):
Want to use this? Here’s the MSBuild..
If you think this could be useful to you, here are the details – essentially you need to edit your .csproj file, add the SspId_Replacements.xml file to your project and finally define the custom Configuration in Visual Studio. The sequence doesn’t matter, so long as all pieces are in place before you try a build. In team development, these steps need to be performed just once per VS project (e.g. by the lead developer).
Step 1 – add the custom MSBuild to your project:
Edit your .csproj file (each of them if you have multiple – the solution is currently “project-scoped”) to include this at the bottom:
|1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23||
VSToolsPath)\SharePointTools\Microsoft.VisualStudio.SharePoint.targets” Condition=”‘$(VSToolsPath)’ != ”” />
<!– COB 15/08/2013: This next target does the work of replacing the SspId.. –>
<Target Name=”AfterLayout” Condition=”$(Configuration) != ‘Debug’ AND $(Configuration) != ‘Release'”>
<!– Set some variables.. –>
<!– Fetch the appropriate value from the environments.xml file.. –>
ReplacementsFile)..” Importance=”high” />
<XmlPeek XmlInputPath=”$(ReplacementsFile)” Query=”Replacements/Environment[@Name=’$(Configuration)’]/SspId/text()”>
FetchedSspIdValue) from $(ReplacementsFile) file” Importance=”high” />
<!– Now make replacements.. –>
<!– In elements.xml files containing field definitions.. –>
<XmlPoke XmlInputPath=”%(FilesForSspIdReplacement.Identity)” Namespaces=”” Query=”x:Elements/x:Field/x:Customization/x:ArrayOfProperty/x:Property[x:Name=’SspId’]/x:Value” Value=”@(FetchedSspIdValue)” />
<!– ..and in list schema.xml files.. –>
<XmlPoke XmlInputPath=”%(FilesForSspIdReplacement.Identity)” Namespaces=”” Query=”x:List/x:MetaData/x:Fields/x:Field/x:Customization/x:ArrayOfProperty/x:Property[x:Name=’SspId’]/x:Value” Value=”@(FetchedSspIdValue)” />
Step 2 – add the XML file with your config values:
Add an XML file within your project at the path “_Replacements\SspId_Replacements.xml” (N.B. this is configurable, see the MSBuild above). Add in the XML shown above, substituting the relevant SspId(s) for your environment(s). If you don’t know where to find the SspId for your environment, my colleague Luis mentions that in his post.
Step 3 – define the custom Configurations in Visual Studio:
In Visual Studio, define a custom Configuration for each environment you need to build WSPs for. Start by going to Configuration Manager and selecting “New..”:
Now close Configuration Manager – the implementation is complete.
To create WSPs, use the Configuration dropdown to switch to the “TestTenancy” (or whatever label you used) build type – be careful that the Platform stays as “Any CPU” here, you may need to change it back if not. Then just right-click on the project in Solution Explorer and click “Package” as usual – you should then get a WSP package in which the SspId find/replace has occured. You can now test deploying this to your Office 365 tenancy which corresponds to this build type.
A note on requirements
Note that the MSBuild used here is not compatible with SP2010/Visual Studio 2010 (but that’s OK because the entire technique is not needed there). There is a dependency on .NET 4.0 for the XmlPeek/XmlPoke MSBuild activities which are used to update the XML files.