2010/01/21

Working with XML in PowerShell (2)

In a previous post I wrote about examining XML with PowerShell. However, it is often not only necessary to peek into XML, but also to change it.

Updating XML with NAnt

In NAnt, it is possible to use xmlpoke to update XML in arbitrary files. The following NAnt snippet is taking from the build script of ActiveRecord’s test project and allows to adjust the database settings of the build machine.

  <target name="configure-tests">
    <property name="app.config" value="${build.dir}/${project::get-name()}.dll.config" />
    <xmlpoke
      file="${app.config}"
      xpath="/configuration/activerecord/config/add[@key='connection.connection_string']/@value"
      value="${ar.connection.connection_string.1}" 
    />
  </target>

In the snippet above I have omitted some 10 more xmlpoke tasks that all change values in the same application configuration file.

How can this be done in PowerShell? The preceding post in this series showed how to assign the output of a command to a variable, so we only have to use a command that reads the contents of a file and assign the output to the variable:

    $app_config = $project_path + "\build\net-3.5\debug\Castle.ActiveRecord.Tests.dll.config"
    [xml] $cfg = Get-Content $app_config

Now we have the XML to update available as an instance of XmlDocument and the full power of .NET at our hands to manipulate it. When we just had to traverse a path to change our XML, we could simply use dotted path. But if we have to differentiate siblings by its attribute values, the dotted path becomes unwieldy as we had to use a for-each-loop. But we can use XPath instead:

    $cfg.SelectSingleNode("/configuration/activerecord/config/add[@key='connection.connection_string']").value = $myDatabaseConnString
    $cfg.SelectSingleNode("/configuration/activerecord/config/add[@key='dialect']").value = $myDatabaseDialect
    # ...

Finally, we have to save the updated XML. There is one caveat here: XmlDocuments don’t keep whitespace but normalize where it is deemed to be safe. But if we change generated files instead of manually edited ones, this is only a minor nuisance.

    $cfg.Save($app_config)   

The full script is shown below. As before it is quite short compared to the NAnt XML script.

properties {
    [string] $project_path = "C:\dev\castle\SVN\ActiveRecord"
    $myDatabaseConnString = "PSDBCFG"
    $myDatabaseDialect = "PSDBDIALECT"
}

task default -depends configure_tests

task configure_tests {
    $app_config = $project_path + "\build\net-3.5\debug\Castle.ActiveRecord.Tests.dll.config"
    [xml] $cfg = Get-Content $app_config
    $cfg.SelectSingleNode("/configuration/activerecord/config/add[@key='connection.connection_string']").value = $myDatabaseConnString
    $cfg.SelectSingleNode("/configuration/activerecord/config/add[@key='dialect']").value = $myDatabaseDialect
    # ...
    $cfg.Save($app_config)   
}

1 comment:

query bird said...

I'm working on a script for my employer that will search a directory of xml files, find offending node values, and replace them with desired values. The desired values may change from day to day. This post was helpful. Our machines are locked down and our only scripting tool available is Powershell. I'm beginning to see how powerfull it is, and easy to use.