0

I am trying to acquire file properties such as Title, Subject, Author, Copyright, Comments and the various file dates. Other properties would be nice, but not required. My searches all seem to lead to variations of the code I found here as well as here. Copying and pasting the code, I had to do a bit of tweaking to make it work at all. I was finally able to get it to partially work, but only on .doc files. Other files produced "EOleSysError with message '%1 could not be found'". I thought this related to compound files, but the same error occurred with .docx and .xls files.

I have tried STGFMT_STORAGE, STGFMT_FILE, STGFMT_ANY, and STGFMT_DOCFILE, but the only one that works is STGFMT_ANY. Again, that only works on .doc files. Even on .doc files, it doesn't return dates, although I have only treated them as string values. Here's my code, and the results.

Output in a TListView

unit u_fSummary;

interface

uses Windows, ComObj, ActiveX, Variants, Sysutils, dialogs;

function GetFileSummaryInfo(const FileName: WideString): String;
function IsNTFS(AFileName : string) : boolean;

implementation

const

FmtID_SummaryInformation: TGUID =     '{F29F85E0-4FF9-1068-AB91-08002B27B3D9}';
FMTID_DocSummaryInformation : TGUID = '{D5CDD502-2E9C-101B-9397-08002B2CF9AE}';
FMTID_UserDefinedProperties : TGUID = '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}';
IID_IPropertySetStorage : TGUID =     '{0000013A-0000-0000-C000-000000000046}';

STGFMT_STORAGE = 0; {Indicates that the file must be a compound file}

STGFMT_FILE =    3; {Indicates that the file must not be a compound file.
                    This element is only valid when using the StgCreateStorageEx
                    or StgOpenStorageEx functions to access the NTFS file system
                    implementation of the IPropertySetStorage interface.
                    Therefore, these functions return an error if the riid
                    parameter does not specify the IPropertySetStorage interface,
                    or if the specified file is not located on an NTFS file system volume.}

STGFMT_ANY =     4; {Indicates that the system will determine the file type and
                    use the appropriate structured storage or property set
                    implementation.
                    This value cannot be used with the StgCreateStorageEx function.}

STGFMT_DOCFILE = 5; {Indicates that the file must be a compound file, and is
                    similar to the STGFMT_STORAGE flag, but indicates that the
                    compound-file form of the compound-file implementation must
                    be used. For more information}


// Summary Information
 PID_TITLE        = 2;
 PID_SUBJECT      = 3;
 PID_AUTHOR       = 4;
 PID_KEYWORDS     = 5;
 PID_COMMENTS     = 6;
 PID_TEMPLATE     = 7;
 PID_LASTAUTHOR   = 8;
 PID_REVNUMBER    = 9;
 PID_EDITTIME     = 10;
 PID_LASTPRINTED  = 11;
 PID_CREATE_DTM   = 12;
 PID_LASTSAVE_DTM = 13;
 PID_PAGECOUNT    = 14;
 PID_WORDCOUNT    = 15;
 PID_CHARCOUNT    = 16;
 PID_THUMBNAIL    = 17;
 PID_APPNAME      = 18;
 PID_SECURITY     = 19;

 // Document Summary Information
 PID_CATEGORY     = 2;
 PID_PRESFORMAT   = 3;
 PID_BYTECOUNT    = 4;
 PID_LINECOUNT    = 5;
 PID_PARCOUNT     = 6;
 PID_SLIDECOUNT   = 7;
 PID_NOTECOUNT    = 8;
 PID_HIDDENCOUNT  = 9;
 PID_MMCLIPCOUNT  = 10;
 PID_SCALE        = 11;
 PID_HEADINGPAIR  = 12;
 PID_DOCPARTS     = 13;
 PID_MANAGER      = 14;
 PID_COMPANY      = 15;
 PID_LINKSDIRTY   = 16;
 PID_CHARCOUNT2   = 17;

function IsNTFS(AFileName : string) : boolean;
var
fso, drv : OleVariant;
begin
  IsNTFS := False;
  fso := CreateOleObject('Scripting.FileSystemObject');
  drv := fso.GetDrive(fso.GetDriveName(AFileName));
  if drv.FileSystem = 'NTFS' then
    IsNTFS := True;
end;

function StgOpenStorageEx (
 const pwcsName : POleStr;  //Pointer to the path of the
                            //file containing storage object
 grfMode : LongInt;         //Specifies the access mode for the object
 stgfmt : DWORD;            //Specifies the storage file format
 grfAttrs : DWORD;          //Reserved; must be zero
 pStgOptions : Pointer;     //Address of STGOPTIONS pointer
 reserved2 : Pointer;       //Reserved; must be zero
 riid : PGUID;              //Specifies the GUID of the interface pointer
 out stgOpen :              //Address of an interface pointer
 IStorage ) : HResult; stdcall; external 'ole32.dll';


function GetFileSummaryInfo(const FileName: WideString): String;
var
  I: Integer;
  PropSetStg: IPropertySetStorage;
  PropSpec: array of TPropSpec;
  PropStg: IPropertyStorage;
  PropVariant: array of TPropVariant;
  Rslt: HResult;
  S: String;
  Stg: IStorage;
  PropEnum: IEnumSTATPROPSTG;
  HR : HResult;
  PropStat: STATPROPSTG;
  k : integer;

      function _PropertyPIDToCaption(const ePID: Cardinal): string;
      begin
        case ePID of
          PID_TITLE:
            Result := 'Title';
          PID_SUBJECT:
            Result := 'Subject';
          PID_AUTHOR:
            Result := 'Author';
          PID_KEYWORDS:
            Result := 'Keywords';
          PID_COMMENTS:
            Result := 'Comments';
          PID_TEMPLATE:
            Result := 'Template';
          PID_LASTAUTHOR:
            Result := 'Last Saved By';
          PID_REVNUMBER:
            Result := 'Revision Number';
          PID_EDITTIME:
            Result := 'Total Editing Time';
          PID_LASTPRINTED:
            Result := 'Last Printed';
          PID_CREATE_DTM:
            Result := 'Create Time/Date';
          PID_LASTSAVE_DTM:
            Result := 'Last Saved Time/Date';
          PID_PAGECOUNT:
            Result := 'Number of Pages';
          PID_WORDCOUNT:
            Result := 'Number of Words';
          PID_CHARCOUNT:
            Result := 'Number of Characters';
          PID_THUMBNAIL:
            Result := 'Thumbnail';
          PID_APPNAME:
            Result := 'Creating Application';
          PID_SECURITY:
            Result := 'Security';
        else
         Result := '$' + IntToHex(ePID, 8);
        end
      end;

begin
 Result := '';
  try

    OleCheck(StgOpenStorageEx(StringToOleStr(FileName), STGM_READ or STGM_SHARE_DENY_WRITE,
      STGFMT_ANY, 0, nil,  nil, @IID_IPropertySetStorage, stg));

    PropSetStg := Stg as IPropertySetStorage;

    OleCheck(PropSetStg.Open(FmtID_SummaryInformation,
      STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg));

    OleCheck(PropStg.Enum(PropEnum));
    i := 0;

    hr := PropEnum.Next(1, PropStat, nil);
    while hr = S_OK do
    begin
      inc(I);
      SetLength(PropSpec,I);
      PropSpec[i-1].ulKind := PRSPEC_PROPID;
      PropSpec[i-1].propid := PropStat.propid;
      hr := PropEnum.Next(1, PropStat, nil);
    end;

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

    if Rslt <>  S_FALSE then;
    begin
    for k := 0 to i -1 do
      begin
        S := '';
        if PropVariant[k].vt = VT_LPSTR then
          if Assigned(PropVariant[k].pszVal) then
           S := PropVariant[k].pszVal;
        S := Trim(Format(_PropertyPIDToCaption(PropSpec[k].Propid)+ ': %s',[s]));
        if S <> '' then Result := Result + S + #176;
      end;
    end;
  finally
  end;
end;

end.
Progman
  • 16,827
  • 6
  • 33
  • 48
  • What does "only in Delphi 2007" mean? Is this just another UTF 16 Unicode delphi question? Did you do any debugging? Like stepping through line by line with the debugger.. – David Heffernan Dec 21 '21 at 19:58
  • I'm not sure where you got "only in Delphi 2007", but this is part of a very large project that is written in D2007, and upgrading to a newer environment is not possible. Regarding stepping through line by line, of course I did. But that would only tell where the problem occurs and not why. Regarding it being a Unicode question, I have ruled that out because if it were a unicode issue, why would it find .doc files and not others. Perhaps my question should be: can anyone see why I this code can only find .doc files when the MS documentation implies it should work on any compound file. – Homer Jones Dec 21 '21 at 20:10
  • @David Heffernan your comment about Unicode made me wonder. The break occurs here: ` OleCheck(PropSetStg.Open(FmtID_SummaryInformation, STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg)); ` So I'm wondering if I need some sort of type casting since D2007 doesn't do Unicode. If that could be the case, what do you suggest? – Homer Jones Dec 21 '21 at 20:46
  • `STGFMT_ANY` doesn't work, because it can't be used with `StgCreateStorageEx` (which is stated in the comment in your own code). .Docx files are compressed, and therefore your only option would be `STGFMT_DOCFILE` (again, according to the comments in your own code). – Ken White Dec 22 '21 at 01:57
  • Cannot reproduce - works for my XLS files. Make sure `StringToOleStr(FileName)` is not your culprit (i.e. have a file `D:\my.xls`). Also "_doesn't return dates_" is obvious when you only handle `VT_LPSTR` but should also expect `VT_FILETIME`. [Most likely code source](https://www.delphipraxis.net/20244-erweiterte-dateiinformationen-schreiben.html). – AmigoJack Dec 22 '21 at 02:06
  • @KenWhite It's odd that STGFMT_ANY was the first value that worked once I finally got it to run at all. It does conflict with the documentation. STGFMT_DOCFILE produced "%1 already exists" when trying to open a .DOCX file, and "%1 could not be found" when trying to open a .XLS file. I wonder if my using D2007 has anything to do with this odd behavior. – Homer Jones Dec 22 '21 at 05:15
  • The WinAPI doesn't change based on the language you're using to access it, so it's most likely not an issue with Delphi 20007 (unless the issue is Unicode-related). .docx and .xlsx files are compressed (they're actually zip files containing XML content), so the `STGFMT_DOCFILE` is the only option available. If it's not working, there's another issue that isn't clear from your posted code. – Ken White Dec 22 '21 at 05:22
  • @AmigoJack I'll look into StringToOleStr to see if there is another alternative. I made the changes suggested by Ken White, but still get "%1 could not be found". Also, I added VT_FILETIME to the IF statement -- still no dates. Then I added VT_DATE, and still no dates. Then I went back to the original IF statement but replaced VT_LPSTR with the other two (one at a time) -- still no dates. I wonder if I need to do something like DateTimeToStr. I'll try that even though the MS documentation doesn't indicate it would be necessary. Thanks for catching the variant type issue. – Homer Jones Dec 22 '21 at 05:28
  • @KenWhite - I agree, my concern about D2007 is its lack of Unicode support. You are right about the code. It's a challenge to wrap my brain around someone else's code when there is no documentation (other than what was copied out of the MS pages on STGFMT_FILE etc. I very much appreciate your help. – Homer Jones Dec 22 '21 at 05:35
  • D2007 was before Unicode became prevalent. You can't blame it for not supporting something that wasn't used at the time it was produced. The `2007` is relevant - you're using a development tool that is nearly a decade and a half old, and expecting it to work perfectly on an OS that has evolved considerably during that time. If you can't upgrade to a newer version of your tools, you're bound to have issues to overcome. – Ken White Dec 22 '21 at 05:45
  • @KenWhite -- Lighten up, Ken. Do you think I don't know how old D2007 is? Do you think I'm not aware of the advancements that are in the later products? Come on. I'm not expecting D2007 to do anything. I'm just trying to figure out if I can use that old tool to do something that seems like it would be a nice feature to add to my application. By the way, the app is 25 years old and is well over 2 million lines of code. Forgetting about the 3rd party components, I'm too old and tired to even think about the complete rewrite it would take to upgrade. – Homer Jones Dec 22 '21 at 06:51
  • Well, D2007 was actually released well after Unicode became prevalent on the Windows platform. – David Heffernan Dec 22 '21 at 08:22
  • David is correct. Unicode certainly predates D2007, but it wasn't until D2009 that Borland (CodeGear) really supported it. Unfortunately, I'm stuck with D2007, and that makes Unicode harder. Since solutions to my problem are not jumping off the page, I may be looking at GetDetailsOf or GetDetailsEx. This is a document management module of a larger office automation product. All I would like to display are things like Author, creation date, last modified, and a few high level properties. This procedure may not be my best choice. – Homer Jones Dec 22 '21 at 16:27
  • You current approach would be fine for files that use structured storage. However, you appear to want to handle files that don't use structured storage. And sorry for misreading the question title originally. – David Heffernan Dec 23 '21 at 08:37
  • @DavidHeffernan -- Thanks, David. This all started with one of my many brainstorms that turn out to be a light breeze. My document manager program has been working very well for several years. Every now and then I get the impulse to give my customers a little something extra, like displaying the author, document dates, and copyright. None of them have ever requested that feature so I really don't know why I've been spinning my wheels on something that is a low priority. I really do appreciate your input -- and no need to apologize. Just keep up the great help you've been offering for years. – Homer Jones Dec 23 '21 at 17:18

0 Answers0