/*++

Copyright (C) 1997-2001 Microsoft Corporation

Module Name:

Abstract:

History:

--*/
// OpWrap.cpp

#include "stdafx.h"
#include "OpWrap.h"
#include "resource.h"
#include "OpView.h"
#include "MainFrm.h"

void Trace(LPCTSTR szFormat, ...)
{
	va_list ap;

	TCHAR szMessage[512];

	va_start(ap, szFormat);
	_vstprintf(szMessage, szFormat, ap);
	va_end(ap);

	lstrcat(szMessage, _T("\n"));
	OutputDebugString(szMessage);
}

CString GetEmbeddedObjectText(IUnknown *pUnk)
{
    IWbemClassObjectPtr pObj;
    CString             strRet;

    if (pUnk)
    {
        pUnk->QueryInterface(
            IID_IWbemClassObject, 
            (LPVOID*) &pObj);

        if (pObj != NULL)
        {
            CObjInfo obj;

            obj.SetObj(pObj);

            strRet = obj.GetObjText();
        }
    }
    else
        strRet.LoadString(IDS_NULL);

    return strRet;
}

/////////////////////////////////////////////////////////////////////////////
// CObjInfo

CObjInfo::~CObjInfo()
{
}

BOOL CObjInfo::GetPropValue(int iIndex, CString &strValue, BOOL bTranslate)
{
    BOOL        bRet;
    _variant_t  var;
    CPropInfo   &prop = (*m_ppProps)[iIndex];

    ValueToVariant(iIndex, &var);

    bRet = prop.VariantToString(&var, strValue, bTranslate);

    // Otherwise the destructor freaks out because it doesn't know what to do
    // with VT_I8.
    if (var.vt == VT_I8)
        var.vt = VT_I4;

    return bRet;
}

CString CObjInfo::GetStringPropValue(int iIndex)
{
    _bstr_t strProp = m_ppProps->GetData()[iIndex].m_strName;

    return GetStringPropValue(strProp);
}

CString CObjInfo::GetStringPropValue(LPCWSTR szName)
{
    CString    strRet;
    _variant_t var;

    if (m_pObj != NULL)
    {
        m_pObj->Get(
            szName,
            0,
            &var,
            NULL,
            NULL);
    }

    if (var.vt == VT_BSTR)
        strRet = var.bstrVal;

    return strRet;
}

#define KEY_PROP_FORMAT         _T("\t\t%s (%s)*  = %s\n")
#define NON_KEY_PROP_FORMAT     _T("\t\t%s (%s)  = %s\n")

void CObjInfo::Export(CStdioFile *pFile, BOOL bShowSystem, BOOL bTranslate, 
    int iLevel)
{
#ifdef _DEBUG
    // Don't leave this in here!  It's a hack I'm using to check out the 
    // internal layout of IWbemClassObject.
    DoDebugStuff();
#endif
    
    CPropInfo *pProps;
    int       nItems;

    // Get out if the object isn't valid (like on an object where the
    // refresh failed).
    if (m_pObj == NULL)
        return;

    pProps = GetProps()->GetData();
    nItems = GetProps()->GetSize();

    // Add the object path.
    for (int i = 0; i <= iLevel; i++)
        pFile->Write(_T("\t"), sizeof(TCHAR));

    pFile->WriteString(GetObjText());
    pFile->Write(_T("\n"), sizeof(TCHAR));
    
    for (i = 0; i < nItems; i++)
    {
        if (bShowSystem || pProps[i].m_iFlavor != WBEM_FLAVOR_ORIGIN_SYSTEM)
        {
            CString    strLine,
                       strType,
                       strValue;
            _variant_t vValue;
            IUnknown   **pUnks = NULL;
            int        nUnks = 0,
                       iImage;
            BOOL       bArray;

            pProps[i].GetPropType(strType, &iImage);
            ValueToVariant(i, &vValue);

            if (vValue.vt == VT_UNKNOWN)
            {
                bArray = FALSE;
                nUnks = 1;
                pUnks = &vValue.punkVal; 
            }
            else if (vValue.vt == ((int) VT_UNKNOWN | VT_ARRAY))
            {
                bArray = TRUE;
                nUnks = vValue.parray->rgsabound[0].cElements;
                pUnks = (IUnknown**) vValue.parray->pvData; 
            }
            else
                pProps[i].VariantToString(&vValue, strValue, bTranslate, TRUE);

            strLine.Format(
                pProps[i].m_bKey ? KEY_PROP_FORMAT : NON_KEY_PROP_FORMAT,
                (LPCTSTR) pProps[i].m_strName,
                (LPCTSTR) strType,
                (LPCTSTR) strValue);

            // Add some additional space if we're in a nested level.
            for (int i = 0; i < iLevel; i++)
                pFile->Write(_T("\t"), sizeof(TCHAR));

            pFile->WriteString(strLine);

            // Now do the embedded object stuff.
            if (nUnks)
            {
                for (int i = 0; i < nUnks; i++)
                {
                    IWbemClassObjectPtr pObj;
                    HRESULT             hr;

                    hr =
                        pUnks[i]->QueryInterface(
                            IID_IWbemClassObject, 
                            (LPVOID*) &pObj);

                    if (SUCCEEDED(hr))
                    {
                        CObjInfo info;

                        info.SetObj(pObj);
                        info.SetBaseImage(IMAGE_OBJECT);
                        info.LoadProps(NULL);

                        info.Export(pFile, bShowSystem, bTranslate, iLevel + 2);

                        // This looks bad, but normally this is done by a 
                        // controlling COpWrap.  In this case we faked one, so 
                        // we have to get rid of it ourselves.
                        delete info.GetProps();
                    }
                }
            }

            // Otherwise the destructor freaks out because it doesn't know what to do
            // with VT_I8.
            if (vValue.vt == VT_I8)
                vValue.vt = VT_I4;
        }
    }

    // Leave room for another one.
    pFile->Write(_T("\n"), sizeof(TCHAR));
}

CString CObjInfo::GetObjText()
{
    CString strRet = GetStringPropValue(L"__RELPATH");

    if (strRet.IsEmpty())
    {
        CString strClass = GetStringPropValue(L"__CLASS");

        if (!strClass.IsEmpty())
            strRet.Format(IDS_CLASS_NO_KEY, (LPCTSTR) strClass);
        else
            strRet.LoadString(IDS_NO_KEY);
    }

    return strRet;    
}

#define MAX_STR_SIZE 4096

BOOL CObjInfo::ValueToVariant(int iIndex, VARIANT *pVar)
{
    CPropInfo &prop = (*m_ppProps)[iIndex];
    BOOL      bRet = FALSE;

    VariantClear(pVar);
    pVar->vt = VT_NULL;

    if (prop.m_iHandle && !(prop.m_type & CIM_FLAG_ARRAY))
    {
        BOOL  bString = prop.m_vt == VT_BSTR;
        long  nRead = 0;

        if (bString)
        {
            WCHAR szBuff[MAX_STR_SIZE];

            bRet = 
                SUCCEEDED(m_pAccess->ReadPropertyValue(
                    prop.m_iHandle,
                    prop.m_dwExpectedSize,
                    &nRead,
                    (LPBYTE) szBuff)) && nRead != 0;

            if (bRet)
            {
                pVar->vt = VT_BSTR;
                V_BSTR(pVar) = SysAllocString(szBuff);
            }
        }
        else
        {
            bRet = 
                SUCCEEDED(m_pAccess->ReadPropertyValue(
                    prop.m_iHandle,
                    prop.m_dwExpectedSize,
                    &nRead,
                    (LPBYTE) &V_BSTR(pVar))) && 
                        (nRead == prop.m_dwExpectedSize);

            if (bRet)
                pVar->vt = prop.m_vt;
        }
    }
    else
    {
        bRet = SUCCEEDED(m_pObj->Get(
            _bstr_t(prop.m_strName),
            0,
            pVar,
            NULL,
            NULL));
    }

    return bRet;
}

CString GetStringPropType(int iIndex);
void CObjInfo::GetPropInfo(
    int iIndex, 
    CString &strValue,
    CString &strType,
    int *piImage,
    int *piFlavor,
    BOOL bTranslate)
{
    CPropInfo &info = (*m_ppProps)[iIndex];

    info.GetPropType(strType, piImage);
        
    strValue = _T("");

    *piFlavor = info.m_iFlavor;

    GetPropValue(iIndex, strValue, bTranslate);
}

void CObjInfo::SetObj(IWbemClassObject *pObj)
{
    m_pObj = pObj;

    pObj->QueryInterface(
        IID_IWbemObjectAccess, 
        (LPVOID*) &m_pAccess);
}

BOOL IsVarStringArray(VARIANT *pVar)
{
    return pVar->vt == (VT_BSTR | VT_ARRAY) && 
        pVar->parray->rgsabound[0].cElements != 0;
}

// This converts a BitMap qualifier value string into a bitmask scalar value.
// If the number is a decimal number we have to shift it (e.g. convert '3',
// or bit 3 (zero based), into 0x8.
// If the number is hex, just use wcstoul to convert it into a DWORD.
DWORD CObjInfo::BitmaskStrToValue(LPCWSTR szStr)
{
    WCHAR *szBad;

    if (szStr[0] && towupper(szStr[1]) != 'X')
        return 1 << wcstoul(szStr, &szBad, 0);
    else
        return wcstoul(szStr, &szBad, 0);
}

HRESULT CObjInfo::LoadProps(IWbemServices *pNamespace)
{
    // We need to get the class definition for the amended qualifiers.
    IWbemClassObjectPtr pClass;
    CString             strClass = GetStringPropValue(L"__CLASS");

    if (!m_ppProps)
        m_ppProps = new CPropInfoArray;

    if (pNamespace)
    {
        pNamespace->GetObject(
            _bstr_t(strClass),
            WBEM_FLAG_USE_AMENDED_QUALIFIERS,
            NULL,
            &pClass,
            NULL);
    }


    SAFEARRAY  *pArr = NULL;
    HRESULT    hr;
    CMap<CString, LPCTSTR, BOOL, BOOL>
               mapNameToKey;
    _variant_t vTrue(true);

    // Find our key properties
    if (SUCCEEDED(m_pObj->GetNames(
        L"KEY",
        WBEM_FLAG_ONLY_IF_IDENTICAL,
        &vTrue,
        &pArr)) && pArr->rgsabound[0].cElements != 0)
    {
        BSTR *pNames = (BSTR*) pArr->pvData;

        for (int i = 0; 
            i < pArr->rgsabound[0].cElements;
            i++)
        {
            mapNameToKey.SetAt(_bstr_t(pNames[i]), TRUE);
        }

        SafeArrayDestroy(pArr);
    }


    // Find out how big we need to make our array.
    if (SUCCEEDED(hr = m_pObj->GetNames(
        NULL,
        WBEM_FLAG_ALWAYS,
        NULL,
        &pArr)) && pArr->rgsabound[0].cElements != 0)
    {
        BSTR *pNames = (BSTR*) pArr->pvData;

        m_ppProps->SetSize(pArr->rgsabound[0].cElements);

        for (int i = 0; 
            i < pArr->rgsabound[0].cElements && SUCCEEDED(hr);
            i++)
        {
            BOOL bKey; // This value isn't really used, but we need it for the
                       // mapNameToKey.Lookup call.

            CPropInfo &info = (*m_ppProps)[i];

            CIMTYPE type;

            m_pObj->Get(pNames[i], 0, NULL, &type, &info.m_iFlavor);
            info.m_strName = (LPCWSTR) _bstr_t(pNames[i]);
                
            BOOL bSystemProp = info.m_iFlavor == WBEM_FLAVOR_ORIGIN_SYSTEM;

            // Don't bother getting the handle for system props since they don't
            // exist.
            if (!bSystemProp)
                m_pAccess->GetPropertyHandle(pNames[i], NULL, &info.m_iHandle);

            info.SetType(type);

            if (mapNameToKey.Lookup(info.m_strName, bKey))
                info.m_bKey = TRUE;

            // Load up the valuemap/bitmap values.
            
            IWbemQualifierSetPtr pQuals;

            if (pClass != NULL && 
                SUCCEEDED(pClass->GetPropertyQualifierSet(pNames[i], &pQuals)))
            {
                _variant_t vValues;

                // Try to get the Values/Valuemap stuff.
                if (SUCCEEDED(pQuals->Get(L"Values", 0, &vValues, NULL)) && 
                    IsVarStringArray(&vValues))
                {
                    _variant_t vMap;
                    BOOL       bUsingMap;
                    
                    bUsingMap = SUCCEEDED(pQuals->Get(L"ValueMap", 0, &vMap, NULL)) &&
                                    IsVarStringArray(&vMap) &&
                                    vMap.parray->rgsabound[0].cElements ==
                                        vValues.parray->rgsabound[0].cElements;

                    // Indicate whether this property will be using 
                    // m_mapValues.
                    info.m_bValueMap = 
                        vValues.parray->rgsabound[0].cElements != 0;

                    // Clear this out in case we're refreshing our data.
                    info.m_mapValues.RemoveAll();

                    for (int i = 0; i < vValues.parray->rgsabound[0].cElements;
                        i++)
                    {
                        CString strKey,
                                strValue;

                        if (bUsingMap)
                            strKey = ((BSTR*)(vMap.parray->pvData))[i];
                        else
                            strKey.Format(_T("%d"), i);

                        strValue = ((BSTR*)(vValues.parray->pvData))[i];

                        info.m_mapValues.SetAt(strKey, strValue);
                    }
                }


                // Try to get the Values/Valuemap stuff.
                if (SUCCEEDED(pQuals->Get(L"BitValues", 0, &vValues, NULL)) && 
                    IsVarStringArray(&vValues))
                {
                    _variant_t vMap;
                    BOOL       bUsingBitmap;
                    
                    bUsingBitmap = SUCCEEDED(pQuals->Get(L"BitMap", 0, &vMap, NULL)) &&
                                    IsVarStringArray(&vMap) &&
                                    vMap.parray->rgsabound[0].cElements ==
                                        vValues.parray->rgsabound[0].cElements;

                    // Indicate whether this property will be using 
                    // m_bitmaskValues.
                    info.m_bBitmask = 
                        vValues.parray->rgsabound[0].cElements != 0;

                    if (info.m_bBitmask)
                        info.m_bitmaskValues.SetSize(
                            vValues.parray->rgsabound[0].cElements);

                    for (int i = 0; i < vValues.parray->rgsabound[0].cElements;
                        i++)
                    {
                        CString      strValue;
                        CBitmaskInfo &value = info.m_bitmaskValues[i];

                        if (bUsingBitmap)
                            value.m_dwBitmaskValue = 
                                BitmaskStrToValue(((BSTR*)(vMap.parray->pvData))[i]);
                        else
                            value.m_dwBitmaskValue = 1 << i;

                        value.m_strName = ((BSTR*)(vValues.parray->pvData))[i];
                    }
                }
            }
        }
        
        SafeArrayDestroy(pArr);
    }

    // Load up the methods.
    m_ppProps->LoadMethods(pClass);

    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// COpWrap

COpWrap::COpWrap() :
    m_nCount(0),
    m_status(WBEM_STATUS_COMPLETE),
    m_nExpectedStatusCalls(0),
    m_lRef(0)
{
}

COpWrap::COpWrap(
    WMI_OP_TYPE type, 
    LPCTSTR szText,
    BOOL bOption) :
    m_nCount(0),
    m_strOpText(szText),
    m_type(type),
    m_status(WBEM_STATUS_COMPLETE),
    m_bOption(bOption),
    m_nExpectedStatusCalls(0),
    m_lRef(0)
{
    Init();
}

COpWrap::~COpWrap()
{
}

STDMETHODIMP_(ULONG) CObjSink::AddRef(void) 
{ 
    LONG lRet = InterlockedIncrement(&m_lRef);

    //Trace("Addref = %d", m_lRef);

    return lRet; 
}

STDMETHODIMP_(ULONG) CObjSink::Release(void) 
{ 
    LONG lRet = InterlockedDecrement(&m_lRef);

    //Trace("Release = %d", m_lRef);

    if (lRet == 0)
        delete this;

    return lRet; 
}

HRESULT STDMETHODCALLTYPE CObjSink::Indicate(
    LONG lObjectCount,
    IWbemClassObject **ppObjArray)
{
    return m_pWrap->Indicate(lObjectCount, ppObjArray);
}

HRESULT STDMETHODCALLTYPE CObjSink::SetStatus(
    LONG lFlags,
    HRESULT hResult, 
    BSTR strParam, 
    IWbemClassObject *pObjParam)
{
    return m_pWrap->SetStatus(lFlags, hResult, strParam, pObjParam);
}

const COpWrap& COpWrap::operator=(const COpWrap &other)
{
    m_strOpText = other.m_strOpText;
    m_type = other.m_type;
    m_bOption = other.m_bOption;

    return *this;
}

HRESULT COpWrap::Execute(IWbemServices *pNamespace)
{
    _bstr_t strWQL = L"WQL",
            strText = m_strOpText;

    m_hr = S_OK;

    m_pNamespace = pNamespace;

    m_nCount = 0;
    m_status = WBEM_STATUS_PROGRESS;

    m_strProps.RemoveAll();
    m_piDisplayCols.RemoveAll();

    m_mapClassToProps.Flush();

    m_hr = S_OK;

    m_nExpectedStatusCalls++;

    CObjSink *pSink = new CObjSink(this);

    m_pObjSink = pSink;
    
    // It seems like WMI never does this.  Why?
    //pSink->AddRef();

    switch(m_type)
    {
        case WMI_QUERY:
            m_hr = 
                pNamespace->ExecQueryAsync(
                    strWQL,
                    strText,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS,
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);
            break;

        case WMI_EVENT_QUERY:
            m_hr = 
                pNamespace->ExecNotificationQueryAsync(
                    strWQL,
                    strText,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS |
                        (m_bOption ? WBEM_FLAG_MONITOR : 0),
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);
            break;

        case WMI_ENUM_OBJ:
            m_hr = 
                pNamespace->CreateInstanceEnumAsync(
                    strText,
                    //WBEM_FLAG_FORWARD_ONLY | // WMI doesn't seem to like this.  Why?
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS |
                        (m_bOption ? WBEM_FLAG_DEEP : WBEM_FLAG_SHALLOW),
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);
            break;

        case WMI_GET_OBJ:
            m_hr = 
                pNamespace->GetObjectAsync(
                    strText,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS,
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);
            break;

        case WMI_ENUM_CLASS:
        {
            // First see if we can get the class.  If we do, set m_strOpText
            // to be the 'pretty' class name.
            IWbemClassObject *pClass = NULL;

            m_hr = 
                pNamespace->GetObject(
                    strText,
                    0,
                    NULL,
                    &pClass,
                    NULL);

            if (SUCCEEDED(m_hr))
            {
                _variant_t var;

                pClass->Get(
                    L"__CLASS",
                    0,
                    &var,
                    NULL,
                    NULL);

                if (var.vt == VT_BSTR)
                    m_strOpText = var.bstrVal;

                pClass->Release();
            }

            m_hr =
                pNamespace->CreateClassEnumAsync(
                    strText,
                        //WBEM_FLAG_FORWARD_ONLY | // WMI doesn't seem to like this.  Why?
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS |
                        m_bOption ? WBEM_FLAG_DEEP : WBEM_FLAG_SHALLOW,
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);

            break;
        }
        
        case WMI_GET_CLASS:
            m_hr = 
                pNamespace->GetObjectAsync(
                    strText,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS,
                    NULL,
                    m_pObjSink);
                    //(IWbemObjectSink*) this);
            break;

        case WMI_CREATE_CLASS:
        {
            BSTR             bstrClass = *(LPCWSTR) strText == 0 ? NULL : 
                                            (BSTR) strText;
            IWbemClassObject *pSuperClass = NULL;

            // Get the superclass.
            m_hr = 
                pNamespace->GetObject(
                    bstrClass,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS,
                    NULL,
                    &pSuperClass,
                    NULL);

            if (SUCCEEDED(m_hr))
            {
                IWbemClassObject *pClass = NULL;
                
                // If bstrClass isn't null then we didn't get a top-level 
                // class, which means we now need to spawn a derived class.
                if (bstrClass)
                {
                    m_hr =
                        pSuperClass->SpawnDerivedClass(
                            0,
                            &pClass);

                    if (SUCCEEDED(m_hr))
                    {
                        Indicate(1, &pClass);

                        // Show the user this object already needs to be saved.
                        m_objInfo.SetModified(TRUE);
                        
                        pClass->Release();

                        // Fake the status callback.
                        SetStatus(WBEM_STATUS_COMPLETE, m_hr, NULL, NULL);
                    }
                }
                else
                {
                    Indicate(1, &pSuperClass);

                    // Fake the status callback.
                    SetStatus(WBEM_STATUS_COMPLETE, m_hr, NULL, NULL);
                }

                pSuperClass->Release();
            }

            break;
        }

        case WMI_CREATE_OBJ:
        {
            IWbemClassObject *pClass = NULL;

            // Get the superclass.
            m_hr = 
                pNamespace->GetObject(
                    strText,
                    WBEM_FLAG_USE_AMENDED_QUALIFIERS,
                    NULL,
                    &pClass,
                    NULL);


            if (SUCCEEDED(m_hr))
            {
                IWbemClassObject *pObject = NULL;
                
                m_hr =
                    pClass->SpawnInstance(
                        0,
                        &pObject);

                if (SUCCEEDED(m_hr))
                {
                    Indicate(1, &pObject);

                    // Show the user this object already needs to be saved.
                    m_objInfo.SetModified(TRUE);
                        
                    pObject->Release();

                    // Fake the status callback.
                    SetStatus(WBEM_STATUS_COMPLETE, m_hr, NULL, NULL);
                }

                pClass->Release();
            }

            break;
        }
    }

    if (FAILED(m_hr))
    {
        IWbemClassObjectPtr pErrorObj;
        
        pErrorObj.Attach(GetWMIErrorObject(), FALSE);

        if (!m_nExpectedStatusCalls)
            m_nExpectedStatusCalls = 1;

        SetStatus(WBEM_STATUS_COMPLETE, m_hr, NULL, pErrorObj);
    }

    return m_hr;
}

HRESULT COpWrap::RefreshPropInfo(CObjInfo *pInfo)
{
    if (!pInfo->IsInstance())
    {
        HRESULT hr = pInfo->LoadProps(m_pNamespace);

        if (SUCCEEDED(hr))
            AddPropsToGlobalIndex(pInfo);

        return hr;
    }
    else
        return S_OK;
}

HRESULT COpWrap::LoadPropInfo(CObjInfo *pInfo)
{
    CString strClass = pInfo->GetStringPropValue(L"__CLASS");

    m_cs.Lock();

    CPropInfoArray *pProps;

    if (!m_mapClassToProps.Lookup(strClass, pProps))
    {
        // Since it's not in our map it must have gotten deleted.
        pInfo->SetProps(NULL);

        pInfo->LoadProps(m_pNamespace);

        pProps = pInfo->GetProps();
        m_mapClassToProps.SetAt(strClass, pProps);

        AddPropsToGlobalIndex(pInfo);
    }
    else
        pInfo->SetProps(pProps);

    m_cs.Unlock();

    return S_OK;
}

HRESULT STDMETHODCALLTYPE COpWrap::Indicate(
    LONG lObjectCount,
    IWbemClassObject **ppObjArray)
{
    for (LONG i = 0; i < lObjectCount; i++, m_nCount++)
    {
        CObjPtr ptr(ppObjArray[i]);

        m_listObj.AddTail(ptr);

        CObjInfo *pInfo;
        
        if (!IsObject())
            pInfo = new CObjInfo;
        else
            pInfo = &m_objInfo;

        pInfo->SetObj(ppObjArray[i]);
        pInfo->SetBaseImage(m_iChildImage);

        LoadPropInfo(pInfo);

        g_pOpView->PostMessage(WM_OBJ_INDICATE, (WPARAM) this, (LPARAM) pInfo);
    }

    return S_OK;
}

CString COpWrap::GetWbemErrorText(HRESULT hres)
{
    CString strRet,
            strError,
            strFacility;
	
    IWbemStatusCodeText *pStatus = NULL;

    SCODE sc = CoCreateInstance(
                  CLSID_WbemStatusCodeText, 
                  0, 
                  CLSCTX_INPROC_SERVER,
				  IID_IWbemStatusCodeText, 
                  (LPVOID *) &pStatus);
	
	if (sc == S_OK)
	{
		BSTR bstr = NULL;
		
        if (SUCCEEDED(pStatus->GetErrorCodeText(hres, 0, 0, &bstr)))
        {
			strError = bstr;
            SysFreeString(bstr);
		}

		if (SUCCEEDED(pStatus->GetFacilityCodeText(hres, 0, 0, &bstr)))
		{
			strFacility = bstr;
			SysFreeString(bstr);
		}

		pStatus->Release();
	}

    if (!strError.IsEmpty() && !strFacility.IsEmpty())
    {
        strRet.FormatMessage(
            IDS_ERROR_FORMAT, 
            hres,
            (LPCSTR) strFacility,
            (LPCSTR) strError);
    }
    else
    {
        strRet.FormatMessage(IDS_ERROR_FAILED, hres);
    }

    return strRet;
}

HRESULT STDMETHODCALLTYPE COpWrap::SetStatus(
    LONG lFlags,
    HRESULT hResult, 
    BSTR strParam, 
    IWbemClassObject *pObjParam)
{
    if (m_nExpectedStatusCalls)
    {
        m_status = lFlags;

        m_hr = hResult;

        if (SUCCEEDED(hResult))
            m_strErrorText.Empty();
        else
            m_strErrorText = GetWbemErrorText(hResult);

        g_pOpView->PostMessage(WM_OP_STATUS, (WPARAM) this, 0);

        m_nExpectedStatusCalls--;

        m_pErrorObj = pObjParam;
    }

    return S_OK;
}

void COpWrap::SetHoldsObjects()
{
    switch(m_type)
    {
        case WMI_QUERY:
        {
            CString strQuery = m_strOpText;

            strQuery.MakeUpper();
            strQuery.TrimLeft();
            if (strQuery.Find(_T("ASSOCIATORS")) != -1 ||
                strQuery.Find(_T("REFERENCES")) != -1)
                m_bShowPathsOnly = TRUE;
            else
                m_bShowPathsOnly = FALSE;

            break;
        }

        case WMI_ENUM_OBJ:
        case WMI_GET_OBJ:
        case WMI_EVENT_QUERY:
        case WMI_CREATE_OBJ:
            m_bShowPathsOnly = FALSE;
            break;

        default:
        case WMI_ENUM_CLASS:
        case WMI_GET_CLASS:
        case WMI_CREATE_CLASS:
            m_bShowPathsOnly = TRUE;
    }
}

void COpWrap::GetPropValue(
    CObjInfo *pInfo,
    int iGlobalIndex, 
    CString &strValue,
    BOOL bTranslate)
{
    int iIndex;

    if (pInfo->GetProps()->m_mapGlobalToLocalIndex.Lookup(iGlobalIndex, iIndex))
        pInfo->GetPropValue(iIndex, strValue, bTranslate);
}

CString COpWrap::GetClassName()
{
    CString strRet;

    if (m_type != WMI_ENUM_CLASS)
    {
        if (m_listObj.GetCount())
        {
            CObjPtr    &pObj = m_listObj.GetAt(m_listObj.GetHeadPosition());
            _variant_t var;

            if (SUCCEEDED(pObj->Get(
                L"__CLASS",
                0,
                &var,
                NULL,
                NULL)) && var.vt == VT_BSTR)
            {
                strRet = var.bstrVal;
            }
        }
    }
    else
    {
        return m_strOpText;
    }

    return strRet;
}

CString COpWrap::GetCaption()
{
    CString strRet;

    switch(m_type)
    {
        case WMI_CREATE_CLASS:
            if (!m_strOpText.IsEmpty())
                strRet.Format(IDS_SUBCLASS_OF, (LPCTSTR) m_strOpText);
            else
                strRet.LoadString(IDS_TOPLEVEL_CLASS);

            break;

        case WMI_GET_CLASS:
            strRet = GetClassName();

            if (strRet.IsEmpty())
                strRet = m_strOpText;
                
            break;             

        case WMI_ENUM_CLASS:
            if (!m_strOpText.IsEmpty())
                strRet.Format(IDS_ENUM_CLASS_CAPTION, (LPCTSTR) m_strOpText);
            else
                strRet.LoadString(m_bOption ? IDS_ALL_CLASSES : 
                    IDS_TOP_LEVEL_CLASSES);

            break;             

        case WMI_GET_OBJ:
        case WMI_EVENT_QUERY:
        case WMI_QUERY:
            strRet = m_strOpText;
            break;

        case WMI_CREATE_OBJ:
            strRet.Format(IDS_INSTANCE_OF, (LPCTSTR) m_strOpText);
            break;

        case WMI_ENUM_OBJ:
        {
            CString strClass = GetClassName();

            if (strClass.IsEmpty())
                strClass = m_strOpText;
                
            strRet.Format(IDS_ENUM_OBJ_CAPTION, (LPCTSTR) strClass);

            break;             
        }
    }

    return strRet;    
}

#define DEF_MAX_COLS 100

#define DEF_COL_SIZE 100

int COpWrap::GetPropIndex(LPCTSTR szName, BOOL bAddToDisplay)
{
    for (int i = 0; i <= m_strProps.GetUpperBound(); i++)
    {
        if (m_strProps[i] == szName)
            return i;
    }

    m_strProps.Add(szName);

    if (bAddToDisplay)
    {
        m_piDisplayCols.Add(i);
        
        if (m_piDisplayCols.GetUpperBound() > 
            m_piDisplayColsWidth.GetUpperBound())
        {
            m_piDisplayColsWidth.Add(DEF_COL_SIZE);
        }
    }

    return i;
}

void COpWrap::AddPropsToGlobalIndex(CObjInfo *pInfo)
{
    CPropInfoArray *ppProps = pInfo->GetProps();

    if (ppProps)
    {
        CPropInfo *pProps = ppProps->GetData();
        int       nItems = ppProps->GetSize();

        for (int i = 0; i < nItems; i++)
        {
            int iGlobalIndex = GetPropIndex(pProps[i].m_strName, 
                                !(pProps[i].m_iFlavor == WBEM_FLAVOR_ORIGIN_SYSTEM));
    
            ppProps->m_mapGlobalToLocalIndex.SetAt(iGlobalIndex, i);
        }
    }
}

void CPropInfoArray::LoadMethods(IWbemClassObject *pClass)
{
    HRESULT hr;

    // Flush the list.
    while(m_listMethods.GetCount())
        m_listMethods.RemoveHead();

    m_nStaticMethods = 0;

    if (pClass && SUCCEEDED(hr = pClass->BeginMethodEnumeration(0)))
    {
        BSTR pName = NULL;

        while(1)
        {
            hr = 
                pClass->NextMethod(
                    0,
                    &pName,
                    NULL,
                    NULL);

            if (FAILED(hr) || hr == WBEM_S_NO_MORE_DATA)
                break;

            IWbemQualifierSetPtr pQuals;
            CMethodInfo          method;

            method.m_strName = pName;

            SysFreeString(pName);

            if (SUCCEEDED(hr = pClass->GetMethodQualifierSet(
                pName,
                &pQuals)))
            {
                _variant_t vStatic;

                if (SUCCEEDED(hr = pQuals->Get(
                    L"static",
                    0,
                    &vStatic,
                    NULL)) && vStatic.vt == VT_BOOL && (bool) vStatic == true)
                {
                    method.m_bStatic = TRUE;
                    m_nStaticMethods++;
                }

                _variant_t vDesc;

                if (SUCCEEDED(hr = pQuals->Get(
                    L"Description",
                    0,
                    &vDesc,
                    NULL)) && vDesc.vt == VT_BSTR)
                {
                    method.m_strDescription = vDesc.bstrVal;
                }

                m_listMethods.AddTail(method);
            }
        }
    }

}

int COpWrap::GetImage()
{
    if (m_status == WBEM_STATUS_PROGRESS)
        return m_iImageBase + 1; // + 1 == busy
    else
    {
        if (SUCCEEDED(m_hr))
            // If we're an object, use our m_objInfo to get the image.
            return IsObject() ? m_objInfo.GetImage() : m_iImageBase;
        else
            // + 2 == error
            return m_iImageBase + 2;
    }
}

void COpWrap::CancelOp(IWbemServices *pNamespace)
{
    if (m_status == WBEM_STATUS_PROGRESS)
    {
        HRESULT hr;

        if (FAILED(hr = pNamespace->CancelAsyncCall(m_pObjSink)))
        {
            //((IWbemObjectSink*) this)->Release();
                
            SetStatus(
                WBEM_STATUS_COMPLETE,
                hr, 
                NULL, 
                NULL);
        }
    }
}

IMPLEMENT_SERIAL(COpWrap, CObject, VERSIONABLE_SCHEMA|1)

void COpWrap::Serialize(CArchive &archive)
{
    if (archive.IsLoading())
    {
        int type;

        archive >> type;
        m_type = (WMI_OP_TYPE) type;

        archive >> m_strOpText;
        archive >> m_bOption;

        int nCols;

        archive >> nCols;

        if (nCols > 0)
        {
            m_piDisplayColsWidth.SetSize(nCols);

            for (int i = 0; i < nCols; i++)
            {
                int iVal;

                archive >> iVal;
                m_piDisplayColsWidth[i] = iVal;
            }
        }

        Init();
    }
    else
    {
        archive.SetObjectSchema(1);

        archive << (int) m_type;
        archive << m_strOpText;
        archive << m_bOption;

        int nCols = m_piDisplayColsWidth.GetUpperBound() + 1;

        archive << nCols;

        for (int i = 0; i < nCols; i++)
            archive << m_piDisplayColsWidth[i];
    }
}

void COpWrap::Init()
{
    m_hr = S_OK;

    SetHoldsObjects();

    switch(m_type)
    {
        case WMI_QUERY:
            m_iImageBase = IMAGE_QUERY;
            m_iChildImage = IMAGE_OBJECT;
            break;

        case WMI_ENUM_OBJ:
            m_iImageBase = IMAGE_ENUM_OBJ;
            m_iChildImage = IMAGE_OBJECT;
            break;

        case WMI_CREATE_OBJ:
        case WMI_GET_OBJ:
            m_iImageBase = IMAGE_OBJECT;
            m_iChildImage = IMAGE_OBJECT;
            break;

        case WMI_EVENT_QUERY:
            m_iImageBase = IMAGE_EVENT_QUERY;
            m_iChildImage = IMAGE_OBJECT;
            break;

        case WMI_ENUM_CLASS:
            m_iImageBase = IMAGE_ENUM_CLASS;
            m_iChildImage = IMAGE_CLASS;
            break;

        case WMI_CREATE_CLASS:
        case WMI_GET_CLASS:
            m_iImageBase = IMAGE_CLASS;
            m_iChildImage = IMAGE_CLASS;
            break;
    }
}



/////////////////////////////////////////////////////////////////////////////
// CClassToProps

CClassToProps::~CClassToProps()
{
    Flush();
}

void CClassToProps::Flush()
{
    POSITION       pos = GetStartPosition();
    CPropInfoArray *pProps;
    CString        strClass;

    while (pos)
    {
        GetNextAssoc(pos, strClass, pProps);

        delete pProps;
    }

    RemoveAll();
}


/////////////////////////////////////////////////////////////////////////////
// CPropInfo

// Copy constructor.
CPropInfo::CPropInfo(const CPropInfo& other)
{
    *this = other;
}

const CPropInfo& CPropInfo::operator=(const CPropInfo& other)
{
    m_strName = other.m_strName;
    m_iHandle = other.m_iHandle;
    m_type = other.m_type;
    m_iFlavor = other.m_iFlavor;
    m_bKey = other.m_bKey;
    m_dwExpectedSize = other.m_dwExpectedSize;
    m_vt = other.m_vt;
    m_bSigned = other.m_bSigned;
    m_bValueMap = other.m_bValueMap;
    m_bBitmask = other.m_bBitmask;
    
    // Copy the m_mapValues member.
    POSITION pos = other.m_mapValues.GetStartPosition();

    m_mapValues.RemoveAll();
    while (pos)
    {
        CString strKey,
                strValue;

        other.m_mapValues.GetNextAssoc(pos, strKey, strValue);
        m_mapValues.SetAt(strKey, strValue);
    }

    // Copy the m_bitmaskValues member.
    int nItems = other.m_bitmaskValues.GetSize();

    m_bitmaskValues.SetSize(nItems);

    for (int i = 0; i < nItems; i++)
        m_bitmaskValues[i] = other.m_bitmaskValues[i];

    return *this;
}

void CPropInfo::SetType(CIMTYPE type)
{
    m_type = type;

    m_bSigned = FALSE;

    switch(m_type & ~CIM_FLAG_ARRAY)
    {
	    default:
        case CIM_EMPTY:
            m_dwExpectedSize = 0;
            //m_dwArrayItemSize = 0;
            m_vt = VT_EMPTY;
            break;
            
	    case CIM_SINT8:
            m_bSigned = TRUE;
            m_dwExpectedSize = 1;
            // Fall through...

	    case CIM_UINT8:
            m_dwExpectedSize = 1;
            //m_dwArrayItemSize = 1;
            m_vt = VT_UI1;
            break;
            
	    case CIM_SINT16:
            m_bSigned = TRUE;
            // Fall through...

	    case CIM_CHAR16:
	    case CIM_UINT16:
            m_dwExpectedSize = 2;
            //m_dwArrayItemSize = 4;
            m_vt = VT_I2;
            break;
            
	    case CIM_SINT64:
            m_bSigned = TRUE;
            // Fall through...

	    case CIM_UINT64:
            m_dwExpectedSize = sizeof(__int64);
            //m_dwArrayItemSize = sizeof(__int64);
            m_vt = VT_I8;
            break;
            
	    case CIM_REAL32:
            m_dwExpectedSize = sizeof(float);
            //m_dwArrayItemSize = sizeof(float);
            m_vt = VT_R4;
            break;
            
	    case CIM_REAL64:
            m_dwExpectedSize = sizeof(double);
            //m_dwArrayItemSize = sizeof(double);
            m_vt = VT_R8;
            break;
            
	    case CIM_BOOLEAN:
            m_dwExpectedSize = sizeof(short);
            //m_dwArrayItemSize = sizeof(short);
            m_vt = VT_BOOL;
            break;
            
        case CIM_STRING:
	    case CIM_DATETIME:
	    case CIM_REFERENCE:
            m_dwExpectedSize = MAX_STR_SIZE;
            //m_dwArrayItemSize = sizeof(BSTR*);
            m_vt = VT_BSTR;
            break;

	    case CIM_OBJECT:
            m_dwExpectedSize = sizeof(LPVOID);
            //m_dwArrayItemSize = sizeof(BSTR*);
            m_vt = VT_UNKNOWN;
            break;
            
        case CIM_SINT32:
            m_bSigned = TRUE;
            // Fall through...

        case CIM_UINT32:
            m_dwExpectedSize = sizeof(DWORD);
            //m_dwArrayItemSize = sizeof(DWORD);
            m_vt = VT_I4;
            break;
    }

    if (m_type & CIM_FLAG_ARRAY)
        m_vt = (VARENUM) ((int) m_vt | VT_ARRAY);
}

void CPropInfo::GetPropType(
    CString &strType,
    int *piImage, 
    BOOL bIgnoreArrayFlag)
{
    DWORD dwStrID;

    *piImage = IMAGE_PROP_BINARY;
    
    switch(m_type & ~CIM_FLAG_ARRAY)
    {
	    default:
        case CIM_EMPTY:
            dwStrID = IDS_CIM_EMPTY;
            break;
            
	    case CIM_SINT8:
            dwStrID = IDS_CIM_SINT8;
            break;
            
	    case CIM_UINT8:
            dwStrID = IDS_CIM_UINT8;
            break;
            
	    case CIM_SINT16:
            dwStrID = IDS_CIM_SINT16;
            break;
            
	    case CIM_UINT16:
            dwStrID = IDS_CIM_UINT16;
            break;
            
	    case CIM_SINT64:
            dwStrID = IDS_CIM_SINT64;
            break;
            
	    case CIM_UINT64:
            dwStrID = IDS_CIM_UINT64;
            break;
            
	    case CIM_REAL32:
            dwStrID = IDS_CIM_REAL32;
            break;
            
	    case CIM_REAL64:
            dwStrID = IDS_CIM_REAL64;
            break;
            
	    case CIM_BOOLEAN:
            dwStrID = IDS_CIM_BOOLEAN;
            break;
            
	    case CIM_DATETIME:
            dwStrID = IDS_CIM_DATETIME;
            break;
            
	    case CIM_REFERENCE:
            dwStrID = IDS_CIM_REFERENCE;
            *piImage = IMAGE_PROP_OBJECT;
            break;
            
	    case CIM_CHAR16:
            dwStrID = IDS_CIM_CHAR16;
            break;
            
	    case CIM_OBJECT:
            dwStrID = IDS_CIM_OBJECT;
            break;
            
        case CIM_STRING:
            dwStrID = IDS_CIM_STRING;
            *piImage = IMAGE_PROP_TEXT;
            break;

        case CIM_UINT32:
            dwStrID = IDS_CIM_UINT32;
            break;

        case CIM_SINT32:
            dwStrID = IDS_CIM_SINT32;
            break;
    }

    strType.LoadString(dwStrID);

    if ((m_type & CIM_FLAG_ARRAY) && !bIgnoreArrayFlag)
    {
        CString strArray;

        strArray.LoadString(IDS_CIM_ARRAY);
        strType += _T(" | ");
        strType += strArray;
    }

    if (m_bKey)
        *piImage = *piImage + 1;
}

DWORD64 ato64u(LPCTSTR szVal)
{
    DWORD64 dwRet = 0;

    _stscanf(szVal, _T("%I64u"), &dwRet);

    return dwRet;
}

BOOL CPropInfo::SetArrayItem(VARIANT *pVar, DWORD dwIndex, DWORD dwValue)
{
    ASSERT(m_vt & VT_ARRAY);
    ASSERT(pVar->vt & VT_ARRAY);
    ASSERT(dwIndex < pVar->parray->rgsabound[0].cElements);

    DWORD  dwArrayItemSize = GetArrayItemSize((VARENUM) pVar->vt);
    LPBYTE pDest = (LPBYTE) pVar->parray->pvData + (dwArrayItemSize * dwIndex);
    BOOL   bRet = TRUE;

    switch(dwArrayItemSize)
    {
        case 1:
            *(char*) pDest = dwValue;
            break;

        case 2:
            *(short*) pDest = dwValue;
            break;

        case 4:
            *(int*) pDest = dwValue;
            break;

        default:
            bRet = FALSE;
    }

    return bRet;
}

DWORD CPropInfo::GetArrayItem(VARIANT *pVar, DWORD dwIndex)
{
    ASSERT(m_vt & VT_ARRAY);
    ASSERT(pVar->vt & VT_ARRAY);
    ASSERT(dwIndex < pVar->parray->rgsabound[0].cElements);

    DWORD  dwArrayItemSize = GetArrayItemSize((VARENUM) pVar->vt);
    LPBYTE pSrc = (LPBYTE) pVar->parray->pvData + 
                    (dwArrayItemSize * dwIndex);
    DWORD  dwRet = 0;

    switch(dwArrayItemSize)
    {
        case 1:
            dwRet = *(char*) pSrc;
            break;

        case 2:
            dwRet = *(short*) pSrc;
            break;

        case 4:
            dwRet = *(int*) pSrc;
            break;
    }

    return dwRet;
}

// This one doesn't handle arrays since we'll never go from a single string to
// an array.
BOOL CPropInfo::StringToVariant(LPCSTR szValue, VARIANT *pVar, BOOL bTranslate)
{
    BOOL    bRet;
    VARENUM typeRaw = (VARENUM) (m_vt & ~VT_ARRAY);

    VariantClear(pVar);

    pVar->vt = typeRaw;
    if (pVar->vt == VT_I8)
        pVar->vt = VT_BSTR;

    LPVOID pData = &V_BSTR(pVar);

    bRet = StringToVariantBlob(szValue, pData, bTranslate);
    
    return bRet;        
}

// TODO: Need to be able to accept dates with month names.
LPTSTR IntToDMTF(LPTSTR szOut, int iValue, int iDigits)
{
    _ASSERT(iDigits <= 6 && iDigits >= 0);

    if (iValue == -1)
    {
        memcpy(szOut, _T("******"), iDigits * sizeof(TCHAR));
        szOut[iDigits] = 0;
    }
    else
    {
        char szFormat[100] = _T("%0*d");

        szFormat[2] = '0' + iDigits;
        
        wsprintf(szOut, szFormat, iValue);
    }

    return szOut;
}

enum LOCALE_TOKEN
{
    L_UNKNOWN,
    
    L_MONTH_1,
    L_MONTH_2,
    L_MONTH_3,
    L_MONTH_4,
    L_MONTH_5,
    L_MONTH_6,
    L_MONTH_7,
    L_MONTH_8,
    L_MONTH_9,
    L_MONTH_10,
    L_MONTH_11,
    L_MONTH_12,

    L_DATE_SEP,
    L_TIME_SEP,

    L_AM,
    L_PM,

    L_UTC
};

class CLocaleInfo
{
public:
    TCHAR szDateSep[MAX_PATH],
          szTimeSep[MAX_PATH],
          szPM[MAX_PATH],
          szAM[MAX_PATH],
          szUTC[MAX_PATH],
          szLongMonths[12][MAX_PATH],
          szShortMonths[12][MAX_PATH];

    CLocaleInfo() { Init(); }
    
    void Init();
    LOCALE_TOKEN GetNextToken(LPCTSTR *ppszCurrent);

protected:
    BOOL CheckAndIncrement(LPCTSTR szCheckAgainst, LPCTSTR *ppszCurrent);
};

LOCALE_TOKEN CLocaleInfo::GetNextToken(LPCTSTR *ppszCurrent)
{
    if (CheckAndIncrement(szDateSep, ppszCurrent))
        return L_DATE_SEP;
    else if (CheckAndIncrement(szTimeSep, ppszCurrent))
        return L_TIME_SEP;
    else if (CheckAndIncrement(szAM, ppszCurrent))
        return L_AM;
    else if (CheckAndIncrement(szPM, ppszCurrent))
        return L_PM;
    else if (CheckAndIncrement(szUTC, ppszCurrent))
        return L_UTC;
    else
    {
        for (int i = 0; i < 12; i++)
        {
            if (CheckAndIncrement(szLongMonths[i], ppszCurrent))
                return (LOCALE_TOKEN) (L_MONTH_1 + i);
        }

        for (i = 0; i < 12; i++)
        {
            if (CheckAndIncrement(szShortMonths[i], ppszCurrent))
                return (LOCALE_TOKEN) (L_MONTH_1 + i);
        }
    }

    return L_UNKNOWN;
}

BOOL CLocaleInfo::CheckAndIncrement(LPCTSTR szCheckAgainst, LPCTSTR *ppszCurrent)
{
    // Get past the spaces.
    while (isspace(**ppszCurrent))
        (*ppszCurrent)++;
            
    int  nLen = _tcslen(szCheckAgainst);
    BOOL bRet;

    if (!_tcsncmp(szCheckAgainst, *ppszCurrent, nLen))
    {
        bRet = TRUE;
        *ppszCurrent += nLen;
    }
    else
        bRet = FALSE;

    return bRet;
        
}

void CLocaleInfo::Init()
{
    GetLocaleInfo(
        LOCALE_USER_DEFAULT,
        LOCALE_SDATE,
        szDateSep,
        sizeof(szDateSep) / sizeof(TCHAR));
    _tcsupr(szDateSep);

    GetLocaleInfo(
        LOCALE_USER_DEFAULT,
        LOCALE_STIME,
        szTimeSep,
        sizeof(szTimeSep) / sizeof(TCHAR));
    _tcsupr(szTimeSep);

    GetLocaleInfo(
        LOCALE_USER_DEFAULT,
        LOCALE_S2359,
        szPM,
        sizeof(szPM) / sizeof(TCHAR));
    _tcsupr(szPM);

    GetLocaleInfo(
        LOCALE_USER_DEFAULT,
        LOCALE_S1159,
        szAM,
        sizeof(szAM) / sizeof(TCHAR));
    _tcsupr(szAM);

    for (int i = 0; i < 12; i++)
    {
        GetLocaleInfo(
            LOCALE_USER_DEFAULT,
            LOCALE_SMONTHNAME1 + i,
            szLongMonths[i],
            sizeof(szLongMonths[0]) / sizeof(TCHAR));
        _tcsupr(szLongMonths[i]);

        GetLocaleInfo(
            LOCALE_USER_DEFAULT,
            LOCALE_SABBREVMONTHNAME1 + i,
            szShortMonths[i],
            sizeof(szShortMonths[0]) / sizeof(TCHAR));
        _tcsupr(szShortMonths[i]);
    }

    CString strUTC;

    strUTC.LoadString(IDS_UTC_TOKEN);

    lstrcpy(szUTC, strUTC);
}

enum DATE_STATE
{
    S_BEGIN,
    S_MONTH,
    S_DAY,
    S_YEAR,
    S_HOUR,
    S_MINUTE,
    S_SECOND,
    S_NEED_UTC,
};

BOOL DoAMPM(LOCALE_TOKEN token, int *piHour, DATE_STATE *pState)
{
    BOOL bRet;

    ASSERT(*piHour != -1);

    if (*pState == S_HOUR || *pState == S_MINUTE || *pState == S_SECOND)
    {
        bRet = TRUE;

        if (*piHour == 12 && token == L_AM)
            *piHour = 0;
        else if (*piHour < 12 && token == L_PM)
            *piHour += 12;

        *pState = S_NEED_UTC;
    }
    else
        bRet = FALSE;

    return bRet;
}

BOOL DoUTC(int *piOffset, LPCTSTR *pszCurrent, TCHAR *pcOffsetSign, DATE_STATE *pState)
{
    // Until we prove otherwise...
    BOOL  bRet = FALSE;
    TCHAR cSign = **pszCurrent;

    if (cSign == '+' || cSign == '-')
    {
        // Get past the sign.
        (*pszCurrent)++;

        if (isdigit(**pszCurrent))
        {
            *piOffset = _ttoi(*pszCurrent) * 60;

            // Get past the number.
            while (isdigit(**pszCurrent))
                (*pszCurrent)++;
                        
            if (**pszCurrent != ')')
            {
                // Get past the separator.
                while (!isdigit(**pszCurrent) && **pszCurrent)
                    (*pszCurrent)++;
                        
                *piOffset += _ttoi(*pszCurrent);
            }
                    
            // Find the end of this thing.
            while (**pszCurrent != ')' && **pszCurrent)
                (*pszCurrent)++;
                        
            if (**pszCurrent == ')')
            {
                *pcOffsetSign = cSign;

                *pState = S_BEGIN;

                (*pszCurrent)++;

                bRet = TRUE;
            }
        }
    }

    return bRet;
}

BOOL StringDateTimeToDMTF(LPCTSTR szDateTime, CString &strDateTime)
{
    CLocaleInfo info;
    DATE_STATE  state = S_BEGIN;
    BOOL        bMonthFromString = FALSE,
                bPM = FALSE,
                bAM = FALSE;
    int         iDay = -1,
                iMonth = -1,
                iYear = -1,
                iHour = -1,
                iMin = -1,
                iSec = -1,
                iMicro = -1,
                iOffset = -1;
    char        szOffsetSign[2] = _T("*");
    BOOL        bRet = TRUE;
    CString     strTemp = szDateTime;

    // So we can do case insensitive compares.
    strTemp.MakeUpper();

    LPCTSTR szCurrent = strTemp;

    while (*szCurrent && bRet)
    {
        while (isspace(*szCurrent))
            szCurrent++;
            
        ////////////////////////////////////////////////////
        // State action == number found
        if (isdigit(*szCurrent))
        {
            // Reset if we were potentially looking for a UTC and we didn't get it.
            if (state == S_NEED_UTC)
                state = S_BEGIN;

            int iNumber = _ttoi(szCurrent);
            
            // Get past the current number.
            while (isdigit(*szCurrent))
                szCurrent++;
                        
            LOCALE_TOKEN token = info.GetNextToken(&szCurrent);

            switch(state)
            {
                ////////////////////////////////////////////////////
                // Begin state
                case S_BEGIN:
                    if (token == L_TIME_SEP)
                    {
                        iHour = iNumber;
                        state = S_HOUR;
                    }
                    else if (token == L_DATE_SEP || token == L_UNKNOWN)
                    {
                        iMonth = iNumber;
                        state = S_MONTH;
                    }
                    else if (token == L_AM || token == L_PM)
                    {
                        iHour = iNumber;
                        
                        state = S_HOUR;
                        bRet = DoAMPM(token, &iHour, &state);
                        //bAM = token == L_AM;
                        //bPM = !bAM;

                        //state = S_NEED_UTC;
                    }
                    else
                        bRet = FALSE;

                    break;

                ////////////////////////////////////////////////////
                // Date states
                case S_MONTH:
                    if (token == L_DATE_SEP)
                    {
                        iDay = iNumber;
                        state = S_DAY;
                    }
                    else if (token == L_UNKNOWN)
                    {
                        iYear = iNumber;
                        state = S_BEGIN;
                    }
                    else
                        bRet = FALSE;

                    break;

                case S_DAY:
                    if (token == L_UNKNOWN)
                    {
                        iYear = iNumber;
                        state = S_BEGIN;
                    }
                    else
                        bRet = FALSE;

                    break;

                //case S_YEAR:
                //    iYear = iNumber;
                //    state = S_BEGIN;
                //    break;


                ////////////////////////////////////////////////////
                // Time states
                case S_HOUR:
                    iMin = iNumber;

                    if (token == L_TIME_SEP)
                        state = S_MINUTE;
                    else if (token == L_AM || token == L_PM)
                        bRet = DoAMPM(token, &iHour, &state);
                    else if (token == L_UTC)
                        bRet = DoUTC(&iOffset, &szCurrent, &szOffsetSign[0], &state);
                    else
                        bRet = FALSE;

                    break;

                case S_MINUTE:
                    iSec = iNumber;

                    if (token == L_AM || token == L_PM)
                        bRet = DoAMPM(token, &iHour, &state);
                    else if (token == L_UTC)
                        bRet = DoUTC(&iOffset, &szCurrent, &szOffsetSign[0], &state);
                    else
                        bRet = FALSE;

                    break;

                default:
                    bRet = FALSE;
                    break;
            }
        } // End of state action == number found.
        else
        {
            ////////////////////////////////////////////////////
            // State action == some token
            LOCALE_TOKEN token = info.GetNextToken(&szCurrent);

            // Reset if we were potentially looking for a UTC and we didn't get it.
            if (state == S_NEED_UTC && token != L_UTC)
                state = S_BEGIN;

            // token == string month found
            if (token >= L_MONTH_1 && token <= L_MONTH_12)
            {
                // Move past any non-digits beyond the string date, such as a 
                // separator or a comma, etc.
                while (!isdigit(*szCurrent) && *szCurrent)
                    szCurrent++;

                if (state == S_BEGIN)
                {
                    iMonth = token - L_MONTH_1 + 1;
                    state = S_MONTH;
                    bMonthFromString = TRUE;
                }
                else if (state == S_MONTH && !bMonthFromString)
                {
                    iDay = iMonth;
                    iMonth = token - L_MONTH_1 + 1;
                    state = S_DAY;
                }
                else
                    bRet = FALSE;
            }
            else if (token == L_AM || token == L_PM)
            {
                if (state == S_HOUR || state == S_MINUTE || state == S_SECOND)
                {
                    //bAM = token == L_AM;
                    //bPM = !bAM;
                    bRet = DoAMPM(token, &iHour, &state);

                    //state = S_NEED_UTC;
                }
                else
                    bRet = FALSE;
            }
            else if (state == S_NEED_UTC)
            {
                bRet = DoUTC(&iOffset, &szCurrent, &szOffsetSign[0], &state);
            }
            else if (token == L_UNKNOWN)
                szCurrent++;
            else
                bRet = FALSE;
        }
    }

    // At this point, we may need to juggle our day and month around, depending
    // on what the order is for the current locale.
    if (iDay != -1 && iMonth != -1 && iYear != -1)
    {
        TCHAR szOrder[2] = _T("0");

        GetLocaleInfo(
            LOCALE_USER_DEFAULT,
            LOCALE_IDATE,
            szOrder,
            sizeof(szOrder) / sizeof(TCHAR));

        // dd/mm/yy
        if (*szOrder == '1')
        {
            int iTemp = iDay;

            iDay = iMonth;
            iMonth = iDay;
        }
        // yy/mm/dd
        else if (*szOrder == '2')
        {
            int iTemp = iDay;

            iDay = iYear;
            iYear = iDay;
        }
    }


    // Finally, splice it all together.
    TCHAR szOut[100] = _T(""),
          szTemp[20];
    
    lstrcat(szOut, IntToDMTF(szTemp, iYear, 4));
    lstrcat(szOut, IntToDMTF(szTemp, iMonth, 2));
    lstrcat(szOut, IntToDMTF(szTemp, iDay, 2));
    lstrcat(szOut, IntToDMTF(szTemp, iHour, 2));
    lstrcat(szOut, IntToDMTF(szTemp, iMin, 2));
    lstrcat(szOut, IntToDMTF(szTemp, iSec, 2));
    lstrcat(szOut, _T("."));
    lstrcat(szOut, IntToDMTF(szTemp, iMicro, 6));
    lstrcat(szOut, szOffsetSign);
    lstrcat(szOut, IntToDMTF(szTemp, iOffset, 3));

    strDateTime = szOut;

    return TRUE;
}

BOOL IsDMTF(LPCTSTR szDate)
{
    for (int i = 0; i < 14; i++)
    {
        if (!isdigit(szDate[i]) && szDate[i] != '*')
            return FALSE;
    }

    if (szDate[14] != '.')
        return FALSE;

    for (i = 15; i < 21; i++)
    {
        if (!isdigit(szDate[i]) && szDate[i] != '*')
            return FALSE;
    }

    if (szDate[21] != '-' && szDate[21] != '+' && szDate[21] != '*')
        return FALSE;

    for (i = 22; i < 25; i++)
    {
        if (!isdigit(szDate[i]) && szDate[i] != '*')
            return FALSE;
    }

    return TRUE;
}

BOOL CPropInfo::StringToVariantBlob(LPCTSTR szValue, LPVOID pData, 
    BOOL bTranslate)
{
    BOOL    bRet = TRUE;
    char    *szTemp;
    CString strValue;
    VARENUM typeRaw = (VARENUM) (m_vt & ~VT_ARRAY);

    // This one is special-cased from the others because the value will
    // usually be in a localized form, even if bTranslate == FALSE.
    if (typeRaw == VT_BOOL)
    {
        CString strTrue;

        strTrue.LoadString(IDS_TRUE);

        if (!lstrcmpi(strTrue, szValue))
            *(short*) pData = VARIANT_TRUE;
        else
            *(short*) pData = atoi(szValue) ? VARIANT_TRUE : VARIANT_FALSE;
    } 
    else
    {
        // Translate if necessary.
        if (bTranslate && m_bValueMap)
        {
            // TODO: We could make this faster if we added a reverse lookup 
            // map.  For now we'll do a sequential search.
            POSITION pos = m_mapValues.GetStartPosition();
            CString  strKey;

            while(pos)
            {
                m_mapValues.GetNextAssoc(pos, strKey, strValue);
                if (!lstrcmpi(szValue, strValue))
                {
                    szValue = strKey;
                    break;
                }
            }
        }

        switch(typeRaw)
        {
            case VT_I8:
            case VT_BSTR:
            {
                if (GetRawCIMType() != CIM_DATETIME)
                    *(BSTR*) pData = _bstr_t(szValue).copy();
                else
                {
                    if (!IsDMTF(szValue))
                    {
                        CString strRet;
                        
                        StringDateTimeToDMTF(szValue, strRet);

                        _ASSERT(IsDMTF(strRet));
                    
                        *(BSTR*) pData = strRet.AllocSysString();
                    }
                    else
                        *(BSTR*) pData = _bstr_t(szValue).copy();
                }

                break;
            }

            case VT_R8:
                *(double*) pData = atof(szValue);
                break;

            case VT_R4:
                *(float*) pData = atof(szValue);
                break;

            case VT_UI1:
                *(char*) pData = m_bSigned ? atoi(szValue) : strtoul(szValue, &szTemp, 10);
                break;
             
            case VT_I2:
                *(short*) pData = m_bSigned ? atoi(szValue) : strtoul(szValue, &szTemp, 10);
                break;

            case VT_I4:
                *(int*) pData = m_bSigned ? atoi(szValue) : strtoul(szValue, &szTemp, 10);
                break;

            //case VT_I8:
            //    *(DWORD64*) pData = m_bSigned ? _atoi64(szValue) : ato64u(szValue);
            //    break;
        }
    }

    return bRet;        
}

// We have to pass the vt as contained on the actual variant because WMI sometimes
// uses a different VARENUM for Gets/Puts (go figure).
BOOL CPropInfo::VariantBlobToString(VARENUM vt, LPVOID pData, CString &strValue, 
    BOOL bTranslate, BOOL bQuoteStrings)
{
    VARENUM vtRaw = (VARENUM) (vt & ~VT_ARRAY);

    strValue.Empty();

    switch(vtRaw)
    {
        case VT_BSTR:
            strValue = *(BSTR*) pData;
            break;

        case VT_R8:
            strValue.Format(_T("%.5f"), *(double*) pData);
            break;

        case VT_R4:
            strValue.Format(_T("%.5f"), *(float*) pData);
            break;

        case VT_UI1:
        {
            if (m_bSigned)
                strValue.Format(_T("%d"), *(char*) pData);
            else
                strValue.Format(_T("%u"), *(BYTE*) pData);

            break;
        }
             
        case VT_I2:
        {
            if (m_bSigned)
                strValue.Format(_T("%d"), *(short*) pData);
            else
                strValue.Format(_T("%u"), *(WORD*) pData);

            break;
        }

        case VT_I4:
        {
            if (m_bSigned)
                strValue.Format(_T("%d"), *(int*) pData);
            else
                strValue.Format(_T("%u"), *(DWORD*) pData);

            break;
        }

        case VT_I8:
        {
            if (m_bSigned)
                strValue.Format(_T("%I64d"), *(__int64*) pData);
            else
                strValue.Format(_T("%I64u"), *(DWORD64*) pData);

            break;
        }

        case VT_BOOL:
            strValue.LoadString(*(short*) pData ? IDS_TRUE : IDS_FALSE);
            break;

        case VT_UNKNOWN:
            //strValue.Format(IDS_EMBEDDED_OBJECT, *(IUnknown*) pData);
            strValue = GetEmbeddedObjectText(*(IUnknown**) pData);
            break;
    }

    if (bTranslate && !strValue.IsEmpty())
    {
        if (m_bValueMap)
        {
            CString strKey = strValue;

            m_mapValues.Lookup(strKey, strValue);
        }
        else if (m_bBitmask)
        {
            DWORD nSize = m_bitmaskValues.GetSize(),
                  dwValue = *(DWORD*) pData;

            // Clear this out.
            strValue.Empty();

            for (int i = 0; i < nSize; i++)
            {
                CString strPart;

                if (dwValue & m_bitmaskValues[i].m_dwBitmaskValue)
                {
                    if (!strValue.IsEmpty())
                        strValue += _T(" | ");

                    strValue += m_bitmaskValues[i].m_strName;
                }
            }
        }
        else if (GetRawCIMType() == CIM_DATETIME)
        {
            struct DATETIME_INFO
            {
                TCHAR szYear[5],
                      szMonth[3],
                      szDay[3],
                      szHour[3],
                      szMin[3],
                      szSec[3],
                      szMicro[7],
                      cSep,
                      szOffset[4];
            } datetime;

            ZeroMemory(&datetime, sizeof(datetime));

            if (_stscanf(
                strValue,
                _T("%4c%2c%2c%2c%2c%2c.%6c%c%3c"),
                datetime.szYear,
                datetime.szMonth,
                datetime.szDay,
                datetime.szHour,
                datetime.szMin,
                datetime.szSec,
                datetime.szMicro,
                &datetime.cSep,
                datetime.szOffset) == 9)
            {
                SYSTEMTIME systime;
                TCHAR      szDate[100] = _T(""),
                           szTime[100] = _T("");

                // _stscanf seems to be screwing this up, so fix it.
                datetime.szOffset[3] = 0;

                ZeroMemory(&systime, sizeof(systime));

                // Figure out the date.
                if (*datetime.szYear != '*' && *datetime.szMonth != '*')
                {
                    DWORD dwFlag;
                    TCHAR szFormat[10],
                          *pszFormat;

                    systime.wYear = (WORD) _ttoi(datetime.szYear);
                    systime.wMonth = (WORD) _ttoi(datetime.szMonth);

                    if (*datetime.szDay == '*')
                    {
                        // This would have been nice to use, but it only lives
                        // on W2K.
                        //dwFlag = DATE_YEARMONTH;
                        TCHAR szSep[100];

                        if (GetLocaleInfo(
                            LOCALE_USER_DEFAULT,
                            LOCALE_SDATE,
                            szSep,
                            sizeof(szSep) / sizeof(TCHAR)))
                        {
                            // Otherwise GetDateFormat will fail, even though
                            // it doesn't need it!
                            systime.wDay = 1;

                            wsprintf(szFormat, _T("MM%syyyy"), szSep);
                            dwFlag = 0;
                            pszFormat = szFormat;
                        }
                    }
                    else
                    {
                        dwFlag = DATE_SHORTDATE;
                        pszFormat = NULL;
                        systime.wDay = (WORD) _ttoi(datetime.szDay);
                    }
                    
                    GetDateFormat(
                        LOCALE_USER_DEFAULT,
                        dwFlag,
                        &systime,
                        pszFormat,
                        szDate,
                        sizeof(szDate) / sizeof(TCHAR));
                }

                // Figure out the time.
                if (*datetime.szHour != '*')
                {
                    DWORD dwFlag = 0;

                    systime.wHour = (WORD) _ttoi(datetime.szHour);

                    if (*datetime.szMin == '*')
                        dwFlag = TIME_NOMINUTESORSECONDS;
                    else
                    {
                        systime.wMinute = (WORD) _ttoi(datetime.szMin);

                        if (*datetime.szSec == '*')
                            dwFlag = TIME_NOSECONDS;
                        else
                            systime.wSecond = (WORD) _ttoi(datetime.szSec);
                    }

                    GetTimeFormat(
                        LOCALE_USER_DEFAULT,
                        dwFlag,
                        &systime,
                        NULL,
                        szTime,
                        sizeof(szTime) / sizeof(TCHAR));

                    if (*szTime && 
                        (datetime.cSep == '-' || datetime.cSep == '+') && 
                        *datetime.szOffset != '*')
                    {
                        CString strOffset;
                        DWORD   dwMinutes = _ttoi(datetime.szOffset);

                        strOffset.FormatMessage(
                            IDS_UTC, datetime.cSep, dwMinutes / 60, 
                            dwMinutes % 60);

                        strcat(szTime, strOffset);
                    }
                }

                strValue = szDate;
                if (*szTime)
                {
                    if (!strValue.IsEmpty())
                        strValue += _T(" ");

                    strValue += szTime;
                }
            }
        }
    }

    if (bQuoteStrings && GetRawCIMType() == CIM_STRING)
    {
        CString strTemp;

        strTemp.Format(_T("\"%s\""), (LPCTSTR) strValue);

        strValue = strTemp;
    }

    return TRUE;
}

DWORD CPropInfo::GetArrayItemCount(VARIANT *pVar)
{
    return pVar->parray->rgsabound[0].cElements;
}

BOOL CPropInfo::ArrayItemToString(VARIANT *pVar, DWORD dwIndex, 
    CString &strValue, BOOL bTranslate)
{
    ASSERT(m_vt & VT_ARRAY);
    ASSERT(pVar->vt & VT_ARRAY);
    ASSERT(dwIndex < pVar->parray->rgsabound[0].cElements);

    BOOL bRet = TRUE;

    LPBYTE  pData = (LPBYTE) pVar->parray->pvData;
    CString strItem;
    DWORD   dwArrayItemSize = GetArrayItemSize((VARENUM) pVar->vt);

    if (VariantBlobToString(
        (VARENUM) pVar->vt,
        pData + (dwArrayItemSize * dwIndex), 
        strItem, 
        bTranslate,
        FALSE))
        strValue = strItem;
    
    return bRet;        
}

BOOL CPropInfo::PrepArrayVar(VARIANT *pVar, DWORD dwSize)
{
    BOOL bRet = FALSE;

    ASSERT((m_vt & VT_ARRAY) != 0);

    VariantClear(pVar);

    if (dwSize > 0)
    {
        SAFEARRAYBOUND bound;

        pVar->vt = m_vt;

        if (pVar->vt == (VT_I8 | VT_ARRAY))
            pVar->vt = VT_BSTR | VT_ARRAY;

        bound.lLbound = 0;
        bound.cElements = dwSize;

        pVar->parray = SafeArrayCreate(pVar->vt & ~VT_ARRAY, 1, &bound);

        bRet = pVar->parray != NULL;
    }
    else
    {
        pVar->vt = VT_NULL;
        bRet = TRUE;
    }

    return bRet;
}

BOOL CPropInfo::StringToArrayItem(LPCTSTR szItem, VARIANT *pVar, DWORD dwIndex, 
    BOOL bTranslate)
{
    //ASSERT(m_vt & VT_ARRAY);
    ASSERT(pVar->vt & VT_ARRAY);
    ASSERT(dwIndex < pVar->parray->rgsabound[0].cElements);

    BOOL   bRet = TRUE;
    LPBYTE pData = (LPBYTE) pVar->parray->pvData;
    DWORD  dwArrayItemSize = GetArrayItemSize((VARENUM) pVar->vt);

    bRet = 
        StringToVariantBlob(szItem, pData + (dwArrayItemSize * dwIndex), 
            bTranslate);
    
    return bRet;        
}

int CPropInfo::GetArrayItemSize(VARENUM vt)
{
    switch(vt & ~VT_ARRAY)
    {
        case VT_UI1:
        case VT_I1:
            return 1;
            
	    case VT_BOOL:
        case VT_I2:
            return 2;

        case VT_I4:
            return 4;

        case VT_I8:
            return 8;

	    case VT_UNKNOWN:
        case VT_BSTR:
            return sizeof(BSTR*);

	    case VT_R4:
            return sizeof(float);
            
	    case VT_R8:
            return sizeof(double);

	    default:
            // This shouldn't happen!  If it does, add another case statement.
            ASSERT(FALSE);
            return 0;
    }
}

BOOL CPropInfo::VariantToString(VARIANT *pVar, CString &strValue, 
    BOOL bTranslate, BOOL bQuoteStrings)
{
    BOOL bRet = TRUE;

    if (pVar->vt != VT_NULL)
    {
        if (pVar->vt & VT_ARRAY)
        {
            DWORD dwArrayItemSize = GetArrayItemSize((VARENUM) pVar->vt);

            strValue.Empty();

            LPBYTE  pData = (LPBYTE) pVar->parray->pvData;
            CString strItem;

            for (int i = 0; i < pVar->parray->rgsabound[0].cElements; 
                i++, pData += dwArrayItemSize)
            {
                VariantBlobToString((VARENUM) pVar->vt, pData, strItem, 
                    bTranslate, bQuoteStrings);

                if (i != 0)
                    strValue += _T(", ");

                strValue += strItem;
            }
        }
        else
        {
            LPVOID pData = &V_BSTR(pVar);

            VariantBlobToString((VARENUM) pVar->vt, pData, strValue, 
                bTranslate, bQuoteStrings);
        }
    }
    else
        strValue.LoadString(IDS_NULL);
    
    return bRet;        
}

// Use this to get __ExtendedStatus.
IWbemClassObject *GetWMIErrorObject()
{
    IWbemClassObject *pObj = NULL;
    IErrorInfo       *pInfo = NULL;

    if (SUCCEEDED(GetErrorInfo(0, &pInfo)) && pInfo != NULL)
    {
        pInfo->QueryInterface(IID_IWbemClassObject, (LPVOID*) &pObj);

        pInfo->Release();
    }

    return pObj;        
}

// Wbemcomn.dll prototypes.
typedef HRESULT (IWbemClassObject::*FPGET_PROPERTY_HANDLE_EX)(LPCWSTR, CIMTYPE*, long*);
typedef LPVOID (IWbemClassObject::*FPGET_ARRAY_BY_HANDLE)(long);

void CObjInfo::DoDebugStuff()
{
    // I tried doing directly (from GetProcAddress into memember vars) but
    // I couldn't come up with a conversion the compiler would let me do.
    // So, do it the hardway.
    HINSTANCE hWbemComn = GetModuleHandle(_T("wbemcomn.dll"));
    FARPROC   fpGetPropertyHandleEx = 
                GetProcAddress(
                    hWbemComn, 
                    "?GetPropertyHandleEx@CWbemObject@@QAEJPBGPAJ1@Z");
    FARPROC   fpGetArrayByHandle = 
                GetProcAddress(
                    hWbemComn, 
                    "?GetArrayByHandle@CWbemObject@@QAEPAVCUntypedArray@@J@Z");

    FPGET_PROPERTY_HANDLE_EX
              m_fpGetPropertyHandleEx;
    FPGET_ARRAY_BY_HANDLE            
              m_fpGetArrayByHandle;

    memcpy(
        &m_fpGetPropertyHandleEx, 
        &fpGetPropertyHandleEx, 
        sizeof(fpGetPropertyHandleEx));

    memcpy(
        &m_fpGetArrayByHandle, 
        &fpGetArrayByHandle, 
        sizeof(fpGetArrayByHandle));

    SAFEARRAY *pArr = NULL;

    if (SUCCEEDED(m_pObj->GetNames(NULL, WBEM_FLAG_NONSYSTEM_ONLY, NULL, &pArr)))
    {
        BSTR *pNames = (BSTR*) pArr->pvData;
        int  nItems = pArr->rgsabound[0].cElements;

        for (int i = 0; i < nItems; i++)
        {
            CIMTYPE type;
            long    handle;

            if (SUCCEEDED(
                (m_pObj->*m_fpGetPropertyHandleEx)(
                    pNames[i],
                    &type,
                    &handle)))
            {
                LPVOID pData = (m_pObj->*m_fpGetArrayByHandle)(handle);
                int    x;

                x = 3;

                HRESULT    hr;
                _variant_t vValue;

                hr =
                    m_pObj->Get(
                        pNames[i],
                        0,
                        &vValue,
                        &type,
                        NULL);

                BSTR *pStrings = NULL;
                
                if (vValue.vt == (VT_BSTR | VT_ARRAY))
                    pStrings = (BSTR*) vValue.parray->pvData;

                hr =
                    m_pObj->Put(
                        pNames[i],
                        0,
                        &vValue,
                        0);
            }
        }

        SafeArrayDestroy(pArr);
    }
}