ИЗМЕНИТЬ 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 присоединена. Есть ли что-нибудь в групповой политике, что могло бы вызвать такое поведение? (В этот момент хватается за соломинку.)