Saturday, February 16, 2008 2:22 AM bart

Invoking PowerShell scripts from MSBuild

I'm a firm believer of the "innovation through integration" theme. As a fan of MSBuild and PowerShell I wondered what it would take to bring the two worlds closer together. This post outlines the result of a short but 'powerful' experiment. In order to read this post, I strongly recommend to check out my recent post named The custom MSBuild task cookbook to learn about writing and debugging custom MSBuild tasks.

 

Hosting PowerShell

In order to run PowerShell in a customized environment (as opposed to the default shell that comes with the technology) one needs to work with runspaces. Essentially a runspace allows to host the PowerShell engine and interact with it through pipelines. We'll only cover very basic communication in this post. If one wants to feed back data from PowerShell to the MSBuild output for instance, a PSHost implementation would be required but that goes far beyond the scope of this post.

I've posted about runspaces earlier in my A first introduction to Windows PowerShell runspaces post about one year ago. You might want to check out that post for more information on hosting PowerShell.

 

Introducing PSBuild

Far from original, I admit, but let's call our baby PSBuild. In order to implement it, create a new class library project (C#) and add references to the following assemblies:

image

Including the MSBuild assemblies has been covered in the The custom MSBuild task cookbook post; for more information on the System.Management.Automation assembly, see my Easy Windows PowerShell cmdlet development and debugging post (step 2).

 

Implementing the task

First on our to-do list is implementing the custom MSBuild task in the C# code file. It's barely 45 lines:

using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace PSBuild
{
    public class InvokeScript : Task
    {
        [Required]
        public ITaskItem Script { get; set; }

        [Required]
        public string Function { get; set; }
        public ITaskItem[] Parameters { get; set; }

        public override bool Execute()
        {
            RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create();

            using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfig))
            {
                runspace.Open();

                StringBuilder commandLine = new StringBuilder();
                commandLine.Append(Function + " ");

                foreach (ITaskItem parameter in Parameters)
                {
                    commandLine.AppendFormat("\"{0}\" ", parameter.ItemSpec);
                }

                using (RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace))
                {
                    scriptInvoker.Invoke(Script.ItemSpec);
                    scriptInvoker.Invoke(commandLine.ToString());
                }
            }

            return true;
        }
    }
}

Usual disclaimers apply - this code is far from ideal and is just meant to illustrate the concept. Talking about a concept... Let's discuss:

  • We require two parameters: Script containing a PowerShell script block and Function pointing to the function to be invoked. The Parameters for invocation are optional since a function may have no parameters obviously.
  • Notice the type of the Script and Parameters properties. By using ITaskItem we can integrate nicely with MSBuild as we'll see further.
  • The Execute method does all the work. Essentially we create a Runspace and invoke two commands: first is the Script definition itself, second is the invocation of the script based on the Function and Parameters values.
  • Error handling was omitted from the code above - a production quality implementation needs to catch errors and return false in case of an error. Also, some logging (Log property on Task) would be welcome, e.g. to print the command-line that's being invoked (tip: Log.LogCommandLine).

 

Testing the task

Check out my The custom MSBuild task cookbook post for instructions on testing custom MSBuild tasks. I'll just show a sample MSBuild file below that invokes a script:

image

The UsingTask imports our task library built in the previous step. To define the script, we simply define a MyScript tag under a PropertyGroup element. In here we define a function called "ProcessList" that takes in two arguments. I've spread it across two lines to show that local variables (and in extension to that - try it yourself :-) - more advanced scripting techniques) simply work. Finally, we invoke our InvokeScript task somewhere, in this case in the Debug target (again, see The custom MSBuild task cookbook for more info) but you could imagine it to be part of your core build definition. The InvokeScript task references the MyScript through the property reference syntax $(...) of MSBuild; the Function is a simple string and in Parameters we put a semi-colon separated list of parameters which will be assigned $args[0] ... $args[n] in the invoked PowerShell script.

One could imagine the parameterization of the InvokeScript task to be much more complete and flexible (e.g. one could drop the Function attribute and simply execute some script) but that's just a matter of implementation. Also a way to feed back results isn't too difficult (RunspaceInvoke::Invoke returns a collection of PSObjects).

Here's what it does:

image

Notice that one can use any MSBuild variable in the parameterization which gives us a tremendous amount of power. For example, one could write a script that pre-processes all files in @(Compile), and leverage all of the PowerShell and .NET Framework power to do so. I leave it to the reader to experiment with the possibilities.

 

Happy PSBuilding!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# » Daily Bits - February 18, 2008 Alvin Ashcraft’s Daily Geek Bits: Daily links, development, gadgets and raising rugrats.

Pingback from  » Daily Bits - February 18, 2008 Alvin Ashcraft’s Daily Geek Bits: Daily links, development, gadgets and raising rugrats.

# PowerShell/MSBuild Integration

Monday, February 18, 2008 9:20 AM by Windows PowerShell

Bart De Smet recently posted a blog entry showing to invoke PowerShell script from within MSBuild with

# PowerShell/MSBuild Integration

Monday, February 18, 2008 9:42 AM by Noticias externas

Bart De Smet recently posted a blog entry showing to invoke PowerShell script from within MSBuild with

# MSDN Blog Postings » PowerShell/MSBuild Integration

Monday, February 18, 2008 11:19 AM by MSDN Blog Postings » PowerShell/MSBuild Integration

Pingback from  MSDN Blog Postings  » PowerShell/MSBuild Integration

# MSDN Blog Postings » PowerShell/MSBuild Integration

Monday, February 18, 2008 11:29 AM by MSDN Blog Postings » PowerShell/MSBuild Integration

Pingback from  MSDN Blog Postings  » PowerShell/MSBuild Integration

# MSDN Blog Postings » PowerShell/MSBuild Integration

Monday, February 18, 2008 11:29 AM by MSDN Blog Postings » PowerShell/MSBuild Integration

Pingback from  MSDN Blog Postings  » PowerShell/MSBuild Integration

# MSDN Blog Postings » PowerShell/MSBuild Integration

Monday, February 18, 2008 11:29 AM by MSDN Blog Postings » PowerShell/MSBuild Integration

Pingback from  MSDN Blog Postings  » PowerShell/MSBuild Integration

# re: Invoking PowerShell scripts from MSBuild

Monday, February 18, 2008 12:26 PM by Arild

You reinvented my wheel ;-)

arildf.spaces.live.com/.../cns!E99F8B43533149B0!180.entry

# re: Invoking PowerShell scripts from MSBuild

Monday, February 18, 2008 2:20 PM by bart

Hi Arild,

Sorry for that - I looked around the web for MSBuild + PowerShell and found a few matches that weren't exact matches for what I was looking for.

So, you definitely deserve all credits for having kicked off an earlier implementation than mine. Keep up the good work and keep me posted on your findings!

Thanks,

-Bart

# re: Invoking PowerShell scripts from MSBuild

Tuesday, February 19, 2008 12:30 PM by IgorM

Bart, just adding the PowerShell to my MSBuild wasn't solving all my problems, so I've figured out that I needed a more robust solution to solve all my build dependencies. This is where an idea came to me to reuses a Rules Engine from Worflow Foundation.

Check out how to do this in my recent post in the "WF are not only for business" series: Marrying Workflows into PowerShell, PowerShell Rules Engine [ http://shrinkster.com/uuf ]

# re: Invoking PowerShell scripts from MSBuild

Tuesday, February 19, 2008 10:58 PM by bart

Hi Igor,

Great blog series - keep up the good work! I'm a big fan of WF myself and it believe there are lots of places where it can be applied in a more generic fashion - yours is spot on :-).

Thanks,

-Bart

# Invoking PowerShell scripts from MSBuild

Monday, March 03, 2008 1:36 AM by DotNetKicks.com

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# http://bartdesmet.net/blogs/bart/archive/2008/02/16/invoking-powershell-scripts-from-msbuild.aspx

# Any good PowerShell MSBuild tasks? - Programmers Goodies

Pingback from  Any good PowerShell MSBuild tasks? - Programmers Goodies

# Any good PowerShell MSBuild tasks? | Ask Programming & Technology

Pingback from  Any good PowerShell MSBuild tasks? | Ask Programming & Technology

# 19lou.25941.cn

Tuesday, September 23, 2014 2:19 AM by 19lou.25941.cn

Invoking PowerShell scripts from MSBuild - B# .NET Blog

# Any good PowerShell MSBuild tasks? | Ngoding

Sunday, November 23, 2014 10:36 AM by Any good PowerShell MSBuild tasks? | Ngoding

Pingback from  Any good PowerShell MSBuild tasks? | Ngoding

# PowerShell/MSBuild Integration

Thursday, December 11, 2014 6:11 AM by PowerShell/MSBuild Integration

Pingback from  PowerShell/MSBuild Integration