Using Delphi and Windows API is possible from a PItemIDList to get if the file is a folder or not? With this snippet of code I can get the only the name of the file.
procedure TMain.FolderMonitorFileChange(aSender: TObject; aPIDL: PItemIDList);
var
FileInfo : SHFILEINFOW;
begin
SHGetFileInfo(LPCTSTR(aPIDL), 0 , FileInfo, SizeOf(FileInfo), SHGFI_PIDL or SHGFI_DISPLAYNAME or SHGFI_TYPENAME or SHGFI_ATTRIBUTES);
ShowMessage('File change notification: ' + FileInfo.szDisplayName + ' ' + FileInfo.szTypeName );
end;
Thanks
For a relative PIDL, you can obtain the IShellFolder interface of the PIDL's parent folder, and then pass the PIDL to the IShellFolder::GetAttributesOf() method.
function IsFolder(Parent: IShellFolder; aChildPIDL: PItemIDList): Boolean;
var
Attrs: SFGAOF;
begin
Result := Succeeded(Parent.GetAttributesOf(1, #aChildPidl, #Attrs))
and (Attrs and SFGAO_FOLDER <> 0);
end;
For an absolute PIDL, you have a few different options:
pass the PIDL to SHBindToParent() to convert it to a relative PIDL and retrieve its parent folder's IShellFolder, then call IShellFolder::GetAttributesOf().
function IsFolder(aPIDL: PItemIDList): Boolean;
var
Parent: IShellFolder;
Child: PItemIDList;
Attrs: SFGAOF;
begin
Result := Succeeded(SHBindToParent(aPidl, IShellFolder, #Parent, #Child))
and Succeeded(Parent.GetAttributesOf(1, #Child, #Attrs))
and (Attrs and SFGAO_FOLDER <> 0);
end;
pass the PIDL to SHGetFileInfo() using the SHGFI_PIDL flag. Enable the SHGFI_ATTRIBUTES flag to request the item's attributes.
function IsFolder(aPIDL: PItemIDList): Boolean;
var
FileInfo : SHFILEINFO;
begin
Result := (SHGetFileInfo(LPCTSTR(aPIDL), 0, #FileInfo, SizeOf(FileInfo), SHGFI_PIDL or SHGFI_ATTRIBUTES) <> 0)
and (FileInfo.dwAttributes and SFGAO_FOLDER <> 0);
end;
pass the PIDL to SHCreateItemFromIDList() to retrieve an IShellItem interface for it, and then call IShellItem::GetAttributes().
function IsFolder(aPIDL: PItemIDList): Boolean;
var
Item: IShellItem;
Attrs: SFGAOF;
begin
Result := Succeeded(SHCreateItemFromIDList(aPidl, IShellItem, #Item))
and Succeeded(Item.GetAttributes(SFGAO_FOLDER, #Attrs))
and (Attrs and SFGAO_FOLDER <> 0);
end;
Related
I try to write a string to an external exe file, but when i read the string via the other project i get an other string.
Example:
When i write "test123" then i read "test123Click".
When i write "test" then i read "test.".
When i write "name" then i read "name.".
I use this script to write:
procedure WriteSettings(ServerFile: string; Settings: string);
var
ResourceHandle: THandle;
pwServerFile: PWideChar;
begin
GetMem(pwServerFile, (Length(ServerFile) + 1) * 2);
try
StringToWideChar(ServerFile, pwServerFile, Length(ServerFile) * 2);
ResourceHandle := BeginUpdateResourceW(pwServerFile, False);
UpdateResourceW(ResourceHandle, MakeIntResourceW(10), 'SETTINGS', 0, #Settings[1], ByteLength(settings));
EndUpdateResourceW(ResourceHandle, False);
finally
FreeMem(pwServerFile);
end;
end;
This is the script to read:
function LoadSettings: string;
var
ResourceLocation: HRSRC;
ResourceSize: dword;
ResourceHandle: THandle;
ResourcePointer: pointer;
begin
ResourceLocation := FindResource(hInstance, 'SETTINGS', RT_RCDATA);
ResourceSize := SizeofResource(hInstance, ResourceLocation);
ResourceHandle := LoadResource(hInstance, ResourceLocation);
ResourcePointer := LockResource(ResourceHandle);
if ResourcePointer <> nil then
begin
SetLength(Result, ResourceSize - 1);
CopyMemory(#Result[1], ResourcePointer, ResourceSize);
FreeResource(ResourceHandle);
end;
end;
What i am doing wrong?
I use everywhere a normal string, so not ansi and utf8.
You need to write a null terminator. Every time you call UpdateResourceW pass (Length(str)+1)*SizeOf(str[1]) for the byte count parameter.
Also, #Settings[1] will fail for the empty string. I'd use PWideChar() instead. Like so:
UpdateResourceW(ResourceHandle, MakeIntResourceW(10), 'SETTINGS', 0,
PWideChar(Settings), (Length(Settings)+1)*SizeOf(Settings[1]));
Although all the explicit W suffixing is rather needless. You can write:
UpdateResource(ResourceHandle, MakeIntResource(10), 'SETTINGS', 0,
PChar(Settings), (Length(Settings)+1)*SizeOf(Settings[1]));
And the dynamic memory allocation is needless. You can write:
procedure WriteSettings(ServerFile: string; Settings: string);
var
ResourceHandle: THandle;
begin
ResourceHandle := BeginUpdateResource(PChar(ServerFile), False);
UpdateResource(ResourceHandle, MakeIntResource(10), 'SETTINGS', 0,
PChar(Settings), (Length(Settings)+1)*SizeOf(Settings[1]));
EndUpdateResource(ResourceHandle, False);
end;
And you really should include some error checking:
procedure WriteSettings(ServerFile: string; Settings: string);
var
ResourceHandle: THandle;
begin
ResourceHandle := BeginUpdateResource(PChar(ServerFile), False);
Win32Check(ResourceHandle<>0);
Win32Check(UpdateResource(ResourceHandle, MakeIntResource(10), 'SETTINGS', 0,
PChar(Settings), (Length(Settings)+1)*SizeOf(Settings[1])));
Win32Check(EndUpdateResource(ResourceHandle, False));
end;
We are facing the problem, that different user groups shall be able to read and write files from a common data directory (e.g. c:\ProgramData\xyz).
The data is written from different sources e.g. a service writes files into it and a user shall be able to change it's content later on.
The problem now is that this only works if "everybody" is allowed to read/write/change
files in that directory (und subdirs).
What I want to check in the installer is if all users are allowed to do so aka. check if the "every users" group (or "Jeder" group in german) is in the access list.
I have only basic knowledge about ACL and can change that in the explorer but I would need a few lines of code which push me into the right direction (in Delphi).
many thanks
Mike
I think it's not a Delphi but WinAPI question. Delphi doesn't have any special facilities to make this easier AFAIK.
Getting information from an ACL says you need to do GetSecurityInfo on an open handle, then GetEffectiveRightsFromACL on an ACL you get.
You specify a trustee, which can be by name, but better use SID. Name for "Everyone" can change, but there's a special SID for it which is valid on any PC, google it. Okay, here it is: "(S-1–1–0)". Or you can use CreateWellKnownSid and give it WinWorldSid to get the same SID (more proper but longer way).
All of that from five minutes of googling so watch out for mistakes.
Alright, here's some code.
function ConvertStringSidToSid(StringSid: PWideChar; var Sid: PSID): boolean; stdcall; external advapi32 name 'ConvertStringSidToSidW';
function AclGetEffectiveRights(const path, sid: string): cardinal;
var h: THandle; //handle to our directory
err: integer;
dacl: PACL; //access control list for the object
secdesc: pointer;
tr: TRUSTEE;
bsid: PSid;
begin
Result := 0;
//Open directory
h := CreateFile(PChar(path), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_BACKUP_SEMANTICS, 0);
//we need FILE_FLAG_BACKUP_SEMANTICS to open a directory
if h=INVALID_HANDLE_VALUE then RaiseLastOsError();
try
bsid := nil;
//Query access control list for a directory -- the list you see in the properties box
err := GetSecurityInfo(h, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nil, nil, #dacl, nil, secdesc);
//GetSecurityInfo can return many things but we only need DACL,
//and we are required to also get a security descriptor
if err<>ERROR_SUCCESS then
raise Exception.CreateFmt('Cannot retrieve DACL: error %d',[err]);
try
//Convert string sid to binary sid
if not ConvertStringSidToSid(PChar(sid), bsid) then
RaiseLastOsError();
//Query effective rights for a trustee
BuildTrusteeWithSid(#tr, bsid);
err := GetEffectiveRightsFromAcl(dacl^, tr, Result);
if err<>ERROR_SUCCESS then
raise Exception.CreateFmt('Cannot calculate effective rights: error %d',[err]);
finally
//Documentation says to free some resources this way when we're done with it.
LocalFree(NativeUint(bsid));
LocalFree(NativeUint(secdesc));
end;
finally
CloseHandle(h);
end;
end;
It's used like this:
var rights,test: cardinal;
rights := AclGetEffectiveRights('C:\My\Folder','S-1-1-0');
//List rights you want tested
test := FILE_LIST_DIRECTORY + FILE_ADD_FILE + FILE_ADD_SUBDIRECTORY
+ FILE_READ_EA + FILE_WRITE_EA + FILE_TRAVERSE + FILE_DELETE_CHILD;
Result := (rights and test) = test;
Might not work; gives ACCESS_DENIED on my PC but that's probably because of my complicated domain situation. Anyway that's something for a start.
So.. The above version worked... until Windows Update 1903 hit me and GetEffectiveRightsFromAcl always resulted in an error ERROR_NO_SUCH_DOMAIN (which is obscure since there is no Domain whatsoever involved here).
So I needed to switch to the following procedure:
// ###########################################
// ### translated and extended from https://learn.microsoft.com/de-de/windows/win32/api/aclapi/nf-aclapi-geteffectiverightsfromacla
procedure DisplayAccessMask(Mask : ACCESS_MASK );
begin
{
// This evaluation of the ACCESS_MASK is an example.
// Applications should evaluate the ACCESS_MASK as necessary.
}
if (((Mask and GENERIC_ALL) = GENERIC_ALL)
or ((Mask and FILE_ALL_ACCESS) = FILE_ALL_ACCESS))
then
begin
OutputDebugString( 'Full control');
exit;
end;
if (((Mask and GENERIC_READ) = GENERIC_READ)
or ((Mask and FILE_GENERIC_READ) = FILE_GENERIC_READ))
then
OutputDebugString( 'Read');
if (((Mask and GENERIC_WRITE) = GENERIC_WRITE)
or ((Mask and FILE_GENERIC_WRITE) = FILE_GENERIC_WRITE))
then
OutputDebugString('Write');
if (((Mask and GENERIC_EXECUTE) = GENERIC_EXECUTE)
or ((Mask and FILE_GENERIC_EXECUTE) = FILE_GENERIC_EXECUTE))
then
OutputDebugString('Execute');
end;
function CheckMask( MASK : ACCESS_MASK; refMask : ACCESS_MASK ) : boolean;
var msk : ACCESS_MASK;
begin
msk := 0;
if (((Mask and GENERIC_READ) = GENERIC_READ)
or ((Mask and FILE_GENERIC_READ) = FILE_GENERIC_READ))
then
msk := msk or FILE_GENERIC_READ;
if (((Mask and GENERIC_WRITE) = GENERIC_WRITE)
or ((Mask and FILE_GENERIC_WRITE) = FILE_GENERIC_WRITE))
then
msk := msk or FILE_GENERIC_WRITE;
if (((Mask and GENERIC_EXECUTE) = GENERIC_EXECUTE)
or ((Mask and FILE_GENERIC_EXECUTE) = FILE_GENERIC_EXECUTE))
then
msk := msk or FILE_GENERIC_EXECUTE;
Result := (msk and refMask) = refMask;
end;
function GetAccess(hAuthzClient :AUTHZ_CLIENT_CONTEXT_HANDLE; psd : PSECURITY_DESCRIPTOR) : BOOL;
var AccessRequest : AUTHZ_ACCESS_REQUEST;
AccessReply : AUTHZ_ACCESS_REPLY;
buffer : Array[0..1023] of Byte;
begin
FillChar(AccessRequest, sizeof(AccessRequest), 0);
FillChar(AccessReply, sizeof(AccessReply), 0);
FillChar(buffer, sizeof(buffer), 0);
AccessRequest.DesiredAccess := MAXIMUM_ALLOWED;
AccessRequest.PrincipalSelfSid := nil;
AccessRequest.ObjectTypeList := nil;
AccessRequest.ObjectTypeListLength := 0;
AccessRequest.OptionalArguments := nil;
AccessReply.ResultListLength := 1;
AccessReply.GrantedAccessMask := PACCESS_MASK( LongWord(#Buffer[0]));
AccessReply.Error := PDWORD( LongWord( AccessReply.GrantedAccessMask ) + sizeof(Access_Mask));
Result := AuthzAccessCheck( 0,
hAuthzClient,
#AccessRequest,
0,
psd,
nil,
0,
#AccessReply,
nil);
if Result then
begin
DisplayAccessMask( AccessReply.GrantedAccessMask^ );
Result := CheckMask( AccessReply.GrantedAccessMask^, FILE_GENERIC_WRITE or FILE_GENERIC_READ );
end
else
RaiseLastOSError;
end;
function ConvertStringSidToSid(StringSid: PWideChar; var Sid: PSID): boolean; stdcall; external advapi32 name 'ConvertStringSidToSidW';
function ConvertNameToBinarySid(pAccountName : PCHAR): PSID ;
var pDomainName : PChar;
dwDomainNameSize : DWord;
aSID : PSID;
dwSIDSIZE : DWORD;
sidType : SID_NAME_USE;
begin
pDomainName := nil;
dwDomainNameSize := 0;
aSID := nil;
LookupAccountName( nil, pAccountName, aSID, dwSIDSIZE, pDomainName, dwDomainNameSize, sidType);
aSid := Pointer( LocalAlloc( LPTR, dwSIDSIZE*sizeof(char)) );
pDomainName := Pointer( LocalAlloc(LPTR, dwDomainNameSize*sizeof(char)) );
if not LookupAccountName( nil, pAccountName, aSID, dwSIDSIZE, pDomainName, dwDomainNameSize, sidType) then
begin
LocalFree( Cardinal(aSID) );
Result := nil;
end
else
begin
Result := aSid;
end;
LocalFree( Cardinal(pDomainName) );
end;
function GetEffectiveRightsForSID(hManager :AUTHZ_RESOURCE_MANAGER_HANDLE;
psd : PSECURITY_DESCRIPTOR;
sid : PChar) : BOOL;
var asid : PSID;
bResult : BOOL;
unusedID : LUID;
hAuthzClientContext : AUTHZ_CLIENT_CONTEXT_HANDLE;
begin
Result := False;
asid := nil;
hAuthzClientContext := 0;
FillChar(unusedID, sizeof(unusedID), 0);
if not ConvertStringSidToSid(sid, asid) then
RaiseLastOsError();
// asid := ConvertNameToBinarySid('rabatscher');
if asid = nil then
RaiseLastOSError;
try
if asid <> nil then
begin
bResult := AuthzInitializeContextFromSid( 0, aSid, hManager, nil, unusedId, nil, #hAuthzClientContext );
try
if bResult then
Result := GetAccess(hAuthzClientContext, psd);
finally
if hAuthzClientContext <> 0 then
AuthzFreeContext(hAuthzClientContext);
end;
end;
finally
if asid <> nil then
LocalFree(LongWord(asid));
end;
end;
function UseAuthzSolution( psd : PSECURITY_DESCRIPTOR; const sid : string = 'S-1-1-0') : boolean;
var hManager : AUTHZ_RESOURCE_MANAGER_HANDLE;
bResult : BOOL;
pSid : PChar;
begin
bResult := AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT,
nil, nil, nil, nil, #hManager);
if bResult then
begin
pSid := PChar(sid);
bResult := GetEffectiveRightsForSID(hManager, psd, psid);
AuthzFreeResourceManager(hManager);
end;
Result := bResult;
end;
function GetSecurityInfo(handle: THandle; ObjectType: SE_OBJECT_TYPE;
SecurityInfo: SECURITY_INFORMATION; ppsidOwner, ppsidGroup: PPSID; ppDacl, ppSacl: PACL;
var pSecurityDescriptor: PSECURITY_DESCRIPTOR): DWORD; stdcall; external 'ADVAPI32.DLL' name 'GetSecurityInfo'; {use localfree to release ppSecurityDescriptor}
function CheckDirectoryAccess( path : string ) : boolean;
var dw : DWORD;
apacl : PACL;
psd : PSECURITY_DESCRIPTOR;
apSID : PSID;
h : THandle;
begin
try
apSID := nil;
//Open directory
h := CreateFile(PChar(path), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_BACKUP_SEMANTICS, 0);
//we need FILE_FLAG_BACKUP_SEMANTICS to open a directory
if h = INVALID_HANDLE_VALUE then
RaiseLastOsError();
try
//Query access control list for a directory -- the list you see in the properties box
dw := GetSecurityInfo(h, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION or OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION,
nil, nil, #apacl, nil, psd);
if dw <> ERROR_SUCCESS then
RaiseLastOSError;
try
Result := UseAuthzSolution(psd);
finally
if apSID <> nil then
LocalFree(NativeUint(apSID));
LocalFree(NativeUint(psd));
end;
finally
CloseHandle(h);
end;
except
on E : Exception do
begin
Result := False;
end;
end;
end;
Note that there are a few changes so the procedure works:
GetSecurityInfo (from the above procedure) needs the parameter DACL_SECURITY_INFORMATION or OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION (not only DACL_SECURITY_INFORMATION) otherwise you get an Error 87 in AuthzAccessCheck!
In addition you need to check out the JWA headers from the jedi library.
Hope that helps other people too.
I have multiple services processing some files. Each service must have exclusive access to the file while processing. I solved this problem a while ago by creating a global mutex that uses some temp files, something like this:
function AppLocked: boolean;
begin
result := FileExists(GetTempDir + '__MUTEX__' + LockExt);
end;
procedure AppLock;
var
F: TextFile;
begin
if FileExists(GetTempDir + '__MUTEX__' + LockExt) then
exit
else
try
AssignFile(F, GetTempDir + '__MUTEX__' + LockExt);
Rewrite(F);
Writeln(F, DateTimeToStr(Now));
CloseFile(F);
except
end;
end;
procedure AppUnLock;
begin
if FileExists(GetTempDir + '__MUTEX__' + LockExt) then
SysUtils.DeleteFile(GetTempDir + '__MUTEX__' + LockExt);
end;
This works pretty good, and I don't want to fix something that works, but I just wonder, is there a better solution?
An actual Mutex (as in win32 Mutex) is the preferred method.
Your solution has a problem, if the application terminates and you missed to unlock. This could happen on an abnormal termination. It would be better to create a file, that will automatically erase itself if the application terminates.
All the magic is done by FILE_FLAG_DELETE_ON_CLOSE
unit uAppLock;
interface
function AppLocked : Boolean;
function AppLock : Boolean;
procedure AppUnlock;
implementation
uses
Windows, SysUtils, Classes;
var
// unit global variable
LockFileHandle : THandle;
// function to build the filename
function GetLockFileName : string;
begin
// You have to point out, where to get these informations
Result := GetTempDir + '__MUTEX__' + LockExt;
end;
function AppLocked : Boolean;
begin
Result := FileExists( GetLockFileName );
end;
function AppLock : Boolean;
var
LFileName : string;
LLockFileStream : TStream;
LInfoStream : TStringStream;
begin
Result := False;
if AppLock
then
Exit;
LFileName := GetLockFileName;
// Retrieve the handle of the LockFile
LockFileHandle := CreateFile( PChar( LFileName ), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_DELETE,
nil, CREATE_NEW, FILE_FLAG_DELETE_ON_CLOSE or FILE_ATTRIBUTE_TEMPORARY, 0 );
if LockFileHandle <> INVALID_HANDLE_VALUE
then
begin
Result := True;
LInfoStream := nil;
LLockFileStream := nil;
try
LInfoStream := TStringStream.Create;
LInfoStream.WriteString( DateTimeToStr( Now ) );
LInfoStream.Seek( 0, soFromBeginning );
LLockFileStream := THandleStream.Create( LockFileHandle );
LLockFileStream.CopyFrom( LInfoStream, LInfoStream.Size );
finally
LInfoStream.Free;
LLockFileStream.Free;
end;
end;
end;
procedure AppUnlock;
begin
// Just close the handle and the file will be deleted
CloseHandle( LockFileHandle );
end;
end.
BTW: GetTempDir looks to be a Directory, but you use it as a Path. So it would be better to rename it into GetTempPath instead :o)
DelphiXe.
For reception of the version of a file I use function:
function FileVersion(AFileName: string): string;
var
szName: array[0..255] of Char;
P: Pointer;
Value: Pointer;
Len: UINT;
GetTranslationString: string;
FFileName: PChar;
FValid: boolean;
FSize: DWORD;
FHandle: DWORD;
FBuffer: PChar;
begin
try
FFileName := StrPCopy(StrAlloc(Length(AFileName) + 1), AFileName);
FValid := False;
FSize := GetFileVersionInfoSize(FFileName, FHandle);
if FSize > 0 then
try
GetMem(FBuffer, FSize);
FValid := GetFileVersionInfo(FFileName, FHandle, FSize, FBuffer);
except
FValid := False;
raise;
end;
Result := '';
if FValid then
VerQueryValue(FBuffer, '\VarFileInfo\Translation', p, Len)
else
p := nil;
if P <> nil then
GetTranslationString := IntToHex(MakeLong(HiWord(Longint(P^)),
LoWord(Longint(P^))), 8);
if FValid then
begin
StrPCopy(szName, '\StringFileInfo\' + GetTranslationString +
'\FileVersion');
if VerQueryValue(FBuffer, szName, Value, Len) then
Result := StrPas(PChar(Value));
end;
finally
try
if FBuffer <> nil then
FreeMem(FBuffer, FSize);
except
end;
try
StrDispose(FFileName);
except
end;
end;
end;
For the majority of executed files and libraries it returns correct value. But at some files the version is cut off and shown without Build.
Here for example file BASS.DLL (http://us.un4seen.com/files/bass24.zip)
In Windows Explorer in properties of a file I see version 2.4.7.1, function result='2.4.7' :(
I open a file through Resourcehacker.exe (http://angusj.com/resourcehacker/), I look structure VersionInfo:
1 VERSIONINFO
FILEVERSION 2,4,7,1
PRODUCTVERSION 2,4,0,0
FILEOS 0x4
FILETYPE 0x2
{
BLOCK "StringFileInfo"
{
BLOCK "000004b0"
{
VALUE "CompanyName", "Un4seen Developments"
VALUE "FileDescription", "BASS"
VALUE "FileVersion", "2.4.7"
VALUE "LegalCopyright", "Copyright © 1999-2010"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0000 0x04B0
}
}
Question: how to receive 2.4.7.1, i.e. the full version?
If you want the file version of the root block, then forget about the language specific translation:
function FileVersion(const FileName: TFileName): String;
var
VerInfoSize: Cardinal;
VerValueSize: Cardinal;
Dummy: Cardinal;
PVerInfo: Pointer;
PVerValue: PVSFixedFileInfo;
begin
Result := '';
VerInfoSize := GetFileVersionInfoSize(PChar(FileName), Dummy);
GetMem(PVerInfo, VerInfoSize);
try
if GetFileVersionInfo(PChar(FileName), 0, VerInfoSize, PVerInfo) then
if VerQueryValue(PVerInfo, '\', Pointer(PVerValue), VerValueSize) then
with PVerValue^ do
Result := Format('v%d.%d.%d build %d', [
HiWord(dwFileVersionMS), //Major
LoWord(dwFileVersionMS), //Minor
HiWord(dwFileVersionLS), //Release
LoWord(dwFileVersionLS)]); //Build
finally
FreeMem(PVerInfo, VerInfoSize);
end;
end;
If you want to edit the BASS audio library (Which you can't). The reason why is because it is compressed with "Petite v1.4" which Ian wants so people can not edit it easily.
Also to get the version bass.dll has an export that when used you can get the absolute version of it like you want without a lot of hacking and what not.
HI. Could you help me please. How to show standard windows "File Properties" dialog for a list of files, but the files have different location?
For ex:
D:\
D:\Pictures
E:\Text.txt
I've found an example and it works fine:
function SHMultiFileProperties(pDataObj: IDataObject; Flag: DWORD): HRESULT;
stdcall; external 'shell32.dll';
function GetFileListDataObject(Files: TStrings): IDataObject;
type
PArrayOfPItemIDList = ^TArrayOfPItemIDList;
TArrayOfPItemIDList = array[0..0] of PItemIDList;
var
Malloc: IMalloc;
Root: IShellFolder;
p: PArrayOfPItemIDList;
chEaten, dwAttributes: ULONG;
i, FileCount: Integer;
begin
Result := nil;
FileCount := Files.Count;
if FileCount = 0 then Exit;
OleCheck(SHGetMalloc(Malloc));
OleCheck(SHGetDesktopFolder(Root));
p := AllocMem(SizeOf(PItemIDList) * FileCount);
try
for i := 0 to FileCount - 1 do
try
if not (DirectoryExists(Files[i]) or FileExists(Files[i])) then Continue;
OleCheck(Root.ParseDisplayName(GetActiveWindow,
nil,
PWideChar(WideString(Files[i])),
chEaten,
p^[i],
dwAttributes));
except
end;
OleCheck(Root.GetUIObjectOf(GetActiveWindow,
FileCount,
p^[0],
IDataObject,
nil,
Pointer(Result)));
finally
for i := 0 to FileCount - 1 do
begin
if p^[i] <> nil then Malloc.Free(p^[i]);
end;
FreeMem(p);
end;
end;
procedure ShowFileProperties(Files: TStrings; aWnd: HWND);
type
PArrayOfPItemIDList = ^TArrayOfPItemIDList;
TArrayOfPItemIDList = array[0..0] of PItemIDList;
var
Data: IDataObject;
begin
if Files.Count = 0 then Exit;
Data := GetFileListDataObject(Files);
SHMultiFileProperties(Data, 0);
end;
But when I pass a Drive letter, it shows an empty "File Properties" dialog:
///
SL.Add('D:\');
ShowFileProperties(SL, Handle);
I have another example:
Procedure ShowFileProperties(Const filename: String);
Var
sei: TShellExecuteinfo;
Begin
FillChar(sei,sizeof(sei),0);
sei.cbSize := sizeof(sei);
sei.lpFile := Pchar(filename);
sei.lpVerb := 'Properties';
sei.fMask := SEE_MASK_INVOKEIDLIST;
ShellExecuteEx(#sei);
End;
It also shows "File Properties" Dialog , but unfortunately for one file only. How to pass multiple files with different locations in this example???
I also found another source which has the procedures I need but they require files to be located in the same folder. Here is a link: link text
I would take a look at the code example found here. I think you should be able to use that idea to pass in multiple file paths.