Note : for a updated version of the code check the Github repo.
Today I will show you how you can retrieve the Command line parameters of an external application from Delphi using the WinApi and the WMI. In order to understand how the Command line parameters are stored and treated by the system, I recommend which you read this article from Raymond Chen .
The WinApi way
In order to get the command line from an external process using the WinAPI, you must access to the PEB (Process Environment Block) of the application. To get the PEB you can use the NtQueryInformationProcess function
NTSTATUS WINAPI NtQueryInformationProcess( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength );
function NtQueryInformationProcess( ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll';
Passing the ProcessBasicInformation value in the ProcessInformationClass parameter and a buffer to hold the PROCESS_BASIC_INFORMATION returned in the ProcessInformation.
This is the official (MSDN) definition for the PROCESS_BASIC_INFORMATION structure
typedef struct _PROCESS_BASIC_INFORMATION { PVOID Reserved1; PPEB PebBaseAddress; PVOID Reserved2[2]; ULONG_PTR UniqueProcessId; PVOID Reserved3; } PROCESS_BASIC_INFORMATION;
And this a more friendly delphi translation of this structure using the NTinterlnals.net site
PROCESS_BASIC_INFORMATION = packed record ExitStatus: DWORD; PebBaseAddress: Pointer; AffinityMask: DWORD; BasePriority: DWORD; UniqueProcessId: DWORD; InheritedUniquePID:DWORD; end;
The key field in this structure is PebBaseAddress, which stores the address of the PEB. from this point now you must digging inside of the PEB structure again
typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; BYTE Reserved4[104]; PVOID Reserved5[52]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved6[128]; PVOID Reserved7[1]; ULONG SessionId; } PEB, *PPEB;
and retrieve the value of the ProcessParameters field which is a pointer to a RTL_USER_PROCESS_PARAMETERS structure
typedef struct _RTL_USER_PROCESS_PARAMETERS { BYTE Reserved1[16]; PVOID Reserved2[10]; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
Finally you can note which the CommandLine field stores the info which are looking for.
The WinAPI Delphi Code
This is the Delphi source which retrieves the Command line parameters from an external application
Notes :
- the next code uses hard-coded offsets to read specific locations of the PEB to avoid the declaration the full structures required (feel free to declare these structures and avoid the offsets).
- this code only works for 32 bits process because the structure of the PEB differs from 32 to 64 processes.
- to gain access to the processes owned by the system the code set the SeDebugPrivilege token before to use the OpenProcess function.
//Author Rodrigo Ruz V. //2011-07-20 {$APPTYPE CONSOLE} uses SysUtils, Windows; type _UNICODE_STRING = record Length: Word; MaximumLength: Word; Buffer: LPWSTR; end; UNICODE_STRING = _UNICODE_STRING; PROCESS_BASIC_INFORMATION = packed record ExitStatus: DWORD; PebBaseAddress: Pointer; AffinityMask: DWORD; BasePriority: DWORD; UniqueProcessId: DWORD; InheritedUniquePID:DWORD; end; function NtQueryInformationProcess(ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll'; function GetCommandLineFromPid(PID: THandle): string; const STATUS_SUCCESS = $00000000; SE_DEBUG_NAME = 'SeDebugPrivilege'; OffsetProcessParametersx32 = $10;//16 OffsetCommandLinex32 = $40;//64 var ProcessHandle : THandle; rtlUserProcAddress : Pointer; CommandLine : UNICODE_STRING; CommandLineContents : WideString; ProcessBasicInfo : PROCESS_BASIC_INFORMATION; ReturnLength : Cardinal; TokenHandle : THandle; lpLuid : TOKEN_PRIVILEGES; OldlpLuid : TOKEN_PRIVILEGES; begin Result:=''; if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then begin try if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then RaiseLastOSError else begin lpLuid.PrivilegeCount := 1; lpLuid.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; ReturnLength := 0; OldlpLuid := lpLuid; //Set the SeDebugPrivilege privilege if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError; end; ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID); if ProcessHandle=0 then RaiseLastOSError else try // get the PROCESS_BASIC_INFORMATION to access to the PEB Address if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, sizeof(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then begin //get the address of the RTL_USER_PROCESS_PARAMETERS struture if not ReadProcessMemory(ProcessHandle, Pointer(Longint(ProcessBasicInfo.PEBBaseAddress) + OffsetProcessParametersx32), @rtlUserProcAddress, sizeof(Pointer), ReturnLength) then RaiseLastOSError else if ReadProcessMemory(ProcessHandle, Pointer(Longint(rtlUserProcAddress) + OffsetCommandLinex32), @CommandLine, sizeof(CommandLine), ReturnLength) then begin SetLength(CommandLineContents, CommandLine.length); //get the CommandLine field if ReadProcessMemory(ProcessHandle, CommandLine.Buffer, @CommandLineContents[1], CommandLine.Length, ReturnLength) then Result := WideCharLenToString(PWideChar(CommandLineContents), CommandLine.length div 2) else RaiseLastOSError; end; end else RaiseLastOSError; finally CloseHandle(ProcessHandle); end; finally CloseHandle(TokenHandle); end; end else RaiseLastOSError; end; begin try Writeln(GetCommandLineFromPid(5440)); except on E:Exception do Writeln(E.Classname, ':', E.Message); end; Readln; end.
The WMI way
The WMI provides a very reliable and easy way to access the Command line parameters from an external process, all you must to do is use the Win32_Process wmi class and look in the CommandLine property.
The WMI Delphi Code
Notes
- The next code can retrieve the command line for 32 and 64 bits processes.
- The code uses Late binding to access the WMI, if you want use another way to access the WMI from Delphi (like direct COM access or importing th e Microsoft scripting library) take a look to the Delphi WMI Code creator.
- You can change the credentials of the ConnectServer function to access to the command line parameters of a remote machine process.
{$APPTYPE CONSOLE} uses Windows, SysUtils, ActiveX, Variants, ComObj; function GetCommandLineFromPid(ProcessId:DWORD): string; var FSWbemLocator : OLEVariant; FWMIService : OLEVariant; FWbemObjectSet: OLEVariant; begin; Result:=''; FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator'); FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', ''); //if the pid not exist a EOleException exception will be raised with the code $80041002 - Object Not Found FWbemObjectSet:= FWMIService.Get(Format('Win32_Process.Handle="%d"',[ProcessId])); Result:=FWbemObjectSet.CommandLine; end; begin try CoInitialize(nil); try Writeln(GetCommandLineFromPid(5452)); finally CoUninitialize; end; except on E:EOleException do Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode])); on E:Exception do Writeln(E.Classname, ':', E.Message); end; Writeln('Press Enter to exit'); Readln; end.
July 27, 2011 at 9:40 am
Great article very clear to understand
July 29, 2011 at 10:14 am
There is no way to determine CMD Line or ImagePath from a x64 process, if it is runnig from a “subst” drive (subst-mounted folder).
Pingback: Get path to file currently open in Windows Picture & Fax viewer | PHP Developer Resource
October 20, 2014 at 6:10 am
THandle is the wrong type to use for PID. It should be DWORD.
December 2, 2016 at 6:56 am
Great article! Exactly the code I was looking for!
Both approaches work fine.
Thank you!