13

I'm invoking a PowerShell script from MSBuild. MSBuild is able to capture the output returned, but thinks the project is built successfully.

The problem is that the exit code from PowerShell is not passed to the command in MSBuild. Has someone tried this before and been able to throw an exit code to MSBuild?

testmsbuild.proj

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <ScriptLocation>c:\scripts\test.ps1</ScriptLocation>
  </PropertyGroup>

  <Target Name="AfterDropBuild" >
        <Exec Command="powershell.exe -NoProfile -Noninteractive -command &quot;&amp; { $(ScriptLocation)%3Bexit $LASTEXITCODE }&quot; " >
        <Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
        </Exec>
   </Target>    

 </Project>

test.ps1 (of course this is going to error out)

function CallFromMSBuild {

   Invoke-command {Powershell.exe C:\a.ps1} -computername $computers
} 

When MSBuild project is triggered, it should have caught the issue and the build should have failed (considers build succeeded instead)

When I call from MSBuild

C:\Scripts>msbuild testmsbuild.proj /t:AfterDropBuild
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.225]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 4/28/2011 2:46:29 PM.
Project "C:\scripts\testmsbuild.proj" on node 1 (AfterDropBuild target(s)).
AfterDropBuild:
  powershell.exe -NoProfile -Noninteractive -command "& { c:\scripts\test.ps1;e
  xit $LASTEXITCODE }"
  Invoke-Command : Cannot validate argument on parameter 'ComputerName'. The argu
  ment is null or empty. Supply an argument that is not null or empty and then tr
  y the command again.
  At C:\install\test.ps1:3 char:58
  +    Invoke-command {Powershell.exe C:\a.ps1} -computername <<<<  $computers
      + CategoryInfo          : InvalidData: (:) [Invoke-Command], ParameterBind
     ingValidationException
      + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Power
     Shell.Commands.InvokeCommandCommand

Done Building Project "C:\scripts\testmsbuild.proj" (AfterDropBuild target(s)).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.04
wonea
  • 4,783
  • 17
  • 86
  • 139
Sanjeev
  • 673
  • 4
  • 10
  • 19

3 Answers3

15

This question is the top answer on a major search engine. The best answer is this from James Kovacs (of psake fame - i.e., he's kinda FizzBinned on PowerShell and MSBuild integration).

In summary, in a ps1 file:

  1. stick a $ErrorActionPreference='Stop' at the top of your script so it's not the default, which is SilentlyContinue (the trap bit in the accepted answer has the same effect but is far more indirect and confusing)
  2. stick in his function exec {... (or use psake itself)
  3. wrap invocations of external EXEs in an exec { x.exe }
  4. don't need to do any explicit exit ... stuff

The default error handling propagates an exception up as an ERRORLEVEL of 1 from the powershell.exe myscript.ps1, i.e. in an MSBuild <Exec you don't need to do any trickery re telling it to ignore exit codes etc. (unless you want to do something conditional on the specific exit code, in which you want to do IgnoreExitCode="true" and capture it with an <Output element)

A final important thing to understand is that within PowerShell, there's a $? which is the outcome of the last expression (which is not relevant if you're in ErrorAction='Stop' mode) which changes with every thing you do, whereas $LastExitCode is the 'DOS' exit code of the last .exe triggered in the system. Details here - be sure to read the comments

wonea
  • 4,783
  • 17
  • 86
  • 139
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • Sweet! Thanks for the information. I'm using "Exec function" code mentioned in the blog link to capture the error from another PS script(remoteinvoke.ps1) invoked in remote computers from myscript.ps1. Trap isn't working in those cases. I'll write a detailed post (and link it here) about my findings and how exec helped me solved the issues :D – Sanjeev May 02 '11 at 16:40
  • @Sanjeev: Happy to hear it. Certainly a `trap` (`on error goto` :P) isnt much better than `on error resume next` in most cases. But it'll definitely be good to get another summary of the bits that are actually relevant as opposed to piling in lots of little things to force it to work... – Ruben Bartelink May 03 '11 at 23:24
  • @RubenBartelink what is about http://geekswithblogs.net/dwdii/archive/2011/05/27/part-2-automating-a-visual-studio-build-with-powershell.aspx ? – Kiquenet May 03 '13 at 07:16
  • @Kiquenet Well... There are hardcoded C Drive paths and no error handling... Anyway, for me if you're taking that route, stick with a proven one, e.g. http://stackoverflow.com/a/3465029/11635 – Ruben Bartelink May 03 '13 at 07:49
4

Add exit $lastexitcode to test.ps1

After comment:

Try this in test.ps1:

trap {Write-Host -foreground red $_.Exception.Message; exit 1; continue} Invoke-command   {Powershell.exe C:\a.ps1} -computername $computers

Basically, I don't think MSBuild is at fault here.

I tried your wrong Invoke-Command and the $lastexitcode was set to 0 even though the Invoke-Command had failed! You can test whether this works or not from cmd itself by doing echo %errorlevel% and see you get 1.

wonea
  • 4,783
  • 17
  • 86
  • 139
manojlds
  • 290,304
  • 63
  • 469
  • 417
  • No luck! Still the same. The exit code is 255 when ran from a powershell window directly – Sanjeev Apr 28 '11 at 20:35
  • Thanks for the update. trap { } does the trick! Now the MSbuild says "Build Failed". The catch is it doesn't show the $._Exception.Message thought. All it shows is 'code' C:\scripts\testmsbuild.proj(12,3): error MSB3073: The command "powershell.exe c:\scripts\test.ps1" exited with code 1 'code'. Half the battle is over though :) – Sanjeev Apr 28 '11 at 21:07
  • 1
    Can you try without the Write-Host and see...like `trap {$_.Exception.Message; exit 1; continue}` – manojlds Apr 28 '11 at 21:17
  • Also I would add the other changes I had to make to get this working. ' ' code in testmsproj.proj had to be replaced with '' – Sanjeev Apr 28 '11 at 21:43
  • What is about http://geekswithblogs.net/dwdii/archive/2011/05/27/part-2-automating-a-visual-studio-build-with-powershell.aspx ? – Kiquenet May 03 '13 at 07:15
0

I tried several options but none works for me.

To capture the powershell error on msbuild target, best way is to return the error code from powershell script like $host.SetShouldExit($remotelastexitcode).

e.g. test1.ps1

function CallFromMSBuild {

   Invoke-command {Powershell.exe C:\a.ps1} -computername $computers
   $host.SetShouldExit(1)
} 

<Target Name="AfterDropBuild" >
        <Exec Command="powershell.exe -NoProfile -Noninteractive -command &quot;&amp; { $(ScriptLocation)%3Bexit $LASTEXITCODE }&quot; " >
        <Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
        </Exec>
   </Target>   

Worth to have a look at https://snagify.wordpress.com/2008/04/06/teambuild-powershell-and-exit-codes/

vishal shah
  • 177
  • 5