HOWTO: Read Compound Document Properties Directly with VC++
 
 
The information in this article applies to:
- Microsoft Visual C++, 32-bit Editions, version 5.0
- Microsoft Office 97 for Windows
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>
      // 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>=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->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) {
            char *pName = strrchr(argv[0], '.');
            printf("- OLE Document Property Viewer\n");
            printf("- Usage: %s filename", pName+1);
            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:
- Windows NT 4.0 or later
  - or -
- Windows 95 with Internet Explorer version 4.0 or later
  - or -
- Windows 95 with DCOM installed
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 kbOffice 
Version           : WINDOWS:97; WINNT:5.0
Platform          : WINDOWS winnt
Issue type        : kbhowto