首页 文章

为什么我的PowerShell退出代码始终为“0”?

提问于
浏览
68

我有一个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']

据我所知,an uncaught 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 ,它每次都会返回正确的代码 .

5 回答

  • 13

    这是PowerShell的一个已知问题 . 使用 -file 执行脚本时不会返回退出代码0 .

    (更新:以下链接不再有效 . 请在PowerShell: Hot (1454 ideas) – Windows Server上查找或报告此问题)

    由于使用 -command 不适合您,您可以尝试在脚本顶部添加陷阱:

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

    抛出异常时,上面应该会产生正确的退出代码 .

  • 0

    我在使用 -file 运行时遇到了这个问题,但由于某些原因,凯文提供的陷阱语法或'exit'语法在我的场景中无效 .

    我不知道为什么,但是为了防止其他人遇到同样的问题,我使用了下面的语法,它对我有用:

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

    直到这个(大概)被关闭为dup of my self-answer of an older question,我将总结这里最干净的解决方案:

    • 大多数其他答案都涉及从PowerShell位向 stderr 发送一些内容 . 这可以通过 Format stderr output as 选项直接使用TeamCity完成(将其设置为 Error 而不是默认值,即警告)

    • 然而,关键的是,还有必要打开“失败构建如果:...... An error message is logged by (sic) build runner " under " Failure Conditions ”(如果其他任何答案对你有用,那么你很容易忘记!)

  • 24

    无论出于何种原因,这些选项都不适用于我的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脚本)

    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 test@test.com if error occurs.");
                }
            }
        }
    }
    

    并用四个参数调用我的脚本 . 该脚本是第一个参数,加上三个参数 . 所以基本上这里发生的是我正在执行PowershellWrapper.exe而不是PowerShell脚本本身来捕获错误的退出代码0,它仍然报告完整的脚本运行回TeamCity日志 .

    我希望这是有道理的 . 它对我们来说就像一个魅力 .

  • 1

    在命令上使用 -ErrorAction stop 默认返回退出代码1,并在TeamCity中显示它而不添加失败条件 . 现在,我们将使用 $ErrorActionPreference = "Stop"; 为每个PowerShell命令默认实现此行为 .

相关问题