Question

EDIT 3

OK, so it seems like this might not be an Installer issue after all. When I make a simple batch file:

exit /b 12

and call it as

cmd /c test.cmd
echo %ERRORLEVEL%

I get "12" on Windows Server 2003 R2, but "0" on XP. I thought I had tested this simple test case many times before but apparently not.

So, I've changed the tags and title but I'm leaving the other information here as there's actually a lot of useful stuff here that is not directly related to this issue.

Thoughts?

Original below

I have a custom action written in VBScript that in turn is calling a Windows batch file (the custom action is essentially allowing the user to execute something at install time they can also run later by running the batch file - it's a convenience). The function is below:

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

When I run the same kind of code outside of a custom action, and the batch file returns an error code (via EXIT /B), the return value is correctly captured in intReturnCode. However, from the custom action, the exit code seems to be "lost" - I always get a 0 back (I can see this in the installer log from the WriteMessage call). It doesn't matter if I use Exec or Run on the shell, I still get back a 0. The script writes its own return code out before returning it (I can see this in the stdout stream from Exec) so I know it's not actually 0. I need that return code to properly report an error back to the installer.

Ideas?

For the record this is Windows Installer 3.0 on Windows XP SP3. The installer is in Wise so I don't have a WiX snippet or I would include it, but this is the function being called.

Also this is somewhat stripped - I've left out comments and other calls to WriteMessage as well as that function. And yes psuedo-Hungarian is evil blah blah blah.

Edit: Here is the C version of the code. It's giving the same exact issue:

#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; 
} 

When run on my Windows Server 2003 R2 Visual Studio 2008 box, I get the error code as expected:

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

When run on my Windows XP test box, I get a 0, even though it should be an error:

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

Both machines have Windows Installer 3.1. XP is 3.01.4001.5512, 2003 R2 is 3.01.4000.3959.

So it's something acting different between the boxes although I have no idea what.

EDIT 2

The exact table row for the action, as generated by the Wise for Windows Installer tool, is:

"RunMigrationActionCA","1537","Calllaunchchildprocess","RunMigrationAction","0"

To test the immediate flag I added 0x800 to the type column and no change was seen in the end behavior.

To be clear - this works fine on the 2003 R2 machine. That machine is not joined to a domain, but the XP machine is. Is there anything in group policy that could cause this behavior? (Grasping at straws at this point.)

Was it helpful?

Solution

WScript objects don't work inside custom actions Please reader more here. You could use a DLL custom action. Here is a step by step tutorial.

OTHER TIPS

It seems to be a bug in the cmd.exe of WinXP.
The solution is to use exit 123 instead of exit /b 123 in the batch file.

If you don't wish to change existing batch files, just add a wrapper.bat:

@echo off
call %*
exit %errorlevel%

And invoke it:

system("wrapper.bat your.bat all your args")
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top