Web.Config transforms outside of Microsoft MSBuild?
Asked Answered
Z

8

61

Is it possible to use Microsoft's XML document transform, for preparing web.configs, outside of MSBuild? I would like to use PowerShell to do these transform without having to run this through the MSBuild engine. If Microsoft had used standard XSLT it would be easy to do in PowerShell. From what I can tell I have to use their C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll which requires a build engine. Thanks

Zephyrus answered 24/1, 2012 at 15:49 Comment(0)
D
111

I created a small function to handle Microsoft's XML Document Transform in PowerShell.

I copied the Microsoft.Web.XmlTransform.dll file from Visual Studio build folder to my script's path, but you can reference it from the source folder if you'd like.

function XmlDocTransform($xml, $xdt)
{
    if (!$xml -or !(Test-Path -path $xml -PathType Leaf)) {
        throw "File not found. $xml";
    }
    if (!$xdt -or !(Test-Path -path $xdt -PathType Leaf)) {
        throw "File not found. $xdt";
    }

    $scriptPath = (Get-Variable MyInvocation -Scope 1).Value.InvocationName | split-path -parent
    Add-Type -LiteralPath "$scriptPath\Microsoft.Web.XmlTransform.dll"

    $xmldoc = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument;
    $xmldoc.PreserveWhitespace = $true
    $xmldoc.Load($xml);

    $transf = New-Object Microsoft.Web.XmlTransform.XmlTransformation($xdt);
    if ($transf.Apply($xmldoc) -eq $false)
    {
        throw "Transformation failed."
    }
    $xmldoc.Save($xml);
}

To transform web.config using web.release.config:

XmlDocTransform -xml "Web.config" -xdt "Web.Release.config"

Alternatively, you can use Sayed's self-bootstraping Xml Transform script, which will take care of getting the Microsoft.Xml.Xdt.dll for you:

https://gist.github.com/sayedihashimi/f1fdc4bfba74d398ec5b

Donkey answered 28/5, 2013 at 18:0 Comment(5)
I have a self-bootstrapping powershell script you can use at gist.github.com/sayedihashimi/f1fdc4bfba74d398ec5bSilvana
very nice! I had to call Resolve-Path on $xml and $xdt but apart from that, it's very useful!, thxYogurt
minor correction of syntax calling the function: XmlDocTransform "Web.config" "Web.Release.config"Sou
@SayedIbrahimHashimi I added your script to the answer, as its incredibly useful and I only found it after I did a bunch of work myself.Eyeglasses
For those struggling to find Microsoft.Web.XmlTransform.dll - it is now available as nuget package nuget.org/packages/Microsoft.Web.Xdt - you can download it, unpack by hand and use the dll.Sugarplum
S
14

The logic of the transformation is contained inside of the TransformXml task itself. If you want to call it from code you would have to use the MSBuild API with a mock engine and execute it. I have some code for this if you want.

In your case since you mentioned PowerShell the best thing for you to do is to just create a wrapper MSBuild file to invoke the TransformXml task. I say this because PowerShell is configured to run under .NET 2.0, but the TransformXml task requires .NET 4.0. In order to call it from a dummy MSBuild file you can check my blog at http://sedodream.com/2010/04/26/ConfigTransformationsOutsideOfWebAppBuilds.aspx, but I've also pasted a sample from that link below.

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask TaskName="TransformXml"
             AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

    <Target Name="Demo">
        <TransformXml Source="app.config"
                      Transform="Transform.xml"
                      Destination="app.prod.config"/>
    </Target>
</Project>

For mono, this should work (tested on mono 6.4, macos, 2019) :

<Project DefaultTargets="TransformConfig" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
    AssemblyFile="$(MSBuildSDKsPath)/Microsoft.NET.Sdk.Publish/tools/net46/Microsoft.NET.Sdk.Publish.Tasks.dll"/>
  <PropertyGroup>
    <TransformSource>Web.config</TransformSource>
    <Transformer>Web.Live.config</Transformer>
    <Destination>Output.Web.config</Destination>
  </PropertyGroup>
  <Target Name="TransformConfig">
    <Message Text="From TransformSource : $(TransformSource)" />
    <Message Text="Using Transform : $(Transformer)" />
    <Message Text="Output : $(Destination)" />
    <Message Text="MSBuildSDKsPath=$(MSBuildSDKsPath)" Condition="'$(MSBuildSDKsPath)' != ''" />
    <TransformXml Source="$(TransformSource)" Transform="$(Transformer)" Destination="$(Destination)"/>
  </Target>
</Project>

which you can run with just msbuild or supply parameters with

msbuild /p:TransformSource=... /p:Transformer=...
Silvana answered 24/1, 2012 at 17:8 Comment(1)
Thanks Sayed, I actually went with your solution. I did modify it slightly to include property groups for the 3 items, so I could override those on the command line from PowerShell. Learned that from your book :) <PropertyGroup> <TransformSource>Web.config</TransformSource> <Transformer>Web.Debug.config</Transformer> <TransformDest>Web.Prod.config</TransformDest> </PropertyGroup> <Target Name="TransConfig"> <TransformXml Source="$(TransformSource)" Transform="$(Transformer)" Destination="$(TransformDest)"/> </Target>Zephyrus
M
11

Based on Michel's answer I wrote a C# function that will accomplish the same.

Of course you could invoke the resultant DLL with PowerShell, but I was actually looking for a fully programatic version, so here it is, in case anybody else is looking for similar solution:

using Microsoft.Web.XmlTransform;

...

public static void TransformConfig(string configFileName, string transformFileName)
{
     var document = new XmlTransformableDocument();
     document.PreserveWhitespace = true;
     document.Load(configFileName);

     var transformation = new XmlTransformation(transformFileName);
     if (!transformation.Apply(document))
     {
         throw new Exception("Transformation Failed");
     }
     document.Save(configFileName);
}

You will just need to include reference to following:

C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.XmlTransform.dll

Marnie answered 2/2, 2014 at 23:18 Comment(1)
You can now add a NuGet package instead of referencing the Visual Studio DLL: nuget.org/packages/Microsoft.Web.XdtConcuss
S
10

Microsoft has posted XDT to codeplex http://xdt.codeplex.com and as a NuGet package https://www.nuget.org/packages/Microsoft.Web.Xdt/. I have also created a NuGet pig with an MSBuild task, TransformXml, and an .exe to invoke them https://www.nuget.org/packages/SlowCheetah.Xdt/1.1.6-beta.

For PowerShell I've created a self-bootstrapping script which you can use https://gist.github.com/sayedihashimi/f1fdc4bfba74d398ec5b.

More about self bootstrapping scripts at http://sedodream.com/2014/07/22/StopCheckinginBinariesInsteadCreateSelfbootstrappingScripts.aspx.

Silvana answered 25/7, 2014 at 8:17 Comment(0)
I
7

I updated the script a bit to make it work with the latest version of powershell and make it a bit easier.

function XmlDocTransform($xml, $xdt)
{
      $scriptpath = $PSScriptRoot + "\"
      $xmlpath = $scriptpath + $xml
      $xdtpath = $scriptpath + $xdt

      if (!($xmlpath) -or !(Test-Path -path ($xmlpath) -PathType Leaf)) {
         throw "Base file not found. $xmlpath";
      }

      if (!($xdtpath) -or !(Test-Path -path ($xdtpath) -PathType Leaf)) {
         throw "Transform file not found. $xdtpath";
      }

      Add-Type -LiteralPath "$PSScriptRoot\Microsoft.Web.XmlTransform.dll"

      $xmldoc = New-Object   Microsoft.Web.XmlTransform.XmlTransformableDocument;
      $xmldoc.PreserveWhitespace = $true
      $xmldoc.Load($xmlpath);

      $transf = New-Object Microsoft.Web.XmlTransform.XmlTransformation($xdtpath);
      if ($transf.Apply($xmldoc) -eq $false)
      {
          throw "Transformation failed."
      }
      $xmldoc.Save($xmlpath);

      Write-Host "Transformation succeeded" -ForegroundColor Green
  }

And to invoke the function use

 XmlDocTransform "App.config" "App.acc.config"
Implosion answered 11/10, 2016 at 6:40 Comment(0)
R
5

Take a looked at using MSDeploy since it has PowerShell scripting APIs that allow you to transform and deploy your package.

You can also look at XML-Document-Transform which if you wanted to you can write your own code to perform the Transform.

Here is a codeplex project that did something similar.XDT Transformation Tool

Rawinsonde answered 24/1, 2012 at 17:15 Comment(0)
W
0

So extended slightly to work recursively

    function XmlDocTransform($xml, $xdt)
    {
        if (!$xml -or !(Test-Path -path $xml -PathType Leaf)) {
            throw "File not found. $xml";
        }
        if (!$xdt -or !(Test-Path -path $xdt -PathType Leaf)) {
            throw "File not found. $xdt";
        }
        $scriptPath = (Get-Variable MyInvocation -Scope 1).Value.InvocationName | split-path -parent
        Add-Type -LiteralPath "$scriptPath\Microsoft.Web.XmlTransform.dll"
        $xmldoc = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument;
        $xmldoc.PreserveWhitespace = $true
        $xmldoc.Load($xml);
        $transf = New-Object Microsoft.Web.XmlTransform.XmlTransformation($xdt);
        if ($transf.Apply($xmldoc) -eq $false)
        {
            throw "Transformation failed."
        }
        $xmldoc.Save($xml);
    }
    function DoConfigTransform($webFolder, $environment)
    {
        $allConfigFiles = Get-ChildItem $webFolder -File -Filter *.config -Recurse
          $transformFiles = $allConfigFiles | Where-Object {$_.Name -like ("*." + $environment + ".config")} | %{$_.fullname}
          ForEach($item in $transformFiles)
          {
            $origFile = $item -replace("$environment.",'')
              XmlDocTransform -xml $origFile -xdt $origFile$item
              #Write-Output ("orig = " + $origFile + ", transform = " + $item)
          }
          cd C:\WebApplications\xxx\xxx\xxx\
          .\PostDeploy.ps1
    }
    DoConfigTransform -webFolder "C:\WebApplications\xxx\xxx\xxx" -environment "xx-xxx-xx"

So the DoConfigTransform logic goes:

  • Find all config files recursively
  • Find all transform templates for the environment we are in #passed in as a parameter
  • For each transform file, find the corresponding config
  • Then do transform
  • Code runs a postdeploy script to remove all unwanted transform files.
Weintraub answered 7/3, 2019 at 15:18 Comment(0)
L
0

I came to this post well after it original posting but it helped resolve my issue so I thought I'd put my solution (based on the solution above) here for anyone having the same problem with a current visual studio / msbuild install.

Currently in VS build time transforms are done using the SlowCheetah nuget pkg. If you can rely on this package in your project you can use the script I have placed below and it will automatically find the required assembly based on the installed cheetah version and perform your transform as needed.

Hope this helps someone.

param(  
  [ValidateScript({$(Test-Path $_) -eq $true})]
  [string] $NuGetRootPath,

  [ValidateScript({$(Test-Path $_) -eq $true})]
  [string] $BaseFile,

  [ValidateScript({$(Test-Path $_) -eq $true})]
  [string] $TransformFile,

  [string] $TargetPath
)

"[INFO] Creating Custom XML Transform..." | Out-Default   
"[INFO] ==> Source:    $BaseFile" | Out-Default   
"[INFO] ==> Transform: $TransformFile" | Out-Default   
"[INFO] ==> Target:    $TargetPath" | Out-Default   

$cheetahDir = Join-Path $NuGetRootPath *SlowCheetah* | Resolve-Path | Select-Object -Last 1 -ExpandProperty Path
$xformDll = Join-Path $cheetahDir "tools\Microsoft.Web.XmlTransform.dll" | Resolve-Path | Select-Object -ExpandProperty Path
Add-Type -LiteralPath $xformDll

$xmldoc = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument;
$xmldoc.PreserveWhitespace = $true
$xmldoc.Load($BaseFile);

"[INFO] Running Transform..." | Out-Default
$transf = New-Object Microsoft.Web.XmlTransform.XmlTransformation($TransformFile);
if ($transf.Apply($xmldoc) -eq $false) {
    throw "[ERROR] Transformation failed."
}

$xmldoc.Save($TargetPath);
"[INFO] Transformation Complete..." | Out-Default
Lamp answered 28/4, 2020 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.