UPDATE: The source and installer for this task can now be found at Codeplex:
http://powershellmsbuild.codeplex.com/
I have written an MSBuild task that hosts a Powershell runspace and allows you to embed Powershell scripts in your MSBuild build files. While MSBuild ships with a large number of useful tasks (and there are also a lot of third party tasks available), there is often something you want to do which doesn’t fit into the tasks provided. You can always write your own tasks to achieve what you want, but this can often become tedious to maintain since you will have to recompile and redeploy the custom task every time you need to make a little change. Having small snippets of script embedded in the MSBuild file itself can be a more easily maintainable solution, and what better language for this than Powershell?
As a custom task, the Powershell task needs to be loaded into your build file with the <UsingTask> element:
<UsingTask AssemblyFile="PowershellMSBuildTask.dll" TaskName ="Powershell"/>
Since tasks in MSBuild don’t have access to the contents of their tags, the scripts themselves need to be MSBuild properties, declared in a <PropertyGroup>:
<PropertyGroup> <HelloWorldScript>Write-Host "Hello world"</HelloWorldScript> </PropertyGroup>
Now, to invoke this script, you will use the Powershell task and invoke it from within a target. You reference the script property the same way you reference any other MSBuild script property:
<Target Name="HelloWorld"> <Powershell Script="$(HelloWorldScript)"/> </Target>
The full MSBuild file will now be something like this:
<?xml version="1.0" encoding="utf-8" ?> <Project DefaultTargets="HelloWorld" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask AssemblyFile="PowershellMSBuildTask.dll" TaskName ="Powershell"/> <PropertyGroup> <HelloWorldScript>Write-Host "Hello world"</HelloWorldScript> </PropertyGroup> <Target Name="HelloWorld"> <Powershell Script="$(HelloWorldScript)"/> </Target> </Project>
Save it as HelloWorld.proj, place PowershellMSBuildTask.dll in the same directory and run it like this:
msbuild HelloWorld.proj
The output should be as follows:
PS D:tmp> msbuild HelloWorld.proj Microsoft (R) Build Engine Version 2.0.50727.42 [Microsoft .NET Framework, Version 2.0.50727.42] Copyright (C) Microsoft Corporation 2005. All rights reserved. Build started 13.09.2006 19:02:21. __________________________________________________ Project "D:tmpHelloWorld.proj" (default targets): Target HelloWorld: Hello world Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:05.09
Not horribly exciting, but it works.
Of course, such a task would have limited usefullness if there wasn’t a way to pass arguments from MSBuild into the custom script. The Powershell task has two ways of doing this.
The first way is an attribute ‘Arguments’. This takes in named arguments on the form ‘varname1=value1;varname2=value2’. varname1 and varname2 will then be available as string variables inside the script. For example:
<PropertyGroup> <HelloArgumentsScript><![CDATA[ Write-Host ("{0}" -f (([int]$value1) + ([int]$value2))) ]]></HelloArgumentsScript> </PropertyGroup> <Target Name="HelloArguments"> <Powershell Script="$(HelloArgumentsScript)" Arguments="value1=40;value2=2"/> </Target>
The output is as expected:
Project "D:tmpHelloWorld.proj" (HelloArguments target(s)): Target HelloArguments: 42 Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:02.78
The other way is through the task attribute ‘TaskItems’. As the name implies, this takes in a set of taskitem objects. They are available in the Powershell script as a collection of ITaskItem objects named $TaskItems:
<PropertyGroup> <TaskItemsScript> <![CDATA[ Write-Host ("The answer is {0}" -f (([int]$taskitems[0].ItemSpec) + ([int]$taskitems[1].ItemSpec))) ]]> </TaskItemsScript> </PropertyGroup> <PropertyGroup> <Argument1>12</Argument1> <Argument2>30</Argument2> </PropertyGroup>
<Target Name="TaskItems"> <Powershell Script="$(TaskItemsScript)" TaskItems="$(Argument1);$(Argument2)"> </Target>
It is also possible for a script to return values to the calling task. This happens through the Output taskparameter, and works like this:
<Powershell Script="$(ValueReturningScript)"> <Output TaskParameter="Output" PropertyName="ScriptOutput"/> </Powershell> <Message Lines="$(ScriptOutput)"/>
The source code can be found in a Subversion repository here, and a downloadable zip including both source and the prebuilt task can be found here.