Skip to content

Instantly share code, notes, and snippets.

@pavel-a
Last active March 17, 2020 05:53
Show Gist options
  • Save pavel-a/c6ff584f9c758f37135a1ca632da9bf9 to your computer and use it in GitHub Desktop.
Save pavel-a/c6ff584f9c758f37135a1ca632da9bf9 to your computer and use it in GitHub Desktop.
Small utility to detect when a VC++ program cannot start because runtime libs or some other DLL is missing.

Launcher-vc

This is a small utility to detect when a program cannot start because VC++ runtime or .NET, or some DLL is missing. It can be useful for verifying automated builds or deployment.

No popups like "Application error... The application failed to initialize properly..." should be displayed. If the app cannot initialize, there will be a plain message printed saying so, and errorlevel will be set.

Usage: launcher-vc <app.exe> [arg for the app.exe]

The optional arg for the app is some parameter that causes the app exit quickly when it can start, such as "/help". Only one arg can be currently passed.

The timeout for the app to exit is 0.5 second. Change it in the code if needed.

Errorlevel:

0 : the app successfully started and exited with any status < STILL_ACTIVE

1, 2 : error in parameters or starting the app

3 : detected app startup errors related to missing/wrong DLLs

4 : other error or exception

104 : the app has been successfully started and still runs.

/**
* Utility to detect when program cannot start because .NET or VC++
* runtime or some DLL is missing.
* It hides the annoying message box:
* "Application error... The application failed to initialize properly..."
*
* Usage: launcher-vc <app.exe> [arg for the app.exe]
*
* No popups should be displayed. If the app cannot initialize,
* there will be a plain message printed saying so, and errorlevel will be set.
*
* The optional arg for the app is some parameter that causes the app
* exit quickly in case it _can_ start, such as "/help"
* ( note: only one arg is currently accepted )
*
* Changelog:
* pa 2013-04-29 Update to vc++ 2012 R2
* pa02 2010-07-29 - Created. tested on VC2005, XP SP3
*/
#define _CRT_SECURE_NO_WARNINGS 1 /* we know what we do. sometimes. */
#if 1 // ntstatus
// Older VC versions do not have in windows.h (winnt.h) statuses that I need below.
// So instead include ntstatus.h and define WIN32_NO_STATUS before windows.h.
typedef signed long NTSTATUS;
#include <ntstatus.h>
#define WIN32_NO_STATUS 1
#endif // ntstatus
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef STATUS_SXS_CANT_GEN_ACTCTX //+ values missing in old sdk:
#define STATUS_SXS_CANT_GEN_ACTCTX 0xc0150002L
#define STATUS_ORDINAL_NOT_FOUND 0xC0000138L
#define STATUS_ENTRYPOINT_NOT_FOUND 0xC0000139L
#define STATUS_DLL_INIT_FAILED 0xC0000142L
#define STATUS_DLL_NOT_FOUND 0xC0000135L
#endif //-
#include <stdio.h>
#include <malloc.h>
#define EXST_ERR_PARAMS 1
#define EXST_ERR_PREP 2
#define EXST_ERR_START 3
#define EXST_ERR_OTHER 4
#define EXST_RUNAWAY (STILL_ACTIVE+1)
static const int launchTimeoutMs = 500; //TODO tweak as needed if the app needs more time to init & exit
static
int launcher( __in wchar_t const *exe, __in_opt wchar_t const *arg, __out UINT32 *exstatus )
{
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD cflags = 0;
BOOL b = FALSE;
DWORD ex;
auto prev_em = SetErrorMode( SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX|SEM_NOGPFAULTERRORBOX );
*exstatus = -1;
memset( &pi, 0, sizeof(pi) );
memset( &si, 0, sizeof(si) );
si.cb = sizeof(STARTUPINFOW);
LPWSTR cmdline = NULL;
if ( arg ) {
// paste this as 2nd token to the command line. 1st token must be prog name. TODO pass real app name?
static const wchar_t *fakename = L"app.exe ";
wchar_t *p = (wchar_t *)alloca( sizeof(wchar_t) * (wcslen(arg) + wcslen(fakename) + 3) );
if ( p ) {
wcscpy( p, fakename );
wcscat( p, arg );
cmdline = p;
}
}
__try {
b = CreateProcessW( exe, cmdline, NULL, NULL, FALSE, cflags, NULL, NULL, &si, &pi );
} __finally {
SetErrorMode( prev_em );
}
if ( !b ) {
*exstatus = GetLastError();
printf("Err CreateProcess: %u\n", *exstatus );
return EXST_ERR_PREP;
}
// CreateProcess worked. Now wait it to exit:
ex = WaitForSingleObject(pi.hProcess, launchTimeoutMs);
switch ( ex ) {
case WAIT_OBJECT_0:
if ( !GetExitCodeProcess( pi.hProcess, &ex ) ) {
printf("Err GetExitCodeProcess: %u\n", GetLastError() );
*exstatus = ERROR_INVALID_HANDLE; //?
return EXST_ERR_PREP;
}
break;
case WAIT_TIMEOUT:
ex = STILL_ACTIVE;
break;
default:
printf("Error waiting for app exit: %#X gle=%u\n", ex, GetLastError());
ex = ERROR_INVALID_HANDLE; //?
}
if ( ex == STILL_ACTIVE ) {
printf("App still runs: pid=%u\n", pi.dwProcessId );
}
if ( pi.hProcess ) {
CloseHandle( pi.hProcess );
}
if ( pi.hThread ) {
CloseHandle( pi.hThread );
}
*exstatus = ex;
return 0;
}
int wmain( int argc, wchar_t **argv )
{
UINT32 ret = EXST_ERR_OTHER;
UINT32 exstatus = 0;
if ( (argc < 2) || (argc > 3) ) {
printf("App startup diagnostic utility r.2a (" __DATE__ ")\n");
printf("Usage: launcher <exe-to-run> [arg]\n");
return 1;
}
// printf( "args 0=[%ws] 1=[%ws]\n\n", argv[0], argv[1]);
__try {
ret = launcher( argv[1], argc > 2 ? argv[2] : NULL, &exstatus );
} __except(EXCEPTION_EXECUTE_HANDLER) {
ret = EXST_ERR_OTHER;
}
if ( ret != 0 ) {
printf("Error launching specified program\n");
switch ( exstatus ) {
case ERROR_SXS_CANT_GEN_ACTCTX: //14001
printf("VC++ 2005/2008 runtime or other SxS library/assembly is not installed properly"
" (ERROR_SXS_CANT_GEN_ACTCTX)\n");
ret = EXST_ERR_START; // indicate that known startup error occurred
break;
case ERROR_DLL_NOT_FOUND:
printf("Some required DLL(s) not found (ERROR_DLL_NOT_FOUND)\n");
ret = EXST_ERR_START;
break;
case ERROR_DLL_INIT_FAILED:
printf("Some DLL failed initialization (ERROR_DLL_INIT_FAILED)\n");
ret = EXST_ERR_START;
break;
case ERROR_EXTENDED_ERROR:
printf("A complicated error occurred (ERROR_EXTENDED_ERROR)\n");
ret = EXST_ERR_START;
break;
case ERROR_PATH_NOT_FOUND:
case ERROR_FILE_NOT_FOUND:
printf("File or path not found\n");
break;
default:
break;
}
return EXST_ERR_PREP;
}
// The app has been started, in sense that CreateProcess worked.
// Loader errors occur in the context of the process itself.
if ( exstatus == STILL_ACTIVE ) {
printf("\nLauncher: the app still runs.............\n");
return EXST_RUNAWAY;
}
// it exited with some normal status
if ( exstatus < 0x80000000 ) {
printf("Launcher: the app has run and exited with status %u (0x%x)\n", exstatus, exstatus);
return 0;
}
printf("\nLauncher: the app exited because of exception ");
switch( exstatus) {
case STATUS_SXS_CANT_GEN_ACTCTX:
printf("STATUS_SXS_CANT_GEN_ACTCTX:\n"
".NET or VC++ runtime or other required SxS library/assembly is not installed\n");
break;
case STATUS_DLL_NOT_FOUND:
printf("STATUS_DLL_NOT_FOUND: some required DLL(s) not found\n");
break;
case STATUS_ENTRYPOINT_NOT_FOUND:
printf("STATUS_ENTRYPOINT_NOT_FOUND: some required DLL is wrong/old version\n");
break;
case STATUS_ORDINAL_NOT_FOUND:
printf("STATUS_ORDINAL_NOT_FOUND: some required DLL is wrong/old version\n");
break;
case STATUS_DLL_INIT_FAILED:
printf("STATUS_DLL_INIT_FAILED: some DLL has failed its initialization\n");
break;
case STATUS_BREAKPOINT:
printf("STATUS_BREAKPOINT: assertion failed or debug breakpoint hit\n");
break;
case STATUS_SINGLE_STEP:
printf("STATUS_SINGLE_STEP: assertion failed or debug breakpoint hit\n");
break;
default:
printf( "0x%x (%d)\n", exstatus, exstatus);
//TODO: print description
}
printf("The app may or may not start run.\n");
return EXST_ERR_START;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment