For a win32 executable (x86) we can set the LargeAddressAware flag so it can access a virtual address space of 4 GB (instead of just 2 GB) when running on x64 Windows.
This looks very appealing. However, there are risks involved.
For example see: Drawbacks of using /LARGEADDRESSAWARE for 32 bit Windows executables?
So let's go ahead and configure the system that is executing some unit tests with the system-wide registry switch AllocationPreference set to MEM_TOP_DOWN.
That should do, shouldn't it?
It doesn't!
The problem is that the x86 "test runner" (execution engine) of Visual Studio is not LAA enabled itself.
This parent process will only see the "lower" 2 GB of VAS, and so will our modules to be tested.
Examples from VS2013.1
mstest.exe spawns QTAgent32.exe
vstest.console.exe spawns vstest.executionengine.x86.exe
All of them are not LAA enabled!
So what is the recommended way to use a x86 test runner that is LAA enabled?
here's a little code snippet (VS unit test, csharp) to check for LAA execution environment.
unless it succeeds your test environment is not suitable to have your set of unit tests (also) cover the compatibility with LAA:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestCheckEnv32LAA
{
[TestClass]
public class CheckEnv32LAA
{
#region [Native DLL import]
[Flags()]
public enum AllocationType : uint
{
COMMIT = 0x1000,
RESERVE = 0x2000,
RESET = 0x80000,
LARGE_PAGES = 0x20000000,
PHYSICAL = 0x400000,
TOP_DOWN = 0x100000,
WRITE_WATCH = 0x200000
}
[Flags()]
public enum MemoryProtection : uint
{
EXECUTE = 0x10,
EXECUTE_READ = 0x20,
EXECUTE_READWRITE = 0x40,
EXECUTE_WRITECOPY = 0x80,
NOACCESS = 0x01,
READONLY = 0x02,
READWRITE = 0x04,
WRITECOPY = 0x08,
GUARD_Modifierflag = 0x100,
NOCACHE_Modifierflag = 0x200,
WRITECOMBINE_Modifierflag = 0x400
}
[StructLayout(LayoutKind.Sequential)]
struct MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
}
[DllImport("kernel32.dll")]
extern static void GlobalMemoryStatusEx(ref MEMORYSTATUSEX status);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
AllocationType flAllocationType, MemoryProtection flProtect);
#endregion
public CheckEnv32LAA()
{
}
[TestMethod]
public void CheckEnvironment32LAA()
{
// check for a suitable environment to test modules for compatibility with LargeAddressAware (LAA):
// 1) OS must be x64
// 2) test runner must be x86
// 3) test runner must be LAA enabled itself
// 4) memory allocation (with manual TopDown flag) must happen beyond the 2 GB boundary
// 5) memory allocation (with default settings) must happen beyond the 2 GB boundary
//
// RE 3) this requirement is true for "regular" unit tests (to test DLL modules). it does not apply
// for any tests spawning the application (EXE) to be tested as a separate process.
//
// RE 5) a failure indicates the following registry switch has not been set:
// [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]
// "AllocationPreference"=dword:00100000
//
// see:
// https://stackoverflow.com/questions/2288728/
String sParentProcName = Process.GetCurrentProcess().MainModule.FileName;
//CHECK_1
Assert.IsTrue(Environment.Is64BitOperatingSystem, "Test is not executing on x64 OS");
//CHECK_2
Assert.IsFalse(Environment.Is64BitProcess, "Test runner is not x86: " + sParentProcName);
//CHECK_3
MEMORYSTATUSEX tmpStatus = new MEMORYSTATUSEX();
tmpStatus.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
tmpStatus.ullTotalPhys = 0;
GlobalMemoryStatusEx(ref tmpStatus);
ulong uVM = tmpStatus.ullTotalVirtual;
Assert.IsTrue(uVM > 0x80000000, "Test runner is not LAA enabled (max: " + uVM / (1024 * 1024) + "): " + sParentProcName);
Assert.IsTrue(uVM <= 0x100000000, "Test runner is not x86 (max: " + uVM / (1024 * 1024) + "): " + sParentProcName);
//CHECK_4
UIntPtr pMem = UIntPtr.Zero;
ulong uAddress = 0;
pMem = VirtualAlloc(UIntPtr.Zero, (UIntPtr)1024, AllocationType.RESERVE | AllocationType.TOP_DOWN, MemoryProtection.READWRITE);
uAddress = (ulong)pMem;
Assert.IsTrue(uAddress > 0x80000000, "Test runner is not LAA enabled (highest: " + uAddress / (1024 * 1024) + "): " + sParentProcName);
//CHECK_5
pMem = VirtualAlloc(UIntPtr.Zero, (UIntPtr)1024, AllocationType.RESERVE, MemoryProtection.READWRITE);
uAddress = (ulong)pMem;
Assert.IsTrue(uAddress > 0x80000000, "System-wide MEM_TOP_DOWN is not set (allocated at: " + uAddress / (1024 * 1024) + ")");
}
}
}
So far I have only come across suggestions to mess with the Microsoft binaries listed in the question (i.e. use editbin.exe to manually "patch" them). But this has the following disadvantages:
I need to repeat the patching once any future service pack is installed for Visual Studio
I can no longer test in parallel: "regular" x86 and "extended" x86 with LAA
It seems a proper long-term solution would have to be implemented by microsoft?:
http://visualstudio.uservoice.com/forums/196039-microsoft-test-tools/suggestions/5781437
Related
To test a corner case in our debugger, I need to come up with a program which has a DLL loaded above 2GB (0x80000000). Current test case is a multi-GB game which loads >700 DLLs, and I'd like to have something simpler and smaller. Is there a way to achieve it reliably without too much fiddling? I assume I need to use /LARGEADDRESSAWARE and somehow consume enough of the VA space to bump the new DLLs above 2GB but I'm fuzzy on the details...
Okay, it took me a few tries but I managed to come up with something working.
// cl /MT /Ox test.cpp /link /LARGEADDRESSAWARE
// occupy the 2 gigabytes!
#define ALLOCSIZE (64*1024)
#define TWOGB (2*1024ull*1024*1024)
#include <windows.h>
#include <stdio.h>
int main()
{
int nallocs = TWOGB/ALLOCSIZE;
for ( int i = 0; i < nallocs+200; i++ )
{
void * p = VirtualAlloc(NULL, ALLOCSIZE, MEM_RESERVE, PAGE_NOACCESS);
if ( i%100 == 0)
{
if ( p != NULL )
printf("%d: %p\n", i, p);
else
{
printf("%d: failed!\n", i);
break;
}
}
}
printf("finished VirtualAlloc. Loading a DLL.\n");
//getchar();
HMODULE hDll = LoadLibrary("winhttp");
printf("DLL base: %p.\n", hDll);
//getchar();
FreeLibrary(hDll);
}
On Win10 x64 produces:
0: 00D80000
100: 03950000
200: 03F90000
[...]
31800: 7FBC0000
31900: 00220000
32000: 00860000
32100: 80140000
32200: 80780000
32300: 80DC0000
32400: 81400000
32500: 81A40000
32600: 82080000
32700: 826C0000
32800: 82D00000
32900: 83340000
finished VirtualAlloc. Loading a DLL.
DLL base: 83780000.
for your own DLL you need set 3 linker option:
/LARGEADDRESSAWARE
/DYNAMICBASE:NO
/BASE:"0x********"
note that link.exe allow only image full located bellow 3GB (0xC0000000) for 32-bit image. in other word, he want that ImageBase + ImageSize <= 0xC0000000
so say /BASE:0xB0000000 will be ok, /BASE:0xBFFF0000 only if your image size <= 0x10000 and for /BASE:0xC0000000 and higher we always got error LNK1249 - image exceeds maximum extent with base address address and size size
also EXE mandatory must have /LARGEADDRESSAWARE too, because are all 4GB space available for wow64 process based only on EXE options.
if we want do this for external DLL - here question more hard. first of all - are this DLL can correct handle this situation (load base > 0x80000000) ? ok. let test this. any api (including most low level LdrLoadDll) not let specify base address, for DLL load. here only hook solution exist.
when library loaded, internal always called ZwMapViewOfSection and it 3-rd parameter BaseAddress - Pointer to a variable that receives the base address of the view. if we set this variable to 0 - system yourself select loaded address. if we set this to specific address - system map view (DLL image in our case) only at this address, or return error STATUS_CONFLICTING_ADDRESSES.
working solution - hook call to ZwMapViewOfSection and replace value of variable, to which point BaseAddress. for find address > 0x80000000 we can use VirtualAlloc with MEM_TOP_DOWN option. note - despite ZwMapViewOfSection also allow use MEM_TOP_DOWN in AllocationType parameter, here it will be have not needed effect - section anyway will be loaded by preferred address or top-down from 0x7FFFFFFF not from 0xFFFFFFFF. but with VirtualAlloc the MEM_TOP_DOWN begin search from 0xFFFFFFFF if process used 4Gb user space. for know - how many memory need for section - we can call ZwQuerySection with SectionBasicInformation - despite this is undocumented - for debug and test - this is ok.
for hook of course can be used some detour lib, but possible do hook with DRx breakpoint - set some Drx register to NtMapViewOfSection address. and set AddVectoredExceptionHandler - which handle exception. this will be perfect work, if process not under debugger. but under debugger it break - most debuggers alwas stop under single step exception and usually no option not handle it but pass to application. of course we can start program not under debugger, and attach it later - after dll load. or possible do this task in separate thread and hide this thread from debugger. disadvantage here - that debugger not got notify about dll load in this case and not load symbols for this. however for external (system dll) for which you have not src code - this in most case can be not a big problem. so solution exit, if we can implement it ). possible code:
PVOID pvNtMapViewOfSection;
LONG NTAPI OnVex(::PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP &&
ExceptionInfo->ExceptionRecord->ExceptionAddress == pvNtMapViewOfSection)
{
struct MapViewOfSection_stack
{
PVOID ReturnAddress;
HANDLE SectionHandle;
HANDLE ProcessHandle;
PVOID *BaseAddress;
ULONG_PTR ZeroBits;
SIZE_T CommitSize;
PLARGE_INTEGER SectionOffset;
PSIZE_T ViewSize;
SECTION_INHERIT InheritDisposition;
ULONG AllocationType;
ULONG Win32Protect;
} * stack = (MapViewOfSection_stack*)(ULONG_PTR)ExceptionInfo->ContextRecord->Esp;
if (stack->ProcessHandle == NtCurrentProcess())
{
SECTION_BASIC_INFORMATION sbi;
if (0 <= ZwQuerySection(stack->SectionHandle, SectionBasicInformation, &sbi, sizeof(sbi), 0))
{
if (PVOID pv = VirtualAlloc(0, (SIZE_T)sbi.Size.QuadPart, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS))
{
if (VirtualFree(pv, 0, MEM_RELEASE))
{
*stack->BaseAddress = pv;
}
}
}
}
// RESUME_FLAG ( 0x10000) not supported by xp, but anyway not exist 64bit xp
ExceptionInfo->ContextRecord->EFlags |= RESUME_FLAG;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
struct LOAD_DATA {
PCWSTR lpLibFileName;
HMODULE hmod;
ULONG dwError;
};
ULONG WINAPI HideFromDebuggerThread(LOAD_DATA* pld)
{
NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, 0, 0);
ULONG dwError = 0;
HMODULE hmod = 0;
if (PVOID pv = AddVectoredExceptionHandler(TRUE, OnVex))
{
::CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
ctx.Dr7 = 0x404;
ctx.Dr1 = (ULONG_PTR)pvNtMapViewOfSection;
if (SetThreadContext(GetCurrentThread(), &ctx))
{
if (hmod = LoadLibraryW(pld->lpLibFileName))
{
pld->hmod = hmod;
}
else
{
dwError = GetLastError();
}
ctx.Dr7 = 0x400;
ctx.Dr1 = 0;
SetThreadContext(GetCurrentThread(), &ctx);
}
else
{
dwError = GetLastError();
}
RemoveVectoredExceptionHandler(pv);
}
else
{
dwError = GetLastError();
}
pld->dwError = dwError;
return dwError;
}
HMODULE LoadLibHigh(PCWSTR lpLibFileName)
{
BOOL bWow;
HMODULE hmod = 0;
if (IsWow64Process(GetCurrentProcess(), &bWow) && bWow)
{
if (pvNtMapViewOfSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtMapViewOfSection"))
{
LOAD_DATA ld = { lpLibFileName };
if (IsDebuggerPresent())
{
if (HANDLE hThread = CreateThread(0, 0, (PTHREAD_START_ROUTINE)HideFromDebuggerThread, &ld, 0, 0))
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
}
else
{
HideFromDebuggerThread(&ld);
}
if (!(hmod = ld.hmod))
{
SetLastError(ld.dwError);
}
}
}
else
{
hmod = LoadLibrary(lpLibFileName);
}
return hmod;
}
I'm targeting Windows XP, and I need a function similar to GetTickCount64, that does not overflow.
I couldn't find a decent solution that is correct and thread safe, so I tried to roll my own.
Here's what I came up with:
ULONGLONG MyGetTickCount64(void)
{
static volatile DWORD dwHigh = 0;
static volatile DWORD dwLastLow = 0;
DWORD dwTickCount;
dwTickCount = GetTickCount();
if(dwTickCount < (DWORD)InterlockedExchange(&dwLastLow, dwTickCount))
{
InterlockedIncrement(&dwHigh);
}
return (ULONGLONG)dwTickCount | (ULONGLONG)dwHigh << 32;
}
Is it really thread safe?
Thread safety is difficult to check for correctness, so I'm not sure whether it's really correct in all cases.
On Windows the timer overflow problem in usually solved (in games) with using QueryPerformanceCounter() functions instead of GetTickCount():
double GetCycles() const
{
LARGE_INTEGER T1;
QueryPerformanceCounter( &T1 );
return static_cast<double>( T1.QuadPart );
}
Then you can multiply this number by reciprocal number of cycles per second to convert cycles to seconds:
void Initialize()
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency( &Freq );
double CyclesPerSecond = static_cast<double>( Freq.QuadPart );
RecipCyclesPerSecond = 1.0 / CyclesPerSecond;
}
After initialization, this code is thread safe:
double GetSeconds() const
{
return GetCycles() * RecipCyclesPerSecond;
}
You can also checkout the full source code (portable between Windows and many other platforms) from our open-source Linderdaum Engine: http://www.linderdaum.com
I have searched to find if this has been asked and I have not seen it. There are questions asking if the OS is 64 or 32 bits. This is not the question I am asking.
On a Windows 64 bit OS, how can you tell if an app (program) is 64 or 32 bits?
The code itself does not say and the installation does not say.
I have a 64 bit machine but I know that I have an other program that is a 32 bit loaded and it runs. So my OS does not exclude me from having a 32 bit program.
So, how can I tell?
You can use tool such as PE Viewer to see information about EXE files.
Personally I use Altap Salamander's viewer feature to see the EXE or DLL architecture.
It is Intel x86 for 32-bit EXEs and AMD64 for 64-bit.
You need to analyze PE header.
It is header of any windows executable. To do that read *.exe as binary, take a bytes that are at offsets 0x3C and 0x3D the WORD made of these is offset of PE beginning.
After you have offset, validate if you are right, beginning of PE is PE\0\0 (PE and two NULL symbols), just skip this indication block and read the next two bytes and make another WORD. It indicates target machine, if you want to check for 64-bit it will be one of the these:
0x8664 - x64
0xaa64 - ARMv8 in 64-bit mode
0x0200 - Intel Itanium processor family
NOTE after you read two bytes, to make WORD you may need to flip them.
Also take a look at this question with answers
And also download PE header documentation from Microsoft
I have knocked up a console application utility in C# to check. It's basic but can be expanded upon.
It returns informational text to the console but also uses the exit code to indicate the type just in case you wish to use this in a batch file.
See below
Cheers
Roj
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ExeType
{
class Program
{
const int st_error = 99;
const int st_unknown = 98;
const int st_unidentified = 97;
const int st_not_PE_image = 96;
const int st_Exec_x86 = 1;
const int st_Exec_x64 = 2;
const int st_offset = 0x3c;
const int st_P = 0x50;
const int st_E = 0x45;
const int st_ind_x86_1 = 0x4c;
const int st_ind_x86_2 = 0x1;
const int st_ind_x64_1 = 0x64;
const int st_ind_x64_2 = 0x86;
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Please specify a file");
Environment.Exit(st_error);
}
BinaryReader br = new BinaryReader(File.OpenRead(args[0]));
byte[] block = br.ReadBytes(0x1000);
br.Close();
if (block.Length < st_offset+1)
{
Console.WriteLine("Unknown");
Environment.Exit(st_unknown);
}
int offset = (block[st_offset+1] << 8) + block[st_offset];
if (block.Length < offset)
{
Console.WriteLine("Unknown");
Environment.Exit(st_unknown);
}
int indicator1 = block[offset];
int indicator2 = block[offset+1];
if (indicator1 != st_P || indicator2 != st_E)
{
Console.WriteLine("Not a PE format image file");
Environment.Exit(st_not_PE_image);
}
offset += 4;
indicator1 = block[offset];
indicator2 = block[offset + 1];
int retval = st_unidentified;
switch (indicator1)
{
case st_ind_x86_1:
if (indicator2 == st_ind_x86_2)
{
Console.WriteLine("32 bit");
retval = st_Exec_x86;
}
break;
case st_ind_x64_1:
if (indicator2 == st_ind_x64_2)
{
Console.WriteLine("64 bit");
retval = st_Exec_x64;
}
break;
default:
Console.WriteLine("Unidentified");
break;
}
Environment.Exit( retval );
}
}
}
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.)
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")
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.
I am writing an adapter TSP for a phone system. This system has a TAPI API but it is incompatible with the application I am trying to TAPI-enable. In order to place a call from the correct line, I need to know some information (from HKCU) about who is making the request. Since the TSP runs in the context of the Telephony service, I cannot access is directly. My plan was to use the functionality of LINE_CREATEDIALOGINSTANCE to read this information.
The problem I'm having is that the Telephony service is crashing immediately after returning from TUISPI_providerGenericDialog with the following stack trace:
72004400()
tapisrv.dll!_FreeDialogInstance#20() + 0xa93 bytes
tapisrv.dll!_ClientRequest#16() + 0x8f bytes
rpcrt4.dll!_Invoke#12() + 0x30 bytes
rpcrt4.dll!_NdrStubCall2#16() + 0x217 bytes
rpcrt4.dll!_NdrServerCall2#4() + 0x19 bytes
rpcrt4.dll!_DispatchToStubInCNoAvrf#12() + 0x17 bytes
rpcrt4.dll!RPC_INTERFACE::DispatchToStubWorker() + 0xae bytes
rpcrt4.dll!RPC_INTERFACE::DispatchToStub() + 0x4b bytes
rpcrt4.dll!LRPC_SCALL::DealWithRequestMessage() + 0x1d5 bytes
rpcrt4.dll!LRPC_ADDRESS::DealWithLRPCRequest() + 0x90 bytes
rpcrt4.dll!LRPC_ADDRESS::ReceiveLotsaCalls() + 0x20c bytes
rpcrt4.dll!RecvLotsaCallsWrapper() + 0xd bytes
rpcrt4.dll!BaseCachedThreadRoutine() + 0x92 bytes
rpcrt4.dll!ThreadStartRoutine() + 0x1b bytes
kernel32.dll!_BaseThreadStart#8() + 0x34 bytes
As per this book, the Telephony service will crash if TSPI_providerFreeDialogInstance is not implemented. I have implemented this function and DepWalker shows it as being properly exported. ApiSpy32 shows that its address is correctly returned via GetProcAddress when my TSP is loaded. Why is it still crashing?
The relevant code:
LONG TSPIAPI TSPI_lineMakeCall(DRV_REQUESTID dwRequestID, HDRVLINE hdLine, HTAPICALL htCall,
LPHDRVCALL lphdCall, LPCWSTR lpszDestAddress, DWORD dwCountryCode, LPLINECALLPARAMS const lpCallParams)
{
OutputDebugString("TSPI_lineMakeCall\n");
PDRVLINE pLine = (PDRVLINE) hdLine;
*lphdCall = (HDRVCALL)hdLine;
typedef TUISPICREATEDIALOGINSTANCEPARAMS PARAMS;
pLine->htCall = htCall;
DWORD lLength = (lstrlenW(lpszDestAddress) + 1) * sizeof(WCHAR);
PARAMS* lParams = (PARAMS*)DrvAlloc(sizeof(PARAMS) + lLength);
RtlZeroMemory(lParams, sizeof(PARAMS) + lLength);
lParams->dwRequestID = dwRequestID;
lParams->hdDlgInst = (HDRVDIALOGINSTANCE)1000;
lParams->lpszUIDLLName = L"TapiAdapter.tsp";
lParams->lpParams = lParams + 1;
lParams->dwSize = lLength;
lstrcpyW((LPWSTR)(lParams + 1), lpszDestAddress);
(*pLine->pfnEventProc)(pLine->htLine, 0, LINE_CREATEDIALOGINSTANCE, (DWORD)lParams, 0, 0);
return dwRequestID;
}
LONG TSPIAPI TSPI_providerGenericDialogData(DWORD_PTR dwObjectID, DWORD dwObjectType, LPVOID lpParams, DWORD dwSize)
{
OutputDebugString("TSPI_providerGenericDialogData\n");
return 0;
}
LONG TSPIAPI TSPI_providerFreeDialogInstance(HDRVDIALOGINSTANCE hdDlgInst)
{
OutputDebugString("TSPI_providerFreeDialogInstance\n");
return 0;
}
LONG TSPIAPI TUISPI_providerGenericDialog(TUISPIDLLCALLBACK lpfnUIDLLCallback, HTAPIDIALOGINSTANCE htDlgInst, LPVOID lpParams, DWORD dwSize, HANDLE hEvent)
{
SetEvent(hEvent);
LPCWSTR lNumber = (LPCWSTR)lpParams;
MessageBoxW(0, lNumber, L"Dial Number", MB_OK);
return 0;
}
I don't know but the help for the TUISPICREATEDIALOGINSTANCEPARAMS Structure says that lpszUIDLLName should be a ...
pointer to a NULL-terminated string
specifying the fully qualified name of
the UI DLL to load in the application
context
... however L"TapiAdapter.tsp" doesn't look like a fully qualified name of the UI DLL ("fully-qualified" means that it includes the path name). Do you have a UI DLL to be loaded? Is it loaded? Does it display the dialog? Is it unloaded? Does TUISPI_providerGenericDialog exist in your TSP, or does it existin your UI DLL (they're supposed to be two different DLLs)?
I have found the solution: As per MSDN, the first parameter of the LINEEVENT call for this event only needs to be an HPROVIDER, not an HTAPILINE. Since the first parameter of LINEEVENT is of type HTAPILINE, the HPROVIDER will need to be cast.