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
1

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

4 Comments

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: 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...
@RubenBartelink what is about geekswithblogs.net/dwdii/archive/2011/05/27/… ?
@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. stackoverflow.com/a/3465029/11635
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.

5 Comments

No luck! Still the same. The exit code is 255 when ran from a powershell window directly
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 :)
Can you try without the Write-Host and see...like trap {$_.Exception.Message; exit 1; continue}
Also I would add the other changes I had to make to get this working. '<Exec Command="powershell.exe -NoProfile -Noninteractive -command &quot;&amp; { $(ScriptLocation) }&quot; " > ' code in testmsproj.proj had to be replaced with '<Exec Command="powershell.exe $(ScriptLocation)" >'
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/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.