HOWTO: Read Compound Document Properties Directly with VC++

ID: Q186898


The information in this article applies to:


SUMMARY

You can retrieve compound document properties from a document using standard interfaces without the server running or even being installed. For instance, you can retrieve built-in document properties such as Author, Last Modified Time, and Page Count properties of a Microsoft Office 97 document as well as other custom document properties.


MORE INFORMATION

The following steps illustrate how you can build a compound document property viewer with Microsoft Visual C++. The example is a Win32 Console Application project, and can be modified to suit your needs.

Steps to Create Sample

  1. Create a new Win32 Console Application project, and call it PropDump.


  2. Add a new file called main.cpp to your project.


  3. Copy the following code into main.cpp:
    
       #include <stdio.h>
       #include <windows.h>
       #include <ole2.h>
    
    
       // Dumps simple PROPVARIANT values.
       void DumpPropVariant(PROPVARIANT *pPropVar) {
          // Don't iterate arrays, just inform as an array.
          if(pPropVar->vt & VT_ARRAY) {
             printf("(Array)\n");
             return;
          }
          // Don't handle byref for simplicity, just inform byref.
          if(pPropVar->vt & VT_BYREF) {
             printf("(ByRef)\n");
             return;
           }
    
          // Switch types.
          switch(pPropVar->vt) {
          case VT_EMPTY:
             printf("(VT_EMPTY)\n");
             break;
          case VT_NULL:
             printf("(VT_NULL)\n");
             break;
          case VT_BLOB:
             printf("(VT_BLOB)\n");
             break;
          case VT_BOOL:
             printf("%s (VT_BOOL)\n",
             pPropVar->boolVal ? "TRUE/YES" : "FALSE/NO");
             break;
          case VT_I2: // 2-byte signed int.
             printf("%d (VT_I2)\n", (int)pPropVar->iVal);
             break;
          case VT_I4: // 4-byte signed int.
             printf("%d (VT_I4)\n", (int)pPropVar->lVal);
             break;
          case VT_R4: // 4-byte real.
             printf("%.2lf (VT_R4)\n", (double)pPropVar->fltVal);
             break;
          case VT_R8: // 8-byte real.
             printf("%.2lf (VT_R8)\n", (double)pPropVar->dblVal);
             break;
             case VT_BSTR: // OLE Automation string.
             {
                // Translate into ASCII.
                char dbcs[1024];
                char *pbstr = (char *)pPropVar->bstrVal;
                int i = wcstombs(
                dbcs, pPropVar->bstrVal, *((DWORD *)(pbstr-4)));
                dbcs[i] = 0;
                printf("%s (VT_BSTR)\n", dbcs);
             }
             break;
          case VT_LPSTR: // Null-terminated string.
             {
             printf("%s (VT_LPSTR)\n", pPropVar->pszVal);
             }
             break;
          case VT_FILETIME:
             {
                char *dayPre[] =
                             {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
    
                FILETIME lft;
                FileTimeToLocalFileTime(&pPropVar->filetime, &lft);                SYSTEMTIME lst;
                FileTimeToSystemTime(&lft, &lst);
    
                printf("%02d:%02d.%02d %s, %s %02d/%02d/%d (VT_FILETIME)\n",
                   1+(lst.wHour-1)%12, lst.wMinute, lst.wSecond,
                   (lst.wHour&gt;=12) ? "pm" : "am",
                   dayPre[lst.wDayOfWeek%7],
                   lst.wMonth, lst.wDay, lst.wYear);
             }
             break;
          case VT_CF: // Clipboard format.
             printf("(Clipboard format)\n");
    
             break;
          default: // Unhandled type, consult wtypes.h's VARENUM structure.
             printf("(Unhandled type: 0x%08lx)\n", pPropVar-&gt;vt);
             break;
          }
       }
    
       // Dump's built-in properties of a property storage.
       void DumpBuiltInProps(IPropertySetStorage *pPropSetStg) {
          printf("\n==================================================\n");
          printf("BuiltInProperties Properties...\n");
          printf("==================================================\n");
    
          IPropertyStorage *pPropStg = NULL;
          HRESULT hr;
    
          // Open summary information, getting an IpropertyStorage.
          hr = pPropSetStg->Open(FMTID_SummaryInformation,
          STGM_READ | STGM_SHARE_EXCLUSIVE, &pPropStg);
          //hr = pPropSetStg->Open(FMTID_UserDefinedProperties,
             //STGM_READ | STGM_SHARE_EXCLUSIVE, &pPropStg);
          if(FAILED(hr)) {
             printf("No Summary-Information.\n");
             return;
          }
          // Array of PIDSI's you are interested in.
          struct pidsiStruct {
             char *name;
             long pidsi;
          } pidsiArr[] = {
             {"Title",            PIDSI_TITLE}, // VT_LPSTR
             {"Subject",          PIDSI_SUBJECT}, // ...
             {"Author",           PIDSI_AUTHOR},
             {"Keywords",         PIDSI_KEYWORDS},
             {"Comments",         PIDSI_COMMENTS},
             {"Template",         PIDSI_TEMPLATE},
             {"LastAuthor",       PIDSI_LASTAUTHOR},
             {"Revision Number",  PIDSI_REVNUMBER},
             {"Edit Time",        PIDSI_EDITTIME}, // VT_FILENAME (UTC)
             {"Last printed",     PIDSI_LASTPRINTED}, // ...
             {"Created",          PIDSI_CREATE_DTM},
             {"Last Saved",       PIDSI_LASTSAVE_DTM},
             {"Page Count",       PIDSI_PAGECOUNT}, // VT_I4
             {"Word Count",       PIDSI_WORDCOUNT}, // ...
             {"Char Count",       PIDSI_CHARCOUNT},
             {"Thumpnail",        PIDSI_THUMBNAIL}, // VT_CF
             {"AppName",          PIDSI_APPNAME}, // VT_LPSTR
             {"Doc Security",     PIDSI_DOC_SECURITY}, // VT_I4
             {0, 0}
          };
          // Count elements in pidsiArr.
          int nPidsi = 0;
          for(nPidsi=0; pidsiArr[nPidsi].name; nPidsi++);
    
          // Initialize PROPSPEC for the properties you want.
          PROPSPEC *pPropSpec = new PROPSPEC [nPidsi];
          PROPVARIANT *pPropVar = new PROPVARIANT [nPidsi];
    
          for(int i=0; i<nPidsi; i++) {
             ZeroMemory(&pPropSpec[i], sizeof(PROPSPEC));
             pPropSpec[i].ulKind = PRSPEC_PROPID;
             pPropSpec[i].propid = pidsiArr[i].pidsi;
          }
    
          // Read properties.
          hr = pPropStg->ReadMultiple(nPidsi, pPropSpec, pPropVar);
    
          if(FAILED(hr)) {
             printf("IPropertyStg::ReadMultiple() failed w/error %08lx\n",
                    hr);
          }
          else {
             // Dump properties.
             for(i=0; i<nPidsi; i++) {
                printf("%16s: ", pidsiArr[i].name);
                DumpPropVariant(pPropVar + i);
             }
          }
    
          // De-allocate memory.
          delete [] pPropVar;
          delete [] pPropSpec;
    
          // Release obtained interface.
          pPropStg->Release();
    
       }
    
       // Dump's custom properties of a property storage.
       void DumpCustomProps(IPropertySetStorage *pPropSetStg) {
          printf("\n==================================================\n");
          printf("Custom Properties...\n");
          printf("==================================================\n");
    
          IPropertyStorage *pPropStg = NULL;
          HRESULT hr;
          IEnumSTATPROPSTG *pEnumProp;
    
          // Open User-Defined-Properties, getting an IpropertyStorage.
          hr = pPropSetStg->Open(FMTID_UserDefinedProperties,
             STGM_READ | STGM_SHARE_EXCLUSIVE, &pPropStg);
          if(FAILED(hr)) {
             printf("No User Defined Properties.\n");
             return;
          }
    
          // Get property enumerator.
          hr = pPropStg->Enum(&pEnumProp);
          if(FAILED(hr)) {
          pPropStg->Release();
             printf("Couldn't enumerate custom properties.\n");
             return;
          }
    
          // Enumerate properties.
          STATPROPSTG sps;
          ULONG fetched;
          PROPSPEC propSpec[1];
          PROPVARIANT propVar[1];
          while(pEnumProp->Next(1, &sps, &fetched) == S_OK) {
             // Build a PROPSPEC for this property.
             ZeroMemory(&propSpec[0], sizeof(PROPSPEC));
             propSpec[0].ulKind = PRSPEC_PROPID;
             propSpec[0].propid = sps.propid;
    
             // Read this property.
             hr = pPropStg->ReadMultiple(1, &propSpec[0], &propVar[0]);
             if(!FAILED(hr)) {
                // Translate Prop name into ASCII.
                char dbcs[1024];
                char *pbstr = (char *)sps.lpwstrName;
                int i = wcstombs(dbcs, sps.lpwstrName,
                                 *((DWORD *)(pbstr-4)));
                dbcs[i] = 0;
    
                // Dump this property.
                printf("%16s: ", dbcs);
                DumpPropVariant(&propVar[0]);
             }
          }
    
          // Release obtained interface.
          pEnumProp->Release();
          pPropStg->Release();
    
       }
    
       // Dump's custom and built-in properties of a compound document.
       void DumpProps(char *filename) {
          // Translate filename to Unicode.
          WCHAR wcFilename[1024];
          int i = mbstowcs(wcFilename, filename, strlen(filename));
          wcFilename[i] = 0;
    
          IStorage *pStorage = NULL;
          IPropertySetStorage *pPropSetStg = NULL;
          HRESULT hr;
    
          // Open the document as an OLE compound document.
          hr = ::StgOpenStorage(wcFilename, NULL,
          STGM_READ | STGM_SHARE_EXCLUSIVE, NULL, 0, &pStorage);
    
          if(FAILED(hr)) {
             if(hr == STG_E_FILENOTFOUND)
                printf("File not found.");
             else if(hr == STG_E_FILEALREADYEXISTS)
                printf("Not a compound file.");
             else
                printf("StgOpenStorage() failed w/error %08lx", hr);
             return;
          }
    
          // Obtain the IPropertySetStorage interface.
          hr = pStorage->QueryInterface(
                  IID_IPropertySetStorage, (void **)&pPropSetStg);
          if(FAILED(hr)) {
             printf("QI for IPropertySetStorage failed w/error %08lx", hr);
             pStorage->Release();
             return;
          }
    
          // Dump properties.
          DumpBuiltInProps(pPropSetStg);
          DumpCustomProps(pPropSetStg);
    
          // Release obtained interfaces.
          pPropSetStg->Release();
          pStorage->Release();
       }
    
          // Program entry-point.
          void main(int argc, char **argv) {
             // Validate arguments.
             if(argc != 2) {
                printf("- OLE Document Property Viewer\n");
                printf("- Usage: %s filename", argv[0]);
                return;
             }
    
             // Pass filename to the subroutine.
             DumpProps(argv[1]);
          }
       } 


  4. Compile the program.


To run the example, you should copy the PropDump.exe file to a directory in your default path; for instance c:\Windows\ or c:\Windows\Command\. Then, in a directory containing a compound document file, type PropDump followed by the name of the file. You should see output similar to the following:

==================================================

BuiltInProperties Properties...


           Title: MyTitle (VT_LPSTR)
         Subject: MySubject (VT_LPSTR)
          Author: MyAuthor (VT_LPSTR)
        Keywords: MyKeywords (VT_LPSTR)
        Comments: MyComments (VT_LPSTR)
        Template: Normal (VT_LPSTR)
      LastAuthor: Me (VT_LPSTR) 
Revision Number: 8 (VT_LPSTR)

       Edit Time: 01:05.47 pm, Mon 01/01/1601 (VT_FILETIME)
    Last printed: (VT_EMPTY)
         Created: 01:42.00 pm, Fri 05/29/1998 (VT_FILETIME)
      Last Saved: 12:31.00 pm, Mon 06/01/1998 (VT_FILETIME)
      Page Count: 1 (VT_I4)
      Word Count: 3 (VT_I4)
      Char Count: 19 (VT_I4)
       Thumpnail: (VT_EMPTY)
         AppName: Microsoft Word 8.0 (VT_LPSTR)
    Doc Security: 0 (VT_I4) 


==================================================

Custom Properties...


   _PID_LINKBASE: (VT_BLOB)

       _PID_GUID: (VT_BLOB)
       CustProp1: CustProp1TextValue (VT_LPSTR)
       CustProp2: 77777 (VT_I4)
       CustProp3: TRUE/YES (VT_BOOL)
       CustProp4: 00:00.00 am, Tue 05/17/1977 (VT_FILETIME) 

Additional Notes

The IPropertyStorage and IPropertySetStorage interfaces were not defined in the original release of COM; thus this sample code requires a system with: Previous versions of COM specified very little with respect to properties and their usage, but did define a serialized format that allowed developers to store properties and property sets in an IStorage instance. The property identifiers and semantics of a single property set, used for summary information about a document, were also defined. At that time, it was necessary to create and manipulate that structure directly as a data stream. For more information on the property set serialized data format structure, refer to "OLE Serialized Property Set Format" in the Microsoft Developer Network.


REFERENCES

Microsoft Developer Network: Persistent Property Sets

Microsoft Developer Network: OLE Serialized Property Set Format

(c) Microsoft Corporation 1998, All Rights Reserved. Contributions by Joe Crump, Microsoft Corporation.

Additional query words: Excel Word Access Powerpoint Binder documentproperties builtindocumentproperties customdocumentproperties


Keywords          : kbcode kbCmpDoc kbCOMt kbVC500 kbDSupport kbOffice 
Version           : WINDOWS:97; winnt:5.0
Platform          : WINDOWS winnt 
Issue type        : kbhowto 

Last Reviewed: July 21, 1999