What is KEY_WOW64_RES? - winapi

In the windows headers, there are
//
// Registry Specific Access Rights.
//
#define KEY_QUERY_VALUE (0x0001)
#define KEY_SET_VALUE (0x0002)
#define KEY_CREATE_SUB_KEY (0x0004)
#define KEY_ENUMERATE_SUB_KEYS (0x0008)
#define KEY_NOTIFY (0x0010)
#define KEY_CREATE_LINK (0x0020)
#define KEY_WOW64_32KEY (0x0200)
#define KEY_WOW64_64KEY (0x0100)
#define KEY_WOW64_RES (0x0300)
These are all well documented in the MSDN article, Registry Key Security and Access Rights, except
KEY_WOW64_RES. What does this mean? It appears to turn on contradictory flags.

KEY_WOW64_RES is a combination of both KEY_WOW64_32KEY and KEY_WOW64_64KEY. It is useful for masking out WOW64 bits, such as when you need to make changes to a existing rights mask but want to preserve the WOW64 bits.
For example, you can AND a mask with KEY_WOW64_RES to retrieve its existing WOW64 bits, mask out KEY_WOW64_RES from the new mask, and then OR the original WOW64 bits into the new mask, eg:
REGSAM Rights = ...;
REGSAM Wow64Rights = rights & KEY_WOW64_RES;
Rights = (NewRights & ~KEY_WOW64_RES) | Wow64Rights;
One library I know uses KEY_WOW64_RES when attempting to open a Registry key for read-only access using less restrictive rights each time it fails:
// Preserve KEY_WOW64_XXX flags for later use
WOWFlags = FAccess & KEY_WOW64_RES;
Result = RegOpenKeyEx(..., KEY_READ | WOWFlags, ...);
if (Result == 0)
{
...
}
else
{
Result = RegOpenKeyEx(..., STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | WOWFlags, ...);
if (Result == 0)
{
...
}
else
{
Result = RegOpenKeyEx(..., KEY_QUERY_VALUE | WOWFlags, ...);
if (Result == 0)
{
...
}
}
}

Related

Exact way to get permissions of a file in windows using C++ and api

My project is like i need to modify the file permissions from a web application. I'm using java for backend and emberjs for client side. To get the file permissions I'm using C++ native code with windows api with JNI. Here is my problem,
I need to get the permissions of the files in a directory in windows using api. I'm new to windows api so I've googled and got the below code and modified it to my needs. Now the problem is when i run this, it gives me the results when the file has "Full Control" as permission otherwise the permissions are not showing. Please help me with this. What need to be modified in here or if there are any other possible solutions, please suggest me that too. Thanks in advance.
Here is my code,
#include <Windows.h>
#include <vector>
#include <map>
#include <iostream>
#include <aclapi.h>
#include <windows.h>
#include <string>
#include <memory>
#include <tchar.h>
using namespace std;
bool CanAccessFolder(LPCTSTR folderName, DWORD genericAccessRights,DWORD& grantedRights)
{
bool bRet = false;
DWORD length = 0;
if (!::GetFileSecurity(folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION, NULL, NULL, &length) &&
ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) {
PSECURITY_DESCRIPTOR security = static_cast< PSECURITY_DESCRIPTOR >(::malloc(length));
if (security && ::GetFileSecurity(folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION, security, length, &length)) {
HANDLE hToken = NULL;
if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_IMPERSONATE | TOKEN_QUERY |
TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &hToken)) {
HANDLE hImpersonatedToken = NULL;
if (::DuplicateToken(hToken, SecurityImpersonation, &hImpersonatedToken)) {
GENERIC_MAPPING mapping = { 0xFFFFFFFF };
PRIVILEGE_SET privileges = { 0 };
DWORD grantedAccess = 0, privilegesLength = sizeof(privileges);
BOOL result = FALSE;
mapping.GenericRead = FILE_GENERIC_READ;
mapping.GenericWrite = FILE_GENERIC_WRITE;
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
mapping.GenericAll = FILE_ALL_ACCESS;
::MapGenericMask(&genericAccessRights, &mapping);
if (::AccessCheck(security, hImpersonatedToken, genericAccessRights,
&mapping, &privileges, &privilegesLength, &grantedAccess, &result))
{
bRet = (result == TRUE);
grantedRights = grantedAccess;
}
::CloseHandle(hImpersonatedToken);
}
::CloseHandle(hToken);
}
::free(security);
}
}
return bRet;
}
vector<string> printMasks(DWORD Mask)
{
// This evaluation of the ACCESS_MASK is an example.
// Applications should evaluate the ACCESS_MASK as necessary.
vector<string> access;
std::wcout << "Effective Allowed Access Mask : "<< Mask << std::hex << std::endl;
if (((Mask & GENERIC_ALL) == GENERIC_ALL)
|| ((Mask & FILE_ALL_ACCESS) == FILE_ALL_ACCESS))
{
// wprintf_s(L"Full Control\n");
access.push_back("Full Control");
// return access;
}
if (((Mask & GENERIC_READ) == GENERIC_READ)
|| ((Mask & FILE_GENERIC_READ) == FILE_GENERIC_READ))
// wprintf_s(L"Read\n");
access.push_back("Read");
if (((Mask & GENERIC_WRITE) == GENERIC_WRITE)
|| ((Mask & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE))
// wprintf_s(L"Write\n");
access.push_back("Write");
if (((Mask & GENERIC_EXECUTE) == GENERIC_EXECUTE)
|| ((Mask & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE))
// wprintf_s(L"Execute\n");
access.push_back("Execute");
return access;
}
std::map<std::string, std::vector<std::string>>
list_directory(const std::string &directory)
{
DWORD access_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | FILE_ALL_ACCESS;
std::map<std::string, std::vector<std::string>> files;
WIN32_FIND_DATAA findData;
HANDLE hFind = INVALID_HANDLE_VALUE;
std::string full_path = directory + "\\*";
hFind = FindFirstFileA(full_path.c_str(), &findData);
if (hFind == INVALID_HANDLE_VALUE)
throw std::runtime_error("Invalid handle value! Please check your path...");
while (FindNextFileA(hFind, &findData) != 0)
{
std::string file = findData.cFileName;
std::string filepath = directory + "/" + file;
DWORD grant = 0;
bool b = CanAccessFolder(filepath.c_str(), access_mask, grant);
files[file] = printMasks(grant);
}
FindClose(hFind);
return files;
}
int main() {
std::map<std::string, std::vector<std::string>> files;
files = list_directory("C:/Users/Vicky/Desktop/samples");
int i = 1;
map<string, vector<string>> :: iterator it=files.begin();
//iteration using traditional for loop
for(it=files.begin();it!=files.end();it++)
{
//accessing keys
cout << it->first << " : \t";
//accessing values (vectors)
for (auto &&i : it->second)
{
cout << i << "\t";
}
cout << endl;
}
}
Here are the results,
sample1.txt permissions
sample2.txt permissions
When you are performing the access check the line
DWORD access_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | FILE_ALL_ACCESS;
Is specifying that you are checking that the if you have read/write/execute/full control access to each item you are checking.
As a result when you call AccessCheck on sample2.txt where you don't have all those permissions AccessCheck reports in its last parameter that you don't have access. In that case MSDN for the GrantedAccess parameter states
[out] GrantedAccess
A pointer to an access mask that receives the granted access rights. If >AccessStatus is set to FALSE, the function sets the access mask to zero. If
the function fails, it does not set the access mask.
That access mask of all zero's is what you are printing out for sample2.txt
If you want to see what you can actually do for each file change the line above to
DWORD access_mask = MAXIMUM_ALLOWED;
This causes the file to be checked for whatever access it can get hold of rather than full control which it does not have.

Force a DLL to be loaded above 2GB (0x80000000) in a 32-bit process on Windows

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

RtlQueryRegistryValues read REG_DWORD

I'm writing a Uart16550 driver, and one of the things I have to do is read from the registry some initialization parameters using RtlQueryRegistryValues.
(a lot of code skipped...)
RTL_QUERY_REGISTRY_TABLE table[2];
and
table[1].QueryRoutine = NULL;
table[1].Name = NULL;
The parameter is stored in the registry as a REG_DWORD and I'm trying to set my table with:
unsigned long buffer;
(because DWORD is not defined in ntddk.h and I'm not sure if I break anything including WinDef.h)
and
table[0].EntryContext = &buffer;
The status of the call to RtlQueryRegistryValues is STATUS_SUCCESS
status = RtlQueryRegistryValues(
RTL_REGISTRY_ABSOLUTE,
path,
table,
NULL,
NULL);
but when I try to
DbgPrint("registry: %lu", buffer)
I get way too big numbers (I expected the result to be 1 or 2).
Am I doing something wrong with the DbgPrint, or my parameters aren't set correctly for the query table?
And where is the RED_DWORD data type defined?
EDIT:
WCHAR keyname[20] = "parameter";
CHAR def[20] = "parameter";
WCHAR path[100] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\hello\\Parameters";
unsigned long buffer;
table[0].QueryRoutine = NULL;
table[0].Name = keyname;
table[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
table[0].EntryContext = &buffer;
table[0].DefaultType = REG_DWORD;
table[0].DefaultData = def;
table[0].DefaultLength = strlen(def) + 1;
table[0].DefaultData should be a pointer to an unsigned long.
table[0].DefaultLength should be sizeof(unsigned long).
Otherwise you may get back 1634886000 which is your default value converted to long ('para'). Everything else looks like it should work (assuming the key and value actually exists).
REG_DWORD is defined in winnt.h
#define REG_DWORD ( 4 ) // 32-bit number
#define REG_DWORD_LITTLE_ENDIAN ( 4 ) // 32-bit number (same as REG_DWORD)
#define REG_DWORD_BIG_ENDIAN ( 5 ) // 32-bit number

force any running process to crash

I would like to crash a running program of my choice (e.g., notepad++, becrypt, word) for software testing purposes.
I know how to BSOD, I know how to cause a program I write to crash, I know how to end process - but how to crash an existing process I do not!
any help?
Well, use CreateRemoteThread on a remote process and invoke something [1] that crashes the process reliably. I'm not sure whether CreateRemoteThread guards against null pointers, but you could pass an address in the null page to it and have the remote process execute that.
[1] null pointer or null page access, division by zero, invoking a privileged instruction, int3 ...
Example:
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
BOOL setCurrentPrivilege(BOOL bEnable, LPCTSTR lpszPrivilege)
{
HANDLE hToken = 0;
if(::OpenThreadToken(::GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &hToken)
|| ::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
TOKEN_PRIVILEGES tp;
LUID luid;
if(!::LookupPrivilegeValue(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid ) ) // receives LUID of privilege
{
::CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = (bEnable) ? SE_PRIVILEGE_ENABLED : 0;
// Enable the privilege or disable all privileges.
if(!::AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL)
)
{
CloseHandle(hToken);
return FALSE;
}
::CloseHandle(hToken);
}
return TRUE;
}
int killProcess(DWORD processID)
{
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if(hProcess)
{
if(!setCurrentPrivilege(TRUE, SE_DEBUG_NAME))
{
_tprintf(TEXT("Could not enable debug privilege\n"));
}
HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)1, NULL, 0, NULL);
if(hThread)
{
::CloseHandle(hThread);
}
else
{
_tprintf(TEXT("Error: %d\n"), GetLastError());
::CloseHandle(hProcess);
return 1;
}
::CloseHandle(hProcess);
}
return 0;
}
int __cdecl _tmain(int argc, _TCHAR *argv[])
{
killProcess(3016);
}
Of course you'll want to adjust the PID in the call to killProcess. Compiled with WNET DDK and tested on 2003 Server R2.
The gist here is that we tell the remote process to execute code at address 0x1 ((LPTHREAD_START_ROUTINE)1), which is inside the null page but not a null pointer (in case there are checks against that). The crud around the function, in particular setCurrentPrivilege is used to gain full debug privileges so we can do our evil deed.
You can use DLL injection technique in order to inject your code into another process. Then in your injected code do something simple like abort() or division by zero.
A two steps mechanism is needed:
inject the process to crash (using an injection library, using Detours, using a Hook installation, etc..). What you choose depends on the time and knowledge you have and other preconditions (like credentials, anti-injection protection, size of the foot-print you want to leave..)
perform an invalid operation in the injected process (like int 2Eh, divide by null, etc..)
Here's how to do that with the winapiexec tool:
winapiexec64.exe CreateRemoteThread ( OpenProcess 0x1F0FFF 0 1234 ) 0 0 0xDEAD 0 0 0
Replace 1234 with the process id and run the command, the process will crash.

Exit code "lost" from child process in Windows XP, not in Windows Server 2003

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.

Resources