Код выхода потерян из дочернего процесса в Windows XP, а не в Windows Server 2003

ИЗМЕНИТЬ 3

Хорошо, похоже, что это может быть не проблема установщика. Когда я делаю простой командный файл:

exit /b 12

и назовите это как

cmd /c test.cmd
echo %ERRORLEVEL%

Я получаю «12» в Windows Server 2003 R2, но «0» в XP. Я думал, что уже много раз тестировал этот простой тестовый пример, но, видимо, нет.

Итак, я изменил теги и заголовок, но оставлю здесь другую информацию, так как на самом деле здесь много полезного, не имеющего прямого отношения к этой проблеме.

Мысли?

Исходный текст ниже

У меня есть настраиваемое действие, написанное на 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). Неважно, использую ли я Exec или Run в оболочке, я все равно получаю 0. Сценарий записывает свой собственный код возврата перед его возвратом (я вижу это в потоке stdout из Exec), поэтому я знаю, что это не так. на самом деле 0. Мне нужен этот код возврата, чтобы правильно сообщить об ошибке установщику.

Идеи?

Для справки, это установщик Windows 3.0 для Windows XP SP3. Установщик находится в 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 3.1. XP - 3.01.4001.5512, 2003 R2 - 3.01.4000.3959.

Так что между боксами что-то действует по-разному, хотя я понятия не имею, что именно.

ИЗМЕНИТЬ 2

Точная строка таблицы для действия, созданная инструментом Wise для Windows Installer:

«RunMigrationActionCA», «1537», «Calllaunchchildprocess», «RunMigrationAction», «0»

Чтобы проверить немедленный флаг, я добавил 0x800 в столбец типа, и никаких изменений в конечном поведении не наблюдалось.

Чтобы было ясно - это отлично работает на машине 2003 R2. Эта машина не присоединена к домену, но машина XP присоединена. Есть ли что-нибудь в групповой политике, что могло бы вызвать такое поведение? (В этот момент хватается за соломинку.)


person MikeBaz - MSFT    schedule 06.03.2012    source источник
comment
Если все, что вы хотите сделать, это запустить исполняемый файл, почему вы не используете запуск исполняемого типа настраиваемого действия (базовый тип 2, 34 или 50)? Тогда вам даже не придется возиться с CustomActionData. Значения 1 и 3, возвращаемые установщику Windows, тоже кажутся некорректными; они должны быть одним из значений, задокументированных на msdn.microsoft.com /en-us/library/windows/desktop/aa368072.aspx   -  person Michael Urman    schedule 07.03.2012
comment
Я вызываю командный файл, а не исполняемый файл - и, что еще хуже, мне нужны данные о настраиваемых действиях, доступные для параметризации вызова. 1 и 3 подходят для настраиваемых действий скрипта - см. URL-адрес в комментарии к коду.   -  person MikeBaz - MSFT    schedule 08.03.2012
comment
Хорошая точка зрения на коды возврата сценария (хотя версия C может пострадать; я бы сопоставил коды возврата с одним из 0, 1602, 1603). Если вы хотите увидеть фактический код возврата, установите его в свойстве или зарегистрируйте его. Затем верните безопасный код из настраиваемого действия. Регистрация поведения выполнения может помочь понять, когда и как что-то не получается.   -  person Michael Urman    schedule 08.03.2012
comment
Версия C (показанная выше) действительно использует соответствующие коды возврата. Мой вход в ЦС сообщает мне, что он видит 0 как код выхода (см. Цитируемый в конце отредактированного вопроса), даже если журнал, записанный дочерним процессом, сообщает мне, что он возвращает не-0.   -  person MikeBaz - MSFT    schedule 08.03.2012
comment
Ага! [stackoverflow.com/questions/1924497/ Это ошибка или по дизайну, или что-то в этом роде. [1]: stackoverflow.com/questions/1924497/   -  person MikeBaz - MSFT    schedule 08.03.2012


Ответы (2)


Объекты WScript не работают внутри настраиваемых действий. Подробнее здесь. Вы можете использовать настраиваемое действие DLL. Вот пошаговое руководство.

person Ciprian    schedule 07.03.2012
comment
Я уже писал библиотеки DLL с настраиваемыми действиями; Это огромный кошмар поддержки - дать клиенту все, что мне нужно, - это запустить подпроцесс. Возможно, мне удастся отреагировать на это по-другому, поэтому я собираюсь пойти этим путем. Для других, которые смотрят на это, есть тонкий момент, который не освещен в связанной публикации блога, но находится в комментариях; в отличие от некоторых мест (stackoverflow.com/questions/98778/), в котором прямо указано, что в противном случае значение ProgID в пространстве имен WScript имеет значение. Что раздражает, так это то, что большинство вещей работает. - person MikeBaz - MSFT; 07.03.2012
comment
Ладно, оказывается, это не при чем. Я написал C DLL и столкнулся с той же самой проблемой. Более того, тот же код работает на моем тестовом блоке Windows Server 2003 - я получаю код ошибки. Я не получаю код ошибки в Windows XP. Та же версия установщика Windows. - person MikeBaz - MSFT; 08.03.2012
comment
Попробуйте выполнить настраиваемое действие без олицетворения. - person Ciprian; 08.03.2012
comment
Без изменений. Я признаю, что не понимаю, как это повлияет на разницу между машиной XP и машиной 2003 R2 (я знаю, что это будет намного важнее на машине Vista +), особенно в отношении правильного захвата кодов выхода. - person MikeBaz - MSFT; 08.03.2012
comment
Я приму этот ответ, так как в нем есть хорошие вещи, хотя здесь это не настоящий ответ. - person MikeBaz - MSFT; 08.03.2012

Похоже, это ошибка в cmd.exe WinXP.
Решение состоит в том, чтобы использовать exit 123 вместо exit /b 123 в пакетном файле.

Если вы не хотите изменять существующие командные файлы, просто добавьте wrapper.bat:

@echo off
call %*
exit %errorlevel%

И вызываем его:

system("wrapper.bat your.bat all your args")
person Nickolay Merkin    schedule 15.06.2013
comment
exit /? говорит: exitCode указывает числовое число. если / B указан, устанавливает ОШИБКУ для этого числа. При выходе из CMD.EXE устанавливает код выхода процесса с этим номером., вот почему предложенный вами обходной путь дает желаемые результаты. Powershell наследует код выхода последней команды в качестве кода выхода процесса powershell, cmd, похоже, не наследует - по крайней мере, не во всех случаях. - person syneticon-dj; 27.02.2014