Pentia.Invoke-ConfigurationTransform.psm1
<#
.SYNOPSIS Determines the location of a configuration file in a target directory based on the location of a XDT file in a source directory. .DESCRIPTION The location of the "configuration target file" is determined based on: EITHER the location of the transform file relative to the "App_Config" directory, OR in case of files named "Web.config", "Web.Feature.MyProject.config" etc., defaults to "<WebrootOutputPath>\Web.config". E.g.: Given the following project folder structure: "C:\MySolution\src\Foundation\MyProject\App_Config\MyConfig.Debug.config" "C:\MySolution\src\Foundation\MyProject\Web.MyProject.Debug.config" ... and given the webroot "C:\MyWebsite\www" ... the XDT file "MyConfig.Debug.config" would match the configuration file: "C:\MyWebsite\www\App_Config\MyConfig.config" ... and the XDT file "Web.MyProject.Debug.config" would match the configuration file: "C:\MyWebsite\www\Web.config" .PARAMETER ConfigurationTransformFilePath E.g. "C:\MySolution\src\Foundation\Code\App_Config\Sitecore\Include\MyConfig.Debug.config". .PARAMETER WebrootOutputPath E.g. "D:\websites\MySolution\www". #> function Get-PathOfFileToTransform { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string]$ConfigurationTransformFilePath, [Parameter(Mandatory = $true)] [string]$WebrootOutputPath ) $WebrootOutputPath = $WebrootOutputPath.Trim([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) $nameOfFileToTransform = Get-NameOfFileToTransform -ConfigurationTransformFilePath $ConfigurationTransformFilePath if ($nameOfFileToTransform -eq "Web.config") { Write-Verbose "Using path of the main 'Web.config' file." $pathOfFileToTransform = [System.IO.Path]::Combine($WebrootOutputPath, "Web.config") } elseif ($nameOfFileToTransform -imatch "Web\..*\.config") { Write-Verbose "Using path of the main 'Web.config' file, by convention." $pathOfFileToTransform = [System.IO.Path]::Combine($WebrootOutputPath, "Web.config") } elseif ($ConfigurationTransformFilePath.ToUpperInvariant().StartsWith($WebrootOutputPath.ToUpperInvariant())) { Write-Verbose "Resolving path to the matching configuration file within the same webroot." $configurationTransformDirectoryPath = [System.IO.Path]::GetDirectoryName($ConfigurationTransformFilePath) if ($configurationTransformDirectoryPath.ToUpperInvariant() -eq $WebrootOutputPath.ToUpperInvariant()) { $pathOfFileToTransform = [System.IO.Path]::Combine($WebrootOutputPath, $nameOfFileToTransform) } else { $relativePath = $configurationTransformDirectoryPath.Substring($WebrootOutputPath.Length + [IO.Path]::DirectorySeparatorChar.ToString().Length) $pathOfFileToTransform = [System.IO.Path]::Combine($WebrootOutputPath, $relativePath, $nameOfFileToTransform) } } else { Write-Verbose "Resolving path to the matching configuration file in 'App_Config'." $relativeConfigurationDirectory = Get-RelativeConfigurationDirectory -ConfigurationTransformFilePath $ConfigurationTransformFilePath $pathOfFileToTransform = [System.IO.Path]::Combine($WebrootOutputPath, $relativeConfigurationDirectory, $nameOfFileToTransform) } Write-Verbose "Found matching configuration file '$pathOfFileToTransform' for configuration transform '$ConfigurationTransformFilePath'." $pathOfFileToTransform } function Get-RelativeConfigurationDirectory { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$ConfigurationTransformFilePath ) # "C:\MySite\App_Config\Sitecore\Include\Pentia\Sites.Debug.Config" -> "C:\MySite\App_Config\Sitecore\Include\Pentia" $directoryName = [System.IO.Path]::GetDirectoryName($ConfigurationTransformFilePath) $configurationDirectoryName = "App_Config" $configurationDirectoryIndex = $DirectoryName.IndexOf($configurationDirectoryName, [System.StringComparison]::InvariantCultureIgnoreCase) if ($configurationDirectoryIndex -lt 0) { throw "Can't determine relative configuration directory. '$configurationDirectoryName' not found in path '$ConfigurationTransformFilePath'." } # "C:\MySite\App_Config\Sitecore\Include\Pentia" -> "App_Config\Sitecore\Include\Pentia" $directoryName.Substring($configurationDirectoryIndex) } function Get-NameOfFileToTransform { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string]$ConfigurationTransformFilePath ) # "C:\MySite\App_Config\Sitecore\Include\MyConfig.Debug.Config" -> "MyConfig.Debug.Config" $fileName = [System.IO.Path]::GetFileName($ConfigurationTransformFilePath) $fileNamePartSeparator = "." # ["MyConfig", "Debug", "config"] [System.Collections.ArrayList]$fileNameParts = $fileName.Split($fileNamePartSeparator) $buildConfigurationIndex = $fileNameParts.Count - 2 if ($buildConfigurationIndex -lt 1) { throw "Can't determine file to transform based on file name '$fileName'. The file name must follow the convention 'my.file.name.<BuildConfiguration>.config', e.g. 'Solr.Index.Debug.config'." } # ["MyConfig", "Debug", "config"] -> ["MyConfig", "config"] $fileNameParts.RemoveAt($buildConfigurationIndex) # ["MyConfig", "config"] -> "MyConfig.config" [string]::Join($fileNamePartSeparator, $fileNameParts.ToArray()) } <# .SYNOPSIS Applies a configuration transform to a configuration file, and returns the result. .PARAMETER XmlFilePath The path to the configuration file. .PARAMETER XdtFilePath The path to the configuration transform file. .EXAMPLE Invoke-ConfigurationTransform -XmlFilePath "C:\Solution\src\Web.config" -XdtFilePath "C:\Solution\src\Web.Debug.config" | Set-Content "C:\Website\Web.config" Note the call to "Set-Content", to use the resulting output for something meaningful. #> function Invoke-ConfigurationTransform { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$XmlFilePath, [Parameter(Mandatory = $true)] [string]$XdtFilePath ) if (!(Test-Path -Path $XmlFilePath -PathType Leaf)) { throw "File '$XmlFilePath' not found." } if (!(Test-Path -Path $XdtFilePath -PathType Leaf)) { throw "File '$XdtFilePath' not found." } if (-not ([System.IO.Path]::IsPathRooted($XmlFilePath))) { $XmlFilePath = [System.IO.Path]::Combine($PWD, $XmlFilePath) } if (-not ([System.IO.Path]::IsPathRooted($XdtFilePath))) { $XdtFilePath = [System.IO.Path]::Combine($PWD, $XdtFilePath) } Add-Type -LiteralPath "$PSScriptRoot\lib\Microsoft.Web.XmlTransform.dll" -ErrorAction Stop $xmlDocument = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument $xmlDocument.PreserveWhitespace = $true $xmlDocument.Load($XmlFilePath) $transformation = New-Object Microsoft.Web.XmlTransform.XmlTransformation($XdtFilePath) $errorMessage = "Transformation of '$XmlFilePath' failed using transform '$XdtFilePath'." try { if ($transformation.Apply($xmlDocument) -eq $false) { $exception = New-Object "System.InvalidOperationException" -ArgumentList $errorMessage throw $exception } } catch [Microsoft.Web.XmlTransform.XmlNodeException] { $innerException = $_.Exception $errorMessage = $errorMessage + " See the inner exception for details." $exception = New-Object "System.InvalidOperationException" -ArgumentList $errorMessage, $innerException throw $exception } $stringWriter = New-Object -TypeName "System.IO.StringWriter" $xmlTextWriter = [System.Xml.XmlWriter]::Create($stringWriter) $xmlDocument.WriteTo($xmlTextWriter) $xmlTextWriter.Flush() $transformedXml = $stringWriter.GetStringBuilder().ToString() $xmlTextWriter.Dispose() $stringWriter.Dispose() $transformedXml.Trim() } <# .SYNOPSIS Applies all configuration transforms found under the specified root path, matching a certain build configuration. By convention, XDTs ending with ".Always.config" are always applied. .PARAMETER SolutionOrProjectRootPath The root path from which to fetch XDT files. If null or empty, $WebrootOutputPath is used. .PARAMETER WebrootOutputPath The root path in which to search for configuration files. .PARAMETER BuildConfiguration The build configuration for which to apply transforms. .EXAMPLE Invoke-AllConfigurationTransforms -SolutionOrProjectRootPath "C:\MySolution\src\MyProject" -WebrootOutputPath "C:\Websites\MySolution\www" -BuildConfiguration "Debug" #> function Invoke-AllConfigurationTransforms { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$SolutionOrProjectRootPath, [Parameter(Mandatory = $true)] [string]$WebrootOutputPath, [Parameter(Mandatory = $true)] [string]$BuildConfiguration ) if ([string]::IsNullOrEmpty($SolutionOrProjectRootPath)) { $SolutionOrProjectRootPath = $WebrootOutputPath } $xdtFiles = @(Get-ConfigurationTransformFile -SolutionRootPath $SolutionOrProjectRootPath -BuildConfigurations "Always", $BuildConfiguration) for ($i = 0; $i -lt $xdtFiles.Count; $i++) { Write-Progress -Activity "Configuring web solution" -PercentComplete ($i / $xdtFiles.Count * 100) -Status "Applying XML Document Transforms" -CurrentOperation "$xdtFile" $xdtFile = $xdtFiles[$i] $fileToTransform = Get-PathOfFileToTransform -ConfigurationTransformFilePath $xdtFile -WebrootOutputPath $WebrootOutputPath [xml]$xml = Invoke-ConfigurationTransform -XmlFilePath $fileToTransform -XdtFilePath $xdtFile $xml.Save($fileToTransform.ToString()) } } Export-ModuleMember -Function Get-PathOfFileToTransform, Invoke-ConfigurationTransform, Invoke-AllConfigurationTransforms |