Share via


Powershell: Script Updates to Machine.config (updatemachineconfig.ps1)

If you maintain a server park with applications requiring WCF extensions (behaviors, etc.), you will most likely want to maintain the machine.config files. However, you might not want to keep the complete files in your source repository.

This article shows a Powershell script that updates/merges an existing machine.config with items from an .xml file with the same structure as a machine.config file.
It also presents a .cmd script that takes several such files and merges both the 32- and 64-bit machine.configs.

Merging an XML file

The script makes heavy use of XMLDocument and XMLNode objects, and XPaths with SelectNodes and SelectSingleNode. The core functions are MergeExtensionNode and CreateExtensionNode, which have basic knowledge of the structure of a machine.config file. That is, they merge only system.serviceModel/extension nodes, but for the purpose this code was written it is sufficient.

MergeExtensionNode

01.function MergeExtensionNode([string]$extensionNodeName)
02.{
03.    [string]$configPath = "/configuration/system.serviceModel/extensions/" + $extensionNodeName;
04.    [System.Xml.XmlNode]$machineconfigExtnsNode = $machineconfig.SelectSingleNode($configPath);
05.    [System.Xml.XmlNodeList]$additionconfigExtnsNodes = $additionconfig.SelectNodes($configPath + "/*");
06.    if (($additionconfigExtnsNodes -ne $null) -and ($machineconfigExtnsNode -ne $null))
07.    {
08.        Write-Output "  $extensionNodeName"
09.        foreach ($additionconfigNode in $additionconfigExtnsNodes)
10.        {
11.            [System.Xml.XmlNode]$newNode = CreateExtensionNode $machineconfig $additionconfigNode.name $additionconfigNode.type
12.            $nodePath = $configPath + "/add[@name='"  + $additionconfigNode.name + "']";
13.            [string]$currNodename = $additionconfigNode.name;
14.            # Check if node already exists in machine.config:
15.            $machineconfigCurrentNode = $machineconfig.SelectSingleNode($nodePath);
16.            if ($machineconfigCurrentNode -ne $null)
17.            {
18.                # It did indeed exist, replace it:
19.                [void]$machineconfigExtnsNode.ReplaceChild($newNode, $machineconfigCurrentNode);
20.                Write-Output "    $currNodename - replaced"
21.            }
22.            else
23.            {
24.                # Create it: (AppendChild adds the node last)
25.                [void]$machineconfigExtnsNode.InsertBefore($newNode, $machineconfigExtnsNode.FirstChild);
26.                Write-Output "    $currNodename - created"
27.            }
28.        }
29.    }
30.}

Lines 3–6 checks whether both the machine.config and the addition file has extension node(s).
Line 9 starts a loop for each extension node in the addition file.
Line 11 creates a new node in the machine.config document (but it is not hung in to the right place yet). The CreateExtensionNode function is shown below.
Line 15–16 looks if a node with the same name atttribute already exists in machine.config.
Line 18–20 replaces an existing node.
Line 24–26 adds the new node first. If you want to add it last, use AppendChild() instead.

CreateExtensionNode

CreateExtensionNode is rather straightforward. It takes the XML Document, the name, and the type attributes as parameters:

01.function CreateExtensionNode ([System.Xml.XmlDocument]$xmlDoc, [string]$name, [string]$type)
02.{
03.    [System.Xml.XmlNode]$node = $xmlDoc.CreateElement("add");
04.    [System.Xml.XmlAttribute]$attrName = $xmlDoc.CreateAttribute("name");
05.    $attrName.InnerText = $name;
06.    [System.Xml.XmlAttribute]$attrType = $xmlDoc.CreateAttribute("type");
07.    $attrType.InnerText = $type;
08.    [void]$node.Attributes.Append($attrName);
09.    [void]$node.Attributes.Append($attrType);
10.    return $node;
11.}

That was the core functions. However, it doesn't make for a complete solution. A complete solution needs arguments checking, showing syntax, etc, and is shown in the end of this post.

Also, not everyone lives in the powershell all day. Therefore, a .cmd script that can call the powershell script for all found addition files for all necessary machine.config files helps the stressed system administrator.

.cmd Wrapper

The .cmd script takes an update file, or a directory, as argument. If the argument is a directory, it calls the powershell script for all files in that directory:

01.set machineconfig32=C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
02.set machineconfig64=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
03. 
04.if exist %1\. goto IsDirectory
05. 
06.:IsSingleFile
07.powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig32% %1
08.powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig64% %1
09.goto End
10. 
11.:IsDirectory
12.for %%f in (%1) do (
13.  powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig32% %%f
14.  powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig64% %%f
15.)
16.goto End

Lines 1–2 tells which machine.configs to update.
Line 4 checks if the argument is a directory.
Lines 7–8 calls the powershell script for a single file.
Line 12 starts the loop for all files in the directory, and lines 13–14 calls the powershell script with the current file in the directory as argument.

Worth explaining is the expansions used to retrieve the current directory of the executing script, to point to the powershell script. Using the expansion ~dp for the variable %0 (the currently executing cmd script), CMD returns the drive (d) and path (p). This way, you can start the .cmd script from anywhere in the file system, the only requirement is that the .cmd and .ps1 scripts are in the same directory.

Conclusion

Since the script updates, and not simply appends, the nodes to the machine.config files, it is safe to include in regular deploys. The only thing it doesn't do, is to delete from machine.configs.

Complete scripts

Updatemachineconfig.ps1

#
# UpdateMachineConfig.ps1
# Adds certain elements from config files that contain only additions to a machine.config file,
# to a machine.config file.
# The file with the additions must have the same XML structure as a machine.config file.
#
# Syntax:
#   UpdateMachineConfig.ps1 machine.config addto.machine.config
# where
#   machine.config        machine.config file to update
#   addto.machine.config  File name with additions to machine.config
#
# Example:
# c:\> Powershell .\UpdateMachineConfig.ps1 c:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config "d:\Deliveries\1.34.1.1\some\application\addto.machine.config.txt"
#
# Returns:
# 0 on success
# 1 on error, such as cannot write to the file
# 2 on bad syntax
# 3 on file reading error, i.e. non-existing or not XML
#
 
###
### Helper functions:
###
function CreateExtensionNode ([System.Xml.XmlDocument]$xmlDoc, [string]$name, [string]$type)
{
    [System.Xml.XmlNode]$node = $xmlDoc.CreateElement("add");
    [System.Xml.XmlAttribute]$attrName = $xmlDoc.CreateAttribute("name");
    $attrName.InnerText = $name;
    [System.Xml.XmlAttribute]$attrType = $xmlDoc.CreateAttribute("type");
    $attrType.InnerText = $type;
    [void]$node.Attributes.Append($attrName);
    [void]$node.Attributes.Append($attrType);
    return $node;
}
 
function MergeExtensionNode([string]$extensionNodeName)
{
    [string]$configPath = "/configuration/system.serviceModel/extensions/" + $extensionNodeName;
    [System.Xml.XmlNode]$machineconfigExtnsNode = $machineconfig.SelectSingleNode($configPath);
    [System.Xml.XmlNodeList]$additionconfigExtnsNodes = $additionconfig.SelectNodes($configPath + "/*");
    if (($additionconfigExtnsNodes -ne $null) -and ($machineconfigExtnsNode -ne $null))
    {
        Write-Output "  $extensionNodeName"
        foreach ($additionconfigNode in $additionconfigExtnsNodes)
        {
            [System.Xml.XmlNode]$newNode = CreateExtensionNode $machineconfig $additionconfigNode.name $additionconfigNode.type
            $nodePath = $configPath + "/add[@name='"  + $additionconfigNode.name + "']";
            [string]$currNodename = $additionconfigNode.name;
            # Check if node already exists in machine.config:
            $machineconfigCurrentNode = $machineconfig.SelectSingleNode($nodePath);
            if ($machineconfigCurrentNode -ne $null)
            {
                # It did indeed exist, replace it:
                [void]$machineconfigExtnsNode.ReplaceChild($newNode, $machineconfigCurrentNode);
                Write-Output "    $currNodename - replaced"
            }
            else
            {
                # Create it: (AppendChild adds the node last)
                [void]$machineconfigExtnsNode.InsertBefore($newNode, $machineconfigExtnsNode.FirstChild);
                Write-Output "    $currNodename - created"
            }
        }
    }
}
 
###
### Main processing:
###
if ($args.Length -ne 2)
{
    Write-Output "Syntax: UpdateMachineConfig.ps1 machine.config addto.machine.config.txt";
    Exit 2;
}
[string]$machineConfigFile = $args[0];
[string]$additionconfigFile = $args[1];
 
if ([System.IO.File]::Exists($machineConfigFile) -and [System.IO.File]::Exists($additionconfigFile))
{
    Write-Output "" "Processing $machineConfigFile with $additionconfigFile";
    [System.Xml.XmlDocument]$machineconfig  = new-object System.Xml.XmlDocument;
    [System.Xml.XmlDocument]$additionconfig = new-object System.Xml.XmlDocument;
    $machineconfig.Load($machineConfigFile);
    $additionconfig.Load($additionconfigFile);
    if (($machineconfig -ne $null) -and ($additionconfig -ne $null))
    {
        MergeExtensionNode "behaviorExtensions";
        MergeExtensionNode "bindingElementExtensions";
        # Overwrite machine.config with resulting document:
        $machineconfig.Save($machineConfigFile);
    }
    else
    {
        Write-Output "Some of the files could not be read, is not XML, or are empty.";
        Exit 3;
    }
}
else
{
    if ([System.IO.File]::Exists($machineConfigFile) -eq $false)
    {
        Write-Output "$machineConfigFile does not exist, or is not readable.";
    }
    if ([System.IO.File]::Exists($additionconfigFile -eq $false))
    {
        Write-Output "$additionconfigFile  does not exist, or is not readable.";
    }
    Exit 3;
}

Updatemachineconfigs.cmd

@echo off
rem
rem UpdateMachineConfigs.cmd
rem
rem Updates (merges) local machine.configs (both 32- och 64-bit) with content from another file with the same structure.
rem If a folder is given instead, the command is run for all files in that folder.
rem 
rem If a node with the same name attribute already exists, it is replaced.
rem
 
if "%1"=="" goto Syntax
if not exist %1 goto FileNotFound
 
set machineconfig32=C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config
set machineconfig64=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
 
if exist %1\. goto IsDirectory
 
:IsSingleFile
powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig32% %1
powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig64% %1
goto End
 
:IsDirectory
for %%f in (%1) do (
  powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig32% %%f
  powershell -f %~dp0\UpdateMachineConfig.ps1 %machineconfig64% %%f
)
goto End
 
:FileNotFound
echo.
echo Cannot find file/folder: %1
goto End
 
:Syntax
echo.
echo Updates (merges) local machine.configs (both 32- och 64-bit) with content from another file with the same structure.
echo If a folder is given instead, the command is run for all files in that folder.
echo.
echo %0 filename
echo %0 foldername
echo.
 
:End