//+--------------------------------------------------------------
//
//  For conditions of distribution and use, see copyright notice
//  in Flashpix.h 
// 
//  Copyright (c) 1999 Digital Imaging Group, Inc.
// 
//  Description:  This is the main file for all the tests
//                of Property set interfaces.
//
//---------------------------------------------------------------

//
//                             IPropertyStorage tests
//

// NOTE: we require more headers here just for convenience of 
// the test program, for other programs only "props.h" should be
// needed.

#ifndef _UNIX
#define INITGUID
#endif // _UNIX

#include "../../props/h/windef.h"
#include "../../h/props.h"

#include "../../props/h/propapi.h"
#include "../../props/h/propset.hxx"

#include "cpropvar.hxx"
#include "../../props/h/propmac.hxx"
#include "../../time.hxx"
#include "proptest.hxx"


#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>  // _mkdir is stored here in win32
#endif

//  -------
//  Globals
//  -------
#ifndef _UNIX
// again for static linkage, these symbols will be duplicated.

OLECHAR aocMap[33], oszSummary[19], oszDocumentSummary[27];

GUID guidSummary =
    { 0xf29f85e0,
      0x4ff9, 0x1068,
      { 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9 } };

GUID guidDocumentSummary =
    { 0xd5cdd502,
      0x2e9c, 0x101b,
      { 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae } };

GUID guidDocumentSummary2 =
    { 0xd5cdd505,
      0x2e9c, 0x101b,
      { 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae } };
#else
extern LPOLESTR aocMap, oszSummary, oszDocumentSummary;
extern GUID guidSummary, guidDocumentSummary, guidDocumentSummary2;
#endif // _UNIX

LARGE_INTEGER g_li0;

CPropVariant  g_rgcpropvarAll[ CPROPERTIES_ALL ];
CPropSpec     g_rgcpropspecAll[ CPROPERTIES_ALL ];
char g_szPropHeader[] = "  propid/name          propid    cb   type value\n";
char g_szEmpty[] = "";
BOOL g_fVerbose = FALSE;

/* just a uuid that we use to create new uuids */
GUID g_curUuid = 
{ /* e4ecf7f0-e587-11cf-b10d-00aa005749e9 */
    0xe4ecf7f0,
    0xe587,
    0x11cf,
    {0xb1, 0x0d, 0x00, 0xaa, 0x00, 0x57, 0x49, 0xe9}
};

#define TEST_STANDARD           0x1
#define TEST_WORD6              0x2
#define TEST_INTEROP_W          0x4
#define TEST_INTEROP_R          0x8

__inline OLECHAR
MapChar(IN ULONG i)
{
    return((OLECHAR) aocMap[i & CHARMASK]);
}

//
// We simulate uuidcreate by incrementing a global uuid
// each time the function is called.
//
void UuidCreate (
    OUT GUID * pUuid
    )
{
    g_curUuid.Data1++;          
    *pUuid = g_curUuid;         // member to member copy
}

#ifndef _UNIX

//+--------------------------------------------------------------------------
// Function:    RtlGuidToPropertySetName
//
// Synopsis:    Map property set GUID to null-terminated OLECODE name string.
//
//              The aocname parameter is assumed to be a buffer with room for
//              CCH_PROPSETSZ (28) OLECHARs.  The first character
//              is always OC_PROPSET0 (0x05), as specified by the OLE Appendix
//              B documentation.  The colon character normally used as an NT
//              stream name separator is not written to the caller's buffer.
//
//              No error is possible.
// Arguments:   IN GUID *pguid        -- pointer to GUID to convert
//              OUT OLECHAR aocname[] -- output string buffer
//
// Returns:     count of non-NULL characters in the output string buffer
//---------------------------------------------------------------------------

ULONG PROPSYSAPI PROPAPI
RtlGuidToPropertySetName(
    IN GUID const *pguid,
    OUT OLECHAR aocname[])
{
    BYTE *pb = (BYTE *) pguid;
    BYTE *pbEnd = pb + sizeof(*pguid);
    ULONG cbitRemain = CBIT_BYTE;
    OLECHAR *poc = aocname;

    *poc++ = OC_PROPSET0;

    // Note: CCH_PROPSET includes the OC_PROPSET0, and sizeof(osz...)
    // includes the trailing L'\0', so sizeof(osz...) is ok because the
    // OC_PROPSET0 character compensates for the trailing NULL character.

    ASSERT(CCH_PROPSET >= sizeof(oszSummary)/sizeof(OLECHAR));
    if (*pguid == guidSummary)
    {
        RtlCopyMemory(poc, oszSummary, sizeof(oszSummary));
        return(sizeof(oszSummary)/sizeof(OLECHAR));
    }

    ASSERT(CCH_PROPSET >= sizeof(oszDocumentSummary)/sizeof(OLECHAR));
    if (*pguid == guidDocumentSummary || *pguid == guidDocumentSummary2)
    {
        RtlCopyMemory(poc, oszDocumentSummary, sizeof(oszDocumentSummary));
        return(sizeof(oszDocumentSummary)/sizeof(OLECHAR));
    }

    while (pb < pbEnd)
    {
        ULONG i = *pb >> (CBIT_BYTE - cbitRemain);

        if (cbitRemain >= CBIT_CHARMASK)
        {
            *poc = MapChar(i);
            if (cbitRemain == CBIT_BYTE && 
                *poc >= ((OLECHAR)'a') && 
                *poc <= ((OLECHAR)'z') )
            {
                *poc += (OLECHAR) ((OLECHAR)('A') - (OLECHAR)('a'));
            }
            poc++;

            cbitRemain -= CBIT_CHARMASK;
            if (cbitRemain == 0)
            {
                pb++;
                cbitRemain = CBIT_BYTE;
            }
        }
        else
        {
            if (++pb < pbEnd)
            {
                i |= *pb << cbitRemain;
            }
            *poc++ = MapChar(i);
            cbitRemain += CBIT_BYTE - CBIT_CHARMASK;
        }
    }
    *poc = (OLECHAR)'\0';
    return(CCH_PROPSET);
}
#endif // #ifndef _UNIX

#define Check(x,y) _Check(x,y, __LINE__)


void Cleanup(); // forward declaration

//+=================================================================
//
//  Function:   _Check
//  
//  Synopsis:   Verify that the actual HR is the expected
//              value.  If not, report an error and exit.
//
//  Inputs:     [HRESULT] hrExpected
//                  What we expected
//              [HRESULT] hrActual
//                  The actual HR of the previous operation.
//              [int] line
//                  The line number of the operation.
//  
//  Outputs:    None.
//  
//+=================================================================


void _Check(HRESULT hrExpected, HRESULT hrActual, int line)
{
    if (hrExpected != hrActual)
    {
        printf("\nFailed with hr=%08x at line %d (expected hr=%08x in proptest.cxx)\n",
               hrActual, line, hrExpected );
        Cleanup();
    }
}

OLECHAR * GetNextTest()
{
    static int nTest;
    static CHAR pchBuf[10];
    static OLECHAR pocs[10];

    sprintf(pchBuf, "%d", nTest++);
    STOT(pchBuf, pocs, strlen(pchBuf)+1);

    return(pocs);
}

void Now(FILETIME *pftNow)
{
#ifdef _WINDOWS
    DfGetTOD(pftNow);
#else
#	include "time.hxx"
	struct timeval	now;

	gettimeofday(&now, NULL);

	TimeTToFileTime(&now.tv_sec, pftNow);
#endif
}

enum CreateOpen
{
    Create, Open
};

IStorage *_pstgTemp=NULL;
IStorage *_pstgTempCopyTo=NULL;  // _pstgTemp is copied to _pstgTempCopyTo

class CTempStorage
{
public:
    CTempStorage(DWORD grfMode = STGM_DIRECT | STGM_CREATE)
    {
        Check(S_OK, (_pstgTemp->CreateStorage(GetNextTest(), grfMode | 
            STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0,
            &_pstg)));
    }


    CTempStorage(CreateOpen co, IStorage *pstgParent, 
                 OLECHAR *pocsChild, DWORD grfMode = STGM_DIRECT)
    {
        if (co == Create)
            Check(S_OK, pstgParent->CreateStorage(pocsChild,
                grfMode | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0,
                &_pstg));
        else
            Check(S_OK, pstgParent->OpenStorage(pocsChild, NULL,
                grfMode | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, NULL, 0,
                &_pstg));
    }

    ~CTempStorage()
    {
        if (_pstg != NULL)
            _pstg->Release();
    }


    IStorage * operator -> ()
    {
        return(_pstg);
    }

    operator IStorage * ()
    {
        return(_pstg);
    }

    void Release()
    {
        if (_pstg != NULL)
        {
            _pstg->Release();
            _pstg = NULL;
        }
    }

private:
    static unsigned int _iName;

    IStorage *  _pstg;
};

unsigned int CTempStorage::_iName;

class CGenProps
{
public:
    CGenProps() : _vt((VARENUM)2) {}
    PROPVARIANT * GetNext(int HowMany, int *pActual, BOOL fWrapOk = FALSE, BOOL fNoNonSimple = TRUE);
    
private:
    BOOL        _GetNext(PROPVARIANT *pVar, BOOL fWrapOk, BOOL fNoNonSimple);
    VARENUM     _vt;

};


PROPVARIANT * CGenProps::GetNext(int HowMany, int *pActual, BOOL fWrapOk, BOOL fNoNonSimple)
{
    PROPVARIANT *pVar = new PROPVARIANT[HowMany];

    if (pVar == NULL)
        return(NULL);
    int l;  
    for (l=0; l<HowMany && _GetNext(pVar + l, fWrapOk, fNoNonSimple); l++) { };

    if (pActual)
        *pActual = l;

    if (l == 0)
    {
        delete pVar;
        return(NULL);
    }

    return(pVar);
}

BOOL CGenProps::_GetNext(PROPVARIANT *pVar, BOOL fWrapOk, BOOL fNoNonSimple)
{
    if (_vt == (VT_VECTOR | VT_CLSID)+1)
    {
        if (!fWrapOk)
            return(FALSE);
        else
            _vt = (VARENUM)2;
    }

    PROPVARIANT Var;
    BOOL fFirst = TRUE;

    do
    {
        GUID *pg;

        if (!fFirst)
        {
            PropVariantClear(&Var);
        }

        fFirst = FALSE;

        memset(&Var, 0, sizeof(Var));
        Var.vt = _vt;

        (*((int*)&_vt))++;

        switch (Var.vt)
        {
        case VT_LPSTR:
                Var.pszVal = (LPSTR)CoTaskMemAlloc(6);
                strcpy(Var.pszVal, "lpstr");
                break;
        case VT_LPWSTR:
                DECLARE_WIDESTR(wcsTemp, "lpwstr");            
                Var.pwszVal = (LPWSTR)CoTaskMemAlloc(14);
                wcscpy(Var.pwszVal, wcsTemp);
                break;
        case VT_CLSID:
                pg = (GUID*)CoTaskMemAlloc(sizeof(GUID));
                UuidCreate(pg);
                Var.puuid = pg;
                break;
        case VT_CF:
                Var.pclipdata = (CLIPDATA*)CoTaskMemAlloc(sizeof(CLIPDATA));
                Var.pclipdata->cbSize = 10;
                Var.pclipdata->pClipData = (BYTE*)CoTaskMemAlloc(10);
                Var.pclipdata->ulClipFmt = 0;
                break;
        }
    } while ( (fNoNonSimple && 
               (Var.vt == VT_STREAM || Var.vt == VT_STREAMED_OBJECT ||
                Var.vt == VT_STORAGE || Var.vt == VT_STORED_OBJECT)) || 
             Var.vt == (VT_VECTOR | VT_VARIANT) || 
             Var.vt == (VT_VECTOR | VT_CF) || 
             Var.vt == (VT_VECTOR | VT_BSTR) || 
             S_OK != PropVariantCopy(pVar, &Var) );  // Is valid propvariant ?

    PropVariantClear(&Var);

    return(TRUE);
}

VOID
CleanStat(ULONG celt, STATPROPSTG *psps)
{
    while (celt--)
    {
        CoTaskMemFree(psps->lpwstrName);
        psps++;
    }
}



HRESULT
PopulateRGPropVar( CPropVariant rgcpropvar[],
                   CPropSpec    rgcpropspec[] )
{
    HRESULT hr = E_FAIL;
    int  i;
    ULONG ulPropIndex = 0;
    CLIPDATA clipdataNull, clipdataNonNull;

    // Initialize the PropVariants

    for( i = 0; i < CPROPERTIES_ALL; i++ )
    {
        rgcpropvar[i].Clear();
    }

    // Create a UI1 property

    DECLARE_OLESTR(ocsUI1, "UI1 Property" );   
    rgcpropspec[ulPropIndex] = ocsUI1;
    rgcpropvar[ulPropIndex] = (UCHAR) 39; // 0x27
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_UI1 );
    ulPropIndex++;

    // Create an I2 property

    DECLARE_OLESTR(ocsI2, "I2 Property" );   
    rgcpropspec[ulPropIndex] = ocsI2;
    rgcpropvar[ulPropIndex] = (SHORT) -502; // 0xfe0a
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_I2 );
    ulPropIndex++;

    // Create a UI2 property
    DECLARE_OLESTR(ocsUI2,  "UI2 Property" );
    rgcpropspec[ulPropIndex] =  ocsUI2;
    rgcpropvar[ulPropIndex] = (USHORT) 502; // 01f6
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_UI2 );
    ulPropIndex++;

    // Create a BOOL property
    DECLARE_OLESTR(ocsBool, "Bool Property" );
    rgcpropspec[ulPropIndex] = ocsBool;
    rgcpropvar[ulPropIndex].SetBOOL( VARIANT_TRUE ); // 0xFFFF
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_BOOL );
    ulPropIndex++;

    // Create a I4 property

    DECLARE_OLESTR(ocsI4, "I4 Property" );
    rgcpropspec[ulPropIndex] = ocsI4;
    rgcpropvar[ulPropIndex] = (long) -523; // 0xFFFFFDF5
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_I4 );
    ulPropIndex++;

    // Create a UI4 property
    DECLARE_OLESTR(ocsUI4, "UI4 Property" );
    rgcpropspec[ulPropIndex] = ocsUI4;
    rgcpropvar[ulPropIndex] = (ULONG) 530; // 0x212
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_UI4 );
    ulPropIndex++;

    // Create a R4 property
    DECLARE_OLESTR(ocsR4,  "R4 Property" );
    rgcpropspec[ulPropIndex] = ocsR4;
    rgcpropvar[ulPropIndex] = (float) 5.37; // 0x40abd70a ?
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_R4 );
    ulPropIndex++;

    // Create an ERROR property
    DECLARE_OLESTR(ocsErr, "ERROR Property" );
    rgcpropspec[ulPropIndex] = ocsErr;
                                // 0x800030002
    rgcpropvar[ulPropIndex].SetERROR( STG_E_FILENOTFOUND );
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_ERROR );
    ulPropIndex++;

    // Create an I8 property

    LARGE_INTEGER large_integer;
    large_integer.LowPart = 551; // 0x227
    large_integer.HighPart = 30; // 0x1E
    DECLARE_OLESTR(ocsI8, "I8 Property" );
    rgcpropspec[ulPropIndex] = ocsI8;
    rgcpropvar[ulPropIndex] = large_integer;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_I8 );
    ulPropIndex++;

    // Create a UI8 property

    ULARGE_INTEGER ularge_integer;
    ularge_integer.LowPart = 561; // 0x231
    ularge_integer.HighPart = 30; // 0x1E
    DECLARE_OLESTR(ocsUI8, "UI8 Property" );
    rgcpropspec[ulPropIndex] = ocsUI8;
    rgcpropvar[ulPropIndex] = ularge_integer;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_UI8 );
    ulPropIndex++;

    // Create an R8 property
    DECLARE_OLESTR( ocsR8, "R8 Property" );
    rgcpropspec[ulPropIndex] = ocsR8;
    rgcpropvar[ulPropIndex] = (double) 571.36; // 4081dae1:47ae147b
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_R8 );
    ulPropIndex++;

    // Create a CY property

    CY cy;
    cy.int64 = 578;             // 0x242
    DECLARE_OLESTR(ocsCY, "Cy Property" );
    rgcpropspec[ulPropIndex] = ocsCY;
    rgcpropvar[ulPropIndex] = cy;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_CY );
    ulPropIndex++;

    // Create a DATE property
    DECLARE_OLESTR(ocsDate, "DATE Property" );
    rgcpropspec[ulPropIndex] = ocsDate;
    rgcpropvar[ulPropIndex].SetDATE( 587 );
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_DATE );
    ulPropIndex++;

    // Create a FILETIME property

    FILETIME filetime;
    filetime.dwLowDateTime = 0x767c0570;
    filetime.dwHighDateTime = 0x1bb7ecf;
    DECLARE_OLESTR(ocsFT, "FILETIME Property" );
    rgcpropspec[ulPropIndex] = ocsFT;
    rgcpropvar[ulPropIndex] = filetime;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_FILETIME );
    ulPropIndex++;

    // Create a CLSID property

    DECLARE_OLESTR(ocsCLSID, "CLSID Property" );
    rgcpropspec[ulPropIndex] = ocsCLSID;
    //  f29f85e0-4ff9-1068-ab91-08002b27b3d9
    rgcpropvar[ulPropIndex] = FMTID_SummaryInformation;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_CLSID );
    ulPropIndex++;


    // Create a vector of CLSIDs
    DECLARE_OLESTR(ocsVectCLSID, "CLSID Vector Property" );
    rgcpropspec[ulPropIndex] = ocsVectCLSID;
    //  f29f85e0-4ff9-1068-ab91-08002b27b3d9
    rgcpropvar[ulPropIndex][0] = FMTID_SummaryInformation;    
    rgcpropvar[ulPropIndex][1] = FMTID_DocSummaryInformation;
    rgcpropvar[ulPropIndex][2] = FMTID_UserDefinedProperties;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_CLSID | VT_VECTOR );
    ulPropIndex++;

    // Create a BSTR property
    DECLARE_OLESTR(ocsBSTR, "BSTR");
    DECLARE_OLESTR(ocsBSTRVal, "BSTR Value");
    rgcpropspec[ulPropIndex] = ocsBSTR;
    rgcpropvar[ulPropIndex].SetBSTR( ocsBSTRVal );
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_BSTR );
    ulPropIndex++;

    // Create a BSTR Vector property
    DECLARE_OLESTR(ocsBSTRVect, "BSTR Vector");
    rgcpropspec[ulPropIndex] = ocsBSTRVect;
    DECLARE_OLESTR(ocsBSTRVectElt, "# - BSTR Vector Element");
    for( i = 0; i < 3; i++ )
    {
        OLECHAR *olestrElement = ocsBSTRVectElt;
        olestrElement[0] = (OLECHAR) i%10 + (OLECHAR)'0';
        rgcpropvar[ulPropIndex].SetBSTR( olestrElement, i );
    }

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_BSTR | VT_VECTOR) );
    ulPropIndex++;

    // Create a variant vector BSTR property.
    DECLARE_OLESTR(ocsBSTRVariantVect, "BSTR Variant Vector");
    DECLARE_OLESTR(ocsBSTRVariantVectElt, "# - Vector Variant Vector");
    rgcpropspec[ulPropIndex ] = ocsBSTRVariantVect;
    
    for( i = 0; i < 3; i++ )
    {
        if( i == 0 )
        {
            rgcpropvar[ulPropIndex][0] = 
                (PROPVARIANT*) CPropVariant((long) 0x1234);
        }
        else
        {
            CPropVariant cpropvarBSTR;
            cpropvarBSTR.SetBSTR( ocsBSTRVariantVectElt );
            (cpropvarBSTR.GetBSTR())[0] = (OLECHAR) i%10 + (OLECHAR)'0';
            rgcpropvar[ulPropIndex][i] = (PROPVARIANT*) cpropvarBSTR;
        }
    }

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_VARIANT | VT_VECTOR) );
    ulPropIndex++;

    // Create an LPSTR property
    DECLARE_OLESTR(ocsLPSTRP, "LPSTR Property");
    rgcpropspec[ulPropIndex] = ocsLPSTRP;
    rgcpropvar[ulPropIndex]  = "LPSTR Value";
    
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_LPSTR );
    ulPropIndex++;

#if 0
    // Create some ClipFormat properties
    DECLARE_OLESTR(ocsClipName, "ClipFormat property");
    rgcpropspec[ ulPropIndex ] = ocsClipName;
    DECLARE_WIDESTR(wcsClipData, "Clipboard Data");
    rgcpropvar[ ulPropIndex ]  = CClipData(wcsClipData);
    ASSERT( rgcpropvar[ ulPropIndex ].VarType() == VT_CF );
    ulPropIndex++;
#endif

    DECLARE_OLESTR(ocsEmptyClipName,
                   "Empty ClipFormat property (NULL pointer)"); 
    rgcpropspec[ ulPropIndex ] = ocsEmptyClipName;
    clipdataNull.cbSize = 4;
    clipdataNull.ulClipFmt = (ULONG) -1; // 0xffff
    clipdataNull.pClipData = NULL;
    rgcpropvar[ ulPropIndex ] = clipdataNull;
    ASSERT( rgcpropvar[ ulPropIndex ].VarType() == VT_CF );
    ulPropIndex++;

    DECLARE_OLESTR(ocsEmptyClipNameNotNull,
                   "Empty ClipFormat property (non-NULL pointer)"); 
    rgcpropspec[ ulPropIndex ] = ocsEmptyClipNameNotNull;
    clipdataNonNull.cbSize = 4;
    clipdataNonNull.ulClipFmt = (ULONG) -1; // 0xffff
    clipdataNonNull.pClipData = (BYTE*) CoTaskMemAlloc(0);
    rgcpropvar[ ulPropIndex ] = clipdataNonNull;
    ASSERT( rgcpropvar[ ulPropIndex ].VarType() == VT_CF );
    ulPropIndex++;


    // Create a vector of ClipFormat properties

    CClipData cclipdataEmpty;
    cclipdataEmpty.Set( (ULONG) -1, "", 0 );
    DECLARE_OLESTR(ocsClipArr, "ClipFormat Array Property");
    rgcpropspec[ ulPropIndex ] = ocsClipArr;
    DECLARE_OLESTR(ocsElt1, "Clipboard Date element 1");
    DECLARE_OLESTR(ocsElt2, "Clipboard Date element 2");     
    rgcpropvar[ ulPropIndex ][0] = CClipData( ocsElt1 );
    rgcpropvar[ ulPropIndex ][1] = cclipdataEmpty;
    rgcpropvar[ ulPropIndex ][2] = clipdataNull;
    rgcpropvar[ ulPropIndex ][3] = clipdataNonNull;
    rgcpropvar[ ulPropIndex ][4] = CClipData( ocsElt2 );

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_CF | VT_VECTOR) );
    ASSERT( rgcpropvar[ulPropIndex].Count() == 5 );
    ulPropIndex++;

    // Create an LPSTR|Vector property (e.g., the DocSumInfo
    // Document Parts array).
    DECLARE_OLESTR(ocsLPSTRorVect, "LPSTR|Vector property");
    rgcpropspec[ ulPropIndex ] = ocsLPSTRorVect;
    rgcpropvar[ ulPropIndex ][0] = "LPSTR Element 0";
    rgcpropvar[ ulPropIndex ][1] = "LPSTR Element 1";

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_LPSTR | VT_VECTOR) );
    ulPropIndex++;

    // Create an LPWSTR|Vector property
    
    DECLARE_OLESTR(ocsLPWSTRVect, "LPWSTR|Vector property");
    DECLARE_WIDESTR(ocslpwvElt1, "LPWSTR Element 0");
    DECLARE_WIDESTR(ocslpwvElt2, "LPWSTR Element 1");    
    rgcpropspec[ ulPropIndex ] = ocsLPWSTRVect;
    rgcpropvar[ ulPropIndex ][0] = ocslpwvElt1;
    rgcpropvar[ ulPropIndex ][1] = ocslpwvElt2;

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_LPWSTR | VT_VECTOR) );
    ulPropIndex++;

    // Create a DocSumInfo HeadingPairs array.
    DECLARE_OLESTR(ocsPairArr, "HeadingPair array");
    rgcpropspec[ ulPropIndex ] = ocsPairArr; 

    rgcpropvar[ ulPropIndex ][0] = (PROPVARIANT*) CPropVariant( "Heading 0" );
    rgcpropvar[ ulPropIndex ][1] = (PROPVARIANT*) CPropVariant( (long) 1 );
    rgcpropvar[ ulPropIndex ][2] = (PROPVARIANT*) CPropVariant( "Heading 1" );
    rgcpropvar[ ulPropIndex ][3] = (PROPVARIANT*) CPropVariant( (long) 1 );

    ASSERT( rgcpropvar[ulPropIndex].VarType() == (VT_VARIANT | VT_VECTOR) );
    ulPropIndex++;

    // Create some NULL (but extant) properties
    DECLARE_OLESTR(ocsEmptyLPSTR, "Empty LPSTR");
    rgcpropspec[ulPropIndex] = ocsEmptyLPSTR;
    rgcpropvar[ulPropIndex]  = "";
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_LPSTR );
    ulPropIndex++;

    DECLARE_OLESTR(ocsEmptyLPWSTR, "Empty LPWSTR");    
    DECLARE_WIDESTR(wcsEmpty, "");
    rgcpropspec[ulPropIndex] = ocsEmptyLPWSTR;
    rgcpropvar[ulPropIndex]  = wcsEmpty;
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_LPWSTR );
    ulPropIndex++;


    DECLARE_OLESTR(ocsEmptyBLOB, "Empty BLOB");
    rgcpropspec[ulPropIndex] = ocsEmptyBLOB;
    rgcpropvar[ulPropIndex] = CBlob(0);
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_BLOB );
    ulPropIndex++;

    DECLARE_OLESTR(ocsEmptyBSTR, "Empty BSTR");
    DECLARE_OLESTR(ocsEmpty, "");
    rgcpropspec[ulPropIndex] = ocsEmptyBSTR;
    rgcpropvar[ulPropIndex].SetBSTR( ocsEmpty );
    ASSERT( rgcpropvar[ulPropIndex].VarType() == VT_BSTR );
    ulPropIndex++;

    //  ----
    //  Exit
    //  ----

    CoTaskMemFree( clipdataNonNull.pClipData );
    memset( &clipdataNonNull, 0, sizeof(clipdataNonNull) );
   
    ASSERT( CPROPERTIES_ALL == ulPropIndex );
    hr = S_OK;
    return(hr);
}


//+---------------------------------------------------------------
//
//  Function:   test_WriteReadAllProperties
//
//  Synopsis:   This test simply creates two new property
//              sets in a new file (one Ansi and one Unicode),
//              writes all the properties in g_rgcpropvarAll,
//              reads them back, and verifies that it reads what
//              it wrote.
//
//  Outputs:    None.
//
//+---------------------------------------------------------------

void
test_WriteReadAllProperties(ULONG ulTestOptions )
{
    FMTID fmtidAnsi, fmtidUnicode;
    TSafeStorage< IStorage > pstg;
    TSafeStorage< IPropertySetStorage > ppropsetstg;
    TSafeStorage< IPropertyStorage > ppropstgAnsi;
    TSafeStorage< IPropertyStorage > ppropstgUnicode;

    CPropVariant rgcpropvarAnsi[ CPROPERTIES_ALL ];
    CPropVariant rgcpropvarUnicode[ CPROPERTIES_ALL ];

    printf( "   Simple Write/Read Test\n" );

    //  -----------------------
    //  Create the Property Set
    //  -----------------------

    // Generate FMTIDs.

    fmtidAnsi = IID_IPropertyStorage;
    fmtidUnicode = IID_IPropertySetStorage;

    // Generate a filename from the directory name.

    DECLARE_OLESTR(ocsFile, "AllProps.stg" );

    if (! (ulTestOptions & TEST_INTEROP_R))
    {
        // Create the Docfile.
        printf("        Writing into file ... \n");

        Check( S_OK, StgCreateDocfile( ocsFile,
                                       STGM_CREATE | 
                                       STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                       0L,
                                       &pstg ));
    
        // Get the IPropertySetStorage
        
        Check( S_OK, pstg->QueryInterface( IID_IPropertySetStorage, 
                                           (void**)&ppropsetstg ));
    
        // Create a Property Storage
        
        Check( S_OK, ppropsetstg->Create( fmtidAnsi,
                                          &CLSID_NULL,
                                          PROPSETFLAG_DEFAULT,
                                          STGM_READWRITE | 
                                          STGM_SHARE_EXCLUSIVE,
                                          &ppropstgAnsi ));
        
        Check( S_OK, ppropsetstg->Create( fmtidUnicode,
                                          &CLSID_NULL,
                                          PROPSETFLAG_ANSI,
                                          STGM_READWRITE | 
                                          STGM_SHARE_EXCLUSIVE,
                                          &ppropstgUnicode ));
        
        Check( S_OK, ppropstgAnsi->WriteMultiple( CPROPERTIES_ALL,
                                                  (PROPSPEC*)g_rgcpropspecAll,
                                                  (PROPVARIANT*)g_rgcpropvarAll,
                                                  PID_FIRST_USABLE ));

        Check( S_OK, ppropstgUnicode->WriteMultiple( CPROPERTIES_ALL,
                                                     (PROPSPEC*)g_rgcpropspecAll,
                                                     (PROPVARIANT*)g_rgcpropvarAll,
                                                     PID_FIRST_USABLE ));      
    }
    else
    {
        // open it
        printf("        opening file ... \n");

        Check(S_OK, StgOpenStorage( ocsFile,
                                    (IStorage*) NULL,
                                    STGM_READWRITE | STGM_SHARE_EXCLUSIVE,  
                                    (SNB) 0,
                                    (DWORD) 0,
                                    &pstg ));

        // Get the IPropertySetStorage
        
        Check( S_OK, pstg->QueryInterface( IID_IPropertySetStorage, 
                                           (void**)&ppropsetstg ));
    
        // Open the Property Storage

        Check(S_OK, ppropsetstg->Open(fmtidUnicode,
                                      STGM_SHARE_EXCLUSIVE | 
                                      STGM_DIRECT | STGM_READWRITE,
                                      &ppropstgUnicode));
        Check(S_OK, ppropsetstg->Open(fmtidAnsi,
                                      STGM_SHARE_EXCLUSIVE | 
                                      STGM_DIRECT | STGM_READWRITE,
                                      &ppropstgAnsi));
    }    

    printf("        Verifying file ... \n");
    // either way, read it and make sure it is right
    Check( S_OK, ppropstgAnsi->ReadMultiple( CPROPERTIES_ALL,
                                             (PROPSPEC*)g_rgcpropspecAll,
                                             (PROPVARIANT*)rgcpropvarAnsi ));

    Check( S_OK, ppropstgUnicode->ReadMultiple( CPROPERTIES_ALL,
                                                (PROPSPEC*)g_rgcpropspecAll,
                                                (PROPVARIANT*)rgcpropvarUnicode ));
        
    //  ----------------------
    //  Compare the properties
    //  ----------------------
    
    for( int i = 0; i < (int)CPROPERTIES_ALL; i++ )
    {
        Check( TRUE, rgcpropvarAnsi[i] == g_rgcpropvarAll[i]
                     &&
                     rgcpropvarUnicode[i] == g_rgcpropvarAll[i] );
    }
    
}   // test_WriteReadProperties




//
// DOCFILE -- run all tests on DocFile
//
// IPropertySetStorage tests
//      

void
test_IPropertySetStorage_IUnknown(IStorage *pStorage)
{
    printf( "   IPropertySetStorage::IUnknown\n" );

    //       Check ref counting through different interfaces on object
    //      
    //          QI to IPropertySetStorage
    //          QI to IUnknown on IStorage
    //          QI to IUnknown on IPropertySetStorage
    //          QI back to IPropertySetStorage from IUnknown
    //          QI back to IStorage from IPropertySetStorage
    //      
    //          Release all.
    //

    IStorage *pStorage2;
    IPropertySetStorage *ppss1, *ppss2, *ppss3;
    IUnknown *punk1,*punk2;

    Check(S_OK, pStorage->QueryInterface(IID_IPropertySetStorage, (void**)&ppss1));
    Check(S_OK, pStorage->QueryInterface(IID_IUnknown, (void **)&punk1));
    Check(S_OK, ppss1->QueryInterface(IID_IUnknown, (void **)&punk2));
    Check(S_OK, ppss1->QueryInterface(IID_IStorage, (void **)&pStorage2));
    Check(S_OK, ppss1->QueryInterface(IID_IPropertySetStorage, (void **)&ppss2));
    Check(S_OK, punk1->QueryInterface(IID_IPropertySetStorage, (void **)&ppss3));

    ppss1->AddRef();
    ppss1->Release();

    //pStorage.Release();
    ppss1->Release();
    punk1->Release();
    punk2->Release();
    pStorage2->Release();
    ppss2->Release();
    ppss3->Release();

}


#define INVALID_POINTER     ( (void *) 0xFFFFFFFF )
#define VTABLE_MEMBER_FN(pObj,entry)  ( (*(ULONG ***)(pObj))[ (entry) ] )


//+---------------------------------------------------------
//
//  Template:   Alloc2PageVector
//
//  Purpose:    This function template allocates two pages
//              of memory, and then sets a vector pointer
//              so that its first element is wholy within
//              the first page, and the second element is 
//              wholy within the second.  Then, the protection
//              of the second page is set according to the
//              caller-provided parameter.
//
//
//  Inputs:     [TYPE**] ppBase
//                  Points to the beginning of the two pages.
//              [TYPE**] ppVector
//                  Points to the beginning of the vector of TYPEs.
//              [DWORD] dwProtect
//                  The desired protection on the second page
//                  (from the PAGE_* enumeration).
//              [LPWSTR] lpwstr (optional)
//                  If not NULL, used to initialize the vector
//                  elements.
//
//  Output:     TRUE iff successful.
//
//+---------------------------------------------------------


template< class TYPE > BOOL Alloc2PageVector( TYPE** ppBase,
                                              TYPE** ppVector,
                                              DWORD  dwProtect,
                                              TYPE*  pInit )
{
    DWORD dwOldProtect;
    SYSTEM_INFO si;

    GetSystemInfo( &si );

    *ppBase = (TYPE*) VirtualAlloc( NULL, 2 * si.dwPageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    if( NULL == *ppBase )
        return( FALSE );

    *ppVector = (TYPE*) ( (BYTE*) *ppBase + si.dwPageSize - sizeof(TYPE) );

    if( NULL != pInit )
    {
        memcpy( &((LPWSTR*)*ppVector)[0], pInit, sizeof(TYPE) );
        memcpy( &((LPWSTR*)*ppVector)[1], pInit, sizeof(TYPE) );
    }

    if( !VirtualProtect( (BYTE*) *ppBase + si.dwPageSize, si.dwPageSize, dwProtect, &dwOldProtect ) )
        return( FALSE );

    return( TRUE );
}



void
test_PropVariantValidation( IStorage *pStg )
{

    printf( "   PropVariant Validation\n" );

    TSafeStorage< IPropertySetStorage > pPSStg( pStg );
    TSafeStorage< IPropertyStorage > pPStg;

    CPropVariant cpropvar;
    CLIPDATA     clipdata;
    PROPSPEC     propspec;

    DECLARE_WIDESTR(wszText, "Unicode Text String");

    FMTID fmtid;
    UuidCreate( &fmtid );

    Check(S_OK, pPSStg->Create( fmtid,
                                NULL,
                                PROPSETFLAG_DEFAULT,
                                STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                &pPStg ));


    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = 2;

    //  -------------------------------
    //  Test invalid VT_CF Propvariants
    //  -------------------------------

    // NULL clip format.

    clipdata.cbSize = 4;
    clipdata.ulClipFmt = (ULONG) -1;
    clipdata.pClipData = NULL;

    cpropvar = clipdata;

    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Too short cbSize.

    ((PROPVARIANT*)cpropvar)->pclipdata->cbSize = 3;
    Check(STG_E_INVALIDPARAMETER, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Too short pClipData (it should be 1 byte, but the pClipData is NULL).

    ((PROPVARIANT*)cpropvar)->pclipdata->cbSize = 5;
    Check(STG_E_INVALIDPARAMETER, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));


}

//       Check creation/open/deletion of property sets (check fmtid and predefined names)
//          Create a property set
//          Try recreate of same
//          Try delete
//          Close the property set
//          Try recreate of same 
//          Reopen the property set
//          Try recreate of same
//          Try delete
//          Close the property set
//          Delete the property set
//          Repeat the test once more

void
test_IPropertySetStorage_CreateOpenDelete(IStorage *pStorage)
{
    printf( "   IPropertySetStorage::Create/Open/Delete\n" );

    FMTID fmtid;
    PROPSPEC propspec;

    UuidCreate(&fmtid);
    int i;
    for (i=0; i<4; i++)
    {
        {
            TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
            IPropertyStorage *PropStg, *PropStg2;
            
            Check(S_OK, pPropSetStg->Create(fmtid,
                                            NULL,
                                            PROPSETFLAG_DEFAULT,
                                            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                                            &PropStg));
            Check(S_OK, pPropSetStg->Create(
                fmtid,
                NULL,
                PROPSETFLAG_DEFAULT,
                STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                &PropStg2));
            
            Check(STG_E_REVERTED, PropStg->Commit(0));
            
            PropStg->Release();
            PropStg2->Release();
        }        
        {
            TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
            IPropertyStorage *PropStg, *PropStg2;
            
            // use STGM_FAILIFTHERE
            Check(STG_E_FILEALREADYEXISTS, 
                  pPropSetStg->Create(fmtid,
                                      NULL,
                                      PROPSETFLAG_DEFAULT,
                                      STGM_SHARE_EXCLUSIVE | STGM_DIRECT | 
                                      STGM_READWRITE,
                                      &PropStg));
            
            Check(S_OK, 
                  pPropSetStg->Open(fmtid,
                                    STGM_SHARE_EXCLUSIVE | STGM_DIRECT | 
                                    STGM_READWRITE,
                                    &PropStg));
            
            Check(STG_E_ACCESSDENIED, 
                  pPropSetStg->Create(fmtid,
                                      NULL,
                                      PROPSETFLAG_DEFAULT,
                                      STGM_SHARE_EXCLUSIVE | STGM_DIRECT | 
                                      STGM_READWRITE,
                                      &PropStg2));
        
            Check(S_OK, 
                  pPropSetStg->Delete(fmtid));
        
            propspec.ulKind = PRSPEC_PROPID;
            propspec.propid = 1000;
            PROPVARIANT propvar;
            propvar.vt = VT_I4;
            propvar.lVal = 12345;
            Check(STG_E_REVERTED, 
                  PropStg->WriteMultiple(1, &propspec, &propvar, 
                                     2)); // force dirty
        
            PropStg->Release();
        }
    }

    //  --------------------------------------------------------
    //  Test the Create/Delete of the DocumentSummaryInformation
    //  property set (this requires special code because it
    //  has two sections).
    //  --------------------------------------------------------

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    TSafeStorage< IPropertyStorage> pPropStg1, pPropStg2;

    // Create & Delete a DSI propset with just the first section.

    Check(S_OK, pPropSetStg->Create(FMTID_DocSummaryInformation,
            NULL,
            PROPSETFLAG_DEFAULT,
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStg1));

    pPropStg1->Release(); pPropStg1 = NULL;
    Check(S_OK, pPropSetStg->Delete( FMTID_DocSummaryInformation ));

    // Create & Delete a DSI propset with just the second section

    Check(S_OK, pPropSetStg->Create(FMTID_UserDefinedProperties,
            NULL,
            PROPSETFLAG_DEFAULT,
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStg1 ));

    pPropStg1->Release(); pPropStg1 = NULL;
    Check(S_OK, pPropSetStg->Delete( FMTID_UserDefinedProperties ));

    // Create & Delete a DocumentSummaryInformation propset with both sections.  
    // If you delete the DSI propset first, it should delete both sections.
    // If you delete the UD propset first, the DSI propset should still
    // remain.  We'll loop twice, trying both combinations.

    for( i = 0; i < 2; i++ )
    {

        // Create the first section, which implicitely creates
        // the second section.

        Check(S_OK, pPropSetStg->Create(FMTID_DocSummaryInformation,
                NULL,
                PROPSETFLAG_DEFAULT,
                STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                &pPropStg1));

        pPropStg1->Release(); pPropStg1 = NULL;

        if( i == 0 )
        {
            Check(S_OK, pPropSetStg->Delete( FMTID_UserDefinedProperties ));
            Check(S_OK, pPropSetStg->Delete( FMTID_DocSummaryInformation ));
        }
        else
        {
            Check(S_OK, pPropSetStg->Delete( FMTID_DocSummaryInformation ));
            Check(STG_E_FILENOTFOUND, pPropSetStg->Delete( FMTID_UserDefinedProperties ));
        }
    }   // for( i = 0; i < 2; i++ )

    //  -------------------------------------
    //  Test special properties in DocSumInfo
    //  -------------------------------------

    // This verifies that when we Create a DocSumInfo
    // property set, and write a Vector or LPSTRs,
    // we can read it again.  We test this because 
    // Vectors of LPSTRs are a special case in the DocSumInfo,
    // and the Create & Open path are slightly different
    // in CPropertySetStream::_LoadHeader.

    // Create a new property set.

    Check(S_OK, pPropSetStg->Create(FMTID_DocSummaryInformation,
            NULL,
            PROPSETFLAG_DEFAULT,
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStg1));

    // Create a vector of LPSTRs.  Make the strings
    // varying lengths to ensure we get plenty of
    // opportunity for alignment problems.

    CPropVariant cpropvarWrite, cpropvarRead;

    cpropvarWrite[3] = "12345678";
    cpropvarWrite[2] = "1234567";
    cpropvarWrite[1] = "123456";
    cpropvarWrite[0] = "12345";
    ASSERT( cpropvarWrite.Count() == 4 );

    // Write the property
    DECLARE_OLESTR(ocsVect, "A Vector of LPSTRs");
    propspec.ulKind = PRSPEC_LPWSTR;
    propspec.lpwstr = ocsVect;

    Check(S_OK, pPropStg1->WriteMultiple( 1, &propspec, cpropvarWrite, 2 ));

    // Read the property back.

    Check(S_OK, pPropStg1->ReadMultiple( 1, &propspec, cpropvarRead ));

    // Verify that we read what we wrote.

    for( i = 0; i < (int) cpropvarWrite.Count(); i++ )
    {
        Check(0, strcmp( (LPSTR) cpropvarWrite[i], (LPSTR) cpropvarRead[i] ));
    }

}


void
test_IPropertySetStorage_SummaryInformation(IStorage *pStorage)
{
    printf( "   SummaryInformation\n" );
    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    IPropertyStorage *PropStg;
    IStream *pstm;

    Check(S_OK, pPropSetStg->Create(FMTID_SummaryInformation,
            NULL,
            PROPSETFLAG_DEFAULT, // simple, wide
            STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &PropStg));

    PropStg->Release();
    DECLARE_OLESTR(ocsSummary, "\005SummaryInformation");
    Check(S_OK, pStorage->OpenStream(ocsSummary,
            NULL,
            STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
            0,
            &pstm));

    pstm->Release();
}

//
//       Check STGM_FAILIFTHERE and ~STGM_FAILIFTHERE in following cases
//          Check overwriting simple with simple

void
test_IPropertySetStorage_FailIfThere(IStorage *pStorage)
{
    // (Use "fale" instead of "fail" in this printf so the output won't
    // alarm anyone with the word "fail" uncessarily).
    printf( "   IPropertySetStorage, FaleIfThere\n" );

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);

    // Iter       0        1          2         3          4        5          6         7
    // Create     simple   nonsimple  simple    nonsimple  simple   nonsimple  simple    nonsimple
    // ReCreate   simple   simple     nonsimple nonsimple  simple   simple     nonsimple nonsimple
    //            failif   failif     failif    failif     overw    overw      overw     overw
    //
    // expected   exists   exists     exists    exists     ok       ok         ok        ok

    for (int i=0; i<8; i++)
    {
        FMTID fmtid;
        IPropertyStorage *PropStg;


        UuidCreate(&fmtid);

        Check(S_OK, pPropSetStg->Create(fmtid,
                NULL,
                0,
                STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                &PropStg));

        PropStg->Release();

        Check((i&4) == 4 ? S_OK : STG_E_FILEALREADYEXISTS,
            pPropSetStg->Create(fmtid,
                NULL,
                0,
                ( (i & 4) == 4 ? STGM_CREATE : STGM_FAILIFTHERE) |
                    STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                &PropStg));

        if (PropStg)
        {
            PropStg->Release();
        }
    }
}

void
test_IPropertySetStorage(IStorage *pStorage)
{
    //       Check ref counting through different interfaces on object
    test_IPropertySetStorage_IUnknown(pStorage);
    test_IPropertySetStorage_CreateOpenDelete(pStorage);
    test_IPropertySetStorage_SummaryInformation(pStorage);
    test_IPropertySetStorage_FailIfThere(pStorage);
}

//  IEnumSTATPROPSETSTG
//
//       Check enumeration of property sets
//      
//          Check refcounting and IUnknown
//      
//          Create some property sets, predefined and not, simple and not, one through IStorage
//          Enumerate them and check
//              (check fmtid, grfFlags)
//              (check when asking for more than there is: S_FALSE, S_OK)
//          Delete one
//          Reset the enumerator
//          Enumerate them and check
//          Delete one
//      
//          Reset the enumeratorA
//          Read one from enumeratorA
//          Clone enumerator -> enumeratorB
//          Loop comparing rest of enumerator contents
//      
//          Reset the enumerator
//          Skip all
//          Check none left
//      
//          Reset the enumerator
//          Skip all but one
//          Check one left
//

void CheckTime(const FILETIME &ftStart, const FILETIME &ftPropSet)
{
    FILETIME ftNow;
    Now(&ftNow);

    if (ftPropSet.dwLowDateTime == 0 && ftPropSet.dwHighDateTime == 0)
    {
        return;
    }

    // if ftPropSet < ftStart || ftNow < ftPropSet, error
    ASSERT (!(CompareFileTime(&ftPropSet, &ftStart) == -1 ||
              CompareFileTime(&ftNow, &ftPropSet) == -1));
}

void test_IEnumSTATPROPSETSTG(IStorage *pStorage)
{
    printf( "   IEnumSTATPROPSETSTG\n" );

    FMTID afmtid[8];
    CLSID aclsid[8];
    IPropertyStorage *pPropSet;

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    FILETIME ftStart;

    Now(&ftStart);

    pPropSetStg->Delete(FMTID_SummaryInformation);

    for (int i=0; i<5; i++)
    {
        if (i & 4)
            afmtid[i] = FMTID_SummaryInformation;
        else
            UuidCreate(&afmtid[i]);

        UuidCreate(&aclsid[i]);

        Check(S_OK, pPropSetStg->Create(afmtid[i], aclsid+i,
             ((i & 2) ? PROPSETFLAG_ANSI : 0),
            STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
            &pPropSet));
        pPropSet->Release();
    }


    STATPROPSETSTG StatBuffer[6];
    ULONG celt;
    IEnumSTATPROPSETSTG *penum, *penum2;

    Check(S_OK, pPropSetStg->Enum(&penum));

    IUnknown *punk, *punk2;
    IEnumSTATPROPSETSTG *penum3;
    Check(S_OK, penum->QueryInterface(IID_IUnknown, (void**)&punk));
    Check(S_OK, punk->QueryInterface(IID_IEnumSTATPROPSETSTG, (void**)&penum3));
    Check(S_OK, penum->QueryInterface(IID_IEnumSTATPROPSETSTG, (void**)&punk2));
    ASSERT(punk == punk2);
    punk->Release();
    penum3->Release();
    punk2->Release();

    // test S_FALSE
    Check(S_FALSE, penum->Next(6, StatBuffer, &celt));
    ASSERT(celt == 5);
    penum->Reset();


    // test reading half out, then cloning, then comparing
    // rest of enumeration with other clone.

    Check(S_OK, penum->Next(3, StatBuffer, &celt));
    ASSERT(celt == 3);
    celt = 0;
    Check(S_OK, penum->Clone(&penum2));
    Check(S_OK, penum->Next(2, StatBuffer, &celt));
    ASSERT(celt == 2);
    // check the clone
    for (int c=0; c<2; c++)
    {
        STATPROPSETSTG CloneStat;
        Check(S_OK, penum2->Next(1, &CloneStat, NULL));
        Check(TRUE, 0 == memcmp(&CloneStat, StatBuffer+c, sizeof(CloneStat)));
        Check(TRUE, CloneStat.dwOSVersion == PROPSETHDR_OSVERSION_UNKNOWN);
    }

    // check both empty
    celt = 0;
    Check(S_FALSE, penum->Next(1, StatBuffer, &celt));
    ASSERT(celt == 0);

    Check(S_FALSE, penum2->Next(1, StatBuffer, &celt));
    ASSERT(celt == 0);

    penum->Reset();

    //
    // loop deleting one propset at a time
    // enumerate the propsets checking that correct ones appear.
    //
    for (ULONG d = 0; d<5; d++)
    {
        // d is for delete

        BOOL afFound[5];

        Check(S_OK, penum->Next(5-d, StatBuffer, &celt));
        ASSERT(celt == 5-d);
        penum->Reset();
    
        memset(afFound, 0, sizeof(afFound));
        for (ULONG iPropSet=0; iPropSet<5; iPropSet++)
        {
            ULONG iSearch;  
            for (iSearch=0; iSearch<5-d; iSearch++)
            {
                if (0 == memcmp(&StatBuffer[iSearch].fmtid, 
                                &afmtid[iPropSet], 
                                sizeof(StatBuffer[0].fmtid)))
                {
                    ASSERT (!afFound[iPropSet]);
                    afFound[iPropSet] = TRUE;
                    break;
                }
            }
            if (iPropSet < d)
            {
                ASSERT(!afFound[iPropSet]);
            }
            if (iSearch == 5-d)
            {
                ASSERT(iPropSet < d);
                continue;
            }
            ASSERT( ( (StatBuffer[iSearch].grfFlags 
                           & PROPSETFLAG_NONSIMPLE)  == 0 ) ); 
            ASSERT((StatBuffer[iSearch].grfFlags & PROPSETFLAG_ANSI) == 0);
            if (StatBuffer[iSearch].grfFlags & PROPSETFLAG_NONSIMPLE)
            {
                ASSERT(StatBuffer[iSearch].clsid == aclsid[iPropSet]);
            }
            else
            {
                ASSERT(StatBuffer[iSearch].clsid == CLSID_NULL);
            }
            CheckTime(ftStart, StatBuffer[iSearch].mtime);
            CheckTime(ftStart, StatBuffer[iSearch].atime);
            CheckTime(ftStart, StatBuffer[iSearch].ctime);
        }

        Check(S_OK, pPropSetStg->Delete(afmtid[d]));
        Check(S_OK, penum->Reset());
    }

    penum->Release();
    penum2->Release();

}


//   Creation tests
//
//       Access flags/Valid parameters/Permissions
//          Check readonly cannot be written -
//              WriteProperties, WritePropertyNames
void
test_IPropertyStorage_Access(IStorage *pStorage)
{
    printf( "   IPropertyStorage creation (access) tests\n" );

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    FMTID fmtid;

    UuidCreate(&fmtid);

    // check by name
    IPropertyStorage *pPropStg;
    Check(S_OK, pPropSetStg->Create(fmtid, NULL, 0, 
        STGM_SHARE_EXCLUSIVE | STGM_READWRITE, &pPropStg));

//   QueryInterface tests
//          QI to IPropertyStorage
//          QI to IUnknown on IPropertyStorage
//          QI back to IPropertyStorage from IUnknown
//      
//          Release all.
    IPropertyStorage *pPropStg2,*pPropStg3;
    IUnknown *punk;

    Check(S_OK, pPropStg->QueryInterface(IID_IPropertyStorage, 
        (void**)&pPropStg2));
    Check(S_OK, pPropStg->QueryInterface(IID_IUnknown, 
        (void**)&punk));
    Check(S_OK, punk->QueryInterface(IID_IPropertyStorage, 
        (void**)&pPropStg3));
    pPropStg3->Release();
    pPropStg2->Release();
    punk->Release();

    PROPSPEC ps;
    ps.ulKind = PRSPEC_LPWSTR;
    DECLARE_OLESTR(ocsTestProp, "testprop");
    ps.lpwstr = ocsTestProp;
    PROPVARIANT pv;
    pv.vt = VT_LPWSTR;
    DECLARE_CONST_OLESTR(ocsTestVal, "testval");
    LPOLESTR ocsTest = ocsTestVal;
    DECLARE_WIDESTR(wcsTestVal, "testval");
    pv.pwszVal = wcsTestVal;
    Check(S_OK, pPropStg->WriteMultiple(1, &ps, &pv, 2));
    pPropStg->Release();
    Check(S_OK, pPropSetStg->Open(fmtid, STGM_SHARE_EXCLUSIVE | STGM_READ,
                                  &pPropStg)); 
    Check(STG_E_ACCESSDENIED, pPropStg->WriteMultiple(1, &ps, &pv, 2));
    Check(STG_E_ACCESSDENIED, pPropStg->DeleteMultiple(1, &ps));
    PROPID propid=3;
    Check(STG_E_ACCESSDENIED, pPropStg->WritePropertyNames(1, &propid, 
                                                           &ocsTestVal));
    Check(STG_E_ACCESSDENIED, pPropStg->DeletePropertyNames(1, &propid));
    FILETIME ft;
    Check(STG_E_ACCESSDENIED, pPropStg->SetTimes(&ft, &ft, &ft));
    CLSID clsid;
    Check(STG_E_ACCESSDENIED, pPropStg->SetClass(clsid));

    pPropStg->Release();
}

//   Creation tests
//       Check VT_STREAM etc not usable with simple.

void 
test_IPropertyStorage_Create(IStorage *pStorage)
{
    printf( "   IPropertyStorage creation tests\n" );
    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    FMTID fmtid;

    UuidCreate(&fmtid);

    // check by name
    IPropertyStorage *pPropStg;
    Check(S_OK, pPropSetStg->Create(fmtid, NULL, 0 /* simple */, 
        STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, &pPropStg));
    PROPSPEC ps;
    ps.ulKind = PRSPEC_PROPID;
    ps.propid = 2;
    PROPVARIANT pv;
    pv.vt = VT_STREAM;
    pv.pStream = NULL;
    // the ref impl. does not recognize VT_STREAM, and will thus
    // treat it as a invalid type.
    Check(STG_E_INVALIDPARAMETER, 
          pPropStg->WriteMultiple(1, &ps, &pv, 2000));  
    // 
    pPropStg->Release();
}


void
CheckStat(  IPropertyStorage *pPropSet, REFFMTID fmtid, 
            REFCLSID clsid, ULONG PropSetFlag,
            const FILETIME & ftStart, DWORD dwOSVersion )
{
    STATPROPSETSTG StatPropSetStg;
    Check(S_OK, pPropSet->Stat(&StatPropSetStg));

    Check(TRUE, StatPropSetStg.fmtid == fmtid);
    Check(TRUE, StatPropSetStg.clsid == clsid);
    Check(TRUE, StatPropSetStg.grfFlags == PropSetFlag);
    Check(TRUE, StatPropSetStg.dwOSVersion == dwOSVersion);
    CheckTime(ftStart, StatPropSetStg.mtime);
    CheckTime(ftStart, StatPropSetStg.ctime);
    CheckTime(ftStart, StatPropSetStg.atime);
}

//
//   
//   Stat (Create four combinations)
//       Check ansi/wide fflag
//     Also test clsid on propset

void test_IPropertyStorage_Stat(IStorage *pStorage)
{
    printf( "   IPropertyStorage::Stat\n" );

    DWORD dwOSVersion = 0;

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);
    FMTID fmtid;
    UuidCreate(&fmtid);
    IPropertyStorage *pPropSet;
    STATPROPSETSTG StatPropSetStg;

    // Calculate the OS Version

#ifdef _MAC_
#error Do not know how to calculate the OS Version for the macintosh.
#endif

    dwOSVersion = MAKELONG( 0x0100, OSKIND_REF );

    for (ULONG i=0; i<4; i++)
    {
        FILETIME ftStart;
        DfGetTOD(&ftStart);

        memset(&StatPropSetStg, 0, sizeof(StatPropSetStg));
        CLSID clsid;
        UuidCreate(&clsid);
        Check(S_OK, pPropSetStg->Create(fmtid, &clsid,
            ((i & 2) ? PROPSETFLAG_ANSI : 0),
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
            &pPropSet));

        CheckStat(pPropSet, fmtid, clsid, 
                  ((i & 2) ? PROPSETFLAG_ANSI : 0), ftStart, dwOSVersion );
        pPropSet->Release();

        Check(S_OK, pPropSetStg->Open(fmtid, 
            STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
            &pPropSet));
        CheckStat(pPropSet, fmtid, clsid, 
                  ((i & 2) ? PROPSETFLAG_ANSI : 0), ftStart, dwOSVersion );

        pPropSet->Release();

        Check(S_OK, pPropSetStg->Open(fmtid, 
            STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
            &pPropSet));
        CheckStat(pPropSet, fmtid, clsid, 
            ((i & 2) ? PROPSETFLAG_ANSI : 0), ftStart, dwOSVersion );
        pPropSet->Release();
    }
}

//
// test using IStorage::Commit to commit the changes in a nested
// property set
//

void
test_IPropertyStorage_Commit(IStorage *pStorage)
{
    printf( "   IPropertyStorage::Commit\n" );

    // create another level of storage

    SCODE sc;

    // 8 scenarios: simple  * direct * (release only + commit storage + commit
    // propset)
    // note: some scenarios might repeat since there is no
    // non-simple/transacted cases
    for (int i=0; i<32; i++)
    {
        CTempStorage pDeeper(Create, pStorage, GetNextTest(), 
            STGM_DIRECT);
        TSafeStorage< IPropertySetStorage > pPropSetStg(pDeeper);
    
        FMTID fmtid;
        UuidCreate(&fmtid);
    
        IPropertyStorage *pPropSet;
        if (S_OK != (sc = pPropSetStg->Create(
            fmtid, NULL, 
            PROPSETFLAG_DEFAULT,
            STGM_SHARE_EXCLUSIVE | STGM_READWRITE | 
            STGM_DIRECT,
             &pPropSet)))
        {
            Check(S_OK, sc);
        }
              
        PROPSPEC ps;
        ps.ulKind = PRSPEC_PROPID;
        ps.propid = 100;
        PROPVARIANT pv;
        pv.vt = VT_I4;
        pv.lVal = 1234;
    
        Check(S_OK, pPropSet->WriteMultiple(1, &ps, &pv, 1000));
    
        PropVariantClear(&pv);
        Check(S_OK, pPropSet->ReadMultiple(1, &ps, &pv));
        ASSERT(pv.vt==VT_I4);
        ASSERT(pv.lVal == 1234);

        pv.lVal = 2345; // no size changes
        Check(S_OK, pPropSet->WriteMultiple(1, &ps, &pv, 1000));

        if (i & 4)
            Check(S_OK, pPropSet->Commit(0));
        if (i & 2)
            Check(S_OK, pStorage->Commit(0));

        Check(0, pPropSet->Release()); // implicit commit if i&2 is false

        if (S_OK == pPropSetStg->Open(fmtid, STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
                    &pPropSet))
        {
            PropVariantClear(&pv);
            Check( S_OK, pPropSet->ReadMultiple(1, &ps, &pv));
            ASSERT(pv.vt == VT_I4);
            ASSERT(pv.lVal == 2345);
            pPropSet->Release();
        }
    }
}

void
test_IPropertyStorage_WriteMultiple(IStorage *pStorage)
{
    test_IPropertyStorage_Commit(pStorage);
}

// this serves as a test for WritePropertyNames, ReadPropertyNames, DeletePropertyNames
// DeleteMultiple, PropVariantCopy, FreePropVariantArray.

void
test_IPropertyStorage_DeleteMultiple(IStorage *pStorage)
{
    printf( "   IPropertyStorage::DeleteMultiple\n" );

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStorage);

    FMTID fmtid;
    UuidCreate(&fmtid);

    IPropertyStorage *pPropSet;

    int PropId = 3;

    UuidCreate(&fmtid);
    Check(S_OK, pPropSetStg->Create(
        fmtid,
        NULL, PROPSETFLAG_DEFAULT,
        STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
        &pPropSet));
    
    // create and delete each type.
    
    PROPVARIANT *pVar;
        
    for (int AtOnce=1; AtOnce <3; AtOnce++)
    {
        CGenProps gp;
        int Actual;
        while (pVar = gp.GetNext(AtOnce, &Actual, FALSE, TRUE))
        {
            PROPSPEC ps[3];
            PROPID   rgpropid[3];
            LPOLESTR rglpostrName[3];
            OLECHAR    aosz[3][16];
            char pchTemp [16];

            for (int s=0; s<3; s++)
            {                
                sprintf(pchTemp, "prop%d", PropId );
                STOT(pchTemp, aosz[s], strlen(pchTemp)+1);
                rgpropid[s] = PropId++;
                rglpostrName[s] = &aosz[s][0];
                ps[s].ulKind = PRSPEC_LPWSTR;
                ps[s].lpwstr = &aosz[s][0];
            }

            for (int l=1; l<Actual; l++)
            {                
                PROPVARIANT VarRead[3];
                Check(S_FALSE, pPropSet->ReadMultiple(l, ps, VarRead));
                Check(S_OK, pPropSet->
                      WritePropertyNames(l, rgpropid, rglpostrName));
                Check(S_FALSE, pPropSet->ReadMultiple(l, ps, VarRead));
                
                Check(S_OK, pPropSet->WriteMultiple(l, ps, pVar, 1000));
                Check(S_OK, pPropSet->ReadMultiple(l, ps, VarRead));
                Check(S_OK, FreePropVariantArray(l, VarRead));
                Check(S_OK, pPropSet->DeleteMultiple(l, ps));
                Check(S_FALSE, pPropSet->ReadMultiple(l, ps, VarRead));
                Check(S_OK, FreePropVariantArray(l, VarRead));

                LPOLESTR rglpostrNameCheck[3];
                Check(S_OK, pPropSet->
                      ReadPropertyNames(l, rgpropid, rglpostrNameCheck));

                for (int c=0; c<l; c++)
                {
                    ASSERT(ocscmp(rglpostrNameCheck[c], rglpostrName[c])==0);
                    CoTaskMemFree(rglpostrNameCheck[c]);
                }
                Check(S_OK, pPropSet->DeletePropertyNames(l, rgpropid));
                Check(S_FALSE, pPropSet->ReadPropertyNames(l, rgpropid, 
                                                           rglpostrNameCheck));
            }
            
            FreePropVariantArray(Actual, pVar);
            delete pVar;
        }
    }
    pPropSet->Release();
}

void
test_IPropertyStorage(IStorage *pStorage)
{
    test_IPropertyStorage_Access(pStorage);
    test_IPropertyStorage_Create(pStorage);
    test_IPropertyStorage_Stat(pStorage);
    test_IPropertyStorage_WriteMultiple(pStorage);
    test_IPropertyStorage_DeleteMultiple(pStorage);
}



//
//   Word6.0 summary information
//      Open
//      Read fields
//      Stat
//

#define W6TEST "w6test.doc"

void test_Word6(IStorage *pStorage)
{
    printf( "   Word 6.0 compatibility test\n" );

    extern unsigned char g_achTestDoc[];
    extern unsigned g_cbTestDoc;
    OLECHAR ocsTempFile[MAX_PATH+1];

    FILE *f = fopen(W6TEST, "w+b");
    int nWritten = fwrite(g_achTestDoc, 1, g_cbTestDoc, f);
    ASSERT(nWritten == (int)g_cbTestDoc);
    fclose(f);

    STOT(W6TEST, ocsTempFile, strlen(W6TEST)+1);
    IStorage *pStg;
    Check(S_OK, StgOpenStorage(ocsTempFile, 
                               (IStorage*)NULL,
                               (DWORD) STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 
                               0, 
                               0, 
                               &pStg));

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStg);
    IPropertyStorage *pPropStg;

    Check(S_OK, pPropSetStg->Open(FMTID_SummaryInformation,
                    STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READ,
                    &pPropStg));

#define WORDPROPS 18

    static struct tagWordTest {
        VARENUM vt;
        void *pv;
    } avt[WORDPROPS] = {
        VT_LPSTR, "Title of the document.",    // PID_TITLE                                 
        VT_LPSTR, "Subject of the document.",  // PID_SUBJECT                               
        VT_LPSTR, "Author of the document.",   // PID_AUTHOR                                
        VT_LPSTR, "Keywords of the document.", // PID_KEYWORDS                              
        VT_LPSTR, "Comments of the document.", // PID_COMMENTS                              
        VT_LPSTR, "Normal.dot",                // PID_TEMPLATE -- Normal.dot                
        VT_LPSTR, "Bill Morel",                // PID_LASTAUTHOR --                         
        VT_LPSTR, "3",                         // PID_REVNUMBER -- '3'                      
        VT_EMPTY, 0,                           // PID_EDITTIME -- 3 Minutes FILETIME        
        VT_EMPTY, 0,                           // PID_LASTPRINTED -- 04/07/95 12:04 FILETIME
        VT_EMPTY, 0,                           // PID_CREATE_DTM                            
        VT_EMPTY, 0,                           // PID_LASTSAVE_DTM                          
        VT_I4, (void*) 1,                      // PID_PAGECOUNT                             
        VT_I4, (void*) 7,                      // PID_WORDCOUNT                             
        VT_I4, (void*) 65,                     // PID_CHARCOUNT                             
        VT_EMPTY, 0,                           // PID_THUMBNAIL
        VT_LPSTR, "Microsoft Word 6.0",        // PID_APPNAME
        VT_I4, 0  };                           // PID_SECURITY

    PROPSPEC propspec[WORDPROPS+2];
    int i;
    for (i=2; i<WORDPROPS+2; i++)
    {
        propspec[i].ulKind = PRSPEC_PROPID;
        propspec[i].propid = (PROPID)i;
    }

    PROPVARIANT propvar[WORDPROPS+2];

    Check(S_OK, pPropStg->ReadMultiple(WORDPROPS, propspec+2, propvar+2));

    for (i=2; i<WORDPROPS+2; i++)
    {
        if ( propvar[i].vt != avt[i-2].vt )
        {
            printf( " PROPTEST: 0x%x retrieved type 0x%x, expected type 0x%x\n",
                    i, propvar[i].vt, avt[i-2].vt );
            ASSERT(propvar[i].vt == avt[i-2].vt);
        }

        switch (propvar[i].vt)
        {
        case VT_LPSTR:
            ASSERT(strcmp(propvar[i].pszVal, (char*)avt[i-2].pv)==0);
            break;
        case VT_I4:
            ASSERT(propvar[i].lVal == (int)avt[i-2].pv);
            break;
        }
    }
    FreePropVariantArray( WORDPROPS, propvar+2 );    
    pPropStg->Release();
    pStg->Release();

    //_unlink("w6test");
}

BOOL
IsEqualSTATPROPSTG(const STATPROPSTG *p1, const STATPROPSTG *p2)
{
    BOOL f1 = p1->propid == p2->propid;
    BOOL f2 = p1->vt == p2->vt;
    BOOL f3 = (p1->lpwstrName == NULL && p2->lpwstrName == NULL) ||
              ((p1->lpwstrName != NULL && p2->lpwstrName != NULL) &&
               ocscmp(p1->lpwstrName, p2->lpwstrName) == 0);
    return(f1 && f2 && f3);
}

void
test_IEnumSTATPROPSTG(IStorage *pstgTemp)
{
    printf( "   IEnumSTATPROPSTG\n" );

    PROPID apropid[8];
    LPOLESTR alpostrName[8];
    OLECHAR aosz[8][32];
    PROPID PropId=2;
    PROPSPEC ps[8];
    char pchTemp[32];

    FMTID fmtid;
    IPropertyStorage *pPropStg;

    TSafeStorage< IPropertySetStorage > pPropSetStg(pstgTemp);

    UuidCreate(&fmtid);

    for (int setup=0; setup<8; setup++)
    {
        alpostrName[setup] = &aosz[setup][0];
    }


    CGenProps gp;

    // simple/simple, ansi/wide, named/not named
    for (int outer=0; outer<8; outer++)
    {
        Check(S_OK, pPropSetStg->Create(fmtid, NULL,
            ((outer&2) ? PROPSETFLAG_ANSI : 0),
            STGM_CREATE | STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
            &pPropStg));


        for (int i=0; i<CPROPERTIES; i++)
        {
            apropid[i] = PropId++;
            if (outer & 1)
            {
                ps[i].ulKind = PRSPEC_LPWSTR;
                sprintf(pchTemp, "prop%d\0", apropid[i]);
                STOT(pchTemp, aosz[i], strlen(pchTemp)+1);
                ps[i].lpwstr = aosz[i];
            }
            else
            {
                ps[i].ulKind = PRSPEC_PROPID;
                ps[i].propid = apropid[i];
            }
        }

        if (outer & 1)
        {
            Check(S_OK, pPropStg->WritePropertyNames(CPROPERTIES, apropid, alpostrName));
        }
        
        PROPVARIANT *pVar = gp.GetNext(CPROPERTIES, NULL, TRUE, TRUE); 
        ASSERT(pVar != NULL);

        Check(S_OK, pPropStg->WriteMultiple(CPROPERTIES, ps, pVar, 1000));
        FreePropVariantArray(CPROPERTIES, pVar);
        delete pVar;

        // Allocate enough STATPROPSTGs for one more than the actual properties
        // in the set.

        STATPROPSTG StatBuffer[CPROPERTIES+1];
        ULONG celt;
        IEnumSTATPROPSTG *penum, *penum2;
    
        Check(S_OK, pPropStg->Enum(&penum));
    
        IUnknown *punk, *punk2;
        IEnumSTATPROPSTG *penum3;
        Check(S_OK, penum->QueryInterface(IID_IUnknown, (void**)&punk));
        Check(S_OK, punk->QueryInterface(IID_IEnumSTATPROPSTG, (void**)&penum3));
        Check(S_OK, penum->QueryInterface(IID_IEnumSTATPROPSTG, (void**)&punk2));
        ASSERT(punk == punk2);
        punk->Release();
        penum3->Release();
        punk2->Release();
    
        // test S_FALSE
        Check(S_FALSE, penum->Next( CPROPERTIES+1, StatBuffer, &celt));
        ASSERT(celt == CPROPERTIES);

        CleanStat(celt, StatBuffer);

        penum->Reset();
    
    
        // test reading half out, then cloning, then comparing
        // rest of enumeration with other clone.

        Check(S_OK, penum->Next(CPROPERTIES/2, StatBuffer, &celt));
        ASSERT(celt == CPROPERTIES/2);
        CleanStat(celt, StatBuffer);
        celt = 0;
        Check(S_OK, penum->Clone(&penum2));
        Check(S_OK, penum->Next(CPROPERTIES - CPROPERTIES/2, StatBuffer, &celt));
        ASSERT(celt == CPROPERTIES - CPROPERTIES/2);
        // check the clone
        for (int c=0; c<CPROPERTIES - CPROPERTIES/2; c++)
        {
            STATPROPSTG CloneStat;
            Check(S_OK, penum2->Next(1, &CloneStat, NULL));
            ASSERT(IsEqualSTATPROPSTG(&CloneStat, StatBuffer+c));
            CleanStat(1, &CloneStat);
        }
    
        CleanStat(celt, StatBuffer);

        // check both empty
        celt = 0;
        Check(S_FALSE, penum->Next(1, StatBuffer, &celt));
        ASSERT(celt == 0);
    
        Check(S_FALSE, penum2->Next(1, StatBuffer, &celt));
        ASSERT(celt == 0);
    
        penum->Reset();
    
        //
        // loop deleting one property at a time
        // enumerate the propertys checking that correct ones appear.
        //
        for (ULONG d = 0; d<CPROPERTIES; d++)
        {
            // d is for delete
    
            BOOL afFound[CPROPERTIES];
            ULONG cTotal = 0;

            Check(S_OK, penum->Next(CPROPERTIES-d, StatBuffer, &celt));
            ASSERT(celt == CPROPERTIES-d);
            penum->Reset();
        
            memset(afFound, 0, sizeof(afFound));

            for (ULONG iProperty=0; iProperty<CPROPERTIES; iProperty++)
            {

                // Search the StatBuffer for this property.

                for (ULONG iSearch=0; iSearch<CPROPERTIES-d; iSearch++)
                {

                    // Compare this entry in the StatBuffer to the property for which we're searching.
                    // Use the lpstrName or propid, whichever is appropriate for this pass (indicated
                    // by 'outer').

                    if ( ( (outer & 1) == 1 && 0 == ocscmp(StatBuffer[iSearch].lpwstrName, ps[iProperty].lpwstr) )
                         ||
                         ( (outer & 1) == 0 && StatBuffer[iSearch].propid == apropid[iProperty] )
                       )
                    {
                        ASSERT (!afFound[iSearch]);
                        afFound[iSearch] = TRUE;
                        cTotal++;
                        break;
                    }
                }
            }

            CleanStat(celt, StatBuffer);

            ASSERT(cTotal == CPROPERTIES-d);

            Check(S_OK, pPropStg->DeleteMultiple(1, ps+d));
            Check(S_OK, penum->Reset());
        }
    
        penum->Release();
        penum2->Release();

        pPropStg->Release();

    }
}

void
test_MaxPropertyName(IStorage *pstgTemp)
{

    printf( "   Max Property Name length\n" );

    //  ----------
    //  Initialize
    //  ----------

    CPropVariant cpropvar;

    // Create a new storage, because we're going to create
    // well-known property sets, and this way we can be sure
    // that they don't already exist.

    TSafeStorage< IStorage > pstg;
    DECLARE_OLESTR(ocsMaxProp, "MaxPropNameTest");
    Check(S_OK, pstgTemp->CreateStorage( ocsMaxProp,
                                         STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                         0L, 0L,
                                         &pstg ));

    // Generate a new Format ID.

    FMTID fmtid;
    UuidCreate(&fmtid);

    // Get a IPropertySetStorage from the IStorage.

    TSafeStorage< IPropertySetStorage > pPropSetStg(pstg);
    TSafeStorage< IPropertyStorage > pPropStg;

    //  ----------------------------------
    //  Test the non-SumInfo property set.
    //  ----------------------------------

    // Create a new PropertyStorage.

    Check(S_OK, pPropSetStg->Create(fmtid,
            NULL,
            PROPSETFLAG_DEFAULT,
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStg));

    // Generate a property name which is max+1 characters.
    OLECHAR *poszPropertyName;
    poszPropertyName = (OLECHAR*) 
        CoTaskMemAlloc( (CCH_MAXPROPNAMESZ+1) * sizeof(OLECHAR) );
    Check(TRUE, poszPropertyName != NULL );

    for( ULONG ulIndex = 0; ulIndex < CCH_MAXPROPNAMESZ; ulIndex++ )
        poszPropertyName[ ulIndex ] = (OLECHAR)'a' + (OLECHAR) ( ulIndex % 26 );
    poszPropertyName[ CCH_MAXPROPNAMESZ ] = (OLECHAR)0;  // terminating null


    // Write out a property with this max+1 name.

    PROPSPEC propspec;

    propspec.ulKind = PRSPEC_LPWSTR;
    propspec.lpwstr = poszPropertyName;

    cpropvar = (long) 0x1234;

    Check(STG_E_INVALIDPARAMETER, pPropStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Write out a property with a max-character name (we create a max-
    // char name by terminating the previously-used string one character
    // earlier).

    poszPropertyName[ CWC_MAXPROPNAME ] = 0;
    Check(S_OK, pPropStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Write out a property with a minimum-character name.
    DECLARE_OLESTR(ocsX, "X");
    propspec.lpwstr = ocsX;
    Check(S_OK, pPropStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Write out a property with a below-minimum-character name.
    DECLARE_OLESTR(ocsEmpty, ""); 
    propspec.lpwstr = ocsEmpty;
    Check(STG_E_INVALIDPARAMETER, 
          pPropStg->WriteMultiple( 1, &propspec,
                                   cpropvar,
                                   PID_FIRST_USABLE));
    CoTaskMemFree( poszPropertyName );
}

#define CODEPAGE_TEST_NAMED_PROPERTY     "Named Property"
#define CODEPAGE_TEST_UNNAMED_BSTR_PROPID   3
#define CODEPAGE_TEST_UNNAMED_I4_PROPID     4
#define CODEPAGE_TEST_VBSTR_PROPID          7
#define CODEPAGE_TEST_VPROPVAR_BSTR_PROPID  9

void
CreateCodePageTestFile( LPOLESTR poszFileName, 
                        IStorage **ppStg,
                        BOOL fUseUnicode)
{
    ASSERT( poszFileName != NULL );

    //  --------------
    //  Initialization
    //  --------------

    TSafeStorage< IPropertySetStorage > pPSStg;
    TSafeStorage< IPropertyStorage > pPStg;

    PROPSPEC propspec;
    CPropVariant cpropvar;

    *ppStg = NULL;

    OLECHAR poszActualFile[MAX_PATH];
    ocscpy(poszActualFile, poszFileName);

    // assume the path name has enough space, change the last character
    // of the filename
    int len = ocslen(poszActualFile);
    if (!fUseUnicode) 
    {
        
        poszActualFile[len]='a';
        poszActualFile[len+1]=0;
    }
    else
    {
        poszActualFile[len]='w';
        poszActualFile[len+1]=0;
    }

    Check(S_OK, StgCreateDocfile( poszActualFile,
                                  STGM_CREATE | STGM_READWRITE |
                                  STGM_SHARE_EXCLUSIVE,  
                                  0,
                                  ppStg ));

    // Get an IPropertySetStorage

    Check(S_OK, (*ppStg)->QueryInterface( IID_IPropertySetStorage, (void**)&pPSStg ));

    // Create an IPropertyStorage (ANSI or UNICODE)  
    DWORD psFlag= (fUseUnicode) ? 0 : PROPSETFLAG_ANSI ;

    Check(S_OK, pPSStg->Create( FMTID_NULL,
                                NULL,
                                psFlag,
                                STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                &pPStg ));

    //  ----------------------
    //  Write a named property
     //  ----------------------

    // Write a named I4 property

    propspec.ulKind = PRSPEC_LPWSTR;
    DECLARE_OLESTR(ocsNamed,  CODEPAGE_TEST_NAMED_PROPERTY);
    propspec.lpwstr = ocsNamed;

    cpropvar = (LONG) 0x12345678;
    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    //  --------------------------
    //  Write singleton properties
    //  --------------------------

    // Write an un-named BSTR.

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_UNNAMED_BSTR_PROPID;
    DECLARE_OLESTR(ocsBSTR, "BSTR Property");
    cpropvar.SetBSTR( ocsBSTR );
    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    // Write an un-named I4

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_UNNAMED_I4_PROPID;

    cpropvar = (LONG) 0x76543210;
    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    //  -----------------------
    //  Write vector properties
    //  -----------------------

    // Write a vector of BSTRs.

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_VBSTR_PROPID;
    DECLARE_OLESTR(ocsElt0, "BSTR Element 0");
    DECLARE_OLESTR(ocsElt1, "BSTR Element 1");
    
    cpropvar.SetBSTR( ocsElt1, 1 );
    cpropvar.SetBSTR( ocsElt0, 0 );
    ASSERT( (VT_VECTOR | VT_BSTR) == cpropvar.VarType() );
    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

    //  -------------------------------
    //  Write Variant Vector Properties
    //  -------------------------------

    // Write a variant vector that has a BSTR

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_VPROPVAR_BSTR_PROPID;

    CPropVariant cpropvarT;
    DECLARE_OLESTR(ocsPropVect, "PropVar Vector BSTR");
    cpropvarT.SetBSTR( ocsPropVect );
    cpropvar[1] = (LPPROPVARIANT) cpropvarT;
    cpropvar[0] = (LPPROPVARIANT) CPropVariant((long) 44);
    ASSERT( (VT_VARIANT | VT_VECTOR) == cpropvar.VarType() );
    Check(S_OK, pPStg->WriteMultiple( 1, &propspec, cpropvar, PID_FIRST_USABLE ));

}   // CreateCodePageTestFile()


void
OpenCodePageTestFile( LPOLESTR poszFileName, 
                      IStorage **ppStg,
                      BOOL fUseUnicode)
{
    OLECHAR poszActualFile[MAX_PATH];
    ocscpy(poszActualFile, poszFileName);

    // assume the path name has enough space, change the last character
    // of the filename
    int len = ocslen(poszActualFile);
    if (!fUseUnicode) 
    {
        poszActualFile[len]='a';
        poszActualFile[len+1]=0;
    }
    else
    {
        poszActualFile[len]='w';
        poszActualFile[len+1]=0;
    }    

    Check(S_OK, StgOpenStorage( poszActualFile,
                                (IStorage*) NULL,
                                STGM_READWRITE | STGM_SHARE_EXCLUSIVE,  
                                (SNB) 0,
                                (DWORD) 0,
                                ppStg ));
}

void
ModifyPropSetCodePage( IStorage *pStg, ULONG ulCodePage )
{

    ASSERT( pStg != NULL );

    //  --------------
    //  Initialization
    //  --------------

    OLECHAR aocPropSetName[ 32 ];
    DWORD dwOffset = 0;
    DWORD dwcbSection = 0;
    DWORD dwcProperties = 0;
    ULONG ulcbWritten = 0;

    LARGE_INTEGER   liSectionOffset, liCodePageOffset;

    TSafeStorage< IStream > pStm;

    CPropVariant cpropvar;

    // Open the Stream

    RtlGuidToPropertySetName( &FMTID_NULL, aocPropSetName );
    Check(S_OK, pStg->OpenStream( aocPropSetName,
                                  (VOID*)NULL,
                                  STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 
                                  (DWORD)NULL,
                                  &pStm ));

    // Seek past the propset header and the format ID.

    liSectionOffset.HighPart = 0;
    liSectionOffset.LowPart = sizeof(PROPERTYSETHEADER) + sizeof(FMTID);
    Check(S_OK, pStm->Seek(liSectionOffset, STREAM_SEEK_SET, NULL ));

    // Move to the beginning of the property set.

    liSectionOffset.HighPart = 0;
    Check(S_OK, pStm->Read( &liSectionOffset.LowPart, sizeof(DWORD), NULL ));
    PropByteSwap(&liSectionOffset.LowPart);

    Check(S_OK, pStm->Seek( liSectionOffset, STREAM_SEEK_SET, NULL ));

    // Get the section size & property count.

    Check(S_OK, pStm->Read( &dwcbSection, sizeof(DWORD), NULL ));
    PropByteSwap( &dwcbSection );

    Check(S_OK, pStm->Read( &dwcProperties, sizeof(DWORD), NULL ));
    PropByteSwap( &dwcProperties );

    // Scan for the PID_CODEPAGE property.
    ULONG ulIndex; 
    for(ulIndex = 0; ulIndex < dwcProperties; ulIndex++ )
    {
        PROPID propid;
        DWORD dwOffset;

        // Read in the PROPID
        Check(S_OK, pStm->Read( &propid, sizeof(PROPID), NULL ));

        // Is it the codepage?
        if( PropByteSwap(propid) == PID_CODEPAGE )
            break;

        // Read in this PROPIDs offset (we don't need it, but we want
        // to seek past it.
        Check(S_OK, pStm->Read( &dwOffset, sizeof(dwOffset), NULL ));
    }

    // Verify that the above for loop terminated because we found
    // the codepage.
    Check( TRUE, ulIndex < dwcProperties );

    // Move to the code page.

    liCodePageOffset.HighPart = 0;
    Check(S_OK, pStm->Read( &liCodePageOffset.LowPart, sizeof(DWORD), NULL ));
    PropByteSwap( &liCodePageOffset.LowPart );

    liCodePageOffset.LowPart += liSectionOffset.LowPart + sizeof(ULONG); // Move past VT too.
    ASSERT( liSectionOffset.HighPart == 0 );

    Check(S_OK, pStm->Seek( liCodePageOffset, STREAM_SEEK_SET, NULL ));

    // this is so that you can manually verify what was read
    // i.e. that it is the code page
    WORD wCodePage;                                          
    Check(S_OK, pStm->Read( &wCodePage, sizeof(WORD), NULL));
    Check(S_OK, pStm->Seek( liCodePageOffset, STREAM_SEEK_SET, NULL )); 
    
    // Write the new code page.
    wCodePage = PropByteSwap( (WORD) ((ulCodePage << 16) >> 16) );
    Check(S_OK, pStm->Write( &wCodePage, sizeof(wCodePage), &ulcbWritten ));
    Check(TRUE, ulcbWritten == sizeof(wCodePage) );

    //pStm->Commit(0);   
}   // ModifyPropSetCodePage()

void
ModifyOSVersion( IStorage* pStg, DWORD dwOSVersion )
{

    ASSERT( pStg != NULL );

    //  --------------
    //  Initialization
    //  --------------

    OLECHAR aocPropSetName[ 32 ];
    ULONG ulcbWritten = 0;

    LARGE_INTEGER   liOffset;
    PROPERTYSETHEADER propsetheader;
    TSafeStorage< IStream > pStm;

    // Open the Stream

    RtlGuidToPropertySetName( &FMTID_NULL, aocPropSetName );
    Check(S_OK, pStg->OpenStream( aocPropSetName,
                                  (VOID*)NULL,
                                  STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 
                                  (DWORD)NULL,
                                  &pStm ));


    // Seek to the OS Version field in the header.

    liOffset.HighPart = 0;
    propsetheader;  // avoid compiler warning of unref'd var.
    liOffset.LowPart = sizeof(propsetheader.wByteOrder) + sizeof(propsetheader.wFormat);
    Check(S_OK, pStm->Seek( liOffset, STREAM_SEEK_SET, NULL ));

    // Set the new OS Version
    PropByteSwap( &dwOSVersion );
    Check(S_OK, pStm->Write( &dwOSVersion, sizeof(dwOSVersion), &ulcbWritten ));
    Check(TRUE, ulcbWritten == sizeof(dwOSVersion) );

}   // ModifyOSVersion()


#define CODEPAGE_DEFAULT    0x04e4  // US English
#define CODEPAGE_GOOD       0x0000  // Present on a US English machine
#define CODEPAGE_BAD        0x9999  // Non-existent code page

void
test_CodePages( LPOLESTR poszDirectory, 
                ULONG ulTestOptions, 
                BOOL fTestUnicode)
{

    printf( "   Code Page compatibility -- " );

    if (ulTestOptions & TEST_INTEROP_R)
        printf("Verify Read, ");
    else 
        printf("Write & Read, ");
    if (fTestUnicode)
        printf("UNICODE files\n");
    else
        printf("ASCII files\n");

    //  --------------
    //  Initialization
    //  --------------

    OLECHAR oszBadFile[ MAX_PATH ];
    OLECHAR oszGoodFile[ MAX_PATH ];
    OLECHAR oszUnicodeFile[ MAX_PATH ];
    OLECHAR oszMacFile[ MAX_PATH ];
    HRESULT hr = S_OK;

    TSafeStorage< IStorage > pStgBad, pStgGood, pStgUnicode, pStgMac;
    CPropVariant cpropvarWrite, cpropvarRead;

    Check( TRUE, GetACP() == CODEPAGE_DEFAULT );
    
    //  ------------------------------
    //  Create test property sets
    //  ------------------------------

    // Create a property set with a bad codepage.
#ifdef _WIN32
    DECLARE_OLESTR(ocsBad, "\\badcp.sg");
    ocscpy( oszBadFile, poszDirectory );    
    ocscat( oszBadFile, ocsBad );
#else
    DECLARE_OLESTR(ocsBad, "badcp.sg");
    ocscpy( oszBadFile, ocsBad );
#endif
    if (! (ulTestOptions & TEST_INTEROP_R))
    {
        CreateCodePageTestFile( oszBadFile, &pStgBad, fTestUnicode );
        if (!fTestUnicode)
        {                       
            // modification of code page is only
            // interesting for ansi property sets,
            // otherwise it will result in in error
            ModifyPropSetCodePage( pStgBad, CODEPAGE_BAD );
        }
    }
    else 
        OpenCodePageTestFile( oszBadFile, &pStgBad, fTestUnicode );

    // Create a property set with a good codepage.

#ifdef _WIN32
    ocscpy( oszGoodFile, poszDirectory );
    DECLARE_OLESTR(ocsGood, "\\goodcp.sg");
    ocscat( oszGoodFile, ocsGood );
#else
    DECLARE_OLESTR(ocsGood, "goodcp.sg");
    ocscpy( oszGoodFile, ocsGood );
#endif

    if (! (ulTestOptions & TEST_INTEROP_R))
    {
        CreateCodePageTestFile( oszGoodFile, &pStgGood, fTestUnicode );
        // We can only modify code page of ansi property sets; Modfiying 
        // code page of UNICODE pages will result in a INVALID_HEADER
        // 'cos it will treat strings as wide chars instead of single-byte
        if (!fTestUnicode)
        {
            ModifyPropSetCodePage( pStgGood, CODEPAGE_GOOD );
        }
    }
    else 
        OpenCodePageTestFile( oszGoodFile, &pStgGood, fTestUnicode );

    // Create a property set that has the OS Kind (in the
    // header) set to "Mac".

#ifdef _WIN32
    DECLARE_OLESTR(ocsMac, "\\mackind.sg");
    ocscpy( oszMacFile, poszDirectory );
    ocscat( oszMacFile, ocsMac );
#else
    DECLARE_OLESTR(ocsMac, "mackind.sg");
    ocscpy( oszMacFile, ocsMac );
#endif
    
    if (! (ulTestOptions & TEST_INTEROP_R))
    {
        CreateCodePageTestFile( oszMacFile, &pStgMac, fTestUnicode );        
        ModifyOSVersion( pStgMac, 0x00010904 );
    }
    else
        OpenCodePageTestFile( oszMacFile, &pStgMac, fTestUnicode );

    //  ---------------------------
    //  Open the Ansi property sets
    //  ---------------------------

    TSafeStorage< IPropertySetStorage > pPropSetStgBad(pStgBad);
    TSafeStorage< IPropertySetStorage > pPropSetStgGood(pStgGood);
    TSafeStorage< IPropertySetStorage > pPropSetStgMac(pStgMac);

    TSafeStorage< IPropertyStorage > pPropStgBad, pPropStgGood, pPropStgMac;

    Check(S_OK, pPropSetStgBad->Open(FMTID_NULL,
            STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStgBad));

    Check(S_OK, pPropSetStgGood->Open(FMTID_NULL,
            STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStgGood));

    Check(S_OK, pPropSetStgMac->Open(FMTID_NULL,
            STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStgMac));
    
    PROPSPEC propspec;
    PROPVARIANT propvar;
    PropVariantInit( &propvar );

    // ASSUMPTION: all three files are created all together by
    //    proptest, they will all be ansi or they will all be
    //    unicode.
    //
    // For UNICODE APIs, when the code page is not Unicode, it will
    // try and translate it to unicode and thus result in an error

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = 1;        // propid for code page indicator
    Check(S_OK, pPropStgGood->ReadMultiple( 1, &propspec, &propvar));

    HRESULT pStgBadHr = S_OK;   // default
    if (propvar.iVal == (SHORT) CODEPAGE_GOOD) 
    {                           // files created in Ansi
#ifdef _UNICODE
        pStgBadHr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION);
#endif
    }

    // Since we are doing interoperability testing and pStgBad with
    // a wrong codepage number will result in a invalid_header error,
    // we change it back to normal to get rid of the error.
    //
    // Also, we only change it when the current code page is bad,
    // otherwise, we leave it as it is.
    //
    // We know that the correct number to put in is 1252 'cos we only
    // change the code page for ansi property sets.
    if (propvar.iVal == (SHORT) CODEPAGE_BAD)
        ModifyPropSetCodePage( pStgBad, 1252 );  

    //  ------------------------------------------
    //  Test BSTRs in the three property sets
    //  ------------------------------------------
    // Attempt to read by name.

    propspec.ulKind = PRSPEC_LPWSTR;
    DECLARE_OLESTR(ocsCP_test, CODEPAGE_TEST_NAMED_PROPERTY);
    propspec.lpwstr = ocsCP_test;

    CPropVariant cpropvar=(LONG) 0x12345678;

    Check(S_OK,
          pPropStgGood->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));
    PropVariantClear( &propvar );

    Check(pStgBadHr, 
          pPropStgBad->ReadMultiple( 1, &propspec, &propvar ));    
    PropVariantClear( &propvar );

    Check(S_OK, 
          pPropStgMac->ReadMultiple( 1, &propspec, &propvar ));    
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));
    

    // Attempt to write by name.

    Check(S_OK,
          pPropStgMac->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(pStgBadHr,
          pPropStgBad->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(S_OK,
          pPropStgGood->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    PropVariantClear( &propvar );

    // Attempt to read the BSTR property

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_UNNAMED_BSTR_PROPID;

    DECLARE_OLESTR(ocsBSTR, "BSTR Property");
    cpropvar.SetBSTR( ocsBSTR );
    Check(S_OK, 
          pPropStgMac->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    PropVariantClear( &propvar );
    Check(pStgBadHr, 
          pPropStgBad->ReadMultiple( 1, &propspec, &propvar ));
    PropVariantClear( &propvar );
    Check(S_OK,
          pPropStgGood->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    // Attempt to write the BSTR property

    Check(S_OK,
          pPropStgMac->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(pStgBadHr,
          pPropStgBad->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(S_OK,
          pPropStgGood->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    PropVariantClear( &propvar );

    // Attempt to read the BSTR Vector property

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_VBSTR_PROPID;
    DECLARE_OLESTR(ocsElt0, "BSTR Element 0");
    DECLARE_OLESTR(ocsElt1, "BSTR Element 1");    
    cpropvar.SetBSTR( ocsElt1, 1 );
    cpropvar.SetBSTR( ocsElt0, 0 );
    ASSERT( (VT_VECTOR | VT_BSTR) == cpropvar.VarType() );

    Check(S_OK, 
          pPropStgMac->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    PropVariantClear( &propvar );
    Check(pStgBadHr, 
          pPropStgBad->ReadMultiple( 1, &propspec, &propvar ));
    
    PropVariantClear(&propvar);
    Check(S_OK,
          pPropStgGood->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    // Attempt to write the BSTR Vector property

    Check(S_OK,
          pPropStgMac->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(pStgBadHr,
          pPropStgBad->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(S_OK,
          pPropStgGood->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    PropVariantClear( &propvar );
    
    // Attempt to read the Variant Vector which has a BSTR
    CPropVariant cpropvarT;
    DECLARE_OLESTR(ocsPropVect, "PropVar Vector BSTR");
    cpropvarT.SetBSTR( ocsPropVect );
    cpropvar[1] = (LPPROPVARIANT) cpropvarT;
    cpropvar[0] = (LPPROPVARIANT) CPropVariant((long) 44);
    ASSERT( (VT_VARIANT | VT_VECTOR) == cpropvar.VarType() );

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_VPROPVAR_BSTR_PROPID;

    Check(S_OK, 
          pPropStgMac->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));
    PropVariantClear( &propvar );
    Check(pStgBadHr, 
          pPropStgBad->ReadMultiple( 1, &propspec, &propvar ));
    
    PropVariantClear(&propvar);
    Check(S_OK,
          pPropStgGood->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    // Attempt to write the Variant Vector which has a BSTR

    Check(S_OK,
          pPropStgMac->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(pStgBadHr,
          pPropStgBad->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    Check(S_OK,
          pPropStgGood->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    PropVariantClear( &propvar );

    // Attempt to read the I4 property.

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = CODEPAGE_TEST_UNNAMED_I4_PROPID;
    Check(S_OK, 
          pPropStgMac->ReadMultiple( 1, &propspec, &propvar ));
    cpropvar = (LONG) 0x76543210;
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));
    PropVariantClear( &propvar );

    hr = pPropStgBad->ReadMultiple( 1, &propspec, &propvar );
    Check(TRUE, S_OK == hr || pStgBadHr == hr );
    PropVariantClear( &propvar );

    Check(S_OK,
          pPropStgGood->ReadMultiple( 1, &propspec, &propvar ));
    Check(S_OK, CPropVariant::Compare( &propvar, &cpropvar ));

    // Attempt to write the I4 property

    Check(S_OK,
          pPropStgMac->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));

    hr = pPropStgBad->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE );
    Check(TRUE, S_OK == hr || pStgBadHr == hr );

    Check(S_OK,
          pPropStgGood->WriteMultiple( 1, &propspec, &propvar, PID_FIRST_USABLE ));
    PropVariantClear( &propvar );

    //  ---------------------------------------
    //  Test LPSTRs in the Unicode property set
    //  ---------------------------------------

    // This test doesn't verify that the LPSTRs are actually
    // written in Unicode.  A manual test is required for that.

    // Create a Unicode property set.  We'll make it
    // non-simple so that we can test a VT_STREAM (which
    // is stored like an LPSTR).
#ifdef _WIN32
    DECLARE_OLESTR(ocsUni, "\\UnicodCP.stg");
    ocscpy( oszUnicodeFile, poszDirectory );
    ocscat( oszUnicodeFile, ocsUni);
#else
    DECLARE_OLESTR(ocsUni, "UnicodCP.stg");
    ocscpy( oszUnicodeFile, ocsUni );
#endif

    Check(S_OK, StgCreateDocfile(oszUnicodeFile,
                                 STGM_CREATE | STGM_READWRITE |
                                 STGM_SHARE_EXCLUSIVE, 
                                 (DWORD)NULL,
                                 &pStgUnicode));

    TSafeStorage< IPropertySetStorage > pPropSetStgUnicode(pStgUnicode);
    TSafeStorage< IPropertyStorage > pPropStgUnicode;
    Check(S_OK, pPropSetStgUnicode->Create(FMTID_NULL,
                                           &CLSID_NULL,
                                           PROPSETFLAG_DEFAULT,
                                           STGM_CREATE | STGM_SHARE_EXCLUSIVE
                                           | STGM_READWRITE, 
                                           &pPropStgUnicode));


    // Write/verify an LPSTR property.

    propspec.ulKind = PRSPEC_LPWSTR;
    DECLARE_OLESTR(ocsLPSTR, "LPSTR Property");
    propspec.lpwstr = ocsLPSTR;

    cpropvarWrite = "An LPSTR Property";

    Check(S_OK, pPropStgUnicode->WriteMultiple( 1, &propspec, cpropvarWrite, PID_FIRST_USABLE ));
    Check(S_OK, pPropStgUnicode->ReadMultiple( 1, &propspec, cpropvarRead ));

    Check(0, strcmp( (LPSTR) cpropvarWrite, (LPSTR) cpropvarRead ));
    cpropvarRead.Clear();

    // Write/verify a vector of LPSTR properties
    DECLARE_OLESTR(ocsVectLPSTR, "Vector of LPSTR properties"); 
    propspec.lpwstr = ocsVectLPSTR;

    cpropvarWrite[1] = "LPSTR Property #1";
    cpropvarWrite[0] = "LPSTR Property #0";

    Check(S_OK, pPropStgUnicode->WriteMultiple( 1, &propspec, cpropvarWrite, PID_FIRST_USABLE ));
    Check(S_OK, pPropStgUnicode->ReadMultiple( 1, &propspec, cpropvarRead ));

    Check(0, strcmp( (LPSTR) cpropvarWrite[1], (LPSTR) cpropvarRead[1] ));
    Check(0, strcmp( (LPSTR) cpropvarWrite[0], (LPSTR) cpropvarRead[0] ));
    cpropvarRead.Clear();

    // Write/verify a vector of variants which has an LPSTR
    DECLARE_OLESTR(ocsVariantWithLPSTR, "Variant Vector with an LPSTR");
    propspec.lpwstr = ocsVariantWithLPSTR;

    cpropvarWrite[1] = (LPPROPVARIANT) CPropVariant("LPSTR in a Variant Vector");
    cpropvarWrite[0] = (LPPROPVARIANT) CPropVariant((long) 22); // an I4
    ASSERT( (VT_VECTOR | VT_VARIANT) == cpropvarWrite.VarType() );

    Check(S_OK, pPropStgUnicode->WriteMultiple( 1, &propspec, cpropvarWrite, PID_FIRST_USABLE ));
    Check(S_OK, pPropStgUnicode->ReadMultiple( 1, &propspec, cpropvarRead ));

    Check(0, strcmp( (LPSTR) cpropvarWrite[1], (LPSTR) cpropvarRead[1] ));
    cpropvarRead.Clear();

}

void
test_PropertyInterfaces(IStorage *pstgTemp)
{
    // this test depends on being first for enumerator
    test_IEnumSTATPROPSETSTG(pstgTemp);

    test_MaxPropertyName(pstgTemp);
    test_IPropertyStorage(pstgTemp);
    test_IPropertySetStorage(pstgTemp);
    test_IEnumSTATPROPSTG(pstgTemp);
}


//===================================================================
//
//  Function:   test_CopyTo
//
//  Synopsis:   Verify that IStorage::CopyTo copies an
//              un-flushed property set.
//
//              This test creates and writes to a simple property set,
//              a non-simple property set, and a new Storage & Stream,
//              all within the source (caller-provided) Storage.
//
//              It then copies the entire source Storage to the
//              destination Storage, and verifies that all commited
//              data in the Source is also in the destination.
//  
//              All new Storages and property sets are created
//              under a new base storage.  The caller can specify
//              if this base Storage is direct or transacted, and
//              can specify if the property sets are direct or
//              transacted.
//
//===================================================================

void test_CopyTo(IStorage *pstgSource,          // Source of the CopyTo
                 IStorage *pstgDestination,     // Destination of the CopyTo
                 ULONG ulBaseStgTransaction,    // Transaction bit for the base storage.
                 ULONG ulPropSetTransaction,    // Transaction bit for the property sets.
                 LPOLESTR oszBaseStorageName )
{
    printf( "   IStorage::CopyTo (Base Storage is %s, PropSets are %s)\n",
            ulBaseStgTransaction & STGM_TRANSACTED ? "transacted" : "directed",
            ulPropSetTransaction & STGM_TRANSACTED ? "transacted" : "directed" );


    //  ---------------
    //  Local Variables
    //  ---------------
    
    DECLARE_OLESTR(poszTestSubStorage     ,"TestStorage" );
    DECLARE_OLESTR(poszTestSubStream      ,"TestStream" );
    DECLARE_OLESTR(poszTestDataPreCommit  ,"Test Data (pre-commit)" );
    DECLARE_OLESTR(poszTestDataPostCommit ,"Test Data (post-commit)");
    
    long lSimplePreCommit = 0x0123;
    long lSimplePostCommit = 0x4567;

    long lNonSimplePreCommit  = 0x89AB;
    long lNonSimplePostCommit = 0xCDEF;

    BYTE acReadBuffer[ 80 ];
    ULONG cbRead;

    FMTID fmtidSimple, fmtidNonSimple;

    // Base Storages for the Source & Destination.  All
    // new Streams/Storages/PropSets will be created below here.

    TSafeStorage< IStorage > pstgBaseSource;
    TSafeStorage< IStorage > pstgBaseDestination;

    TSafeStorage< IStorage > pstgSub;   // A sub-storage of the base.
    TSafeStorage< IStream >  pstmSub;   // A Stream in the sub-storage (pstgSub)

    PROPSPEC propspec;
    PROPVARIANT propvarSourceSimple,
                propvarDestination;


    //  -----
    //  Begin
    //  -----

    // Create new format IDs

    UuidCreate(&fmtidSimple);
    UuidCreate(&fmtidNonSimple);

    //  -----------------------
    //  Create the base Storage
    //  -----------------------

    // Create a base Storage for the Source.  All of this test will be under
    // that Storage.

    // In the source Storage.

    Check( S_OK, pstgSource->CreateStorage(
                                oszBaseStorageName,
                                STGM_CREATE | ulBaseStgTransaction | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
                                0L, 0L,
                                &pstgBaseSource ));


    // And in the destination Storage.

    Check( S_OK, pstgDestination->CreateStorage(
                                oszBaseStorageName,
                                STGM_CREATE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
                                0L, 0L,
                                &pstgBaseDestination ));



    //  -------------------------------------------
    //  Write data to a new Stream in a new Storage
    //  -------------------------------------------

    // We'll partially verify the CopyTo by checking that this data
    // makes it into the destination Storage.


    // Create a Storage, and then a Stream within it.

    Check( S_OK, pstgBaseSource->CreateStorage(
                                poszTestSubStorage,
                                STGM_CREATE | ulPropSetTransaction | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
                                0L, 0L,
                                &pstgSub ));

    Check( S_OK, pstgSub->CreateStream(
                            poszTestSubStream,
                            STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                            0L, 0L,
                            &pstmSub ));

    // Write data to the Stream.

    Check( S_OK, pstmSub->Write(
                    poszTestDataPreCommit,
                    ( sizeof(OLECHAR)
                      *
                      ( ocslen(poszTestDataPreCommit) + sizeof( OLECHAR ))
                    ),
                    NULL ));


    //  ---------------------------------------------------------
    //  Write to a new simple property set in the Source storage.
    //  ---------------------------------------------------------

    TSafeStorage< IPropertySetStorage > pPropSetStgSource(pstgBaseSource);
    TSafeStorage< IPropertyStorage >    pPropStgSource1,
                                        pPropStgSource2,
                                        pPropStgDestination;

    // Create a property set mode.

    Check(S_OK, pPropSetStgSource->Create(fmtidSimple,
            NULL,
            PROPSETFLAG_DEFAULT,
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
            &pPropStgSource1));

    // Write the property set name (just to test this functionality).

    PROPID pidDictionary = 0;
    DECLARE_CONST_OLESTR(cposzPropSetName, "Property Set for CopyTo Test");
    ASSERT( CWC_MAXPROPNAMESZ >= ocslen(cposzPropSetName) + sizeof(OLECHAR) );
    
    Check(S_OK, pPropStgSource1->WritePropertyNames( 1, &pidDictionary,
                                                     &cposzPropSetName )); 

    // Create a PROPSPEC.  We'll use this throughout the rest of the routine.

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = 1000;

    // Create a PROPVARIANT for this test of the Simple case.

    propvarSourceSimple.vt = VT_I4;
    propvarSourceSimple.lVal = lSimplePreCommit;

    // Write the PROPVARIANT to the property set.

    Check(S_OK, pPropStgSource1->WriteMultiple(1, &propspec, &propvarSourceSimple, 2));



    //  -------------------------
    //  Commit everything so far.
    //  -------------------------

    // Commit the sub-Storage.
    Check(S_OK, pstgSub->Commit( STGC_DEFAULT ));

    // Commit the simple property set.
    Check(S_OK, pPropStgSource1->Commit( STGC_DEFAULT ));

    // Commit the base Storage which holds all of the above.
    Check(S_OK, pstgBaseSource->Commit( STGC_DEFAULT ));


    //  -------------------------------------------------
    //  Write new data to everything but don't commit it.
    //  -------------------------------------------------

    // Write to the sub-storage.
    Check(S_OK, pstmSub->Seek(g_li0, STREAM_SEEK_SET, NULL));
    Check( S_OK, pstmSub->Write(
                    poszTestDataPostCommit,
                    ( sizeof(OLECHAR)
                      *
                      (ocslen( poszTestDataPostCommit ) + sizeof(OLECHAR))
                    ),
                    NULL ));

    // Write to the simple property set.
    propvarSourceSimple.lVal = lSimplePostCommit;
    Check(S_OK, pPropStgSource1->WriteMultiple(1, &propspec, &propvarSourceSimple, 2));

    //  -------------------------------------------
    //  Copy the source Storage to the destination.
    //  -------------------------------------------

    // Release the sub-Storage (which is below the base Storage, and has
    // a Stream with data in it), just to test that the CopyTo can
    // handle it.

    pstgSub->Release();
    pstgSub = NULL;

    Check(S_OK, pstgBaseSource->CopyTo( 0, NULL, NULL, pstgBaseDestination ));


    //  ----------------------------------------------------------
    //  Verify the simple property set in the destination Storage.
    //  ----------------------------------------------------------


    TSafeStorage< IPropertySetStorage > pPropSetStgDestination(pstgBaseDestination);

    // Open the simple property set.

    Check(S_OK, pPropSetStgDestination->Open(fmtidSimple,
            STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
            &pPropStgDestination));

    // Verify the property set name.

    OLECHAR *poszPropSetNameDestination;
    BOOL   bReadPropertyNamePassed = FALSE;

    Check(S_OK, pPropStgDestination->
          ReadPropertyNames( 1, &pidDictionary, &poszPropSetNameDestination ));
    if( poszPropSetNameDestination  // Did we get a name back?
        &&                          // If so, was it the correct name?
        !ocscmp( cposzPropSetName, poszPropSetNameDestination )
      )
    {
        bReadPropertyNamePassed = TRUE;
    }
    CoTaskMemFree( poszPropSetNameDestination );
    poszPropSetNameDestination = NULL;

    Check( TRUE, bReadPropertyNamePassed );

    // Read the PROPVARIANT that we wrote earlier.

    Check(S_OK, pPropStgDestination->ReadMultiple(1, &propspec, &propvarDestination));

    // Verify that it's correct.

    Check(TRUE, propvarDestination.vt   == propvarSourceSimple.vt );
    Check(TRUE, propvarDestination.lVal == lSimplePostCommit);

    Check(S_OK, pPropStgDestination->Commit( STGC_DEFAULT ));
    Check(S_OK, pPropStgDestination->Release());
    pPropStgDestination = NULL;


    //  ------------------------------------------------
    //  Verify the test data in the destination Storage.
    //  ------------------------------------------------

    // Now we can release and re-use the Stream pointer that
    // currently points to the sub-Stream in the source docfile.

    Check(STG_E_REVERTED, pstmSub->Commit( STGC_DEFAULT ));
    Check(S_OK, pstmSub->Release());
    pstmSub = NULL;

    // Get the Storage then the Stream.

    Check( S_OK, pstgBaseDestination->OpenStorage(
                                poszTestSubStorage,
                                NULL,
                                STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
                                NULL,
                                0L,
                                &pstgSub ));

    Check( S_OK, pstgSub->OpenStream(
                            poszTestSubStream,
                            NULL,
                            STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                            0L,
                            &pstmSub ));

    // Read the data and compare it against what we wrote.

    Check( S_OK, pstmSub->Read(
                    acReadBuffer,
                    sizeof( acReadBuffer ),
                    &cbRead ));

    OLECHAR const *poszTestData =   ( STGM_TRANSACTED & ulPropSetTransaction )
                                  ?
                                  poszTestDataPreCommit
                                  :
                                  poszTestDataPostCommit;

    Check( TRUE, cbRead == sizeof(OLECHAR)
                           *
                           (ocslen( poszTestData ) + sizeof(OLECHAR))
         );

    Check( FALSE, ocscmp( poszTestData, (OLECHAR *) acReadBuffer ));


    //  ----
    //  Exit
    //  ----

    // We're done.  Don't bother to release anything;
    // they'll release themselves in their destructors.

    return;

}   // test_CopyTo()



//--------------------------------------------------------
//
//  Function:   test_OLESpecTickerExample
//
//  Synopsis:   This function generates the ticker property set
//              example that's used in the OLE Programmer's Reference
//              (when describing property ID 0 - the dictionary).
//
//--------------------------------------------------------


#define PID_SYMBOL  0x7
#define PID_OPEN    0x3
#define PID_CLOSE   0x4
#define PID_HIGH    0x5
#define PID_LOW     0x6
#define PID_LAST    0x8
#define PID_VOLUME  0x9

void test_OLESpecTickerExample( IStorage* pstg )
{
    printf( "   Generate the Stock Ticker property set example from the OLE Programmer's Ref\n" );

    //  ------
    //  Locals
    //  ------

    FMTID fmtid;

    PROPSPEC propspec;

    DECLARE_CONST_OLESTR(coszPropSetName, "Stock Quote" );
    DECLARE_CONST_OLESTR(coszTickerSymbolName, "Ticker Symbol" );
    DECLARE_CONST_OLESTR(coszOpenName, "Opening Price" );
    DECLARE_CONST_OLESTR(coszCloseName, "Last Closing Price" );
    DECLARE_CONST_OLESTR(coszHighName, "High Price" );
    DECLARE_CONST_OLESTR(coszLowName, "Low Price" );
    DECLARE_CONST_OLESTR(coszLastName, "Last Price" );
    DECLARE_CONST_OLESTR(coszVolumeName, "Volume" );

    //  ---------------------------------
    //  Create a new simple property set.
    //  ---------------------------------

    TSafeStorage< IPropertySetStorage > pPropSetStg(pstg);
    IPropertyStorage *pPropStg;

    UuidCreate( &fmtid );

    Check(S_OK, pPropSetStg->Create(fmtid,
            NULL,
            PROPSETFLAG_DEFAULT,    // Unicode
            STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
            &pPropStg));


    //  ---------------------------------------------
    //  Fill in the simply property set's dictionary.
    //  ---------------------------------------------

    // Write the property set's name.

    PROPID pidDictionary = 0;
    Check(S_OK, pPropStg->WritePropertyNames(1, &pidDictionary, &coszPropSetName ));

    // Write the High price, forcing the dictionary to pad.

    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = PID_HIGH;

    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszHighName ));


    // Write the ticker symbol.

    propspec.propid = PID_SYMBOL;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszTickerSymbolName));

    // Write the rest of the dictionary.

    propspec.propid = PID_LOW;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszLowName));

    propspec.propid = PID_OPEN;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszOpenName));
    
    propspec.propid = PID_CLOSE;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszCloseName));
    
    propspec.propid = PID_LAST;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszLastName));
    
    propspec.propid = PID_VOLUME;
    Check(S_OK, pPropStg->WritePropertyNames(1, &propspec.propid, &coszVolumeName));


    // Write out the ticker symbol.

    propspec.propid = PID_SYMBOL;

    PROPVARIANT propvar;
    propvar.vt = VT_LPWSTR;
    DECLARE_WIDESTR(wszMSFT, "MSFT");
    propvar.pwszVal = wszMSFT;

    Check(S_OK, pPropStg->WriteMultiple(1, &propspec, &propvar, 2));


    //  ----
    //  Exit
    //  ----

    Check(S_OK, pPropStg->Commit( STGC_DEFAULT ));
    Check(S_OK, pPropStg->Release());
    Check(S_OK, pstg->Commit( STGC_DEFAULT ));

    return;


}  // test_OLESpecTickerExample()


void
test_Office( LPOLESTR wszTestFile )
{

    printf( "   Generate Office Property Sets\n" );

    TSafeStorage<IStorage> pStg;
    TSafeStorage<IPropertyStorage> pPStgSumInfo, pPStgDocSumInfo, pPStgUserDefined;

    PROPVARIANT propvarWrite, propvarRead;
    PROPSPEC    propspec;

    PropVariantInit( &propvarWrite );
    PropVariantInit( &propvarRead );

    // Create the DocFile

    Check(S_OK, StgCreateDocfile(  wszTestFile,
                                   STGM_DIRECT | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                   0,
                                   &pStg));

    // Create the SummaryInformation property set.

    TSafeStorage<IPropertySetStorage> pPSStg( pStg );
    Check(S_OK, pPSStg->Create( FMTID_SummaryInformation,
                                NULL,
                                PROPSETFLAG_ANSI,
                                STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                                &pPStgSumInfo ));

    // Write a Title to the SumInfo property set.

    PropVariantInit( &propvarWrite );
    propvarWrite.vt = VT_LPSTR;
    propvarWrite.pszVal = "Title from PropTest";
    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = PID_TITLE;

    Check( S_OK, pPStgSumInfo->WriteMultiple( 1, &propspec, &propvarWrite, PID_FIRST_USABLE ));
    Check( S_OK, pPStgSumInfo->ReadMultiple( 1, &propspec, &propvarRead ));

    Check( TRUE, propvarWrite.vt == propvarRead.vt );
    Check( FALSE, strcmp( propvarWrite.pszVal, propvarRead.pszVal ));

    PropVariantClear( &propvarRead );
    PropVariantInit( &propvarRead );
    pPStgSumInfo->Release();
    pPStgSumInfo = NULL;


    // Create the DocumentSummaryInformation property set.

    Check(S_OK, pPSStg->Create( FMTID_DocSummaryInformation,
                                NULL,
                                PROPSETFLAG_ANSI,
                                STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                                &pPStgDocSumInfo ));

    // Write a word-count to the DocSumInfo property set.

    PropVariantInit( &propvarWrite );
    propvarWrite.vt = VT_I4;
    propvarWrite.lVal = 100;
    propspec.ulKind = PRSPEC_PROPID;
    propspec.propid = PID_WORDCOUNT;

    Check( S_OK, pPStgDocSumInfo->WriteMultiple( 1, &propspec, &propvarWrite, PID_FIRST_USABLE ));
    Check( S_OK, pPStgDocSumInfo->ReadMultiple( 1, &propspec, &propvarRead ));

    Check( TRUE, propvarWrite.vt == propvarRead.vt );
    Check( TRUE, propvarWrite.lVal == propvarRead.lVal );

    PropVariantClear( &propvarRead );
    PropVariantInit( &propvarRead );
    pPStgDocSumInfo->Release();
    pPStgDocSumInfo = NULL;


    // Create the UserDefined property set.

    Check(S_OK, pPSStg->Create( FMTID_UserDefinedProperties,
                                NULL,
                                PROPSETFLAG_ANSI,
                                STGM_CREATE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                                &pPStgUserDefined ));

    // Write named string to the UserDefined property set.

    PropVariantInit( &propvarWrite );
    propvarWrite.vt = VT_LPSTR;
    propvarWrite.pszVal = "User-Defined string from PropTest";
    propspec.ulKind = PRSPEC_LPWSTR;
    DECLARE_OLESTR(ocsTest, "PropTest String");
    propspec.lpwstr = ocsTest;

    Check( S_OK, pPStgUserDefined->WriteMultiple( 1, &propspec, &propvarWrite, PID_FIRST_USABLE ));
    Check( S_OK, pPStgUserDefined->ReadMultiple( 1, &propspec, &propvarRead ));

    Check( TRUE, propvarWrite.vt == propvarRead.vt );
    Check( FALSE, strcmp( propvarWrite.pszVal, propvarRead.pszVal ));

    PropVariantClear( &propvarRead );
    PropVariantInit( &propvarRead );
    pPStgUserDefined->Release();
    pPStgUserDefined = NULL;


    // And we're done!  (Everything releases automatically)

    return;

}


inline BOOL operator == ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1.dwHighDateTime == ft2.dwHighDateTime
            &&
            ft1.dwLowDateTime  == ft2.dwLowDateTime );
}

inline BOOL operator != ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1.dwHighDateTime != ft2.dwHighDateTime
            ||
            ft1.dwLowDateTime  != ft2.dwLowDateTime );
}


inline BOOL operator > ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1.dwHighDateTime >  ft2.dwHighDateTime
            ||
            ft1.dwHighDateTime == ft2.dwHighDateTime
            &&
            ft1.dwLowDateTime  > ft2.dwLowDateTime );
}

inline BOOL operator < ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1.dwHighDateTime <  ft2.dwHighDateTime
            ||
            ft1.dwHighDateTime == ft2.dwHighDateTime
            &&
            ft1.dwLowDateTime  <  ft2.dwLowDateTime );
}

inline BOOL operator >= ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1 > ft2
            ||
            ft1 == ft2 );
}

inline BOOL operator <= ( FILETIME &ft1, FILETIME &ft2 )
{
    return( ft1 < ft2
            ||
            ft1 == ft2 );
}



FILETIME operator - ( FILETIME &ft1, FILETIME &ft2 )
{
    FILETIME ftDiff;

    if( ft1 < ft2 )
    {
        ftDiff.dwLowDateTime  = 0;
        ftDiff.dwHighDateTime = 0;
    }

    else if( ft1.dwLowDateTime >= ft2.dwLowDateTime )
    {
        ftDiff.dwLowDateTime  = ft1.dwLowDateTime  - ft2.dwLowDateTime;
        ftDiff.dwHighDateTime = ft1.dwHighDateTime - ft2.dwHighDateTime;
    }
    else
    {
        ftDiff.dwLowDateTime = ft1.dwLowDateTime - ft2.dwLowDateTime;
        ftDiff.dwLowDateTime = (DWORD) -1 - ftDiff.dwLowDateTime;

        ftDiff.dwHighDateTime = ft1.dwHighDateTime - ft2.dwHighDateTime - 1;
    }

    return( ftDiff );
}




void test_PropVariantCopy( )
{
    printf( "   PropVariantCopy\n" );

    PROPVARIANT propvarCopy;
    PropVariantInit( &propvarCopy );

    for( int i = 0; i < CPROPERTIES_ALL; i++ )
    {
        Check(S_OK, PropVariantCopy( &propvarCopy, &g_rgcpropvarAll[i] ));
        Check(S_OK, CPropVariant::Compare( &propvarCopy, &g_rgcpropvarAll[i]
            ));
        PropVariantClear( &propvarCopy );
    }

}

char* oszft(FILETIME *pft)
{
    static char szBuf[32];
    szBuf[0] = '\0';
    sprintf(szBuf, "(H)%x (L)%x", pft->dwLowDateTime, 
            pft->dwHighDateTime);
    return szBuf;
}

void PrintOC(char *ocsStr)
{
    // simple subsitute to print both WIDE and BYTE chars
    for (  ;*ocsStr; ocsStr++)
        printf("%c", (char) *(ocsStr));
}

void PrintOC(WCHAR *ocsStr)
{
    // simple subsitute to print both WIDE and BYTE chars
    for (  ;*ocsStr; ocsStr++)
        if ( (int) *ocsStr < (int) 0xff)        // in range
            printf("%c", (char) *(ocsStr));
        else
            printf("[%d]", *ocsStr);
}

void DumpTime(WCHAR *pszName, FILETIME *pft)
{
    PrintOC(pszName);
    printf("(H)%x (L)%x\n", 
           pft->dwHighDateTime,
           pft->dwLowDateTime);
}

void DumpTime(char *pszName, FILETIME *pft)
{
    printf("%s (H)%x (L)%x\n", 
           pszName,
           pft->dwHighDateTime,
           pft->dwLowDateTime);
}

VOID
PrintGuid(GUID *pguid)
{
    printf(
        "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        pguid->Data1,
        pguid->Data2,
        pguid->Data3,
        pguid->Data4[0],
        pguid->Data4[1],
        pguid->Data4[2],
        pguid->Data4[3],
        pguid->Data4[4],
        pguid->Data4[5],
        pguid->Data4[6],
        pguid->Data4[7]);
}


VOID
ListPropSetHeader(
    STATPROPSETSTG *pspss,
    OLECHAR *poszName)
{
    BOOLEAN fDocumentSummarySection2;
    OLECHAR oszStream[80];      // should be enough

    fDocumentSummarySection2 = (BOOLEAN)
  memcmp(&pspss->fmtid, &FMTID_UserDefinedProperties, sizeof(GUID)) == 0;

    printf(" Property set ");
    PrintGuid(&pspss->fmtid);

    RtlGuidToPropertySetName(&pspss->fmtid, oszStream);

    printf("\n  %s Name ",
           (pspss->grfFlags & PROPSETFLAG_NONSIMPLE)?
      "Embedding" : "Stream");
    PrintOC(oszStream);
    if (poszName != NULL || fDocumentSummarySection2)
    {
        printf(" (");
        if (poszName != NULL)
            PrintOC(poszName);
        else
            printf("User defined properties");
        printf(")");
    }
    printf("\n");

    if (pspss->grfFlags & PROPSETFLAG_NONSIMPLE)
    {
  DumpTime("  Create Time ", &pspss->ctime);
    }
    DumpTime("  Modify Time ", &pspss->mtime);
    if (pspss->grfFlags & PROPSETFLAG_NONSIMPLE)
    {
  DumpTime("  Access Time ", &pspss->atime);
    }
}




typedef enum _PUBLICPROPSET
{
    PUBPS_UNKNOWN = 0,
    PUBPS_SUMMARYINFO = 3,
    PUBPS_DOCSUMMARYINFO = 4,
    PUBPS_USERDEFINED = 5,
} PUBLICPROPSET;


#define BSTRLEN(bstrVal)      *((ULONG *) bstrVal - 1)
ULONG
SizeProp(PROPVARIANT *pv)
{
    ULONG j;
    ULONG cbprop = 0;

    switch (pv->vt)
    {
    default:
    case VT_EMPTY:
    case VT_NULL:
        break;

    case VT_UI1:
        cbprop = sizeof(pv->bVal);
        break;

    case VT_I2:
    case VT_UI2:
    case VT_BOOL:
        cbprop = sizeof(pv->iVal);
        break;

    case VT_I4:
    case VT_UI4:
    case VT_R4:
    case VT_ERROR:
        cbprop = sizeof(pv->lVal);
        break;

    case VT_I8:
    case VT_UI8:
    case VT_R8:
    case VT_CY:
    case VT_DATE:
    case VT_FILETIME:
        cbprop = sizeof(pv->hVal);
        break;

    case VT_CLSID:
        cbprop = sizeof(*pv->puuid);
        break;

    case VT_BLOB_OBJECT:
    case VT_BLOB:
        cbprop = pv->blob.cbSize + sizeof(pv->blob.cbSize);
        break;

    case VT_CF:
        cbprop = sizeof(pv->pclipdata->cbSize) +
                 pv->pclipdata->cbSize;
        break;

    case VT_BSTR:
  // count + string
  cbprop = sizeof(ULONG);
  if (pv->bstrVal != NULL)
  {
      cbprop += BSTRLEN(pv->bstrVal);
  }
  break;

    case VT_LPSTR:
  // count + string + null char
  cbprop = sizeof(ULONG);
  if (pv->pszVal != NULL)
  {
      cbprop += strlen(pv->pszVal) + 1;
  }
  break;

    case VT_STREAM:
    case VT_STREAMED_OBJECT:
    case VT_STORAGE:
    case VT_STORED_OBJECT:
    case VT_LPWSTR:
  // count + string + null char
  cbprop = sizeof(ULONG);
  if (pv->pwszVal != NULL)
  {
      cbprop += sizeof(pv->pwszVal[0]) * (wcslen(pv->pwszVal) + 1);
  }
  break;

    //  vectors
    case VT_VECTOR | VT_UI1:
        cbprop = sizeof(pv->caub.cElems) +
             pv->caub.cElems * sizeof(pv->caub.pElems[0]);
        break;

    case VT_VECTOR | VT_I2:
    case VT_VECTOR | VT_UI2:
    case VT_VECTOR | VT_BOOL:
        cbprop = sizeof(pv->cai.cElems) +
             pv->cai.cElems * sizeof(pv->cai.pElems[0]);
        break;

    case VT_VECTOR | VT_I4:
    case VT_VECTOR | VT_UI4:
    case VT_VECTOR | VT_R4:
    case VT_VECTOR | VT_ERROR:
        cbprop = sizeof(pv->cal.cElems) +
             pv->cal.cElems * sizeof(pv->cal.pElems[0]);
        break;

    case VT_VECTOR | VT_I8:
    case VT_VECTOR | VT_UI8:
    case VT_VECTOR | VT_R8:
    case VT_VECTOR | VT_CY:
    case VT_VECTOR | VT_DATE:
    case VT_VECTOR | VT_FILETIME:
        cbprop = sizeof(pv->cah.cElems) +
             pv->cah.cElems * sizeof(pv->cah.pElems[0]);
        break;

    case VT_VECTOR | VT_CLSID:
        cbprop = sizeof(pv->cauuid.cElems) +
             pv->cauuid.cElems * sizeof(pv->cauuid.pElems[0]);
        break;

    case VT_VECTOR | VT_CF:
        cbprop = sizeof(pv->caclipdata.cElems);
        for (j = 0; j < pv->caclipdata.cElems; j++)
        {
            cbprop += sizeof(pv->caclipdata.pElems[j].cbSize) +
                      DwordAlign(pv->caclipdata.pElems[j].cbSize);
        }
        break;

    case VT_VECTOR | VT_BSTR:
  cbprop = sizeof(pv->cabstr.cElems);
  for (j = 0; j < pv->cabstr.cElems; j++)
  {
      // count + string + null char
      cbprop += sizeof(ULONG);
      if (pv->cabstr.pElems[j] != NULL)
      {
    cbprop += DwordAlign(BSTRLEN(pv->cabstr.pElems[j]));
      }
  }
  break;

    case VT_VECTOR | VT_LPSTR:
  cbprop = sizeof(pv->calpstr.cElems);
  for (j = 0; j < pv->calpstr.cElems; j++)
  {
      // count + string + null char
      cbprop += sizeof(ULONG);
      if (pv->calpstr.pElems[j] != NULL)
      {
    cbprop += DwordAlign(strlen(pv->calpstr.pElems[j]) + 1);
      }
  }
  break;

    case VT_VECTOR | VT_LPWSTR:
  cbprop = sizeof(pv->calpwstr.cElems);
  for (j = 0; j < pv->calpwstr.cElems; j++)
  {
      // count + string + null char
      cbprop += sizeof(ULONG);
      if (pv->calpwstr.pElems[j] != NULL)
      {
    cbprop += DwordAlign(
      sizeof(pv->calpwstr.pElems[j][0]) *
      (wcslen(pv->calpwstr.pElems[j]) + 1));
      }
  }
  break;

    case VT_VECTOR | VT_VARIANT:
        cbprop = sizeof(pv->calpwstr.cElems);
        for (j = 0; j < pv->calpwstr.cElems; j++)
        {
            cbprop += SizeProp(&pv->capropvar.pElems[j]);
        }
        break;
    }
    return(DwordAlign(cbprop) + DwordAlign(sizeof(pv->vt)));
}


PUBLICPROPSET
GuidToPropSet(GUID *pguid)
{
    PUBLICPROPSET pubps = PUBPS_UNKNOWN;
  
    if (pguid != NULL)
    {
  if (memcmp(pguid, &FMTID_SummaryInformation, sizeof(GUID)) == 0)
  {
      pubps = PUBPS_SUMMARYINFO;
  }
  else if (memcmp(pguid, &FMTID_DocSummaryInformation, sizeof(GUID)) == 0)
  {
      pubps = PUBPS_DOCSUMMARYINFO;
  }
  else if (memcmp(pguid, &FMTID_UserDefinedProperties, sizeof(GUID)) == 0)
  {
      pubps = PUBPS_USERDEFINED;
  }
    }
    return(pubps);
}


char
PrintableChar(char ch)
{
    if (ch < ' ' || ch > '~')
    {
        ch = '.';
    }
    return(ch);
}


VOID
DumpHex(BYTE *pb, ULONG cb, ULONG base)
{
    char *pszsep;
    ULONG r, i, cbremain;
    int fZero = FALSE;
    int fSame = FALSE;

    for (r = 0; r < cb; r += 16)
    {
        cbremain = cb - r;
        if (r != 0 && cbremain >= 16)
        {
            if (pb[r] == 0)
            {
                ULONG j;

                for (j = r + 1; j < cb; j++)
                {
                    if (pb[j] != 0)
                    {
                        break;
                    }
                }
                if (j == cb)
                {
                    fZero = TRUE;
                    break;
                }
            }
            if (memcmp(&pb[r], &pb[r - 16], 16) == 0)
            {
                fSame = TRUE;
                continue;
            }
        }
        if (fSame)
        {
            printf("\n\t  *");
            fSame = FALSE;
        }
        unsigned int iLimit =  (cbremain > 16) ? 16 : cbremain;
        for (i = 0; i < iLimit; i++)
        {
            pszsep = " ";
            if ((i % 8) == 0)           // 0 or 8
            {
                pszsep = "  ";
                if (i == 0)             // 0
                {
        // start a new line
        printf("%s    %04x:", r == 0? "" : "\n", r + base);
        pszsep = " ";
                }
            }
            printf("%s%02x", pszsep, pb[r + i]);
        }
        if (i != 0)
        {
            printf("%*s", 3 + (16 - i)*3 + ((i <= 8)? 1 : 0), "");
            for (i = 0; i < iLimit; i++)
            {
                printf("%c", PrintableChar(pb[r + i]));
            }
        }
    }
    if (r != 0)
    {
        printf("\n");
    }
    if (fZero)
    {
        printf("    Remaining %lx bytes are zero\n", cbremain);
    }
}


// Property Id's for Summary Info
#define PID_TITLE   0x00000002L // VT_LPSTR
#define PID_SUBJECT   0x00000003L // VT_LPSTR
#define PID_AUTHOR    0x00000004L // VT_LPSTR
#define PID_KEYWORDS    0x00000005L // VT_LPSTR
#define PID_COMMENTS    0x00000006L // VT_LPSTR
#define PID_TEMPLATE    0x00000007L // VT_LPSTR
#define PID_LASTAUTHOR    0x00000008L // VT_LPSTR
#define PID_REVNUMBER   0x00000009L // VT_LPSTR
#define PID_EDITTIME    0x0000000aL // VT_FILETIME
#define PID_LASTPRINTED   0x0000000bL // VT_FILETIME
#define PID_CREATE_DTM    0x0000000cL // VT_FILETIME
#define PID_LASTSAVE_DTM  0x0000000dL // VT_FILETIME
#define PID_PAGECOUNT   0x0000000eL // VT_I4
#define PID_WORDCOUNT   0x0000000fL // VT_I4
#define PID_CHARCOUNT   0x00000010L // VT_I4
#define PID_THUMBNAIL   0x00000011L // VT_CF
#define PID_APPNAME   0x00000012L // VT_LPSTR
#define PID_SECURITY_DSI  0x00000013L // VT_I4

// Property Id's for Document Summary Info
#define PID_CATEGORY    0x00000002L // VT_LPSTR
#define PID_PRESFORMAT    0x00000003L // VT_LPSTR
#define PID_BYTECOUNT   0x00000004L // VT_I4
#define PID_LINECOUNT   0x00000005L // VT_I4
#define PID_PARACOUNT   0x00000006L // VT_I4
#define PID_SLIDECOUNT    0x00000007L // VT_I4
#define PID_NOTECOUNT   0x00000008L // VT_I4
#define PID_HIDDENCOUNT   0x00000009L // VT_I4
#define PID_MMCLIPCOUNT   0x0000000aL // VT_I4
#define PID_SCALE   0x0000000bL // VT_BOOL
#define PID_HEADINGPAIR   0x0000000cL // VT_VECTOR | VT_VARIANT
#define PID_DOCPARTS    0x0000000dL // VT_VECTOR | VT_LPSTR
#define PID_MANAGER   0x0000000eL // VT_LPSTR
#define PID_COMPANY   0x0000000fL // VT_LPSTR
#define PID_LINKSDIRTY    0x00000010L // VT_BOOL
#define PID_CCHWITHSPACES 0x00000011L // VT_I4
#define PID_GUID    0x00000012L // VT_LPSTR
#define PID_SHAREDDOC   0x00000013L // VT_BOOL
#define PID_LINKBASE    0x00000014L // VT_LPSTR
#define PID_HLINKS    0x00000015L // VT_VECTOR | VT_VARIANT
#define PID_HYPERLINKSCHANGED 0x00000016L // VT_BOOL

VOID
DisplayProps(
    GUID *pguid,
    ULONG cprop,
    PROPID apid[],
    STATPROPSTG asps[],
    FULLPROPSPEC afps[],
    PROPVARIANT *av,
    BOOLEAN fsumcat,
    ULONG *pcbprop)
{
    PROPVARIANT *pv;
    PROPVARIANT *pvend;
    STATPROPSTG *psps;
    FULLPROPSPEC *pfps, *pfpsLast = NULL;
    BOOLEAN fVariantVector;
    PUBLICPROPSET pubps;
    DECLARE_OLESTR(ocsNull,"");

    ASSERT(asps == NULL || afps == NULL);
    fVariantVector = (asps == NULL && afps == NULL);

    pubps = GuidToPropSet(pguid);
    pvend = &av[cprop];
    for (pv = av, psps = asps, pfps = afps; pv < pvend; pv++, psps++, pfps++)
    {
        ULONG j;
        ULONG cbprop;
        PROPID propid;
        OLECHAR *postrName;
        char *psz;
        BOOLEAN fNewLine = TRUE;
        int ccol;
        static char szNoFormat[] = " (no display format)";
        char achvt[19 + 8 + 1];

        cbprop = SizeProp(pv);
        *pcbprop += cbprop;

        postrName = NULL;
        if (asps != NULL)
        {
            propid = psps->propid;
            postrName = psps->lpwstrName;
        }
        else if (afps != NULL)          // If multiple propsets are possible
        {
            if (pfpsLast == NULL ||     // print unique GUIDs only
                memcmp(
                    &pfps->guidPropSet,
                    &pfpsLast->guidPropSet,
                    sizeof(pfps->guidPropSet)) != 0)
            {
                OLECHAR oszStream[80];

    printf("%s Guid: ", pfpsLast == NULL? "" : "\n");
    PrintGuid(&pfps->guidPropSet);

    pubps = GuidToPropSet(&pfps->guidPropSet);
                RtlGuidToPropertySetName(&pfps->guidPropSet, oszStream);

                printf( " Name: " ); 
    PrintOC(oszStream);
                printf( "%s", pubps == PUBPS_USERDEFINED?
      g_szEmpty : " (User defined properties)");
                pfpsLast = pfps;
            }
            if (pfps->psProperty.ulKind == PRSPEC_PROPID)
            {
                propid = pfps->psProperty.propid;
            }
            else
            {
                propid = PID_ILLEGAL;
                postrName = pfps->psProperty.lpwstr;
            }
        }
        else
        {
            ASSERT(apid != NULL);
            propid = apid[0];
        }

        printf(" ");
        ccol = 0;

        if (propid != PID_ILLEGAL)
        {
            printf(" %04x", propid);
            ccol += 5;
            if (propid & (0xf << 28))
            {
                ccol += 4;
            }
            else if (propid & (0xf << 24))
            {
                ccol += 3;
            }
            else if (propid & (0xf << 20))
            {
                ccol += 2;
            }
            else if (propid & (0xf << 16))
            {
                ccol++;
            }
        }
        if (postrName != NULL)
        {
            printf(" '");
      PrintOC(postrName);
            printf("' ");
      ccol += ocslen(postrName) + 3;
        }
        else if (fVariantVector)
        {
            ULONG i = pv - av;
                
            printf("[%x]", i);
            do
            {
                ccol++;
                i >>= 4;
            } while (i != 0);
            ccol += 2;
        }
        else
        {
            psz = NULL;

            switch (propid)
            {
                case PID_LOCALE:               psz = "Locale";           break;
                case PID_SECURITY:             psz = "SecurityId";       break;
                case PID_MODIFY_TIME:          psz = "ModifyTime";       break;
                case PID_CODEPAGE:             psz = "CodePage";         break;
                case PID_DICTIONARY:           psz = "Dictionary";       break;
            }
            if (psz == NULL)
    switch (pubps)
    {
    case PUBPS_SUMMARYINFO:
        switch (propid)
        {
        case PID_TITLE:              psz = "Title";          break;
        case PID_SUBJECT:            psz = "Subject";        break;
        case PID_AUTHOR:             psz = "Author";         break;
        case PID_KEYWORDS:           psz = "Keywords";       break;
        case PID_COMMENTS:           psz = "Comments";       break;
        case PID_TEMPLATE:           psz = "Template";       break;
        case PID_LASTAUTHOR:         psz = "LastAuthor";     break;
        case PID_REVNUMBER:          psz = "RevNumber";      break;
        case PID_EDITTIME:           psz = "EditTime";       break;
        case PID_LASTPRINTED:        psz = "LastPrinted";    break;
        case PID_CREATE_DTM:         psz = "CreateDateTime"; break;
        case PID_LASTSAVE_DTM:       psz = "LastSaveDateTime";break;
        case PID_PAGECOUNT:          psz = "PageCount";      break;
        case PID_WORDCOUNT:          psz = "WordCount";      break;
        case PID_CHARCOUNT:          psz = "CharCount";      break;
        case PID_THUMBNAIL:          psz = "ThumbNail";      break;
        case PID_APPNAME:            psz = "AppName";        break;
        case PID_DOC_SECURITY:       psz = "Security";       break;

        }
        break;

    case PUBPS_DOCSUMMARYINFO:
        switch (propid)
        {
        case PID_CATEGORY:          psz = "Category";        break;
        case PID_PRESFORMAT:        psz = "PresFormat";      break;
        case PID_BYTECOUNT:         psz = "ByteCount";       break;
        case PID_LINECOUNT:         psz = "LineCount";       break;
        case PID_PARACOUNT:         psz = "ParaCount";       break;
        case PID_SLIDECOUNT:        psz = "SlideCount";      break;
        case PID_NOTECOUNT:         psz = "NoteCount";       break;
        case PID_HIDDENCOUNT:       psz = "HiddenCount";     break;
        case PID_MMCLIPCOUNT:       psz = "MmClipCount";     break;
        case PID_SCALE:             psz = "Scale";           break;
        case PID_HEADINGPAIR:       psz = "HeadingPair";     break;
        case PID_DOCPARTS:          psz = "DocParts";        break;
        case PID_MANAGER:           psz = "Manager";         break;
        case PID_COMPANY:           psz = "Company";         break;
        case PID_LINKSDIRTY:        psz = "LinksDirty";      break;
        case PID_CCHWITHSPACES:     psz = "CchWithSpaces";   break;
        case PID_GUID:              psz = "Guid";            break;
        case PID_SHAREDDOC:         psz = "SharedDoc";       break;
        case PID_LINKBASE:          psz = "LinkBase";        break;
        case PID_HLINKS:            psz = "HLinks";          break;
        case PID_HYPERLINKSCHANGED: psz = "HyperLinksChanged";break;
        }
        break;
            }
            if (psz != NULL)
            {
                printf(" %s", psz);
                ccol += strlen(psz) + 1;
            }
        }
#define CCOLPROPID 20
        if (ccol != CCOLPROPID)
  {
      if (ccol > CCOLPROPID)
      {
    ccol = -1;
      }
            printf("%s%*s", ccol == -1? "\n" : "", CCOLPROPID - ccol, "");
  }
        printf(" %08x  %04x  %04x ", propid, cbprop, pv->vt);

        psz = "";
        switch (pv->vt)
        {
        default:
            psz = achvt;
            sprintf(psz, "Unknown (vt = %hx)", pv->vt);
            break;

        case VT_EMPTY:
            printf("EMPTY");
            break;

        case VT_NULL:
            printf("NULL");
            break;

        case VT_UI1:
            printf("UI1 = %02lx", pv->bVal);
            psz = "";
            break;

        case VT_I2:
            psz = "I2";
            goto doshort;

        case VT_UI2:
            psz = "UI2";
            goto doshort;

        case VT_BOOL:
            psz = "BOOL";
doshort:
            printf("%s = %04hx", psz, pv->iVal);
            psz = g_szEmpty;
            break;

        case VT_I4:
            psz = "I4";
            goto dolong;

        case VT_UI4:
            psz = "UI4";
            goto dolong;

        case VT_R4:
            psz = "R4";
            goto dolong;

        case VT_ERROR:
            psz = "ERROR";
dolong:
            printf("%s = %08lx", psz, pv->lVal);
            psz = g_szEmpty;
            break;

        case VT_I8:
            psz = "I8";
            goto dotwodword;

        case VT_UI8:
            psz = "UI8";
dotwodword:
            printf( "%s = %08lx:%08lx",
                   psz,
                   pv->hVal.HighPart,
                   pv->hVal.LowPart );
            psz = g_szEmpty;
            break;

        case VT_R8:
            psz = "R8";
            goto dolonglong;

        case VT_CY:
            psz = "R8";
            goto dolonglong;

        case VT_DATE:
            psz = "R8";
dolonglong:
            printf(
                "%s = %08lx:%08lx",
                psz,
                (pv->cyVal).split.Hi,
                (pv->cyVal).split.Lo);
            psz = g_szEmpty;
            break;

        case VT_FILETIME:
            DumpTime("FILETIME =\n\t  ", &pv->filetime);
            fNewLine = FALSE;           // skip newline printf
            break;

        case VT_CLSID:
            printf("CLSID =\n\t  ");
            PrintGuid(pv->puuid);
            break;

        case VT_BLOB:
            psz = "BLOB";
            goto doblob;

        case VT_BLOB_OBJECT:
            psz = "BLOB_OBJECT";
doblob:
            printf("%s (cbSize %x)", psz, pv->blob.cbSize);
            if (pv->blob.cbSize != 0)
            {
                printf(" =\n");
                DumpHex(pv->blob.pBlobData, pv->blob.cbSize, 0);
            }
            psz = g_szEmpty;
            break;

        case VT_CF:
            printf(
                "CF (cbSize %x, ulClipFmt %x)\n",
                pv->pclipdata->cbSize,
                pv->pclipdata->ulClipFmt);
            DumpHex(pv->pclipdata->pClipData,
                    pv->pclipdata->cbSize - sizeof(pv->pclipdata->ulClipFmt),
                    0);
            break;

        case VT_STREAM:
            psz = "STREAM";
            goto dostring;

        case VT_STREAMED_OBJECT:
            psz = "STREAMED_OBJECT";
            goto dostring;

        case VT_STORAGE:
            psz = "STORAGE";
            goto dostring;

        case VT_STORED_OBJECT:
            psz = "STORED_OBJECT";
            goto dostring;

        case VT_BSTR:
            printf(
    "BSTR (cb = %04lx)%s\n",
    pv->bstrVal == NULL? 0 : BSTRLEN(pv->bstrVal),
    pv->bstrVal == NULL? " NULL" : g_szEmpty);
            if (pv->bstrVal != NULL)
      {
    DumpHex(
        (BYTE *) pv->bstrVal,
        BSTRLEN(pv->bstrVal) + sizeof(WCHAR),
        0);
      }
            break;

        case VT_LPSTR:
            psz = "LPSTR";
            printf(
    "%s = %s%s%s",
    psz,
    pv->pszVal == NULL? g_szEmpty : "'",
    pv->pszVal == NULL? "NULL" : pv->pszVal,
    pv->pszVal == NULL? g_szEmpty : "'");
      psz = g_szEmpty;
            break;

        case VT_LPWSTR:
            psz = "LPWSTR";
dostring:
            printf(
    "%s = %s",
    psz,
    pv->pwszVal == NULL? g_szEmpty : "'");
            if ( pv->pwszVal == NULL)
                printf("NULL");
            else
                PrintOC(pv->pwszVal);
            printf("%s", pv->pwszVal == NULL? g_szEmpty : "'");
            psz = g_szEmpty;
            break;

        //  vectors

        case VT_VECTOR | VT_UI1:
            printf("UI1[%x] =", pv->caub.cElems);
            for (j = 0; j < pv->caub.cElems; j++)
            {
                if ((j % 16) == 0)
                {
                    printf("\n    %02hx:", j);
                }
                printf(" %02hx", pv->caub.pElems[j]);
            }
            break;

        case VT_VECTOR | VT_I2:
            psz = "I2";
            goto doshortvector;

        case VT_VECTOR | VT_UI2:
            psz = "UI2";
            goto doshortvector;

        case VT_VECTOR | VT_BOOL:
            psz = "BOOL";
doshortvector:
            printf("%s[%x] =", psz, pv->cai.cElems);
            for (j = 0; j < pv->cai.cElems; j++)
            {
                if ((j % 8) == 0)
                {
                    printf("\n    %04hx:", j);
                }
                printf(" %04hx", pv->cai.pElems[j]);
            }
            psz = g_szEmpty;
            break;

        case VT_VECTOR | VT_I4:
            psz = "I4";
            goto dolongvector;

        case VT_VECTOR | VT_UI4:
            psz = "UI4";
            goto dolongvector;

        case VT_VECTOR | VT_R4:
            psz = "R4";
            goto dolongvector;

        case VT_VECTOR | VT_ERROR:
            psz = "ERROR";
dolongvector:
            printf("%s[%x] =", psz, pv->cal.cElems);
            for (j = 0; j < pv->cal.cElems; j++)
            {
                if ((j % 4) == 0)
                {
                    printf("\n    %04x:", j);
                }
                printf(" %08lx", pv->cal.pElems[j]);
            }
            psz = g_szEmpty;
            break;

        case VT_VECTOR | VT_I8:
            psz = "I8";
            goto dolonglongvector;

        case VT_VECTOR | VT_UI8:
            psz = "UI8";
            goto dolonglongvector;

        case VT_VECTOR | VT_R8:
            psz = "R8";
            goto dolonglongvector;

        case VT_VECTOR | VT_CY:
            psz = "CY";
            goto dolonglongvector;

        case VT_VECTOR | VT_DATE:
            psz = "DATE";
dolonglongvector:
            printf("%s[%x] =", psz, pv->cah.cElems);
            for (j = 0; j < pv->cah.cElems; j++)
            {
                if ((j % 2) == 0)
                {
                    printf("\n    %04x:", j);
                }
                printf(
                    " %08lx:%08lx",
                    pv->cah.pElems[j].HighPart,
                    pv->cah.pElems[j].LowPart);
            }
            psz = g_szEmpty;
            break;

        case VT_VECTOR | VT_FILETIME:
            printf("FILETIME[%x] =\n", pv->cafiletime.cElems);
            for (j = 0; j < pv->cafiletime.cElems; j++)
            {
    printf("    %04x: ", j);
    DumpTime(ocsNull, &pv->cafiletime.pElems[j]);
            }
            fNewLine = FALSE;           // skip newline printf
            break;

        case VT_VECTOR | VT_CLSID:
            printf("CLSID[%x] =", pv->cauuid.cElems);
            for (j = 0; j < pv->cauuid.cElems; j++)
            {
                printf("\n    %04x: ", j);
                PrintGuid(&pv->cauuid.pElems[j]);
            }
            break;

        case VT_VECTOR | VT_CF:
            printf("CF[%x] =", pv->caclipdata.cElems);
            for (j = 0; j < pv->caclipdata.cElems; j++)
            {
                printf("\n    %04x: (cbSize %x, ulClipFmt %x) =\n",
                    j,
                    pv->caclipdata.pElems[j].cbSize,
                    pv->caclipdata.pElems[j].ulClipFmt);
                DumpHex(
                    pv->caclipdata.pElems[j].pClipData,
                    pv->caclipdata.pElems[j].cbSize - sizeof(pv->caclipdata.pElems[j].ulClipFmt),
        0);
            }
            break;

        case VT_VECTOR | VT_BSTR:
            printf("BSTR[%x] =", pv->cabstr.cElems);
            for (j = 0; j < pv->cabstr.cElems; j++)
            {
    BSTR bstr = pv->cabstr.pElems[j];

                printf(
        "\n    %04x: cb = %04lx%s\n",
        j,
        bstr == NULL? 0 : BSTRLEN(pv->cabstr.pElems[j]),
        bstr == NULL? " NULL" : g_szEmpty);
    if (bstr != NULL)
    {
        DumpHex((BYTE *) bstr, BSTRLEN(bstr) + sizeof(WCHAR), 0);
    }
            }
            break;

        case VT_VECTOR | VT_LPSTR:
            printf("LPSTR[%x] =", pv->calpstr.cElems);
            for (j = 0; j < pv->calpstr.cElems; j++)
            {
    CHAR *psz = pv->calpstr.pElems[j];

                printf(
        "\n    %04x: %s%s%s",
        j,
        psz == NULL? g_szEmpty : "'",
        psz == NULL? "NULL" : psz,
        psz == NULL? g_szEmpty : "'");
            }
            break;

        case VT_VECTOR | VT_LPWSTR:
            printf("LPWSTR[%x] =", pv->calpwstr.cElems);
            for (j = 0; j < pv->calpwstr.cElems; j++)
            {
    WCHAR *pwsz = pv->calpwstr.pElems[j];
                printf( "\n     %04x: %s",
                        j, pv->pwszVal == NULL? g_szEmpty : "'");
                if ( pwsz == NULL)
                    printf("NULL");
                else
                    PrintOC(pwsz);
                printf("%s", pwsz == NULL? g_szEmpty : "'");       
            }
            break;

        case VT_VECTOR | VT_VARIANT:
            printf("VARIANT[%x] =\n", pv->capropvar.cElems);
            DisplayProps(
        pguid,
                    pv->capropvar.cElems,
                    &propid,
                    NULL,
                    NULL,
                    pv->capropvar.pElems,
        fsumcat,
                    pcbprop);
            fNewLine = FALSE;           // skip newline printf
            break;
        }
        if (*psz != '\0')
        {
            printf("%s", psz);
            if (pv->vt & VT_VECTOR)
            {
                printf("[%x]", pv->cal.cElems);
            }
            printf("%s", szNoFormat);
        }
        if (!fVariantVector && apid != NULL && apid[pv - av] != propid)
        {
            printf(" (bad PROPID: %04x)", apid[pv - av]);
            fNewLine = TRUE;
        }
        if (asps != NULL && pv->vt != psps->vt)
        {
            printf(" (bad STATPROPSTG VARTYPE: %04x)", psps->vt);
            fNewLine = TRUE;
        }
        if (fNewLine)
        {
            printf("\n");
        }
    }
}

STATPROPSTG aspsStatic[] = {
    { NULL, PID_CODEPAGE,    VT_I2 },
    { NULL, PID_MODIFY_TIME, VT_FILETIME },
    { NULL, PID_SECURITY,    VT_UI4 },
};
#define CPROPSTATIC      (sizeof(aspsStatic)/sizeof(aspsStatic[0]))


#define CB_STREAM_OVERHEAD      28
#define CB_PROPSET_OVERHEAD     (CB_STREAM_OVERHEAD + 8)
#define CB_PROP_OVERHEAD        8

HRESULT
DumpOlePropertySet(
    IPropertySetStorage *ppsstg,
    STATPROPSETSTG *pspss,
    ULONG *pcprop,
    ULONG *pcbprop)
{
    HRESULT hr;
    IEnumSTATPROPSTG *penumsps = NULL;
    IPropertyStorage *pps;
    ULONG cprop, cbpropset;
    PROPID propid;
    OLECHAR *poszName;
    ULONG ispsStatic;

    *pcprop = *pcbprop = 0;
    hr = ppsstg->Open(
        pspss->fmtid,
        STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
        &pps);

    if (FAILED(hr))
        return (hr);

    propid = PID_DICTIONARY;
    
    hr = pps->ReadPropertyNames(1, &propid, &poszName);
    if( S_FALSE == hr )
        hr = S_OK;
    Check( S_OK, hr );

    ListPropSetHeader(pspss, poszName);
    if (poszName != NULL)
    {
  CoTaskMemFree(poszName);
    }

    cprop = cbpropset = 0;

    Check(S_OK, pps->Enum(&penumsps) );

    ispsStatic = 0;
    hr = S_OK;
    while (hr == S_OK)
    {
  STATPROPSTG sps;
  PROPSPEC propspec;
  PROPVARIANT propvar;
  ULONG count;

  hr = S_FALSE;
  if (ispsStatic == 0)
  {
      hr = penumsps->Next(1, &sps, &count);
  }

  if (hr != S_OK)
  {
      if (hr == S_FALSE)
      {
    hr = S_OK;
    if (ispsStatic >= CPROPSTATIC)
    {
        break;
    }
    sps = aspsStatic[ispsStatic];
    ispsStatic++;
    count = 1;
      }
            Check( S_OK, hr );
  }
  PropVariantInit(&propvar);
  if (sps.lpwstrName != NULL)
  {
      propspec.ulKind = PRSPEC_LPWSTR;
      propspec.lpwstr = sps.lpwstrName;
  }
  else
  {
      propspec.ulKind = PRSPEC_PROPID;
      propspec.propid = sps.propid;
  }

        hr = pps->ReadMultiple(1, &propspec, &propvar);
  if (hr == S_FALSE)
  {
      if (g_fVerbose)
      {
    printf(
        "%s(%u, %x) vt=%x returned hr=%x\n",
        "IPropertyStorage::ReadMultiple",
        ispsStatic,
        propspec.propid,
        propvar.vt,
        hr);
      }
      ASSERT(propvar.vt == VT_EMPTY);
      hr = S_OK;
  }
        Check( S_OK, hr );

  if (ispsStatic == 0 || propvar.vt != VT_EMPTY)
  {
      ASSERT(count == 1);
      cprop += count;
      if (cprop == 1)
      {
    printf(g_szPropHeader);
      }

      DisplayProps(
        &pspss->fmtid,
        1,
        NULL,
        &sps,
        NULL,
        &propvar,
        FALSE,
        &cbpropset);
      PropVariantClear(&propvar);
  }
  if (sps.lpwstrName != NULL)
  {
      CoTaskMemFree(sps.lpwstrName);
  }
    }
    if (penumsps != NULL)
    {
  penumsps->Release();
    }
    pps->Release();
    if (cprop != 0)
    {
  cbpropset += CB_PROPSET_OVERHEAD + cprop * CB_PROP_OVERHEAD;
  printf("  %04x bytes in %u properties\n\n", cbpropset, cprop);
    }
    *pcprop = cprop;
    *pcbprop = cbpropset;
    return(hr);
}


HRESULT
DumpOlePropertySets(
    IStorage *pstg,
    OLECHAR *aocpath)
{

    HRESULT hr = S_OK;
    IPropertySetStorage *ppsstg;
    ULONG cbproptotal = 0;
    ULONG cproptotal = 0;
    ULONG cpropset = 0;
    IID IIDpsstg = IID_IPropertySetStorage;

    Check(S_OK, pstg->QueryInterface(IID_IPropertySetStorage, (void **) &ppsstg) );

    {
  IEnumSTATPROPSETSTG *penumspss = NULL;

  Check(S_OK, ppsstg->Enum(&penumspss) );

  while (hr == S_OK)
  {
      STATPROPSETSTG spss;
      ULONG count;
      BOOLEAN fDocumentSummarySection2;

      hr = penumspss->Next(1, &spss, &count);

      if (hr != S_OK)
      {
    if (hr == S_FALSE)
    {
        hr = S_OK;
    }

                Check( S_OK, hr );
    break;
      }
      ASSERT(count == 1);

      fDocumentSummarySection2 = FALSE;
      while (TRUE)
      {
    ULONG cprop, cbprop;
                HRESULT hr;

    DumpOlePropertySet(
        ppsstg,
        &spss,
        &cprop,
        &cbprop);
                if ( STG_E_FILENOTFOUND == hr 
                     && fDocumentSummarySection2 )
                {
                    hr = S_OK;
                }

    cpropset++;
    cproptotal += cprop;
    cbproptotal += cbprop;

    if (memcmp(&spss.fmtid, &guidDocumentSummary, sizeof(GUID)))
    {
        break;
    }
    spss.fmtid = FMTID_UserDefinedProperties;
    fDocumentSummarySection2 = TRUE;
      }
  }

  if (penumspss != NULL)
  {
      penumspss->Release();
  }
  ppsstg->Release();
    }
    if ((cbproptotal | cproptotal | cpropset) != 0)
    {
  printf(
      " %04x bytes in %u properties in %u property sets\n",
      cbproptotal,
      cproptotal,
      cpropset);
    }
    return(hr);
}

inline ULONG min(ULONG ul1, ULONG ul2)
{
    if (ul1 > ul2) return ul2;
    else return ul1;
}

NTSTATUS
DumpOleStream(
    LPSTREAM pstm,
    ULONG cb)
{
    ULONG cbTotal = 0;

    while (TRUE)
    {
  ULONG cbOut;
  BYTE ab[4096];

  Check(S_OK, pstm->Read(ab, min(cb, sizeof(ab)), &cbOut) );
  if (cbOut == 0)
  {
      break;
  }
  if (g_fVerbose)
  {
      DumpHex(ab, cbOut, cbTotal);
  }
  cb -= cbOut;
  cbTotal += cbOut;
    }
    return(STATUS_SUCCESS);
}

VOID
DumpOleStorage(
    IStorage *pstg,
    LPOLESTR aocpath )
{
    LPENUMSTATSTG penum;
    STATSTG ss;
    char *szType;
    OLECHAR *pocChild;
    HRESULT hr;
    NTSTATUS Status = STATUS_INVALID_PARAMETER;

    Check( S_OK, DumpOlePropertySets(pstg, aocpath) );
    Check( S_OK, pstg->EnumElements(0, NULL, 0, &penum) );

    pocChild = &aocpath[ocslen(aocpath)];

    // Continue enumeration until IEnumStatStg::Next returns non-S_OK

    while (TRUE)
    {
  ULONG ulCount;

        // Enumerate one element at a time
        hr = penum->Next(1, &ss, &ulCount);
        if( S_FALSE == hr )
            break;
        else
            Check( S_OK, hr );

        // Select the human-readable type of object to display
        switch (ss.type)
        {
      case STGTY_STREAM:    szType = "Stream";    break;
      case STGTY_STORAGE:   szType = "Storage";   break;
      case STGTY_LOCKBYTES: szType = "LockBytes"; break;
      case STGTY_PROPERTY:  szType = "Property";  break;
      default:              szType = "<Unknown>"; break;
        }
  if (g_fVerbose)
  {
      printf(
    "Type=%hs Size=%lx Mode=%lx LocksSupported=%lx StateBits=%lx",
    szType,
    ss.cbSize.LowPart,
    ss.grfMode,
    ss.grfLocksSupported,
    ss.grfStateBits);
            PrintOC(aocpath);
            PrintOC(ss.pwcsName);
            printf("\n");
      printf("ss.clsid = ");
      PrintGuid(&ss.clsid);
      printf("\n");
  }

        // If a stream, output the data in hex format.

        CoTaskMemFree(ss.pwcsName);
    }
    penum->Release();
    return;
}


//+---------------------------------------------------------
//
//  Function:   MungePropertyStorage
//  
//  Synopsis:   This routine munges the properties in a
//              Property Storage.  The values of the properties
//              remain the same, but the underlying serialization
//              is new (the properties are read, the property
//              storage is deleted, and the properties are
//              re-written).
//
//  Inputs:     [IPropertySetStorage*] ppropsetgstg (in)
//                  The Property Storage container.
//              [FMTID] fmtid
//                  The Property Storage to munge.
//
//  Returns:    None.
//
//  Note:       Property names in the dictionary for which
//              there is no property are not munged.
//
//+---------------------------------------------------------

#define MUNGE_PROPVARIANT_STEP  10  

void
MungePropertyStorage( IPropertySetStorage *ppropsetstg,
                      FMTID fmtid )
{
    //  ------
    //  Locals
    //  ------

    HRESULT hr;
    ULONG celt, ulIndex;
    TSafeStorage< IPropertyStorage > ppropstg;

    IEnumSTATPROPSTG *penumstatpropstg=NULL;

    PROPVARIANT *rgpropvar = NULL;
    STATPROPSTG *rgstatpropstg = NULL;
    ULONG        cProperties = 0;

    // Allocate an array of PropVariants.  We may grow this later.
    rgpropvar = (PROPVARIANT*) CoTaskMemAlloc( MUNGE_PROPVARIANT_STEP * sizeof(*rgpropvar) );
    Check( FALSE, NULL == rgpropvar );

    // Allocate an array of STATPROPSTGs.  We may grow this also.
    rgstatpropstg = (STATPROPSTG*) CoTaskMemAlloc( MUNGE_PROPVARIANT_STEP * sizeof(*rgstatpropstg) );
    Check( FALSE, NULL == rgstatpropstg );

    //  -----------------
    //  Get an Enumerator
    //  -----------------

    // Open the Property Storage.  We may get an error if we're attempting
    // the UserDefined propset.  If it's file-not-found, then simply return,
    // it's not an error, and there's nothing to do.

    hr = ppropsetstg->Open( fmtid,
                            STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                            &ppropstg );
    if( FMTID_UserDefinedProperties == fmtid
        &&
        STG_E_FILENOTFOUND == hr )
    {
        goto Exit;
    }
    Check( S_OK, hr );

    // Get an Enumerator
    Check(S_OK, ppropstg->Enum( &penumstatpropstg ));


    //  --------------------------------------------
    //  Read & delete in all of the properties/names
    //  --------------------------------------------

    // Get the first property from the enumerator
    hr = penumstatpropstg->Next( 1, &rgstatpropstg[cProperties], &celt );
    Check( TRUE, S_OK == hr || S_FALSE == hr );

    // Iterate through the properties.
    while( celt > 0 )
    {
        PROPSPEC propspec;
        propspec.ulKind = PRSPEC_PROPID;
        propspec.propid = rgstatpropstg[cProperties].propid;

        // Read and delete the property

        Check(S_OK, ppropstg->ReadMultiple( 1, &propspec, &rgpropvar[cProperties] ));
        Check(S_OK, ppropstg->DeleteMultiple( 1, &propspec ));

        // If there is a property name, delete it also.

        if( NULL != rgstatpropstg[cProperties].lpwstrName )
        {
            // We have a name.
            Check(S_OK, ppropstg->DeletePropertyNames( 1, &rgstatpropstg[cProperties].propid ));
        }

        // Increment the property count.
        cProperties++;

        // Do we need to grow the arrays?

        if( 0 != cProperties
            &&
            (cProperties % MUNGE_PROPVARIANT_STEP) == 0 )
        {
            // Yes - they must be reallocated.

            rgpropvar = (PROPVARIANT*)
                        CoTaskMemRealloc( rgpropvar,
                                          ( (cProperties + MUNGE_PROPVARIANT_STEP)
                                            *
                                            sizeof(*rgpropvar) 
                                          ));
            Check( FALSE, NULL == rgpropvar );

            rgstatpropstg = (STATPROPSTG*)
                            CoTaskMemRealloc( rgstatpropstg,
                                              ( (cProperties + MUNGE_PROPVARIANT_STEP)
                                                 *
                                                 sizeof(*rgstatpropstg)
                                              ));
            Check( FALSE, NULL == rgstatpropstg );
        }

        // Move on to the next property.
        hr = penumstatpropstg->Next( 1, &rgstatpropstg[cProperties], &celt );
        Check( TRUE, S_OK == hr || S_FALSE == hr );

    }   // while( celt > 0 )


    //  -------------------------------------
    //  Write the properties & names back out
    //  -------------------------------------

    for( ulIndex = 0; ulIndex < cProperties; ulIndex++ )
    {

        // Write the property.

        PROPSPEC propspec;
        propspec.ulKind = PRSPEC_PROPID;
        propspec.propid = rgstatpropstg[ ulIndex ].propid;

        Check(S_OK, ppropstg->WriteMultiple(1, &propspec, &rgpropvar[ulIndex], PID_FIRST_USABLE ));

        // If this property has a name, write it too.
        if( rgstatpropstg[ ulIndex ].lpwstrName != NULL )
        {
            Check(S_OK, ppropstg->WritePropertyNames(
                                            1,
                                            &rgstatpropstg[ulIndex].propid, 
                                            &rgstatpropstg[ulIndex].lpwstrName ));
        }

    }   // for( ulIndex = 0; ulIndex < cProperties; ulIndex++ )


    //  ----
    //  Exit
    //  ----

Exit:

    if( penumstatpropstg )
    {
        penumstatpropstg->Release();
        penumstatpropstg = NULL;
    }

    // Free the PropVariants
    if( rgpropvar )
    {
        FreePropVariantArray( cProperties, rgpropvar );
        CoTaskMemFree( rgpropvar );
    }

    // Free the property names
    if( rgstatpropstg )
    {
        for( ulIndex = 0; ulIndex < cProperties; ulIndex++ )
        {
            if( NULL != rgstatpropstg[ ulIndex ].lpwstrName )
            {
                CoTaskMemFree( rgstatpropstg[ ulIndex ].lpwstrName );
            }
        }   // for( ulIndex = 0; ulIndex < cProperties; ulIndex++ )

        CoTaskMemFree( rgstatpropstg );
    }


}   // MungePropertyStorage

//+---------------------------------------------------------
//
//  Function:   MungeStorage
//
//  Synopsis:   This routine munges the property sets in a
//              Storage.  The properties themselves are not
//              modified, but the serialized bytes are.
//              For each property set, all the properties are
//              read, the property set is deleted, and
//              the properties are re-written.
//
//  Inputs:     [IStorage*] pstg (in)
//                  The Storage to munge.
//
//  Returns:    None.
//
//  Note:       This routine only munges simple property
//              sets.
//
//+---------------------------------------------------------

void
MungeStorage( IStorage *pstg )
{
    //  ------
    //  Locals
    //  ------

    HRESULT hr;
    ULONG celt;

    STATPROPSETSTG statpropsetstg;
    STATSTG statstg;

    TSafeStorage< IPropertySetStorage > ppropsetstg;
    TSafeStorage< IPropertyStorage > ppropstg;

    IEnumSTATPROPSETSTG *penumstatpropsetstg;
    IEnumSTATSTG *penumstatstg;
    
    //  -----------------------------------------------
    //  Munge each of the property sets in this Storage
    //  -----------------------------------------------

    // Get the IPropertySetStorage interface
    Check(S_OK, pstg->QueryInterface( IID_IPropertySetStorage, (VOID**) &ppropsetstg ));

    // Get a property storage enumerator
    Check(S_OK, ppropsetstg->Enum( &penumstatpropsetstg ));

    // Get the first STATPROPSETSTG
    hr = penumstatpropsetstg->Next( 1, &statpropsetstg, &celt );
    Check( TRUE, S_OK == hr || S_FALSE == hr );

    // Loop through the STATPROPSETSTGs.
    while( celt > 0 )
    {
        // Is this a simple property storage (we don't
        // handle non-simple sets)?

        if( !(statpropsetstg.grfFlags & PROPSETFLAG_NONSIMPLE) )
        {
            // Munge the Property Storage.
            MungePropertyStorage( ppropsetstg, statpropsetstg.fmtid );
        }

        // Get the next STATPROPSETSTG
        // If we just did the first section of the DocSumInfo
        // property set, then attempt the second section

        if( FMTID_DocSummaryInformation == statpropsetstg.fmtid )
        {                                                         
            statpropsetstg.fmtid = FMTID_UserDefinedProperties;
        }
        else
        {
            hr = penumstatpropsetstg->Next( 1, &statpropsetstg, &celt );
            Check( TRUE, S_OK == hr || S_FALSE == hr );
        }
    }

    // We're done with the Property Storage enumerator.
    penumstatpropsetstg->Release();
    penumstatpropsetstg = NULL;

    //  ------------------------------------------
    //  Recursively munge each of the sub-storages
    //  ------------------------------------------

    // Get the IEnumSTATSTG enumerator
    Check(S_OK, pstg->EnumElements( 0L, NULL, 0L, &penumstatstg ));

    // Get the first STATSTG structure.
    hr = penumstatstg->Next( 1, &statstg, &celt );
    Check( TRUE, S_OK == hr || S_FALSE == hr );

    // Loop through the elements of this Storage.
    while( celt > 0 )
    {
        // Is this a sub-Storage which must be
        // munged?

        if( STGTY_STORAGE & statstg.type  // This is a Storage
            &&
            0x20 <= *statstg.pwcsName )   // But not a system Storage.
        {
            // We'll munge it.
            IStorage *psubstg;
            
            // Open the sub-storage.
            Check(S_OK, pstg->OpenStorage( statstg.pwcsName,
                                           NULL,
                                           STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
                                           NULL,
                                           0L,
                                           &psubstg ));

            // Munge the sub-storage.
            MungeStorage( psubstg );
            psubstg->Release();
            psubstg = NULL;
        }

        CoTaskMemFree(statstg.pwcsName);
        // Move on to the next Storage element.
        hr = penumstatstg->Next( 1, &statstg, &celt );
        Check( TRUE, S_OK == hr || S_FALSE == hr );
    }

    penumstatstg->Release();
    penumstatstg = NULL;


}   // MungeStorage

void
DisplayUsage( LPSTR pszCommand )
{
    printf("\n");
    printf("   Usage: %1 [/-][w|iw|ir] <path to test dir>\n", pszCommand);
    printf("   E.g.:  %1 -m d:\\test\n", pszCommand );
    printf("   Where:\n");
    printf("      /w enables the Word 6 test,\n");
    printf("      /iw creates a file for interop test\n");
    printf("      /ir verfies file created for interop test\n" );    
    printf("   If you use the /m option, make sure you specify an absolute\n");
    printf("   path name as the first parameter.  E.g., don't use \".\\Test\"\n");
    printf("\n");
    printf("   File & Directory Options:\n" );
    printf("      /g<file> specifies a file to be munGed\n" );
    printf("          (propsets are read, deleted, & re-written)\n" );
    printf("      /d<file> specifies a file to be Dumped\n" );
    printf("          (propsets are dumped to stdout\n" );
    printf("\n");
    printf("   For Example:\n" );
    printf("      %s -dword6.doc\n", pszCommand );
    printf("      %s -gword6.doc\n", pszCommand );
    printf("\n");
    return;
}

//
//   Interoperability test
//
//   test_interop_write writes in a doc file
//   test_interop_read reads it in and verifies that it is right.
typedef struct tagInteropTest {
    VARENUM vt;
    void *pv;
} interopStruct;

const int cInteropPROPS=18;

static interopStruct  
avtInterop[cInteropPROPS] = {
    VT_LPSTR, "Title of the document.",                                  
    VT_LPSTR, "Subject of the document.",                                
    VT_LPSTR, "Author of the document.",                                 
    VT_LPSTR, "Keywords of the document.",                               
    VT_LPSTR, "Comments of the document.",                               
    VT_LPSTR, "Normal.dot",                
    VT_LPSTR, "Mr. Unknown",               
    VT_LPSTR, "3",                         
    VT_EMPTY, 0,                           
    VT_EMPTY, 0,                           
    VT_EMPTY, 0,                           
    VT_EMPTY, 0,                           
    VT_I4, (void*) 1,                                                   
    VT_I4, (void*) 7,                                                   
    VT_I4, (void*) 65,                                                  
    VT_EMPTY, 0,                           
    VT_LPSTR, "Reference",                 
    VT_I4, (void*) 1121                            
};


void test_interop_write()
{
    printf( "   Interoperability - write \n" );

    DECLARE_OLESTR(szFile, "t_interop");

    IStorage *pStg;
    Check(S_OK, 
          StgCreateDocfile(szFile, 
                           STGM_CREATE| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 
                           (DWORD)NULL, &pStg));

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStg);
    IPropertyStorage *pPropStg;

    Check(S_OK, pPropSetStg->Create(FMTID_SummaryInformation, NULL, 0,
                    STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READWRITE,
                    &pPropStg));

    PROPSPEC propspec[cInteropPROPS+2];
    int i;
    for (i=2; i<cInteropPROPS+2; i++)
    {
        propspec[i].ulKind = PRSPEC_PROPID;
        propspec[i].propid = (PROPID)i;
    }

    PROPVARIANT propvar[cInteropPROPS+2];

    for (i=2; i<cInteropPROPS+2; i++)
    {
        propvar[i].vt = avtInterop[i-2].vt;
        switch (avtInterop[i-2].vt)
        {
        case VT_LPSTR:
            propvar[i].pszVal = (char*)avtInterop[i-2].pv;
            break;
        case VT_I4:
            propvar[i].lVal = (int)avtInterop[i-2].pv;
            break;
        default: 
            break;
        }
    }
    Check(S_OK, 
          pPropStg->WriteMultiple(cInteropPROPS, propspec+2, 
                                        propvar+2, 2) );
    pPropStg->Release();
    pStg->Release();
}

void test_interop_read()
{
    printf( "   Interoperability - read \n" );

    DECLARE_OLESTR(szFile, "t_interop");

    IStorage *pStg;
    Check(S_OK, StgOpenStorage(szFile, NULL,
                    STGM_READWRITE | STGM_SHARE_EXCLUSIVE, NULL, 0, &pStg));

    TSafeStorage< IPropertySetStorage > pPropSetStg(pStg);
    IPropertyStorage *pPropStg;

    Check(S_OK, pPropSetStg->Open(FMTID_SummaryInformation,
                    STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_READ,
                    &pPropStg));

    PROPSPEC propspec[cInteropPROPS+2];
    int i;
    for (i=2; i<cInteropPROPS+2; i++)
    {
        propspec[i].ulKind = PRSPEC_PROPID;
        propspec[i].propid = (PROPID)i;
    }

    PROPVARIANT propvar[cInteropPROPS+2];

    Check(S_OK, pPropStg->ReadMultiple(cInteropPROPS, propspec+2, propvar+2));

    for (i=2; i<cInteropPROPS+2; i++)
    {
        if ( propvar[i].vt != avtInterop[i-2].vt )
        {
            printf( " PROPTEST: 0x%x retrieved type 0x%x, expected type 0x%x\n",
                    i, propvar[i].vt, avtInterop[i-2].vt );
            ASSERT(propvar[i].vt == avtInterop[i-2].vt);
        }
        
        switch (propvar[i].vt) 
        {
        case VT_LPSTR:
            ASSERT(strcmp(propvar[i].pszVal, (char*)avtInterop[i-2].pv)==0);
            break;
        case VT_I4:
            ASSERT(propvar[i].lVal == (int)avtInterop[i-2].pv);
            break; 
        }
        PropVariantClear(propvar+i);
    }

    pPropStg->Release();
    pStg->Release();
}

void Cleanup()
{
    ULONG ul;
    // Clean up and exit.
    if (_pstgTemp)
    {
        ul = _pstgTemp->Release();
        assert(ul==0 && "_pstgTemp ref counting is wrong!");
    }
    if (_pstgTempCopyTo)
    {
        ul = _pstgTempCopyTo->Release();
        assert( 0 == ul 
           && "_pstgTempCopyTo ref counting is wrong!");
    }
}

int main(int argc, char *argv[])
{
    int nArgIndex;
#ifndef _UNIX
    INIT_OLESTR(oszSummary, "SummaryInformation");
    INIT_OLESTR(aocMap, "abcdefghijklmnopqrstuvwxyz012345");
    INIT_OLESTR( oszDocumentSummary, "DocumentSummaryInformation");
#endif

    ULONG ulTestOptions = 0L;
    BOOL  fOffice97TestDoc = FALSE;
    CHAR* pszFileToMunge = NULL;
    CHAR* pszFileToDump = NULL;

    printf("Property Set Tests\n");

    // Check for command-line switches
    if( 2 > argc )
    {
        printf("Too many arguments\n");
        DisplayUsage( argv[0] );
        exit(0);
    }

    for( nArgIndex = 1; nArgIndex < argc; nArgIndex++ )
    {
        if( argv[nArgIndex][0] == '/'
            ||
            argv[nArgIndex][0] == '-'
          )
        {
            BOOL fNextArgument = FALSE;

            for( int nOptionSubIndex = 1;
                 argv[nArgIndex][nOptionSubIndex] != '\0' && !fNextArgument;
                 nOptionSubIndex++
                )
            {
                switch( argv[nArgIndex][nOptionSubIndex] )
                {                                    
                case 'w':
                case 'W':
                    ulTestOptions |= TEST_WORD6;
                    break;
                    
                case '?':
                    DisplayUsage(argv[0]);
                    exit(1);
                    
                case 'i':    
                    if (argv[nArgIndex][nOptionSubIndex+1]=='w')
                        ulTestOptions |= TEST_INTEROP_W;
                    else if (argv[nArgIndex][nOptionSubIndex+1]=='r')
                        ulTestOptions |= TEST_INTEROP_R;
                    else
                    {
                        DisplayUsage(argv[0]);
                        printf("You must specify 'r' or 'w' for interop!\n");
                        exit(-1);
                    }
                    break;                            

                case 'd':
                case 'D':
                    if( NULL != pszFileToDump )
                    {
                        printf( "Error:  Only one file may be dumped\n" );
                        DisplayUsage( argv[0] );
                    }
                    else
                    {
                        pszFileToDump = &argv[nArgIndex][nOptionSubIndex+1];
                        fNextArgument = TRUE;
                    }
                    
                    if( '\0' == *pszFileToDump )
                    {
                        printf( "Error:  Missing filename for dump option\n" );
                        DisplayUsage( argv[0] );
                        exit(1);
                    }
                    break;

                case 'g':
                case 'G':

                    if( NULL != pszFileToMunge )
                    {
                        printf( "Error:  Only one file may be munged\n" );
                        DisplayUsage( argv[0] );
                        exit(1);
                    }
                    else
                    {
                        pszFileToMunge = &argv[nArgIndex][nOptionSubIndex+1];
                        fNextArgument = TRUE;
                    }
                    
                    if( '\0' == *pszFileToMunge )
                    {
                        printf( "Error:  Missing filename for munge option\n" );
                        DisplayUsage( argv[0] );
                        exit(1);
                    }
                    break;
                    
                default:
                    printf( "Option '%c' ignored\n", 
                            argv[nArgIndex][nOptionSubIndex] );
                    break;

                }   // switch( argv[nArgIndex][1] )

            }   // for( int nOptionSubIndex = 1; ...
        }   // if( argv[nArgIndex][0] == '/'
        else
        {
            break;
        }
    }   // for( ULONG nArgIndex = 2; nArgIndex < argc; nArgIndex++ )


    // If any other command-line parameters were given, ignore them.

    for( int nExtraArg = nArgIndex+1; nExtraArg < argc; nExtraArg++ )
    {
        printf( "Illegal argument ignored:  %s\n", argv[nExtraArg] );
    }

    OLECHAR ocsFile[256], ocsTest[256], ocsTest2[256], ocsTestOffice[256];
    CHAR szDir[256];
    HRESULT hr;
    IStorage *pstg;

    int i=0;

    if ( NULL != pszFileToDump )
    {
        printf("DUMPING: %s\n", pszFileToDump);
        printf("========================\n");        
        STOT( pszFileToDump, ocsFile, strlen(pszFileToDump)+1 );
        Check(S_OK, StgOpenStorage( ocsFile,
                                    (IStorage*)NULL,
                                    (DWORD)STGM_DIRECT | STGM_READWRITE | 
                                    STGM_SHARE_EXCLUSIVE,
                                    NULL,
                                    0L,
                                    &pstg ));
        DumpOleStorage( pstg, ocsFile );
        Check(0, pstg->Release()); // ensure we released the last reference
        exit(0);
    }

    // Is there a file to munge?
    if ( NULL != pszFileToMunge )
    {
        STOT(pszFileToMunge, ocsFile, strlen(pszFileToMunge)+1 );
        Check(S_OK, StgOpenStorage( ocsFile,
                                    NULL,
                                    (DWORD) STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                    NULL,
                                    0L,
                                    &pstg ));
        MungeStorage( pstg );
        PrintOC(ocsFile);
        printf( " successfully munged\n");
        Check(0, pstg->Release());
        exit(0);
    }

    // Verify that the user-provided directory path
    // exists

    struct _stat filestat;
    if (_stat(argv[nArgIndex], &filestat)!=0 || 
        !(filestat.st_mode && S_IFDIR))
    {
        printf("Error in opening %s as a directory", argv[nArgIndex] );
        exit(1);
    }

#ifdef _WIN32  // don't bother to create a directory for test files on UNIX
               // since there are some problems with creating files with 
               // path names

    if (!(ulTestOptions & TEST_INTEROP_R) && !(ulTestOptions & TEST_INTEROP_W))
    {
        // Find an new directory name to use for temporary
        // files ("testdirX", where "X" is a number).
        do
        {
            strcpy(szDir, argv[nArgIndex]);
            sprintf(strchr(szDir,0), "\\testdir%d", i++);
        } while ( (_mkdir(szDir, 0x744) == -1) && (i<20) );      
    
        if (i>=20) 
        {
            printf("Too many testdirX subdirectories, delete some and re-run\n");
            exit(-1);
        }
    }
    else 
    {
        // use current directory for interop testing
        szDir[0] = '.';
        szDir[1] = 0;
    }
#endif


#ifdef _WIN32
    printf( "Generated files will be put in \"%s\"\n", szDir );
    // Create "tesdoc"
    STOT(szDir, ocsFile, strlen(szDir)+1);

    ocscpy(ocsTest, ocsFile);
    DECLARE_OLESTR(ocsTestDoc, "\\testdoc");
    ocscat(ocsTest, ocsTestDoc);
#else
    DECLARE_OLESTR(ocsTestDoc, "testdoc");
    ocscpy(ocsTest, ocsTestDoc);
#endif

    hr = StgCreateDocfile(ocsTest, STGM_DIRECT | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                          0, &_pstgTemp);
    if (hr != S_OK)
    {
        printf("Can't create %s\n", ocsTest);
        exit(1);
    }

    // Create "testdoc2"
#ifdef _WIN32
    DECLARE_OLESTR(ocsTestDoc2,"\\testdoc2");
    ocscpy(ocsTest2, ocsFile);
    ocscat(ocsTest2, ocsTestDoc2); 
#else
    DECLARE_OLESTR(ocsTestDoc2,"testdoc2");
    ocscpy(ocsTest2, ocsTestDoc2);
#endif

    hr = StgCreateDocfile(ocsTest2, STGM_CREATE | STGM_READWRITE |
                          STGM_SHARE_EXCLUSIVE,
                          0, &_pstgTempCopyTo);
    if (hr != S_OK)
    {
        printf("Can't create %ls\n", ocsTest);
        exit(1);
    }

    Check(S_OK, PopulateRGPropVar( g_rgcpropvarAll, g_rgcpropspecAll ));

    printf( "\nStandard Tests\n" );
    printf(   "--------------\n" );

    test_WriteReadAllProperties(ulTestOptions);

    test_PropertyInterfaces(_pstgTemp);

    // test with Unicode, then ansi files

    test_CodePages(ocsFile, ulTestOptions, TRUE);
    test_CodePages(ocsFile, ulTestOptions, FALSE);     

    // Test the IStorage::CopyTo operation, using all combinations of 
    // direct and transacted mode for the base and PropSet storages.

    for( int iteration = 0; iteration < 4; iteration++ )
    {
        DECLARE_OLESTR(aocStorageName, "#0 Test CopyTo");
        aocStorageName[1] = (OLECHAR) iteration + (OLECHAR)'0';

        test_CopyTo( _pstgTemp, _pstgTempCopyTo,
                     STGM_DIRECT,
                     STGM_DIRECT,
                     aocStorageName );
    }


    // Generate the stock ticker property set example
    // from the OLE programmer's reference spec.
    test_OLESpecTickerExample( _pstgTemp );

#ifdef _WIN32
    ocscpy(ocsTestOffice, ocsFile);
    DECLARE_OLESTR(ocsOffice, "\\Office");
    ocscat(ocsTestOffice, ocsOffice);
#else
    DECLARE_OLESTR(ocsOffice, "Office");
    ocscpy(ocsTestOffice, ocsOffice);
#endif

    test_Office( ocsTestOffice );
    test_PropVariantValidation( _pstgTemp );
    test_PropVariantCopy();

    if( ulTestOptions )
    {
        printf( "\nOptional Tests\n" );
        printf(   "--------------\n" );

        // If requested, test for compatibility with Word 6.0 files.

        if ( ulTestOptions & TEST_WORD6 )
            test_Word6(_pstgTemp);

        if ( ulTestOptions & TEST_INTEROP_W)
        {
            test_interop_write();
        }
        if ( ulTestOptions & TEST_INTEROP_R)
        {
            test_interop_read();
        }

    }   // if( ulTestOptions )

    Cleanup();

    printf("\nPASSED\n");

    return 0;
}


