cmd - Windows XP 中子进程的退出代码 "lost",而不是 Windows Server 2003 中的子进程

标签 cmd exit-code errorlevel

编辑 3

好的,看起来这可能根本不是安装程序问题。当我制作一个简单的批处理文件时:

exit /b 12

并将其命名为

cmd /c test.cmd
echo %ERRORLEVEL%

我在 Windows Server 2003 R2 上得到“12”,但在 XP 上得到“0”。我以为我之前已经多次测试过这个简单的测试用例,但显然没有。

所以,我更改了标签和标题,但我将其他信息保留在这里,因为这里实际上有很多有用的内容与此问题没有直接关系。

想法?

原文如下

我有一个用 VBScript 编写的自定义操作,该操作依次调用 Windows 批处理文件(自定义操作本质上是允许用户在安装时执行某些操作,他们也可以稍后通过运行批处理文件来运行 - 这很方便) 。其功能如下:

Function MainFunction
    strCustomActionData = Session.Property("CustomActionData")
    strSplit = Split(strCustomActionData, ";")
    strInstallDir = strSplit(0)
    strPostCopyAction = strSplit(1)

    strScriptLocation = strInstallDir & "\MigrationMasterProcess.cmd"

    strFullCommand = """" & strScriptLocation & """ " & strPostCopyAction

    Set objShell = CreateObject("WScript.Shell")

    Dim objExec
    Set objExec = objShell.Exec(strFullCommand)

    intReturnCode = objExec.ExitCode

    Set objExec = Nothing
    Set objShell = Nothing

    WriteMessage "Return value: " & intReturnCode

    ' cf. http://msdn.microsoft.com/en-us/library/windows/desktop/aa371254(v=vs.85).aspx
    If (intReturnCode = 0) Then
        MainFunction = 1
    Else
        MainFunction = 3
    End If
End Function

当我在自定义操作之外运行相同类型的代码,并且批处理文件返回错误代码(通过 EXIT/B)时,返回值会在 intReturnCode 中正确捕获。但是,从自定义操作来看,退出代码似乎“丢失”了 - 我总是得到 0(我可以在 WriteMessage 调用的安装程序日志中看到这一点)。如果我在 shell 上使用 Exec 或 Run 并不重要,我仍然会返回 0。脚本在返回之前会写出自己的返回代码(我可以在 Exec 的标准输出流中看到这一点),所以我知道它不是实际上是 0。我需要该返回代码才能正确地将错误报告给安装程序。

想法?

据记录,这是 Windows XP SP3 上的 Windows Installer 3.0。安装程序位于 Wise 中,因此我没有 WiX 代码段,否则我会包含它,但这是正在调用的函数。

此外,这有些精简 - 我省略了对 WriteMessage 以及该函数的注释和其他调用。是的,伪匈牙利语是邪恶的等等等等。

编辑:这是代码的 C 版本。它给出了同样的问题:

#include <Windows.h>
#include <msi.h>
#include <msiquery.h>
#include <stdio.h>
#include <stdlib.h>
#include "LaunchChildProcess.h"

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) {
    return TRUE;
}

UINT __stdcall RunMigrationAction(MSIHANDLE hModule) {
    UINT  uiStat;
    DWORD dwPropertySize = MAX_PATH * 2;
    TCHAR szValueBuf[MAX_PATH * 2]; // arbitrary but we know the strings won't be near that long
    TCHAR *szInstallDir, *szPostCopyAction;
    TCHAR *szNextToken;
    TCHAR szScriptLocation[MAX_PATH * 2];
    TCHAR szParameters[MAX_PATH * 2];
    INT   iReturnValue;

    LogTaggedString(hModule, TEXT("Action Status"), TEXT("Starting"));

    uiStat = MsiGetProperty(hModule, TEXT("CustomActionData"), szValueBuf, &dwPropertySize);
    if (ERROR_SUCCESS != uiStat) {
        LogTaggedString(hModule, TEXT("Startup"), TEXT("Failed to get custom action data"));
        return ERROR_INSTALL_FAILURE;
    }

    LogTaggedString(hModule, TEXT("Properties given"), szValueBuf);
    LogTaggedInteger(hModule, TEXT("Property length"), dwPropertySize);

    if (0 == dwPropertySize) {
        return ERROR_INSTALL_FAILURE;
    }

    LogTaggedString(hModule, TEXT("Properties given"), szValueBuf);

    szInstallDir     = wcstok_s(szValueBuf, TEXT(";"), &szNextToken);
    szPostCopyAction = wcstok_s(NULL,       TEXT(";"), &szNextToken);

    LogTaggedString(hModule, TEXT("Install dir"), szInstallDir);
    LogTaggedString(hModule, TEXT("Post-copy action"), szPostCopyAction);

    wcscpy_s(szScriptLocation, MAX_PATH * 2, szInstallDir);
    wcscat_s(szScriptLocation, MAX_PATH * 2, TEXT("\\MigrationMasterProcess.cmd"));

    LogTaggedString(hModule, TEXT("Script location"), szScriptLocation);

    wcscpy_s(szParameters, MAX_PATH * 2, TEXT(" /C "));
    wcscat_s(szParameters, MAX_PATH * 2, szScriptLocation);
    wcscat_s(szParameters, MAX_PATH * 2, TEXT(" "));
    wcscat_s(szParameters, MAX_PATH * 2, szPostCopyAction);

    LogTaggedString(hModule, TEXT("Parameters to cmd.exe"), szParameters);

    iReturnValue = ExecuteProcess(TEXT("cmd.exe"), szParameters);
    LogTaggedInteger(hModule, TEXT("Return value from command"), iReturnValue);

    LogTaggedString(hModule, TEXT("Action Status"), TEXT("Finished"));

    return (0 == iReturnValue) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
}

void LogTaggedInteger(MSIHANDLE hInstall, TCHAR* szTag, INT iValue) {
    TCHAR szValue[15];
    _itow_s(iValue, szValue, 15, 10);
    LogTaggedString(hInstall, szTag, szValue);
}

void LogTaggedString(MSIHANDLE hInstall, TCHAR* szTag, TCHAR* szMessage) {
    MSIHANDLE hRecord;
    UINT uiStat;
    //TCHAR szFullMessage[4096];
    //wcscpy_s(szFullMessage, 4096, TEXT("--------------- "));
    //wcscat_s(szFullMessage, 4096, szTag);
    //wcscat_s(szFullMessage, 4096, TEXT(": "));
    //wcscat_s(szFullMessage, 4096, szMessage);
    hRecord = MsiCreateRecord(3);
    uiStat = MsiRecordSetString(hRecord, 0, TEXT("--------- [1]: [2]"));
    uiStat = MsiRecordSetString(hRecord, 1, szTag);
    uiStat = MsiRecordSetString(hRecord, 2, szMessage);
    uiStat = MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_INFO), hRecord);
    MsiCloseHandle(hRecord);
    return;
}


int MsiMessageBox(MSIHANDLE hInstall, TCHAR* szString, DWORD dwDlgFlags) {
    PMSIHANDLE newHandle = ::MsiCreateRecord(2);
    MsiRecordSetString(newHandle, 0, szString);
    return (MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_USER + dwDlgFlags), newHandle));
}


DWORD ExecuteProcess(TCHAR *szProcess, TCHAR *szParams) { 
    INT iMyCounter = 0, iPos = 0;
    DWORD dwReturnVal = 0;
    TCHAR *sTempStr = L""; 

    /* CreateProcessW can modify Parameters thus we allocate needed memory */
    wchar_t * pwszParam = new wchar_t[wcslen(szParams) + 1]; 
    if (NULL == pwszParam) { 
        return 1; 
    } 

    wcscpy_s(pwszParam, wcslen(szParams) + 1, szParams); 

    /* CreateProcess API initialization */
    STARTUPINFOW siStartupInfo; 
    PROCESS_INFORMATION piProcessInfo; 
    memset(&siStartupInfo, 0, sizeof(siStartupInfo)); 
    memset(&piProcessInfo, 0, sizeof(piProcessInfo)); 
    siStartupInfo.cb = sizeof(siStartupInfo); 

    if (CreateProcessW(const_cast<LPCWSTR>(szProcess), 
                            pwszParam, 0, 0, false, 
                            CREATE_DEFAULT_ERROR_MODE, 0, 0, 
                            &siStartupInfo, &piProcessInfo) != false) { 
        /* Watch the process. */
        WaitForSingleObject(piProcessInfo.hProcess, INFINITE);
        if (!GetExitCodeProcess(piProcessInfo.hProcess, &dwReturnVal)) {
            dwReturnVal = GetLastError();
        }
    } else { 
        /* CreateProcess failed */
        dwReturnVal = GetLastError(); 
    } 

    /* Free memory */
    free(pwszParam);
    pwszParam = NULL;

    /* Release handles */
    CloseHandle(piProcessInfo.hProcess); 
    CloseHandle(piProcessInfo.hThread); 

    return dwReturnVal; 
} 

在我的 Windows Server 2003 R2 Visual Studio 2008 机器上运行时,我收到预期的错误代码:

--------- Return value from command: 5023

当在我的 Windows XP 测试盒上运行时,我得到一个 0,尽管它应该是一个错误:

--------- Return value from command: 0

两台机器都有 Windows Installer 3.1。 XP 是 3.01.4001.5512,2003 R2 是 3.01.4000.3959。

所以这两个盒子之间的行为有所不同,尽管我不知道是什么。

编辑 2

由 Wise for Windows Installer 工具生成的操作的确切表行是:

“RunMigrationActionCA”,“1537”,“Calllaunchchildprocess”,“RunMigrationAction”,“0”

为了测试立即标志,我将 0x800 添加到类型列,但最终行为没有看到任何变化。

需要明确的是 - 这在 2003 R2 机器上运行良好。该计算机未加入域,但 XP 计算机已加入域。组策略中是否有任何内容可能导致此行为? (此时捕获吸管。)

最佳答案

这似乎是WinXP的cmd.exe中的一个错误。
解决方案是在批处理文件中使用 exit 123 而不是 exit/b 123

如果您不想更改现有批处理文件,只需添加一个wrapper.bat:

@echo off
call %*
exit %errorlevel%

并调用它:

system("wrapper.bat your.bat all your args")

关于cmd - Windows XP 中子进程的退出代码 "lost",而不是 Windows Server 2003 中的子进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9593357/

相关文章:

unit-testing - 如何在Groovy脚本中设置退出状态

python - 在 Python 3 中,使用 Pytest,我们如何测试退出代码 : exit(1) and exit(0) for a python program?

windows - ERRORLEVEL与%ERRORLEVEL%与感叹号ERRORLEVEL感叹号

batch-file - 如何在 Batch/CMD 中使用 PSLIST 和 FIND 查找精确匹配

windows - 如何使用 Windows forfiles 命令的完整路径运行命令?

Windows:删除多个子文件夹中的公共(public)文件夹

python - PyInstaller .exe 文件什么都不做

cmd - 如何使用 Linqpad 运行带有路径和确认的命令

c# - 在C#中手动设置线程退出代码?

Windows 批处理文件包含所有错误级别