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.















