/*++

Copyright (C) 1995-2001 Microsoft Corporation

Module Name:

    PROVCOMP.CPP

Abstract:

    Purpose: Defines the acutal "Put" and "Get" functions for the
             Ole compound file provider.

History:

    a-davj  10-10-95    v0.01

--*/

#include "precomp.h"
#include "stdafx.h"
#include <wbemidl.h>
#include "impdyn.h"

#define GET_FLAGS STGM_READ|STGM_SHARE_DENY_WRITE
#define PUT_FLAGS STGM_READWRITE|STGM_SHARE_EXCLUSIVE
enum {NORMAL, STRING, WSTRING, DBLOB, DCLSID, UNKNOWN};


void CImpComp::AddBlankData(CBuff * pBuff,DWORD dwType)
{            
    VARIANTARG vTemp;
    DWORD dwDataSize;
    BOOL bStringType;
    
    // If we have an variant array, then add an empty entry and be done
    if(dwType == VT_VARIANT) {
        DWORD dwEmpty = VT_EMPTY;   //TODO, is the right? maybe VT_NULL??
        pBuff->Add((void *)&dwEmpty,sizeof(DWORD));
        return;
        }
        
    // Create an empty variantarg, get its statistics
    VariantInit(&vTemp);

    vTemp.cyVal.int64 = 0;
    vTemp.vt = (VARTYPE)dwType;
    if(!bGetVariantStats(&vTemp, NULL,  &dwDataSize, &bStringType))
        return;
    
    if(!bStringType) {
        pBuff->Add((void *)&vTemp.cyVal.int64,dwDataSize);
        }
    else if (dwType == VT_BLOB){
        DWORD dwSize = 0;
        pBuff->Add((void *)&dwSize,sizeof(DWORD));
        }
    else {
        DWORD dwSize = 1;
        pBuff->Add((void *)&dwSize,sizeof(DWORD));
        pBuff->Add((void *)&vTemp.cyVal.int64,2); // add a null, 
        pBuff->RoundOff();
        }

}
//***************************************************************************
//
//  CImpComp::AddVariantData
//
//  Adds the property value to a buffer that is to be output.  For normal
//  data, this acutally sets the buffer.  However, for the case of array
//  data, it is probably adding to the buffer (unless its the first element)
//
//***************************************************************************

void CImpComp::AddVariantData(VARIANTARG * pIn,CBuff * pBuff) 
{
    void * pCopy;
    BOOL bSizeNeeded;
    DWORD dwDataSize;
    
    bGetVariantStats(pIn, NULL, &dwDataSize, &bSizeNeeded);

    // If strings, blobs etc, copy size in first

    if(bSizeNeeded) { 
        if(pIn->vt == VT_LPWSTR){
            DWORD dwTemp = dwDataSize /2 ;
            pBuff->Add((void*)&dwTemp,sizeof(DWORD));
            }
        else
            pBuff->Add((void*)&dwDataSize,sizeof(DWORD));
        pCopy = (void *)pIn->bstrVal;
        }
     else
        pCopy = (void *)&pIn->iVal;
      
    // copy the data

    pBuff->Add((void *)pCopy,dwDataSize);

    // round the data to a 4 byte boundry

    if(bSizeNeeded)
        pBuff->RoundOff();
    return;
}

//***************************************************************************
//
//  CImpComp::bCreateWriteArray
//
//  Create a buffer with an array of values.  This is used when writting
//  out properties that are members of arrays.  To do this, an array must 
//  be constructed.
//
//***************************************************************************

BOOL CImpComp::bCreateWriteArray(VARIANTARG * pIn, CBuff * pBuff,
    MODYNPROP *pMo, int iIndex, DWORD & dwType, BOOL bExisting, CProp * pProp)
{
    DWORD * dwp;
    DWORD dwExisting;       // number of existing entries
    DWORD dwNumElem;        // number of elements to be written
    DWORD dwTempType;
    SCODE sc;

    // If the property alread exists, get the current type and number
    // of existing elements.

    if(bExisting) {
        dwType = pProp->GetType();
        dwType &= ~VT_VECTOR;
        dwp = (DWORD *)pProp->Get();
        dwExisting = *dwp;
        }
    else {
        if(!bGetVariantStats(pIn, &dwType, NULL, NULL)){
            pMo->dwResult = ERROR_UNKNOWN;  // BAD INPUT DATA
            return FALSE;
            }
        dwExisting = 0;
        }
    
    // Determine the number of elements to write.  It may be more that the
    // number of existing elements when writting a new vector or adding to
    // an existing one

    if((unsigned)iIndex >= dwExisting)
        dwNumElem = iIndex + 1;     // iIndex has 0 as the first element
    else
        dwNumElem = dwExisting;
    
    // Copy in the number of elements

    pBuff->Add((void *)&dwNumElem,sizeof(DWORD));

    // Now build the output array one element at the time
    // todo, more error checking in here???

    DWORD dwCnt;
    for(dwCnt = 0; dwCnt < dwNumElem; dwCnt++) {
        if(dwCnt == (unsigned)iIndex) {
            
            // This element is being supplied by the caller
            
            if(!bExisting) {
            
                // no converstion necessary since this is a new array
            
                AddVariantData(pIn,pBuff);

                }
            else if(dwType == VT_VARIANT) {
            
                // write type first
                DWORD dwTemp = 0; 
                dwTemp = pIn->vt;
                pBuff->Add((void *)&dwTemp,sizeof(DWORD));
                
                // write the data;
                AddVariantData(pIn,pBuff);
                }
            else {
                // existing array that isnt VARIANT type.  Convert the input
                // data into the same format and then add

                VARIANTARG vTemp;
                VariantInit(&vTemp);
                vTemp.cyVal.int64 = 0;
                sc = OMSVariantChangeType(&vTemp,pIn,0,(VARTYPE)dwType);
                if(sc != S_OK) {
                    pMo->dwResult = sc;
                    return FALSE;
                }
                    
                // write the data;
                AddVariantData(&vTemp,pBuff);
                OMSVariantClear(&vTemp);        // all done, free it
                }
            }
        else if (dwCnt < dwExisting) {
            // Add from the read data
            int iIgnore;
            void * pTemp = ExtractData(pProp,dwTempType,dwCnt,TRUE);
            DWORD dwSkip = dwSkipSize((char *)pTemp,dwTempType,iIgnore);
            pBuff->Add(pTemp,dwSkip);
            }
        else
            // create a blank entry
            AddBlankData(pBuff,dwType);
        }
    dwType |= VT_VECTOR;
    return TRUE;
}

//***************************************************************************
//
//  CImpComp::bGetVariantStats
//
//  Determines various characteristics of a VARIANTARG.
//
//  Returns TRUE if the variant type is valid.
//
//***************************************************************************

BOOL CImpComp::bGetVariantStats(VARIANTARG * pIn, DWORD * pType, DWORD * pDataSize, BOOL * pSizeNeeded)
{
    BOOL bSizeNeeded = FALSE; // set to true for strings and blobs etc.
    DWORD dwDataSize = 0, dwType;
    void * pData = (void *)pIn->bstrVal;
    
    // normall the type is whatever is in the VARIANTARG.  However, 
    // certain new types, such as VT_UI4 are not in the ole spec and
    // might cause problems to older programs.  Therefore, the new
    // types have an equivalent older type returned.

    dwType = pIn->vt;

    // just in case we are running in a 16 bit environment

    if((sizeof(INT) == 2) && (pIn->vt == VT_INT || pIn->vt == VT_UINT))
        pIn->vt = VT_I2;
    
    // determine how big the data is, where to copy it from and determine
    // if the size needs to also be written.

    switch (pIn->vt) {
        case VT_I1:
        case VT_UI1:
        case VT_I2: 
        case VT_BOOL:
        case VT_UI2:    // all get handled as I2
            if(pIn->vt != VT_BOOL)
                dwType = VT_I2;
            dwDataSize = 2;
            break;

        case VT_R4: 
        case VT_I4: 
        case VT_INT:
        case VT_UINT:
        case VT_UI4:    
            if(pIn->vt != VT_R4)
                dwType = VT_I4;
            dwDataSize = 4;
            break;

        case VT_I8:
        case VT_UI8:
        case VT_R8: 
        case VT_CY: 
        case VT_DATE: 
            if(pIn->vt == VT_UI8)
                dwType = VT_I8;
            dwDataSize = 8;
            break;

        case VT_LPSTR:
            dwDataSize = lstrlenA((char *)pData)+1;
            bSizeNeeded = TRUE;
            break;

        case VT_LPWSTR:
            dwDataSize = 2*(lstrlenW((WCHAR *)pData)+1);
            bSizeNeeded = TRUE;
            break;

        case VT_BSTR:
            if(sizeof(OLECHAR) == 2)         
                dwDataSize = 2*(lstrlenW((WCHAR *)pData)+1);
            else
                dwDataSize = lstrlenA((char *)pData)+1;
            bSizeNeeded = TRUE;
            break;

        default:
            return FALSE;
        }

    // Set the desired values

    if(pType)
        *pType = dwType;
    if(pDataSize)
        *pDataSize = dwDataSize;
    if(pSizeNeeded)
        *pSizeNeeded = bSizeNeeded;
    return TRUE;
}


//***************************************************************************
//
//  CImpComp::CImpComp
//
//  Constructor.  Doesnt do much except call base class constructor.
//
//***************************************************************************

CImpComp::CImpComp(LPUNKNOWN pUnkOuter) : CImpDyn(pUnkOuter)
    {
    return;
    }

//***************************************************************************
//
//  CImpComp::ConvertGetData
//
//  Converts the data from the property set into the format desired by
//    the caller.
//
//***************************************************************************


void CImpComp::ConvertGetData(MODYNPROP *pMo,char * pData,DWORD dwType)
{
    HRESULT sc;
    OLECHAR * poTemp = NULL;
    GUID * piid;
    int iCat;
    DWORD dwLen = dwSkipSize(pData,dwType,iCat);

    // TODO, remove this temporary stuff!!!

    DWORD dwSave = pMo->dwType;
    
    if(pMo->dwType == M_TYPE_LPSTR)
        pMo->dwType = VT_LPSTR;
    else if(pMo->dwType == M_TYPE_DWORD)
        pMo->dwType = VT_UINT;
    else if(pMo->dwType == M_TYPE_INT64)
        pMo->dwType = VT_I8;
    else {
        pMo->dwResult = M_INVALID_TYPE;
        return;
    }

    // bail out if bad data
    if(iCat == UNKNOWN || pData == NULL) {
        pMo->dwResult = ERROR_UNKNOWN;
        return;
        }

    // convert the data into standard form

    VARIANTARG vTemp,* pvConvert;
    pvConvert = (VARIANTARG *)CoTaskMemAlloc(sizeof(VARIANTARG));
    if(pvConvert == NULL) {
        pMo->dwResult = WBEM_E_OUT_OF_MEMORY;
        return;
        }
    VariantInit(&vTemp);
    VariantInit(pvConvert);
    vTemp.cyVal.int64 = 0;    
    pvConvert->cyVal.int64 = 0;

    switch(iCat) {
        case NORMAL:
            memcpy((void *)&vTemp.iVal,pData,dwLen);            
            vTemp.vt = (VARTYPE)dwType;
            break;
       
        case STRING:
            vTemp.bstrVal = (BSTR)(pData + sizeof(DWORD));
            vTemp.vt = VT_LPSTR;
            break;

        case WSTRING:
            vTemp.bstrVal = (BSTR)(pData + sizeof(DWORD));
            vTemp.vt = VT_LPWSTR;
            break;

        case DBLOB:
            vTemp.bstrVal = (BSTR)pData;  // TODO, check on this!
            vTemp.vt = VT_BLOB;
            break;

        case DCLSID:
            piid = (GUID * )pData;
            sc = StringFromIID(*piid,&poTemp); //todo, error checking!
            vTemp.bstrVal = (BSTR)poTemp;
            if(sizeof(OLECHAR) == 1)
                vTemp.vt = VT_LPSTR;
            else
                vTemp.vt = VT_LPWSTR;
            break;
        }
    
    sc = OMSVariantChangeType(pvConvert,&vTemp,0,(VARTYPE)pMo->dwType);
    
    if(poTemp)
        CoTaskMemFree(poTemp);

    // note, that the variant memory is not freeded here since it is
    // actually "owned" by the CProp object which will free it.

    if(sc != S_OK) {
        pMo->dwResult = sc;
        return;
        }

    //todo, in the future, the size should just be s
    // sizeof VARIANTARG! AND WE SHOULD RETURN THE VARIANT
    // POINTER AND NOT THE DATA!!!!!

    int iSize = sizeof(VARIANTARG);
    if(pMo->dwType == VT_LPSTR)
        iSize = lstrlen((LPTSTR)pvConvert->pbstrVal) + 1;
    if(pMo->dwType == VT_LPWSTR)
        iSize = 2*wcslen((WCHAR *)pvConvert->pbstrVal) + 2;
    if(pMo->dwType == VT_BSTR)
        iSize = 2*wcslen((WCHAR *)pvConvert->pbstrVal) + 6;
    
    pMo->pPropertyValue = CoTaskMemAlloc(iSize);
    
    // check for errors

    if(pMo->pPropertyValue == NULL){
        pMo->dwResult = WBEM_E_OUT_OF_MEMORY;
        }
    else {
        if(pMo->dwType != VT_LPSTR)
            memcpy(pMo->pPropertyValue,(void *)&pvConvert->iVal,iSize);
        else
            memcpy(pMo->pPropertyValue,(void *)pvConvert->pbstrVal,iSize);
        pMo->dwResult = ERROR_SUCCESS;
        }
    pMo->dwType = dwSave ;
    
}

//***************************************************************************
//
//  CImpComp::DoSetData
//
//  Conversts the property data into propset format and writes it out.
//
//***************************************************************************

void CImpComp::DoSetData(MODYNPROP *pMo,int iIndex,CProp * pProp,
                    CPropSet * pSet,GUID fmtid,int iPid,LPSTREAM pIStream)
{
    BOOL bExisting = TRUE;
    CBuff OutBuff;
    DWORD dwType;
   
    // If the property doesnt exist, I.e, its new, then create it

    if(pProp == NULL) {
        bExisting = FALSE;
        pProp = new CProp;
        if(pProp == NULL) {
            pMo->dwResult = WBEM_E_OUT_OF_MEMORY;
            return;
            }
        pSet->AddProperty(fmtid,pProp);
        }
        
    // TODO REMOVE, TEMP CODE, create variant
    VARIANTARG vIn;
    VariantInit(&vIn);
    vIn.cyVal.int64 = 0;
    if(pMo->dwType == M_TYPE_DWORD){
        vIn.vt = VT_I4;
        vIn.lVal = *(DWORD *)pMo->pPropertyValue;
        }
    else if(pMo->dwType == M_TYPE_LPSTR){
        vIn.vt = VT_LPSTR;
        vIn.bstrVal = (BSTR)pMo->pPropertyValue;
        }
    else {
        pMo->dwResult = M_TYPE_NOT_SUPPORTED;
        return;
        }
    // TODO REMOVE, TEMP CODE, 
    
    if(iIndex >= 0) {
        // Get the data for an array
        if(!bCreateWriteArray(&vIn,&OutBuff,pMo,iIndex,dwType,
                bExisting,pProp)) {
            return;
            }
        }
    else {
        if(!bGetVariantStats(&vIn, &dwType, NULL, NULL)) {
            pMo->dwResult = ERROR_UNKNOWN;  // bad input data
            return;
            }
        AddVariantData(&vIn,&OutBuff);
        /* temp stuff to generate a sample vector 
        dwType = VT_VARIANT | VT_VECTOR;
        DWORD dwCnt = 3;
        OutBuff.Add((void *)&dwCnt, 4);
        DWORD dwT = VT_I4;
        OutBuff.Add((void *)&dwT, 4);
        DWORD dwData = 0x1234;
        OutBuff.Add((void *)&dwData, 4);

        dwT = VT_I2;
        dwData = 1;
        OutBuff.Add((void *)&dwT, 4);
        OutBuff.Add((void *)&dwData, 2);

        dwData = 2;
        OutBuff.Add((void *)&dwT, 4);
        OutBuff.Add((void *)&dwData, 2);
             end of temp stuff       */
        }
    if(!OutBuff.bOK()) 
        pMo->dwResult = WBEM_E_OUT_OF_MEMORY;  // bad input data
    else {
        pProp->Set(iPid, OutBuff.Get(),dwType,OutBuff.GetSize());
        pSet->WriteToStream(pIStream);
        }
//xxx     pIStream->Commit(STGC_OVERWRITE);
    return;
}

//***************************************************************************
//
//  CImpComp::dwSkip
//
//  Returns the size of a data element.  Also sets the iCat reference to 
//  indicate the category of the data type.
//
//***************************************************************************

DWORD CImpComp::dwSkipSize(char *pData,DWORD dwType, int & iCat)
{
    DWORD cbItem, cbVariant = 0;
    DWORD * dwp;
    iCat = NORMAL;
    dwType &= ~VT_VECTOR;
    if(dwType == VT_VARIANT) {
        dwp =  (DWORD *)pData;
        dwType = *dwp;
        pData += sizeof(DWORD);
        cbVariant = sizeof(DWORD);
        }
    switch (dwType)
        {
        case VT_EMPTY:          // nothing
            cbItem = 0;
            break;

        case VT_I2:             // 2 byte signed int
        case VT_BOOL:           // True=-1, False=0
            cbItem = 2;
            break;

        case VT_I4:             // 4 byte signed int
        case VT_R4:             // 4 byte real
            cbItem = 4;
            break;

        case VT_R8:             // 8 byte real
        case VT_CY:             // currency
        case VT_DATE:           // date
        case VT_I8:             // signed 64-bit int
        case VT_FILETIME:       // FILETIME
            cbItem = 8;
            break;

        case VT_LPSTR:          // null terminated string
        case VT_BSTR:           // binary string
        case VT_BLOB:           // Length prefixed bytes
        case VT_BLOB_OBJECT:    // Blob contains an object
        case VT_BLOB_PROPSET:   // Blob contains a propset

            // Read the DWORD that gives us the size, making
            // sure we increment cbValue.
            dwp = (DWORD *)pData;
            cbItem = sizeof(DWORD)+ *dwp;
            if(dwType != VT_BLOB && dwType != VT_BLOB_OBJECT &&
                dwType != VT_BLOB_PROPSET)
                    iCat = STRING;
                else
                    iCat = DBLOB;
            for(;cbItem % 4; cbItem++); // round up to 4 byte boundry
            break;

        case VT_LPWSTR:         // UNICODE string
            dwp = (DWORD *)pData;
            cbItem = sizeof(DWORD)+ (*dwp) * sizeof(WCHAR);
            iCat = WSTRING;
            for(;cbItem % 4; cbItem++); // round up to 4 byte boundry
            break;

        case VT_CLSID:          // A Class ID
            cbItem = sizeof(CLSID);
            iCat = DCLSID;
            break;

        default:
            iCat = UNKNOWN;
            cbItem = 0;
        }

    return cbItem + cbVariant;
}

//***************************************************************************
//
//  CImpComp::EndBatch
//
//  Called at the end of a batch of Refrest/Update Property calls.  Free up 
//  any cached handles and then delete the handle cache.
//
//***************************************************************************

void CImpComp::EndBatch(MODYNPROP * pMo,CObject *pObj,DWORD dwListSize,BOOL bGet)
{
    if(pObj != NULL) {
        Free(0,(CHandleCache *)pObj);
        delete pObj;
        }
}
//***************************************************************************
//
//  CImpComp::ExtractData
//
//  Gets the property objects data and returns a pointer to it.  This is
//  complicated since the data may be a vector (array) or the data might
//  be a variant, or even a vectory of variants.
//
//  Returns a pointer to the data and also sets the dwType reference to 
//  indicate the type.  Note that the type refernce indicates the acutal
//  data and does not include the vector bit or the fact that the data
//  may be a variant.
//
//***************************************************************************

void * CImpComp::ExtractData( CProp * pProp,DWORD & dwType,int iIndex, BOOL bRaw)
{
    // Get the type and data

    char * pRet;
    int iIgnore;
    pRet = (char *)pProp->Get();
    dwType = pProp->GetType();
    
    // if the type is a vector, then step into the data.  Ie if there are
    // 10 elements and we want the 5th, step over the first four

    if(dwType & VT_VECTOR) {
        DWORD dwCnt;
        DWORD * dwpNumVec = (DWORD *)pRet; // first dword is num elements
        pRet += sizeof(DWORD);
        if(iIndex == -1 || (unsigned)iIndex >= *dwpNumVec) 
            return NULL;
        for(dwCnt = 0; dwCnt < (unsigned)iIndex; dwCnt++) 
            pRet += dwSkipSize(pRet,dwType, iIgnore); 
        }

    dwType &= ~VT_VECTOR;

    // If the data type is VARIANT, then get the actual type out of
    // the variant structure and increment the pointer past it.

    if(dwType == VT_VARIANT && !bRaw) {
        DWORD *dwp = (DWORD *)pRet;
        dwType = *dwp;
        pRet += sizeof(DWORD);
        }
 
    return pRet;
}

//***************************************************************************
//
//  CImpComp::Free
//
//  Frees up cached registry handles starting with position
//  iStart till the end.  After freeing handles, the cache object 
//  member function is used to delete the cache entries.
//
//***************************************************************************

void CImpComp::Free(int iStart, CHandleCache * pCache)
{
    int iCurr,iLast;
    iLast = pCache->lGetNumEntries()-1;
    for(iCurr = iLast; iCurr >= iStart; iCurr--) { 
        LPSTORAGE pIStorage = (LPSTORAGE)pCache->hGetHandle(iCurr);
//        if(iCurr == 0)
//            pIStorage->Commit(STGC_OVERWRITE);
        if(pIStorage != NULL) 
            pIStorage->Release();
        }
    pCache->Delete(iStart); // get cache to delete the entries
}



//***************************************************************************
//
//  CImpComp::GetData
//
//  Gets a pointer to a property object and then calls the conversion
//  routines to convert the properties data.  Note that some properties
//  are stream or storage names and so this routine may be called 
//  recursively to get the acutual data.
//
//***************************************************************************

void CImpComp::GetData(MODYNPROP * pMo, CProvObj & ProvObj, int iToken, 
                LPSTORAGE pIStore, LPSTREAM pIStream, BOOL bGet, BOOL bNewStream)
{
    SCODE hr;
    COleString sTemp;
    DWORD dwType;
    void * pData;
   
    // create a property set object

    CPropSet Set;
    GUID fmtid;
    if(!bNewStream) {
        BOOL bRet = Set.ReadFromStream(pIStream);
        if(bRet == 0) {
            pMo->dwResult = ERROR_UNKNOWN;  //bad stream, probably bad mapping
            return;
            }
        }

    // Get the property. 

    sTemp = ProvObj.sGetToken(iToken++);
    hr = IIDFromString(sTemp,&fmtid);

    if(hr != S_OK) {
        pMo->dwResult = ERROR_UNKNOWN;
        return;
        }
    int iPid = ProvObj.iGetIntExp(iToken,0,pMo->dwOptArrayIndex);
    if(iPid == -1) {
        pMo->dwResult = ERROR_UNKNOWN;  // bad mapping string!
        return;
        }

    CProp * pProp = Set.GetProperty(fmtid,iPid);

    int iIndex = ProvObj.iGetIntExp(iToken,1,pMo->dwOptArrayIndex);

    if(bGet) {
        // Get the data, it might be complicated if the property is an array
        // etc.
       
       if(pProp == NULL) {
            pMo->dwResult = ERROR_UNKNOWN;  // bad mapping string!
            return;
            }
    
        pData = ExtractData(pProp, dwType,iIndex,FALSE);
        if(pData == NULL) 
            pMo->dwResult = ERROR_UNKNOWN;  // bad mapping string!
        else
            ConvertGetData(pMo, (char *)pData, dwType); 
        }
    else
        DoSetData(pMo, iIndex, pProp, &Set,fmtid, iPid, pIStream);
    return;
}

//***************************************************************************
//
//  CImpComp::GetProp
//
//  Gets the value of a single property from an Ole Compound file
//
//***************************************************************************

void CImpComp::GetProp(MODYNPROP * pMo, CProvObj & ProvObj,CObject * pPackage)
{
    int iCnt;
    int iNumSkip;   // number of handles already provided by cache.

    SCODE hr;
    int iLast;
    CHandleCache * pCache = (CHandleCache *)pPackage; 
    CString sRoot,sRet;
    COleString soTemp;
    LPSTORAGE pCurr,pNew;
    LPSTREAM pIStream;

    // Do a second parse on the provider string.  The initial parse
    // is done by the calling routine and it's first token is 
    // the path.  The path token is then parsed 
    // into StorePath and it will have a token for each part of the
    // storage path.  

    CProvObj StorePath(ProvObj.sGetFullToken(1),SUB_DELIM);
    pMo->dwResult = StorePath.dwGetStatus();
    if(pMo->dwResult != WBEM_NO_ERROR)
        return;
    
    // Get the root storage (ie, the file) and possibly other substorages
    // if available in the cache.

    pMo->dwResult = GetRoot(&pCurr,StorePath,ProvObj.sGetFullToken(0),
                        pCache,iNumSkip,TRUE);
    if(pMo->dwResult != ERROR_SUCCESS)  
        return;
    pIStream = (LPSTREAM)pCurr; // just in case cache matched all the way. 

    // Go down the storage path till we get to the stream

    iLast = StorePath.iGetNumTokens() -1;
    for(iCnt = iNumSkip; iCnt <= iLast; iCnt ++) {
        soTemp = StorePath.sGetToken(iCnt);
        if(iCnt == iLast) {
            
            // the last entry in the path specifies the stream

            hr = pCurr->OpenStream(soTemp,NULL,
                STGM_READ|STGM_SHARE_EXCLUSIVE,
                0,&pIStream);
            if(hr != S_OK) {
                pMo->dwResult = hr;  // bad storage name!
                return;
                }
            pMo->dwResult = pCache->lAddToList(soTemp,pIStream);
            if(pMo->dwResult != WBEM_NO_ERROR)
                return;
            }
        else {
            hr = pCurr->OpenStorage(soTemp,NULL,
                STGM_READ|STGM_SHARE_EXCLUSIVE,
                NULL,0,&pNew);
            if(hr != S_OK) {
                pMo->dwResult = hr;  // bad storage name!
                return;
                }
            pMo->dwResult = pCache->lAddToList(soTemp,pNew);
            if(pMo->dwResult != WBEM_NO_ERROR)
                return;
            pCurr = pNew;
            }
       }

    // Finish up getting the data.
   
    GetData(pMo,ProvObj,2, pNew, pIStream, TRUE); 

    return;
}

//***************************************************************************
//
//  CImpComp::GetRoot
//
//  Sets the pointer to an open storage.  Typically this is the root storage,
//  but it might be a substorage if the path matches the storages in the
//  cachee.
//  Returns 0 if OK, otherwise return is error code.  Also the storage 
//  pointer is set as well as the number of substorages to skip in
//  cases where the cache is being used.
//
//***************************************************************************
    
int CImpComp::GetRoot(LPSTORAGE * pRet,CProvObj & Path,const TCHAR * pNewFilePath,CHandleCache * pCache,int & iNumSkip,BOOL bGet)
{
    *pRet = NULL;
    int iRet;
    iNumSkip = 0;
    LPSTORAGE pIStorage;
    SCODE hr;
    if(pCache->lGetNumEntries() > 0){    //I.e. in use
        const TCHAR * pOldFilePath = pCache->sGetString(0);
        if(lstrcmpi(pOldFilePath,pNewFilePath)) //todo, handle nulls here!
    
                 // If the file path has changed, free all
                 // the cached handles and get a new root

                 Free(0,pCache);
             else {
                 
                 // FilePath is in common.
                 // determine how much else is in
                 // common, free what isnt in common, 
                 // return handle of best match.

                 iNumSkip = pCache->lGetNumMatch(1,0,Path);
                 Free(1+iNumSkip,pCache);
                 *pRet = (LPSTORAGE)pCache->hGetHandle(1+iNumSkip);
                 return ERROR_SUCCESS;
                 }
        }

    
    // If the is a Set, and the file doesnt exist, create it
    
    COleString sNew;
    sNew = pNewFilePath;
    if(!bGet)
        if(NOERROR != StgIsStorageFile(sNew)) {
            hr = StgCreateDocfile(sNew,
                    STGM_READWRITE|STGM_SHARE_DENY_WRITE|STGM_CREATE,
                    0L, &pIStorage);
            if(hr == S_OK) { 
                iRet = pCache->lAddToList(pNewFilePath,pIStorage);
                if(iRet)
                    return iRet;
                *pRet = pIStorage;
                }
            return hr;    
        }

    // open an existing file

    hr = StgOpenStorage(sNew,NULL,
                      (bGet) ? GET_FLAGS : PUT_FLAGS,
                      NULL,0L, &pIStorage);
    if(hr == S_OK){ 
        hr = pCache->lAddToList(pNewFilePath,pIStorage);
        *pRet = pIStorage;
        }
    return hr;    
}


//***************************************************************************
//
//  CImpComp::SetProp
//
//  Writes the value of a single property into an Ole Compound file
//
//***************************************************************************

void CImpComp::SetProp(MODYNPROP *  pMo, CProvObj & ProvObj,CObject * pPackage)
{
    int iCnt;
    int iNumSkip;   // number of handles already provided by cache.

    SCODE hr;
    int iLast;
    CHandleCache * pCache = (CHandleCache *)pPackage; 
    CString sRoot,sRet;
    COleString soTemp;
    LPSTORAGE pCurr,pNew;
    LPSTREAM pIStream;
    BOOL bStreamCreated = FALSE;

    // Do a second parse on the provider string.  The initial parse
    // is done by the calling routine and it's first token is 
    // the path.  The path token is then parsed 
    // into StorePath and it will have a token for each part of the
    // storage path.  

    CProvObj StorePath(ProvObj.sGetFullToken(1),SUB_DELIM);
    pMo->dwResult = StorePath.dwGetStatus();
    if(pMo->dwResult != WBEM_NO_ERROR)
        return;
    
    // Get the root storage (ie, the file) and possibly other substorages
    // if available in the cache.

    pMo->dwResult = GetRoot(&pCurr,StorePath,ProvObj.sGetFullToken(0),
                        pCache,iNumSkip,FALSE);
    if(pMo->dwResult != ERROR_SUCCESS)  
        return;
    pIStream = (LPSTREAM)pCurr; // just in case cache matched all the way. 

    // Go down the storage path till we get to the stream

    iLast = StorePath.iGetNumTokens() -1;
    for(iCnt = iNumSkip; iCnt <= iLast; iCnt ++) {
        soTemp = StorePath.sGetToken(iCnt);
        if(iCnt == iLast) {
            
            // the last entry in the path specifies the stream

            hr = pCurr->OpenStream(soTemp,NULL,
                    STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
                    0,&pIStream);
            if(hr == STG_E_FILENOTFOUND) {
                bStreamCreated = TRUE;
                hr = pCurr->CreateStream(soTemp,
                        STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_FAILIFTHERE,
                        0,0,&pIStream);
                }

            if(hr != S_OK) {
                pMo->dwResult = hr;  // bad storage name!
                return;
                }
            pMo->dwResult = pCache->lAddToList(soTemp,pIStream);
            if(pMo->dwResult != WBEM_NO_ERROR)
                return;
            }
        else {
            hr = pCurr->OpenStorage(soTemp,NULL,
                STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
                NULL,0,&pNew);
            if(hr == STG_E_FILENOTFOUND)
                hr = pCurr->CreateStorage(soTemp,
                        STGM_READWRITE|STGM_SHARE_EXCLUSIVE|STGM_FAILIFTHERE,
                        0,0,&pNew);
            if(hr != S_OK) {
                pMo->dwResult = hr;  // bad storage name!
                return;
                }
            pMo->dwResult = pCache->lAddToList(soTemp,pNew);
            if(pMo->dwResult != WBEM_NO_ERROR)
                return;
            pCurr = pNew;
            }
       }

    // Finish up getting the data.
   
    GetData(pMo,ProvObj,2, pNew, pIStream, FALSE, bStreamCreated); 

    return;
}

//***************************************************************************
//
//  CImpComp::StartBatch
//
//  Called at the start of a batch of Refrest/Update Property calls.  Initialize
//  the handle cache.
//
//***************************************************************************

void CImpComp::StartBatch(MODYNPROP * pMo,CObject **pObj,DWORD dwListSize,BOOL bGet)
{
    *pObj = new CHandleCache;
}