/*++ Copyright (c) 1988-1999 Microsoft Corporation Module Name: cext.c Abstract: External command support --*/ #include "cmd.h" #define DBENV 0x0080 #define DBENVSCAN 0x0010 HANDLE hChildProcess; unsigned start_type ; /* D64 */ extern UINT CurrentCP; extern TCHAR Fmt16[] ; /* @@5h */ extern unsigned DosErr ; extern BOOL CtrlCSeen; extern TCHAR CurDrvDir[] ; extern TCHAR CmdExt[], BatExt[], PathStr[] ; extern TCHAR PathExtStr[], PathExtDefaultStr[]; extern TCHAR ComSpec[] ; /* M033 - Use ComSpec for SM memory */ extern TCHAR ComSpecStr[] ; /* M033 - Use ComSpec for SM memory */ extern void tokshrink(TCHAR*); extern TCHAR PathChar ; extern TCHAR SwitChar ; extern PTCHAR pszTitleCur; extern BOOLEAN fTitleChanged; extern int LastRetCode ; extern HANDLE PipePid ; /* M024 - Store PID from piped cmd */ extern struct envdata CmdEnv ; // Holds info to manipulate Cmd's environment extern int glBatType; // to distinguish OS/2 vs DOS errorlevel behavior depending on a script file name TCHAR szNameEqExitCodeEnvVar[] = TEXT ("=ExitCode"); TCHAR szNameEqExitCodeAsciiEnvVar[] = TEXT ("=ExitCodeAscii"); WORD GetProcessSubsystemType( HANDLE hProcess ); /*** ExtCom - controls the execution of external programs * * Purpose: * Synchronously execute an external command. Call ECWork with the * appropriate values to have this done. * * ExtCom(struct cmdnode *n) * * Args: * Parse tree node containing the command to be executed. * * Returns: * Whatever ECWork returns. * * Notes: * During batch processing, labels are ignored. Empty commands are * also ignored. * */ int ExtCom(n) struct cmdnode *n ; { if (CurrentBatchFile && *n->cmdline == COLON) return(SUCCESS) ; if (n && n->cmdline && mystrlen(n->cmdline)) { return(ECWork(n, AI_SYNC, CW_W_YES)) ; /* M024 */ } ; return(SUCCESS) ; } /********************* START OF SPECIFICATION **************************/ /* */ /* SUBROUTINE NAME: ECWork */ /* */ /* DESCRIPTIVE NAME: Execute External Commands Worker */ /* */ /* FUNCTION: Execute External Commands */ /* This routine calls SearchForExecutable routine to search */ /* for the executable command. If the command ( .EXE, .COM, */ /* or .CMD file ) is found, the command is executed. */ /* */ /* ENTRY POINT: ECWork */ /* LINKAGE: NEAR */ /* */ /* INPUT: n - the parse tree node containing the command to be executed*/ /* */ /* ai - the asynchronous indicator */ /* - 0 = Exec synchronous with parent */ /* - 1 = Exec asynchronous and discard child return code */ /* - 2 = Exec asynchronous and save child return code */ /* */ /* wf - the wait flag */ /* - 0 = Wait for process completion */ /* - 1 = Return immediately (Pipes) */ /* */ /* OUTPUT: None. */ /* */ /* EXIT-NORMAL: */ /* If synchronous execution, the return code of the command is */ /* returned. */ /* */ /* If asynchronous execution, the return code of the exec call */ /* is returned. */ /* */ /* EXIT-ERROR: */ /* Return FAILURE to the caller. */ /* */ /* */ /********************** END OF SPECIFICATION **************************/ /*** ECWork - begins the execution of external commands * * Purpose: * To search for and execute an external command. Update LastRetCode * if an external program was executed. * * int ECWork(struct cmdnode *n, unsigned ai, unsigned wf) * * Args: * n - the parse tree node containing the command to be executed * ai - the asynchronous indicator * - 0 = Exec synchronous with parent * - 1 = Exec asynchronous and discard child return code * - 2 = Exec asynchronous and save child return code * wf - the wait flag * - 0 = Wait for process completion * - 1 = Return immediately (Pipes) * * Returns: * If syncronous execution, the return code of the command is returned. * If asyncronous execution, the return code of the exec call is returned. * * Notes: * The pid of a program that will be waited on is placed in the global * variable Retcds.ptcod so that WaitProc can use it and so SigHand() * can kill it if necessary (only during SYNC exec's). * M024 - Added wait flag parm so pipes can get immediate return while * still doing an AI_KEEP async exec. * - Considerable revisions to structure. */ int ECWork(n, ai, wf) struct cmdnode *n ; unsigned ai ; unsigned wf ; { TCHAR *fnptr, /* Ptr to filename */ *argptr, /* Command Line String */ *tptr; /* M034 - Temps */ int i ; /* Work variable */ TCHAR * onb ; /* M035 - Obj name buffer */ ULONG rc; if (!(fnptr = mkstr(MAX_PATH*sizeof(TCHAR)+sizeof(TCHAR)))) /* Loc/nam of file to exec */ return(FAILURE) ; argptr = GetTitle( n ); tptr = argptr; if (argptr == NULL) { return( FAILURE ); } i = SearchForExecutable(n, fnptr) ; if (i == SFE_ISEXECOM) { /* Found .com or .exe file */ if (!(onb = (TCHAR *)mkstr(MAX_PATH*sizeof(TCHAR)+sizeof(TCHAR)))) /* M035 */ { return(FAILURE) ; } SetConTitle( tptr ); rc = ExecPgm( n, onb, ai, wf, argptr, fnptr, tptr); ResetConTitle( pszTitleCur ); return( rc ); } ; if (i == SFE_ISBAT) { /* Found .cmd file, call BatProc */ SetConTitle(tptr); rc = BatProc(n, fnptr, BT_CHN) ; ResetConTitle( pszTitleCur ); return(rc) ; } ; if (i == SFE_ISDIR) { return ChangeDir2(fnptr, TRUE); } LastRetCode = DosErr; if (i == SFE_FAIL) { /* Out of Memory Error */ return(FAILURE) ; } ; if (DosErr == MSG_DIR_BAD_COMMAND_OR_FILE) { PutStdErr(DosErr, ONEARG, n->cmdline); } else { PutStdErr(DosErr, NOARGS); } return(FAILURE) ; } #ifndef WIN95_CMD VOID RestoreCurrentDirectories( VOID ) /* this routine sets the current process's current directories to those of the child if the child was the VDM to fix DOS batch files. */ { ULONG cchCurDirs; UINT PreviousErrorMode; PTCHAR pCurDirs,pCurCurDir; BOOL CurDrive=TRUE; #ifdef UNICODE PCHAR pT; #endif cchCurDirs = GetVDMCurrentDirectories( 0, NULL ); if (cchCurDirs == 0) return; pCurDirs = gmkstr(cchCurDirs*sizeof(TCHAR)); #ifdef UNICODE pT = gmkstr(cchCurDirs); #endif GetVDMCurrentDirectories( cchCurDirs, #ifdef UNICODE pT #else pCurDirs #endif ); #ifdef UNICODE MultiByteToWideChar(CurrentCP, MB_PRECOMPOSED, pT, -1, pCurDirs, cchCurDirs); #endif // set error mode so we don't get popup if trying to set curdir // on empty floppy drive PreviousErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); for (pCurCurDir=pCurDirs;*pCurCurDir!=NULLC;pCurCurDir+=(_tcslen(pCurCurDir)+1)) { ChangeDir2(pCurCurDir,CurDrive); CurDrive=FALSE; } SetErrorMode(PreviousErrorMode); //free(pCurDirs); #ifdef UNICODE FreeStr((PTCHAR)pT); #endif } #endif // WIN95_CMD /********************* START OF SPECIFICATION **************************/ /* */ /* SUBROUTINE NAME: ExecPgm */ /* */ /* DESCRIPTIVE NAME: Call DosExecPgm to execute External Command */ /* */ /* FUNCTION: Execute External Commands with DosExecPgm */ /* This routine calls DosExecPgm to execute the command. */ /* */ /* */ /* NOTES: This is a new routine added for OS/2 1.1 release. */ /* */ /* */ /* ENTRY POINT: ExecPgm */ /* LINKAGE: NEAR */ /* */ /* INPUT: n - the parse tree node containing the command to be executed*/ /* */ /* ai - the asynchronous indicator */ /* - 0 = Exec synchronous with parent */ /* - 1 = Exec asynchronous and discard child return code */ /* - 2 = Exec asynchronous and save child return code */ /* */ /* wf - the wait flag */ /* - 0 = Wait for process completion */ /* - 1 = Return immediately (Pipes) */ /* */ /* OUTPUT: None. */ /* */ /* EXIT-NORMAL: */ /* If synchronous execution, the return code of the command is */ /* returned. */ /* */ /* If asynchronous execution, the return code of the exec call */ /* is returned. */ /* */ /* EXIT-ERROR: */ /* Return FAILURE to the caller. */ /* */ /* EFFECTS: */ /* */ /* INTERNAL REFERENCES: */ /* ROUTINES: */ /* ExecError - Handles execution error */ /* PutStdErr - Print an error message */ /* WaitProc - wait for the termination of the specified process, */ /* its child process, and related pipelined */ /* processes. */ /* */ /* */ /* EXTERNAL REFERENCES: */ /* ROUTINES: */ /* DOSEXECPGM - Execute the specified program */ /* DOSSMSETTITLE - Set title for the presentation manager */ /* */ /********************** END OF SPECIFICATION **************************/ int ExecPgm( IN struct cmdnode *n, IN TCHAR *onb, IN unsigned int ai, IN unsigned int wf, IN TCHAR * argptr, IN TCHAR * fnptr, IN TCHAR * tptr ) { int i ; /* Work variable */ BOOL b; BOOL VdmProcess = FALSE; BOOL WowProcess = FALSE; BOOL GuiProcess = FALSE; LPTSTR CopyCmdValue; HDESK hdesk; LPTSTR lpDesktop; DWORD cbDesktop = 0; HWINSTA hwinsta; LPTSTR p; DWORD cbWinsta = 0; TCHAR szValEqExitCodeEnvVar [20]; TCHAR szValEqExitCodeAsciiEnvVar [12]; STARTUPINFO StartupInfo; PROCESS_INFORMATION ChildProcessInfo; memset( &StartupInfo, 0, sizeof( StartupInfo ) ); StartupInfo.cb = sizeof( StartupInfo ); StartupInfo.lpTitle = tptr; StartupInfo.dwX = 0; StartupInfo.dwY = 1; StartupInfo.dwXSize = 100; StartupInfo.dwYSize = 100; StartupInfo.dwFlags = 0;//STARTF_SHELLOVERRIDE; StartupInfo.wShowWindow = SW_SHOWNORMAL; // Pass current Desktop to a new process. hwinsta = GetProcessWindowStation(); GetUserObjectInformation( hwinsta, UOI_NAME, NULL, 0, &cbWinsta ); hdesk = GetThreadDesktop ( GetCurrentThreadId() ); GetUserObjectInformation (hdesk, UOI_NAME, NULL, 0, &cbDesktop); if ((lpDesktop = HeapAlloc (GetProcessHeap(), HEAP_ZERO_MEMORY, cbDesktop + cbWinsta + 32) ) != NULL ) { p = lpDesktop; if ( GetUserObjectInformation (hwinsta, UOI_NAME, p, cbWinsta, &cbWinsta) ) { if (cbWinsta > 0) { p += ((cbWinsta/sizeof(TCHAR))-1); *p++ = L'\\'; } if ( GetUserObjectInformation (hdesk, UOI_NAME, p, cbDesktop, &cbDesktop) ) { StartupInfo.lpDesktop = lpDesktop; } } } // // Incredibly ugly hack for Win95 compatibility. // // XCOPY in Win95 reaches up into it's parent process to see if it was // invoked in a batch file. If so, then XCOPY pretends COPYCMD=/Y // // There is no way we can do this for NT. The best that can be done is // to detect if we're in a batch file starting XCOPY and then temporarily // change COPYCMD=/Y. // { const TCHAR *p = MyGetEnvVarPtr( TEXT( "COPYCMD" )); if (p == NULL) { p = TEXT( "" ); } CopyCmdValue = malloc( (mystrlen( p ) + 1) * sizeof( TCHAR )); if (CopyCmdValue == NULL) { PutStdErr( MSG_NO_MEMORY, NOARGS ); return FAILURE; } mystrcpy( CopyCmdValue, p ); if ((SingleBatchInvocation || SingleCommandInvocation || CurrentBatchFile ) && (p = mystrrchr( fnptr, TEXT( '\\' ))) != NULL && !lstrcmp( p, TEXT( "\\XCOPY.EXE" )) ) { SetEnvVar( TEXT( "COPYCMD" ), TEXT( "/Y" ), NULL ); } } // // If the restricted token exists then create the process with the // restricted token. // Else create the process without any restrictions. // if ((CurrentBatchFile != NULL) && (CurrentBatchFile->hRestrictedToken != NULL)) { b = CreateProcessAsUser( CurrentBatchFile->hRestrictedToken, fnptr, argptr, (LPSECURITY_ATTRIBUTES) NULL, (LPSECURITY_ATTRIBUTES) NULL, TRUE, 0, NULL, CurDrvDir, &StartupInfo, &ChildProcessInfo ); } else { b = CreateProcess( fnptr, argptr, (LPSECURITY_ATTRIBUTES) NULL, (LPSECURITY_ATTRIBUTES) NULL, TRUE, 0, NULL, CurDrvDir, &StartupInfo, &ChildProcessInfo ); } if (!b) { DosErr = i = GetLastError(); } else { hChildProcess = ChildProcessInfo.hProcess; #if !defined( WIN95_CMD ) && DBG if (hChildProcess == NULL) { DbgBreakPoint( ); } #endif CloseHandle(ChildProcessInfo.hThread); } // // Undo ugly hack // SetEnvVar( TEXT( "COPYCMD" ), CopyCmdValue, NULL ); free( CopyCmdValue ); HeapFree (GetProcessHeap(), 0, lpDesktop); if (!b) { if (fEnableExtensions && DosErr == ERROR_BAD_EXE_FORMAT) { SHELLEXECUTEINFO sei; memset(&sei, 0, sizeof(sei)); // // Use the DDEWAIT flag so apps can finish their DDE conversation // before ShellExecuteEx returns. Otherwise, apps like Word will // complain when they try to exit, confusing the user. // sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_HASTITLE | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NOCLOSEPROCESS; sei.lpFile = fnptr; sei.lpParameters = n->argptr; sei.lpDirectory = CurDrvDir; sei.nShow = StartupInfo.wShowWindow; try { b = ShellExecuteEx( &sei ); if (b) { hChildProcess = sei.hProcess; leave; } if (sei.hInstApp == NULL) { DosErr = ERROR_NOT_ENOUGH_MEMORY; } else if ((DWORD_PTR)sei.hInstApp == HINSTANCE_ERROR) { DosErr = ERROR_FILE_NOT_FOUND; } else { DosErr = HandleToUlong(sei.hInstApp); } } except( DosErr = GetExceptionCode( ), EXCEPTION_EXECUTE_HANDLER ) { b = FALSE; } } if (!b) { mystrcpy( onb, fnptr ); ExecError( onb ) ; return (FAILURE) ; } } #ifndef WIN95_CMD VdmProcess = ((UINT_PTR)(hChildProcess) & 1) ? TRUE : FALSE; WowProcess = ((UINT_PTR)(hChildProcess) & 2) ? TRUE : FALSE; #endif // WIN95_CMD if (hChildProcess == NULL || (fEnableExtensions && CurrentBatchFile == 0 && !SingleBatchInvocation && !SingleCommandInvocation && ai == AI_SYNC && (WowProcess || GetProcessSubsystemType(hChildProcess) == IMAGE_SUBSYSTEM_WINDOWS_GUI ) ) ) { // // If extensions enabled and doing a synchronous exec of a GUI // application, then change it into an asynchronous exec, with the // return code discarded, ala Win 3.x and Win 95 COMMAND.COM // ai = AI_DSCD; GuiProcess = TRUE; } i = SUCCESS; start_type = EXECPGM; if (ai == AI_SYNC) { /* Async exec... */ LastRetCode = WaitProc( hChildProcess ); i = LastRetCode ; // // this is added initially to support new network batch scripts // but it can be used by any process. // // always set "=ExitCode" env. variable as string of 8 hex digits // representing LastRetCode _stprintf (szValEqExitCodeEnvVar, TEXT("%08X"), i); SetEnvVar(szNameEqExitCodeEnvVar, szValEqExitCodeEnvVar, &CmdEnv); // IF LastRetCode printable // THEN set "=ExitCodeAscii" env. variable equal LastRetCode // ELSE remove "=ExitCodeAscii" env. variable if ( (i >= 0x20) && (i <= 0x7e) ) { // isprint _stprintf (szValEqExitCodeAsciiEnvVar, TEXT("%01C"), i); SetEnvVar(szNameEqExitCodeAsciiEnvVar, szValEqExitCodeAsciiEnvVar, &CmdEnv); } else SetEnvVar(szNameEqExitCodeAsciiEnvVar, TEXT("\0"), &CmdEnv); #ifndef WIN95_CMD if (VdmProcess) { RestoreCurrentDirectories(); } #endif // WIN95_CMD } /* If real async (discarding retcode), just print PID and return. If * piped process (keeping retcode but not waiting) just store PID for * ePipe and return SUCCESS from exec. Else, wait around for the return * code before going back. */ if (ai == AI_DSCD) { /* DETach only */ if (!GuiProcess) PutStdErr(MSG_PID_IS, ONEARG, argstr1(TEXT("%u"), HandleToUlong(hChildProcess))); if (hChildProcess != NULL) { CloseHandle( hChildProcess ); hChildProcess = NULL; } } else if (ai == AI_KEEP) { /* Async exec... */ if (wf == CW_W_YES) { /* ...normal type */ LastRetCode = WaitProc(hChildProcess); i = LastRetCode ; } else { /* Async exec... */ PipePid = hChildProcess ; /* M035 */ } } else { } return (i) ; /* i == return from DOSEXECPGM */ } /********************* START OF SPECIFICATION **************************/ /* */ /* SUBROUTINE NAME: SearchForExecutable */ /* */ /* DESCRIPTIVE NAME: Search for Executable File */ /* */ /* FUNCTION: This routine searches the specified executable file. */ /* If the file extension is specified, */ /* CMD.EXE searches for the file with the file extension */ /* to execute. If the specified file with the extension */ /* is not found, CMD.EXE will display an error message to */ /* indicate that the file is not found. */ /* If the file extension is not specified, */ /* CMD.EXE searches for the file with the order of these */ /* file extensions, .COM, .EXE, .CMD, and .BAT. */ /* The file which is found first will be executed. */ /* */ /* NOTES: 1) If a path is given, only the specified directory is */ /* searched. */ /* 2) If no path is given, the current directory of the */ /* drive specified is searched followed by the */ /* directories in the PATH environment variable. */ /* 3) If no executable file is found, an error message is */ /* printed. */ /* */ /* ENTRY POINT: SearchForExecutable */ /* LINKAGE: NEAR */ /* */ /* INPUT: */ /* n - parse tree node containing the command to be searched for */ /* loc - the string in which the location of the command is to be */ /* placed */ /* */ /* OUTPUT: None. */ /* */ /* EXIT-NORMAL: */ /* Returns: */ /* SFE_EXECOM, if a .EXE or .COM file is found. */ /* SFE_ISBAT, if a .CMD file is found. */ /* SFE_ISDIR, if a directory is found. */ /* SFE_NOTFND, if no executable file is found. */ /* */ /* EXIT-ERROR: */ /* Return FAILURE or */ /* SFE_FAIL, if out of memory. */ /* */ /* EFFECTS: None. */ /* */ /* INTERNAL REFERENCES: */ /* ROUTINES: */ /* DoFind - Find the specified file. */ /* GetEnvVar - Get full path. */ /* FullPath - build a full path name. */ /* TokStr - tokenize argument strings. */ /* mkstr - allocate space for a string. */ /* */ /* EXTERNAL REFERENCES: */ /* ROUTINES: */ /* None */ /* */ /********************** END OF SPECIFICATION **************************/ SearchForExecutable(n, loc) struct cmdnode *n ; TCHAR *loc ; { TCHAR *tokpath ; TCHAR *extPath ; TCHAR *extPathWrk ; TCHAR *fname; TCHAR *p1; TCHAR *tmps01; TCHAR *tmps02 = NULL; TCHAR pcstr[3]; LONG BinaryType; size_t cName; // number of characters in file name. int tplen; // Length of the current tokpath token int dotloc; // loc offset where extension is appended int pcloc; // M014 - Flag. True=user had partial path int addpchar; // True - append PathChar to string @@5g TCHAR *j ; TCHAR wrkcmdline[MAX_PATH] ; unsigned tokpathlen; // // Test for name too long first. If it is, we avoid wasting time // p1 = StripQuotes( n->cmdline ); if ((cName = mystrlen(p1)) >= MAX_PATH) { DosErr = MSG_LINES_TOO_LONG; return(SFE_NOTFND) ; } // // If cmd extensions enable, then treat CMD without an extension // or path as a reference to COMSPEC variable. Guarantees we dont // get a random copy of CMD.EXE // if (fEnableExtensions && (p1 == NULL || !_tcsnicmp( p1, TEXT( "cmd " ), 4))) { p1 = GetEnvVar( ComSpecStr ); if (p1 == NULL) { DosErr = MSG_INVALID_COMSPEC; return SFE_NOTFND; } } mytcsnset(wrkcmdline, NULLC, MAX_PATH); mystrcpy(wrkcmdline, p1); FixPChar( wrkcmdline, SwitChar ); // // Create the path character string, this will be search string // pcstr[0] = PathChar; pcstr[1] = COLON; pcstr[2] = NULLC; // // The variable pcloc is used as a flag to indicate whether the user // did or didn't specify a drive or a partial path in his original // input. It will be NZ if drive or path was specified. // pcloc = ((mystrcspn(wrkcmdline,pcstr)) < cName) ; pcstr[1] = NULLC ; // Fixup pathchar string // // handle the case of the user typing in a file name of // ".", "..", or ending in "\" // pcloc true say string has to have either a pathchar or colon // if ( pcloc ) { if (!(p1 = mystrrchr( wrkcmdline, PathChar ))) { p1 = mystrchr( wrkcmdline, COLON ); } p1++; // move to terminator if hanging ":" or "\" } else { p1 = wrkcmdline; } // // p1 is guaranteed to be non-zero // if ( !(*p1) || !_tcscmp( p1, TEXT(".") ) || !_tcscmp( p1, TEXT("..") ) ) { // // If CMD.EXE extensions enable, see if name matches // subdirectory name. // if (fEnableExtensions) { DWORD dwFileAttributes; dwFileAttributes = GetFileAttributes( loc ); if (dwFileAttributes != 0xFFFFFFFF && (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ) { return(SFE_ISDIR); } } DosErr = MSG_DIR_BAD_COMMAND_OR_FILE; return(SFE_NOTFND) ; } if (!(tmps01 = mkstr(2*sizeof(TCHAR)*MAX_PATH))) { DosErr = ERROR_NOT_ENOUGH_MEMORY; return(SFE_FAIL) ; } // // Handle the case of file..ext on a fat drive // mystrcpy( loc, wrkcmdline ); loc[ &p1[0] - &wrkcmdline[0] ] = 0; mystrcat( loc, TEXT(".") ); // // Check for a malformed name // if (FullPath(tmps01, loc,MAX_PATH*2)) { // // If CMD.EXE extensions enable, see if name matches // subdirectory name. // if (fEnableExtensions) { DWORD dwFileAttributes; dwFileAttributes = GetFileAttributes( loc ); if (dwFileAttributes != 0xFFFFFFFF && (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ) { return(SFE_ISDIR); } } DosErr = MSG_DIR_BAD_COMMAND_OR_FILE; return(SFE_NOTFND); } if ( *lastc(tmps01) != PathChar ) { mystrcat( tmps01, TEXT("\\") ); } // // tmps01 contains full path + file name // mystrcat( tmps01, p1 ); tmps01 = resize(tmps01, (mystrlen(tmps01)+1)*sizeof(TCHAR)) ; if (tmps01 == NULL) { DosErr = ERROR_NOT_ENOUGH_MEMORY; return( SFE_FAIL ) ; } // // fname will point to last '\' // tmps01 is to be path and fname is to be name // fname = mystrrchr(tmps01,PathChar) ; *fname++ = NULLC ; DEBUG((DBENV, DBENVSCAN, "SearchForExecutable: Command:%ws",fname)); // // If only fname type in get path string // if (!pcloc) { tmps02 = GetEnvVar(PathStr) ; } DEBUG((DBENV, DBENVSCAN, "SearchForExecutable: PATH:%ws",tmps02)); // // tmps02 is PATH environment variable // compute enough for PATH environment plus file default path // tokpath = mkstr( (2 + mystrlen(tmps01) + mystrlen(tmps02) + 2)*sizeof(TCHAR)) ; if ( ! tokpath ) { DosErr = ERROR_NOT_ENOUGH_MEMORY; return( SFE_FAIL ) ; } // // Copy default path // mystrcat(tokpath,TEXT("\"")); mystrcat(tokpath,tmps01) ; mystrcat(tokpath,TEXT("\"")); // // If only name type in get also delim and path string // if (!pcloc) { mystrcat(tokpath, TEXT(";")) ; mystrcat(tokpath,tmps02) ; } // // Shift left string at ';;' // tokshrink(tokpath); tokpath = TokStr(tokpath, TEXT(";"), TS_WSPACE) ; cName = mystrlen(fname) + 1 ; // file spec. length // // Build up the list of extensions we are going to search // for. If extensions are enabled, get the list from the PATHEXT // variable, otherwise use the hard coded default. // extPath = NULL; if (fEnableExtensions) extPath = GetEnvVar(PathExtStr); if (extPath == NULL) extPath = PathExtDefaultStr; tokshrink(extPath); extPath = TokStr(extPath, TEXT(";"), TS_WSPACE) ; // // Everything is now set up. Var tokpath contains a sequential series // of asciz strings terminated by an extra null. If the user specified // a drive or partial path, it contains only that one converted to full // root-based form. If the user typed only a filename (pcloc = 0) it // begins with the current directory and contains each directory that // was contained in the PATH variable. This loop will search each of // the tokpath elements once for each possible executable extention. // Note that 'i' is used as a constant to test for excessive string // length prior to performing the string copies. // for ( ; ; ) { // // Length of current path // tplen = mystrlen(tokpath) ; mystrcpy( tokpath, StripQuotes( tokpath ) ); tokpathlen = mystrlen(tokpath); if (*lastc(tokpath) != PathChar) { addpchar = TRUE; tplen++; tokpathlen++; } else { addpchar = FALSE; } /* path + name too long */ // // Check if path + name is too long // if (*tokpath && (tokpathlen + cName) > MAX_PATH) { tokpath += addpchar ? tplen : tplen+1; // get next path continue; } // // If no more paths to search return descriptive error // if (*(tokpath) == NULLC) { if (pcloc) { if (DosErr == 0 || DosErr == ERROR_FILE_NOT_FOUND) DosErr = MSG_DIR_BAD_COMMAND_OR_FILE; } else { /* return generic message */ DosErr = MSG_DIR_BAD_COMMAND_OR_FILE; } return(SFE_NOTFND) ; } // // Install this path and setup for next one // mystrcpy(loc, tokpath) ; tokpath += addpchar ? tplen : tplen+1; if (addpchar) { mystrcat(loc, pcstr) ; } mystrcat(loc, fname) ; mystrcpy(loc, StripQuotes(loc) ); dotloc = mystrlen(loc) ; DEBUG((DBENV, DBENVSCAN, "SearchForExecutable: PATH:%ws",loc)); // // Check drive in each path to insure it is valid before searching // if (*(loc+1) == COLON) { if (!IsValidDrv(*loc)) continue ; }; // // If fname has ext & ext > "." look for given filename // this says that all executable files must have an extension // j = mystrrchr( fname, DOT ); if ( j && j[1] ) { // // If access was denied and the user included a path, // then say we found it. This handles the case where // we don't have permission to do the findfirst and so we // can't see the binary, but it actually exists -- if we // have execute permission, CreateProcess will work // just fine. // if (exists_ex(loc,TRUE) || (pcloc && (DosErr == ERROR_ACCESS_DENIED))) { // // Recompute j as exists_ex trims trailing spaces // j = mystrrchr( loc, DOT ); if (j != NULL) { if ( !_tcsicmp(j,CmdExt) ) { return(SFE_ISBAT) ; } else if ( !_tcsicmp(j,BatExt) ) { return(SFE_ISBAT) ; } else { return(SFE_ISEXECOM) ; } } } if ((DosErr != ERROR_FILE_NOT_FOUND) && DosErr) continue; // Try next path } if (mystrchr( fname, STAR ) || mystrchr( fname, QMARK ) ) { DosErr = MSG_DIR_BAD_COMMAND_OR_FILE; return(SFE_NOTFND); } // // Search for each type of extension // extPathWrk = extPath; if (DoFind(loc, dotloc, TEXT(".*"), FALSE)) // Found anything? while (*extPathWrk) { // // if name + path + ext is less then max path length // if ( (cName + tokpathlen + mystrlen(extPathWrk)) <= MAX_PATH) { // // See if this extension is a match. // if (DoFind(loc, dotloc, extPathWrk, TRUE)) { if (!_tcsicmp(extPathWrk, BatExt) || !_tcsicmp(extPathWrk, CmdExt)) return(SFE_ISBAT) ; // found .bat or .cmd else return(SFE_ISEXECOM) ; // found executable } else { // // Any kind of error other than file not found, bail from // search and try next element in path // if ((DosErr != ERROR_FILE_NOT_FOUND) && DosErr) break; } } // // Not this extension, try next. while (*extPathWrk++) ; } // // If we get here, then no match with list of extensions. // If no wierd errors, deal with NUll extension case. // if (DosErr == NO_ERROR || DosErr == ERROR_FILE_NOT_FOUND) { if (DoFind(loc, dotloc, TEXT("\0"), TRUE)) { if (GetBinaryType(loc,&BinaryType) && BinaryType == SCS_POSIX_BINARY) { // Found . return(SFE_ISEXECOM) ; } } } } // end for return(SFE_NOTFND); } /*** DoFind - does indiviual findfirsts during searching * * Purpose: * Add the extension to loc and do the find first for * SearchForExecutable(). * * DoFind(TCHAR *loc, int dotloc, TCHAR *ext) * * Args: * loc - the string in which the location of the command is to be placed * dotloc - the location loc at which the extension is to be appended * ext - the extension to append to loc * * Returns: * 1 if the file is found. * 0 if the file isn't found. * */ int DoFind(loc, dotloc, ext, metas) TCHAR *loc ; int dotloc ; TCHAR *ext ; BOOL metas; { *(loc+dotloc) = NULLC ; mystrcat(loc, ext) ; DEBUG((DBENV, DBENVSCAN, "DoFind: exists_ex(%ws)",loc)); return(exists_ex(loc,metas)) ; /*@@4*/ } /*** ExecError - handles exec errors * * Purpose: * Print the exec error message corresponding to the error number in the * global variable DosErr. * @@ lots of error codes added. * ExecError() * */ void ExecError( onb ) TCHAR *onb; { unsigned int errmsg; unsigned int count; count = ONEARG; switch (DosErr) { case ERROR_BAD_DEVICE: errmsg = MSG_DIR_BAD_COMMAND_OR_FILE; count = NOARGS; break; case ERROR_LOCK_VIOLATION: errmsg = ERROR_SHARING_VIOLATION; break ; case ERROR_NO_PROC_SLOTS: errmsg = ERROR_NO_PROC_SLOTS; count = NOARGS; break ; case ERROR_NOT_DOS_DISK: errmsg = ERROR_NOT_DOS_DISK; break ; case ERROR_NOT_ENOUGH_MEMORY: errmsg = ERROR_NOT_ENOUGH_MEMORY; count = NOARGS; break ; case ERROR_PATH_NOT_FOUND: errmsg = MSG_CMD_FILE_NOT_FOUND; break ; case ERROR_FILE_NOT_FOUND: errmsg = MSG_CMD_FILE_NOT_FOUND; break ; case ERROR_ACCESS_DENIED: errmsg = ERROR_ACCESS_DENIED; break ; case ERROR_EXE_MACHINE_TYPE_MISMATCH: errmsg = ERROR_EXE_MACHINE_TYPE_MISMATCH; break; case ERROR_DRIVE_LOCKED: errmsg = ERROR_DRIVE_LOCKED; break ; case ERROR_INVALID_STARTING_CODESEG: errmsg = ERROR_INVALID_STARTING_CODESEG; break ; case ERROR_INVALID_STACKSEG: errmsg = ERROR_INVALID_STACKSEG; break ; case ERROR_INVALID_MODULETYPE: errmsg = ERROR_INVALID_MODULETYPE; break ; case ERROR_INVALID_EXE_SIGNATURE: errmsg = ERROR_INVALID_EXE_SIGNATURE; break ; case ERROR_EXE_MARKED_INVALID: errmsg = ERROR_EXE_MARKED_INVALID; break ; case ERROR_BAD_EXE_FORMAT: errmsg = ERROR_BAD_EXE_FORMAT; break ; case ERROR_INVALID_MINALLOCSIZE: errmsg = ERROR_INVALID_MINALLOCSIZE; break ; case ERROR_SHARING_VIOLATION: errmsg = ERROR_SHARING_VIOLATION; break ; case ERROR_BAD_ENVIRONMENT: errmsg = ERROR_INFLOOP_IN_RELOC_CHAIN; count = NOARGS; break ; case ERROR_INVALID_ORDINAL: errmsg = ERROR_INVALID_ORDINAL; break ; case ERROR_CHILD_NOT_COMPLETE: errmsg = ERROR_CHILD_NOT_COMPLETE; break ; case ERROR_DIRECTORY: errmsg = MSG_BAD_CURDIR; count = NOARGS; break; case ERROR_NOT_ENOUGH_QUOTA: errmsg = ERROR_NOT_ENOUGH_QUOTA; count = NOARGS; break; case MSG_REAL_MODE_ONLY: errmsg = MSG_REAL_MODE_ONLY; count = NOARGS; break ; default: // printf( "Exec failed code %x\n", DosErr ); count = NOARGS; errmsg = MSG_EXEC_FAILURE ; /* M031 */ } LastRetCode = errmsg; PutStdErr(errmsg, count, onb ); } /* * tokshrink @@4 * * remove duplicate ';' in a path */ void tokshrink( tokpath ) TCHAR *tokpath; { int i, j; i = 0; do { if ( tokpath[i] == QUOTE ) { do { i++; } while ( tokpath[i] && tokpath[i] != QUOTE ); } if ( tokpath[i] && tokpath[i] != TEXT(';') ) { i++; } if ( tokpath[i] == TEXT(';') ) { j = i; while ( tokpath[j+1] == TEXT(';') ) { j++; } if ( j > i ) { mystrcpy( &tokpath[i], &tokpath[j] ); } i++; } } while ( tokpath[i] ); } /*** eAssoc - execute an Assoc command * * Purpose: * To set/modify the file associations stored in the registry under the * HKEY_LOCAL_MACHINE\Software\Classes key * * int eAssoc(struct cmdnode *n) * * Args: * n - the parse tree node containing the set command * * Returns: * If setting and the command is syntactically correct, whatever SetAssoc() * returns. Otherwise, FAILURE. * * If displaying, SUCCESS is always returned. * */ int eAssoc(n) struct cmdnode *n ; { if (glBatType != CMD_TYPE) { // if set command is executed from .bat file OR entered at command prompt return( SetLastRetCodeIfError(AssocWork( n ))); } else { return( LastRetCode = AssocWork( n ) ); } } int AssocWork(n) struct cmdnode *n ; { HKEY hKeyClasses; TCHAR *tas ; /* Tokenized argument string */ TCHAR *wptr ; /* Work pointer */ int i ; /* Work variable */ int rc ; rc = RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("Software\\Classes"), &hKeyClasses); if (rc) { return rc; } tas = TokStr(n->argptr, ONEQSTR, TS_WSPACE|TS_SDTOKENS) ; if (!*tas) rc = DisplayAssoc(hKeyClasses, NULL) ; else { for (wptr = tas, i = 0 ; *wptr ; wptr += mystrlen(wptr)+1, i++) ; /* If too many parameters were given, the second parameter */ /* wasn't an equal sign, or they didn't specify a string */ /* return an error message. */ if ( i > 3 || *(wptr = tas+mystrlen(tas)+1) != EQ || !mystrlen(mystrcpy(tas, StripQuotes(tas))) ) { if (i==1) { rc =DisplayAssoc(hKeyClasses, tas); } else { PutStdErr(MSG_BAD_SYNTAX, NOARGS); rc = FAILURE ; } } else { rc = SetAssoc(hKeyClasses, tas, wptr+2) ; } } ; RegCloseKey(hKeyClasses) ; return rc; } /*** DisplayAssoc - display a specific file association or all * * Purpose: * To display a specific file association or all * * int DisplayAssoc( hKeyClasses, tas ) * * Returns: * SUCCESS if all goes well * FAILURE if it runs out of memory or cannot lock the env. segment */ int DisplayAssoc(hKeyClasses, tas) HKEY hKeyClasses; TCHAR *tas; { int i; int rc = SUCCESS; TCHAR NameBuffer[MAX_PATH]; TCHAR ValueBuffer[MAX_PATH]; TCHAR *vstr ; DWORD cb; if (tas == NULL) { for (i=0 ; rc == SUCCESS ; i++) { rc =RegEnumKey(hKeyClasses, i, NameBuffer, sizeof( NameBuffer ) / sizeof( TCHAR )); if (rc != 0) { if (rc==ERROR_NO_MORE_ITEMS) rc = SUCCESS; break; } else if (NameBuffer[0] == DOT) { cb = sizeof(ValueBuffer); rc = RegQueryValue(hKeyClasses, NameBuffer, ValueBuffer, &cb); if (rc != 0) { break; } rc = cmd_printf(Fmt16, NameBuffer, ValueBuffer); } if (CtrlCSeen) { return(FAILURE); } } } else { tas = EatWS(tas, NULL); if ((vstr = mystrrchr(tas, ' ')) != NULL) *vstr = NULLC; cb = sizeof(ValueBuffer); rc = RegQueryValue(hKeyClasses, tas, ValueBuffer, &cb); if (rc == 0) rc = cmd_printf(Fmt16, tas, ValueBuffer); else PutStdErr(MSG_ASSOC_NOT_FOUND, ONEARG, tas); } return(rc); } /*** SetAssoc - controls adding/changing a file association * * Purpose: * Add/replace a file association * * int SetAssoc(HKEY hKeyClasses, TCHAR *fileext, TCHAR *filetype) * * Args: * hKeyClasses - handle to HKEY_LOCAL_MACHINE\Software\Classes key * fileext - file extension string to associate * filetype - file type associate * * Returns: * SUCCESS if the association could be added/replaced. * FAILURE otherwise. * */ int SetAssoc(hKeyClasses, fileext, filetype) HKEY hKeyClasses; TCHAR *fileext ; TCHAR *filetype ; { int rc; int i; DWORD cb; if (filetype==NULL || *filetype==NULLC) { rc = RegDeleteKey(hKeyClasses, fileext); if (rc != 0) PutStdErr(MSG_ASSOC_NOT_FOUND, ONEARG, fileext); } else { rc = RegSetValue(hKeyClasses, fileext, REG_SZ, filetype, _tcslen(filetype)); if (rc == 0) { try { SHChangeNotify( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL ); cmd_printf( Fmt16, fileext, filetype ); } except (DosErr = GetExceptionCode( ), EXCEPTION_EXECUTE_HANDLER) { } } else PutStdErr(MSG_ERR_PROC_ARG, ONEARG, fileext); } return rc; } /*** eFType - execute an FType command * * Purpose: * To set/modify the file types stored in the registry under the * HKEY_LOCAL_MACHINE\Software\Classes key * * int eFType(struct cmdnode *n) * * Args: * n - the parse tree node containing the set command * * Returns: * If setting and the command is syntactically correct, whatever SetFType() * returns. Otherwise, FAILURE. * * If displaying, SUCCESS is always returned. * */ int eFType(n) struct cmdnode *n ; { if (glBatType != CMD_TYPE) { // if set command is executed from .bat file OR entered at command prompt return( SetLastRetCodeIfError(FTypeWork( n ))); } else { return( LastRetCode = FTypeWork( n ) ); } } int FTypeWork(n) struct cmdnode *n ; { HKEY hKeyClasses; TCHAR *tas ; /* Tokenized argument string */ TCHAR *wptr ; /* Work pointer */ int i ; /* Work variable */ int rc ; rc = RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("Software\\Classes"), &hKeyClasses); if (rc) { return rc; } tas = TokStr(n->argptr, ONEQSTR, TS_WSPACE|TS_SDTOKENS) ; if (!*tas) rc = DisplayFType(hKeyClasses, NULL) ; else { for (wptr = tas, i = 0 ; *wptr ; wptr += mystrlen(wptr)+1, i++) ; /* If too many parameters were given, the second parameter */ /* wasn't an equal sign, or they didn't specify a string */ /* return an error message. */ if ( i > 3 || *(wptr = tas+mystrlen(tas)+1) != EQ || !mystrlen(mystrcpy(tas, StripQuotes(tas))) ) { if (i==1) { rc =DisplayFType(hKeyClasses, tas); } else { PutStdErr(MSG_BAD_SYNTAX, NOARGS); rc = FAILURE ; } } else { rc = SetFType(hKeyClasses, tas, wptr+2) ; } } ; RegCloseKey(hKeyClasses) ; return rc; } /*** DisplayFType - display a specific file type or all * * Purpose: * To display a specific file type or all * * int DisplayFType( hKeyClasses, tas ) * * Returns: * SUCCESS if all goes well * FAILURE if it runs out of memory or cannot lock the env. segment */ int DisplayFType(hKeyClasses, tas) HKEY hKeyClasses; TCHAR *tas; { int i; int rc; HKEY hKeyOpenCmd; TCHAR NameBuffer[MAX_PATH]; TCHAR ValueBuffer[MAX_PATH]; TCHAR *vstr ; DWORD cb, j, Type; if (tas == NULL) { for (i=0;;i++) { rc =RegEnumKey(hKeyClasses, i, NameBuffer, sizeof( NameBuffer ) / sizeof( TCHAR )); if (rc != 0) { if (rc==ERROR_NO_MORE_ITEMS) rc = SUCCESS; break; } else if (NameBuffer[0] != DOT) { j = _tcslen(NameBuffer); _tcscat(NameBuffer, TEXT("\\Shell\\Open\\Command")); _tcscpy(ValueBuffer,TEXT("*** no open command defined ***")); rc = RegOpenKey(hKeyClasses, NameBuffer, &hKeyOpenCmd); if (!rc) { NameBuffer[j] = TEXT('\0'); cb = sizeof(ValueBuffer); rc = RegQueryValueEx(hKeyOpenCmd, TEXT(""), NULL, &Type, (LPBYTE)ValueBuffer, &cb); RegCloseKey(hKeyOpenCmd); } if (!rc) { cmd_printf(Fmt16, NameBuffer, ValueBuffer); } } if (CtrlCSeen) { return(FAILURE); } } } else { if (*tas == DOT) { PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, tas); return ERROR_INVALID_NAME; } tas = EatWS(tas, NULL); if ((vstr = mystrrchr(tas, ' ')) != NULL) *vstr = NULLC; _sntprintf(NameBuffer, MAX_PATH, TEXT("%s\\Shell\\Open\\Command"), tas); rc = RegOpenKey(hKeyClasses, NameBuffer, &hKeyOpenCmd); if (rc) { PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, tas); return rc; } cb = sizeof(ValueBuffer); rc = RegQueryValueEx(hKeyOpenCmd, TEXT(""), NULL, &Type, (LPBYTE)ValueBuffer, &cb); if (rc == 0) { ValueBuffer[ (cb / sizeof( TCHAR )) - 1 ]; cmd_printf(Fmt16, tas, ValueBuffer); } else PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, tas); } return(rc); } /*** SetFType - controls adding/changing the open command associated with a file type * * Purpose: * Add/replace an open command string associated with a file type * * int SetFType(HKEY hKeyOpenCmd, TCHAR *filetype TCHAR *opencmd) * * Args: * hKeyClasses - handle to HKEY_LOCAL_MACHINE\Software\Classes * filetype - file type name * opencmd - open command string * * Returns: * SUCCESS if the file type could be added/replaced. * FAILURE otherwise. * */ int SetFType(hKeyClasses, filetype, opencmd) HKEY hKeyClasses; TCHAR *filetype ; TCHAR *opencmd ; { HKEY hKeyOpenCmd; TCHAR NameBuffer[MAX_PATH]; TCHAR c, *s; DWORD Disposition; int rc; int i; DWORD cb; _sntprintf(NameBuffer, MAX_PATH, TEXT("%s\\Shell\\Open\\Command"), filetype); rc = RegOpenKey(hKeyClasses, NameBuffer, &hKeyOpenCmd); if (rc) { if (opencmd==NULL || *opencmd==NULLC) { PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, filetype); return rc; } s = NameBuffer; while (TRUE) { while (*s && *s != TEXT('\\')) { s += 1; } c = *s; *s = TEXT('\0'); rc = RegCreateKeyEx(hKeyClasses, NameBuffer, 0, NULL, 0, (REGSAM)MAXIMUM_ALLOWED, NULL, &hKeyOpenCmd, &Disposition ); if (rc) { PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, filetype); return rc; } if (c == TEXT('\0')) { break; } *s++ = c; RegCloseKey(hKeyOpenCmd); } } if (opencmd==NULL || *opencmd==NULLC) { rc = RegDeleteKey(hKeyOpenCmd, NULL); if (rc != 0) PutStdErr(MSG_FTYPE_NOT_FOUND, ONEARG, filetype); } else { rc = RegSetValueEx(hKeyOpenCmd, TEXT(""), 0, REG_EXPAND_SZ, (LPBYTE)opencmd, (_tcslen(opencmd)+1)*sizeof(TCHAR)); if (rc == 0) cmd_printf(Fmt16, filetype, opencmd); else PutStdErr(MSG_ERR_PROC_ARG, ONEARG, filetype); } RegCloseKey(hKeyOpenCmd); return rc; } typedef NTSTATUS (NTAPI *PNTQUERYINFORMATIONPROCESS)( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ); HMODULE hNtDllModule; PNTQUERYINFORMATIONPROCESS lpNtQueryInformationProcess; WORD GetProcessSubsystemType( HANDLE hProcess ) { PIMAGE_NT_HEADERS NtHeader; PPEB PebAddress; PEB Peb; SIZE_T SizeOfPeb; NTSTATUS Status; PROCESS_BASIC_INFORMATION ProcessInfo; BOOL b; PVOID ImageBaseAddress; LONG e_lfanew; WORD Subsystem; Subsystem = IMAGE_SUBSYSTEM_UNKNOWN; if (hNtDllModule == NULL) { hNtDllModule = LoadLibrary( TEXT("NTDLL.DLL") ); if (hNtDllModule != NULL) { lpNtQueryInformationProcess = (PNTQUERYINFORMATIONPROCESS) GetProcAddress( hNtDllModule, "NtQueryInformationProcess" ); } else { hNtDllModule = INVALID_HANDLE_VALUE; } } if (lpNtQueryInformationProcess != NULL) { // // Get the Peb address // Status = (*lpNtQueryInformationProcess)( hProcess, ProcessBasicInformation, &ProcessInfo, sizeof( ProcessInfo ), NULL ); if (NT_SUCCESS( Status )) { PebAddress = ProcessInfo.PebBaseAddress; // // Read the subsystem type from the Peb // if (ReadProcessMemory( hProcess, PebAddress, &Peb, sizeof( Peb ), &SizeOfPeb ) ) { // // See if we are running on a system that has the image subsystem // type captured in the PEB. If so use it. Otherwise go the slow // way and try to get it from the image header. // if (SizeOfPeb >= FIELD_OFFSET( PEB, ImageSubsystem ) && ((UINT_PTR)Peb.ProcessHeaps - (UINT_PTR)PebAddress) > FIELD_OFFSET( PEB, ImageSubsystem ) ) { Subsystem = (WORD)Peb.ImageSubsystem; } else { // // read e_lfanew from imageheader // if (ReadProcessMemory( hProcess, &((PIMAGE_DOS_HEADER)Peb.ImageBaseAddress)->e_lfanew, &e_lfanew, sizeof( e_lfanew ), NULL ) ) { // // Read subsystem version info // NtHeader = (PIMAGE_NT_HEADERS)((PUCHAR)Peb.ImageBaseAddress + e_lfanew); if (ReadProcessMemory( hProcess, &NtHeader->OptionalHeader.Subsystem, &Subsystem, sizeof( Subsystem ), NULL ) ) { } } } } } } return Subsystem; }