Injecting CDATA into MPs

There are situations where it may be useful to have the scripts that are part of your MP to be enclosed in CDATA tags. This allows you to copy and paste scripts directly from MP XML into a file and run them (usually to debug some issue).

If you use the $IncludeFileContent…$ directive in VSAE to include a text file into your MP in VSAE, your script will be wrapped in CDATA tags. If you do this, there’s no need to read the rest of this article. :)

However, if you do not use that directive, your script will not be enclosed in CDATA tags. This means that various characters such as < and > will be escaped to produce valid XML (< will be replaced with &lt;, for example). While the MP will work correctly (both XML formats are equivalent), it makes it difficult to copy and paste the script into a file and run it.

The VSAE import wizard does not extract scripts from MPs, so these will end up inline with the XML and likely without CDATA tags. If you want to force CDATA tags back in, I came up with a quick workaround that adds an extra build step before MP verification in VSA to inject CDATA for specific locations in the MP.

To enable this functionality, please copy and paste the following text (until the closing Project tag) into a file called ManagementPack.CData.targets:

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask
    TaskName="InjectCData"
    TaskFactory="CodeTaskFactory"
    AssemblyName="Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" >
    <ParameterGroup>
      <Input Required="true" ParameterType="System.String"/>
      <Output Required="true" ParameterType="System.String"/>
      <Paths Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]"/>
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml" />
      <Reference Include="System.Xml.Linq" />
      <Using Namespace="System.Collections.Generic" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.Linq" />
      <Using Namespace="System.Xml.Linq" />
      <Using Namespace="System.Xml.XPath" />
      <Code Type="Fragment" Language="cs">
<![CDATA[
            XDocument document = XDocument.Load(this.Input);
            IList<XElement> elements = this.Paths
                .Select(item => item.ItemSpec)
                .SelectMany(document.XPathSelectElements)
                .Where(element => element.HasElements == false)
                .ToList();
            foreach (XElement element in elements)
            {
                string contents = element.Value;
                element.SetValue(string.Empty);
                element.Add(new XCData(contents));
            }
            document.Save(this.Output);
]]>
      </Code>
    </Task>
  </UsingTask>

  <PropertyGroup>
    <CDataInjectionEnabled Condition=" $(CDataInjectionEnabled) == '' ">true</CDataInjectionEnabled>
  </PropertyGroup>
  <ItemGroup>
    <CDataInjectorPath Include="//ScriptBody"/>
    <CDataInjectorPath Include="//Files/File/Contents"/>
  </ItemGroup>

  <Target Name="InjectCDataIntoUnsealedMp" BeforeTargets="MpVerifyManagementPack" Condition=" $(CDataInjectionEnabled) == 'true' ">
    <Message Importance="High" Text="Injecting CData"/>
    <InjectCData Input="$(VerificationInput)" Output="$(VerificationInput)" Paths="@(CDataInjectorPath)"/>
  </Target>
</Project>

Place the file at the same location as the .mpproj file you are interested in. Then, locate the <Import…/> directive near the bottom of the file and add the following below it:

<Import Project="ManagementPack.CData.targets" />

It should look similar to this:

  <Import Project="$(MSBuildExtensionsPath)\Microsoft\VSAC\Microsoft.SystemCenter.OperationsManager.targets" />
  <Import Project="ManagementPack.CData.targets" />
< /Project>

At this point you can restart VSAE and build your project. When you build, you will see similar build output to the following (this assumes default build verbosity settings in VS 2010):

------ Build started: Project: ManagementPack1, Configuration: Debug x86 ------
        Starting MP Build for ManagementPack1.
        Starting Fragment Verification
        Resolving Project References
        Starting Merge Management Pack Fragments
        Starting Pre Processing
        Injecting CData
        Starting MP Verify
        Resolving resources
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

You should see an extra step in the build called “Injecting CData”.

With the Import directive added to your project, 1 new property and 1 new item is available for customization:

CDataInjectionEnabled – This MS Build property controls whether CData sections are actually injected into the MP. The default value is true, but you can set it to false if, for some reason, you wish to turn off the injection functionality without removing the new import.

CDataInjectorPath – This MS Build item controls which paths in the XML files will be considered for CData section injection. The paths are specified in XPath 1.0 format. If the path resolved to an XML tag with sub-tags, the tag will be skipped and a CData section will not be injected.

The current version will attempt to inject CData sections for every ScriptBody tag (//ScriptBody) and for every Contents tag that appears under a File tag that appears under a Files tag (/Files/File/Content). I may have missed some locations where scripts appear, so feel free to customize the list yourself (and don’t forget to let me know, so I can update the article).

Happy injecting! :)