4
votes

I have a .config in a target project and I need to add a line to it programmatically via an MSBuild task.

Pseduo operations like:

  • find target .config file
  • determine the value of attributes for new node (e.g. 'id' and 'version' for 'package' node)
  • insert new node in correct parent node
  • save changes

The .config file at $TargetProjectDir\Config\packages.config:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="ABC" version="1.1.0.4" />
  <package id="XYZ" version="2.0.0.0" />
</packages>

Needs to look like this afterwards:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="ABC" version="1.1.0.4" />
  <package id="XYZ" version="2.0.0.0" />
  <package id="CarDataWidget" version="3.0.0.0" />
</packages>

So far i've considered using 'inline tasks', the 'EXEC' task and 'XmlPoke' task but haven't managed to get any of them working.

Here is my attempt with XmlPoke and XmlPeek:

I used the following article as an inspiration on how to add nodes to the packages.config file: http://weblogs.asp.net/bsimser/appending-nodes-in-xml-files-with-xmlpeek-and-xmlpoke-using-nant

  <Target Name="AfterBuild" DependsOnTargets="AddPackage">
  </Target>
  <Target Name="AddPackage">
    <!-- Load existing nodes into a Property -->
    <XmlPeek XmlInputPath="config/packages.config" Query="/packages/package" >
      <Output TaskParameter="Result" PropertyName="Peeked" />
    </XmlPeek>
    <Message Text="From Peek: $(Peeked)"></Message>

    <!-- Load new node into Property -->
    <PropertyGroup>
      <WidgetName>CarDataWidget</WidgetName>
      <WidgetVersion>2.0.0.0</WidgetVersion>
      <NewNode>&lt;package id&#61;&quot;$(WidgetName)&quot; version&#61;&quot;$(WidgetVersion)&quot; /&gt;</NewNode>

    <!-- Concatenate existing and new node into a Property -->
      <ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>
    </PropertyGroup>
    <Message Text="New pacakges: $(ConcatenatedNodes)"></Message>

    <!-- Replace existing nodes with concatenated nodes -->
    <XmlPoke Value="$(ConcatenatedNodes)" XmlInputPath="config/packages.config" Query="/packages">
    </XmlPoke>
  </Target>

The output from the above build is:

1>AddPackage:
1>  From Peek: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" />
1>  New pacakges: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />
1>  C:\_dev\CarDataWidget.csproj(184,14): 
    error MSB4094: "<package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />" 
    is an invalid value for the "Value" parameter of the "XmlPoke" task. 
    Multiple items cannot be passed into a parameter of type "Microsoft.Build.Framework.ITaskItem".
1>
1>Build FAILED.

THE QUESTION: How can get it to add to a .config file with existing package nodes???

4
Just curious: have you considered using SlowCheetah extension and simple config transformations? visualstudiogallery.msdn.microsoft.com/… If you still need to do this manually (e.g. if simple configuration-based transformations are not OK for you), XmlPoke is a viable options, but you should probably share your code for XmlPoke usage attempt, so peple can help you make it work.Isantipov

4 Answers

6
votes

I had the same problem. I found the solution here.

The problem is than XmlPoke considers semicolon as a value separator.

Should replace this:

<NewNode>&lt;package id&#61;&quot;$(WidgetName)&quot; version&#61;&quot;$(WidgetVersion)&quot; /&gt;</NewNode>

With:

<NewNode>&lt%3Bpackage id&#61%3B&quot%3B$(WidgetName)&quot%3B version&#61%3&quot%3$(WidgetVersion)&quot%3 /&gt%3</NewNode>

Must replace each semicolon by the secuence %3B

3
votes

Here is a way to do it using MSBuild Extension Pack.

Set the packages and versions in the NewPackage item group and it adds them to the XML file.

<Project 
    ToolsVersion="4.0" 
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" />

  <Target Name="Test" DependsOnTargets="AddPackage">
  </Target>

  <ItemGroup>
    <NewPackage Include="CarDataWidget">
        <Version>3.0.0.0</Version>
    </NewPackage>
    <NewPackage Include="FooBarWidget">
        <Version>1.2.3.4</Version>
    </NewPackage>
  </ItemGroup>

  <Target Name="AddPackage">

    <PropertyGroup>
        <InputFile>in.xml</InputFile>
        <OutputFile>out.xml</OutputFile>
    </PropertyGroup>

    <Copy SourceFiles="$(InputFile)" DestinationFiles="$(OutputFile)" />

    <MSBuild.ExtensionPack.Xml.XmlFile
      TaskAction="AddElement"
      File="$(OutputFile)"
      XPath="//packages"
      Element="package"
      Key="id"
      Value="%(NewPackage.Identity)" />

    <MSBuild.ExtensionPack.Xml.XmlFile
      TaskAction="AddAttribute"
      File="$(OutputFile)"
      XPath="//packages/package[@id='%(NewPackage.Identity)']"
      Key="version"
      Value="%(NewPackage.Version)" />
  </Target>
</Project>
1
votes

Not hoping to wake up an old thread.I had the exact scenario were I had to add new keys to the appsettings section of web.config. I started off with OPs code and was stuck with the same problem with ; in the peeked value preventing the new concatenated value to be written. I fixed it by using Replace function to remove the ;

          <ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>          
          <!--in the concatenatednode, remove semicolon-->
          <ChangedPeek>$(ConcatenatedNodes.Replace(";",""))</ChangedPeek>
          <!-- Replace existing nodes with concatenated nodes-->
          <XmlPoke XmlInputPath="%(WebConfigFilesSolutionDir.FullPath)" Query="//appSettings" Value="$(ChangedPeek)" />

For the complete answer on how to add a new key to appsetting section of webconfig using MSBuild refer https://stackoverflow.com/a/56760009/6664129

0
votes

Take a look at my blog post http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx which compares the following methods.

  1. Use SlowCheetah to transform the files for you
  2. Use the TransformXml task directly
  3. Use the built in (MSBuild 4.0) XmlPoke task
  4. Use a third party task library