Last active
March 3, 2026 00:45
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Get-XdtNodeXPath { | |
| param([System.Xml.XmlNode]$Node) | |
| $parts = @($Node.LocalName) | |
| $current = $Node | |
| while ($current.ParentNode -and $current.ParentNode.NodeType -ne [System.Xml.XmlNodeType]::Document) { | |
| $current = $current.ParentNode | |
| $parts = @($current.LocalName) + $parts | |
| } | |
| return "/" + ($parts -join "/") | |
| } | |
| 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 = Get-XdtNodeXPath $transformNode | |
| $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((Get-XdtNodeXPath $transformNode.ParentNode)) | |
| } | |
| 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((Get-XdtNodeXPath $transformNode.ParentNode)) | |
| $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) | |
| $path = if ($transformType.Value -match '^InsertBefore\((.+)\)$') { $matches[1] } else { $null } | |
| $beforeNode = if ($path) { $document.SelectSingleNode($path) } else { $null } | |
| $newNode = $document.ImportNode($transformNode, $true) | |
| $transformNode.Attributes | ForEach-Object { | |
| if ($_.Name -like "xdt:*") { | |
| $newNode.RemoveAttribute($_.Name) > $null | |
| } | |
| } | |
| if ($beforeNode) { | |
| $parentNode = $beforeNode.ParentNode | |
| Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) before $($beforeNode.LocalName)" -ForegroundColor Blue | |
| $parentNode.InsertBefore($newNode, $beforeNode) > $null | |
| } else { | |
| # Note: This behavior deviates from the original Xml Transform library. | |
| # If the element does not exist, the library just errors out. | |
| # Here we go ahead and append it because that's what the user likely wanted anyways. | |
| $parentNode = $document.SelectSingleNode((Get-XdtNodeXPath $transformNode.ParentNode)) | |
| if (!$parentNode) { | |
| Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow | |
| continue | |
| } | |
| Write-Host "- [InsertBefore] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName)" -ForegroundColor Blue | |
| $parentNode.AppendChild($newNode) > $null | |
| } | |
| } | |
| "InsertAfter*" { | |
| #Example: InsertAfter(/configuration/system.webServer) | |
| $path = if ($transformType.Value -match '^InsertAfter\((.+)\)$') { $matches[1] } else { $null } | |
| $afterNode = if ($path) { $document.SelectSingleNode($path) } else { $null } | |
| $newNode = $document.ImportNode($transformNode, $true) | |
| $transformNode.Attributes | ForEach-Object { | |
| if ($_.Name -like "xdt:*") { | |
| $newNode.RemoveAttribute($_.Name) > $null | |
| } | |
| } | |
| if ($afterNode) { | |
| $parentNode = $afterNode.ParentNode | |
| Write-Host "- [InsertAfter] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) after $($afterNode.LocalName)" -ForegroundColor Blue | |
| $parentNode.InsertAfter($newNode, $afterNode) > $null | |
| } else { | |
| $parentNode = $document.SelectSingleNode((Get-XdtNodeXPath $transformNode.ParentNode)) | |
| if (!$parentNode) { | |
| Write-Host "- [InsertAfter] $($transformNode.ParentNode.LocalName)/$($transformNode.LocalName) skipped" -ForegroundColor Yellow | |
| continue | |
| } | |
| 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 | |
| $rawList = $transformType.Value.Replace("RemoveAttributes","").Replace("(","").Replace(")","").Trim() | |
| if ([string]::IsNullOrEmpty($rawList)) { | |
| $nodeToModify.Attributes.RemoveAll() > $null | |
| } else { | |
| $rawList.Split(",") | ForEach-Object { | |
| $nodeToModify.Attributes.Remove($nodeToModify.Attributes[$_]) > $null | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } catch { | |
| Write-Error "Error applying XDT transform: $_" | |
| return $Base | |
| } | |
| return $document | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| . .\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 | |
| # Test_InsertBefore_CrossSection | |
| # Reproduces Bug 1: the first //add in the document is under <connectionStrings>, but the | |
| # InsertBefore XPath targets a node under <appSettings>. Without the fix this throws because | |
| # <connectionStrings> is used as $parentNode while the reference node lives under <appSettings>. | |
| [xml]$insertBeforeCrossSectionBaseXml = @" | |
| <?xml version="1.0"?> | |
| <configuration> | |
| <connectionStrings> | |
| <add name="Db" connectionString="" /> | |
| </connectionStrings> | |
| <appSettings> | |
| <add key="Env" value="Dev" /> | |
| <add key="Version" value="1" /> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| [xml]$insertBeforeCrossSectionTransformXml = @" | |
| <?xml version="1.0"?> | |
| <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> | |
| <appSettings> | |
| <add key="NewSetting" value="X" | |
| xdt:Transform="InsertBefore(/configuration/appSettings/add[@key='Version'])"/> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| $testExpectedXml = Invoke-XdtTransform -Base $insertBeforeCrossSectionBaseXml.OuterXml -Transform $insertBeforeCrossSectionTransformXml.OuterXml | |
| $testActualXml = Merge-XdtTransform -Base $insertBeforeCrossSectionBaseXml -Transform $insertBeforeCrossSectionTransformXml | |
| Assert-AreEqual -TestName "Test_InsertBefore_CrossSection" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null | |
| # Test_InsertAfter_CrossSection | |
| # Same cross-section parent mismatch scenario for InsertAfter (Bug 2). | |
| [xml]$insertAfterCrossSectionBaseXml = @" | |
| <?xml version="1.0"?> | |
| <configuration> | |
| <connectionStrings> | |
| <add name="Db" connectionString="" /> | |
| </connectionStrings> | |
| <appSettings> | |
| <add key="Env" value="Dev" /> | |
| <add key="Version" value="1" /> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| [xml]$insertAfterCrossSectionTransformXml = @" | |
| <?xml version="1.0"?> | |
| <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> | |
| <appSettings> | |
| <add key="NewSetting" value="X" | |
| xdt:Transform="InsertAfter(/configuration/appSettings/add[@key='Env'])"/> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| $testExpectedXml = Invoke-XdtTransform -Base $insertAfterCrossSectionBaseXml.OuterXml -Transform $insertAfterCrossSectionTransformXml.OuterXml | |
| $testActualXml = Merge-XdtTransform -Base $insertAfterCrossSectionBaseXml -Transform $insertAfterCrossSectionTransformXml | |
| Assert-AreEqual -TestName "Test_InsertAfter_CrossSection" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null | |
| # Test_RemoveAttributes_All | |
| # Reproduces Bugs 3 & 4: RemoveAttributes with no argument list must call RemoveAll() | |
| # on the correct node. The old code never reached RemoveAll() because Split(",") on an | |
| # empty string produces @("") which is truthy, and it also referenced $nodesToModify instead | |
| # of $nodeToModify. | |
| [xml]$removeAttributesAllBaseXml = @" | |
| <?xml version="1.0"?> | |
| <configuration> | |
| <system.web> | |
| <compilation debug="true" strict="true" batch="true" /> | |
| </system.web> | |
| </configuration> | |
| "@ | |
| [xml]$removeAttributesAllTransformXml = @" | |
| <?xml version="1.0"?> | |
| <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> | |
| <system.web> | |
| <compilation xdt:Transform="RemoveAttributes" /> | |
| </system.web> | |
| </configuration> | |
| "@ | |
| $testExpectedXml = Invoke-XdtTransform -Base $removeAttributesAllBaseXml.OuterXml -Transform $removeAttributesAllTransformXml.OuterXml | |
| $testActualXml = Merge-XdtTransform -Base $removeAttributesAllBaseXml -Transform $removeAttributesAllTransformXml | |
| Assert-AreEqual -TestName "Test_RemoveAttributes_All" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null | |
| # Test_InsertBefore_MissingRef | |
| # When the reference node in InsertBefore(...) does not exist in the base document the | |
| # operation should fall back to AppendChild on the declared parent element. | |
| [xml]$insertBeforeMissingRefBaseXml = @" | |
| <?xml version="1.0"?> | |
| <configuration> | |
| <appSettings> | |
| <add key="Env" value="Dev" /> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| [xml]$insertBeforeMissingRefTransformXml = @" | |
| <?xml version="1.0"?> | |
| <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> | |
| <appSettings> | |
| <add key="NewSetting" value="X" | |
| xdt:Transform="InsertBefore(/configuration/appSettings/add[@key='DoesNotExist'])"/> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| $testExpectedXml = Invoke-XdtTransform -Base $insertBeforeMissingRefBaseXml.OuterXml -Transform $insertBeforeMissingRefTransformXml.OuterXml | |
| $testActualXml = Merge-XdtTransform -Base $insertBeforeMissingRefBaseXml -Transform $insertBeforeMissingRefTransformXml | |
| Assert-AreEqual -TestName "Test_InsertBefore_MissingRef" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null | |
| # Test_InsertBefore_FunctionXPath | |
| # Reproduces Bug 5: the old Replace(")","") strips ALL closing parentheses, corrupting any | |
| # XPath that contains a function call such as contains(). The regex fix captures only the | |
| # outermost group so inner parentheses are preserved. | |
| [xml]$insertBeforeFunctionXPathBaseXml = @" | |
| <?xml version="1.0"?> | |
| <configuration> | |
| <appSettings> | |
| <add key="Env-Dev" value="1" /> | |
| <add key="Env-Prod" value="2" /> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| [xml]$insertBeforeFunctionXPathTransformXml = @" | |
| <?xml version="1.0"?> | |
| <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> | |
| <appSettings> | |
| <add key="NewKey" value="X" | |
| xdt:Transform="InsertBefore(/configuration/appSettings/add[contains(@key,'Prod')])"/> | |
| </appSettings> | |
| </configuration> | |
| "@ | |
| $testExpectedXml = Invoke-XdtTransform -Base $insertBeforeFunctionXPathBaseXml.OuterXml -Transform $insertBeforeFunctionXPathTransformXml.OuterXml | |
| $testActualXml = Merge-XdtTransform -Base $insertBeforeFunctionXPathBaseXml -Transform $insertBeforeFunctionXPathTransformXml | |
| Assert-AreEqual -TestName "Test_InsertBefore_FunctionXPath" -Expected $testExpectedXml.OuterXml -Actual $testActualXml.OuterXml > $null |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Found this neat website to perform transforms in the browser. Convenient to compare with results of this script.
Xdt Playground