Newsletter
RSS

Blog posts tagged with 'PowerShell'

Building NHunspell with PowerShell Build Tools

The PowerShell Build Tools are a free toolbox for build, test and deployment automation. The Build Tools combine XML configuration and PowerShell scripting in a new way to get the best of both worlds. NHunspell is a free wrapper for the Open Office Spell Checker Hunspell. Although NHunspell is a small project, it has a rather complex build and deployment workflow due to its native assemblies. We want to make this a bit easier so we switched the NHunspell build process to our new PowerShell based Build Tools. This is a real word use case which demonstrates a lot of the features.

NHunspell Build and Deployment

These are the steps that should be performed during a NHunspell build:

  1. Version Update
    Update all version strings in the solution to match the current build
  2. Compile the 32Bit Native DLL
  3. Compile the 64Bit Native DLL
  4. Compile the NHunspell Assembly
  5. Check the Files
    Check if all files are compiled and have the correct version resource
  6. Run the Unit Tests
  7. Create Zip Files
  8. Create NuGet Packages
  9. Test Deploy the Packages

So theses steps are automated using the PowerShell Build Tools.

PowerShell Build Tools in Action

 In this Video we show how NHunspell is build from PowerShell, within Visual Studio and on a Jenkins Build Server. It also shows how the build configuration can be edited and debugged.

Watch directtly on Youtube unter  Building the NHunspell Spell Checker with PowerShell Build Tools

 Build Configuration

The PowerShell Build Tools use a XML configuration file with the default name BuildConfig.xml. Here are the most important parts from the NHunspell  BuildConfig.xml:

XML Declaration

<?xml version="1.0" encoding="utf-8"?>
<!-- Build configuration for NHunspell -->
<BuildConfig xmlns="http://www.crawler-lib.net/build-tools"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.crawler-lib.net/build-tools http://download.crawler-lib.net/BuildTools/BuildConfig.xsd?action=view">

This XML declaration links to the current build tools schema definition and can be used to enable IntelliSense in Visual Studio.

Build Labeling

<Label>
  <BuildRevisionTimestamp/>
</Label>

The Build is labeled with the Build/Revision Timestamp tool. It works the same way as assemblies are versioned when *.* is specified as build and revision numbers.

Credentials for the Build Steps

<Credentials>
<Plain>
NuGetFeed=http://buildserver:8050/nuget/Test
NuGetApiKey=b763316f-25c2-4d17-bf0a-1c22e071eb05
</Plain>
</Credentials>

The credentials section allows to specify credentials for the use in the build steps later.

Solutions and Versions

<Solutions>
  <!-- The NHunspell solution to build, not to be confused with a visual studio solution -->
  <Solution Name="NHunspell">
  <Version>
    <AppendLabel>1.2</AppendLabel>
  </Version>
  ...

The PowerShell Build Tools are able to build a bunch of solutions during one build. The typical use case for this are solutions that need to have the same build label, but use different versioning. The versioning tool is specified for each solution and in ths sase it appends simply the build label to a fully qualified version.

    Build Sequences

<BuildSequences>

The build sequences are collections of build steps that can be executed to get to a certain stage in the build process. The PowerShell Build Tools use an incremental approach to get a build done. There is a special sequence called “Clear-Build” that clears the current build and forces an completely new build.

New-Build Sequence  

<BuildSequence Name="New-Build">
<!-- Patching the version of the projects to the current build version -->
<Autover Path="..HunspellWindowsHunspellWindows.sln" />
<Autover Path="NHunspell.sln" />
<!-- Compiling the native hunspell DLLs for Windows -->
<MSBuild Path="..HunspellWindowsHunspellWindows.sln" Configuration="Release" Platform="x86" />
<MSBuild Path="..HunspellWindowsHunspellWindows.sln" Configuration="Release" Platform="x64" />
<!-- Compiling NHunspell itself -->
<MSBuild Path="NHunspell.sln" Configuration="Release" Platform="Any CPU" />
<!-- Test if all files exist, have the correct file and product versions and are newly build --> 
<VerifyFile Path="UnitTestsbinreleaseHunspellx86.dll" FileVersion="true" ProductVersion="true" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseHunspellx86.pdb" FileVersion="false" ProductVersion="false" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseHunspellx64.dll" FileVersion="true" ProductVersion="true" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseHunspellx64.pdb" FileVersion="false" ProductVersion="false" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseNHunspell.dll" FileVersion="true" ProductVersion="true" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseNHunspell.pdb" FileVersion="false" ProductVersion="false" New="true"/>
<VerifyFile Path="UnitTestsbinreleaseUnitTests.exe" FileVersion="true" ProductVersion="true" New="true"/> 
</BuildSequence>

The NHunspell build uses the New-Build Sequence to patch the versions of all projects in the solutions with the Autover tool. After that the native DLLs and NHunspell is compiled using MSBuild. Least it is checked if the generated files are new and if they have the correct version.  

Test-Build Sequence

<BuildSequence Name="Test-Build" Depends="New-Build" NewBuild="false">
<!-- Performing several tests with the new files -->
<NUnit Path="UnitTestsbinreleaseUnitTests.exe" />
<FxCop Path="UnitTestsbinreleaseNHunspell.dll" /> 
</BuildSequence> 

The Test-Build Sequence runs NUnit and FxCop to test NHunspell. The Test-Build sequence depends on the New-Build sequence. If the build is clear, the Build Tools will execute the New-Build sequence first. If there is a current build with a successful New-Build sequence, the Test-Build sequence will use the current build and perform the tests on the already available build results. This concept allows to modularize the steps and cuts down the time needed to develop a certain build sequence.  

Complete-Build Sequence

<BuildSequence Name="Complete-Build" Depends="Test-Build" NewBuild="false">
<!-- Make a zipped release package -->
<Zip Path="UnitTestsbinreleaseHunspellx86.dll" Target="Hunspellx86.dll" Output="NHunspell.$($context.PackageVersion).zip" />
<Zip Path="UnitTestsbinreleaseHunspellx86.pdb" Target="Hunspellx86.pdb" Output="NHunspell.$($context.PackageVersion).zip" />
<Zip Path="UnitTestsbinreleaseHunspellx64.dll" Target="Hunspellx64.dll" Output="NHunspell.$($context.PackageVersion).zip" />
<Zip Path="UnitTestsbinreleaseHunspellx64.pdb" Target="Hunspellx64.pdb" Output="NHunspell.$($context.PackageVersion).zip" />
<Zip Path="UnitTestsbinreleaseNHunspell.dll" Target="NHunspell.dll" Output="NHunspell.$($context.PackageVersion).zip" />
<Zip Path="UnitTestsbinreleaseNHunspell.pdb" Target="NHunspell.pdb" Output="NHunspell.$($context.PackageVersion).zip" />
<!-- Write Release Info file --> 
<AppendText Output="NHunspell.$($context.PackageVersion).zip.info.xml">
<![CDATA[<?xml version=`"1.0`" encoding=`"utf-8`" ?>
<infos>
<summary>NHunspell Release Version $($context.Version)</summary>
<description>
<a href=`"http://www.crawler-lib.net/nhunspell`">NHunspell</a> Release Version $($context.Version)
</description>
</infos>]]>
</AppendText>
<!-- Update the version and dependencies versions in the NuSpec file -->
<NuSpecUpdate>
<NuSpec Path ="NHunspell.nuspec" />
</NuSpecUpdate>
<!-- Pack the NuGet package -->
<NuGetPack Path ="NHunspell.nuspec"/> 
</BuildSequence> 

The Complete-Build sequence depends on the Test-Build sequence, so the build can only be completed if successfully tested. In the Complete-Build sequence the Zip- and NuGet packages are created. As you can see some string values  contain PowerShell code which is escaped with the dollar sign ($) as usual in PowerShell. 

Publish-Build Sequence

<!-- Publishes the previously created build -->
<BuildSequence Name="Publish-Build" Depends="Test-Build" NewBuild="false">
<NuGetPush Path ="NHunspell.$($context.PackageVersion).nupkg" ApiKey="$($context.Credentials.NuGetApiKey)" Feed="$($context.Credentials.NuGetFeed)"/>
</BuildSequence> 
</BuildSequences>
</Solution>
</Solutions>
</BuildConfig> 

The Publish-Build sequence performs a publish step on our internal test NuGet feed.

Executing and Debugging XSL Transformations in PowerShell

Unfortunaly the is no Cmdlet to execute XSL Transformations in PowerShell. During the development of the Xslt build step of the Crawler-Lib Build-Tools I had to deal with this. This is a small post about executing and debugging XSLT in PowerShell. 

Executing XSLT in PowerShell

The implementation of the XSLT build step shows how to pass parameters and activate debugging prior to the execution of the XSL Transformation:  

function Invoke-BuildStep_Xslt_Tool($context)
{
  $path = Expand-ParameterString $context.Step.Path
  $template = Expand-ParameterString $context.Step.Template
  $output = Expand-ParameterString $context.Step.Output
  if( ! (test-path $path )) { Throw"XML input file not found: $path"}
  $path = resolve-path $path 
  if( ! (test-path $template )) { Throw"XSL template file not found: $template"}
  $template = resolve-path $template 
  $output = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine((Get-Location), $output))
  if( [System.Diagnostics.Debugger]::IsAttached )
  {
    $xslt = New-Object System.Xml.Xsl.XslCompiledTransform( $true )
  }
  else
  {
    $xslt = New-Object System.Xml.Xsl.XslCompiledTransform( $false )
  }
  $arglist = new-object System.Xml.Xsl.XsltArgumentList
  $arglist.AddParam("Config", "", $context.Config)
  $arglist.AddParam("CurrentBuild", "", $context.CurrentBuild)
  $arglist.AddParam("BuildCreatedUtc", "", $context.BuildCreatedUtc)
  $arglist.AddParam("BuildCreatedLocal", "", $context.BuildCreatedLocal)
  $arglist.AddParam("BuildSequenceName", "", $context.BuildSequenceName)
  $arglist.AddParam("Step", "", $context.Step)
  $arglist.AddParam("StepTool", "", $context.StepTool)
  $arglist.AddParam("StepNumber", "", $context.StepNumber)
  $arglist.AddParam("Version", "", $context.Version)
  $arglist.AddParam("FileVersion", "", $context.FileVersion)
  $arglist.AddParam("ProductVersion", "", $context.ProductVersion)
  $arglist.AddParam("PackageVersion", "", $context.PackageVersion)
  foreach( $param in $context.Step.Param )
  {
    $paramName = Expand-ParameterString $param.Name
    $paramNamespaceUri = Expand-ParameterString $param.NamespaceUri
    $paramValue = Expand-ParameterString $param.Value
    $arglist.AddParam($paramName, $paramNamespaceUri, $paramValue)
  }
  $xsltSettings = New-Object System.Xml.Xsl.XsltSettings($false,$true)
  $xslt.Load($template, $xsltSettings, (New-Object System.Xml.XmlUrlResolver))
  $outFile = New-Object System.IO.FileStream($output, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
  $xslt.Transform($path, $arglist, $outFile)
  $outFile.Close()
}

Debugging XSLT in PowerShell

Debugging of XSLT in PowerShell is possible with Visual Studio. The trick is to attach the PowerShell ISE or the PowerShell Command Processor to the Visual Studio debugger. This is simply be done by “Attach to Process …” under the “Debug” menu. After that the XSLT file must be loaded in Visual Studio. Then breakpoints can be added. After that preparation the PowerShell script and functions can be executed. When the  $xslt.Transform method is called the Visual Studio debugger will break on every breakpoint in the XSLT file.