I'm building a Delphi XE3 application which needs to be able to have files dropped onto it. I have the Explorer > Application side of things working, but for the life of me can't figure out to get the filename when going from Application > Application.
Assuming one file is dropped from say Outlook (or any other application), I have this which works as long as I manually assign filename before hand.
SetFormatEtc( FormatEtc , CF_FILECONTENTS );
OleCheck( dataObj.GetData( FormatEtc , Medium ) );
OleStream := TOleStream.Create( IUnknown( Medium.stm ) as IStream );
MemStream := TMemoryStream.Create;
OleStream.Position := 0;
MemStream.CopyFrom( OleStream , OleStream.Size );
TMemoryStream( MemStream ).SaveToFile( 'C:\' + filename );
MemStream.Free;
OleStream.Free;
ReleaseStgMedium( Medium );
CF_FILECONTENTS format can contain several stream. You must check CF_FILEDESCRIPTORW and CF_FILEDESCRIPTORA formats for detection of stream count and stream names. Some sources:
function ContainFormat(ADataObject: IDataObject; AFormat: TClipFormat;
ATymed: Longint; AAspect: LongInt = DVASPECT_CONTENT; AIndex: LongInt = -1): Boolean;
var Format: TFormatEtc;
begin
ZeroMemory(#Format, SizeOf(Format));
Format.cfFormat := AFormat;
Format.dwAspect := AAspect;
Format.lindex := AIndex;
Format.tymed := ATymed;
Result := ADataObject.QueryGetData(Format) = S_OK;
end;
procedure InvalidMedium;
begin
raise Exception.Create('Invalid medium');
end;
function ExtractStream(ADataObject: IDataObject; AIndex: Integer): IStream;
var Format: TFormatEtc;
Medium: TStgMedium;
begin
ZeroMemory(#Format, SizeOf(Format));
Format.cfFormat := CF_FILECONTENTS;
Format.dwAspect := DVASPECT_CONTENT;
Format.lindex := AIndex;
Format.tymed := TYMED_ISTREAM;
ZeroMemory(#Medium, SizeOf(Medium));
OleCheck(ADataObject.GetData(Format, Medium));
try
if (Medium.tymed and TYMED_ISTREAM = 0) or not Assigned(Medium.stm) then
InvalidMedium;
Result := IStream(Medium.stm);
finally
ReleaseStgMedium(Medium);
end
end;
procedure WorkWithDropObject(const AFileName: UnicodeString; AStream: IStream);
begin
end;
procedure ProcessDataObject(ADataObject: IDataObject);
var Format: TFormatEtc;
Medium: TStgMedium;
FGDA: PFileGroupDescriptorA;
FGDW: PFileGroupDescriptorW;
i: Integer;
Stream: IStream;
begin
if ContainFormat(ADataObject, CF_FILECONTENTS, TYMED_ISTREAM) then
begin
if ContainFormat(ADataObject, CF_FILEDESCRIPTORW, TYMED_HGLOBAL) then
begin
Format.cfFormat := CF_FILEDESCRIPTORW;
Format.dwAspect := DVASPECT_CONTENT;
Format.lindex := -1;
Format.tymed := TYMED_HGLOBAL;
ZeroMemory(#Medium, SizeOf(Medium));
OleCheck(ADataObject.GetData(Format, Medium));
try
if (Medium.tymed and TYMED_HGLOBAL = 0) or (Medium.hGlobal = 0) then
InvalidMedium;
FGDW := GlobalLock(Medium.hGlobal);
if not Assigned(FGDW) then
RaiseLastOSError;
try
for i := 0 to FGDW.cItems - 1 do
begin
Stream := ExtractStream(ADataObject, i);
try
WorkWithDropObject(FGDW.fgd[i].cFileName, Stream);
finally
Stream := nil;
end;
end;
finally
GlobalUnlock(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end
end
else
if ContainFormat(ADataObject, CF_FILEDESCRIPTORA, TYMED_HGLOBAL) then
begin
Format.cfFormat := CF_FILEDESCRIPTORA;
Format.dwAspect := DVASPECT_CONTENT;
Format.lindex := -1;
Format.tymed := TYMED_HGLOBAL;
ZeroMemory(#Medium, SizeOf(Medium));
OleCheck(ADataObject.GetData(Format, Medium));
try
if (Medium.tymed and TYMED_HGLOBAL = 0) or (Medium.hGlobal = 0) then
InvalidMedium;
FGDA := GlobalLock(Medium.hGlobal);
if not Assigned(FGDA) then
RaiseLastOSError;
try
for i := 0 to FGDA.cItems - 1 do
begin
Stream := ExtractStream(ADataObject, i);
try
WorkWithDropObject(FGDA.fgd[i].cFileName, Stream);
finally
Stream := nil;
end;
end;
finally
GlobalUnlock(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end
end;
end;
end;
Also I you want to create universal software you should process the following formats:
CF_FILENAMEW/CF_FILENAMEA
CF_HDROP
CF_IDLIST
CF_FILEDESCRIPTORW/CF_FILEDESCRIPTORA/CF_FILECONTENTS
Related
I am a Delphi developer in our company. We need a function which launches a command-line executable and get its return value.
The code I wrote, and all the examples I found on the Internet, do this via CreateProcess(), but my boss rejected this and told me that there MUST be a solution doing this via ShellExecute(). I can't find any example on the Internet doing this with ShellExecute(). All of them use CreateProcess().
Below are 3 methods I delivered to my boss. He did not like ShellExecute_AndGetReturnValue(). It's named "ShellExecute", but it does not use ShellExecute().
All of these 3 methods are working fine. But the first one is not using ShellExecute(). Instead it is using CreateProcess().
So, is it possible to solve/change the ShellExecute_AndGetReturnValue() method so that it will use ShellExecute() instead of CreateProcess()? All examples I found, all of them, use CreateProcess().
function ShellExecute_AndGetReturnValue(FileName : string; Params : string = ''; Show : Integer = SW_HIDE; WorkingDir : string = '') : string;
const
READ_BUFFER_SIZE = 2048;
var
Security: TSecurityAttributes;
readableEndOfPipe, writeableEndOfPipe, readableErrorEndOfPipe, writeableErrorEndOfPipe: THandle;
start: TStartUpInfo;
ProcessInfo: TProcessInformation;
Buffer: PAnsiChar;
BytesRead: DWORD;
AppRunning: DWORD;
ResultStdOutput : string;
ResultErrOutput : string;
lpDirectory : PAnsiChar;
CmdLine : string;
begin
Result := '';
Security.nLength := SizeOf(TSecurityAttributes);
Security.bInheritHandle := True;
Security.lpSecurityDescriptor := nil;
if CreatePipe(readableEndOfPipe, writeableEndOfPipe, #Security, 0) then
begin
Buffer := AllocMem(READ_BUFFER_SIZE + 1);
FillChar(Start, Sizeof(Start), #0);
FillChar(ProcessInfo, SizeOf(ProcessInfo), #0);
start.cb := SizeOf(start);
start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;
start.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
start.hStdOutput := writeableEndOfPipe;
CreatePipe(readableErrorEndOfPipe, writeableErrorEndOfPipe, #Security, 0);
start.hStdError := writeableErrorEndOfPipe;
start.hStdError := writeableEndOfPipe;
start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;
start.wShowWindow := Show;
UniqueString(FileName);
CmdLine := '"' + FileName + '" ' + Params;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
if CreateProcess(nil, PChar(CmdLine), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, lpDirectory, start, ProcessInfo) then
begin
repeat
Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
Application.ProcessMessages;
until (Apprunning <> WAIT_TIMEOUT);
ResultStdOutput := '';
ResultErrOutput := '';
//Must Close write Handles before reading (if the console application does not output anything)
CloseHandle(writeableEndOfPipe);
CloseHandle(writeableErrorEndOfPipe);
repeat
BytesRead := 0;
ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
ResultStdOutput := ResultStdOutput + String(Buffer);
until (BytesRead < READ_BUFFER_SIZE);
if start.hStdOutput <> start.hStdError then
begin
BytesRead := 0;
ReadFile(readableErrorEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
ResultErrOutput := ResultErrOutput + String(Buffer);
end;
end;
Result := ResultStdOutput;
FreeMem(Buffer);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(readableEndOfPipe);
CloseHandle(readableErrorEndOfPipe);
end;
end;
procedure ShellExecute_NoWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
exInfo: TShellExecuteInfo;
Ph: DWORD;
begin
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do
begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := GetActiveWindow();
lpVerb := PAnsiChar(Action);
lpParameters := PChar(Params);
lpFile := PChar(FileName);
nShow := Show;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
end;
if ShellExecuteEx(#exInfo) then
begin
Ph := exInfo.HProcess;
CloseHandle(Ph);
end;
end;
procedure ShellExecute_AndWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
exInfo: TShellExecuteInfo;
Ph: DWORD;
begin
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do
begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := GetActiveWindow();
lpVerb := PAnsiChar(Action);
lpParameters := PChar(Params);
lpFile := PChar(FileName);
nShow := Show;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
end;
if ShellExecuteEx(#exInfo) then
begin
Ph := exInfo.HProcess;
while WaitForSingleObject(ExInfo.hProcess, 50) <> WAIT_OBJECT_0 do
begin
Application.ProcessMessages;
end;
CloseHandle(Ph);
end;
end;
Task from your boss is not fully correct. Problem is that the generic solution of ShellExecute – is not start cmd.exe, this command starts an application that is linked to this type of file and starts it. So, to make it work like you want – it needs a lot of work.
One more thing – do you need to get the result of work of your program or console output of your program?
Here is modified part of sources from jcl library to return return code:
function PCharOrNil(const S: string): PChar;
begin
Result := Pointer(S);
end;
// memory initialization
procedure ResetMemory(out P; Size: Longint);
begin
if Size > 0 then
begin
Byte(P) := 0;
FillChar(P, Size, 0);
end;
end;
function ShellExecAndWait(const FileName: string; const Parameters: string;
const Verb: string; CmdShow: Integer; const Directory: string): cardinal;
var
Sei: TShellExecuteInfo;
Res: LongBool;
Msg: tagMSG;
ShellResult : boolean;
begin
ResetMemory(Sei, SizeOf(Sei));
Sei.cbSize := SizeOf(Sei);
Sei.fMask := SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS or
SEE_MASK_FLAG_DDEWAIT or SEE_MASK_NOASYNC;
Sei.lpFile := PChar(FileName);
Sei.lpParameters := PCharOrNil(Parameters);
Sei.lpVerb := PCharOrNil(Verb);
Sei.nShow := CmdShow;
Sei.lpDirectory := PCharOrNil(Directory);
{$TYPEDADDRESS ON}
ShellResult := ShellExecuteEx(#Sei);
{$IFNDEF TYPEDADDRESS_ON}
{$TYPEDADDRESS OFF}
{$ENDIF ~TYPEDADDRESS_ON}
if ShellResult then begin
WaitForInputIdle(Sei.hProcess, INFINITE);
while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
repeat
Msg.hwnd := 0;
Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
if Res then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
until not Res;
if not GetExitCodeProcess(Sei.hProcess, Result) then
raise Exception.Create('GetExitCodeProcess fail');
CloseHandle(Sei.hProcess);
end else begin
raise Exception.Create('ShellExecuteEx fail');
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
xResult : cardinal;
begin
xResult := ShellExecAndWait('ping.exe', '', '', 1, ''); //xResult = 1
xResult := ShellExecAndWait('ping.exe', '8.8.8.8', '', 1, ''); //xResult = 0
end;
If you need to specify input/output pipes (to control stdin and stdout of the called process) then ShellExecute cannot be used. It simply does not support specifying these. Neither does ShellExecuteEx.
So the only option you have if you must use ShellExecute is to ShellExecute the command processor (CMD.EXE) and ask it to perform the redirection of input and output. This will limit your redirection source and target to physical files on the disk, as that's the way CMD.EXE allows redirection (>StdOut <StdIn).
Othwewise, your approach with CreateProcess is the way forward. What does your boss give as reason that you must use ShellExecute?
If you don't need redirection support, you can use ShellExecuteEx and then after a successful execution, you can obtain the Handle to the running process in Info.hProcess (Info is the TShellExecuteInfo structure passed to ShellExecuteEx).
This value can then be used in GetExitCodeProcess to determine if the process is still running, or if it has terminated (and you have thus retrieved the "Return Value", if I have correctly understood your use of this expression - it's actually called an "ExitCode", or - in batch files - an "ERRORLEVEL").
Incomplete code:
FUNCTION ShellExecuteAndWait(....) : DWORD;
.
.
VAR Info : TShellExecuteInfo;
.
.
Info.fMask:=Info.fMask OR SEE_MASK_NOCLOSEPROCESS;
IF NOT ShellExecuteEx(Info) THEN EXIT($FFFF8000);
IF Info.hProcess=0 THEN EXIT($FFFF0000);
REPEAT
IF NOT GetExitCodeProcess(Info.hProcess,Result) THEN EXIT($FFFFFFFF)
UNTIL Result<>STILL_ACTIVE
.
.
The above code should demonstrate how to do this...
I have saved my TreeView inside my DataBase by using the next :
var
BlobField :TField;
Query:TADOQuery;
Stream:TStream;
...
try
Query.SQL.Text := 'Select TOP(1) * From MyTable';
DBQueryConnect(Query); // I used this Procedure to connect the Query to the database
try
Query.First;
Query.Edit;
try
BlobField := Query.FieldByName('MyField') as TField;
Stream := Query.CreateBlobStream(BlobField, bmWrite);
try
MyTreeView.SaveToStream(Stream,TEncoding.UTF8);
finally
Stream.Free;
end;
Query.Post;
except
Query.Cancel;
raise;
end;
finally
Query.Close;
end;
finally
Query.Free;
end;
end;
and I loaded back the TTreeView form the DataBase by using the next :
...
var
Query:TADOQuery;
Stream:TStream;
begin
Query:=TADOQuery.Create(Self);
try
Query.SQL.Add('Select * From MyTable') ;
DBQueryConnect(Query);
Query.First;
Stream:=Query.CreateBlobStream(Query.FieldByName('MyField'), bmread);
MyTreeView.LoadFromStream(Stream);
Stream.Free;
finally
Query.Free;
end;
how can I retrive the imageindex for my TreeView items from the saved data ..
Thank you .
Perharps we can modify exsisting SaveTreeToStream and LoadTreeFromStream like this :
function GetBufStart(Buffer,idxSeparator: string; var Level,ImageIndex: Integer): string;
var
Pos: Integer;
sidx:String;
begin
Pos := 1;
Level := 0;
ImageIndex := -1;
while (CharInSet(Buffer[Pos], [' ', #9])) do
begin
Inc(Pos);
Inc(Level);
end;
Result := Copy(Buffer, Pos, Length(Buffer) - Pos + 1);
//Check Image Index
pos:=System.SysUtils.AnsiPos(idxSeparator,Result);
if Pos>0 then begin
sidx:=copy(result,Pos + Length(idxSeparator), length(result) - Pos + 1);
ImageIndex := StrToIntDef(sidx,-1);
Result := Copy(Result, 1, Pos - 1);
end;
end;
procedure LoadTreeFromStream(Nodes:TTreeNodes; Stream:TStream; Encoding:TEncoding; idxSeparator:String='|||');
var
List: TStringList;
ANode, NextNode: TTreeNode;
ALevel, i, ImageIndex: Integer;
CurrStr: string;
begin
List := TStringList.Create;
Nodes.BeginUpdate;
try
try
Nodes.Clear;
List.LoadFromStream(Stream, Encoding);
ANode := nil;
for i := 0 to List.Count - 1 do
begin
CurrStr := GetBufStart(PChar(List[i]), idxSeparator, ALevel, ImageIndex);
if ANode = nil then
ANode := Nodes.AddChild(nil, CurrStr)
else if ANode.Level = ALevel then
ANode := Nodes.AddChild(ANode.Parent, CurrStr)
else if ANode.Level = (ALevel - 1) then
ANode := Nodes.AddChild(ANode, CurrStr)
else if ANode.Level > ALevel then
begin
NextNode := ANode.Parent;
while NextNode.Level > ALevel do
NextNode := NextNode.Parent;
ANode := Nodes.AddChild(NextNode.Parent, CurrStr);
end
else raise Exception.CreateFmt('Invalid level (%d) for item "%s"', [ALevel, CurrStr]);
ANode.ImageIndex:=ImageIndex;
end;
finally
Nodes.EndUpdate;
List.Free;
end;
except
Nodes.Owner.Invalidate; // force repaint on exception
raise;
end;
end;
procedure SaveTreeToStream(Nodes:TTreeNodes; Stream:Tstream; Encoding:TEncoding; idxSeparator:String='|||');
const
TabChar = #9;
EndOfLine = #13#10;
var
I: Integer;
ANode: TTreeNode;
NodeStr: TStringBuilder;
Buffer, Preamble: TBytes;
begin
if Nodes.Count > 0 then
begin
if Encoding = nil then
Encoding := TEncoding.Default;
//Buffer := Encoding.GetBytes('');
Preamble := Encoding.GetPreamble;
if Length(Preamble) > 0 then
Stream.WriteBuffer(Preamble{$IFNDEF CLR}[0]{$ENDIF}, Length(Preamble));
NodeStr := TStringBuilder.Create(1024);
try
ANode := Nodes[0];
while ANode <> nil do
begin
NodeStr.Length := 0;
for I := 0 to ANode.Level - 1 do
NodeStr.Append(TabChar);
NodeStr.Append(ANode.Text);
NodeStr.Append(idxSeparator);
NodeStr.Append(ANode.ImageIndex);
NodeStr.Append(EndOfLine);
Buffer := Encoding.GetBytes(NodeStr.ToString);
Stream.Write(Buffer{$IFNDEF CLR}[0]{$ENDIF}, Length(Buffer));
ANode := ANode.GetNext;
end;
finally
NodeStr.Free;
end;
end;
end;
You can replace
MyTreeView.SaveToStream(Stream,TEncoding.UTF8);
with
SaveTreeToStream(MyTreeView.Items,Stream,TEncoding.UTF8);
and MyTreeView.LoadFromStream(Stream); with LoadTreeFromStream(MyTreeView.Items,Stream,TEncoding.UTF8);
I'm trying to link a database image to a TDBImage component in delphi 10 but it keeps giving me the error that my specified field cannot be found even though there aren't any syntax errors.
This is the code I'm using.
function TForm1.JPEGStartBlob(fPic: TBlobField): integer;
var
bS: TADOBlobStream;
buffer: Word;
hx: string;
begin
Result := -1;
bS := TADOBlobStream.Create(fPic, bmRead);
try
while (Result = -1) and (bS.Position + 1 < bS.Size) do
begin
bS.ReadBuffer(buffer, 1);
hx := IntToHex(buffer, 2);
if hx = 'FF' then
begin
bS.ReadBuffer(buffer, 1);
hx := IntToHex(buffer, 2);
if hx = 'D8' then
Result := bS.Position - 2
else if hx = 'FF' then
bS.Position := bS.Position - 1;
end;
end;
finally
bS.Free;
end;
end;
procedure TForm1.ShowImage(Sender: TObject);
var
bsImage : TADOBlobStream;
jImage : TJPEGImage;
begin
bsImage := TADOBlobStream.Create(adoLodgeI.FieldByName('Image') // this is the field that can't be
// found
AS TBlobField, bmRead);
try
bsImage.Seek(JPEGStartBlob(adoLodgeI.FieldByName('Image') AS TBlobField),
soFromBeginning);
jImage := TJPEGImage.Create;
try
jImage.LoadFromStream(bsImage);
dbiLodge1.Picture.Graphic := jImage;
finally
jImage.Free;
end;
finally
bsImage.Free;
end;
end;
If anyone can help it will be much appreciated.
You can use TWICImage and then you can just assign it a the TDBImage.Picture directly
Var
AStream: TMemoryStream;
APic: TWICImage;
begin
AStream := TMemoryStream.Create;
try
// Here "Data" is a BlobField
AStream:= TMemoryStream(TPics.CreateBlobStream(TPics.FieldByName('Data'), bmRead));
AStream.Position:= 0;
APic := TWICImage.Create;
try
APic.LoadFromStream(AStream);
DBImage1.Picture.Assign(APic);
finally
APic.Free;
end;
finally
AStream.Free;
end;
end;
Works with *.jpeg;*.jpg;*.png;*.bmp;*.ico images and you don't need to worry about if the image is a TJPEGImage or not.
I'm trying to get PIDs of processes which belongs to the current user but I don't know how to check the process owner.
This is my code (the user's checking condition is missing):
uses
TlHelp32, ...;
type
TCardinalArray = array of Cardinal;
function GetCurrentUserPIDs(const AProcessName : string) : TCardinalArray;
var
ContinueLoop: boolean;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
SetLength(Result, 0);
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while(ContinueLoop) do
begin
if(SameText(FProcessEntry32.szExeFile, AProcessName)) then
begin
if((* is this my process? *)) then
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := FProcessEntry32.th32ProcessID;
end;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
I found a GetUserAndDomainFromPID function which allows to easily accomplish the task.
As Sertac Akyuz suggested, the function uses OpenProcessToken and GetTokenInformation. It also uses LookupAccountSid:
uses
TlHelp32;
type
PTOKEN_USER = ^TOKEN_USER;
_TOKEN_USER = record
User: TSidAndAttributes;
end;
TOKEN_USER = _TOKEN_USER;
function GetUserAndDomainFromPID(ProcessId: DWORD;
var User, Domain: string): Boolean;
var
hToken: THandle;
cbBuf: Cardinal;
ptiUser: PTOKEN_USER;
snu: SID_NAME_USE;
ProcessHandle: THandle;
UserSize, DomainSize: DWORD;
bSuccess: Boolean;
begin
Result := False;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION, False, ProcessId);
if ProcessHandle <> 0 then
begin
// EnableProcessPrivilege(ProcessHandle, 'SeSecurityPrivilege', True);
if OpenProcessToken(ProcessHandle, TOKEN_QUERY, hToken) then
begin
bSuccess := GetTokenInformation(hToken, TokenUser, nil, 0, cbBuf);
ptiUser := nil;
while (not bSuccess) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) do
begin
ReallocMem(ptiUser, cbBuf);
bSuccess := GetTokenInformation(hToken, TokenUser, ptiUser, cbBuf, cbBuf);
end;
CloseHandle(hToken);
if not bSuccess then
begin
Exit;
end;
UserSize := 0;
DomainSize := 0;
LookupAccountSid(nil, ptiUser.User.Sid, nil, UserSize, nil, DomainSize, snu);
if (UserSize <> 0) and (DomainSize <> 0) then
begin
SetLength(User, UserSize);
SetLength(Domain, DomainSize);
if LookupAccountSid(nil, ptiUser.User.Sid, PChar(User), UserSize,
PChar(Domain), DomainSize, snu) then
begin
Result := True;
User := StrPas(PChar(User));
Domain := StrPas(PChar(Domain));
end;
end;
if bSuccess then
begin
FreeMem(ptiUser);
end;
end;
CloseHandle(ProcessHandle);
end;
end;
Then I've written a function for getting the current windows username (It uses GetUserName):
const
UNLEN = 256; // Maximum user name length
function GetWindowsUsername: string;
var
UserName : string;
UserNameLen : Dword;
begin
UserNameLen := UNLEN;
SetLength(UserName, UserNameLen) ;
if GetUserName(PChar(UserName), UserNameLen)
then Result := Copy(UserName, 1, UserNameLen - 1)
else Result := '';
end;
The following function returns an array composed by all ids of processes who belongs to the current user (Note that processes are filtered by process name):
uses
TlHelp32;
type
TCardinalArray = array of Cardinal;
function GetCurrentUserPIDs(const AProcessName : string) : TCardinalArray;
var
ContinueLoop: boolean;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
UserName : string;
DomainName : string;
CurrentUser : string;
begin
CurrentUser := GetWindowsUsername();
SetLength(Result, 0);
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while(ContinueLoop) do
begin
if(SameText(FProcessEntry32.szExeFile, AProcessName)) then
begin
if(GetUserAndDomainFromPID(FProcessEntry32.th32ProcessID, UserName, DomainName)) then
begin
if(UserName = CurrentUser) then
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := FProcessEntry32.th32ProcessID;
end;
end;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
In Delphi XE7, I use this code to create a SHELL LINK pointing to a specific folder. This folder is displayed in Windows Explorer with a custom folder icon defined by a desktop.ini file inside this folder. The SHELL LINK should be created with the icon parameters found in the desktop.ini file, i.e. pointing to the same icon resource as the desktop.ini file. So here is the code:
function GetDesktopIniIconDataFromFolder(const APath: string; var VIconIndex: Integer): string;
var
DeskTopIniFile: string;
DesktopIni: System.IniFiles.TIniFile;
ThisIconFileStr, ThisIconIndexStr: string;
ThisIconIndexInt: Integer;
begin
Result := '';
if DirectoryExists(APath) then
begin
DeskTopIniFile := IncludeTrailingPathDelimiter(APath) + 'Desktop.ini';
if FileExists(DeskTopIniFile) then
begin
DesktopIni := System.IniFiles.TIniFile.Create(DeskTopIniFile);
try
ThisIconFileStr := DesktopIni.ReadString('.ShellClassInfo', 'IconFile', '');
if ThisIconFileStr <> '' then
begin
ThisIconIndexStr := DesktopIni.ReadString('.ShellClassInfo', 'IconIndex', '');
if ThisIconIndexStr <> '' then
begin
ThisIconIndexInt := System.SysUtils.StrToIntDef(ThisIconIndexStr, MaxInt);
if ThisIconIndexInt <> MaxInt then
begin
Result := ThisIconFileStr;
VIconIndex := ThisIconIndexInt;
end;
end;
end;
finally
DesktopIni.Free;
end;
end;
end;
end;
function MyCreateShellLink(const LinkFileName, AssocFileName, Desc, WorkDir,
Args, IconFileName: string; const IconIdx: Integer): Boolean;
var
SL: Winapi.ShlObj.IShellLink;
PF: Winapi.ActiveX.IPersistFile;
begin
Result := False;
Winapi.ActiveX.CoInitialize(nil);
try
if Winapi.ActiveX.Succeeded(
Winapi.ActiveX.CoCreateInstance(
Winapi.ShlObj.CLSID_ShellLink,
nil,
Winapi.ActiveX.CLSCTX_INPROC_SERVER,
Winapi.ShlObj.IShellLink, SL
)
) then
begin
SL.SetPath(PChar(AssocFileName));
SL.SetDescription(PChar(Desc));
SL.SetWorkingDirectory(PChar(WorkDir));
SL.SetArguments(PChar(Args));
if (IconFileName <> '') and (IconIdx >= 0) then
SL.SetIconLocation(PChar(IconFileName), IconIdx);
PF := SL as Winapi.ActiveX.IPersistFile;
Result := Winapi.ActiveX.Succeeded(
PF.Save(PWideChar(WideString(LinkFileName)), True)
);
end;
finally
Winapi.ActiveX.CoUninitialize;
end;
end;
// Usage:
var
IconFile: string;
IconIndex: Integer;
begin
IconFile := GetDesktopIniIconDataFromFolder(APath, IconIndex);
if IconFile <> '' then
MyCreateShellLink(ALinkFileName, ATargetFileName, ADescription, AWorkDir, AArgs, IconFile, IconIndex);
This works well, EXCEPT in cases where the IconIndex in the desktop.ini file is a negative value (which means the negative value indicates a resource ID rather than an ordinal value), like in this example:
[.ShellClassInfo]
InfoTip=#Shell32.dll,-12688
IconFile=%SystemRoot%\system32\mydocs.dll
IconIndex=-101
In this case the created SHELL LINK is erroneous, which means the Shell LINK does not contain the correct icon reference.
So how can I translate the negative IconIndex value -101 from the desktop.ini file to a value I can use in the MyCreateShellLink function?
If you want to use negative IconIndex then pass FULL path of icon to SetIconLocation. Use the following variant of GetDesktopIniIconDataFromFolder:
function GetDesktopIniIconDataFromFolder(const APath: string; var AIconIndex: Integer): string;
var
Setting: TSHFolderCustomSettings;
begin
ZeroMemory(#Setting, SizeOf(Setting));
Setting.dwSize := SizeOf(Setting);
Setting.dwMask := FCSM_ICONFILE;
SetLength(Result, MAX_PATH + 1);
Setting.pszIconFile := PChar(Result);
Setting.cchIconFile := MAX_PATH;
if Succeeded(SHGetSetFolderCustomSettings(#Setting, PChar(APath), FCS_READ)) then
begin
Result := PChar(Result);
AIconIndex := Setting.iIconIndex;
end
else
Result := '';
end;
It automatically expands variables of icon path. Also it supports IconResource parameter of desktop.ini.
Variant 2 (universal)
function GetObjectIconFileName(AParentWnd: HWND; const AName: UnicodeString; var AIndex: Integer): UnicodeString;
var
Desktop: IShellFolder;
Attr: DWORD;
Eaten: DWORD;
IDList: PItemIDList;
Parent: IShellFolder;
Child: PItemIDList;
ExtractIconW: IExtractIconW;
ExtractIconA: IExtractIconA;
AnsiResult: AnsiString;
Flags: DWORD;
Ext: UnicodeString;
BuffSize: DWORD;
P: Integer;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := SFGAO_STREAM;
OleCheck(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(AName), Eaten, IDList, Attr));
try
OleCheck(SHBindToParent(IDList, IShellFolder, Pointer(Parent), Child));
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconW, nil, ExtractIconW)) then
try
SetLength(Result, MAX_PATH + 1);
if (ExtractIconW.GetIconLocation(0, PWideChar(Result), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := PWideChar(Result);
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconW := nil;
end
else
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconA, nil, ExtractIconA)) then
try
SetLength(AnsiResult, MAX_PATH + 1);
if (ExtractIconA.GetIconLocation(0, PAnsiChar(AnsiResult), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := UnicodeString(PAnsiChar(AnsiResult));
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconA := nil;
end;
finally
CoTaskMemFree(IDList);
end;
finally
Desktop := nil;
end;
if Attr and SFGAO_STREAM <> 0 then
begin
Ext := ExtractFileExt(AName);
if (AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, nil, #BuffSize) = S_FALSE) and (BuffSize > 1) then
begin
SetLength(Result, BuffSize - 1);
if Succeeded(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, PWideChar(Result), #BuffSize)) then
begin
AIndex := 0;
P := LastDelimiter(',', Result);
if P > 0 then
begin
AIndex := StrToIntDef(Copy(Result, P + 1, MaxInt), MaxInt);
if AIndex <> MaxInt then
Delete(Result, P, MaxInt)
else
AIndex := 0;
end;
Exit;
end;
end;
end;
Result := '';
end;