web-dev-qa-db-ja.com

PowerShellの終了コードが常に「0」になるのはなぜですか?

私は次のようなPowerShellスクリプトを持っています

_##teamcity[progressMessage 'Beginning build']
# If the build computer is not running the appropriate version of .NET, then the build will not run. Throw an error immediately.
if( (ls "$env:windir\Microsoft.NET\Framework\v4.0*") -eq $null ) {
    throw "This project requires .NET 4.0 to compile. Unfortunately .NET 4.0 doesn't appear to be installed on this machine."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Setting up variables']
# Set up variables for the build script
$invocation = (Get-Variable MyInvocation).Value
$directorypath = Split-Path $invocation.MyCommand.Path
$v4_net_version = (ls "$env:windir\Microsoft.NET\Framework\v4.0*").Name
$nl = [Environment]::NewLine

Copy-Item -LiteralPath "$directorypath\packages\NUnit.2.6.2\lib\nunit.framework.dll" "$directorypath\Pandell.Tests\bin\debug" -Force

##teamcity[progressMessage 'Using msbuild.exe to build the project']
# Build the project using msbuild.exe.
# Note we've already determined that .NET is already installed on this computer.
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Release
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Debug

# Break if the build throws an error.
if(! $?) {
    throw "Fatal error, project build failed"
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Build Passed']
# Good, the build passed
Write-Host "$nl project build passed."  -ForegroundColor Green


##teamcity[progressMessage 'running tests']
# Run the tests.
cmd /c $directorypath\build_tools\nunit\nunit-console.exe $directorypath\Pandell.Tests\bin\debug\Pandell.Tests.dll

# Break if the tests throw an error.
if(! $?) {
    throw "Test run failed."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Tests passed']
_

私が信じるようになったものから、 キャッチされていないThrow は_1_の終了コードになりますが、残念ながらTeamCityはそうではないと言っています。

_[19:32:20]Test run failed.
[19:32:20]At C:\BuildAgent\work\e903de7564e599c8\build.ps1:44 char:2
[19:32:20]+     throw "Test run failed."
[19:32:20]+     ~~~~~~~~~~~~~~~~~~~~~~~~
[19:32:20]    + CategoryInfo          : OperationStopped: (Test run failed.:String) [],
[19:32:20]   RuntimeException
[19:32:20]    + FullyQualifiedErrorId : Test run failed.
[19:32:20]
[19:32:20]Process exited with code 0
[19:32:20]Publishing internal artifacts
[19:32:20][Publishing internal artifacts] Sending build.finish.properties.gz file
[19:32:20]Build finished
_

_Execution Mode_が_Execute .ps1 script with "-File" argument_に設定されていることに注意することも重要です。

それを_Put script into PowerShell stdin with "-Command -" arguments_に変更しようとしましたが、テストに合格しても_1_の終了コードで失敗しました。 _-File_として実行するのが正しい方法になると確信しています。

_C:\BuildAgent\work\e903de7564e599c8\build.ps1_にあるスクリプトを開いてCMDで手動で実行すると、同じことが行われます...つまり、失敗したテストは失敗し、_%errorlevel%_はまだ_0_です。

それでも、PowerShellで実行して_$LASTEXITCODE_を呼び出すと、毎回正しいコードが返されます。

69
Chase Florell

これはPowerShellの既知の問題です。 -fileを指定してスクリプトを実行すると、終了コード0が返されるはずがありません。

(更新:以下のリンクは機能しなくなりました。でこの問題を検索または報告してください。 PowerShell:Hot(1454 ideas)– Windows Server))

-commandを使用しても効果がなかったため、スクリプトの先頭にトラップを追加してみてください。

trap
{
    write-output $_
    ##teamcity[buildStatus status='FAILURE' ]
    exit 1
}

上記により、例外がスローされたときに適切な終了コードが返されるはずです。

93

-fileで実行しているときにこの問題が発生していましたが、何らかの理由で、ケビンが提供するトラップ構文または「終了」構文が私のシナリオで機能しませんでした。

理由はわかりませんが、他の誰かが同じ問題に遭遇した場合に備えて、以下の構文を使用しました。

try{
    #DO SOMETHING HERE
}
catch
{
    Write-Error $_
    ##teamcity[buildStatus status='FAILURE']
    [System.Environment]::Exit(1)
}
24
Jay S

これが(おそらく)dup 古い質問に対する私の自己回答の としてクローズされるまで、ここで最もクリーンなソリューションを要約します:

  • 他のほとんどの答えは、PowerShellビットからstderrに何かを送信することです。stderr出力をオプションとしてTeamCityで直接実現できます(デフォルトのWarningではなくErrorに設定します)

  • ただし、「失敗条件」の下に「...エラーメッセージが(sic)ビルドランナー」によって記録される場合、ビルドをオンにする必要もあります。他の回答のいずれかがあなたのために働く、あなたはおそらくこれをすでにオンにしているでしょうが、IMEはvery忘れやすいです!)

13
Ruben Bartelink

PowerShellスクリプトでは、何らかの理由でこれらのオプションが機能しませんでした。私はそれに何時間も費やしました。

私にとって最良の選択肢は、TeamCityとPowerShellの間にレイヤーを配置することでした。そのため、PowerShellスクリプトを呼び出すC#コンソールアプリケーションを作成しました。

私のやり方は、TeamCityで次の名前のスクリプトを呼び出します:RemoteFile.ps1

スクリプト引数あり:%system.RemoteServerFQDN%%system.RemoteUser%%system.RemoteUserPassword%%system.RemoteScriptName%%system.RemotePropertiesFile%%system.BuildVersion%%system.RunList%

param (
    [Parameter(Mandatory=$true)]
    $Computername,
    [Parameter(Mandatory=$true)]
    $Username,
    [Parameter(Mandatory=$true)]
    $Password,
    [Parameter(Mandatory=$true)]
    $ScriptName,
    [Parameter(Mandatory=$true)]
    $Propfile,
    [Parameter(Mandatory=$true)]
    $Version,
    [Parameter(Mandatory=$true)]
    [string[]]$DeploymentTypes
)

$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
Write-Host "Readying to execute invoke-command..."
Invoke-Command -ComputerName $Computername -Credential $cred -ScriptBlock {       D:\Deployment\PowershellWrapper.exe $using:ScriptName $using:Propfile $using:Version      $using:DeploymentTypes } -ArgumentList $ScriptName,$Propfile,$Version,$DeploymentTypes

指定された場所のリモートサーバーに存在します。

次に、そのファイルはこれを呼び出します:powershellwrapper.exeも指定された場所にあります(私のスクリプトには、PowerShellスクリプトに渡す4つのパラメーターがあります)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace PowershellWrapper
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string argFull = @"""{0} {1} {2} {3}""";
                string arg0 = args[0];
                string arg1 = args[1];
                string arg2 = args[2];
                string arg3 = args[3];
                string argFinal = string.Format(argFull, arg0, arg1, arg2, arg3);

                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = @"powershell.exe";
                startInfo.Arguments = argFinal;
                startInfo.RedirectStandardOutput = false;
                startInfo.RedirectStandardError = false;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardInput = true;
                startInfo.CreateNoWindow = false;
                Process process = new Process();
                process.StartInfo = startInfo;
                process.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("{0} Exception caught.", e);
                Console.WriteLine("An error occurred in the deployment.", e);
                Console.WriteLine("Please contact [email protected] if error occurs.");
            }
        }
    }
}

そして、4つのパラメーターを使用してスクリプトを呼び出します。最初のパラメーターであるスクリプトと、3つの引数。基本的に、ここで行われているのは、PowerShellスクリプト自体の代わりにPowershellWrapper.exeを実行して誤った終了コード0をキャプチャし、実行中の完全なスクリプトをTeamCityログに報告することです。

それが理にかなっていることを願っています。それは私たちにとって魅力のようです。

1
mumbles

コマンドで-ErrorAction stopを使用すると、デフォルトで終了コード1が返され、失敗条件を追加せずにTeamCityでも表示されます。 $ErrorActionPreference = "Stop";を使用して、すべてのPowerShellコマンドにデフォルトでこの動作を実装します。

0
Bart VdA