Recent

Author Topic: Read Windows Storage FileProperties  (Read 5018 times)

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Read Windows Storage FileProperties
« on: September 27, 2018, 12:17:49 am »
I am trying to read out the file properties of a file in Windows using Lazarus.
That is, for example, the rating of an image file or other properties you can access via the file properties dialog in Windows.

Here are links to the Microsoft Documentation of what I mean:
https://docs.microsoft.com/en-us/uwp/api/windows.storage.fileproperties
https://docs.microsoft.com/en-us/uwp/api/windows.storage.fileproperties.imageproperties

I have found some Delphi code on the Internet for doing that task, for example:
https://www.entwickler-ecke.de/topic_Dateiinfos+lesen+und+schreiben_25452,0.html
https://www.swissdelphicenter.com/de/showcode.php?id=1614

However, I could not make to run that code without an error.

For example, the following code:

Code: Pascal  [Select]
  1. unit u_filesummary;
  2.  
  3. {$MODE DELPHI}
  4.  
  5. interface
  6.  
  7. uses Windows, ComObj, ActiveX, Variants, Sysutils;
  8.  
  9. procedure SetFileSummaryInfo(const FileName : WideString; Author, Title, Subject, Keywords, Comments : WideString);
  10. function GetFileSummaryInfo(const FileName: WideString): String;
  11. function IsNTFS(AFileName : string) : boolean;
  12.  
  13. implementation
  14.  
  15. const
  16.  
  17. FmtID_SummaryInformation: TGUID =     '{F29F85E0-4FF9-1068-AB91-08002B27B3D9}';
  18. FMTID_DocSummaryInformation : TGUID = '{D5CDD502-2E9C-101B-9397-08002B2CF9AE}';
  19. FMTID_UserDefinedProperties : TGUID = '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}';
  20. IID_IPropertySetStorage : TGUID =     '{0000013A-0000-0000-C000-000000000046}';
  21.  
  22. STGFMT_FILE = 3; //Indicates that the file must not be a compound file.
  23.                  //This element is only valid when using the StgCreateStorageEx
  24.                  //or StgOpenStorageEx functions to access the NTFS file system
  25.                  //implementation of the IPropertySetStorage interface.
  26.                  //Therefore, these functions return an error if the riid
  27.                  //parameter does not specify the IPropertySetStorage interface,
  28.                  //or if the specified file is not located on an NTFS file system volume.
  29.  
  30. STGFMT_ANY = 4; //Indicates that the system will determine the file type and
  31.                 //use the appropriate structured storage or property set
  32.                 //implementation.
  33.                 //This value cannot be used with the StgCreateStorageEx function.
  34.  
  35.  
  36. // Summary Information
  37.  PID_TITLE        = 2;
  38.  PID_SUBJECT      = 3;
  39.  PID_AUTHOR       = 4;
  40.  PID_KEYWORDS     = 5;
  41.  PID_COMMENTS     = 6;
  42.  PID_TEMPLATE     = 7;
  43.  PID_LASTAUTHOR   = 8;
  44.  PID_REVNUMBER    = 9;
  45.  PID_EDITTIME     = 10;
  46.  PID_LASTPRINTED  = 11;
  47.  PID_CREATE_DTM   = 12;
  48.  PID_LASTSAVE_DTM = 13;
  49.  PID_PAGECOUNT    = 14;
  50.  PID_WORDCOUNT    = 15;
  51.  PID_CHARCOUNT    = 16;
  52.  PID_THUMBNAIL    = 17;
  53.  PID_APPNAME      = 18;
  54.  PID_SECURITY     = 19;
  55.  
  56.  // Document Summary Information
  57.  PID_CATEGORY     = 2;
  58.  PID_PRESFORMAT   = 3;
  59.  PID_BYTECOUNT    = 4;
  60.  PID_LINECOUNT    = 5;
  61.  PID_PARCOUNT     = 6;
  62.  PID_SLIDECOUNT   = 7;
  63.  PID_NOTECOUNT    = 8;
  64.  PID_HIDDENCOUNT  = 9;
  65.  PID_MMCLIPCOUNT  = 10;
  66.  PID_SCALE        = 11;
  67.  PID_HEADINGPAIR  = 12;
  68.  PID_DOCPARTS     = 13;
  69.  PID_MANAGER      = 14;
  70.  PID_COMPANY      = 15;
  71.  PID_LINKSDIRTY   = 16;
  72.  PID_CHARCOUNT2   = 17;
  73.  
  74. function IsNTFS(AFileName : string) : boolean;
  75. var
  76. fso, drv : OleVariant;
  77. begin
  78. IsNTFS := False;
  79. fso := CreateOleObject('Scripting.FileSystemObject');
  80. drv := fso.GetDrive(fso.GetDriveName(AFileName));
  81.  if drv.FileSystem = 'NTFS' then
  82.   IsNTFS := True;
  83. end;
  84.  
  85. function StgOpenStorageEx (
  86.  const pwcsName : POleStr;  //Pointer to the path of the
  87.                             //file containing storage object
  88.  grfMode : LongInt;         //Specifies the access mode for the object
  89.  stgfmt : DWORD;            //Specifies the storage file format
  90.  grfAttrs : DWORD;          //Reserved; must be zero
  91.  pStgOptions : Pointer;     //Address of STGOPTIONS pointer
  92.  reserved2 : Pointer;       //Reserved; must be zero
  93.  riid : PGUID;              //Specifies the GUID of the interface pointer
  94.  out stgOpen :              //Address of an interface pointer
  95.  IStorage ) : HResult; stdcall; external 'ole32.dll';
  96.  
  97.  
  98. function GetFileSummaryInfo(const FileName: WideString): String;
  99.  
  100. var
  101.  I: Integer;
  102.  PropSetStg: IPropertySetStorage;
  103.  PropSpec: array of TPropSpec;
  104.  PropStg: IPropertyStorage;
  105.  PropVariant: array of TPropVariant;
  106.  Rslt: HResult;
  107.  S: String;
  108.  Stg: IStorage;
  109.  PropEnum: IEnumSTATPROPSTG;
  110.  HR : HResult;
  111.  PropStat: STATPROPSTG;
  112.  k : integer;
  113.  AHRes: HRESULT;
  114.  
  115. function PropertyPIDToCaption(const ePID: Cardinal): string;
  116. begin
  117.  case ePID of
  118.    PID_TITLE:         Result := 'Title';
  119.    PID_SUBJECT:       Result := 'Subject';
  120.    PID_AUTHOR:        Result := 'Author';
  121.    PID_KEYWORDS:      Result := 'Keywords';
  122.    PID_COMMENTS:      Result := 'Comments';
  123.    PID_TEMPLATE:      Result := 'Template';
  124.    PID_LASTAUTHOR:    Result := 'Last Saved By';
  125.    PID_REVNUMBER:     Result := 'Revision Number';
  126.    PID_EDITTIME:      Result := 'Total Editing Time';
  127.    PID_LASTPRINTED:   Result := 'Last Printed';
  128.    PID_CREATE_DTM:    Result := 'Create Time/Date';
  129.    PID_LASTSAVE_DTM:  Result := 'Last Saved Time/Date';
  130.    PID_PAGECOUNT:     Result := 'Number of Pages';
  131.    PID_WORDCOUNT:     Result := 'Number of Words';
  132.    PID_CHARCOUNT:     Result := 'Number of Characters';
  133.    PID_THUMBNAIL:     Result := 'Thumbnail';
  134.    PID_APPNAME:       Result := 'Creating Application';
  135.    PID_SECURITY:      Result := 'Security';
  136.    else
  137.      Result := '$' + IntToHex(ePID, 8);
  138.    end
  139. end;
  140.  
  141. begin
  142.  Result := '';
  143. try
  144.  OleCheck(StgOpenStorageEx(PWideChar(FileName), STGM_READ or STGM_SHARE_DENY_WRITE,  STGFMT_FILE,  0, nil,  nil, @IID_IPropertySetStorage, stg));
  145.  
  146.  PropSetStg := Stg as IPropertySetStorage;
  147.  
  148.  AHRes := PropSetStg.Open(FmtID_SummaryInformation, STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);
  149.  
  150.  if AHRes <> S_OK then exit;   // AHRes should be 0 -> ERROR
  151.  
  152.  OleCheck(AHRes);
  153.  
  154.  OleCheck(PropStg.Enum(PropEnum));
  155.  I := 0;
  156.  
  157.  hr := PropEnum.Next(1, PropStat, nil);
  158.   while hr = S_OK do begin
  159.     inc(I);
  160.     SetLength(PropSpec,I);
  161.     PropSpec[i-1].ulKind := PRSPEC_PROPID;
  162.     PropSpec[i-1].propid := PropStat.propid;
  163.     hr := PropEnum.Next(1, PropStat, nil);
  164.  end;
  165.  
  166.  SetLength(PropVariant,i);
  167.  Rslt := PropStg.ReadMultiple(i, @PropSpec[0], @PropVariant[0]);
  168.  
  169.  if Rslt =  S_FALSE then Exit;
  170.  
  171.  for k := 0 to i -1 do begin
  172.     S := '';
  173.     if PropVariant[k].vt = VT_LPSTR then
  174.       if Assigned(PropVariant[k].pszVal) then
  175.        S := PropVariant[k].pszVal;
  176.  
  177.     S := Format(PropertyPIDToCaption(PropSpec[k].Propid)+ ' %s',[s]);
  178.    if S <> '' then Result := Result + S + LineEnding;
  179.  end;
  180.  finally
  181.  end;
  182. end;
  183.  
  184. procedure SetFileSummaryInfo(const FileName : WideString; Author, Title, Subject, Keywords, Comments : WideString);
  185.  
  186. var
  187. PropSetStg: IPropertySetStorage;
  188. PropSpec: array of TPropSpec;
  189. PropStg: IPropertyStorage;
  190. PropVariant: array of TPropVariant;
  191. Stg: IStorage;
  192.  
  193. begin
  194.  OleCheck(StgOpenStorageEx(PWideChar(FileName), STGM_SHARE_EXCLUSIVE or STGM_READWRITE, STGFMT_ANY, 0, nil,  nil, @IID_IPropertySetStorage, stg));
  195.  PropSetStg := Stg as IPropertySetStorage;
  196.  OleCheck(PropSetStg.Create(FmtID_SummaryInformation, FmtID_SummaryInformation, PROPSETFLAG_DEFAULT,STGM_CREATE or STGM_READWRITE or STGM_SHARE_EXCLUSIVE, PropStg));
  197.  Setlength(PropSpec,6);
  198.  PropSpec[0].ulKind := PRSPEC_PROPID;
  199.  PropSpec[0].propid := PID_AUTHOR;
  200.  PropSpec[1].ulKind := PRSPEC_PROPID;
  201.  PropSpec[1].propid := PID_TITLE;
  202.  PropSpec[2].ulKind := PRSPEC_PROPID;
  203.  PropSpec[2].propid := PID_SUBJECT;
  204.  PropSpec[3].ulKind := PRSPEC_PROPID;
  205.  PropSpec[3].propid := PID_KEYWORDS;
  206.  PropSpec[4].ulKind := PRSPEC_PROPID;
  207.  PropSpec[4].propid := PID_COMMENTS;
  208.  PropSpec[5].ulKind := PRSPEC_PROPID;
  209.  PropSpec[5].propid := 99;
  210.  
  211.  SetLength(PropVariant,6);
  212.  PropVariant[0].vt:=VT_LPWSTR;
  213.  PropVariant[0].pwszVal:=PWideChar(Author);
  214.  PropVariant[1].vt:=VT_LPWSTR;
  215.  PropVariant[1].pwszVal:=PWideChar(Title);
  216.  PropVariant[2].vt:= VT_LPWSTR;
  217.  PropVariant[2].pwszVal:=PWideChar(Subject);
  218.  PropVariant[3].vt:= VT_LPWSTR;
  219.  PropVariant[3].pwszVal:=PWideChar(Keywords);
  220.  PropVariant[4].vt:= VT_LPWSTR;
  221.  PropVariant[4].pwszVal:=PWideChar(Comments);
  222.  PropVariant[5].vt:= VT_LPWSTR;
  223.  PropVariant[5].pwszVal:=PWideChar(Comments);
  224.  OleCheck(PropStg.WriteMultiple(6, @PropSpec[0], @PropVariant[0], 2 ));
  225.  PropStg.Commit(STGC_DEFAULT);
  226. end;
  227.  
  228. end.    
  229.  

Here, the problem is the following line in GetFileSummaryInfo:

Code: Pascal  [Select]
  1. AHRes := PropSetStg.Open(FmtID_SummaryInformation,
  2.      STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);
  3.  
  4. if AHRes <> S_OK then exit;
  5.  
  6. OleCheck(AHRes);  
  7.  

PropSetStg.Open should return S_OK = 0 if the function is not failing. But it fails and the result is something else. I have added the AHRes check, because without an exit, it crashes at that point.

Have someone an idea to make it work? According to the discussion, the same code seems to run in Delphi.

« Last Edit: September 27, 2018, 12:19:33 am by LazProgger »

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 680
    • Lebeau Software
Re: Read Windows Storage FileProperties
« Reply #1 on: September 27, 2018, 01:54:43 am »
fso := CreateOleObject('Scripting.FileSystemObject');
drv := fso.GetDrive(fso.GetDriveName(AFileName));
 if drv.FileSystem = 'NTFS' then
  IsNTFS := True;

Why not use GetVolumeInformation() instead?  It has an optional FileSystemName output, and doesn't require you to use a COM object to access it.

Code: [Select]
AHRes := PropSetStg.Open(FmtID_SummaryInformation, STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);

 if AHRes <> S_OK then exit;   // AHRes should be 0 -> ERROR

 OleCheck(AHRes);

That OleCheck() is useless, since you already checked AHRes for error.

Code: [Select]
SetLength(PropVariant,i);
 Rslt := PropStg.ReadMultiple(i, @PropSpec[0], @PropVariant[0]);

 if Rslt =  S_FALSE then Exit;

You are not handling the case where the number of values may be 0, thus PropSpec[0] and PropVariant[0] would be invalid, and cause a bounds error if range checking is enabled.

Code: [Select]
if i = 0 then Exit; // <-- add this!

SetLength(PropVariant,i);
...

Here, the problem is the following line in GetFileSummaryInfo:

Code: Pascal  [Select]
  1. AHRes := PropSetStg.Open(FmtID_SummaryInformation,
  2.      STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);
  3.  
  4. if AHRes <> S_OK then exit;
  5.  
  6. OleCheck(AHRes);  
  7.  

PropSetStg.Open should return S_OK = 0 if the function is not failing. But it fails and the result is something else.

What value is it ACTUALLY returning instead of 0?

I have added the AHRes check, because without an exit, it crashes at that point.

It doesn't *crash*, it just raises an exception you are not handling.
« Last Edit: September 27, 2018, 01:57:10 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #2 on: September 27, 2018, 03:40:21 am »
First, I have to say that I just took this code from the Internet and I have not optimized it yet. I found several codes, but they are all not working or stopping at some point.
Suggestions such as better using GetVolumeInformation(), are good points for the optimization later, I will keep that in mind.
But now I just want to make it work and I even don't know whether this is the right approach to read those information.

Code: [Select]
AHRes := PropSetStg.Open(FmtID_SummaryInformation, STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);

 if AHRes <> S_OK then exit;   // AHRes should be 0 -> ERROR

 OleCheck(AHRes);

That OleCheck() is useless, since you already checked AHRes for error.

The original code was OleCheck(PropSetStg.Open...) in just one line - I have introduced the variable AHRes to see what's going on there and I have added the line " if AHRes <> S_OK then exit;" to find out at which line the error occurs. Again, this is just the debugging code to make it work at all (to find out how to make it work), the optimization will be done later.

Here, the problem is the following line in GetFileSummaryInfo:

Code: Pascal  [Select]
  1. AHRes := PropSetStg.Open(FmtID_SummaryInformation,
  2.      STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);
  3.  
  4. if AHRes <> S_OK then exit;
  5.  
  6. OleCheck(AHRes);  
  7.  

PropSetStg.Open should return S_OK = 0 if the function is not failing. But it fails and the result is something else.

What value is it ACTUALLY returning instead of 0?


It is returning -2147287038.
The exception is:
exception class EOleSysError
dgb unparsed remainder: Cannot acces memory at address 0x0

I have added the AHRes check, because without an exit, it crashes at that point.

It doesn't *crash*, it just raises an exception you are not handling.

Yes, you are right. But the problem remains the same, something is not working.
For example, this code:

Code: Pascal  [Select]
  1. unit u_fileprops;
  2.  
  3. {$MODE DELPHI}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Windows, ComObj, ShlObj, ActiveX;
  9.  
  10. const
  11.   FMTID_SummaryInformation          : TGUID = '{F29F85E0-4FF9-1068-AB91-08002B27B3D9}';
  12.   FMTID_DocSummaryInformation       : TGUID = '{D5CDD502-2E9C-101B-9397-08002B2CF9AE}';
  13.   FMTID_UserDefinedProperties       : TGUID = '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}';
  14.   FMTID_AudioSummaryInformation     : TGuid = '{64440490-4C8B-11D1-8B70-080036B11A03}';
  15.   FMTID_VideoSummaryInformation     : TGUID = '{64440491-4C8B-11D1-8B70-080036B11A03}';
  16.   FMTID_ImageSummaryInformation     : TGUID = '{6444048f-4c8b-11d1-8b70-080036b11a03}';
  17.   FMTID_MediaFileSummaryInformation : TGUID = '{64440492-4c8b-11d1-8b70-080036b11a03}';
  18.  
  19.  
  20.   STGFMT_STORAGE     = 0;
  21.   STGFMT_FILE        = 3;
  22.   STGFMT_ANY         = 4;
  23.   STGFMT_DOCFILE     = 5;
  24.  
  25.   // Summary Information
  26.   PIDSI_TITLE        = $00000002;     // VT_LPSTR
  27.   PIDSI_SUBJECT      = $00000003;
  28.   PIDSI_AUTHOR       = $00000004;
  29.   PIDSI_KEYWORDS     = $00000005;
  30.   PIDSI_COMMENTS     = $00000006;
  31.   PIDSI_TEMPLATE     = $00000007;
  32.   PIDSI_LASTAUTHOR   = $00000008;
  33.   PIDSI_REVNUMBER    = $00000009;
  34.   PIDSI_EDITTIME     = $0000000A;     // VT_FILETIME (UTC)
  35.   PIDSI_LASTPRINTED  = $0000000B;
  36.   PIDSI_CREATE_DTM   = $0000000C;
  37.   PIDSI_LASTSAVE_DTM = $0000000D;
  38.   PIDSI_PAGECOUNT    = $0000000E;     // VT_I4
  39.   PIDSI_WORDCOUNT    = $0000000F;
  40.   PIDSI_CHARCOUNT    = $00000010;
  41.   PIDSI_THUMBNAIL    = $00000011;     // VT_CF
  42.   PIDSI_APPNAME      = $00000012;     // VT_LPSTR
  43.   PIDSI_DOC_SECURITY = $00000013;     // VT_I4
  44.  
  45.   // Document Summary Information
  46.   PIDDSI_CATEGORY    = $00000002;     // VT_LPSTR
  47.   PIDDSI_PRESFORMAT  = $00000003;
  48.   PIDDSI_BYTECOUNT   = $00000004;     // VT_I4
  49.   PIDDSI_LINECOUNT   = $00000005;
  50.   PIDDSI_PARCOUNT    = $00000006;
  51.   PIDDSI_SLIDECOUNT  = $00000007;
  52.   PIDDSI_NOTECOUNT   = $00000008;
  53.   PIDDSI_HIDDENCOUNT = $00000009;
  54.   PIDDSI_MMCLIPCOUNT = $0000000A;
  55.   PIDDSI_SCALE       = $0000000B;     // VT_BOOL
  56.   PIDDSI_HEADINGPAIR = $0000000C;     // VT_VARIANT | VT_VECTOR
  57.   PIDDSI_DOCPARTS    = $0000000D;     // VT_LPSTR | VT_VECTOR
  58.   PIDDSI_MANAGER     = $0000000E;     // VT_LPSTR
  59.   PIDDSI_COMPANY     = $0000000F;
  60.   PIDDSI_LINKSDIRTY  = $00000010;     // VT_BOOL
  61.  
  62.   // FMTID_MediaFileSummaryInfo
  63.   PIDMSI_EDITOR      = $00000002;     // VT_LPWSTR
  64.   PIDMSI_SUPPLIER    = $00000003;
  65.   PIDMSI_SOURCE      = $00000004;
  66.   PIDMSI_SEQUENCE_NO = $00000005;
  67.   PIDMSI_PROJECT     = $00000006;
  68.   PIDMSI_STATUS      = $00000007;     // VT_UI4
  69.   PIDMSI_OWNER       = $00000008;     // VT_LPWSTR
  70.   PIDMSI_RATING      = $00000009;
  71.   PIDMSI_PRODUCTION  = $0000000A;     // VT_FILETIME (UTC)
  72.   PIDMSI_COPYRIGHT   = $0000000B;     // VT_LPWSTR
  73.  
  74.   // FMTID_AudioSummaryInformation
  75.   PIDASI_FORMAT        = $00000002;   // VT_BSTR
  76.   PIDASI_TIMELENGTH    = $00000003;   // VT_UI4, milliseconds
  77.   PIDASI_AVG_DATA_RATE = $00000004;   // VT_UI4,  Hz
  78.   PIDASI_SAMPLE_RATE   = $00000005;   // VT_UI4,  bits
  79.   PIDASI_SAMPLE_SIZE   = $00000006;   // VT_UI4,  bits
  80.   PIDASI_CHANNEL_COUNT = $00000007;   // VT_UI4
  81.   PIDASI_STREAM_NUMBER = $00000008;   // VT_UI2
  82.   PIDASI_STREAM_NAME   = $00000009;   // VT_LPWSTR
  83.   PIDASI_COMPRESSION   = $0000000A;   // VT_LPWSTR
  84.  
  85.   // FMTID_VideoSummaryInformation
  86.   PIDVSI_STREAM_NAME   = $00000002;   // "StreamName", VT_LPWSTR
  87.   PIDVSI_FRAME_WIDTH   = $00000003;   // "FrameWidth", VT_UI4
  88.   PIDVSI_FRAME_HEIGHT  = $00000004;   // "FrameHeight", VT_UI4
  89.   PIDVSI_TIMELENGTH    = $00000007;   // "TimeLength", VT_UI4, milliseconds
  90.   PIDVSI_FRAME_COUNT   = $00000005;   // "FrameCount". VT_UI4
  91.   PIDVSI_FRAME_RATE    = $00000006;   // "FrameRate", VT_UI4, frames/millisecond
  92.   PIDVSI_DATA_RATE     = $00000008;   // "DataRate", VT_UI4, bytes/second
  93.   PIDVSI_SAMPLE_SIZE   = $00000009;   // "SampleSize", VT_UI4
  94.   PIDVSI_COMPRESSION   = $0000000A;   // "Compression", VT_LPWSTR
  95.   PIDVSI_STREAM_NUMBER = $0000000B;   // "StreamNumber", VT_UI2
  96.  
  97. type
  98.   TMultipleArray     = record
  99.     pidInfoType : cardinal;
  100.     pidInfoStr  : pchar;
  101.   end;
  102.   TMultipleArrayList = array of TMultipleArray;
  103.  
  104.  
  105. function GetFileSummaryInfo(const FileName: WideString; GUID_SummaryType: TGUID; PID_InfoType: cardinal): string;
  106.  
  107.  
  108. implementation
  109.  
  110.  
  111. const
  112.   szOleDll                = 'ole32.dll';
  113.   IID_IPropertySetStorage : TGUID = '{0000013A-0000-0000-C000-000000000046}';
  114. type
  115.   TStgOpenStorageEx       = function(const pwcsName: PWideChar;
  116.     grfMode: DWORD; stgfmt: DWORD; grfAttrs: DWORD; pStgOptions: pointer;
  117.     reserved2: pointer; riid: PGUID; out ppObjectOpen: IStorage): HRESULT;
  118.     stdcall;
  119. var
  120.   StgOpenStorageEx        : TStgOpenStorageEx = nil;
  121.   dll                     : HANDLE = 0; // dword = 0;
  122.   Win2k                   : boolean = false;
  123.  
  124.  
  125. function GetFileSummaryInfo(const FileName: WideString; GUID_SummaryType: TGUID; PID_InfoType: cardinal): string;
  126. var
  127.   hr          : HRESULT;
  128.   Stg         : IStorage;
  129.   PropSetStg  : IPropertySetStorage;
  130.   PropStg     : IPropertyStorage;
  131.   PropSpec    : TPropSpec;
  132.   PropVariant : TPropVariant;
  133. begin
  134.   Result      := '';
  135.   hr          := S_FALSE;
  136.  
  137.   // for 9x and NT4 users
  138.   if(not Win2k) or (@StgOpenStorageEx = nil) then
  139.     hr := StgOpenStorage(pwidechar(FileName),nil,STGM_READ or  STGM_SHARE_DENY_WRITE,nil,0,Stg)
  140.   // for 2000, XP and higher
  141.   else if(@StgOpenStorageEx <> nil) then
  142.     hr := StgOpenStorageEx(pwidechar(FileName),STGM_READ or STGM_SHARE_DENY_WRITE,STGFMT_ANY,0,nil,nil, @IID_IPropertySetStorage,Stg);
  143.  
  144.   if(hr = S_OK) then  begin
  145.  
  146.     PropSetStg := Stg as IPropertySetStorage;          
  147.  
  148.     if(PropSetStg.Open(GUID_SummaryType,STGM_READ or STGM_SHARE_EXCLUSIVE,PropStg) = S_OK) then begin
  149.       PropSpec.ulKind := PRSPEC_PROPID;
  150.       PropSpec.propid := PID_InfoType;
  151.  
  152.       if(PropStg.ReadMultiple(1,@PropSpec,@PropVariant) = S_OK) and (PropVariant.vt = VT_LPSTR) and (PropVariant.pszVal <> nil) then
  153.       Result := PropVariant.pszVal;
  154.     end;
  155.   end;
  156. end;
  157.  
  158.  
  159.  
  160. //
  161. // Main
  162. //
  163. var
  164.   os : TOSVersionInfo;
  165.  
  166. initialization
  167.   // get OS
  168.   ZeroMemory(@os,sizeof(os));
  169.   os.dwOSVersionInfoSize := sizeof(os);
  170.  
  171.   // is it Windows 2000 or higher?
  172.   Win2k                  := (GetVersionEx(os)) and
  173.     (os.dwMajorVersion >= 5) and
  174.     (os.dwPlatformId = VER_PLATFORM_WIN32_NT);
  175.  
  176.   CoInitialize(nil);
  177.  
  178.   dll := LoadLibrary(szOleDll);
  179.   if(dll <> 0) then begin
  180.     StgOpenStorageEx := GetProcAddress(dll,'StgOpenStorageEx');
  181.     if(@StgOpenStorageEx = nil) then begin
  182.       FreeLibrary(dll);
  183.       dll := 0;
  184.     end;
  185.   end;
  186. finalization
  187.   if(dll <> 0) then FreeLibrary(dll);
  188.   CoUninitialize;
  189. end.
  190.  
  191.  
  192.  

Again, it always "stops" at the PropSetStg.Open() line (I have tested it with several files):

Code: Pascal  [Select]
  1. if(PropSetStg.Open(GUID_SummaryType,STGM_READ or STGM_SHARE_EXCLUSIVE,PropStg) = S_OK) then begin
  2.  

In this case, the code checks whether the result is S_OK and only continues if it is S_OK. So, there is no exception occurring compared to the first code.
But again, I don't know why PropSetStg.Open does not return S_OK and why this function is not working like it should. The result is -2147287038 in this code, too.
« Last Edit: September 27, 2018, 03:47:49 am by LazProgger »

ASerge

  • Hero Member
  • *****
  • Posts: 1422
Re: Read Windows Storage FileProperties
« Reply #3 on: September 28, 2018, 04:12:52 pm »
I am trying to read out the file properties of a file in Windows using Lazarus.
For example: Topic: how to get the original filedate not date modifyed

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #4 on: September 30, 2018, 05:23:17 pm »
For example: Topic: how to get the original filedate not date modifyed

Thanks! I have tried your code, but unfortunately, it crashes.

I get a SIGSEGV here:

Code: Pascal  [Select]
  1. and Succeeded(DirFolder.GetDetailsEx(FilePIDL, Column, @Value))

If I remove this part, the rest of the code is running without any error, but that's the part where the value is read out...

Anyone an idea how to fix that?

ASerge

  • Hero Member
  • *****
  • Posts: 1422
Re: Read Windows Storage FileProperties
« Reply #5 on: September 30, 2018, 06:56:04 pm »
Anyone an idea how to fix that?
Show your code. My works without errors.

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #6 on: September 30, 2018, 08:48:53 pm »
I just took the code you have linked:

Code: Pascal  [Select]
  1.  
  2. {$APPTYPE CONSOLE}
  3. program project1;
  4.  
  5. uses Windows, SysUtils, ActiveX, ShlObj;
  6.  
  7. function GetShellFolder2(const Dir: UnicodeString; out Intf: IShellFolder2): Boolean;
  8. var
  9.   DesktopFolder: IShellFolder;
  10.   DirPIDL: PItemIDList = nil;
  11. begin
  12.   Result := Succeeded(SHGetDesktopFolder(DesktopFolder)) and
  13.             Succeeded(DesktopFolder.ParseDisplayName(0, nil, PWideChar(Dir), ULONG(nil^), DirPIDL, ULONG(nil^))) and
  14.             Succeeded(DesktopFolder.BindToObject(DirPIDL, nil, IID_IShellFolder2, Intf));
  15.   CoTaskMemFree(DirPIDL);
  16. end;
  17.  
  18. function GetFileProperty(const FileName: string; const Column: SHColumnID; out Value: OleVariant): Boolean;
  19. var
  20.   DirFolder: IShellFolder2;
  21.   FilePIDL: PItemIDList = nil;
  22.   WFileName: UnicodeString;
  23.   Path: PChar;
  24. begin
  25.   WFileName := Utf8Decode(FileName);
  26.   Result := False;
  27.   if GetShellFolder2(ExtractFileDir(WFileName), DirFolder) then
  28.   begin
  29.     Result := Succeeded(DirFolder.ParseDisplayName(0, nil, PWideChar(ExtractFileName(WFileName)), ULONG(nil^), FilePIDL, ULONG(nil^))) and
  30.               Succeeded(DirFolder.GetDetailsEx(FilePIDL, Column, @Value));  // FAILS HERE
  31.  
  32.     CoTaskMemFree(FilePIDL);
  33.   end;
  34. end;
  35.  
  36. const
  37.   CFileName =  'E:\file.jpg';
  38.   // All available values see in propkey.h in SDK
  39.   CColumnPhotoDateTaken: SHColumnID = ( // PKEY_Photo_DateTaken
  40.     fmtid: '{14B81DA1-0135-4D31-96D9-6CBFC9671A99}'; // FMTID_ImageProperties
  41.     pid: 36867);
  42. var
  43.   Value: OleVariant;
  44.  
  45. begin
  46.   CoInitialize(nil);
  47.  
  48.   WriteLn('Start...');
  49.   if GetFileProperty(CFileName, CColumnPhotoDateTaken, Value) then begin
  50.     Writeln('Photo Date Taken at UTC: ', Value);
  51.   end else begin
  52.     WriteLn('Error');
  53.   end;
  54.   Readln;
  55. end.
  56.  
  57.  
  58.  

Perhaps, the FilePIDL contains a bad reference, so that it comes to the SIGSEGV when passing it to DirFolder.GetDetailsEx.

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #7 on: September 30, 2018, 09:07:27 pm »
Okay, it becomes stranger!

I have just tested the program and code from my last post in some virtual machines.

And I found:

There is no error and the attribut is read out correctly, when the application is compiled for Win64 and run on a 64bit system.

However, the error occurs when the application is compiled for Win32 and run on a 64bit system.

So, my new question is: How to make the code working as a Win32 application?
« Last Edit: September 30, 2018, 09:12:10 pm by LazProgger »

ASerge

  • Hero Member
  • *****
  • Posts: 1422
Re: Read Windows Storage FileProperties
« Reply #8 on: October 01, 2018, 06:38:31 pm »
However, the error occurs when the application is compiled for Win32 and run on a 64bit system.
Error found. Definition of shlobj.PSHColumnID is incorrect. It must be a pointer, but it is defined equal to the SHColumnID structure.
Due to the fact that the 64-bit stdcall convention implies that complex structures are passed through the pointer, this did not cause problems for the 64-bit program, but 32-bit honestly pushes the entire record onto the stack.

It is easy to make the example work if you copy the definition of the IShellFolder2 interface from shlobj to the beginning and add before it
Code: Pascal  [Select]
  1. PSHColumnID = ^SHColumnID;
and, of course, change Column to @Column in GetDetailsEx call.

Thaddy

  • Hero Member
  • *****
  • Posts: 9285
Re: Read Windows Storage FileProperties
« Reply #9 on: October 01, 2018, 06:48:32 pm »
Due to the fact that the 64-bit stdcall convention implies
Uhmmm win64 does not have stdcall as convention...
https://msdn.microsoft.com/en-us/library/ms235286.aspx

Maybe you are mistaken that Delphi accepts it (but ignores it) as does FPC.
also related to equus asinus.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 680
    • Lebeau Software
Re: Read Windows Storage FileProperties
« Reply #10 on: October 01, 2018, 11:04:30 pm »
It is returning -2147287038.

That is STG_E_FILENOTFOUND, which means the specified FMTID doesn't exist in the storage object you are querying.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #11 on: October 02, 2018, 03:08:20 am »
It is easy to make the example work if you copy the definition of the IShellFolder2 interface from shlobj to the beginning and add before it
Code: Pascal  [Select]
  1. PSHColumnID = ^SHColumnID;
and, of course, change Column to @Column in GetDetailsEx call.

Thank you very much! It's working now. Here is the full test code:

Code: Pascal  [Select]
  1.  
  2. {$APPTYPE CONSOLE}
  3. program project1;
  4.  
  5. uses Windows, SysUtils, ActiveX, ShlObj;
  6.  
  7. type
  8.   PSHColumnID = ^SHColumnID;    
  9.  
  10. function GetShellFolder2(const Dir: UnicodeString; out Intf: IShellFolder2): Boolean;
  11. var
  12.   DesktopFolder: IShellFolder;
  13.   DirPIDL: PItemIDList = nil;
  14. begin
  15.   Result := Succeeded(SHGetDesktopFolder(DesktopFolder)) and
  16.             Succeeded(DesktopFolder.ParseDisplayName(0, nil, PWideChar(Dir), ULONG(nil^), DirPIDL, ULONG(nil^))) and
  17.             Succeeded(DesktopFolder.BindToObject(DirPIDL, nil, IID_IShellFolder2, Intf));
  18.   CoTaskMemFree(DirPIDL);
  19. end;
  20.  
  21. function GetFileProperty(const FileName: string; const Column: SHColumnID; out Value: OleVariant): Boolean;
  22. var
  23.   DirFolder: IShellFolder2;
  24.   FilePIDL: PItemIDList = nil;
  25.   WFileName: UnicodeString;
  26.   Path: PChar;
  27. begin
  28.   WFileName := Utf8Decode(FileName);
  29.   Result := False;
  30.   if GetShellFolder2(ExtractFileDir(WFileName), DirFolder) then
  31.   begin
  32.     Result := Succeeded(DirFolder.ParseDisplayName(0, nil, PWideChar(ExtractFileName(WFileName)), ULONG(nil^), FilePIDL, ULONG(nil^))) and
  33.               Succeeded(DirFolder.GetDetailsEx(FilePIDL, Column, @Value));  // FAILS HERE
  34.  
  35.     CoTaskMemFree(FilePIDL);
  36.   end;
  37. end;
  38.  
  39. const
  40.   CFileName =  'E:\file.jpg';
  41.   // All available values see in propkey.h in SDK
  42.   CColumnPhotoDateTaken: SHColumnID = ( // PKEY_Photo_DateTaken
  43.     fmtid: '{14B81DA1-0135-4D31-96D9-6CBFC9671A99}'; // FMTID_ImageProperties
  44.     pid: 36867);
  45. var
  46.   Value: OleVariant;
  47.  
  48. begin
  49.   CoInitialize(nil);
  50.  
  51.   WriteLn('Start...');
  52.   if GetFileProperty(CFileName, CColumnPhotoDateTaken, Value) then begin
  53.     Writeln('Photo Date Taken at UTC: ', Value);
  54.   end else begin
  55.     WriteLn('Error');
  56.   end;
  57.   Readln;
  58. end.
  59.  

However, I have not change Column to @Column. When doing so, I get the compiler error:

Code: Diff  [Select]
  1. project1.lpr(34,65) Error: Incompatible type for arg no. 2: Got "Pointer", expected "SHColumnID"

But it's also working without @.

ASerge

  • Hero Member
  • *****
  • Posts: 1422
Re: Read Windows Storage FileProperties
« Reply #12 on: October 02, 2018, 11:24:37 am »
However, I have not change Column to @Column. When doing so, I get the compiler error:
But it's also working without @.
I think you did it again on Win64. You skipped "copy the definition of the IShellFolder2 interface from shlobj to the beginning".

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #13 on: October 02, 2018, 02:49:11 pm »
I think you did it again on Win64. You skipped "copy the definition of the IShellFolder2 interface from shlobj to the beginning".

Oh yes... :/

Here is the working code for everyone having the same problem:

Code: Pascal  [Select]
  1.  
  2. {$APPTYPE CONSOLE}
  3. program project1;
  4.  
  5. uses Windows, SysUtils, ActiveX, ShlObj;
  6.  
  7. type
  8.   PSHColumnID = ^SHColumnID;
  9.  
  10.   IShellFolder2 = interface(IShellFolder)
  11.     ['{93F2F68C-1D1B-11d3-A30E-00C04F79ABD1}']
  12.      function GetDefaultSearchGUID(out guid:TGUID):HResult;StdCall;
  13.      function EnumSearches(out ppenum:IEnumExtraSearch):HResult;StdCall;
  14.      function GetDefaultColumn(dwres:DWORD;psort :pulong; pdisplay:pulong):HResult;StdCall;
  15.      function GetDefaultColumnState(icolumn:UINT;pscflag:PSHCOLSTATEF):HResult;StdCall;
  16.      function GetDetailsEx(pidl:LPCITEMIDLIST;pscid:PSHCOLUMNID; pv : pOLEvariant):HResult;StdCall;
  17.      function GetDetailsOf(pidl:LPCITEMIDLIST;iColumn:UINT;psd:PSHELLDETAILS):HResult;StdCall;
  18.      function MapColumnToSCID(iColumn:UINT;pscid:PSHCOLUMNID):HResult;StdCall;
  19.     end;      
  20.  
  21. function GetShellFolder2(const Dir: UnicodeString; out Intf: IShellFolder2): Boolean;
  22. var
  23.   DesktopFolder: IShellFolder;
  24.   DirPIDL: PItemIDList = nil;
  25. begin
  26.   Result := Succeeded(SHGetDesktopFolder(DesktopFolder)) and
  27.             Succeeded(DesktopFolder.ParseDisplayName(0, nil, PWideChar(Dir), ULONG(nil^), DirPIDL, ULONG(nil^))) and
  28.             Succeeded(DesktopFolder.BindToObject(DirPIDL, nil, IID_IShellFolder2, Intf));
  29.   CoTaskMemFree(DirPIDL);
  30. end;
  31.  
  32. function GetFileProperty(const FileName: string; const Column: SHColumnID; out Value: OleVariant): Boolean;
  33. var
  34.   DirFolder: IShellFolder2;
  35.   FilePIDL: PItemIDList = nil;
  36.   WFileName: UnicodeString;
  37.   Path: PChar;
  38. begin
  39.   WFileName := Utf8Decode(FileName);
  40.   Result := False;
  41.   if GetShellFolder2(ExtractFileDir(WFileName), DirFolder) then
  42.   begin
  43.     Result := Succeeded(DirFolder.ParseDisplayName(0, nil, PWideChar(ExtractFileName(WFileName)), ULONG(nil^), FilePIDL, ULONG(nil^))) and
  44.               Succeeded(DirFolder.GetDetailsEx(FilePIDL, @Column, @Value));  // FAILS HERE
  45.  
  46.     CoTaskMemFree(FilePIDL);
  47.   end;
  48. end;
  49.  
  50. const
  51.   CFileName =  'E:\file.jpg';
  52.   // All available values see in propkey.h in SDK
  53.   CColumnPhotoDateTaken: SHColumnID = ( // PKEY_Photo_DateTaken
  54.     fmtid: '{14B81DA1-0135-4D31-96D9-6CBFC9671A99}'; // FMTID_ImageProperties
  55.     pid: 36867);
  56. var
  57.   Value: OleVariant;
  58.  
  59. begin
  60.   CoInitialize(nil);
  61.  
  62.   WriteLn('Start...');
  63.   if GetFileProperty(CFileName, CColumnPhotoDateTaken, Value) then begin
  64.     Writeln('Photo Date Taken at UTC: ', Value);
  65.   end else begin
  66.     WriteLn('Error');
  67.   end;
  68.   Readln;
  69. end.
  70.  
  71.  
« Last Edit: October 02, 2018, 02:57:00 pm by LazProgger »

LazProgger

  • Jr. Member
  • **
  • Posts: 91
Re: Read Windows Storage FileProperties
« Reply #14 on: October 02, 2018, 03:23:42 pm »
I have published a bug report of the issue:

https://bugs.freepascal.org/view.php?id=34375