/******************************************************************
   Printer.CPP -- WMI provider class implementation

   Generated by Microsoft WMI Code Generation Engine
  
   TO DO: - See individual function headers
          - When linking, make sure you link to framedyd.lib & 
            msvcrtd.lib (debug) or framedyn.lib & msvcrt.lib (retail).

   Description: 
   
  
  
******************************************************************/

#include "pchealth.h"
#include "Printer.h"
#include "exdisp.h"


/////////////////////////////////////////////////////////////////////////////
//  tracing stuff

#ifdef THIS_FILE
#undef THIS_FILE
#endif
static char __szTraceSourceFile[] = __FILE__;
#define THIS_FILE __szTraceSourceFile
#define TRACE_ID    DCID_PRINTERDRIVER


/////////////////////////////////////////////////////////////////////////////
//  initialization

CPrinter MyPrinterSet(PROVIDER_NAME_PRINTER, PCH_NAMESPACE);


/////////////////////////////////////////////////////////////////////////////
//  Property names

// PCH 
const static WCHAR *c_wszDate         = L"Date";
const static WCHAR *c_wszDefault      = L"Default";
const static WCHAR *c_wszFilename     = L"Filename";
const static WCHAR *c_wszManufacturer = L"Manufacturer";
const static WCHAR *c_wszName         = L"Name";
const static WCHAR *c_wszPath         = L"Path";
const static WCHAR *c_wszPaused       = L"Paused";
const static WCHAR *c_wszSize         = L"Size";
const static WCHAR *c_wszVersion      = L"Version";
const static WCHAR *c_wszSpooler      = L"SpoolEnabled";
const static WCHAR *c_wszNetwork      = L"Network";
const static WCHAR *c_wszNSTimeout    = L"NSTimeout";
const static WCHAR *c_wszRetryTimeout = L"RetryTimeout";

// Win32
const static WCHAR *c_wszPortName     = L"PortName";
const static WCHAR *c_wszFileSize     = L"FileSize";
const static WCHAR *c_wszLastModified = L"LastModified";
const static WCHAR *c_wszDeviceID     = L"DeviceID";


// method parameters
const static WCHAR *c_wszURL          = L"strURL";
const static WCHAR *c_wszRetVal       = L"ReturnValue";
const static WCHAR *c_wszEnable       = L"fEnable";
const static WCHAR *c_wszTxTimeoutP   = L"uitxTimeout";
const static WCHAR *c_wszDNSTimeoutP  = L"uidnsTimeout";

// misc
const static TCHAR *c_szRegPathPrn    = _T("SYSTEM\\CurrentControlSet\\Control\\Print\\Printers\\");
const static TCHAR *c_szTxTimeout     = _T("txTimeout");
const static TCHAR *c_szDNSTimeout    = _T("dnsTimeout");

CComBSTR           g_bstrDeviceID     = L"DeviceID";
CComBSTR           g_bstrAttrib       = L"Attributes";


//////////////////////////////////////////////////////////////////////////////
// utility functions

// ***************************************************************************
// ***** IMPORTANT NOTE *****
//  You must free the value you get returned via ppPrnInfo via MyFree()
HRESULT GetPrinterInfo(LPTSTR szPrinter, LPBYTE *ppPrnInfo, 
                       HANDLE *phPrinter, DWORD dwLevel)
{
    USES_CONVERSION;
    TraceFunctEnter("GetPrinterInfo");

    HRESULT         hr = NOERROR;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;
    LPBYTE          pbBuff = NULL;
    DWORD           cbRead, cbNeed;
    BOOL            fOk;

    if (szPrinter == NULL)
    {
        hr = E_INVALIDARG;
        goto done;
    }

    // yay!  Now we have a printer name we can call OpenPrinter with.
    fOk = OpenPrinter(szPrinter, &hPrinter, NULL);
    if (fOk == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "Unable to open printer %ls: 0x%08x", szPrinter,
                   hr);
        goto done;
    }

    // only need to get this if the user wants it...
    if (ppPrnInfo != NULL)
    {
        // GetPrinter expects a buffer larger than PRINTER_INFO_2 all by itself...
        //  So gotta figure out how big of a buffer it wants and allocate it...
        fOk = GetPrinter(hPrinter, dwLevel, NULL, 0, &cbNeed);
        if (fOk == FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            ErrorTrace(TRACE_ID, "Unable to get printer info for %ls: 0x%08x", 
                       szPrinter, hr);
            goto done;
        }

        pbBuff = (LPBYTE)MyAlloc(cbNeed);
        if (pbBuff == NULL)
        {
            hr = E_OUTOFMEMORY;
            ErrorTrace(TRACE_ID, "Out of memory allocating buffer for printer data"); 
            goto done;
        }

        fOk = GetPrinter(hPrinter, dwLevel, pbBuff, cbNeed, &cbRead);
        if (fOk == FALSE || cbRead > cbNeed)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            ErrorTrace(TRACE_ID, "Unable to get printer info for %ls: 0x%08x", 
                       szPrinter, hr);
            goto done;
        }

        *ppPrnInfo = pbBuff;
        pbBuff = NULL;
    }

    if (phPrinter != NULL)
    {
        *phPrinter = hPrinter;
        hPrinter = INVALID_HANDLE_VALUE;
    }

done:
    if (pbBuff != NULL)
        MyFree(pbBuff);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);

    TraceFunctLeave();
    return hr;
}

// ***************************************************************************
HRESULT FindJobError(HANDLE hPrinter, DWORD cJobs, LPTSTR szUser, 
                     DWORD *pdwStatus, DWORD *pdwID)
{
    USES_CONVERSION;
    TraceFunctEnter("FindJobError");

    JOB_INFO_2  *rgJobInfo = NULL;
    HRESULT     hr = NOERROR;
    DWORD       cbNeed, cbRead, cFetched, i;
    BOOL        fOk;

    if (szUser == NULL || pdwStatus == NULL || pdwID == NULL)
    {
        ErrorTrace(TRACE_ID, "Invalid parameters");
        hr = E_INVALIDARG;
        goto done;
    }

    // EnumJobs requires a random amount of space to fill up.  Find out 
    //  how much it wants this time.
    fOk = EnumJobs(hPrinter, 0, cJobs, 2, NULL, 0, &cbNeed, &cFetched);
    if (fOk == FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "EnumJobs failed: 0x%08x", hr);
        goto done;
    }

    rgJobInfo = (JOB_INFO_2 *)MyAlloc(cbNeed);
    if (rgJobInfo == NULL)
    {
        hr = E_OUTOFMEMORY;
        ErrorTrace(TRACE_ID, "Out of memory");
        goto done;
    }

    // actually get the data
    fOk = EnumJobs(hPrinter, 0, cJobs, 2, (LPBYTE)rgJobInfo, cbNeed, &cbRead,
                   &cFetched);
    if (fOk == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "EnumJobs failed: 0x%08x", hr);
        goto done;
    }

    // we are looking for two things:

    //  if the current user has job that failed
    for(i = 0; i < cJobs; i++)
    {
        if (rgJobInfo[i].pUserName != NULL && 
            _tcscmp(rgJobInfo[i].pUserName, szUser) == 0)
        {
            if ((rgJobInfo[i].Status & (JOB_STATUS_PAUSED | 
                                        JOB_STATUS_DELETING |
                                        JOB_STATUS_ERROR |
                                        JOB_STATUS_OFFLINE |
                                        JOB_STATUS_PAPEROUT |
                                        JOB_STATUS_BLOCKED_DEVQ |
                                        JOB_STATUS_PAUSED |
                                        JOB_STATUS_USER_INTERVENTION)) != 0)
            {
                *pdwID     = rgJobInfo[i].JobId;
                *pdwStatus = rgJobInfo[i].Status;
                hr = NOERROR;
                goto done;
            }   
        }
    }

    //  if anyone has a job that failed
    for(i = 0; i < cJobs; i++)
    {
        if ((rgJobInfo[i].Status & JOB_STATUS_PRINTING) != 0 && 
            (rgJobInfo[i].Status & (JOB_STATUS_ERROR |
                                    JOB_STATUS_OFFLINE |
                                    JOB_STATUS_PAPEROUT |
                                    JOB_STATUS_BLOCKED_DEVQ |
                                    JOB_STATUS_USER_INTERVENTION)) != 0)
        {
            _tcscpy(szUser, rgJobInfo[i].pUserName);
            *pdwID     = rgJobInfo[i].JobId;
            *pdwStatus = rgJobInfo[i].Status;
            hr = NOERROR;
            goto done;
        }
    }

    *pdwID     = (DWORD)-1;
    *pdwStatus = 0;


done:
    if (rgJobInfo != NULL)
        MyFree(rgJobInfo);
    TraceFunctLeave();
    return hr;
}


//////////////////////////////////////////////////////////////////////////////
// construction / destruction

// ***************************************************************************
CPrinter::CPrinter (LPCWSTR lpwszName, LPCWSTR lpwszNameSpace) :
    Provider(lpwszName, lpwszNameSpace)
{
    m_pParamOut = NULL;
    m_pCurrent  = NULL;
    m_pParamIn  = NULL;
    m_lFlags    = 0;
}

// ***************************************************************************
CPrinter::~CPrinter ()
{
}


//////////////////////////////////////////////////////////////////////////////
// internal methods

// ****************************************************************************
HRESULT CPrinter::GetInstanceData(IWbemClassObjectPtr pObj, CInstance *pInst)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::GetInstanceData");

    IWbemClassObjectPtr     pFileObj = NULL;
    PRINTER_INFO_2          *pPrnInfo2 = NULL;
    PRINTER_INFO_5          *pPrnInfo5 = NULL;
    struct _stat            filestat;
    CComVariant             varValue;
    CComBSTR                bstrPrinterDriverWithPath;
    CComBSTR                bstrPrinterDriver;
    CComBSTR                bstrProperty;
    HRESULT                 hr = WBEM_S_NO_ERROR;
    DWORD                   dwStatus, dwErr;
    ULONG                   ulPrinterRetVal = 0;
    ULONG                   uiReturn = 0;
    TCHAR                   szDeviceID[MAX_PATH];
    TCHAR                   szBuffer[MAX_PATH];
    TCHAR                   *pchToken;
    BOOL                    fDriverFound;
    BOOL                    fLocal = TRUE;

    // ** name
    CopyProperty(pObj, c_wszDeviceID, pInst, c_wszName);

    // ** path
    CopyProperty(pObj, c_wszPortName, pInst, c_wszPath);

    // ** spoolenabled
    CopyProperty(pObj, c_wszSpooler, pInst, c_wszSpooler);

    
    // get the attribute property from the passed in printer object.  With that
    //  we can get all sorts of info (default, network / local, etc)
    hr = pObj->Get(g_bstrAttrib, 0, &varValue, NULL, NULL);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "Unable to get attribute property from WMI: 0x%08x",
                   hr);
    }

    else if (V_VT(&varValue) != VT_I4)
    {
        hr = VariantChangeType(&varValue, &varValue, 0, VT_I4);
        if (FAILED(hr))
            ErrorTrace(TRACE_ID, "Unable to convert type: 0x%08x", hr);
    }

    if (SUCCEEDED(hr))
    {
        DWORD dwAttribs;

        dwAttribs = V_I4(&varValue);


        // ** default

        varValue = VARIANT_FALSE;
        if ((dwAttribs & PRINTER_ATTRIBUTE_DEFAULT) != 0)
            varValue = VARIANT_TRUE;

        if (pInst->SetVariant(c_wszDefault, varValue) == FALSE)
            ErrorTrace(TRACE_ID, "SetVariant on Default failed");

           
        // ** network
        
        varValue = VARIANT_FALSE;
        if ((dwAttribs & PRINTER_ATTRIBUTE_NETWORK) != 0)
        {
            varValue = VARIANT_TRUE;
            fLocal   = FALSE;
        }

        if (pInst->SetVariant(c_wszNetwork, varValue) == FALSE)
            ErrorTrace(TRACE_ID, "SetVariant on Network failed");
    }

    // we need the deviceID to do a whole bunch of stuff... 
    varValue.Clear();
    hr = pObj->Get(g_bstrDeviceID, 0, &varValue, NULL, NULL);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "Unable to get attribute property from WMI: 0x%08x",
                   hr);
    }
    else if (V_VT(&varValue) != VT_BSTR)
    {
        hr = VariantChangeType(&varValue, &varValue, 0, VT_BSTR);
        if (FAILED(hr))
            ErrorTrace(TRACE_ID, "Unable to convert type: 0x%08x", hr);
    }

    if (SUCCEEDED(hr))
    {
        // since we're going to need it a lot as a TCHAR, convert the
        //  name of the printer to one... 
        _tcscpy(szDeviceID, OLE2T(V_BSTR(&varValue)));
    
        // ** paused

        hr = GetPrinterInfo(szDeviceID, (LPBYTE *)&pPrnInfo2, NULL, 2);
        if (SUCCEEDED(hr))
        {
            varValue.Clear();
            varValue = VARIANT_FALSE;
            if ((pPrnInfo2->Status & PRINTER_STATUS_PAUSED) != 0)
                varValue = VARIANT_TRUE;

            if (pInst->SetVariant(c_wszPaused, varValue) == FALSE)
                ErrorTrace(TRACE_ID, "SetVariant on Paused failed");

            MyFree(pPrnInfo2);
            pPrnInfo2 = NULL;
        }


        // ** timeout values

        hr = GetPrinterInfo(szDeviceID, (LPBYTE *)&pPrnInfo5, NULL, 5);
        if (SUCCEEDED(hr))
        {
            varValue.Clear();
            
            V_VT(&varValue) = VT_I4;

            V_I4(&varValue) = pPrnInfo5->DeviceNotSelectedTimeout;
            if (pInst->SetVariant(c_wszNSTimeout, varValue) == FALSE)
                ErrorTrace(TRACE_ID, "SetVariant on NSTimeout failed");


            V_I4(&varValue) = pPrnInfo5->TransmissionRetryTimeout;
            if (pInst->SetVariant(c_wszRetryTimeout, varValue) == FALSE)
                ErrorTrace(TRACE_ID, "SetVariant on RetryTimeout failed");

            MyFree(pPrnInfo5);
            pPrnInfo5 = NULL;
        }


        // ** filename + others

        //  Now call GetProfileString to get the Driver
        varValue.Clear();
        if (GetProfileString(_T("Devices"), szDeviceID, _T("\0"), szBuffer, 
                             MAX_PATH) > 1)
        {
            //  szBuffer contains a string of two tokens, first the driver, 
            //   second the PathName

            //  Get the driver
            pchToken = _tcstok(szBuffer, _T(","));
            if(pchToken != NULL)
            {
                // Got the Driver Name
                bstrPrinterDriver = pchToken;
                varValue = pchToken;
            

                //  ** set the filename 

                if (pInst->SetVariant(c_wszFilename, varValue) == FALSE)
                    ErrorTrace(TRACE_ID, "SetVariant on FileName failed");

                // in order to get the file properties, we have to construct
                //  the full path to the file
                bstrPrinterDriver.Append(L".drv");
                fDriverFound = getCompletePath(bstrPrinterDriver, 
                                               bstrPrinterDriverWithPath);
                if (fDriverFound)
                {
                    //  GetCIMDataFile Function fetches properties of this file.
                    hr = GetCIMDataFile(bstrPrinterDriverWithPath, &pFileObj);
                    if (SUCCEEDED(hr))
                    {
                        // ** version

                        CopyProperty(pFileObj, c_wszVersion, 
                                     pInst, c_wszVersion);


                        // ** filesize

                        CopyProperty(pFileObj, c_wszFileSize, 
                                     pInst, c_wszSize);


                        // ** date

                        CopyProperty(pFileObj, c_wszLastModified, 
                                     pInst, c_wszDate);


                        // ** manufacturer

                        CopyProperty(pFileObj, c_wszManufacturer, 
                                     pInst, c_wszManufacturer);
                    } 
                }
            } 
        }
    }

    TraceFunctLeave();
    return hr;
}


// ****************************************************************************
HRESULT CPrinter::GetStatus(void)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::GetStatus");
    
    PRINTER_INFO_2  *pPrnInfo = NULL;
    HRESULT         hr = NOERROR;
    VARIANT         var;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;
    DWORD           dwStatus;
    DWORD           dwLocation;
    TCHAR           szPrinter[1024];

    VariantInit(&var);

    if (m_pCurrent == NULL || m_pParamOut == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter objects not set.");
        hr = E_FAIL;
        goto done;
    }

    if (m_pCurrent->GetVariant(c_wszName, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&var) != VT_BSTR)
    {
        hr = VariantChangeType(&var, &var, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }

    _tcscpy(szPrinter, OLE2T(V_BSTR(&var)));

    // get the printer info structure
    hr = GetPrinterInfo(szPrinter, (LPBYTE *)&pPrnInfo, &hPrinter, 2);
    if (FAILED(hr))
        goto done;

    dwStatus = pPrnInfo->Status;

    // if the status is not in the error state, then we need to look at the 
    //  list of print jobs available
    if (dwStatus == 0)
    {
        DWORD   dwJobID;
        DWORD   cbUser;
        TCHAR   szUser[512];

        cbUser = 512;
        GetUserName(szUser, &cbUser);
        hr = FindJobError(hPrinter, pPrnInfo->cJobs, szUser, &dwStatus, 
                          &dwJobID);
        if (FAILED(hr))
            goto done;
    }

    VariantClear(&var);
    V_VT(&var) = VT_I4;
    V_I4(&var) = dwStatus;

    if (m_pParamOut->SetVariant(c_wszRetVal, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to set return val object");
        hr = E_FAIL;
        goto done;
    }

done:
    VariantClear(&var);
    if (pPrnInfo != NULL)
        MyFree(pPrnInfo);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);

    TraceFunctLeave();
    return hr;
}

// ****************************************************************************
HRESULT CPrinter::RemovePause(void)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::RemovePause");

    PRINTER_INFO_2  *pPrnInfo = NULL;
    HRESULT         hr = NOERROR;
    VARIANT         var;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;
    BOOL            fOk;

    VariantInit(&var);

    if (m_pCurrent == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }

    if  (m_pCurrent->GetVariant(c_wszName, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&var) != VT_BSTR)
    {
        hr = VariantChangeType(&var, &var, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }

    hr = GetPrinterInfo(OLE2T(V_BSTR(&var)), (LPBYTE *)&pPrnInfo, &hPrinter, 
                        2);
    if (FAILED(hr))
        goto done;

    if (pPrnInfo->Status == PRINTER_STATUS_PAUSED)
    {
        fOk = SetPrinter(hPrinter, 0, NULL, PRINTER_CONTROL_RESUME);
        if (fOk == FALSE)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            ErrorTrace(TRACE_ID, "SetPrinter failed: 0x%08x", hr);
            goto done;
        }
    }

done:
    VariantClear(&var);
    if (pPrnInfo != NULL)
        MyFree(pPrnInfo);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);

    TraceFunctLeave();
    return hr;
}

// ****************************************************************************
HRESULT CPrinter::PrinterProperties(void)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::PrinterProperties");

    PRINTER_INFO_2  *pPrnInfo = NULL;
    LPDEVMODE       pDevMode = NULL;
    HRESULT         hr = NOERROR;
    VARIANT         var;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;
    DWORD           cbDevMode;

    VariantInit(&var);

    if (m_pCurrent == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }
    
    if  (m_pCurrent->GetVariant(c_wszName, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&var) != VT_BSTR)
    {
        hr = VariantChangeType(&var, &var, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }

    hr = GetPrinterInfo(OLE2T(V_BSTR(&var)), (LPBYTE *)pPrnInfo, &hPrinter, 2);
    if (FAILED(hr))
        goto done;


    cbDevMode = DocumentProperties(NULL, hPrinter, OLE2T(V_BSTR(&var)), 
                                   NULL, NULL, 0);
    pDevMode = (LPDEVMODE)MyAlloc(cbDevMode);
    if (pDevMode == NULL)
    {
        hr = E_OUTOFMEMORY;
        ErrorTrace(TRACE_ID, "Out of memory allocating DEVMODE structure");
        goto done;
    }

    // ok, call this for real this time...
    if (DocumentProperties(NULL, hPrinter, OLE2T(V_BSTR(&var)), 
                           pDevMode, NULL, DM_PROMPT) == IDOK)
    {
        // nothing to free here cuz pPrnInfo->pDevMode points into the memory blob
        //  that pPrnInfo points to... 
        pPrnInfo->pDevMode = pDevMode;

        if (SetPrinter(hPrinter, 2, (LPBYTE)pPrnInfo, 0) == FALSE)
        {
            hr = E_OUTOFMEMORY;
            ErrorTrace(TRACE_ID, "Unable to set new printer info.");
            goto done;
        }
    }
    
done:
    VariantClear(&var);
    if (pPrnInfo != NULL)
        MyFree(pPrnInfo);
    if (pDevMode != NULL)
        MyFree(pDevMode);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);
    TraceFunctLeave();
    return hr;
}

// ****************************************************************************
HRESULT CPrinter::SetAsDefault(TCHAR *szOldDefault, DWORD cchOldDefault, 
                               BOOL fSetOldDefault)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::SetAsDefault");

    HRESULT hr = NOERROR;
    VARIANT var;
    DWORD   dw;
    TCHAR   szPrinter[1024], szNewDefault[1024];
    BOOL    fOk;

    VariantInit(&var);

    if (m_pCurrent == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }

    // See if the caller wants to know what the old default is or wants to set
    //  the old default...
    if (szOldDefault != NULL)
    {
        // see if we want to set the default
        if (fSetOldDefault)
        {
            fOk = WriteProfileString(_T("Windows"), _T("Device"), szOldDefault);
            if (fOk == FALSE)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                ErrorTrace(TRACE_ID, "Failed to write old default printer: 0x%08x", 
                           hr);
            }
            
            // can goto done here cuz we don't need to do anything else... 
            goto done;
        }

        // or maybe we just want to grab is and then set m_pCurrent to be the 
        //  default
        else
        {
            dw = GetProfileString(_T("Windows"), _T("Device"), _T("\0"), 
                                  szOldDefault, cchOldDefault);
            if (dw <= 1)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                ErrorTrace(TRACE_ID, "Failed to fetch current default: 0x%08x", 
                           hr);
                goto done;
            }
        }
    }

    // if we're here, then we gotta set the printer pointed to by m_pCurrent as 
    //  the default printer, so fetch the name of the printer we want to be the
    //  default
    if (m_pCurrent->GetVariant(c_wszName, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }
    
    if (V_VT(&var) != VT_BSTR)
    {
        hr = VariantChangeType(&var, &var, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }

    // get the printer info from win.ini
    dw = GetProfileString(_T("Devices"), OLE2T(V_BSTR(&var)), _T("\0"), 
                          szPrinter, sizeof(szPrinter) / sizeof(TCHAR));
    if (dw <= 1)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "Failed to fetch current default: 0x%08x", hr);
        goto done;
    }

    // build a string & slam it back into win.ini
    wsprintf(szNewDefault, "%s,%s", OLE2T(V_BSTR(&var)), szPrinter);
    fOk = WriteProfileString(_T("Windows"), _T("Device"), szNewDefault);
    if (fOk == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "Failed to write new default printer: 0x%08x", 
                   hr);
    }

    // got to notify everyone in existance (well, all the top level windows 
    //  anyway) that we changed the default printer...
    SendMessageTimeout(HWND_BROADCAST, WM_WININICHANGE, 0L, 
                       (LPARAM)(LPCTSTR)_T("windows"), SMTO_NORMAL, 1000, 
                       NULL);

done:
    VariantClear(&var);
    TraceFunctLeave();
    return hr;
}

// ****************************************************************************
// ***  NOTE: this method doesn't work on WinNT cuz WinMgmt runs as a service 
//            which has different printer settings / permissions than the user
//            account
HRESULT CPrinter::TestPrinter(void)
{
    TraceFunctEnter("CPrinter::TestPrinter");

    IWebBrowser2    *pwb = NULL;
    READYSTATE      rs;
    VARIANT         varFlags, varOpt, varURL;
    HRESULT         hr = NOERROR;
    CLSID           clsid;
    DWORD           dwStart;
    TCHAR           szDefault[1024];

    VariantInit(&varFlags);
    VariantInit(&varURL);
    VariantInit(&varOpt);

    if (m_pParamIn == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }

    if (m_pParamIn->GetVariant(c_wszURL, varURL) == FALSE)
    {
        ErrorTrace(TRACE_ID, "strURL parameter not present.");
        hr = E_FAIL;
        goto done;
    }

    hr = VariantChangeType(&varURL, &varURL, 0, VT_BSTR);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "unable to convert strURL to string");
        goto done;
    }

    // the URL should be at least 4 characters long in order for it to be a
    //  valid file path.  Need 3 characters for drive path & at least 1 for
    //  the filename (as in 'd:\a')
    if (SysStringLen(V_BSTR(&varURL)) < 4)
    {
        ErrorTrace(TRACE_ID, "strURL parameter < 4 characters.");
        hr = E_INVALIDARG;
        goto done;
    }

    // we obviously need a web browser object, so make one
    hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, 
                          IID_IWebBrowser2, (LPVOID *)&pwb);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "Unable to CoCreate web browser control: 0x%08x", hr);
        goto done;
    }

    // load the URL
    V_VT(&varFlags)  = VT_I4;
    V_I4(&varFlags)  = navNoHistory;
    V_VT(&varOpt)    = VT_ERROR;
    V_ERROR(&varOpt) = DISP_E_PARAMNOTFOUND;
    hr = pwb->Navigate2(&varURL, &varOpt, &varOpt, &varOpt, &varOpt);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "Unable to Navigate to URL '%ls': 0x%08x", 
                   V_BSTR(&varURL), hr);
        goto done;
    }

    // wait for a maximum of 5 minutes for this URL to come in...
    for(dwStart = GetTickCount(); GetTickCount() - dwStart <= 300000;)
    {
        hr = pwb->get_ReadyState(&rs);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "Unable to get web browser state: 0x%08x", hr);
            goto done;
        }

        if (rs == READYSTATE_COMPLETE)
            break;
    }

    // make sure we didn't timeout...
    if (rs != READYSTATE_COMPLETE)
    {
        ErrorTrace(TRACE_ID, "Timeout waiting for browser to load URL");
        hr = E_FAIL;
        goto done;
    }

    // since we aren't prompting the user, we need to temporarily set the
    //  default printer to be the one we want to test
    hr = this->SetAsDefault(szDefault, sizeof(szDefault) / sizeof(TCHAR), FALSE);
    if (FAILED(hr))
        goto done;

    // do the print
    hr = pwb->ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, &varOpt, &varOpt);
    if (FAILED(hr))
    {
        ErrorTrace(TRACE_ID, "Unable to print: 0x%08x", hr);
        goto done;
    }

    // revert back to the original printer
    hr = this->SetAsDefault(szDefault, sizeof(szDefault) / sizeof(TCHAR), TRUE);
    if (FAILED(hr))
        goto done;

done:
    VariantClear(&varURL);
    if (pwb != NULL)
        pwb->Release();

    TraceFunctLeave();
    return hr;
}

// *****************************************************************************
HRESULT CPrinter::EnableSpooler(void)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::EnableSpooler");

    PRINTER_INFO_2  *pPrnInfo = NULL;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;
    VARIANT         varEnable, varName;
    HRESULT         hr = NOERROR;

    VariantInit(&varEnable);
    VariantInit(&varName);

    // get the parameter
    if (m_pParamIn == NULL || m_pCurrent == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }

    if (m_pParamIn->GetVariant(c_wszEnable, varEnable) == FALSE)
    {
        ErrorTrace(TRACE_ID, "strURL parameter not present.");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&varEnable) != VT_BOOL)
    {
        hr = VariantChangeType(&varEnable, &varEnable, 0, VT_BOOL);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "unable to convert fEnable to bool: 0x%08x",
                       hr);
            goto done;
        }
    }

    if  (m_pCurrent->GetVariant(c_wszName, varName) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&varName) != VT_BSTR)
    {
        hr = VariantChangeType(&varName, &varName, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }
    
    hr = GetPrinterInfo(OLE2T(V_BSTR(&varName)), (LPBYTE *)&pPrnInfo, 
                        &hPrinter, 2);
    if (FAILED(hr))
        goto done;

    if (V_BOOL(&varEnable) == VARIANT_FALSE)
        pPrnInfo->Attributes &= ~PRINTER_ATTRIBUTE_DIRECT;
    else
        pPrnInfo->Attributes |= PRINTER_ATTRIBUTE_DIRECT;

    if (SetPrinter(hPrinter, 2, (LPBYTE)pPrnInfo, 0) == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "SetPrinter failed: 0x%08x", hr);
        goto done;
    }

done:
    VariantClear(&varName);
    VariantClear(&varEnable);
    if (pPrnInfo != NULL)
        MyFree(pPrnInfo);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);
    

    TraceFunctLeave();
    return hr;
}

// *****************************************************************************
HRESULT CPrinter::SetTimeouts(void)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::SetTimeouts");

    PRINTER_INFO_5  *pPrnInfo5 = NULL;
    HRESULT         hr = NOERROR;
    VARIANT         varName, varDNS, varTX;
    HANDLE          hPrinter = INVALID_HANDLE_VALUE;

    VariantInit(&varName);
    VariantInit(&varDNS);
    VariantInit(&varTX);

    // get the parameter
    if (m_pParamIn == NULL || m_pCurrent == NULL)
    {
        ErrorTrace(TRACE_ID, "Parameter object not set.");
        hr = E_FAIL;
        goto done;
    }

    // get uiTxTimeout
    if (m_pParamIn->GetVariant(c_wszTxTimeoutP, varTX) == FALSE)
    {
        ErrorTrace(TRACE_ID, "uiTxTimeout parameter not present.");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&varTX) != VT_I4)
    {
        hr = VariantChangeType(&varTX, &varTX, 0, VT_I4);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x",
                       hr);
            goto done;
        }
    }

    // get uiDNSTimeout
    if (m_pParamIn->GetVariant(c_wszDNSTimeoutP, varDNS) == FALSE)
    {
        ErrorTrace(TRACE_ID, "uiDNSTimeout parameter not present.");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&varDNS) != VT_I4)
    {
        hr = VariantChangeType(&varDNS, &varDNS, 0, VT_I4);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x",
                       hr);
            goto done;
        }
    }

    // get Name
    if  (m_pCurrent->GetVariant(c_wszName, varName) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name from m_pCurrent");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&varName) != VT_BSTR)
    {
        hr = VariantChangeType(&varName, &varName, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }

    hr = GetPrinterInfo(OLE2T(V_BSTR(&varName)), (LPBYTE *)&pPrnInfo5, 
                        &hPrinter, 5);
    if (FAILED(hr))
        goto done;

    pPrnInfo5->TransmissionRetryTimeout = V_I4(&varTX);
    pPrnInfo5->DeviceNotSelectedTimeout = V_I4(&varDNS);

    if (SetPrinter(hPrinter, 5, (LPBYTE)pPrnInfo5, 0) == FALSE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        ErrorTrace(TRACE_ID, "Unable to set printer info: 0x%08x", hr);
        goto done;
    }

done:
    VariantClear(&varName);
    VariantClear(&varDNS);
    VariantClear(&varTX);
    if (pPrnInfo5 != NULL)
        MyFree(pPrnInfo5);
    if (hPrinter != INVALID_HANDLE_VALUE)
        ClosePrinter(hPrinter);

    TraceFunctLeave();
    return hr;
}



//////////////////////////////////////////////////////////////////////////////
// exposed methods

// *****************************************************************************
HRESULT CPrinter::EnumerateInstances(MethodContext* pMethodContext, long lFlags)
{
    USES_CONVERSION;
    TraceFunctEnter("CPrinter::EnumerateInstances");

    IEnumWbemClassObject    *pEnumInst = NULL;
    IWbemClassObjectPtr     pObj = NULL;                   
    CComBSTR                bstrPrinterQuery;
    HRESULT                 hr = WBEM_S_NO_ERROR;
    ULONG                   ulPrinterRetVal = 0;

    // Execute the query to get DeviceID, PortName from the Win32_Printer class
    bstrPrinterQuery = L"Select DeviceID, PortName, SpoolEnabled, Status, Attributes FROM win32_printer";
    hr = ExecWQLQuery(&pEnumInst, bstrPrinterQuery);
    if (FAILED(hr))
        goto done;
    
    //  Enumerate the instances from pEnumInstance
    while(pEnumInst->Next(WBEM_INFINITE, 1, &pObj, &ulPrinterRetVal) == WBEM_S_NO_ERROR)
    {
        // Create a new instance of PCH_PrinterDriver Class based on the 
        //  passed-in MethodContext
        CInstancePtr   pInst(CreateNewInstance(pMethodContext), FALSE);

        // original code didn't really care if this failed, so neither do I...
        hr = GetInstanceData(pObj, pInst);
        
        //  All the properties are set. Commit the instance
        hr = pInst->Commit();
        if(FAILED(hr))
            ErrorTrace(TRACE_ID, "Could not commit instance: 0x%08x", hr);

        // Ok, so WMI does not follow it's own docs on how GetObject
        //  works.  According to them, we should release this object here.  But
        //  if I try, winmgmt GPFs.
        // pObj->Release();
        pObj = NULL;
    } 

done:
    if (pEnumInst != NULL)
        pEnumInst->Release();
    TraceFunctLeave();
    return hr;
}


// *****************************************************************************
HRESULT CPrinter::ExecMethod (const CInstance& Instance,
                              const BSTR bstrMethodName,
                              CInstance *pInParams, CInstance *pOutParams,
                              long lFlags)
{
    TraceFunctEnter("CPrinter::ExecMethod");

    HRESULT     hr = NOERROR;

    m_pCurrent  = (CInstance *)&Instance;
    m_pParamIn  = pInParams;
    m_pParamOut = pOutParams;
    m_lFlags    = lFlags;

    if (_wcsicmp(bstrMethodName, L"SetAsDefault") == 0)
        hr = this->SetAsDefault();

    else if (_wcsicmp(bstrMethodName, L"PrinterProperties") == 0)
        hr = this->PrinterProperties();

    else if (_wcsicmp(bstrMethodName, L"RemovePause") == 0)
        hr = this->RemovePause();

    else if (_wcsicmp(bstrMethodName, L"TestPrinter") == 0)
        hr = this->TestPrinter();

    else if (_wcsicmp(bstrMethodName, L"ErrorStatus") == 0)
        hr = this->GetStatus();

    else if (_wcsicmp(bstrMethodName, L"EnableSpooler") == 0)
        hr = this->EnableSpooler();

    else if (_wcsicmp(bstrMethodName, L"SetTimeouts") == 0)
        hr = this->SetTimeouts();

    else 
        hr = WBEM_E_INVALID_METHOD;

    if (FAILED(hr))
        goto done;

done:
    m_pCurrent  = NULL;
    m_pParamIn  = NULL;
    m_pParamOut = NULL;
    m_lFlags    = 0;

    TraceFunctLeave();
    return hr;
}

// *****************************************************************************
HRESULT CPrinter::GetObject(CInstance* pInstance, long lFlags) 
{ 
    TraceFunctEnter("CPrinter::GetObject");

    IWbemClassObjectPtr pObj = NULL;
    CComBSTR            bstrPath;
    HRESULT             hr = NOERROR;
    VARIANT             var;
    WCHAR               wszBuffer[1024], *pwszPrn, *pwszBuf;
    DWORD               i;
    BSTR                bstrPrn;

    VariantInit(&var);

    if (pInstance == NULL)
    {
        hr = E_INVALIDARG;
        goto done;
    }

    // get the name of the printer
    if (pInstance->GetVariant(c_wszName, var) == FALSE)
    {
        ErrorTrace(TRACE_ID, "Unable to fetch printer name");
        hr = E_FAIL;
        goto done;
    }

    if (V_VT(&var) != VT_BSTR)
    {
        hr = VariantChangeType(&var, &var, 0, VT_BSTR);
        if (FAILED(hr))
        {
            ErrorTrace(TRACE_ID, "VariantChangeType failed: 0x%08x", hr);
            goto done;
        }
    }
       
    // WMI!!  It expects me to turn a printer with a name \\server\share
    //  into \\\\server\\share.  (double '\'s)
    bstrPrn = V_BSTR(&var);
    if ((bstrPrn[0] != L'\\' && bstrPrn[1] != L'\\') ||
        (bstrPrn[0] == L'\\' && bstrPrn[1] == L'\\' && bstrPrn[2] == L'\\' && 
         bstrPrn[3] == L'\\'))
    {
        wcscpy(wszBuffer, bstrPrn);
    }

    else
    {
        // ok, here's the annoying part...
        wcscpy(wszBuffer, L"\\\\\\\\");
        pwszBuf = wszBuffer + 4;
        pwszPrn = bstrPrn + 2;
        
        // actually, we only need to scan to the first '\' cuz we've already
        //  taken care of the 1st two & this needs to fit into  '\\server\share'
        while (pwszPrn != L'\0')
        {
            if (*pwszPrn == L'\\')
            {
                *pwszBuf++ = L'\\';
                break;
            }

            *pwszBuf++ = *pwszPrn++;
        }

        wcscpy(pwszBuf, pwszPrn);
    }


    // build the path to the object
    bstrPath = L"\\\\.\\root\\cimv2:Win32_Printer.DeviceID=\"";
    bstrPath.Append(wszBuffer);
    bstrPath.Append("\"");

    // fetch it
    hr = GetCIMObj(bstrPath, &pObj, lFlags);
    if (FAILED(hr))
        goto done;

    // populate the CInstance object
    hr = GetInstanceData(pObj, pInstance);
    if (FAILED(hr))
        goto done;
    
    //  All the properties are set. Commit the instance
    hr = pInstance->Commit();
    if(FAILED(hr))
        ErrorTrace(TRACE_ID, "Could not commit instance: 0x%08x", hr);

done:
    VariantClear(&var);

    // Ok, so WMI does not follow it's own docs on how GetObject
    //  works.  According to them, we should release this object here.  But
    //  if I try, winmgmt GPFs.
    // if (pObj != NULL)
    //    pObj->Release();

    TraceFunctLeave();
    return hr; 
}

// *****************************************************************************
HRESULT CPrinter::ExecQuery(MethodContext *pMethodContext, 
                            CFrameworkQuery& Query, long lFlags) 
{ 
    return WBEM_E_PROVIDER_NOT_CAPABLE; 
}

// *****************************************************************************
HRESULT CPrinter::PutInstance(const CInstance& Instance, long lFlags)
{ 
    return WBEM_E_PROVIDER_NOT_CAPABLE; 
}

// *****************************************************************************
HRESULT CPrinter::DeleteInstance(const CInstance& Instance, long lFlags)
{ 
    return WBEM_E_PROVIDER_NOT_CAPABLE; 
}
