views:

624

answers:

2

I've just recently completed my first nightly build script (first significant anything script, really) in powershell. I seem to have things working well, if not yet robustly (I haven't handled significant error-checking yet), but I found myself falling into an idiom around the Invoke-Expression cmdlet, and I'm wondering if I'm using it properly.

Specifically, I use a series of variables to build up command-lines that I will use to build the solution, then run the solution's unit tests. e.g., something like:

$tmpDir = "C:\Users\<myuser>\Development\Autobuild"
$solutionPath=$tmpDir+"\MyProj\MyProj.sln"
$devenv="C:\Program Files (x86)\Microsoft Visual Studio 10.0\common7\ide\devenv"
$releaseProfile="Release"
$releaseCommandLine="`"$devenv`" `"$solutionPath`" /build `"$releaseProfile`""

This works well enough, $releaseCommandLine contains the command line that I want to execute when I'm done. I then execute it via this line:

$output = Invoke-Expression "& $releaseCommandLine"

Is this the proper way to execute a manually-built command line from a powershell script? I thought initially that Invoke-Command would do it, but I must have been doing something wrong because I couldn't get that working at all for half an hour, and I got this working almost immediately.

I've followed this same pattern a few other times in this same script. Is this a best-practice?

+4  A: 

Looks fine to me. Only thing I'd change is to use more Powershell features in place of fragile assumptions. E.g.:

  • use Join-Path instead of string concatenation

  • use the Env:\ provider to look up the %programfiles(x86)% dir (or better yet, use the HKML:\ provider to find the path - it's in SOFTWARE\Microsoft\VisualStudio\\InstallDir)

  • when I have to write a string that contains literal doublequotes and variable expansion, I usually fall back to the syntax below. Personal preference, obviously.

    '"{0}" "{1}" /build "{2}"' -f $devenv, $solutionPath, $releaseProfile

In some cases I'd be inclined to use Process.Start() so that I could capture the stdout & stderr streams independently (and maybe even control stdin interactively, depending on the application).

PS - the '&' is not strictly necessary.

Richard Berg
Wouldn't `$devenv $solutionpath /build $releaseprofile` work as well, simple and straightforward? I mean, there's no real reason why one would have to explicitly write quotes here, if everything that can contain spaces is within strings anyway.
Joey
Thanks for the suggestions, I'll see what I can integrate. Didn't know that Join-Path even existed, e.g. :)
Greg D
@Johannes: I recall getting some problems because of the spaces in the paths when the expression was actually invoked.
Greg D
@Johannes: if the string becomes 'devenv.exe c:\solution path\file.sln /build Release' how will devenv.exe know that it's supposed to represent 3 arguments instead of 4?
Richard Berg
Richard, if put into `Invoke-Expression`, yes. I made a mistake, though, you have to call `$devenv` with `.$devenv`. All *arguments* however, are going to be quoted anyway, this is done automatically by PowerShell for you. When you pass a variable to a program as argument, it will be expanded to a string and properly quoted. You can easily test this with a small program that just prints its arguments.
Joey
Cool - I didn't know you could "dot source" an EXE.
Richard Berg
I never really understood how the `.` operator actually works, I just tried :-)
Joey
+3  A: 

I think it is unnecessary to use Invoke-Expression here. I've done this with a lot of build scripts and it usually looks like this:

$vsroot = "$env:ProgramFiles(x86)\Microsoft Visual Studio 9.0"
$devenv = "$vsroot\Common7\IDE\devenv.exe"
$sln = Join-Path <source_root> Source\MyProj\MyProj.sln
& $devenv $sln /build Release

or

& $devenv $sln /build "Release|Any CPU"

Although lately, I have had some troubles with using devenv.exe (mis-behaving add-ins, etc), so now I use msbuild.exe:

$msbuild = 'C:\Windows\Microsoft.NET\Framework\v3.5\MSBuild.exe'
& $msbuild $sln /p:Configuration=Release

Currently MSBuild can handle C#, VB and C++ (invokes vcbuild) but it can't handle solutions with setup & deployment projects in them. However, I have found it to be more reliable than using devenv.exe.

BTW you typically need to invoke other tools (sn.exe, signtool.exe, mt.exe, etc) in a build script that are specific to the version of Visual Studio/.NET you want to build against. So it is usually best to configure your environment variables in the same way that the VS 2008 command prompt does. With the PowerShell Community Extensions installed, you can enable one line in the PSCX profile header to enable this for .NET 3.5/VS 2008 settings:

$Pscx:Preferences["ImportVisualStudioVars"] = $true
Keith Hill
Hey, thanks for the reply on my twitter the other day, Keith. That pdf clued me in to some useful stuff, especially re: changes from v1 to v2. :)
Greg D
As mentioned in my comment, Invoke-Expression and you never need both. Some people like to be verbose in scripts (eg writing out ForEach-Object in place of %). But I personally prefer your way :)
Richard Berg
"$env:ProgramFiles(x86)" doesn't work. It returns "C:\Program Files(x86)" instead of "C:\Program Files (x86)". Yet "dir env:" shows ProgramFiles(x86) with the space.
Greg D
I was able to work around the issue by using "${env:ProgramFiles(x86)}"
Greg D