/*++

   Copyright    (c)    1994-1998    Microsoft Corporation

   Module  Name :

        iisobj.cpp

   Abstract:

        IIS Objects

   Author:

        Ronald Meijer (ronaldm)

   Project:

        Internet Services Manager

   Revision History:

--*/



//
// Include Files
//
#include "stdafx.h"
#include "comprop.h"
#include "inetmgr.h"
#include "iisobj.h"
#include "machine.h"
#include <shlwapi.h>
#include "guids.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif



//
// Static initialization
//
BOOL    CIISObject::m_fIsExtension = FALSE;
CString CIISObject::s_strProperties;
CString CIISObject::s_strRunning;
CString CIISObject::s_strPaused;
CString CIISObject::s_strStopped;
CString CIISObject::s_strUnknown;
CString CIISObject::s_strYes;
CString CIISObject::s_strNo;
CString CIISObject::s_strTCPIP;
CString CIISObject::s_strNetBIOS;
CString CIISObject::s_strDefaultIP;
CString CIISObject::s_strRedirect;
time_t  CIISObject::s_lExpirationTime = (5L * 60L); // 5 Minutes
LPCONSOLENAMESPACE CIISObject::s_lpcnsScopeView = NULL;



//
// Backup/restore taskpad gif resource
//
#define RES_TASKPAD_BACKUP          _T("/img\\backup.gif")



LPCTSTR
PrependParentPath(
    IN OUT CString & strPath,
    IN LPCTSTR lpszParent,
    IN TCHAR chSep
    )
/*++

Routine Description:

    Helper function to prepend a new parent to the given path

Arguments:

    CString & strPath       : Current path
    LPCTSTR lpszParent      : Parent to be prepended
    TCHAR chSep             : Separator character

Return Value:

    Pointer to the path

--*/
{
    if (strPath.IsEmpty())
    {
        strPath = lpszParent;
    }
    else
    {
        CString strTail(strPath);
        strPath = lpszParent;
        strPath += chSep;
        strPath += strTail; 
    }

    TRACEEOLID("PrependParentPath: " << strPath);

    return strPath;
}



HRESULT
ShellExecuteDirectory(
    IN LPCTSTR lpszCommand,
    IN LPCTSTR lpszOwner,
    IN LPCTSTR lpszDirectory
    )
/*++

Routine Description:

    Shell Open or explore on a given directory path

Arguments:

    LPCTSTR lpszCommand    : "open" or "explore"
    LPCTSTR lpszOwner      : Owner server
    LPCTSTR lpszDirectory  : Directory path

Return Value:

    Error return code.

--*/
{
    CString strDir;

    if (::IsServerLocal(lpszOwner) || ::IsUNCName(lpszDirectory))
    {
        //
        // Local directory, or already a unc path
        //
        strDir = lpszDirectory;
    }
    else
    {
        ::MakeUNCPath(strDir, lpszOwner, lpszDirectory);
    }

    TRACEEOLID("Attempting to " << lpszCommand << " Path: " << strDir);

    CError err;
    {
        //
        // AFX_MANAGE_STATE required for wait cursor
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState() );
        CWaitCursor wait;

        if (::ShellExecute(
            NULL, 
            lpszCommand, 
            strDir, 
            NULL,
            _T(""), 
            SW_SHOW
            ) <= (HINSTANCE)32)
        {
            err.GetLastWinError();
        }
    }

    return err;
}



/* static */
void
CIISObject::BuildResultView(
    IN LPHEADERCTRL pHeader,
    IN int cColumns,
    IN int * pnIDS,
    IN int * pnWidths
    )
/*++

Routine Description:

    Build the result view columns

Routine Description:

    LPHEADERCTRL pHeader    : Header control
    int cColumns            : Number of columns
    int * pnIDS             : Array of column header strings
    int * pnWidths          : Array of column widths

Routine Description:

    None

--*/
{
    ASSERT(pHeader != NULL);

    //
    // Needed for loadstring
    //
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    CString str;

    for (int n = 0; n < cColumns; ++n)
    {
        VERIFY(str.LoadString(pnIDS[n]));
        pHeader->InsertColumn(n, str, LVCFMT_LEFT, pnWidths[n]);
    }
}



/* static */
BOOL
CIISObject::CanAddInstance(
    IN LPCTSTR lpszMachineName
    )
/*++

Routine Description:

    Helper function to determine if instances may be added on
    this machine

Arguments:

    LPCTSTR lpszMachineName        : Machine name

Return Value:

    TRUE if instances can be added

--*/
{
    //
    // Assume W3svc and ftpsvc have the same capabilities.
    //
    CServerCapabilities * pcap;
    
    pcap = new CServerCapabilities(lpszMachineName, SZ_MBN_WEB);

    if (!pcap)
    {
        return FALSE;
    }

    if (FAILED(pcap->LoadData()))
    {
        //
        // Try ftp
        //
        delete pcap;
        pcap = new CServerCapabilities(lpszMachineName, SZ_MBN_FTP);
    }
    
    if (!pcap || FAILED(pcap->LoadData()))
    {
        if (pcap)
        {
            delete pcap;
        }

        return FALSE;
    }

    BOOL fCanAdd = pcap->HasMultipleSites();

    delete pcap;

    return fCanAdd;
}



BOOL
CIISObject::IsScopeSelected()
/*++

Routine Description:

    Return TRUE if the scope is currently selected

Arguments:

    None

Return Value:

    TRUE if the item is currently selected, FALSE otherwise

--*/
{
    ASSERT(s_lpcnsScopeView != NULL);

    HSCOPEITEM hItem = GetScopeHandle();
    ASSERT(hItem != NULL);

    if (hItem != NULL)
    {
        SCOPEDATAITEM item;
        ::ZeroMemory(&item, sizeof(SCOPEDATAITEM));
        item.mask = SDI_STATE;
        item.nState = MMC_SCOPE_ITEM_STATE_EXPANDEDONCE;
        item.ID = hItem;

        HRESULT hr = s_lpcnsScopeView->GetItem(&item);

        if (SUCCEEDED(hr))
        {
            return (item.nState & MMC_SCOPE_ITEM_STATE_EXPANDEDONCE) != 0;
        }
    }

    return FALSE;    
}



void
CIISObject::RefreshDisplayInfo()
/*++

Routine Description:

    Refresh the display info parameters in the scope view

Arguments:

    None

Return Value:

    None

--*/
{
    if (IsLeafNode())
    {
        //
        // Not supported on result items
        //
        return;
    }

    ASSERT(s_lpcnsScopeView != NULL);

    HSCOPEITEM hItem = GetScopeHandle();
    ASSERT(hItem != NULL);

    SCOPEDATAITEM item;
    ::ZeroMemory(&item, sizeof(SCOPEDATAITEM));

    //
    // Since we're using a callback, this is
    // all we need to do here
    //
    item.mask = SDI_STR | SDI_IMAGE | SDI_OPENIMAGE;
    item.displayname = MMC_CALLBACK;
    item.nOpenImage = item.nImage = QueryBitmapIndex();
    item.ID = hItem;

    s_lpcnsScopeView->SetItem(&item);
}
    


CIISObject::CIISObject(
    IN const GUID guid,
    IN LPCTSTR lpszNodeName,        OPTIONAL
    IN LPCTSTR lpszPhysicalPath     OPTIONAL
    )
/*++

Routine Description:

    Constructor for CIISObject.  Initialise static member functions
    if not yet initialized.  This is a protected constructor of
    an abstract base class.
                                                                       
Arguments:

    const GUID guid             : GUID of the object
    LPCTSTR lpszNodeName        : Node name
    LPCTSTR lpszPhysicalPath    : Physical path (or empty)

Return Value:

    N/A

--*/
    : m_hScopeItem(NULL),
      m_guid(guid),
      m_strNodeName(lpszNodeName),
      m_strPhysicalPath(lpszPhysicalPath),
      m_strRedirPath(),
      m_fChildOnlyRedir(FALSE),
      m_fIsParentScope(FALSE),
      m_tmChildrenExpanded(0L)
{
    //
    // Initialize static members
    //
    if (CIISObject::s_strRunning.IsEmpty())
    {
        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(AfxGetStaticModuleState());
        TRACEEOLID("Initializing static strings");

        VERIFY(CIISObject::s_strRunning.LoadString(IDS_RUNNING));
        VERIFY(CIISObject::s_strPaused.LoadString(IDS_PAUSED));
        VERIFY(CIISObject::s_strStopped.LoadString(IDS_STOPPED));
        VERIFY(CIISObject::s_strUnknown.LoadString(IDS_UNKNOWN));
        VERIFY(CIISObject::s_strProperties.LoadString(IDS_MENU_PROPERTIES));
        VERIFY(CIISObject::s_strYes.LoadString(IDS_YES));
        VERIFY(CIISObject::s_strNo.LoadString(IDS_NO));
        VERIFY(CIISObject::s_strTCPIP.LoadString(IDS_TCPIP));
        VERIFY(CIISObject::s_strNetBIOS.LoadString(IDS_NETBIOS));
        VERIFY(CIISObject::s_strDefaultIP.LoadString(IDS_DEFAULT_IP));
        VERIFY(CIISObject::s_strRedirect.LoadString(IDS_REDIRECTED));    
    }
}

BOOL CIISObject::IsValidObject() const
{
    // CIISObject could have only one of the following GUIDs
    GUID guid = QueryGUID();
    if (    guid == cInternetRootNode
        ||  guid == cMachineNode
        ||  guid == cServiceCollectorNode
        ||  guid == cInstanceCollectorNode
        ||  guid == cInstanceNode
        ||  guid == cChildNode
        ||  guid == cFileNode
        )
        return TRUE;
    return FALSE;
}

CIISObject::operator LPCTSTR()
/*++

Routine Description:

    Typecast operator to call out the display text
                                                                       
Arguments:

    N/A

Return Value:

    Display text

--*/
{
    static CString strText;

    return GetDisplayText(strText);
}    

//
// Separator menu item definition
//
CONTEXTMENUITEM menuSep = 
{
    NULL,
    NULL,
    -1,
    CCM_INSERTIONPOINTID_PRIMARY_TOP,
    0,
    CCM_SPECIAL_SEPARATOR
};



//
// Menu item definition that uses resource definitions, and
// provides some additional information for taskpads.
//
typedef struct tagCONTEXTMENUITEM_RC
{
    UINT nNameID;
    UINT nStatusID;
    UINT nDescriptionID;
    LONG lCmdID;
    LONG lInsertionPointID;
    LONG fSpecialFlags;
    LPCTSTR lpszMouseOverBitmap;
    LPCTSTR lpszMouseOffBitmap;
} CONTEXTMENUITEM_RC;



//
// Important!  The array indices below must ALWAYS be one
// less than the menu ID -- keep in sync with enumeration
// in inetmgr.h!!!!
//
static CONTEXTMENUITEM_RC menuItemDefs[] = 
{
    //
    // Menu Commands in toolbar order
    //
    { IDS_MENU_CONNECT,         IDS_MENU_TT_CONNECT,         -1,                          IDM_CONNECT,              CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_DISCOVER,        IDS_MENU_TT_DISCOVER,        -1,                          IDM_DISCOVER,             CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_START,           IDS_MENU_TT_START,           -1,                          IDM_START,                CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_STOP,            IDS_MENU_TT_STOP,            -1,                          IDM_STOP,                 CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_PAUSE,           IDS_MENU_TT_PAUSE,           -1,                          IDM_PAUSE,                CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    //
    // These are menu commands that do not show up in the toolbar
    //
    { IDS_MENU_EXPLORE,         IDS_MENU_TT_EXPLORE,         -1,                          IDM_EXPLORE,              CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_OPEN,            IDS_MENU_TT_OPEN,            -1,                          IDM_OPEN,                 CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_BROWSE,          IDS_MENU_TT_BROWSE,          -1,                          IDM_BROWSE,               CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_PROPERTIES,      IDS_MENU_TT_PROPERTIES,      -1,                          IDM_CONFIGURE,            CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_DISCONNECT,      IDS_MENU_TT_DISCONNECT,      -1,                          IDM_DISCONNECT,           CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_BACKUP,          IDS_MENU_TT_BACKUP,          IDS_MENU_TT_BACKUP,          IDM_METABACKREST,         CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_SHUTDOWN_IIS,    IDS_MENU_TT_SHUTDOWN_IIS,    -1,                          IDM_SHUTDOWN,             CCM_INSERTIONPOINTID_PRIMARY_TOP,  0, NULL, NULL, },
    { IDS_MENU_NEWVROOT,        IDS_MENU_TT_NEWVROOT,        IDS_MENU_DS_NEWVROOT,        IDM_NEW_VROOT,            CCM_INSERTIONPOINTID_PRIMARY_NEW,  0, RES_TASKPAD_NEWVROOT, RES_TASKPAD_NEWVROOT, },
    { IDS_MENU_NEWINSTANCE,     IDS_MENU_TT_NEWINSTANCE,     IDS_MENU_DS_NEWINSTANCE,     IDM_NEW_INSTANCE,         CCM_INSERTIONPOINTID_PRIMARY_NEW,  0, RES_TASKPAD_NEWSITE,  RES_TASKPAD_NEWSITE,  },
    { IDS_MENU_TASKPAD,         IDS_MENU_TT_TASKPAD,         -1,                          IDM_VIEW_TASKPAD,         CCM_INSERTIONPOINTID_PRIMARY_VIEW, 0, NULL, NULL, },
    { IDS_MENU_SECURITY_WIZARD, IDS_MENU_TT_SECURITY_WIZARD, IDS_MENU_TT_SECURITY_WIZARD, IDM_TASK_SECURITY_WIZARD, CCM_INSERTIONPOINTID_PRIMARY_TASK, 0, RES_TASKPAD_SECWIZ, RES_TASKPAD_SECWIZ, },             
};



/* static */
HRESULT
CIISObject::AddMenuItemByCommand(
    IN LPCONTEXTMENUCALLBACK lpContextMenuCallback,
    IN LONG lCmdID,

    IN LONG fFlags
    )
/*++

Routine Description:

    Add menu item by command

Arguments:

    LPCONTEXTMENUCALLBACK lpContextMenuCallback : Callback pointer
    LONG lCmdID                                 : Command ID
    LONG fFlags                                 : Flags
    
Return Value:

    HRESULT

--*/
{
    ASSERT(lpContextMenuCallback != NULL);

    //
    // Offset 1 menu commands
    //
    LONG l = lCmdID -1; 

    CString strName;
    CString strStatus;

    {
        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState());
        VERIFY(strName.LoadString(menuItemDefs[l].nNameID));
        VERIFY(strStatus.LoadString(menuItemDefs[l].nStatusID));
    }

    CONTEXTMENUITEM cmi;
    cmi.strName = strName.GetBuffer(0);
    cmi.strStatusBarText = strStatus.GetBuffer(0);
    cmi.lCommandID = menuItemDefs[l].lCmdID;
    cmi.lInsertionPointID = menuItemDefs[l].lInsertionPointID;
    cmi.fFlags = fFlags;
    cmi.fSpecialFlags = menuItemDefs[l].fSpecialFlags;

    return lpContextMenuCallback->AddItem(&cmi);
}



/* static */
HRESULT
CIISObject::AddTaskpadItemByInfo(
    OUT MMC_TASK * pTask,
    IN  LONG    lCommandID,
    IN  LPCTSTR lpszMouseOn,
    IN  LPCTSTR lpszMouseOff,
    IN  LPCTSTR lpszText,
    IN  LPCTSTR lpszHelpString
    )
/*++

Routine Description:

    Add taskpad item from the information given

Arguments:

    MMC_TASK * pTask            : Task info
    LPCTSTR lpszMouseOn         : Mouse on URL
    LPCTSTR lpszMouseOff        : Mouse off URL
    LPCTSTR lpszText            : Text to be displayed
    LPCTSTR lpszHelpString      : Help string

Return Value:

    HRESULT

--*/
{
    TRACEEOLID(lpszMouseOn);
    TRACEEOLID(lpszMouseOff);

    pTask->sDisplayObject.eDisplayType = MMC_TASK_DISPLAY_TYPE_VANILLA_GIF;

    pTask->sDisplayObject.uBitmap.szMouseOverBitmap 
        = CoTaskDupString((LPCOLESTR)lpszMouseOn);
    pTask->sDisplayObject.uBitmap.szMouseOffBitmap 
        = CoTaskDupString((LPCOLESTR)lpszMouseOff);
    pTask->szText = CoTaskDupString((LPCOLESTR)lpszText);
    pTask->szHelpString = CoTaskDupString((LPCOLESTR)lpszHelpString);

    if (pTask->sDisplayObject.uBitmap.szMouseOverBitmap
     && pTask->sDisplayObject.uBitmap.szMouseOffBitmap
     && pTask->szText
     && pTask->szHelpString
       )
    {
        //
        // Add action
        //
        pTask->eActionType = MMC_ACTION_ID;
        pTask->nCommandID = lCommandID;

        return S_OK;
    }

    //
    // Failed
    //
    CoTaskStringFree(pTask->sDisplayObject.uBitmap.szMouseOverBitmap);
    CoTaskStringFree(pTask->sDisplayObject.uBitmap.szMouseOffBitmap);
    CoTaskStringFree(pTask->szText);
    CoTaskStringFree(pTask->szHelpString);

    return S_FALSE;
}



/* static */
HRESULT
CIISObject::AddTaskpadItemByCommand(
    IN  LONG lCmdID,
    OUT MMC_TASK * pTask,
    IN  HINSTANCE hInstance     OPTIONAL
    )
/*++

Routine Description:

    Add taskpad item by command

Arguments:

    LONG lCmdID             : Command ID
    CString & strResURL     : Resource ID    
    MMC_TASK * pTask        : Task structure to be filled in

Return Value:

    HRESULT

--*/
{
    ASSERT(pTask != NULL);

    //
    // Offset 1 menu commands
    //
    LONG l = lCmdID -1; 

    CString strName;
    CString strStatus;

    CString strResURL;
    HRESULT hr = BuildResURL(strResURL, hInstance);

    if (FAILED(hr))
    {
        return hr;
    }

    {
        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState());
        VERIFY(strName.LoadString(menuItemDefs[l].nStatusID));
        VERIFY(strStatus.LoadString(menuItemDefs[l].nDescriptionID));
    }

    //
    // Make sure this menu command was intended to go onto a taskpad
    //
    ASSERT(menuItemDefs[l].lpszMouseOverBitmap != NULL);
    ASSERT(menuItemDefs[l].lpszMouseOffBitmap != NULL);    

    //
    // Fill out bitmap URL (use defaults if nothing provided)
    //
    CString strMouseOn(strResURL);
    CString strMouseOff(strResURL);

    strMouseOn += menuItemDefs[l].lpszMouseOverBitmap;
    strMouseOff += menuItemDefs[l].lpszMouseOffBitmap;

    return AddTaskpadItemByInfo(
        pTask,
        menuItemDefs[l].lCmdID,
        strMouseOn,
        strMouseOff,
        strName,
        strStatus
        );
}



/* virtual */ 
HRESULT 
CIISObject::AddMenuItems(
    IN LPCONTEXTMENUCALLBACK lpContextMenuCallback
    )
/*++

Routine Description:

    Add menu items to the context that are valid for this
    object

Arguments:

    LPCONTEXTMENUCALLBACK lpContextMenuCallback : Callback

Return Value:

    HRESULT

--*/
{
    if (IsConnectable() && !m_fIsExtension)
    {
        AddMenuItemByCommand(lpContextMenuCallback, IDM_CONNECT);    
        AddMenuItemByCommand(lpContextMenuCallback, IDM_DISCONNECT);    
    }

    if (IsExplorable())
    {
        lpContextMenuCallback->AddItem(&menuSep);
        AddMenuItemByCommand(lpContextMenuCallback, IDM_EXPLORE);
    }
               
    if (IsOpenable())
    {
        AddMenuItemByCommand(lpContextMenuCallback, IDM_OPEN);
    }

    if (IsBrowsable())
    {
        AddMenuItemByCommand(lpContextMenuCallback, IDM_BROWSE);
    }

    if (IsControllable())
    {
        lpContextMenuCallback->AddItem(&menuSep);

        UINT nPauseFlags = IsPausable() ? 0 : MF_GRAYED;

        if (IsPaused())
        {
            nPauseFlags |= MF_CHECKED;
        }

        AddMenuItemByCommand(lpContextMenuCallback, IDM_START,  IsStartable() ? 0 : MF_GRAYED);
        AddMenuItemByCommand(lpContextMenuCallback, IDM_STOP,   IsStoppable()   ? 0 : MF_GRAYED);
        AddMenuItemByCommand(lpContextMenuCallback, IDM_PAUSE,  nPauseFlags);
    }

#ifdef MMC_PAGES

    //
    // Bring up private config menu item only if not
    // configurable through MMC
    //
    if (IsConfigurable() && !IsMMCConfigurable())
    {
        lpContextMenuCallback->AddItem(&menuSep);
        AddMenuItemByCommand(lpContextMenuCallback, IDM_CONFIGURE);
    }

#else

    if (IsConfigurable())
    {
        lpContextMenuCallback->AddItem(&menuSep);
        AddMenuItemByCommand(lpContextMenuCallback, IDM_CONFIGURE);
    }

#endif // MMC_PAGES

    return S_OK;
}



/* virtual */
HRESULT
CIISObject::AddNextTaskpadItem(
    OUT MMC_TASK * pTask
    )
/*++

Routine Description:

    Add next taskpad item

Arguments:

    MMC_TASK * pTask    : Task structure to fill in

Return Value:

    HRESULT

--*/
{
    //
    // CODEWORK:  Because of enumeration, this isn't easy
    //            to handle the way menu items are handled
    //
    ASSERT(FALSE);

    return S_FALSE;
}



/* virtual */
CIISObject * 
CIISObject::GetParentObject() const
/*++

Routine Description:

    Get the parent object (in the scope view) of this object.

Arguments:

    None

Return Value:

    Pointer to the parent object, or NULL if not found

--*/
{
    MMC_COOKIE cookie;
    HSCOPEITEM hParent;

    HRESULT hr = GetScopeView()->GetParentItem(
        GetScopeHandle(), 
        &hParent, 
        &cookie
        );

    if (hParent == NULL || cookie == 0L || FAILED(hr))
    {
        //
        // None found
        //
        return NULL;
    }

    return (CIISObject *)cookie;
}



LPCTSTR
CIISObject::BuildParentPath(
    OUT CString & strParent,
    IN  BOOL fMetabasePath
    ) const
/*++

Routine Description:

    Walk up the parent nodes to build either a metabase path,
    or a physical path to the parent of this node.

Arguments:

    CString & strParent     : Returns the parent path
    BOOL fMetabasePath      : If TRUE want full metabse path
                              If FALSE, relative path from the instance only

Return Value:

    Pointer to the path

--*/
{
    const CIISObject * pObject = this;

    //
    // Walk up the tree to build a proper parent path
    //
    for (;;)
    {
        if (pObject->IsTerminalPoint(fMetabasePath))
        {
            break;
        }

        pObject = pObject->GetParentObject();

        if (pObject == NULL)
        {
            //
            // Should have stopped before this.
            //
            ASSERT(FALSE);
            break;
        }

        PrependParentPath(
            strParent, 
            pObject->QueryNodeName(fMetabasePath), 
            g_chSep
            );

        //
        // Keep looking
        //
    }

    TRACEEOLID("BuildParentPath: " << strParent);
    
    return strParent;
}



LPCTSTR
CIISObject::BuildFullPath(
    OUT CString & strPath,
    IN  BOOL fMetabasePath
    ) const
/*++ 

Routine Description:

    Build complete path for the current object.  Either a metabase path or
    a directory path.

Arguments:

Return Value:

    Pointer to the path

--*/
{
    strPath = QueryNodeName(fMetabasePath);
    BuildParentPath(strPath, fMetabasePath);
    TRACEEOLID("CIISObject::BuildFullPath:" << strPath);

    return strPath;
}



LPCTSTR 
CIISObject::BuildPhysicalPath(
    OUT CString & strPhysicalPath
    ) const
/*++

Routine Description:

    Build a physical path for the current node.  Starting with the current
    node, walk up the tree appending node names until a virtual directory
    with a real physical path is found

Arguments:

    CString & strPhysicalPath       : Returns file path

Return Value:

    Pointer to path

--*/
{
    const CIISObject * pObject = this;

    //
    // Walk up the tree to build a physical path
    //
    for (;;)
    {
        if (pObject->IsVirtualDirectory())
        {
            //
            // Path is properly terminated here
            //
            PrependParentPath(
                strPhysicalPath, 
                pObject->QueryPhysicalPath(), 
                _T('\\')
                );
            break;
        }

        PrependParentPath(
            strPhysicalPath, 
            pObject->QueryNodeName(), 
            _T('\\')
            );

        pObject = pObject->GetParentObject();

        if (pObject == NULL)
        {
            //
            // Should have stopped before this.
            //
            ASSERT(FALSE);
            break;
        }

        //
        // Keep looking
        //
    }

    TRACEEOLID("BuildPhysicalPath: " << strPhysicalPath);
    
    return strPhysicalPath;
}



DWORD
CIISObject::QueryInstanceID()
/*++

Routine Description:

    Return the ID of the owner instance

Arguments:

    None

Return Value:

    Owner instance ID or 0xffffffff

--*/
{
    CIISInstance * pInstance = FindOwnerInstance();
    
    return pInstance ? pInstance->QueryID() : 0xffffffff;
}



HRESULT
CIISObject::Open()
/*++

Routine Description:

    Open the physical path of the current node in the explorer

Arguments:

    None

Return Value:

    Error return code

--*/
{
    CString strPath;
    BuildPhysicalPath(strPath);

    return ShellExecuteDirectory(_T("open"), GetMachineName(), strPath);
}



HRESULT
CIISObject::Explore()
/*++

Routine Description:

    "explore" the physical path of the current node

Arguments:

    None

Return Value:

    Error return code

--*/
{
    CString strPath;
    BuildPhysicalPath(strPath);

    return ShellExecuteDirectory(_T("explore"), GetMachineName(), strPath);
}



HRESULT
CIISObject::Browse()
/*++

Routine Description:

    Bring up the current path in the browser.

Arguments:

    None

Return Value:

    Error return code

--*/
{
    CString strPath;

    BuildFullPath(strPath, FALSE);

    CIISInstance * pInstance = FindOwnerInstance();

    if (pInstance == NULL)
    {
        ASSERT(FALSE);

        return CError::HResult(ERROR_INVALID_PARAMETER);
    }

    return pInstance->ShellBrowsePath(strPath);
}



//
// Machine pages
//
CObListPlus * CIISMachine::s_poblNewInstanceCmds = NULL;
CString CIISMachine::s_strLocalMachine;



//
// Define result view for machine objects
//
int CIISMachine::rgnLabels[COL_TOTAL] =
{
    IDS_RESULT_COMPUTER_NAME,
    IDS_RESULT_COMPUTER_LOCAL,
    IDS_RESULT_COMPUTER_CONNECTION_TYPE,
    IDS_RESULT_STATUS,
};
    


int CIISMachine::rgnWidths[COL_TOTAL] =
{
    200,
    50,
    100,
    200,
};



/* static */
void
CIISMachine::InitializeHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result view headers for a machine object

Arguments:

    LPHEADERCTRL pHeader : Pointer to header control

Return Value:

    None.

--*/
{
    CIISObject::BuildResultView(pHeader, COL_TOTAL, rgnLabels, rgnWidths);
}



CIISMachine::CIISMachine(
    IN LPCTSTR lpszMachineName
    )
/*++

Routine Description:

    Constructor for machine object
                                                                       
Arguments:

    LPCTSTR lpszMachineName : Machine name

Return Value:

    N/A

--*/
    : CIISObject(cMachineNode),
      m_strMachineName(lpszMachineName),
      m_fLocal(::IsServerLocal(lpszMachineName))
{
    ASSERT(lpszMachineName != NULL);

    VERIFY(m_strDisplayName.LoadString(IDS_NODENAME));

    //
    // Initialize static members
    //
    if (CIISMachine::s_strLocalMachine.IsEmpty())
    {
        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(AfxGetStaticModuleState());
        TRACEEOLID("Initializing static strings");
        VERIFY(CIISMachine::s_strLocalMachine.LoadString(IDS_LOCAL_COMPUTER));
    }
	// Determine if current user is administrator
	CMetaKey key(lpszMachineName);
	if (key.Succeeded())
	{
		DWORD err = DetermineIfAdministrator(
			&key,
			_T("w3svc"),
			0,
			&m_fIsAdministrator);
		if (err != ERROR_SUCCESS)
		{
			err = DetermineIfAdministrator(
			   &key,
			   _T("msftpsvc"),
			   0,
			   &m_fIsAdministrator);
		}
	}
}



/* virtual */
BOOL 
CIISMachine::IsConfigurable() const
/*++

Routine Description:

    Determine if the machine is configurable, that is at least
    one property page handler was registered for it.
                                                                       
Arguments:

    None

Return Value:

    TRUE if the machine is configurable, FALSE otherwise

--*/
{
    //
    // Codework: do a metabase check here
    //    
    return m_fIsAdministrator;
    //return  CIISMachine::s_poblISMMachinePages != NULL
    //     && CIISMachine::s_poblISMMachinePages->GetCount() > 0;
}



/* virtual */
HRESULT
CIISMachine::Configure(
    IN CWnd * pParent OPTIONAL
    )
/*++

Routine Description:

    Configure the machine object.  In order for the machine object to
    be configurable, at least one property page add-on function must
    be defined.
                                                                       
Arguments:

    CWnd * pParent : Parent window handle

Return Value:

    Error return code

--*/
{
    AFX_MANAGE_STATE(::AfxGetStaticModuleState() );

    CError err;

/*
    try
    {
        CString strTitle, str;
        
        VERIFY(str.LoadString(IDS_MACHINE_PROPERTIES));

        strTitle.Format(str, GetMachineName());

        PROPSHEETHEADER psh;
        psh.dwSize = sizeof(psh);
        psh.dwFlags = PSH_DEFAULT | PSH_HASHELP;
        psh.hwndParent = pParent ? pParent->m_hWnd : NULL;
        psh.hInstance = NULL;
        psh.pszIcon = NULL;
        psh.pszCaption = (LPTSTR)(LPCTSTR)strTitle;
        psh.nStartPage = 0;
        psh.nPages = 0;
        psh.phpage = NULL;
        psh.pfnCallback = NULL;

        ASSERT(CIISMachine::s_poblISMMachinePages != NULL);
        CObListIter obli(*CIISMachine::s_poblISMMachinePages);
        CISMMachinePageExt * pipe;
        while (pipe = (CISMMachinePageExt *)obli.Next())
        {
            DWORD errDLL = pipe->Load();
            if (errDLL == ERROR_SUCCESS)
            {
                errDLL = pipe->AddPages(GetMachineName(), &psh);
            }

            if (errDLL != ERROR_SUCCESS)
            {
                //
                // Unable to load, display error message
                // with the name of the DLL.  This does not
                // necessarily pose problems for the rest
                // of the property sheet.
                //
                ::DisplayFmtMessage(
                    IDS_ERR_NO_LOAD,
                    MB_OK,
                    0,
                    (LPCTSTR)*pipe,
                    ::GetSystemMessage(errDLL)
                    );
            }
        }

        if (psh.nPages > 0)
        {
            //
            // Display the property sheet
            //
            PropertySheet(&psh);

            //
            // Now clean up the property sheet structure
            //
            // FreeMem(psh.phpage);
        }

        //
        // Unload the extentions
        //
        obli.Reset();
        while (pipe = (CISMMachinePageExt *)obli.Next())
        {
            if ((HINSTANCE)*pipe)
            {
                VERIFY(pipe->UnLoad());
            }
        }
    }
    catch(CMemoryException * e)
    {
        err = ERROR_NOT_ENOUGH_MEMORY;
        e->Delete();
    }
*/

    return err;
}



/* virtual */
HRESULT
CIISMachine::ConfigureMMC(
    IN LPPROPERTYSHEETCALLBACK lpProvider,
    IN LPARAM param,
    IN LONG_PTR handle
    )
/*++

Routine Description:

    Configure using MMC property sheets

Arguments:

    LPPROPERTYSHEETCALLBACK lpProvider  : Prop sheet provider
    LPARAM param                        : Prop parameter
    LONG_PTR handle                     : console handle

Return Value:

    Error return code

--*/
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    HINSTANCE hOld = AfxGetResourceHandle();
    AfxSetResourceHandle(GetModuleHandle(COMPROP_DLL_NAME));

    HINSTANCE hInstance = AfxGetInstanceHandle();
    CIISMachinePage * ppgMachine = new CIISMachinePage(
        GetMachineName(),
        hInstance
        );

    AfxSetResourceHandle(hOld);

    if (ppgMachine)
    {
        CError err(ppgMachine->QueryResult());

        if (err.Failed())
        {
            if (err == REGDB_E_CLASSNOTREG)
            {
                //
                // There isn't a metabase -- fail gracefully
                //
                ::AfxMessageBox(IDS_NO_MACHINE_PROPS);
                err.Reset();
            }

            delete ppgMachine;

            return err;
        }

        //
        // Patch MFC property page class.
        //
        ppgMachine->m_psp.dwFlags |= PSP_HASHELP;
        MMCPropPageCallback(&ppgMachine->m_psp);

        HPROPSHEETPAGE hPage = CreatePropertySheetPage(
            (LPCPROPSHEETPAGE)&ppgMachine->m_psp
            );

        if (hPage != NULL)
        {
            lpProvider->AddPage(hPage);

            return ERROR_SUCCESS;
        }
    }

    return ERROR_NOT_ENOUGH_MEMORY;   
}



/* virtual */ 
HRESULT 
CIISMachine::AddMenuItems(
    IN LPCONTEXTMENUCALLBACK lpContextMenuCallback
    )
/*++

Routine Description:

    Add menu items for machine object

Arguments:

    LPCONTEXTMENUCALLBACK pContextMenuCallback : Context menu items callback

Return Value:

    HRESULT

--*/
{
    CIISObject::AddMenuItems(lpContextMenuCallback);

    //
    // Add metabase backup/restore
    //
    lpContextMenuCallback->AddItem(&menuSep);
    AddMenuItemByCommand(lpContextMenuCallback, IDM_METABACKREST);

    //
    // Add 'IIS shutdown' command
    //
    // ISSUE: Should there be a capability bit?
    //
    AddMenuItemByCommand(lpContextMenuCallback, IDM_SHUTDOWN);

    if (!CanAddInstance(GetMachineName()))
    {
        return FALSE;
    }

    //
    // Add one 'new instance' for every service that supports
    // instances
    //
    ASSERT(CIISMachine::s_poblNewInstanceCmds);

    POSITION pos = CIISMachine::s_poblNewInstanceCmds->GetHeadPosition();

    int lCommandID = IDM_NEW_EX_INSTANCE;
    HRESULT hr;

    while(pos)
    {
        CNewInstanceCmd * pcmd = 
            (CNewInstanceCmd *)s_poblNewInstanceCmds->GetNext(pos);
        ASSERT(pcmd != NULL);

        CONTEXTMENUITEM cmi;
        cmi.strName = pcmd->GetMenuCommand().GetBuffer(0);
        cmi.strStatusBarText = pcmd->GetTTText().GetBuffer(0);
        cmi.lCommandID = lCommandID++;
        cmi.lInsertionPointID = CCM_INSERTIONPOINTID_PRIMARY_NEW;
        cmi.fFlags = 0;
        cmi.fSpecialFlags = 0;

        hr = lpContextMenuCallback->AddItem(&cmi);
        ASSERT(SUCCEEDED(hr));
    }

    return S_OK;
}



/* virtual */
HRESULT
CIISMachine::AddNextTaskpadItem(
    OUT MMC_TASK * pTask
    )
/*++

Routine Description:

    Add next taskpad item

Arguments:

    MMC_TASK * pTask    : Task structure to fill in

Return Value:

    HRESULT

--*/
{
    //
    // Add one 'new instance' for every service that supports
    // instances
    //
    ASSERT(CIISMachine::s_poblNewInstanceCmds);

    static POSITION pos = NULL;
    static BOOL fShownBackup = FALSE;
    static int lCommandID = -1;

    HRESULT hr;

    CString strName, strStatus, strMouseOn, strMouseOff;
    long lCmdID;

    //
    // Metabase backup/restore
    //
    if (!fShownBackup)
    {
        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState());

        CString strResURL;
        hr = BuildResURL(strResURL);

        if (FAILED(hr))
        {
            return hr;
        }
    
        VERIFY(strName.LoadString(IDS_MENU_BACKUP));
        VERIFY(strStatus.LoadString(IDS_MENU_TT_BACKUP));

        strMouseOn = strResURL + RES_TASKPAD_BACKUP;
        strMouseOff = strMouseOn;
        lCmdID = IDM_METABACKREST;

        ++fShownBackup;
    }
    else
    {
        //
        // Display the new instance commands for each service that 
        // supports it.
        //
        if (lCommandID == -1)
        {
            //
            // Initialize (use lCommandID == -1 as a flag
            // to indicate we're at the beginning of the list)
            //
            if (CanAddInstance(GetMachineName()))
            {
                pos = CIISMachine::s_poblNewInstanceCmds->GetHeadPosition();
                lCommandID = IDM_NEW_EX_INSTANCE;
            }
            else
            {
                return S_FALSE;
            }
        }

        if (pos == NULL)
        {
            //
            // No more items remain in the list.
            //
            lCommandID = -1;
            fShownBackup = FALSE;

            return S_FALSE;
        }

        CNewInstanceCmd * pcmd = 
            (CNewInstanceCmd *)s_poblNewInstanceCmds->GetNext(pos);
        ASSERT(pcmd != NULL);

        CString strResURL;
        hr = BuildResURL(strResURL, pcmd->QueryInstanceHandle());

        if (FAILED(hr))
        {
            return hr;
        }

        //
        // AFX_MANAGE_STATE required for resource load
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState());
        VERIFY(strStatus.LoadString(IDS_MENU_DS_NEWINSTANCE));

        strMouseOn = strResURL + RES_TASKPAD_NEWSITE;
        strMouseOff = strMouseOn;
        strName = pcmd->GetTTText();

        lCmdID = lCommandID++;
    }

    return AddTaskpadItemByInfo(
        pTask,
        lCmdID,
        strMouseOn,
        strMouseOff,
        strName,
        strStatus
        );
}



/* virtual */
LPCTSTR 
CIISMachine::GetDisplayText(
    OUT CString & strText
    ) const
/*++

Routine Description:

    Get the display text of this object
                                                                       
Arguments:

    CString & strText : String in which display text is to be returned

Return Value:

    Pointer to the string

--*/
{
    try
    {
        if (m_fIsExtension)
        {
            //
            // This is an extension
            //
            strText = m_strDisplayName;
        }
        else
        {
            if (IsLocalMachine())
            {
                ASSERT(!s_strLocalMachine.IsEmpty());
                strText.Format(s_strLocalMachine, m_strMachineName);
            }
            else
            {
                strText = m_strMachineName;
            }
        }
    }
    catch(CMemoryException * e)
    {
        TRACEEOLID("!!!exception in GetDisplayText");
        e->ReportError();
        e->Delete();
    }

    return strText;
}



/* virtual */
int 
CIISMachine::QueryBitmapIndex() const
/*++

Routine Description:

    Determine the bitmap index that represents the current item

Arguments:

    None

Return Value:

    Index into the bitmap strip

--*/
{
    if (m_fIsExtension)
    {
        return BMP_INETMGR;
    }

    return IsLocalMachine() ? BMP_LOCAL_COMPUTER : BMP_COMPUTER;
}



/* virtual */
void 
CIISMachine::GetResultDisplayInfo(
    IN  int nCol,
    OUT CString & str,
    OUT int & nImage
    ) const
/*++

Routine Description:

    Get result data item display information.

Arguments:

    int nCol            : Column number
    CString &  str      : String data
    int & nImage        : Image number

Return Value:

    None    

--*/
{
    //
    // Need different icon for extension...
    //
    nImage = QueryBitmapIndex();

    str.Empty();

    switch(nCol)
    {
    case COL_NAME:  // Computer Name
        GetDisplayText(str);
        break;

    case COL_LOCAL: // Local
        if (m_fIsExtension)
        {
            //
            // If we're an extension, display the type
            //
            AFX_MANAGE_STATE(AfxGetStaticModuleState());
            VERIFY(str.LoadString(IDS_SNAPIN_TYPE));
        }
        else
        {
            //
            // 
            str = IsLocalMachine()
                ? CIISObject::s_strYes
                : CIISObject::s_strNo;
        }
        break;

    case COL_TYPE: // Connection Type
        if (m_fIsExtension)
        {
            //
            // If we're an extension snap-in we should display
            // the snap-in description in this column
            //
            AFX_MANAGE_STATE(AfxGetStaticModuleState());
            VERIFY(str.LoadString(IDS_SNAPIN_DESC));
        }
        else
        {
            str = IS_NETBIOS_NAME(GetMachineName())
                ? CIISObject::s_strNetBIOS
                : CIISObject::s_strTCPIP;
        }
        break;

    case COL_STATUS:
        break;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }
}



int 
CIISMachine::Compare(
    IN int nCol, 
    IN CIISObject * pObject
    )
/*++

Routine Description:

    Compare against another CIISObject

Arguments:

    int nCol                : Compare on this column
    CIISObject * pObject    : Compare against this object

Routine Description:

    Comparison Return Value

--*/
{
    ASSERT(QueryGUID() == pObject->QueryGUID());
    if (QueryGUID() != pObject->QueryGUID())
    {
        //
        // Invalid comparison
        //
        return +1;
    }

    CString str1, str2;
    int n1, n2;

    CIISMachine * pMachine = (CIISMachine *)pObject;

    switch(nCol)
    {
    case COL_NAME: // Computer Name
        GetDisplayText(str1);
        pMachine->GetDisplayText(str2);
        return str1.CompareNoCase(str2);

    case COL_LOCAL: // Local
        n1 = IsLocalMachine();
        n2 = pMachine->IsLocalMachine();   
        return n1 - n2;

    case COL_TYPE: // Connection Type
        n1 = IS_NETBIOS_NAME(GetMachineName());
        n2 = IS_NETBIOS_NAME(pMachine->GetMachineName());
        return n1 - n2;

    case COL_STATUS:
        return 0;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }

    return -1;
}



void
CIISMachine::InitializeChildHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Build result view underneath

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    CIISInstance::InitializeHeaders(pHeader);
}



//
// Static Initialization
//
CString CIISInstance::s_strFormatState;
BOOL CIISInstance::s_fServerView = TRUE;
BOOL CIISInstance::s_fAppendState = TRUE;



//
// Instance Result View definition
//
int CIISInstance::rgnLabels[COL_TOTAL] =
{
    IDS_RESULT_SERVICE_DESCRIPTION,
    IDS_RESULT_SERVICE_STATE,
    IDS_RESULT_SERVICE_DOMAIN_NAME,
    IDS_RESULT_SERVICE_IP_ADDRESS,
    IDS_RESULT_SERVICE_TCP_PORT,
    IDS_RESULT_STATUS,
};
    


int CIISInstance::rgnWidths[COL_TOTAL] =
{
    180,
    70,
    120,
    105,
    40,
    200,
};



/* static */
void 
CIISInstance::InitializeStrings()
/*++

Routine Description:

    Static method to initialize the strings.    
                                                                       
Arguments:

    None

Return Value:

    None

--*/
{
    if (!IsInitialized())
    {
        TRACEEOLID("Initializing static strings");

        //
        // iisui.dll is an extension DLL, and this should 
        // load automatically, but doesn't -- why?
        //
        HINSTANCE hOld = AfxGetResourceHandle();
        AfxSetResourceHandle(GetModuleHandle(COMPROP_DLL_NAME));
        VERIFY(CIISInstance::s_strFormatState.LoadString(IDS_INSTANCE_STATE_FMT));
        AfxSetResourceHandle(hOld);
    }
}



/* static */
void
CIISInstance::InitializeHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    CIISObject::BuildResultView(pHeader, COL_TOTAL, rgnLabels, rgnWidths);
}



CIISInstance::CIISInstance(
    IN CServerInfo * pServerInfo
    )
/*++

Routine Description:

    Constructor for instance object without instance ID code.  In other
    words, this is a wrapper for a plain-old down-level CServerInfo
    object.
                                                                       
Arguments:

    CServerInfo * pServerInfo : Controlling server info

Return Value:

    N/A

--*/
    : CIISObject(cInstanceNode),
      m_fDownLevel(TRUE),
      m_fDeletable(FALSE),
      m_fClusterEnabled(FALSE),
      m_fLocalMachine(FALSE),
      m_hrError(S_OK),
      m_sPort(0),
      m_dwIPAddress(0L),
      m_strHostHeaderName(),
      m_strComment(),
      m_strMachine(),
      m_pServerInfo(pServerInfo)
{
    if (!IsInitialized())
    {
        CIISInstance::InitializeStrings();
    }

    //
    // Some service types do not support instances, but
    // still understand the instance codes
    //
    ASSERT(m_pServerInfo != NULL);
    m_dwID = 0L;
    m_strMachine = m_pServerInfo->QueryServerName();
    m_fLocalMachine = ::IsServerLocal(m_strMachine);
}



CIISInstance::CIISInstance(
    IN ISMINSTANCEINFO * pii,
    IN CServerInfo * pServerInfo
    )
/*++

Routine Description:

    Initialize IIS Instance from ISMINSTANCEINFO structure

Arguments:

    ISMINSTANCEINFO * pii     : Pointer to ISMINSTANCEINFO structure
    CServerInfo * pServerInfo : Server info structure

Return Value:

    N/A

--*/
    : CIISObject(cInstanceNode),
      m_fDownLevel(FALSE),
      m_fLocalMachine(),
      m_strMachine(),
      m_pServerInfo(pServerInfo)
{
    InitializeFromStruct(pii);

    if (!IsInitialized())
    {
        CIISInstance::InitializeStrings();
    }

    ASSERT(m_pServerInfo != NULL);
    m_strMachine = m_pServerInfo->QueryServerName();
    m_fLocalMachine = ::IsServerLocal(m_strMachine);
}



void
CIISInstance::InitializeFromStruct(
    IN ISMINSTANCEINFO * pii
    )
/*++

Routine Description:

    Initialize data from ISMINSTANCEINFO structure

Arguments:

    ISMINSTANCEINFO * pii     : Pointer to ISMINSTANCEINFO structure

Return Value:

    None.

--*/
{
    ASSERT(pii);

    m_dwID              = pii->dwID;
    m_strHostHeaderName = pii->szServerName;
    m_strComment        = pii->szComment;
    m_nState            = pii->nState;
    m_dwIPAddress       = pii->dwIPAddress;
    m_sPort             = pii->sPort;
    m_fDeletable        = pii->fDeletable;
    m_fClusterEnabled   = pii->fClusterEnabled;
    
    CError err(pii->dwError);
    m_hrError           = err;

    m_strRedirPath      = pii->szRedirPath;
    m_fChildOnlyRedir   = pii->fChildOnlyRedir;

    //
    // Home directory path should exist unless someone's been
    // messing up the metabase.
    //
    m_strPhysicalPath   = pii->szPath;

    m_strNodeName.Format(_T("%s%c%s%c%ld%c%s"),
        g_cszMachine,
        g_chSep,
        GetServerInfo()->GetMetabaseName(),
        g_chSep,
        m_dwID,
        g_chSep,
        g_cszRoot
        );

    TRACEEOLID(m_strNodeName);
}               



/* virtual */
BOOL 
CIISInstance::IsRunning() const
/*++

Routine Description:

    Check to see if this instance is currently running.

Arguments:

    None.

Return Value:

    TRUE if currently running

Notes:

    On a down-level (v3) version, this applies to the service, not
    the instance

--*/
{
    if (IsDownLevel())
    {
        ASSERT(m_pServerInfo != NULL);
        return m_pServerInfo->IsServiceRunning();
    }

    return m_nState == INetServiceRunning;
}



/* virtual */
BOOL 
CIISInstance::IsStopped() const
/*++

Routine Description:

    Check to see if this instance is currently stopped.

Arguments:

    None.

Return Value:

    TRUE if currently stopped

Notes:

    On a down-level (v3) version, this applies to the server, not
    the instance

--*/
{
    if (IsDownLevel())
    {
        ASSERT(m_pServerInfo != NULL);
        return m_pServerInfo->IsServiceStopped();
    }

    return m_nState == INetServiceStopped;
}



/* virtual */
BOOL 
CIISInstance::IsPaused() const
/*++

Routine Description:

    Check to see if this instance is currently paused.

Arguments:

    None.

Return Value:

    TRUE if currently paused

Notes:

    On a down-level (v3) version, this applies to the server, not
    the instance

--*/
{
    if (IsDownLevel())
    {
        ASSERT(m_pServerInfo != NULL);

        return m_pServerInfo->IsServicePaused();
    }

    return m_nState == INetServicePaused;
}



/* virtual */
int
CIISInstance::QueryState() const
/*++

Routine Description:

    Get the ISM state of the instance

Arguments:

    None.

Return Value:

    The ISM (INet*) state of the instance

Notes:

    On a down-level (v3) version, this applies to the server, not
    the instance

--*/
{
    if (IsDownLevel())
    {
        return m_pServerInfo->QueryServiceState();
    }

    return m_nState;
}



/* virtual */
HRESULT
CIISInstance::ChangeState(
    IN int nNewState
    )
/*++

Routine Description:

    Change the ISM state of the instance

Arguments:

    int nNewState

Return Value:

    Error Return Code

Notes:

    On a down-level (v3) version, this applies to the server, not
    the instance

--*/
{
    ASSERT(m_pServerInfo != NULL);

    int * pnCurrentState;
    DWORD dwID;

    if (IsClusterEnabled())
    {
        //
        // Not supported on cluster server
        //
        return ERROR_BAD_DEV_TYPE;
    }

    if (IsDownLevel())
    {
        dwID = 0;
        pnCurrentState = m_pServerInfo->GetServiceStatePtr();
    }
    else
    {   
        dwID = QueryID();
        pnCurrentState = &m_nState;
    }

    return m_pServerInfo->ChangeServiceState(nNewState, pnCurrentState, dwID);
}



/* virtual */
HRESULT
CIISInstance::Configure(
    IN CWnd * pParent       OPTIONAL
    )
/*++

Routine Description:

    Configure the instance object

Arguments:

    CWnd * pParent      : Optional parent window

Return Value:

    Error return code

--*/
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    ASSERT(m_pServerInfo != NULL);

    //
    // Set help file
    //
    theApp.SetHelpPath(m_pServerInfo);

    CError err(m_pServerInfo->ConfigureServer(pParent->m_hWnd, QueryID()));

    //
    // Restore help path
    //
    theApp.SetHelpPath();

    if (err.Succeeded())
    {
        RefreshData();
    }

    return err;
}



/* virtual */
HRESULT
CIISInstance::RefreshData()
/*++

Routine Description:

    Refresh internal data

Arguments:

    None

Return Value:

    Error return code.

--*/
{
    CError err;

    if (!IsDownLevel())
    {
        ISMINSTANCEINFO ii;
        err = m_pServerInfo->QueryInstanceInfo(
            WITHOUT_INHERITANCE, 
            &ii, 
            QueryID()
            );

        if (err.Succeeded())
        {
            InitializeFromStruct(&ii);        
        }
    }

    return err;
}



/* virtual */
HRESULT
CIISInstance::ConfigureMMC(
    IN LPPROPERTYSHEETCALLBACK lpProvider,
    IN LPARAM param,
    IN LONG_PTR handle
    )
/*++

Routine Description:

    Configure using MMC property sheets

Arguments:

    LPPROPERTYSHEETCALLBACK lpProvider  : Prop sheet provider
    LPARAM param                        : Prop parameter
    LONG_PTR handle                     : console handle

Return Value:

    Error return code

--*/
{
    ASSERT(m_pServerInfo != NULL);

    return m_pServerInfo->MMMCConfigureServer(
        lpProvider,
        param,
        handle,
        QueryID()
        );
}



/* virtual */
LPCTSTR 
CIISInstance::GetStateText() const
/*++

Routine Description:

    Return text representation of current state (running/paused/stopped).
                                                                       
Arguments:

    None

Return Value:

    Pointer to the string

--*/
{
    ASSERT(!CIISObject::s_strRunning.IsEmpty());

    return IsStopped()
        ? CIISObject::s_strStopped
        : IsPaused()
            ? CIISObject::s_strPaused
            : IsRunning()
                ? CIISObject::s_strRunning
                : CIISObject::s_strUnknown;
}



/* virtual */
LPCTSTR 
CIISInstance::GetDisplayText(
    OUT CString & strText
    ) const
/*++

Routine Description:

    Get the display text of this object
                                                                       
Arguments:

    CString & strText : String in which display text is to be returned

Return Value:

    Pointer to the string

--*/
{
    try
    {
        if (m_fDownLevel)
        {
            ASSERT(m_pServerInfo != NULL);

            if (CIISInstance::s_fServerView)
            {
                strText = m_pServerInfo->GetServiceName();
            }
            else
            {
                strText = m_pServerInfo->QueryServerName();
            }
        }
        else
        {
            //
            // GetDisplayText loads from resources
            //
            AFX_MANAGE_STATE(::AfxGetStaticModuleState() );

            CIPAddress ia(m_dwIPAddress);

            CInstanceProps::GetDisplayText(
                strText,
                GetComment(),
                GetHostHeaderName(),
                m_pServerInfo->GetServiceName(),
                ia,
                m_sPort,
                m_dwID
                );
        }

        //
        // Append state (paused, etc) if not running
        //
        if (CIISInstance::s_fAppendState && !IsRunning())
        {
            CString strState;
            strState.Format(CIISInstance::s_strFormatState, GetStateText());
            strText += strState;    
        }
    }
    catch(CMemoryException * e)
    {
        TRACEEOLID("!!!exception in GetDisplayText");
        e->ReportError();
        e->Delete();
    }

    return strText;
}



/* virtual */
HRESULT
CIISInstance::AddChildNode(
    IN OUT CIISChildNode *& pChild
    )
/*++

Routine Description:

    Add child node under current node

Arguments:

    CIISChildNode *& pChild     : Child node to be added

Return Value:

    Error return code.

--*/
{
    pChild = NULL;

    ISMCHILDINFO ii;
    CString strParent(g_cszRoot);
    CError err(GetServerInfo()->AddChild(
        &ii, 
        sizeof(ii), 
        QueryID(), 
        strParent
        ));

    if (err.Succeeded())
    {
        pChild = new CIISChildNode(&ii, this);
    }

    return err;
}



/* virtual */
BOOL 
CIISInstance::ChildrenExpanded() const
/*++

Routine Description:

    Determine if the child items of this node have already been expanded

Arguments:

    None

Return Value:

    TRUE if the items do not need expansion, FALSE if they do.

--*/
{
    ASSERT(m_pServerInfo != NULL);

    if (m_pServerInfo->SupportsChildren())
    {
        //
        // Note: timestamps ignored, because MMC will never send me another
        // expansion notification, so we can't refresh after a certain time.
        //
        return m_tmChildrenExpanded > 0L;
    }

    //
    // Can't drill down any lower than this.
    //
    return TRUE;
}



/* virtual */
HRESULT
CIISInstance::ExpandChildren(
    IN HSCOPEITEM pParent
    )
/*++

Routine Description:

    Mark the children as being expanded.

Arguments:

    HSCOPEITEM pParent      : Parent scope item

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pServerInfo != NULL);

    if (m_pServerInfo->SupportsChildren() )
    {
        //
        // Mark the last epxansion time as being now.
        //
        time(&m_tmChildrenExpanded);

        return S_OK;
    }

    //
    // Can't drill down any lower than this.
    //
    return CError::HResult(ERROR_INVALID_FUNCTION);
}



/* virtual */
int 
CIISInstance::QueryBitmapIndex() const
/*++

Routine Description:

    Get the bitmap index of the object.  This varies with the state
    of the view
                                                                       
Arguments:

    None

Return Value:

    Bitmap index

--*/
{
    ASSERT(m_pServerInfo != NULL);

/*
    return OK()
        ?  m_pServerInfo->QueryBitmapIndex()
        :  BMP_ERROR;
*/

    return m_pServerInfo->QueryBitmapIndex();
}



/* virtual */ 
HRESULT 
CIISInstance::AddMenuItems(
    IN LPCONTEXTMENUCALLBACK pContextMenuCallback
    )
/*++

Routine Description:

    Add context menu items for the instance

Arguments:

    LPCONTEXTMENUCALLBACK pContextMenuCallback : Context menu callback

Return Value:

    HRESULT

--*/
{
    CIISObject::AddMenuItems(pContextMenuCallback);

    if (SupportsInstances() && CanAddInstance(GetMachineName()))
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_NEW_INSTANCE);    
    }
    
    if (SupportsChildren())
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_NEW_VROOT);
    }

    if (SupportsSecurityWizard())
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_TASK_SECURITY_WIZARD);
    }

    return S_OK;
}



/* virtual */
HRESULT
CIISInstance::AddNextTaskpadItem(
    OUT MMC_TASK * pTask
    )
/*++

Routine Description:

    Add next taskpad item

Arguments:

    MMC_TASK * pTask    : Task structure to fill in

Return Value:

    HRESULT

--*/
{
    //
    // CODEWORK:  Ideally I would call the base class here, but that's
    //            a pain with enumeration
    //
    static int iIndex = 0;

    //
    // Fall through on the switch until an applicable command is found
    //
    UINT nID;
    switch(iIndex)
    {
    case 0:
        if (SupportsInstances() && CanAddInstance(GetMachineName()))
        {
            nID = IDM_NEW_INSTANCE;
            break;
        }

        ++iIndex;

        //
        // Fall through
        //
        
    case 1:    
        if (SupportsChildren())
        {
            nID = IDM_NEW_VROOT;
            break;
        }
        
        ++iIndex;

        //
        // Fall through
        //

    case 2:
        if (SupportsSecurityWizard())
        {
            nID = IDM_TASK_SECURITY_WIZARD;
            break;
        }

        ++iIndex;

        //
        // Fall through
        //

    default:
        //
        // All done
        //
        iIndex = 0;
        return S_FALSE;
    }


    HRESULT hr = AddTaskpadItemByCommand(
        nID, 
        pTask, 
        GetServerInfo()->QueryInstanceHandle()
        );

    if (SUCCEEDED(hr))
    {
        ++iIndex;
    }

    return hr;
}



HRESULT
CIISInstance::Delete()
/*++

Routine Description:

    Handle deletion of the current item

Arguments:

    None

Return Value:

    Error return code

--*/
{
    ASSERT(m_pServerInfo != NULL);

    return m_pServerInfo->DeleteInstance(m_dwID);
}



HRESULT
CIISInstance::SecurityWizard()
/*++

Routine Description:

    Launch the security wizard on this instance

Arguments:

    None

Return Value:

    Error return code

--*/
{
    ASSERT(m_pServerInfo != NULL);
    ASSERT(m_pServerInfo->SupportsSecurityWizard());

    CString strPhysicalPath;
    BuildPhysicalPath(strPhysicalPath);

    return m_pServerInfo->ISMSecurityWizard(m_dwID,  _T(""), g_cszRoot);
}



int 
CIISInstance::Compare(
    IN int nCol, 
    IN CIISObject * pObject
    )
/*++

Routine Description:

    Compare against another CIISObject

Arguments:

    int nCol                : Compare on this column
    CIISObject * pObject    : Compare against this object

Routine Description:

    Comparison Return Value

--*/
{
    ASSERT(QueryGUID() == pObject->QueryGUID());

    if (QueryGUID() != pObject->QueryGUID())
    {
        //
        // Invalid comparison
        //
        return +1;
    }

    CString str1, str2;
    CIPAddress ia1, ia2;
    DWORD dw1, dw2;
    int n1, n2;
    CIISInstance * pInstance = (CIISInstance *)pObject;

    switch(nCol)
    {
    case COL_DESCRIPTION:   // Description
        GetDisplayText(str1);
        pInstance->GetDisplayText(str2);
        return str1.CompareNoCase(str2);

    case COL_STATE:         // State 
        str1 = GetStateText();
        str2 = pInstance->GetStateText();
        return str1.CompareNoCase(str2);

    case COL_DOMAIN_NAME:   // Domain name
        str1 = GetHostHeaderName();
        str2 = pInstance->GetHostHeaderName();
        return str1.CompareNoCase(str2);

    case COL_IP_ADDRESS:    // IP Address
        ia1 = GetIPAddress();
        ia2 = pInstance->GetIPAddress();
        return ia1.CompareItem(ia2);

    case COL_TCP_PORT:      // Port
        n1 = GetPort();
        n2 = pInstance->GetPort();
        return n1 - n2;

    case COL_STATUS:
        dw1 = m_hrError;
        dw2 = pInstance->QueryError();
        return dw1 > dw2 ? +1 : dw1==dw2 ? 0 : -1;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
        return 0;
    }

    return -1;
}



/* virtual */
void 
CIISInstance::GetResultDisplayInfo(
    IN  int nCol,
    OUT CString & str,
    OUT int & nImage
    ) const
/*++

Routine Description:

    Get result data item display information.

Arguments:

    int nCol            : Column number
    CString &  str      : String data
    int & nImage        : Image number

Return Value:

    None    

--*/
{
    nImage = QueryBitmapIndex();
    CIPAddress ia;

    str.Empty();
    switch(nCol)
    {
    case COL_DESCRIPTION:   // Description
        GetDisplayText(str);
        break;

    case COL_STATE:         // State 
        if (OK())
        {
            str = GetStateText();
        }
        break;

    case COL_DOMAIN_NAME:   // Domain name
        if (OK())
        {
            str = GetHostHeaderName();
        }
        break;

    case COL_IP_ADDRESS:    // IP Address
        if (!IsDownLevel() && OK())
        {
            ia = GetIPAddress();
            if (ia.IsZeroValue())
            {
                str = CIISObject::s_strDefaultIP;
            }
            else
            {
                ia.QueryIPAddress(str);
            }
        }
        break;

    case COL_TCP_PORT:      // Port
        if (OK())
        {
            if (GetPort())
            {
                str.Format(_T("%u"), GetPort());
            }
            else
            {
                str.Empty();
            }
        }
        break;

    case COL_STATUS:
        if (!OK())
        {
            CError::TextFromHRESULT(QueryErrorCode(), str);
        }
        break;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }
}



/* virtual */ 
LPCTSTR 
CIISInstance::GetComment() const
/*++

Routine Description:

    Get the comment string

Arguments:

    None

Return Value:

    Pointer to the comment string

--*/
{
    if (IsDownLevel())
    {
        ASSERT(m_pServerInfo != NULL);
        return m_pServerInfo->GetServerComment();
    }

    return m_strComment;
}



HRESULT
CIISInstance::ShellBrowsePath(
    IN LPCTSTR lpszPath
    )
/*++

Routine Description:

    Generate an URL from the instance and path information given, and
    attempt to resolve that URL

Arguments:

    IN LPCTSTR lpszPath             : Path

Return Value:

    Error return code

--*/
{
    CString strDir;
    CString strOwner;

    ASSERT(IsBrowsable());

    ///////////////////////////////////////////////////////////////////////////
    //
    // Try to build an URL.  Use in order of priority:
    //
    //     Domain name:port/root
    //     ip address:port/root
    //     computer name:port/root
    //
    if (HasHostHeaderName())
    {
        strOwner = GetHostHeaderName();
    }
    else if (HasIPAddress())
    {
        CIPAddress ia(GetIPAddress());
        ia.QueryIPAddress(strOwner);
    }
    else
    {
        //
        // Use the computer name (w/o backslashes)
        //
		if (IsLocalMachine())
		{
			strOwner = _T("localhost");
		}
		else
		{
			LPCTSTR lpOwner = GetMachineName();
			strOwner = PURE_COMPUTER_NAME(lpOwner);
		}
    }

    int nPort = GetPort();

    LPCTSTR lpProtocol = GetServerInfo()->GetProtocol();

    if (lpProtocol == NULL)
    {
        return CError::HResult(ERROR_INVALID_PARAMETER);
    }

    //
    // "Root" is a metabase concept which has no place in the 
    // path.
    //
    TRACEEOLID(lpszPath);
    lpszPath += lstrlen(g_cszRoot);
    strDir.Format(
        _T("%s://%s:%u%s"), 
        lpProtocol, 
        (LPCTSTR)strOwner, 
        nPort, 
        lpszPath
        );

    TRACEEOLID("Attempting to open URL: " << strDir);

    CError err;
    {
        //
        // AFX_MANAGE_STATE required for wait cursor
        //
        AFX_MANAGE_STATE(AfxGetStaticModuleState());    
        CWaitCursor wait;

        if (::ShellExecute(
            NULL, 
            _T("open"), 
            strDir, 
            NULL,
             _T(""), 
             SW_SHOW
             ) <= (HINSTANCE)32)
        {
            err.GetLastWinError();
        }
    }

    return err;
}



void 
CIISInstance::InitializeChildHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    CIISChildNode::InitializeHeaders(pHeader);
}



int CIISChildNode::rgnLabels[COL_TOTAL] =
{
    IDS_RESULT_VDIR_ALIAS,
    IDS_RESULT_PATH,
    IDS_RESULT_STATUS,
};



int CIISChildNode::rgnWidths[COL_TOTAL] =
{
    120,
    300,
    200,
};



/* static */
void
CIISChildNode::InitializeHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    CIISObject::BuildResultView(pHeader, COL_TOTAL, rgnLabels, rgnWidths);
}



void
CIISChildNode::InitializeChildHeaders(
    LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers for a child item

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    InitializeHeaders(pHeader);
}




CIISChildNode::CIISChildNode(
    IN ISMCHILDINFO * pii,
    IN CIISInstance * pOwner
    )
/*++

Routine Description:

    Initialize child node object from ISMCHILDINFO structure information

Arguments:

    ISMCHILDINFO * pii     : Pointer to ISMCHILDINFO structure
    CIISInstance * pOwner  : Owner instance

Return Value:

    N/A

--*/
    : CIISObject(cChildNode),
      m_pOwner(pOwner)
{
    InitializeFromStruct(pii);
}



void 
CIISChildNode::InitializeFromStruct(
    IN ISMCHILDINFO * pii
    )
/*++

Routine Description:

    Initialize data from ISMCHILDINFO structure

Arguments:

    ISMCHILDINFO * pii     : Pointer to ISMCHILDINFO structure

Return Value:

    None.

--*/

{
    ASSERT(pii);
    ASSERT(pii->szAlias);
    ASSERT(pii->szPath);

    try
    {
        m_fEnabledApplication = pii->fEnabledApplication;
        CError err(pii->dwError);
        m_hrError = err;
        m_strRedirPath = pii->szRedirPath;
        m_fChildOnlyRedir = pii->fChildOnlyRedir;

        //
        // Initialize base objects
        //
        m_strNodeName = pii->szAlias;
        m_strPhysicalPath = pii->szPath;
    }
    catch(CMemoryException * e)
    {
        e->ReportError();
        e->Delete();
    }
}



/* virtual */
int 
CIISChildNode::QueryBitmapIndex() const
/*++

Routine Description:

    Get the bitmap index of the object.  This varies with the state
    of the view
                                                                       
Arguments:

    None

Return Value:

    Bitmap index

--*/
{
    if (!OK())
    {
        return BMP_ERROR;
    }

    if (IsEnabledApplication())
    {
        return BMP_APPLICATION;
    }

    if (IsVirtualDirectory())
    {
        ASSERT(m_pOwner != NULL);

        return m_pOwner->GetServerInfo()->QueryChildBitmapIndex();
    }

    return BMP_DIRECTORY;
}



/* virtual */
LPCTSTR 
CIISChildNode::GetDisplayText(
    OUT CString & strText
    ) const
/*++

Routine Description:

    Get the display text of this object
                                                                       
Arguments:

    CString & strText : String in which display text is to be returned

Return Value:

    Pointer to the string

--*/
{
    strText = m_strNodeName;

    return strText;
}



/* virtual */ 
HRESULT 
CIISChildNode::AddMenuItems(
    IN LPCONTEXTMENUCALLBACK pContextMenuCallback
    )
/*++

Routine Description:

    Add context menu items relevant to this node

Arguments:

    LPCONTEXTMENUCALLBACK pContextMenuCallback

Return Value:

    HRESULT

--*/
{
    CIISObject::AddMenuItems(pContextMenuCallback);

    AddMenuItemByCommand(pContextMenuCallback, IDM_NEW_VROOT);

    if (SupportsSecurityWizard())
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_TASK_SECURITY_WIZARD);
    }

    return S_OK;
}



/* virtual */
HRESULT
CIISChildNode::AddNextTaskpadItem(
    OUT MMC_TASK * pTask
    )
/*++

Routine Description:

    Add next taskpad item

Arguments:

    MMC_TASK * pTask    : Task structure to fill in

Return Value:

    HRESULT

--*/
{
    //
    // CODEWORK:  Ideally I would call the base class here, but that's
    //            a pain with enumeration
    //
    static int iIndex = 0;

    //
    // Fall through on the switch until an applicable command is found
    //
    UINT nID;
    switch(iIndex)
    {
    case 0:
        nID = IDM_NEW_VROOT;
        break;

    case 1:
        if (SupportsSecurityWizard())
        {
            nID = IDM_TASK_SECURITY_WIZARD;
            break;
        }

        ++iIndex;

        //
        // Fall through
        //
                
    default:
        //
        // All done
        //
        iIndex = 0;
        return S_FALSE;
    }

    HRESULT hr = AddTaskpadItemByCommand(
        nID, 
        pTask,
        GetServerInfo()->QueryInstanceHandle()
        );

    if (SUCCEEDED(hr))
    {
        ++iIndex;
    }

    return hr;
}



int 
CIISChildNode::Compare(
    IN int nCol, 
    IN CIISObject * pObject
    )
/*++

Routine Description:

    Compare against another CIISObject

Arguments:

    int nCol                : Compare on this column
    CIISObject * pObject    : Compare against this object

Routine Description:

    Comparison Return Value

--*/
{
    ASSERT(QueryGUID() == pObject->QueryGUID());
    if (QueryGUID() != pObject->QueryGUID())
    {
        //
        // Invalid comparison
        //
        return +1;
    }

    DWORD dw1, dw2;

    CIISChildNode * pChild = (CIISChildNode *)pObject;

    switch(nCol)
    {
    case COL_ALIAS:         // Alias
        return m_strNodeName.CompareNoCase(pChild->m_strNodeName);

    case COL_PATH:          // Path
        dw1 = IsRedirected();
        dw2 = pChild->IsRedirected();
        if (dw1 != dw2)
        {
            return dw1 > dw2 ? +1 : -1;
        }

        return GetPhysicalPath().CompareNoCase(pChild->GetPhysicalPath());

    case COL_STATUS:
        dw1 = m_hrError;
        dw2 = pChild->QueryError();
        return dw1 > dw2 ? +1 : dw1==dw2 ? 0 : -1;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }

    return -1;
}



/* virtual */
void 
CIISChildNode::GetResultDisplayInfo(
    IN  int nCol,
    OUT CString & str,
    OUT int & nImage
    ) const
/*++

Routine Description:

    Get result data item display information.

Arguments:

    int nCol            : Column number
    CString &  str      : String data
    int & nImage        : Image number

Return Value:

    None    

--*/
{
    nImage = QueryBitmapIndex();

    str.Empty();
    switch(nCol)
    {
    case COL_ALIAS:         // Alias
        str = m_strNodeName;
        break;

    case COL_PATH:          // Path
        if (IsRedirected())
        {
            str.Format(s_strRedirect, m_strRedirPath);
        }
        else
        {
            str = m_strPhysicalPath;
        }
        break;

    case COL_STATUS:
        if (!OK())
        {
            CError::TextFromHRESULT(QueryErrorCode(), str);
        }
        break;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }
}



/* virtual */
HRESULT
CIISChildNode::Configure(
    IN CWnd * pParent
    )
/*++

Routine Description:

    Configure the child node object
                                                                       
Arguments:

    CWnd * pParent : Parent window handle

Return Value:

    Error return code

--*/

{
    ASSERT(m_pOwner != NULL);

    //
    // Ok, build a metabase path on the fly now by getting
    // the nodes of the parents up until the owning instance.
    //
    CString strParent;
    BuildParentPath(strParent, FALSE);

    //
    // Set help file
    //
    theApp.SetHelpPath(GetServerInfo());

    CError err(GetServerInfo()->ConfigureChild(
        pParent->m_hWnd, 
        FILE_ATTRIBUTE_VIRTUAL_DIRECTORY,
        m_pOwner->QueryID(), 
        strParent,
        m_strNodeName
        ));

    //
    // Set help file
    //
    theApp.SetHelpPath();

    if (err.Succeeded())
    {
        RefreshData();
    }

    return err;
}



/* virtual */
HRESULT
CIISChildNode::RefreshData()
/*++

Routine Description:

    Refresh internal data

Arguments:

    None

Return Value:

    Error return code.

--*/
{
    ISMCHILDINFO ii;

    CString strParent;
    BuildParentPath(strParent, FALSE);

    CError err(GetServerInfo()->QueryChildInfo(
        WITH_INHERITANCE,
        &ii, 
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        ));

    if (err.Succeeded())
    {
        InitializeFromStruct(&ii);        
    }

    return err;
}



/* virtual */
HRESULT
CIISChildNode::ConfigureMMC(
    IN LPPROPERTYSHEETCALLBACK lpProvider,
    IN LPARAM param,
    IN LONG_PTR handle
    )
/*++

Routine Description:

    Configure using MMC property sheets

Arguments:

    LPPROPERTYSHEETCALLBACK lpProvider  : Prop sheet provider
    LPARAM param                        : Prop parameter
    LONG_PTR handle                      : console handle

Return Value:

    Error return code

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strParent;
    BuildParentPath(strParent, FALSE);

    return GetServerInfo()->MMCConfigureChild(lpProvider, 
        param,
        handle,
        FILE_ATTRIBUTE_VIRTUAL_DIRECTORY,
        m_pOwner->QueryID(), 
        strParent,
        m_strNodeName
        );
}



/* virtual */
HRESULT
CIISChildNode::AddChildNode(
    CIISChildNode *& pChild
    )
/*++

Routine Description:

    Add child node under current node

Arguments:

    CIISChildNode *& pChild     : Child node to be added

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);
    ISMCHILDINFO ii;

    CString strPath;
    BuildFullPath(strPath, FALSE);

    pChild = NULL;
    CError err(GetServerInfo()->AddChild(
        &ii, 
        sizeof(ii), 
        m_pOwner->QueryID(), 
        strPath
        ));

    if (err.Succeeded())
    {
        pChild = new CIISChildNode(&ii, m_pOwner);
    }

    return err;
}



/* virtual */
HRESULT
CIISChildNode::Rename(
    IN LPCTSTR lpszNewName
    )
/*++

Routine Description:

    Rename the current node

Arguments:

    LPCTSTR lpszNewName        : New name

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strPath;
    BuildParentPath(strPath, FALSE);

    CError err(GetServerInfo()->RenameChild(
        m_pOwner->QueryID(), 
        strPath, 
        m_strNodeName, 
        lpszNewName
        ));

    if (err.Succeeded())
    {
        m_strNodeName = lpszNewName;
    }

    return err;
}



HRESULT
CIISChildNode::Delete()
/*++

Routine Description:

    Delete the current node

Arguments:

    None

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strParent;
    BuildParentPath(strParent, FALSE);

    return GetServerInfo()->DeleteChild(
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        );
}



HRESULT
CIISChildNode::SecurityWizard()
/*++

Routine Description:

    Launch the security wizard on this child node

Arguments:

    None

Return Value:

    Error return code

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strParent;
    BuildParentPath(strParent, FALSE);

    return GetServerInfo()->ISMSecurityWizard(
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        );
}



int CIISFileNode::rgnLabels[COL_TOTAL] =
{
    IDS_RESULT_VDIR_ALIAS,
    IDS_RESULT_PATH,
    IDS_RESULT_STATUS,
};
    


int CIISFileNode::rgnWidths[COL_TOTAL] =
{
    120,
    300,
    200,
};



/* static */
void
CIISFileNode::InitializeHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    CIISObject::BuildResultView(pHeader, COL_TOTAL, rgnLabels, rgnWidths);
}



void
CIISFileNode::InitializeChildHeaders(
    IN LPHEADERCTRL pHeader
    )
/*++

Routine Description:

    Initialize the result headers

Arguments:

    LPHEADERCTRL pHeader : Header control

Return Value:

    None

--*/
{
    InitializeHeaders(pHeader);
}



CIISFileNode::CIISFileNode(
    IN LPCTSTR lpszAlias,  
    IN DWORD dwAttributes,
    IN CIISInstance * pOwner,
    IN LPCTSTR lpszRedirect,
    IN BOOL fDir               OPTIONAL
    )
/*++

Routine Description:

    Constructor for file object
                                                                       
Arguments:

    LPCTSTR lpszPath     : Path name
    DWORD dwAttributes    : Attributes
    CIISInstance * pOwner : Owner instance
    BOOL fDir             : TRUE for directory, FALSE for file

Return Value:

    N/A

--*/
    : CIISObject(cFileNode, lpszAlias),
      m_dwAttributes(dwAttributes),
      m_pOwner(pOwner),
      m_fEnabledApplication(FALSE),
      m_fDir(fDir)
{
    m_strRedirPath = lpszRedirect;
}



/* virtual */
int 
CIISFileNode::QueryBitmapIndex() const
/*++

Routine Description:

    Get the bitmap index of the object.  This varies with the state
    of the view
                                                                       
Arguments:

    None

Return Value:

    Bitmap index

--*/
{
    if (IsEnabledApplication())
    {
        return BMP_APPLICATION;
    }

    return IsDirectory() ? BMP_DIRECTORY : BMP_FILE;
}



/* virtual */
LPCTSTR 
CIISFileNode::GetDisplayText(
    OUT CString & strText
    ) const
/*++

Routine Description:

    Get the display text of this object
                                                                       
Arguments:

    CString & strText : String in which display text is to be returned

Return Value:

    Pointer to the string

--*/
{
    strText = m_strNodeName;

    return strText;
}



int 
CIISFileNode::Compare(
    IN int nCol, 
    IN CIISObject * pObject
    )
/*++

Routine Description:

    Compare against another CIISObject

Arguments:

    int nCol                : Compare on this column
    CIISObject * pObject    : Compare against this object

Routine Description:

    Comparison Return Value

--*/
{
    ASSERT(QueryGUID() == pObject->QueryGUID());
    if (QueryGUID() != pObject->QueryGUID())
    {
        //
        // Invalid comparison
        //
        return +1;
    }

    return m_strNodeName.CompareNoCase(pObject->GetNodeName());
}



/* virtual */
void 
CIISFileNode::GetResultDisplayInfo(
    IN  int nCol,
    OUT CString & str,
    OUT int & nImage
    ) const
/*++

Routine Description:

    Get result data item display information.

Arguments:

    int nCol            : Column number
    CString &  str      : String data
    int & nImage        : Image number

Return Value:

    None    

--*/
{
    nImage = QueryBitmapIndex();
    str.Empty();

    switch(nCol)
    {
    case COL_ALIAS:
        str = m_strNodeName;
        break;

    case COL_PATH:          // Path
        if (IsRedirected())
        {
            str.Format(s_strRedirect, m_strRedirPath);
        }
        else
        {
            str.Empty();
        }
        break;

    case COL_STATUS:
        if (!OK())
        {
            CError::TextFromHRESULT(QueryErrorCode(), str);
        }
        break;

    default:
        //
        // No other columns current supported
        //
        ASSERT(FALSE);
    }
}



/* virtual */
CIISObject *
CIISFileNode::GetParentObject() const
/*++

Routine Description:

    Get the parent object (in the scope view) of this object.

Arguments:

    None

Return Value:

    Pointer to the parent object, or NULL if not found

--*/
{
    if (!IsDir())
    {
        //
        // File nodes exist on the result side only, and the scope 
        // item handle they have points to their _parent_ not to 
        // themselves.
        //
        SCOPEDATAITEM sdi;
        sdi.mask = SDI_PARAM;
        sdi.ID = GetScopeHandle(); 
        HRESULT hr = GetScopeView()->GetItem(&sdi);

        return (CIISObject *)sdi.lParam;
    }

    //
    // Directory nodes work like anyone else
    //
    return CIISObject::GetParentObject();
}



/* virtual */
HRESULT
CIISFileNode::Configure(
    IN CWnd * pParent
    )
/*++

Routine Description:

    Configure using private property sheet provider

Arguments:

    CWnd * pParent      : Parent window

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strParent;
    BuildParentPath(strParent, FALSE);

    //
    // Set help file
    //
    theApp.SetHelpPath(GetServerInfo());

    CError err(GetServerInfo()->ConfigureChild(
        pParent->m_hWnd, 
        m_dwAttributes,
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        ));

    //
    // Restore help file
    //
    theApp.SetHelpPath();

    if (err.Succeeded())
    {
        RefreshData();
    }

    return err;
}



/* virtual */
HRESULT
CIISFileNode::Rename(
    IN LPCTSTR lpszNewName
    )
/*++

Routine Description:

    Rename the current node

Arguments:

    LPCTSTR lpszNewName        : New name

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);
	ASSERT(lpszNewName != NULL);

	// We need to check if this new name is OK
	LPTSTR p = (LPTSTR)lpszNewName;
	if (*p == 0)
	{
		AFX_MANAGE_STATE(::AfxGetStaticModuleState() );
		::AfxMessageBox(IDS_ERR_EMPTY_FILENAME, MB_ICONSTOP);
		return ERROR_BAD_PATHNAME;
	}

	while (*p != 0)
	{
		UINT type = PathGetCharType(*p);
		if (type & (GCT_LFNCHAR | GCT_SHORTCHAR))
		{
			p++;
			continue;
		}
		else
		{
			AFX_MANAGE_STATE(::AfxGetStaticModuleState() );
			::AfxMessageBox(IDS_ERR_INVALID_CHAR, MB_ICONSTOP);
			return ERROR_BAD_PATHNAME;
		}
	}

    CString strPath, strPhysicalPath, strDir, strNewName;
    BuildPhysicalPath(strPhysicalPath);
    BuildParentPath(strPath, FALSE);

    //
    // First make sure there's no conflicting name in the metabase
    //
    ISMCHILDINFO ii;
    ii.dwSize = sizeof(ii);

    CError err(GetServerInfo()->QueryChildInfo(
        WITHOUT_INHERITANCE,
        &ii,
        m_pOwner->QueryID(), 
        strPath, 
        lpszNewName
        ));

    if (err.Succeeded())
    {
        //
        // That won't do -- the name already exists in the metabase
        // We handle the UI for this message
        //
        AFX_MANAGE_STATE(::AfxGetStaticModuleState() );
        ::AfxMessageBox(IDS_ERR_DUP_METABASE);

        return ERROR_ALREADY_EXISTS;
    }

    if (IsLocalMachine() || ::IsUNCName(strPhysicalPath))
    {
        //
        // Local directory, or already a unc path
        //
        strDir = strPhysicalPath;
    }
    else
    {
        ::MakeUNCPath(strDir, GetMachineName(), strPhysicalPath);
    }

    //
    // Have to make a fully qualified destination name
    //
    strNewName = strDir;
    int iPos = strNewName.ReverseFind(_T('\\'));

    if (iPos <= 0)
    {
        //
        // Bogus file name path!
        //
        ASSERT(FALSE);
        return ERROR_INVALID_PARAMETER;
    }

    strNewName.ReleaseBuffer(iPos + 1);
    strNewName += lpszNewName;

    TRACEEOLID("Attempting to rename file/directory: " << strDir);
    TRACEEOLID("To " << strNewName);

    //
    // Convert to double-null terminated list of null terminated
    // strings
    //
    TCHAR szPath[MAX_PATH + 1];
    TCHAR szDest[MAX_PATH + 1];
    ZeroMemory(szPath, sizeof(szPath));
    ZeroMemory(szDest, sizeof(szDest));
    _tcscpy(szPath, strDir);
    _tcscpy(szDest, strNewName);

    CWnd * pWnd = AfxGetMainWnd();

    //
    // Attempt to delete using shell APIs
    //
    SHFILEOPSTRUCT sos;
    ZeroMemory(&sos, sizeof(sos));
    sos.hwnd = pWnd ? pWnd->m_hWnd : NULL;
    sos.wFunc = FO_RENAME;
    sos.pFrom = szPath;
    sos.pTo = szDest;
    sos.fFlags = FOF_ALLOWUNDO;

    int iReturn = SHFileOperation(&sos);

    if (sos.fAnyOperationsAborted)
    {
        err = ERROR_CANCELLED;
    }

    err = iReturn;

    if (!iReturn && !sos.fAnyOperationsAborted)
    {
        //
        // Successful rename -- now handle the metabase
        // rename as well.
        //
        err = GetServerInfo()->RenameChild(
            m_pOwner->QueryID(), 
            strPath, 
            m_strNodeName, 
            lpszNewName
            );

        if (err.Win32Error() == ERROR_PATH_NOT_FOUND
         || err.Win32Error() == ERROR_FILE_NOT_FOUND
           )
        {
            //
            // Harmless
            //
            err.Reset();
        }

        if (err.Succeeded())
        {
            m_strNodeName = lpszNewName;
        }
    }

    return err;
}



HRESULT
CIISFileNode::Delete()
/*++

Routine Description:

    Delete the current node

Arguments:

    None

Return Value:

    Error return code.

--*/
{
    CString strPhysicalPath, strDir;
    BuildPhysicalPath(strPhysicalPath);

    if (IsLocalMachine() || ::IsUNCName(strPhysicalPath))
    {
        //
        // Local directory, or already a unc path
        //
        strDir = strPhysicalPath;
    }
    else
    {
        ::MakeUNCPath(strDir, GetMachineName(), strPhysicalPath);
    }

    TRACEEOLID("Attempting to remove file/directory: " << strDir);

    //
    // Convert to double-null terminated list of null terminated
    // strings
    //
    TCHAR szPath[MAX_PATH + 1];
    ZeroMemory(szPath, sizeof(szPath));
    _tcscpy(szPath, strDir);

    CWnd * pWnd = AfxGetMainWnd();

    //
    // Attempt to delete using shell APIs
    //
    SHFILEOPSTRUCT sos;
    ZeroMemory(&sos, sizeof(sos));
    sos.hwnd = pWnd ? pWnd->m_hWnd : NULL;
    sos.wFunc = FO_DELETE;
    sos.pFrom = szPath;
    sos.fFlags = FOF_ALLOWUNDO;

    CError err;
    // Use assignment to avoid conversion and wrong constructor call
    err = ::SHFileOperation(&sos);

    if (sos.fAnyOperationsAborted)
    {
        err = ERROR_CANCELLED;
    }

    if (err.Succeeded())
    {
        //
        // Successful deletion -- now delete metabase properties
        ASSERT(m_pOwner != NULL);

        CString strParent;
        BuildParentPath(strParent, FALSE);

        err = GetServerInfo()->DeleteChild(
            m_pOwner->QueryID(), 
            strParent, 
            m_strNodeName
            );

        if (err.Win32Error() == ERROR_PATH_NOT_FOUND
         || err.Win32Error() == ERROR_FILE_NOT_FOUND
           )
        {
            //
            // Harmless
            //
            err.Reset();
        }
    }

    return err;
}




HRESULT
CIISFileNode::FetchMetaInformation(
    IN  CString & strParent,
    OUT BOOL * pfVirtualDirectory           OPTIONAL
    )
/*++

Routine Description:
    
    Fetch metabase information on this item.

Arguments:

    CString & strParent         : Parent path
    BOOL * pfVirtualDirectory   : Directory

Return Value:

    Error return code.

--*/
{
    ISMCHILDINFO ii;

    CError err(GetServerInfo()->QueryChildInfo(
        WITH_INHERITANCE,
        &ii, 
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        ));

    if (err.Succeeded())
    {
        //
        // Exists in the metabase
        //
        m_fEnabledApplication = ii.fEnabledApplication;
        m_strRedirPath = ii.szRedirPath;
        m_fChildOnlyRedir = ii.fChildOnlyRedir;

        if (pfVirtualDirectory)
        {
            *pfVirtualDirectory = !ii.fInheritedPath;
        }
    }
    else
    {
        //
        // No entry for this in the metabase
        //
        m_fEnabledApplication = FALSE; 
        m_strRedirPath.Empty();
    }

    return S_OK;
}



/* virtual */
HRESULT
CIISFileNode::RefreshData()
/*++

Routine Description:

    Refresh internal data

Arguments:

    None

Return Value:

    Error return code.

--*/
{
    CString strParent;
    BuildParentPath(strParent, FALSE);

    return FetchMetaInformation(strParent);
}



/* virtual */
HRESULT
CIISFileNode::ConfigureMMC(
    IN LPPROPERTYSHEETCALLBACK lpProvider,
    IN LPARAM param,
    IN LONG_PTR handle
    )
/*++

Routine Description:

    Configure using MMC property sheet provider

Arguments:

    LPPROPERTYSHEETCALLBACK lpProvider      : Property sheet provider
    LPARAM param                            : Parameter
    LONG_PTR handle                          : Handle

Return Value:

    Error return code

--*/
{
    ASSERT(m_pOwner != NULL);

    CString strParent;
    BuildParentPath(strParent, FALSE);

    return GetServerInfo()->MMCConfigureChild(
        lpProvider, 
        param,
        handle,
        m_dwAttributes,
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        );
}



/* virtual */ 
HRESULT 
CIISFileNode::AddMenuItems(
    IN LPCONTEXTMENUCALLBACK pContextMenuCallback
    )
/*++

Routine Description:

    Add context menu items relevant to this node

Arguments:

    LPCONTEXTMENUCALLBACK pContextMenuCallback

Return Value:

    HRESULT

--*/
{
    CIISObject::AddMenuItems(pContextMenuCallback);

    if (IsDir())
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_NEW_VROOT);
    }

    if (SupportsSecurityWizard())
    {
        AddMenuItemByCommand(pContextMenuCallback, IDM_TASK_SECURITY_WIZARD);
    }

    return S_OK;
}



/* virtual */
HRESULT
CIISFileNode::AddNextTaskpadItem(
    OUT MMC_TASK * pTask
    )
/*++

Routine Description:

    Add next taskpad item

Arguments:

    MMC_TASK * pTask    : Task structure to fill in

Return Value:

    HRESULT

--*/
{
    //
    // CODEWORK:  Ideally I would call be the base class here, but that's
    //            a pain with enumeration
    //
    static int iIndex = 0;

    //
    // Fall through on the switch until an applicable command is found
    //
    UINT nID;

    switch(iIndex)
    {
    case 0:
        if (IsDir())
        {
            nID = IDM_NEW_VROOT;
            break;
        }
                
    default:
        //
        // All done
        //
        iIndex = 0;
        return S_FALSE;
    }

    HRESULT hr = AddTaskpadItemByCommand(
        nID, 
        pTask,
        GetServerInfo()->QueryInstanceHandle()
        );

    if (SUCCEEDED(hr))
    {
        ++iIndex;
    }

    return hr;
}



/* virtual */
HRESULT
CIISFileNode::AddChildNode(
    IN CIISChildNode *& pChild
    )
/*++

Routine Description:

    Add child node under current node

Arguments:

    CIISChildNode *& pChild     : Child node to be added

Return Value:

    Error return code.

--*/
{
    ASSERT(m_pOwner != NULL);
    ISMCHILDINFO ii;

    CString strPath;
    BuildFullPath(strPath, FALSE);

    pChild = NULL;
    CError err(GetServerInfo()->AddChild(
        &ii, 
        sizeof(ii), 
        m_pOwner->QueryID(), 
        strPath
        ));

    if (err.Succeeded())
    {
        pChild = new CIISChildNode(
            &ii,
            m_pOwner
            );
    }

    return err;
}



HRESULT
CIISFileNode::SecurityWizard()
/*++

Routine Description:

    Launch the security wizard on this file node

Arguments:

    None

Return Value:

    Error return code

--*/
{
    ASSERT(m_pOwner != NULL);
    ASSERT(SupportsSecurityWizard());

    CString strParent;
    BuildParentPath(strParent, FALSE);

    return GetServerInfo()->ISMSecurityWizard(
        m_pOwner->QueryID(), 
        strParent, 
        m_strNodeName
        );
}