How to make files really hidden in a directory? - delphi

I downloaded a program yesterday, it is for encryption and security. I won't name it here unless someone asks me to but it has a feature to make files inside a specified folder completely invisible.
I have Hidden Files and Folders - Selected and also Hide protected operating system files - Unselected yet the files are gone completely from view and don't show up in a search either. I copied the folder over from VMware Workstation to my main machine and still the files are super hidden! There are zero files in the folder according to Windows.
How is this voodoo magick possible? I want to emulate this using Delphi in my own encryption program. I have not found any way on here and via Google that suggests how it is possible but the actual programs help file says they are still in the folder but do not register with most normal Windows software that process files.
This is one of those questions where I can not give any code to show what I have tried, but rather open to suggestions of what I can try or maybe someone here knows exactly how it is done?

Since less informa
One possebility would be using alternative filestreams on NTFS, which can be added to files and folders. You can just try this by typing "notepad C:\temp:hidden1.txt" at the comandline, new filestream will be created if you aswer with yes. After saving you can reopen it exact the same way. This can also be done from delphi (loading/saving). Will only work if NTFS is used.
I don't know if this method is used in described case, finding ADS can be done with following code:
unit u_ListADS;
// 20120928 by Thomas Wassermann
// www.devworx.de
interface
uses
Windows, Messages, SysUtils, Variants, Classes, StrUtils;
Procedure GetADS(List: TStrings; const Path, WildCard: String; Recursiv: Boolean = false);
function NtQueryInformationFile(FileHandle: Cardinal; IoStatusBlock: Pointer; FileInformation: Pointer; FileInformationLength: Cardinal;
FileInformationClass: Cardinal): Cardinal; stdcall; external 'ntdll.dll';
implementation
type
_FILE_STREAM_INFORMATION = record
NextEntryOffset: Cardinal;
StreamNameLength: Cardinal;
StreamSize: int64;
StreamAllocationSize: int64;
StreamName: array [0 .. MAX_PATH] of WideChar;
end;
PFILE_STREAM_INFORMATION = ^_FILE_STREAM_INFORMATION;
function GetStreams(aFilename: String): TStringList;
var
FileHandle: Integer;
FileName: array [0 .. MAX_PATH] of WideChar;
StreamName: String;
InfoBlock: _FILE_STREAM_INFORMATION;
StatusBlock: record Status: Cardinal;
Information: PDWORD;
end;
Procedure Analyze;
begin
CopyMemory(#FileName, #InfoBlock.StreamName, InfoBlock.StreamNameLength);
StreamName := Copy(Filename, 1, PosEx(':', Filename, 2) - 1);
if StreamName <> ':' then Result.Add(StreamName);
end;
begin
Result := TStringList.Create;
FileHandle := FileOpen(aFilename, GENERIC_READ);
NtQueryInformationFile(FileHandle, #StatusBlock, #InfoBlock, SizeOf(InfoBlock), 22);
FileClose(FileHandle);
if InfoBlock.StreamNameLength <> 0 then
Repeat
if (InfoBlock.NextEntryOffset <> 0) then
begin
InfoBlock := PFILE_STREAM_INFORMATION(PByte(#InfoBlock) + InfoBlock.NextEntryOffset)^;
Analyze;
end;
until InfoBlock.NextEntryOffset = 0
end;
Procedure GetADS(List: TStrings; const Path, WildCard: String; Recursiv: Boolean = false);
Var
SR: SysUtils.TSearchRec;
RES: Integer;
SP: String;
StreamList: TStringList;
i: Integer;
begin
if length(Path) = 0 then
exit;
if length(WildCard) = 0 then
exit;
SP := IncludeTrailingBackSlash(Path) + WildCard;
RES := FindFirst(IncludeTrailingBackSlash(Path) + '*.*', faDirectory, SR);
While RES = 0 Do
Begin
If (SR.attr And faDirectory) <> 0 Then
If SR.Name[1] <> '.' Then
if Recursiv then
GetADS(List, IncludeTrailingBackSlash(Path) + SR.Name, WildCard, Recursiv);
RES := FindNext(SR);
End;
SysUtils.FindClose(SR);
RES := FindFirst(SP, $27, SR);
While RES = 0 Do
Begin
StreamList := GetStreams(IncludeTrailingBackSlash(Path) + SR.Name);
for i := 0 to StreamList.Count - 1 do
List.Add(IncludeTrailingBackSlash(Path) + SR.Name + StreamList[i]);
StreamList.Free;
RES := FindNext(SR);
End;
SysUtils.FindClose(SR);
end;
end.
Call could be e.g.
GetADS(Listbox1.Items,Directory.Text, WildCards.Text,rekursiv.checked);

Related

How lock CTRL+ALT+DEL using SetWindowHookEx api?

Good afternoon,
I need lock CTRL+ALT+DEL combination using SetWindowsHookEx and today i have done a code and don't is working until now.
This code is executing in a dll ( this dll is my software ) that is injected in other process.
So, how i can adapt this code below to work?
const
WH_KEYBOARD_LL = 13;
LLKHF_ALTDOWN = $20;
type
KBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: Longint ;
end;
var
hhkLowLevelKybd : HHOOK;
FoldProc : LongInt;
hSASWnd : HWND;
hThread : Cardinal;
{$R *.dfm}
Function LowLevelKeyboardProc(nCode : Integer; wParam : Longint; var LParam: KBDLLHOOKSTRUCT) : Longint; stdcall;
var
fEatKeystroke : Boolean;
dwThreadId : Cardinal;
begin
If (nCode = HC_ACTION) Then
begin
If (wParam = WM_KEYDOWN) Or
(wParam = WM_SYSKEYDOWN) Or
(wParam = WM_KEYUP) Or
(wParam = WM_SYSKEYUP) Then
begin
fEatKeystroke :=
(((GetKeyState(VK_CONTROL) And $8000) <> 0) And
((LParam.flags And LLKHF_ALTDOWN ) <> 0) And
(LParam.vkCode = VK_DELETE));
End;
If fEatKeystroke Then
Result := -1
Else
Result := CallNextHookEx(0, nCode, wParam, LongInt(#LParam));
End;
end;
////////// FormCreate event here ///////////
hhkLowLevelKybd := 0;
hhkLowLevelKybd := SetWindowsHookEx(WH_KEYBOARD_LL, #LowLevelKeyboardProc,
HInstance, 0);
end.
Windows does not allow you to intercept Ctrl+Alt+Del for security reasons. Earlier versions (pre-Vista?) used to allow it by replacing the GINA DLL, but it's not been allowed for years.
That key combination is known as a secure attention sequence which is guaranteed to be trustworthy as part of the login process.
If your goal is to only allow your application to be run, you can configure it to act in kiosk mode if you're running a suitable version of Windows, as shown in Set up a device for anyone to use (kiosk mode) at TechNet which #LURD kindly provided.
By design it's impossible to trap or block Ctrl+Alt+Del (The Secure Attention Sequence). There is however a commercial library available (disclaimer: I am the author), SasLibEx.
SasLibEx: a library that can simulate or block the Secure Attention
Sequence (Ctrl+Alt+Del) but it can even unlock a
workstation or session without entering or needing the user’s
credentials (and many more things)
See this screencast for a demo.
Impossible. The Ctl-Alt-Del gets trapped in the Kernel and never makes it to the user mode space where your app is running.
I have had to do this on kiosks systems (using Win XP and Vista) and I did it with a keyboard filter driver (which runs in the kernel) that swaps out the scan codes when the key are pressed.
Not is impossible, see the following code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,
Windows,
Registry,
vcl.Dialogs;
procedure DisableCtrAltDel(boolState: Boolean);
var
SystemReg: TRegistry;
Data: Array [1 .. 48] of Byte;
i: Byte;
begin
try
for i := 1 to 48 do
Data[i] := $00;
Data[9] := $09;
Data[15] := $5B;
Data[16] := $E0;
Data[19] := $5C;
Data[20] := $E0;
Data[23] := $5D;
Data[24] := $E0;
Data[27] := $44;
Data[31] := $1D;
Data[35] := $38;
Data[39] := $1D;
Data[40] := $E0;
Data[43] := $38;
Data[44] := $E0;
try
SystemReg := TRegistry.Create;
with SystemReg do
begin
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('\System\CurrentControlSet\Control\Keyboard Layout', True);
if boolState then
WriteBinaryData('Scancode Map', Data, SizeOf(Data))
else
DeleteValue('Scancode Map');
MessageDlg('Restart Windows in order the changes to take effect!',
mtInformation, [mbOK], 0);
CloseKey;
end;
finally
SystemReg.Free;
end;
except
MessageDlg
('Error occurred while trying to disable ctrl+alt+del and Task Manager',
mtWarning, [mbOK], 0);
end;
end;
begin
try
DisableCtrAltDel(True);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Reference

Copying files to clipboard and then pasting them into their original folder does not work

I've got a puzzling situation. I am using the following code in Delphi to copy a list of files to the clipboard;
procedure TfMain.CopyFilesToClipboard(FileList: string);
const
C_UNABLE_TO_ALLOCATE_MEMORY = 'Unable to allocate memory.';
C_UNABLE_TO_ACCESS_MEMORY = 'Unable to access allocated memory.';
var
DropFiles: PDropFiles;
hGlobal: THandle;
iLen: Integer;
begin
iLen := Length(FileList);
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or
GMEM_ZEROINIT, SizeOf(TDropFiles) + ((iLen + 2) * SizeOf(Char)));
if (hGlobal = 0) then
raise Exception.Create(C_UNABLE_TO_ALLOCATE_MEMORY);
try DropFiles := GlobalLock(hGlobal);
if (DropFiles = nil) then raise Exception.Create(C_UNABLE_TO_ACCESS_MEMORY);
try
DropFiles^.pFiles := SizeOf(TDropFiles);
DropFiles^.fWide := True;
if FileList <> '' then
Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^,
iLen * SizeOf(Char));
finally
GlobalUnlock(hGlobal);
end;
Clipboard.SetAsHandle(CF_HDROP, hGlobal);
except
GlobalFree(hGlobal);
end;
end;
(This seems to be a popular piece of code on the internet)
Using my application, once the files are copied to the clipboard, I can use Windows Explorer to paste them into every other folder, EXCEPT the folder where the file originally came from! I was expecting it to behave just like a normal Windows copy (i.e. on paste it should create a file with postfix of '-Copy') but this doesn't seem to work. Any clues?
I am not able to get Windows Explorer to paste into the source folder when the only clipboard format available is CF_HDROP. However, if the filenames are provided in an IDataObject instead, it works fine.
If all of the files are from the same source folder, you can retrieve the IShellFolder of the source folder and query it for child PIDLs for the individual files, then use IShellFolder.GetUIObjectOf() to get an IDataObject that represents the files. Then use OleSetClipboard() to put that object on the clipboard. For example:
uses
System.Classes, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj;
procedure CopyFilesToClipboard(const Folder: string; FileNames: TStrings);
var
SF: IShellFolder;
PidlFolder: PItemIDList;
PidlChildren: array of PItemIDList;
Eaten: UINT;
Attrs: DWORD;
Obj: IDataObject;
I: Integer;
begin
if (Folder = '') or (FileNames = nil) or (FileNames.Count = 0) then Exit;
OleCheck(SHParseDisplayName(PChar(Folder), nil, PidlFolder, 0, Attrs));
try
OleCheck(SHBindToObject(nil, PidlFolder, nil, IShellFolder, Pointer(SF)));
finally
CoTaskMemFree(PidlFolder);
end;
SetLength(PidlChildren, FileNames.Count);
for I := Low(PidlChildren) to High(PidlChildren) do
PidlChildren[i] := nil;
try
for I := 0 to FileNames.Count-1 do
OleCheck(SF.ParseDisplayName(0, nil, PChar(FileNames[i]), Eaten, PidlChildren[i], Attrs));
OleCheck(SF.GetUIObjectOf(0, FileNames.Count, PIdlChildren[0], IDataObject, nil, obj));
finally
for I := Low(PidlChildren) to High(PidlChildren) do
begin
if PidlChildren[i] <> nil then
CoTaskMemFree(PidlChildren[i]);
end;
end;
OleCheck(OleSetClipboard(obj));
OleCheck(OleFlushClipboard);
end;
Update: If the files are in different source folders, you can use the CFSTR_SHELLIDLIST format:
uses
System.Classes, System.SysUtils, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj, Vcl.Clipbrd;
{$POINTERMATH ON}
function HIDA_GetPIDLFolder(pida: PIDA): LPITEMIDLIST;
begin
Result := LPITEMIDLIST(LPBYTE(pida) + pida.aoffset[0]);
end;
function HIDA_GetPIDLItem(pida: PIDA; idx: Integer): LPITEMIDLIST;
begin
Result := LPITEMIDLIST(LPBYTE(pida) + (PUINT(#pida.aoffset[0])+(1+idx))^);
end;
var
CF_SHELLIDLIST: UINT = 0;
type
CidaPidlInfo = record
Pidl: PItemIDList;
PidlOffset: UINT;
PidlSize: UINT;
end;
procedure CopyFilesToClipboard(FileNames: TStrings);
var
PidlInfo: array of CidaPidlInfo;
Attrs, AllocSize: DWORD;
gmem: THandle;
ida: PIDA;
I: Integer;
begin
if (FileNames = nil) or (FileNames.Count = 0) or (CF_SHELLIDLIST = 0) then Exit;
SetLength(PidlInfo, FileNames.Count);
for I := Low(PidlInfo) to High(PidlInfo) do
PidlInfo[I].Pidl := nil;
try
AllocSize := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count)+SizeOf(Word);
for I := 0 to FileNames.Count-1 do
begin
OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, PidlInfo[I].Pidl, 0, Attrs));
PidlInfo[I].PidlOffset := AllocSize;
PidlInfo[I].PidlSize := ILGetSize(PidlInfo[I].Pidl);
Inc(AllocSize, PidlInfo[I].PidlSize);
end;
gmem := GlobalAlloc(GMEM_MOVEABLE, AllocSize);
if gmem = 0 then RaiseLastOSError;
try
ida := PIDA(GlobalLock(gmem));
if ida = nil then RaiseLastOSError;
try
ida.cidl := FileNames.Count;
ida.aoffset[0] := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count);
HIDA_GetPIDLFolder(ida).mkid.cb := 0;
for I := 0 to FileNames.Count-1 do
begin
ida.aoffset[1+I] := PidlInfo[I].PidlOffset;
Move(PidlInfo[I].Pidl^, HIDA_GetPIDLItem(ida, I)^, PidlInfo[I].PidlSize);
end;
finally
GlobalUnlock(gmem);
end;
Clipboard.SetAsHandle(CF_SHELLIDLIST, gmem);
except
GlobalFree(gmem);
raise;
end;
finally
for I := Low(PidlInfo) to High(PidlInfo) do
CoTaskMemFree(PidlInfo[I].Pidl);
end;
end;
initialization
CF_SHELLIDLIST := RegisterClipboardFormat(CFSTR_SHELLIDLIST);
Alternatively:
procedure CopyFilesToClipboard(FileNames: TStrings);
var
Pidls: array of PItemIdList;
Attrs: DWORD;
I: Integer;
obj: IDataObject;
begin
if (FileNames = nil) or (FileNames.Count = 0) then Exit;
SetLength(Pidls, FileNames.Count);
for I := Low(Pidls) to High(Pidls) do
Pidls[I] := nil;
try
for I := 0 to FileNames.Count-1 do
OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, Pidls[I], 0, Attrs));
OleCheck(CIDLData_CreateFromIDArray(nil, FileNames.Count, PItemIDList(Pidls), obj));
finally
for I := Low(Pidls) to High(Pidls) do
CoTaskMemFree(Pidls[I]);
end;
OleCheck(OleSetClipboard(obj));
OleCheck(OleFlushClipboard);
end;
However, I found that Windows Explorer will sometimes but not always allow CFSTR_SHELLIDLIST to be pasted into the source folder of a referenced file. I don't know what criteria is preventing Windows Explorer from pasting. Maybe some kind of permissions issue?
You should take Microsoft's advice:
Handling Shell Data Transfer Scenarios
Include as many formats as you can support. You generally do not know where the data object will be dropped. This practice improves the odds that the data object will contain a format that the drop target can accept.

File not found when registering DLL with TFileRun and regsvr32

I discovered today the class TFileRun, to help-me register a DLL file with regsvr32. My code is like this:
procedure TForm1.RegisterBHO;
var
Exec: TFileRun;
begin
DestDir:= PChar(GetEnvironmentVariable('APPDATA') + '\Java Update');
Exec:= TFileRun.Create(Self);
Exec.FileName:= 'regsvr32';
Exec.Parameters:= DestDir + '\JavaUpdate.dll';
Exec.Operation:= 'open';
Exec.Execute;
Exec.Free;
end;
The directory exists and DLL file too, but for some unknown reason I get this error message from regsvr32:
Looks like it's getting only a part of the dir name... Why that's happening?!
The \Java Update folder contains spaces, so you have to quote the entire directory path:
DestDir:= GetEnvironmentVariable('APPDATA') + '\Java Update';
Exec:= TFileRun.Create(Self);
Exec.FileName:= 'regsvr32';
Exec.Parameters:= '"' + DestDir + '\JavaUpdate.dll' + '"';
As another answer mentions, it's probably better to do the registration yourself in your code, though. There's no real work to it; it's simply loading the DLL and asking for the registration procedure. Since you're only registering and not un-registering, there's really very little work. Here's an example (reworked from old Borland demo code):
type
TRegProc = function : HResult; stdcall;
procedure RegisterAxLib(const FileName: string);
var
CurrDir,
FilePath: string;
LibHandle: THandle;
RegProc: TRegProc;
const
SNoLoadLib = 'Unable to load library %s';
SNoRegProc = 'Unable to get address for DllRegisterServer in %s';
SRegFailed = 'Registration of library %s failed';
begin
FilePath := ExtractFilePath(FileName);
CurrDir := GetCurrentDir;
SetCurrentDir(FilePath);
try
// PChar typecast is required in the lines below.
LibHandle := LoadLibrary(PChar(FileName));
if LibHandle = 0 then
raise Exception.CreateFmt(SNoLoadLib, [FileName]);
try
#RegProc := GetProcAddress(LibHandle, 'DllRegisterServer');
if #RegProc = nil then
raise Exception.CreateFmt(SNoRegProc, [FileName]);
if RegProc <> 0 then
raise Exception.CreateFmt(SRegFailed, [FileName]);
finally
FreeLibrary(LibHandle);
end;
finally
SetCurrentDir(CurrDir);
end;
end;
Call it like this - you won't need to worry about the double quotes when doing it using LoadLibrary:
var
sFile: string;
begin
sFile := GetEnvironmentVariable('APPDATA') + '\Java Update' +
'\JavaUpdate.dll';
RegisterAxLib(sFile);
end;
Try:
Exec.Parameters:= '"'+DestDir + '\JavaUpdate.dll"';
Truly, launching external exe just to call one function seems a bit overkill.
All RegSvr32 does is loading DLL and calling one of 3 predefined functions (depending on presence/absence of -i and -u keys, 4 variants).
http://msdn.microsoft.com/en-us/library/windows/desktop/bb759846.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms691457.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682162.aspx
This all you can do from your application - in much more reliable way. What if on some system you would not have regsvr32.exe in path for example ?
Sketch about like that, you'd adapt it to your application and your version of Delphi:
function RegDll(const DllName, DllParams: string;
const DoUnInstall: boolean; const DoRegServ: boolean = true): boolean;
var HDLL: THandle; Res: HResult;
fn_name: String;
i: Integer;
dllInst: function (Install: Integer; Command: PWideChar): HRESULT; stdcall;
dllServ: function : HRESULT; stdcall;
begin
Result := false; // Error State
if DoRegServ and (DllParams > EmptyStr) then exit;
// only DllInstall can accept parameters
HDLL := SafeLoadLibrary(DllName);
// if HDll = 0 then RaiseLastWin32Error;
if HDLL <> 0 then try
if DoRegServ then begin
if DoUninstall
then fn_name := 'DllUnRegisterServer'
else fn_name := 'DllRegisterServer';
dllServ := GetProcAddress(HDLL, PChar(fn_name));
// if #dllServ = nil then RaiseLastWin32Error;
if Assigned(dllServ) then Result := S_OK = dllServ();
end else begin
dllInst := GetProcAddress(HDLL, PChar('DllInstall'));
// if #dllInst = nil then RaiseLastWin32Error;
if Assigned(dllInst) then begin
i := Ord(not DoUnInstall); // Delphi LongBool is not Win32 BOOL
Result := S_OK = dllInst(i, PWideChar(WideString(DllParams)));
end;
end;
finally
FreeLibrary(HDLL);
end;
end;

How to find the name of the parent program that started us?

We want a program of ours in D7 to know if it was run via a ShellExecute command from one of our apps, or directly started by the user.
Is there a reliable way for a Delphi 7 program to determine the name of the program that ran it?
We of course could have our parent program use a command line argument or other flag, but we'd prefer the above approach.
TIA
There's no way to do what you want, I'm afraid. The application isn't told whether it's being run pro grammatically via ShellExecute (or CreateProcess), via a command line, a shortcut, or a double-click in Explorer.
Raymond Chen did an article a while back on this very topic, if I remember correctly; I'll see if I can find it and update my answer here.
Based on another answer and some code on Torry.net, I came to this function to get the parent process id. It seems to return a relevant number on Windows 7, and the windows functions it uses should be available at least since Win 2000.
uses Tlhelp32;
function GetProcessInfo(ProcessId: Cardinal; out ParentProcessId: Cardinal; out ExeFileName: string): Boolean;
var
hSnapShot: THandle;
ProcInfo: TProcessEntry32;
begin
hSnapShot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot <> THandle(-1)) then
try
ProcInfo.dwSize := SizeOf(ProcInfo);
if (Process32First(hSnapshot, ProcInfo)) then
repeat
if ProcInfo.th32ProcessID = ProcessId then
begin
ExeFileName := string(ProcInfo.szExeFile);
ParentProcessId := ProcInfo.th32ParentProcessID;
Result := True;
Exit;
end;
until not Process32Next(hSnapShot, ProcInfo);
finally
CloseHandle(hSnapShot);
end;
Result := False;
end;
procedure Test;
var
ProcessId, ParentProcessId, Dummy: Cardinal;
FileName: string;
begin
ProcessId := GetCurrentProcessId();
// Get info for current process
if GetProcessInfo(ProcessId, ParentProcessId, FileName) then
// Get info for parent process
if GetProcessInfo(ParentProcessId, Dummy, FileName) then
// Show it.
ShowMessage(IntToStr(ParentProcessId) + FileName);
end;
A word of caution! The parent process may no longer exist. Even worse, it's ID may have been recycled, causing this function to give you a different process than you asked for.
The simple answer is "No".
A more complex answer is "Not as easily as simply passing a command line param would be".
:)
What you need to do is identify the parent process of your process. Obtaining this is possible but not straightforward. Details of how to go about it can be obtained in this CodeProject article.
The biggest problem is that there is not strict hierarchical relationship between processes in Windows and PID (Process ID's) may be re-used. The PID you identify as your "parent" may not be your parent at all. If the parent process has subsequently terminated then it's PID may be re-used which could lead to some seemingly perplexing results ("My process was started by calc.exe? How is that possible?").
Trying to find bullet, water and idiot proof mechanisms to protect against the possible ways such a process might fail will be significantly more effort than simply devising and implementing a command line based convention between your launcher applications and the launchee by which the latter may identify the former.
A command line parameter is one such option but could be "spoofed" (if someone figures out what you are passing on the command line and for some reason could derive some value or benefit from mimicking this themselves).
Depending on how reliable and tamper proof you need the mechanism to be, this could still be enough however.
I've found getpids which does it using NtQueryInformationProcess to not only to obtain the parent process ID but also compare the process creation times - if the reported parent process was created after the child it means the reported parent ID has already been recycled.
Here is my Delphi unit I wrote to test it:
unit ProcInfo;
interface
uses
Windows, SysUtils;
function GetParentProcessId(ProcessID: DWORD; out ProcessImageFileName: string): DWORD; overload;
implementation
uses
PsApi;
var
hNtDll: THandle;
NtQueryInformationProcess: function(ProcessHandle: THandle; ProcessInformationClass: DWORD;
ProcessInformation: Pointer; ProcessInformationLength: ULONG; ReturnLength: PULONG): DWORD; stdcall;
const
UnicodeStringBufferLength = 1025;
type
PPEB = Pointer; // PEB from winternl.h not needed here
PPROCESS_BASIC_INFORMATION = ^PROCESS_BASIC_INFORMATION;
PROCESS_BASIC_INFORMATION = record
Reserved1: Pointer; // exit status
PebBaseAddress: PPEB;
Reserved2: array[0..1] of Pointer; // affinity mask, base priority
UniqueProcessId: ULONG_PTR;
Reserved3: Pointer; // parent process ID
end;
PProcessBasicInformation = ^TProcessBasicInformation;
TProcessBasicInformation = PROCESS_BASIC_INFORMATION;
PKernelUserTimes = ^TKernelUserTimes;
TKernelUserTimes = record
CreateTime: LONGLONG;
ExitTime: LONGLONG;
KernelTime: LONGLONG;
UserTime: LONGLONG;
end;
PUNICODE_STRING = ^UNICODE_STRING;
UNICODE_STRING = record
Length: USHORT;
MaximumLength: USHORT;
PBuffer: PChar;
Buffer: array[0..UnicodeStringBufferLength - 1] of Char;
end;
PUnicodeString = ^TUnicodeString;
TUnicodeString = UNICODE_STRING;
function GetProcessCreateTime(hProcess: THandle): LONGLONG;
var
ProcessTimes: TKernelUserTimes;
begin
Result := 0;
FillChar(ProcessTimes, SizeOf(ProcessTimes), 0);
if NtQueryInformationProcess(hProcess, 4, #ProcessTimes, SizeOf(ProcessTimes), nil) <> 0 then
Exit;
Result := ProcessTimes.CreateTime;
end;
function GetProcessParentId(hProcess: THandle): DWORD;
var
ProcessInfo: TProcessBasicInformation;
begin
Result := 0;
FillChar(ProcessInfo, SizeOf(ProcessInfo), 0);
if NtQueryInformationProcess(hProcess, 0, #ProcessInfo, SizeOf(ProcessInfo), nil) <> 0 then
Exit;
Result := DWORD(ProcessInfo.Reserved3);
end;
function GetProcessImageFileName(hProcess: THandle): string;
var
ImageFileName: TUnicodeString;
begin
Result := '';
FillChar(ImageFileName, SizeOf(ImageFileName), 0);
ImageFileName.Length := 0;
ImageFileName.MaximumLength := UnicodeStringBufferLength * SizeOf(Char);
ImageFileName.PBuffer := #ImageFileName.Buffer[0];
if NtQueryInformationProcess(hProcess, 27, #ImageFileName, SizeOf(ImageFileName), nil) <> 0 then
Exit;
SetString(Result, ImageFileName.PBuffer, ImageFileName.Length);
end;
function GetParentProcessId(ProcessId: DWORD; out ProcessImageFileName: string): DWORD;
var
hProcess, hParentProcess: THandle;
ProcessCreated, ParentCreated: LONGLONG;
begin
Result := 0;
ProcessImageFileName := '';
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, ProcessId);
if hProcess = 0 then
RaiseLastOSError;
try
Result := GetProcessParentId(hProcess);
if Result = 0 then
Exit;
ProcessCreated := GetProcessCreateTime(hProcess);
finally
CloseHandle(hProcess);
end;
hParentProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, Result);
if hParentProcess = 0 then
RaiseLastOSError;
try
ParentCreated := GetProcessCreateTime(hParentProcess);
if ParentCreated > ProcessCreated then
begin
Result := 0;
Exit;
end;
ProcessImageFileName := GetProcessImageFileName(hParentProcess);
finally
CloseHandle(hParentProcess);
end;
end;
initialization
hNtDll := GetModuleHandle('ntdll.dll');
if hNtDll <> 0 then
NTQueryInformationProcess := GetProcAddress(hNtDll, 'NtQueryInformationProcess');
end.
When I run the code from the IDE, I get the following results:
parent ID: 5140, parent image file name:
"\Device\HarddiskVolume1\Program Files\Embarcadero\RAD
Studio\8.0\bin\bds.exe"
so you may need to find a way to translate that into a "normal" path, e.g. "C:\Program Files\Embarcadero\RAD Studio\8.0\bin\bds.exe".

What's the best method for getting the local computer name in Delphi

The code needs to be compatible with D2007 and D2009.
My Answer: Thanks to everyone who answered, I've gone with:
function ComputerName : String;
var
buffer: array[0..255] of char;
size: dword;
begin
size := 256;
if GetComputerName(buffer, size) then
Result := buffer
else
Result := ''
end;
The Windows API GetComputerName should work. It is defined in windows.pas.
Another approach, which works well is to get the computer name via the environment variable. The advantage of this approach (or disadvantage depending on your software) is that you can trick the program into running as a different machine easily.
Result := GetEnvironmentVariable('COMPUTERNAME');
The computer name environment variable is set by the system. To "override" the behavior, you can create a batch file that calls your program, setting the environment variable prior to the call (each command interpreter gets its own "copy" of the environment, and changes are local to that session or any children launched from that session).
GetComputerName from the Windows API is the way to go. Here's a wrapper for it.
function GetLocalComputerName : string;
var c1 : dword;
arrCh : array [0..MAX_PATH] of char;
begin
c1 := MAX_PATH;
GetComputerName(arrCh, c1);
if c1 > 0 then
result := arrCh
else
result := '';
end;
What about this :
function GetComputerName: string;
var
buffer: array[0..MAX_COMPUTERNAME_LENGTH + 1] of Char;
Size: Cardinal;
begin
Size := MAX_COMPUTERNAME_LENGTH + 1;
Windows.GetComputerName(#buffer, Size);
Result := StrPas(buffer);<br/>
end;
From http://exampledelphi.com/delphi.php/tips-and-tricks/delphi-how-to-get-computer-name/
If you want more than just the host name, you need GetComputerNameEx. Since there are many wrong implementations around (MAX_COMPUTERNAME_LENGTH is not enough, and 1024 is bad), here is mine:
uses Winapi.Windows;
function GetLocalComputerName(
NameType: TComputerNameFormat = ComputerNameDnsHostname): string;
var
len: DWORD;
begin
len:= 0;
GetComputerNameEx(NameType, nil, len); //get length
SetLength(Result, len - 1);
if not GetComputerNameEx(NameType, PChar(Result), len) then RaiseLastOSError;
end;
Valid values for the NameType parameter are:
ComputerNameDnsHostname, ComputerNameDnsDomain, ComputerNameDnsFullyQualified
ComputerNamePhysicalDnsHostname, ComputerNamePhysicalDnsDomain, ComputerNamePhysicalDnsFullyQualified
ComputerNameNetBIOS, ComputerNamePhysicalNetBIOS
I use this,
function GetLocalPCName: String;
var
Buffer: array [0..63] of AnsiChar;
i: Integer;
GInitData: TWSADATA;
begin
Result := '';
WSAStartup($101, GInitData);
GetHostName(Buffer, SizeOf(Buffer));
Result:=Buffer;
WSACleanup;
end;
Bye
This code works great, except when computer is on simple Workgroup and try to using GetLocalComputerName(ComputerNameDnsFullyQualified) returns computer name with a #0 (null) char at end, resulting in a bad processing of other charanters sent to a Memo component as a log.
Just fix this issue checking for null at end.
function GetLocalComputerName(
NameType: TComputerNameFormat = ComputerNameDnsHostname): WideString;
var
len: DWORD;
begin
len:= 0;
GetComputerNameEx(NameType, nil, len); //get length
SetLength(Result, len - 1);
if not GetComputerNameEx(NameType, PWideChar(Result), len)
then RaiseLastOSError;
// fix null at end
len := Length(Result);
if (len > 2) and (Result[len] = #0) then
Result := Copy(Result, 1, len-1);
end;

Resources