////////////////////////////////////////////////////////////////////////////
// File:   TBExt.cpp   (toolbar extension classes)
// Author: Karim Farouki
//
// We define here three classes:
// (1) CToolbarExt a base class that takes care of the
//     button work for our custom extensions
// (2) CToolbarExtBand the object which deals with custom
//     buttons that plug into bands
// (3) CToolbarExtExec the object which deals with custom
//     buttons (or tools menu items) that exec stuff.
//
// The latter two are derived from the former 
#include "priv.h"
#include <mshtmcid.h>
#include "tbext.h"


//////////////////////////////
// Class CToolbarExt
//
// This is the base class from which CToolbarExtBand and CToolbarExtExec
// both inherit.  It takes care of all the ToolbarButton specific stuff
// like lazy loading the appropriate icons, and keeping track of the button
// text.

// Constructor / Destructor
//
CToolbarExt::CToolbarExt() : _cRef(1)
{ 
    ASSERT(_hIcon == NULL);
    ASSERT(_hIconSm == NULL);
    ASSERT(_hHotIcon == NULL);
    ASSERT(_hHotIconSm == NULL);
    ASSERT(_bstrButtonText == NULL);
    ASSERT(_bstrToolTip == NULL);
    ASSERT(_hkeyThisExtension == NULL);
    ASSERT(_hkeyCurrentLang == NULL);
    ASSERT(_pisb == NULL);
    
    DllAddRef();
}

// Destructor
//
CToolbarExt::~CToolbarExt() 
{ 
    if (_pisb)
        _pisb->Release();
    
    if (_bstrButtonText)
        SysFreeString(_bstrButtonText);

    if (_bstrToolTip)
        SysFreeString(_bstrToolTip);

    if (_hIcon)
        DestroyIcon(_hIcon);

    if (_hIconSm)
        DestroyIcon(_hIconSm);

    if (_hHotIcon)
        DestroyIcon(_hHotIcon);

    if (_hHotIconSm)
        DestroyIcon(_hHotIconSm);

    if (_hkeyThisExtension)
        RegCloseKey(_hkeyThisExtension);

    if (_hkeyCurrentLang)
        RegCloseKey(_hkeyCurrentLang);

    DllRelease();
}


// IUnknown implementation
//
STDMETHODIMP CToolbarExt::QueryInterface(const IID& iid, void** ppv)
{    
	if (iid == IID_IUnknown)
    	*ppv = static_cast<IBrowserExtension*>(this); 
	else if (iid == IID_IBrowserExtension)
		*ppv = static_cast<IBrowserExtension*>(this);
	else if (iid == IID_IOleCommandTarget)
        *ppv = static_cast<IOleCommandTarget*>(this);
    else if (iid == IID_IObjectWithSite)
        *ppv = static_cast<IObjectWithSite*>(this);
    else
	{
		*ppv = NULL;
		return E_NOINTERFACE;
	}
	
    reinterpret_cast<IUnknown*>(*ppv)->AddRef();
	return S_OK;
}

STDMETHODIMP_(ULONG) CToolbarExt::AddRef()
{
	return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CToolbarExt::Release() 
{
	if (InterlockedDecrement(&_cRef) == 0)
	{
		delete this;
		return 0;
	}
	return _cRef;
}

// IBrowserExtension::Init Implementation.  We'll read the ButtonText here but wait on the icons until
// a specific variant of the icon is requested.
STDMETHODIMP CToolbarExt::Init(REFGUID rguid)
{
    HRESULT hr = S_OK;
    LPOLESTR pszGUID;

    if (SUCCEEDED(StringFromCLSID(rguid, &pszGUID)))
    {
        //Open the extension reg key associated with this guid
        WCHAR szKey[MAX_PATH];
        StrCpyN(szKey, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions\\"), ARRAYSIZE(szKey));
        StrCatBuff(szKey, pszGUID, ARRAYSIZE(szKey));

        // We will keep _hkeyThisExtension around... it will be closed in the destructor!
        if (RegOpenKeyEx(HKEY_CURRENT_USER, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS ||
            RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS)
        {
            // See if there is a subkey for the current language
            LANGID langid = MLGetUILanguage();
            WCHAR szBuff[MAX_PATH];
            wnsprintf(szBuff, ARRAYSIZE(szBuff), L"Lang%04x", langid);
            RegOpenKeyEx(_hkeyThisExtension, szBuff, 0, KEY_READ, &_hkeyCurrentLang);
            
            // Now get the button text
            _RegReadString(_hkeyThisExtension, TEXT("ButtonText"), &_bstrButtonText);
        }

        CoTaskMemFree(pszGUID);
    }

    if (!_bstrButtonText)
        hr = E_FAIL;

    return hr;
}

//
// Gets the icon closest to the desired size from an .ico file or from the 
// resource in a .dll of .exe file
//
HICON CToolbarExt::_ExtractIcon
(
    LPWSTR pszPath, // file to get icon from
    int resid,      // resource id (0 if unused)
    int cx,         // desired icon width
    int cy          // desired icon height
)
{
    HICON hIcon = NULL;

    WCHAR szPath[MAX_PATH];
    SHExpandEnvironmentStrings(pszPath, szPath, ARRAYSIZE(szPath));

    // If no resource id, assume it's an ico file
    if (resid == 0)
    {
        hIcon = (HICON)LoadImage(0, szPath, IMAGE_ICON, cx, cy, LR_LOADFROMFILE);
    }

    // Otherwise, see if it's a resouce
    if (hIcon == NULL)
    {
        HINSTANCE hInst = LoadLibraryEx(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
        if (hInst)
        {
            hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(resid), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR);
            FreeLibrary(hInst);
        }
    }

    return hIcon;
}

//
// Returns the desired icon in pvarProperty
//
HRESULT CToolbarExt::_GetIcon
(
    LPCWSTR pszIcon,            // Name of icon value in registry
    int nWidth,                 // icon width
    int nHeight,                // icon height
    HICON& rhIcon,              // location to cached icon
    VARIANTARG * pvarProperty   // used for return icon
)
{
    HRESULT hr = S_OK;
    if (pvarProperty)
    {
        if (rhIcon == NULL)
        {
            BSTR bstrIconName;
            if (_RegReadString(_hkeyThisExtension, pszIcon, &bstrIconName, TRUE))
            {
                // Parse entry such as "file.ext,1" to get the icon index
                int nIconIndex = PathParseIconLocation(bstrIconName);

                // If the entry was ",#" then it's an index into our built-in button bitmap
                if (*bstrIconName == L'\0')
                {
                    pvarProperty->vt = VT_I4;
                    pvarProperty->lVal = nIconIndex;
                    SysFreeString(bstrIconName);
                    return hr;
                }
                else
                {
                    rhIcon = _ExtractIcon(bstrIconName, nIconIndex, nWidth, nHeight);
                }
                SysFreeString(bstrIconName);
            }
        }

        if (rhIcon)
        {
            pvarProperty->vt = VT_BYREF;
            pvarProperty->byref = rhIcon;
        }
        else
        {
            VariantInit(pvarProperty);
        }
    }
    return hr;
}

//
// Implementation of IBrowserExtension::GetProperty().  There are two important points here:
// (1) We are lazy loading the appropriate icons.  This way if the user never goes into small icon
//     mode we never create the images...
// (2) If we are called with a NULL pvarProperty then we must still return S_OK if the iPropID
//     is for a property that we support and E_NOTIMPL if we do not.  This is why the if (pvarProperty)
//     check is done for each case rather tan outside the case block.  This behavior is important
//     for CBrowserExtension::Update() who passes in a NULL pvarProperty but still is trying to determine
//     what kind of extension this is!
//
STDMETHODIMP CToolbarExt::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty)
{
    HRESULT hr = S_OK;

    if (pvarProperty)
        VariantInit(pvarProperty); // in case of failure
    
    switch (iPropID)
    {
        case TBEX_BUTTONTEXT:
            if (pvarProperty)
            {
                pvarProperty->bstrVal = SysAllocString(_bstrButtonText);
                if (pvarProperty->bstrVal)
                {
                    pvarProperty->vt = VT_BSTR;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
            break;

        case TBEX_GRAYICON:

            // For Whistler, we now use a 24 x 24 icons
            if (SHUseClassicToolbarGlyphs())
            {
                hr = _GetIcon(TEXT("Icon"), 20, 20, _hIcon, pvarProperty);
            }
            else
            {
                hr = _GetIcon(TEXT("Icon"), 24, 24, _hIcon, pvarProperty);
            }
            break;

        case TBEX_GRAYICONSM:
            hr = _GetIcon(TEXT("Icon"), 16, 16, _hIconSm, pvarProperty);
            break;

        case TBEX_HOTICON:
            // For Whistler, we now use a 24 x 24 icons
            if (SHUseClassicToolbarGlyphs())
            {
                hr = _GetIcon(TEXT("HotIcon"), 20, 20, _hHotIcon, pvarProperty);
            }
            else
            {
                hr = _GetIcon(TEXT("HotIcon"), 24, 24, _hHotIcon, pvarProperty);
            }
            break;

        case TBEX_HOTICONSM:
            hr = _GetIcon(TEXT("HotIcon"), 16, 16, _hHotIconSm, pvarProperty);
            break;

        case TBEX_DEFAULTVISIBLE:
            if (pvarProperty)
            {
                BOOL fVisible = _RegGetBoolValue(L"Default Visible", FALSE);
                pvarProperty->vt = VT_BOOL;
                pvarProperty->boolVal = fVisible ? VARIANT_TRUE : VARIANT_FALSE;
            }
            break;

        default:
            hr = E_NOTIMPL;
    }

    return hr;
}

//
// IOleCommandTarget Implementation
//
STDMETHODIMP CToolbarExt::QueryStatus(const GUID* pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT* pCmdText)
{
    HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
    {
        // Default to all commands enabled
        for (ULONG i = 0; i < cCmds; i++)
        {
//            if (prgCmds[i].cmdID == 1)
                // Execing this object is supported and can be done at this point
                rgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED;
//            else
//                prgCmds[i].cmdf = 0;
        }
        hr = S_OK;
    }

    // Return an empty pCmdText
    if (pCmdText != NULL)
    {
        pCmdText->cwActual = 0;
    }
    return hr;
}

//
// IObjectWithSite Implementation
//
STDMETHODIMP CToolbarExt::SetSite(IUnknown* pUnkSite)
{
    if (_pisb != NULL)
    {
        _pisb->Release();
        _pisb = NULL;
    }
    
    if (pUnkSite)
        pUnkSite->QueryInterface(IID_IShellBrowser, (void **)&_pisb);
        
    return S_OK;
}
   
STDMETHODIMP CToolbarExt::GetSite(REFIID riid, void ** ppvSite)
{
    return E_NOTIMPL;
}


BOOL CToolbarExt::_RegGetBoolValue
(
    LPCWSTR         pszPropName,
    BOOL            fDefault
)
{
    WCHAR szData[MAX_PATH];
    DWORD cbData = SIZEOF(szData);

    if ((_hkeyCurrentLang && RegQueryValueEx(_hkeyCurrentLang, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS) ||
        (_hkeyThisExtension && RegQueryValueEx(_hkeyThisExtension, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS))
    {
        if ((0 == StrCmpI(L"TRUE", szData)) || 
            (0 == StrCmpI(L"YES", szData)))
        {
            fDefault = TRUE;        // We read TRUE from the registry.
        }
        else if ((0 == StrCmpI(L"FALSE", szData)) || 
            (0 == StrCmpI(L"NO", szData)))
        {
            fDefault = FALSE;        // We read TRUE from the registry.
        }
    }

    return fDefault;
}



// Private Helper Functions
//
// shlwapi has some similar function; however, they all insist on reopening and closing the key in question
// with each read.  It is explicitly suggested that we use our own helper if we are caching the key...
BOOL CToolbarExt::_RegReadString
(
    HKEY hkeyThisExtension,
    LPCWSTR pszPropName,
    BSTR * pbstrProp,
    BOOL fExpand            // = FALSE, Expand Environment strings
    )
{
    WCHAR   szData[MAX_PATH];
    *pbstrProp = NULL;
    BOOL fSuccess = FALSE;
    
    // First try the optional location for localized content
    if (_hkeyCurrentLang)
    {
        if (SUCCEEDED(SHLoadRegUIString(_hkeyCurrentLang, pszPropName, szData, ARRAYSIZE(szData))))
        {
            fSuccess = TRUE;
        }
    }

    // Next try default location
    if (!fSuccess && _hkeyThisExtension)
    {
        if (SUCCEEDED(SHLoadRegUIString(hkeyThisExtension, pszPropName, szData, ARRAYSIZE(szData))))
        {
            fSuccess = TRUE;
        }
    }

    if (fSuccess)
    {
        LPWSTR psz = szData;
        WCHAR szExpand[MAX_PATH];
        if (fExpand)
        {
            SHExpandEnvironmentStrings(szData, szExpand, ARRAYSIZE(szExpand));
            psz = szExpand;
        }
        *pbstrProp = SysAllocString(psz);
    }
    return (NULL != *pbstrProp);
}


///////////////////////////////////////////////////////////
// Class CToolbarExtBand
//
// This class adds to the base functionality of CToolbarExt
// by storing the CLSID for a registered band, and displaying that
// band upon execution of IOleCommandTarget::Exec
//
//
STDAPI CToolbarExtBand_CreateInstance(
            IUnknown        * punkOuter,
            IUnknown        ** ppunk,
            LPCOBJECTINFO   poi
            )
{
    HRESULT hr = S_OK;

    *ppunk = NULL;

    CToolbarExtBand * lpTEB = new CToolbarExtBand();

    if (lpTEB == NULL)
        hr = E_OUTOFMEMORY;
    else
        *ppunk = SAFECAST(lpTEB, IBrowserExtension *);

    return hr;
}

// Constructor / Destructor
//
CToolbarExtBand::CToolbarExtBand()
{
    ASSERT(_cRef == 1);
    ASSERT(_bBandState == FALSE);
    ASSERT(_bstrBandCLSID == NULL);
}

// Destructor
//
CToolbarExtBand::~CToolbarExtBand() 
{ 
    if (_bstrBandCLSID)
        SysFreeString(_bstrBandCLSID);
}

// IBrowserExtension::Init()   We pass the majroity of the work on to the base class, then we load
// the BandCLSID and cache it.
STDMETHODIMP CToolbarExtBand::Init(REFGUID rguid)
{
    HRESULT hr = CToolbarExt::Init(rguid);
    
    _RegReadString(_hkeyThisExtension, TEXT("BandCLSID"), &_bstrBandCLSID);
    
    if (!(_bstrButtonText && _bstrBandCLSID))
        hr = E_FAIL;

    return hr;
}
    
STDMETHODIMP CToolbarExtBand::QueryStatus
(
    const GUID * pguidCmdGroup,
    ULONG  cCmds,
    OLECMD prgCmds[],
    OLECMDTEXT * pCmdText
    )
{
    HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
    {
        VARIANT varClsid;
      
        // Default to all commands enabled
        for (ULONG i = 0; i < cCmds; i++)
        {
            varClsid.vt = VT_BSTR;
            varClsid.bstrVal = _bstrBandCLSID;
            prgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED;

            hr = IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_ISBROWSERBARVISIBLE, 0, &varClsid, NULL);
            if (S_OK == hr)
            {
                prgCmds[i].cmdf |= OLECMDF_LATCHED;
            }
        }
        hr = S_OK;
    }
    return hr;
}

// Take the pIShellBrowser (obtained from IObjectWithSite::SetSite()) and disply the band
STDMETHODIMP CToolbarExtBand::Exec( 
                const GUID              * pguidCmdGroup,
                DWORD                   nCmdID,
                DWORD                   nCmdexecopt,
                VARIANT                 * pvaIn,
                VARIANT                 * pvaOut
                )
{
    HRESULT hr = E_FAIL;
    
    if (_pisb)
    {
        VARIANT varClsid;
        varClsid.vt = VT_BSTR;
        varClsid.bstrVal = _bstrBandCLSID;
      
        _bBandState = !_bBandState;
        IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_SHOWBROWSERBAR, _bBandState, &varClsid, NULL);

        hr = S_OK;
    }
    
    return hr;
}


///////////////////////////////////////////////////////////////////////
// Class CToolbarExtExec
//
// Expands on the base class by adding support for tools menu plug-ins.
// An instance of this class can be a button OR a menu OR BOTH.  It also
// keeps track of a BSTR which it ShellExecutes in its IOleCommandTarget::Exec()
//
STDAPI CToolbarExtExec_CreateInstance(
            IUnknown        * punkOuter,
            IUnknown        ** ppunk,
            LPCOBJECTINFO   poi
            )
{
    HRESULT hr = S_OK;

    *ppunk = NULL;

    CToolbarExtExec * lpTEE = new CToolbarExtExec();

    if (lpTEE == NULL)
        hr = E_OUTOFMEMORY;
    else
        *ppunk = SAFECAST(lpTEE, IBrowserExtension *);

    return hr;
}

CToolbarExtExec::CToolbarExtExec()
{
    ASSERT(_cRef == 1);
    ASSERT(_bstrToolTip == NULL);
    ASSERT(_bstrExec == NULL);
    ASSERT(_bstrScript == NULL);
    ASSERT(_bstrMenuText == NULL);
    ASSERT(_bstrMenuCustomize == NULL);
    ASSERT(_bstrMenuStatusBar == NULL);
    ASSERT(_punkExt == NULL);
}

CToolbarExtExec::~CToolbarExtExec()
{
    if (_bstrToolTip)
        SysFreeString(_bstrToolTip);

    if (_bstrExec)
        SysFreeString(_bstrExec);

    if (_bstrScript)
        SysFreeString(_bstrScript);

    if (_bstrMenuText)
        SysFreeString(_bstrMenuText);

    if (_bstrMenuCustomize)
        SysFreeString(_bstrMenuCustomize);

    if (_bstrMenuStatusBar)
        SysFreeString(_bstrMenuStatusBar);

    if (_punkExt)
        _punkExt->Release();
}

// Pass on the work for the toolbar button intiaztion to the base class then determine the object
// type and initialize the menu information if necessary...
STDMETHODIMP CToolbarExtExec::Init(REFGUID rguid)
{
    HRESULT hr = CToolbarExt::Init(rguid);

    // If the baseclass initialization went OK, then we have a working button
    if (hr == S_OK)
        _bButton = TRUE;

    // Get app and/or script to execute (optional)
    _RegReadString(_hkeyThisExtension, TEXT("Exec"), &_bstrExec, TRUE);
    _RegReadString(_hkeyThisExtension, TEXT("Script"), &_bstrScript, TRUE);

        
    // See if we have a menu item
    if (_RegReadString(_hkeyThisExtension, TEXT("MenuText"), &_bstrMenuText))
    {
        _RegReadString(_hkeyThisExtension, TEXT("MenuCustomize"), &_bstrMenuCustomize);
        _RegReadString(_hkeyThisExtension, TEXT("MenuStatusBar"), &_bstrMenuStatusBar);
        _bMenuItem = TRUE;
    }

    if (_bMenuItem || _bButton)
    {
        hr = S_OK;
    }

    return hr;
}

// It we're a button try passing the work on to the base class, if that doesn't cut it we'll
// check the menu stuff...
STDMETHODIMP CToolbarExtExec::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty)
{
    HRESULT     hr = S_OK;
    BOOL        fImple = FALSE;

    if (_bButton)
    {
        // If The generic button's getproperty returns S_OK then our job here is done
        if (CToolbarExt::GetProperty(iPropID, pvarProperty) == S_OK)
            fImple = TRUE;
    }

    if (_bMenuItem && !fImple)
    {
        fImple = TRUE;

        if (pvarProperty)
            VariantInit(pvarProperty);

        switch (iPropID)
        {
            case TMEX_CUSTOM_MENU:
            {
                if (pvarProperty)
                {
                    pvarProperty->bstrVal = SysAllocString(_bstrMenuCustomize);
                    if (pvarProperty->bstrVal)
                    {
                        pvarProperty->vt = VT_BSTR;
                    }
                    else
                    {
                        hr = E_OUTOFMEMORY;
                    }
                }
            }
            break;

            case TMEX_MENUTEXT:
                if (pvarProperty)
                {
                    pvarProperty->bstrVal = SysAllocString(_bstrMenuText);
                    if (pvarProperty->bstrVal)
                    {
                        pvarProperty->vt = VT_BSTR;
                    }
                    else
                    {
                        hr = E_OUTOFMEMORY;
                    }
                }
                break;

            case TMEX_STATUSBARTEXT:
                if (pvarProperty)
                {
                    pvarProperty->bstrVal = SysAllocString(_bstrMenuStatusBar);
                    if (pvarProperty->bstrVal)
                    {
                        pvarProperty->vt = VT_BSTR;
                    }
                    else
                    {
                        hr = E_OUTOFMEMORY;
                    }
                }
                break;

            default:
                fImple = FALSE;
        }
    }

    if (!fImple)
        hr = E_NOTIMPL;

    return hr;
}

STDMETHODIMP CToolbarExtExec::SetSite(IUnknown* punkSite)
{
    // Give the external object our site
    IUnknown_SetSite(_punkExt, punkSite);
    
    // Call base class
    return CToolbarExt::SetSite(punkSite);
}

STDMETHODIMP CToolbarExtExec::QueryStatus(const GUID * pguidCmdGroup, ULONG  cCmds, OLECMD rgCmds[], OLECMDTEXT * pCmdText)
{
    HRESULT hr = S_OK;

    // Pass query to external object if it exists
    IOleCommandTarget* pCmd;
    if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd)))
    {
        hr = pCmd->QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText);
        pCmd->Release();
    }
    else
    {
        // Let base class handle this
        hr = CToolbarExt::QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText);
    }

    return hr;
}

// Shell execute the _bstrExec 
STDMETHODIMP CToolbarExtExec::Exec(
                const GUID              * pguidCmdGroup,
                DWORD                   nCmdId,
                DWORD                   nCmdexecopt,
                VARIANT                 * pvaIn,
                VARIANT                 * pvaOut
                )
{
    HRESULT hr = S_OK;

    //
    // The first time this is called, we lazy instantiate an external object if
    // one is registered.. This object can JIT in components and provide a
    // command target.
    //
    if (!_bExecCalled)
    {
        // We only do this once
        _bExecCalled = TRUE;

        BSTR bstrExtCLSID;
        if (_RegReadString(_hkeyThisExtension, TEXT("clsidExtension"), &bstrExtCLSID))
        {
            // We have an extension clsid, so create the object.  This gives the object an oportunity
            // to jit in code when its button or menu is invoked.
            CLSID clsidExt;

            if (CLSIDFromString(bstrExtCLSID, &clsidExt) == S_OK)
            {
                if (SUCCEEDED(CoCreateInstance(clsidExt, NULL, CLSCTX_INPROC_SERVER,
                                     IID_IUnknown, (void **)&_punkExt)))
                {
                    // Give the object our site (optional)
                    IUnknown_SetSite(_punkExt, _pisb);
                }
            }
            SysFreeString(bstrExtCLSID);
        }
    }

    // Pass command to external object if it exists
    IOleCommandTarget* pCmd;
    if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd)))
    {
        hr = pCmd->Exec(pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut);
        pCmd->Release();
    }

    // Run a script if one was specified
    if(_bstrScript && _pisb)
    {
        IOleCommandTarget *poct = NULL;
        VARIANT varArg;
        varArg.vt = VT_BSTR;
        varArg.bstrVal = _bstrScript;
        hr = _pisb->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&poct);
        if (SUCCEEDED(hr))
        {
            // Tell MSHTML to execute the script
            hr = poct->Exec(&CGID_MSHTML, IDM_RUNURLSCRIPT, 0, &varArg, NULL);
            poct->Release();
        }
    }

    // Launch executable if one was specified
    if (_bstrExec)
    {
        SHELLEXECUTEINFO sei = { 0 };

        sei.cbSize = sizeof(sei);
        sei.lpFile = _bstrExec;
        sei.nShow = SW_SHOWNORMAL;

        // We are using ShellExecuteEx over ShellExecute because the Unicode version of ShellExecute
        // is bogus on 95/98
        if (ShellExecuteExW(&sei) == FALSE)
            hr = E_FAIL;
    }

    return hr;
}
