Skip to content

Instantly share code, notes, and snippets.

@michaellwest
Last active February 22, 2025 07:38
Show Gist options
  • Save michaellwest/ad10b91ff6650f32171cc4db0262b5ff to your computer and use it in GitHub Desktop.
Save michaellwest/ad10b91ff6650f32171cc4db0262b5ff to your computer and use it in GitHub Desktop.
Pure PowerShell script to apply an XDT transform with a typicaly web.config found in ASP.Net.
function Merge-XdtTransform {
<#
.SYNOPSIS
A function to apply Xml Configuration Transforms without the need for Microsoft.Web.XmlTransform.dll.
.NOTES
Michael West
.EXAMPLE
Merge-XdtTransform -Base $xmlBase -Transform $xmlTransform
#>
param(
[Parameter(Mandatory)]
[ValidateNotNull()]
[xml]$Base,
[Parameter(Mandatory)]
[ValidateNotNull()]
[xml]$Transform
)
$document = $Base.Clone()
try {
Write-Host "Processing Xdt Transforms"
#https://learn.microsoft.com/en-us/previous-versions/aspnet/dd465326(v=vs.110)
$namespaceManager = New-Object System.Xml.XmlNamespaceManager($transform.NameTable)
$namespaceManager.AddNamespace("xdt", "http://schemas.microsoft.com/XML-Document-Transform")
foreach ($transformNode in $transform.SelectNodes("//*[@xdt:Transform]", $namespaceManager)) {
$transformType = $transformNode.Attributes["xdt:Transform"]
$nodesToModify = & {
$xpath = "//$($transformNode.LocalName)"
$locator = $transformNode.Attributes["xdt:Locator"]
if($locator -and $locator.Value -like "Match*") {
#Locator="Match(comma-delimited list of one or more attribute names)"
#xdt:Locator="Match(key)"
$matchAttributeNames = $locator.Value.Replace("Match(","").Replace(")","").Split(",")
foreach($matchAttributeName in $matchAttributeNames) {
$matchAttributeValue = $transformNode.Attributes[$matchAttributeName].Value
$xpath += "[@$($matchAttributeName)='$($matchAttributeValue)']"
}
$document.SelectNodes($xpath)
} elseif($locator -and $locator.Value -like "XPath*") {
#Locator="XPath(XPath expression)"
#xdt:Locator="XPath(configuration/connectionStrings/add[@name='PaymentsContext'])"
$xpath = $locator.Value.Replace("XPath(","").Replace(")","")
$document.SelectNodes($xpath)
} elseif($locator -and $locator.Value -like "Condition*") {
#Locator="Condition(XPath expression)"
#xdt:Locator="Condition(@key='IsStaging')"
if ($locator.Value -match "Condition\((.+)\)") {
$condition = $matches[1]
$xpath += "[$($condition)]"
$document.SelectNodes($xpath)
}
} elseif (!$locator) {
$document.SelectSingleNode($xpath)
}
}
$keepProcessing = $true
$nodeQueue = New-Object System.Collections.Queue
@($nodesToModify) | ForEach-Object { $nodeQueue.Enqueue($_) > $null }
while($keepProcessing) {
$nodeToModify = if($nodeQueue.Count -gt 0) { $nodeQueue.Dequeue() } else { $null }
if($nodeQueue.Count -eq 0) {
$keepProcessing = $false
}
switch -Wildcard ($transformType.Value) {
"CommentOut" {
if(!$nodeToModify) {
Write-Host "- [CommentOut] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$parentNode = $nodeToModify.ParentNode
$comment = [string]::Concat(" ", $nodeToModify.OuterXml, " ")
$newNode = $document.CreateComment($comment)
Write-Host "- [CommentOut] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.InsertAfter($newNode, $nodeToModify) > $null
$parentNode.RemoveChild($nodeToModify) > $null
}
"SetAttributes*" {
if(!$nodeToModify) {
Write-Host "- [SetAttributes] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
Write-Host "- [SetAttributes] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$attributesToSet = $transformType.Value.Replace("SetAttributes","").Replace("(","").Replace(")","").Split(",", [System.StringSplitOptions]::RemoveEmptyEntries)
$attributeLookup = New-Object System.Collections.Generic.HashSet[string](@(,$attributesToSet))
$transformNode.Attributes | ForEach-Object {
if ($_.Name -notlike "xdt:*" -and ($attributeLookup.Count -eq 0 -or $attributeLookup.Contains($_.Name))) {
$nodeToModify.SetAttribute($_.Name, $_.Value) > $null
}
}
}
"Replace" {
if(!$nodeToModify) {
Write-Host "- [Replace] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$parentNode = $nodeToModify.ParentNode
$newNode = $document.ImportNode($transformNode, $true)
$transformNode.Attributes | ForEach-Object {
if ($_.Name -like "xdt:*") {
$newNode.RemoveAttribute($_.Name) > $null
}
}
Write-Host "- [Replace] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.ReplaceChild($newNode, $nodeToModify) > $null
}
"Insert" {
$parentNode = $nodeToModify.ParentNode
if(!$parentNode) {
#Node is missing so let's get the parent.
$parentNode = $document.SelectSingleNode("//$($transformNode.ParentNode.LocalName)")
}
if(!$parentNode) {
Write-Host "- [Insert] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$newNode = $document.ImportNode($transformNode, $true)
$transformNode.Attributes | ForEach-Object {
if ($_.Name -like "xdt:*") {
$newNode.RemoveAttribute($_.Name) > $null
}
}
Write-Host "- [Insert] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.AppendChild($newNode) > $null
}
"InsertIfMissing" {
if($nodeToModify) {
Write-Host "- [InsertIfMissing] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$parentNode = $document.SelectSingleNode("//$($transformNode.ParentNode.LocalName)")
$newNode = $document.ImportNode($transformNode, $true)
$transformNode.Attributes | ForEach-Object {
if ($_.Name -like "xdt:*") {
$newNode.RemoveAttribute($_.Name) > $null
}
}
Write-Host "- [InsertIfMissing] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.AppendChild($newNode) > $null
}
"InsertBefore*" {
#Example: InsertBefore(/configuration/system.web/membership)
$parentNode = $nodeToModify.ParentNode
if(!$parentNode) {
#Node is missing so let's get the parent.
$parentNode = $document.SelectSingleNode("//$($transformNode.ParentNode.LocalName)")
}
if(!$parentNode) {
Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$path = $transformType.Value.Replace("InsertBefore(","").Replace(")","")
$beforeNode = $document.SelectSingleNode($path)
$newNode = $document.ImportNode($transformNode, $true)
$transformNode.Attributes | ForEach-Object {
if ($_.Name -like "xdt:*") {
$newNode.RemoveAttribute($_.Name) > $null
}
}
if ($beforeNode) {
Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) before $($beforeNode.LocalName)" -ForegroundColor Blue
$parentNode.InsertBefore($newNode, $beforeNode) > $null
} else {
Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.AppendChild($newNode) > $null
}
}
"InsertAfter*" {
#Example: InsertAfter(/configuration/system.webServer)
$parentNode = $nodeToModify.ParentNode
if(!$parentNode) {
#Node is missing so let's get the parent.
$parentNode = $document.SelectSingleNode("//$($transformNode.ParentNode.LocalName)")
}
if(!$parentNode) {
Write-Host "- [InsertAfter] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$path = $transformType.Value.Replace("InsertAfter(","").Replace(")","")
$afterNode = $document.SelectSingleNode($path)
$newNode = $document.ImportNode($transformNode, $true)
$transformNode.Attributes | ForEach-Object {
if ($_.Name -like "xdt:*") {
$newNode.RemoveAttribute($_.Name) > $null
}
}
if ($afterNode) {
Write-Host "- [InsertAfter] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) after $($afterNode.LocalName)" -ForegroundColor Blue
$parentNode.InsertAfter($newNode, $afterNode) > $null
} else {
Write-Host "- [InsertAfter] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.AppendChild($newNode) > $null
}
}
"Remove" {
if(!$nodeToModify) {
Write-Host "- [Remove] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
$parentNode = $nodeToModify.ParentNode
Write-Host "- [Remove] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$parentNode.RemoveChild($nodeToModify) > $null
}
"RemoveAll" {
# TODO: Should RemoveAll consider the element type?
$parentNode = $nodeToModify.ParentNode
if(!$parentNode) {
Write-Host "- [RemoveAll] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
Write-Host "- [RemoveAll] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
if($transformNode.Attributes["xdt:Locator"]) {
$parentNode.RemoveChild($nodeToModify) > $null
} else {
$matchingNodes = $parentNode.SelectNodes("$($transformNode.LocalName)")
$matchingNodes | ForEach-Object { $parentNode.RemoveChild($_) > $null }
#$parentNode.RemoveAll() > $null
}
}
"RemoveAttributes*" {
if(!$nodeToModify) {
Write-Host "- [RemoveAttributes] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow
continue
}
Write-Host "- [RemoveAttributes] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue
$attributesToRemove = $transformType.Value.Replace("RemoveAttributes","").Replace("(","").Replace(")","").Split(",")
if(!$attributesToRemove) {
$nodesToModify.Attributes.RemoveAll() > $null
} else {
@($attributesToRemove) | ForEach-Object {
$nodeToModify.Attributes.Remove($nodeToModify.Attributes[$_]) > $null
}
}
}
}
}
}
} catch {
Write-Error "Error applying XDT transform: $_"
return $Base
}
return $document
}
. .\Merge-XdtTransform.ps1
$XdtDllPath = ".\Microsoft.Web.XmlTransform.dll"
Add-Type -Path $XdtDllPath
function Invoke-XdtTransform {
param(
[string]$Base,
[string]$Transform,
[ref]$Output
)
$stringReader = New-Object System.IO.StringReader($Base)
$xmlReader = [System.Xml.XmlReader]::Create($stringReader)
$target = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument
$target.PreserveWhitespace = $true
$target.Load($xmlReader)
$transformation = New-Object Microsoft.Web.XmlTransform.XmlTransformation($Transform, $false, $null);
if (!$transformation.Apply($target))
{
return $Base
}
$stringWriter = New-Object System.IO.StringWriter
$xmlWriter = New-Object System.XMl.XmlTextWriter $stringWriter
$target.WriteContentTo($xmlWriter)
$xmlWriter.Flush()
$stringWriter.Flush()
return [xml]($stringWriter.ToString())
}
[xml]$xmlBase = @"
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
https://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<section name="sitecore" type="Sitecore.Configuration.RuleBasedConfigReader, Sitecore.Kernel" />
</configSections>
<connectionStrings>
<add name="WebContext" connectionString="" providerName="System.Data.SqlClient" />
<add name="PaymentsContext" connectionString="" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings file="Base.config">
<add key="Environment" value="" />
<add key="IsTestMode" value="true" />
<add key="IsStaging" value="true" />
<add key="IsDisabled" value="true" />
</appSettings>
<rewrite>
<rules>
<rule />
<rule />
</rules>
</rewrite>
<system.web>
<compilation defaultLanguage="c#" debug="false" targetFramework="4.8" />
<membership defaultProvider="sitecore" hashAlgorithmType="SHA1">
</membership>
<compilation>
<assemblies>
</assemblies>
</compilation>
<application coldstart="true" user="root" />
</system.web>
<system.webServer>
<urlCompression doDynamicCompression="true" />
<httpCookies httpOnlyCookies="true" requireSSL="false" />
</system.webServer>
</configuration>
"@
[xml]$xmlTransform = @"
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<configSections>
<section xdt:Transform="Replace" xdt:Locator="Match(name)" name="sitecore" type="Scms.Foundation.Configuration.EnvironmentVariablesConfigReader, Scms.Foundation"/>
</configSections>
<connectionStrings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="WebContext" connectionString="data source=CHS-CADEVDB01;initial catalog=Web;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" />
<add xdt:Transform="Replace" xdt:Locator="XPath(configuration/connectionStrings/add[@name='PaymentsContext'])" name="PaymentsContext" connectionString="data source=CHS-CADEVDB01;initial catalog=Payments;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" />
</connectionStrings>
<appSettings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(key)" key="Environment" value="Development" />
<add xdt:Transform="CommentOut" xdt:Locator="Match(key)" key="IsTestMode" />
<add xdt:Locator="Condition(@key='IsStaging' or @key='IsDisabled')" xdt:Transform="Remove"/>
</appSettings >
<rewrite>
<rules>
<rule xdt:Transform="RemoveAll" />
</rules>
</rewrite>
<system.web>
<machineKey xdt:Transform="Remove" />
<machineKey xdt:Transform="InsertBefore(/configuration/system.web/membership)" validationKey="42F21E06B91BC207DAFFF53FDD44375515938CCEF1BF7F2CA70052B3B024C009B385A6E86D816ED1B0144D7BDEAEEECD9C14B20BBD02A29B1C0B1683493497D6" decryptionKey="DF97132E23EE6B974E7EC9FA3701B71BC4EBA440F1A56C744179529786C6EE58" validation="SHA1" decryption="AES" />
<compilation>
<assemblies xdt:Locator="Condition(count(*) = 0)" xdt:Transform="Remove"/>
</compilation>
<application coldstart="true" user="root" xdt:Transform="Remove" />
<compilation xdt:Locator="Condition(count(*) = 0)" xdt:Transform="Remove" />
<application xdt:Transform="Replace" user="guest" type="web" />
</system.web>
<location xdt:Transform="InsertAfter(/configuration/system.webServer)" path="sitecore">
<system.web>
<authorization>
<deny users="*"/>
</authorization>
</system.web>
</location>
<system.webServer>
<urlCompression xdt:Transform="Insert" doDynamicCompression="true" />
<httpCookies xdt:Transform="InsertIfMissing" httpOnlyCookies="true" requireSSL="true" />
</system.webServer>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</configuration>
"@
function Format-Xml ([xml]$xml, $indent=2) {
$StringWriter = New-Object System.IO.StringWriter
$XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter
$xmlWriter.Formatting = "indented"
$xmlWriter.Indentation = $Indent
$xml.WriteContentTo($XmlWriter)
$XmlWriter.Flush()
$StringWriter.Flush()
Write-Output $StringWriter.ToString()
}
#if(Merge-XdtTransform -Base $xmlBase -Transform $xmlTransform) {
# Format-Xml -Xml $xmlBase
#}
function Assert-AreEqual {
param(
[string]$TestName,
[object]$Expected,
[object]$Actual
)
$sb = New-Object System.Text.StringBuilder
if ($Expected -ne $Actual) {
$sb.AppendLine("[$($TestName)] Failed") > $null
$sb.AppendLine("Expected") > $null
$sb.AppendLine((Format-Xml -Xml $Expected)) > $null
$sb.AppendLine("Actual") > $null
$sb.AppendLine((Format-Xml -Xml $Actual)) > $null
Write-Host $sb.ToString() -ForegroundColor Red -BackgroundColor White
return $false
} else {
$sb.AppendLine("[$($TestName)] Succeeded") > $null
Write-Host $sb.ToString() -ForegroundColor Green
}
return $true
}
[xml]$replaceTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" strict="true" batch="true" />
<application coldstart="true" user="root">
<subtag>old content</subtag>
<subtag2>old content</subtag2>
</application>
</system.web>
</configuration>
"@
[xml]$replaceTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
<!-- Replaces the whole tag -->
<application xdt:Transform="Replace" user="guest" type="web" >
<sub>new content</sub>
</application>
</system.web>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $replaceTestBaseXml.OuterXml -Transform $replaceTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $replaceTestBaseXml -Transform $replaceTestTransformXml
Assert-AreEqual -TestName "Test_Replace" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$removeTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="Version" value="14" />
</appSettings>
</configuration>
"@
[xml]$removeTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="Environment"
xdt:Locator="Match(key)" xdt:Transform="Remove" />
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $removeTestBaseXml.Clone().OuterXml -Transform $removeTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $removeTestBaseXml -Transform $removeTestTransformXml
Assert-AreEqual -TestName "Test_Remove" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$removeAllTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="Version" value="14" />
<object key="" />
</appSettings>
</configuration>
"@
[xml]$removeAllTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add xdt:Transform="RemoveAll" />
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $removeAllTestBaseXml.OuterXml -Transform $removeAllTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $removeAllTestBaseXml -Transform $removeAllTestTransformXml
Assert-AreEqual -TestName "Test_RemoveAll" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$removeAllMatchTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="SiteStyle" value="dark" />
<add key="Environment" value="Prod" />
<add key="Version" value="14" />
<add key="Environment" value="Staging" />
</appSettings>
</configuration>
"@
[xml]$removeAllMatchTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="Environment"
xdt:Locator="Match(key)" xdt:Transform="RemoveAll"/>
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $removeAllMatchTestBaseXml.OuterXml -Transform $removeAllMatchTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $removeAllMatchTestBaseXml -Transform $removeAllMatchTestTransformXml
Assert-AreEqual -TestName "Test_RemoveAll_Match" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$insertTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
</appSettings>
</configuration>
"@
[xml]$insertTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="SiteStyle" value="dark"
xdt:Locator="Match(key)" xdt:Transform="Insert" />
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $insertTestBaseXml.OuterXml -Transform $insertTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $insertTestBaseXml -Transform $insertTestTransformXml
Assert-AreEqual -TestName "Test_Insert" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$insertIfMissingTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
</appSettings>
</configuration>
"@
[xml]$insertIfMissingTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<!-- Will not be inserted since already existing in the source-->
<add key="Environment" value="Prod"
xdt:Locator="Match(key)" xdt:Transform="InsertIfMissing" />
<add key="SiteStyle" value="Dark"
xdt:Locator="Match(key)" xdt:Transform="InsertIfMissing" />
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $insertIfMissingTestBaseXml.OuterXml -Transform $insertIfMissingTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $insertIfMissingTestBaseXml -Transform $insertIfMissingTestTransformXml
Assert-AreEqual -TestName "Test_InsertIfMissing" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$insertBeforeTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="Version" value="14" />
</appSettings>
</configuration>
"@
[xml]$insertBeforeTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="SiteStyle" value="dark"
xdt:Transform="InsertBefore(/configuration/appSettings/add[@key='Environment'])"/>
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $insertBeforeTestBaseXml.OuterXml -Transform $insertBeforeTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $insertBeforeTestBaseXml -Transform $insertBeforeTestTransformXml
Assert-AreEqual -TestName "Test_InsertBefore" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$insertAfterTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="Version" value="14" />
</appSettings>
</configuration>
"@
[xml]$insertAfterTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="SiteStyle" value="dark"
xdt:Transform="InsertAfter(/configuration/appSettings/add[@key='Environment'])"/>
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $insertAfterTestBaseXml.OuterXml -Transform $insertAfterTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $insertAfterTestBaseXml -Transform $insertAfterTestTransformXml
Assert-AreEqual -TestName "Test_InsertAfter" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$commentOutTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="Version" value="14" />
</appSettings>
</configuration>
"@
[xml]$commentOutTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="Environment"
xdt:Locator="Match(key)" xdt:Transform="CommentOut" />
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $commentOutTestBaseXml.OuterXml -Transform $commentOutTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $commentOutTestBaseXml -Transform $commentOutTestTransformXml
Assert-AreEqual -TestName "Test_CommentOut" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$removeAttributesTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<system.web>
<application coldstart="true" user="root" />
<compilation debug="true" strict="true" batch="true" />
</system.web>
</configuration>
"@
[xml]$removeAttributesTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
<!-- Remove all attributes-->
<application xdt:Transform="RemoveAttributes" />
<!-- Remove specific attributes-->
<compilation xdt:Transform="RemoveAttributes(debug,strict)" />
</system.web>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $removeAttributesTestBaseXml.OuterXml -Transform $removeAttributesTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $removeAttributesTestBaseXml -Transform $removeAttributesTestTransformXml
Assert-AreEqual -TestName "Test_RemoveAttributes" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$setAttributesTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<system.web>
<application coldstart="true" user="root" />
<compilation debug="true" strict="true" batch="true" />
</system.web>
</configuration>
"@
[xml]$setAttributesTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
<!-- Add or updates all attributes-->
<application user="guest" newAttribute="value"
xdt:Transform="SetAttributes" />
<!-- Add or updates specific attributes-->
<compilation debug="false" strict="false" batch="false"
xdt:Transform="SetAttributes(debug,strict)" />
</system.web>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $setAttributesTestBaseXml.OuterXml -Transform $setAttributesTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $setAttributesTestBaseXml -Transform $setAttributesTestTransformXml
Assert-AreEqual -TestName "SetAttributes_Element" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
[xml]$setAttributesMatchTestBaseXml = @"
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="IsStaging" value="True" />
<add key="Environment" value="Dev" />
<add key="SiteStyle" value="dark" />
</appSettings>
</configuration>
"@
[xml]$setAttributesMatchTestTransformXml = @"
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="Environment" value="Prod"
xdt:Locator="Match(key)" xdt:Transform="SetAttributes"/>
</appSettings>
</configuration>
"@
$testExpectedXml = Invoke-XdtTransform -Base $setAttributesMatchTestBaseXml.OuterXml -Transform $setAttributesMatchTestTransformXml.OuterXml
$testActualXml = Merge-XdtTransform -Base $setAttributesMatchTestBaseXml -Transform $setAttributesMatchTestTransformXml
Assert-AreEqual -TestName "Test_SetAttributesMatch" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null
# TODO: Add tests for Condition and XPath
@michaellwest
Copy link
Author

Found this neat website to perform transforms in the browser. Convenient to compare with results of this script.

Xdt Playground

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment