/////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 1996-2001 Microsoft Corporation
//
//  Module Name:
//      BasePage.cpp
//
//  Abstract:
//      Implementation of the CBasePropertyPage class.
//
//  Author:
//      David Potter (davidp)   June 28, 1996
//
//  Revision History:
//      1. Removed the calls to UpdateData from OnWizardNext and OnApply
//         since OnKillActive, called before both these functions does a
//         data update anyway.
//
//  Notes:
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CluAdmX.h"
#include "ExtObj.h"
#include "BasePage.h"
#include "BasePage.inl"
#include "PropList.h"
#include "ExcOper.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CBasePropertyPage property page
/////////////////////////////////////////////////////////////////////////////

IMPLEMENT_DYNCREATE( CBasePropertyPage, CPropertyPage )

/////////////////////////////////////////////////////////////////////////////
// Message Maps

BEGIN_MESSAGE_MAP( CBasePropertyPage, CPropertyPage )
    //{{AFX_MSG_MAP(CBasePropertyPage)
    ON_WM_CREATE()
    ON_WM_DESTROY()
    ON_WM_HELPINFO()
    ON_WM_CONTEXTMENU()
    ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::CBasePropertyPage
//
//  Routine Description:
//      Default constructor.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CBasePropertyPage::CBasePropertyPage( void )
{
    CommonConstruct();

}  //*** CBasePropertyPage::CBasePropertyPage()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::CBasePropertyPage
//
//  Routine Description:
//      Default constructor.
//
//  Arguments:
//      pdwHelpMap          [IN] Control-to-help ID map.
//      pdwWizardHelpMap    [IN] Control-to-help ID map if this is a wizard page.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CBasePropertyPage::CBasePropertyPage(
    IN const DWORD *    pdwHelpMap,
    IN const DWORD *    pdwWizardHelpMap
    )
    : m_dlghelp( pdwHelpMap, 0 )
{
    CommonConstruct();
    m_pdwWizardHelpMap = pdwWizardHelpMap;

}  //*** CBasePropertyPage::CBasePropertyPage()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::CBasePropertyPage
//
//  Routine Description:
//      Default constructor.
//
//  Arguments:
//      idd                 [IN] Dialog template resource ID.
//      pdwHelpMap          [IN] Control-to-help ID map.
//      pdwWizardHelpMap    [IN] Control-to-help ID map if this is a wizard page.
//      nIDCaption          [IN] Caption string resource ID.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CBasePropertyPage::CBasePropertyPage(
    IN UINT             idd,
    IN const DWORD *    pdwHelpMap,
    IN const DWORD *    pdwWizardHelpMap,
    IN UINT             nIDCaption
    )
    : CPropertyPage( idd, nIDCaption )
    , m_dlghelp( pdwHelpMap, idd )
{
    CommonConstruct();
    m_pdwWizardHelpMap = pdwWizardHelpMap;

}  //*** CBasePropertyPage::CBasePropertyPage(UINT, UINT)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::CommonConstruct
//
//  Routine Description:
//      Common construction.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::CommonConstruct( void )
{
    //{{AFX_DATA_INIT(CBasePropertyPage)
    //}}AFX_DATA_INIT

    m_peo = NULL;
    m_hpage = NULL;
    m_bBackPressed = FALSE;
    m_bSaved = FALSE;

    m_iddPropertyPage = NULL;
    m_iddWizardPage = NULL;
    m_idsCaption = NULL;

    m_pdwWizardHelpMap = NULL;

    m_bDoDetach = FALSE;

}  //*** CBasePropertyPage::CommonConstruct()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::HrInit
//
//  Routine Description:
//      Initialize the page.
//
//  Arguments:
//      peo         [IN OUT] Pointer to the extension object.
//
//  Return Value:
//      S_OK        Page initialized successfully.
//      hr          Error initializing the page.
//
//--
/////////////////////////////////////////////////////////////////////////////
HRESULT CBasePropertyPage::HrInit( IN OUT CExtObject * peo )
{
    ASSERT( peo != NULL );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    HRESULT     hr = S_OK;
    CWaitCursor wc;

    m_peo = peo;

    // Change the help map if this is a wizard page.
    if ( Peo()->BWizard() )
    {
        m_dlghelp.SetMap( m_pdwWizardHelpMap );
    } // if: on wizard page

    // Don't display a help button.
    m_psp.dwFlags &= ~PSP_HASHELP;

    // Construct the property page.
    if ( Peo()->BWizard() )
    {
        ASSERT( IddWizardPage() != NULL );
        Construct( IddWizardPage(), IdsCaption() );
        m_dlghelp.SetHelpMask( IddWizardPage() );
    }  // if:  adding page to wizard
    else
    {
        ASSERT( IddPropertyPage() != NULL );
        Construct( IddPropertyPage(), IdsCaption() );
        m_dlghelp.SetHelpMask( IddPropertyPage() );
    }  // else:  adding page to property sheet

    // Read the properties private to this resource and parse them.
    {
        DWORD           sc;
        CClusPropList   cpl;

        ASSERT( Peo() != NULL );
        ASSERT( Peo()->PrdResData() != NULL );
        ASSERT( Peo()->PrdResData()->m_hresource != NULL );

        // Read the properties.
        sc = cpl.ScGetResourceProperties(
                                Peo()->PrdResData()->m_hresource,
                                CLUSCTL_RESOURCE_GET_PRIVATE_PROPERTIES
                                );

        // Parse the properties.
        if ( sc == ERROR_SUCCESS )
        {
            // Parse the properties.
            try
            {
                sc = ScParseProperties( cpl );
            }  // try
            catch ( CMemoryException * pme )
            {
                sc = ERROR_NOT_ENOUGH_MEMORY;
                pme->Delete();
            }  // catch:  CMemoryException
        }  // if:  properties read successfully

        if ( sc != ERROR_SUCCESS )
        {
            CNTException nte( sc, IDS_ERROR_GETTING_PROPERTIES, NULL, NULL, FALSE );
            nte.ReportError();
            hr = HRESULT_FROM_WIN32( sc );
        }  // if:  error parsing getting or parsing properties
    }  // Read the properties private to this resource and parse them

    return hr;

}  //*** CBasePropertyPage::HrInit()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScParseProperties
//
//  Routine Description:
//      Parse the properties of the resource.  This is in a separate function
//      from HrInit so that the optimizer can do a better job.
//
//  Arguments:
//      rcpl            [IN] Cluster property list to parse.
//
//  Return Value:
//      ERROR_SUCCESS   Properties were parsed successfully.
//      Any error returns from ScParseUnknownProperty().
//
//  Exceptions Thrown:
//      Any exceptions from CString::operator=().
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScParseProperties( IN CClusPropList & rcpl )
{
    DWORD                   sc;
    DWORD                   cprop;
    const CObjectProperty * pprop;

    ASSERT( rcpl.PbPropList() != NULL );

    sc = rcpl.ScMoveToFirstProperty();
    while ( sc == ERROR_SUCCESS )
    {
        //
        // Parse known properties.
        //
        for ( pprop = Pprops(), cprop = Cprops() ; cprop > 0 ; pprop++, cprop-- )
        {
            if ( lstrcmpiW( rcpl.PszCurrentPropertyName(), pprop->m_pwszName ) == 0 )
            {
                ASSERT( rcpl.CpfCurrentValueFormat() == pprop->m_propFormat );
                switch ( pprop->m_propFormat )
                {
                    case CLUSPROP_FORMAT_SZ:
                    case CLUSPROP_FORMAT_EXPAND_SZ:
                        ASSERT( ( rcpl.CbCurrentValueLength() == (lstrlenW( rcpl.CbhCurrentValue().pStringValue->sz ) + 1) * sizeof( WCHAR ) )
                             || ( ( rcpl.CbCurrentValueLength() == 0 )
                                 && ( rcpl.CbhCurrentValue().pStringValue->sz[ 0 ] == L'\0' )
                                )
                              );
                        *pprop->m_value.pstr = rcpl.CbhCurrentValue().pStringValue->sz;
                        *pprop->m_valuePrev.pstr = rcpl.CbhCurrentValue().pStringValue->sz;
                        break;
                    case CLUSPROP_FORMAT_DWORD:
                    case CLUSPROP_FORMAT_LONG:
                        ASSERT( rcpl.CbCurrentValueLength() == sizeof( DWORD ) );
                        *pprop->m_value.pdw = rcpl.CbhCurrentValue().pDwordValue->dw;
                        *pprop->m_valuePrev.pdw = rcpl.CbhCurrentValue().pDwordValue->dw;
                        break;
                    case CLUSPROP_FORMAT_BINARY:
                    case CLUSPROP_FORMAT_MULTI_SZ:
                        *pprop->m_value.ppb = rcpl.CbhCurrentValue().pBinaryValue->rgb;
                        *pprop->m_value.pcb = rcpl.CbhCurrentValue().pBinaryValue->cbLength;
                        *pprop->m_valuePrev.ppb = rcpl.CbhCurrentValue().pBinaryValue->rgb;
                        *pprop->m_valuePrev.pcb = rcpl.CbhCurrentValue().pBinaryValue->cbLength;
                        break;
                    default:
                        ASSERT( 0 );  // don't know how to deal with this type
                } // switch: property format

                // Exit the loop since we found the parameter.
                break;
            } // if: found a match
        } // for: each property that we know about

        //
        // If the property wasn't known, ask the derived class to parse it.
        //
        if ( cprop == 0 )
        {
            sc = ScParseUnknownProperty(
                        rcpl.CbhCurrentPropertyName().pName->sz,
                        rcpl.CbhCurrentValue(),
                        rcpl.RPvlPropertyValue().CbDataLeft()
                        );
            if ( sc != ERROR_SUCCESS )
            {
                return sc;
            } // if: error parsing the unknown property
        } // if: property not parsed

        //
        // Advance the buffer pointer past the value in the value list.
        //
        sc = rcpl.ScMoveToNextProperty();
    } // while: more properties to parse

    //
    // If we reached the end of the properties, fix the return code.
    //
    if ( sc == ERROR_NO_MORE_ITEMS )
    {
        sc = ERROR_SUCCESS;
    } // if: ended loop after parsing all properties

    return sc;

}  //*** CBasePropertyPage::ScParseProperties()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnCreate
//
//  Routine Description:
//      Handler for the WM_CREATE message.
//
//  Arguments:
//      lpCreateStruct  [IN OUT] Window create structure.
//
//  Return Value:
//      -1      Error.
//      0       Success.
//
//--
/////////////////////////////////////////////////////////////////////////////
int CBasePropertyPage::OnCreate( LPCREATESTRUCT lpCreateStruct )
{
    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    // Attach the window to the property page structure.
    // This has been done once already in the main application, since the
    // main application owns the property sheet.  It needs to be done here
    // so that the window handle can be found in the DLL's handle map.
    if ( FromHandlePermanent( m_hWnd ) == NULL ) // is the window handle already in the handle map
    {
        HWND hWnd = m_hWnd;
        m_hWnd = NULL;
        Attach( hWnd );
        m_bDoDetach = TRUE;
    } // if: is the window handle in the handle map

    return CPropertyPage::OnCreate( lpCreateStruct );

}  //*** CBasePropertyPage::OnCreate()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnDestroy
//
//  Routine Description:
//      Handler for the WM_DESTROY message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::OnDestroy( void )
{
    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    // Detach the window from the property page structure.
    // This will be done again by the main application, since it owns the
    // property sheet.  It needs to be done here so that the window handle
    // can be removed from the DLL's handle map.
    if ( m_bDoDetach )
    {
        if ( m_hWnd != NULL )
        {
            HWND hWnd = m_hWnd;

            Detach();
            m_hWnd = hWnd;
        } // if: do we have a window handle?
    } // if: do we need to balance the attach we did with a detach?

    CPropertyPage::OnDestroy();

}  //*** CBasePropertyPage::OnDestroy()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::DoDataExchange
//
//  Routine Description:
//      Do data exchange between the dialog and the class.
//
//  Arguments:
//      pDX     [IN OUT] Data exchange object
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::DoDataExchange( CDataExchange * pDX )
{
    if ( ! pDX->m_bSaveAndValidate || !BSaved() )
    {
        AFX_MANAGE_STATE( AfxGetStaticModuleState() );

        //{{AFX_DATA_MAP(CBasePropertyPage)
            // NOTE: the ClassWizard will add DDX and DDV calls here
        //}}AFX_DATA_MAP
        DDX_Control( pDX, IDC_PP_ICON, m_staticIcon );
        DDX_Control( pDX, IDC_PP_TITLE, m_staticTitle );

        if ( pDX->m_bSaveAndValidate )
        {
            if ( ! BBackPressed() )
            {
                CWaitCursor wc;

                // Validate the data.
                if ( ! BSetPrivateProps( TRUE /*bValidateOnly*/ ) )
                {
                    pDX->Fail();
                } // if: error setting private properties
            }  // if:  Back button not pressed
        }  // if:  saving data from dialog
        else
        {
            // Set the title.
            DDX_Text( pDX, IDC_PP_TITLE, m_strTitle );
        }  // if:  not saving data
    }  // if:  not saving or haven't saved yet

    CPropertyPage::DoDataExchange( pDX );

}  //*** CBasePropertyPage::DoDataExchange()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnInitDialog
//
//  Routine Description:
//      Handler for the WM_INITDIALOG message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      TRUE        We need the focus to be set for us.
//      FALSE       We already set the focus to the proper control.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::OnInitDialog( void )
{
    ASSERT( Peo() != NULL );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    // Set the title string.
    m_strTitle = Peo()->RrdResData().m_strName;

    // Call the base class method.
    CPropertyPage::OnInitDialog();

    // Display an icon for the object.
    if ( Peo()->Hicon() != NULL )
    {
        m_staticIcon.SetIcon( Peo()->Hicon() );
    } // if: icon was specified

    return TRUE;    // return TRUE unless you set the focus to a control
                    // EXCEPTION: OCX Property Pages should return FALSE

}  //*** CBasePropertyPage::OnInitDialog()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnSetActive
//
//  Routine Description:
//      Handler for the PSN_SETACTIVE message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      TRUE    Page successfully initialized.
//      FALSE   Page not initialized.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::OnSetActive( void )
{
    HRESULT     hr;

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    // Reread the data.
    hr = Peo()->HrGetObjectInfo();
    if ( hr != NOERROR )
    {
        return FALSE;
    } // if: error getting object info

    // Set the title string.
    m_strTitle = Peo()->RrdResData().m_strName;

    m_bBackPressed = FALSE;
    m_bSaved = FALSE;
    return CPropertyPage::OnSetActive();

}  //*** CBasePropertyPage::OnSetActive()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnApply
//
//  Routine Description:
//      Handler for the PSM_APPLY message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      TRUE    Page successfully applied.
//      FALSE   Error applying page.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::OnApply( void )
{
    ASSERT( ! BWizard() );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    CWaitCursor wc;

    if ( ! BApplyChanges() )
    {
        return FALSE;
    } // if: error applying changes

    return CPropertyPage::OnApply();

}  //*** CBasePropertyPage::OnApply()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnWizardBack
//
//  Routine Description:
//      Handler for the PSN_WIZBACK message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      -1      Don't change the page.
//      0       Change the page.
//
//--
/////////////////////////////////////////////////////////////////////////////
LRESULT CBasePropertyPage::OnWizardBack( void )
{
    LRESULT     lResult;

    ASSERT( BWizard() );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    lResult = CPropertyPage::OnWizardBack();
    if ( lResult != -1 )
    {
        m_bBackPressed = TRUE;
    } // if: back processing performed successfully

    return lResult;

}  //*** CBasePropertyPage::OnWizardBack()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnWizardNext
//
//  Routine Description:
//      Handler for the PSN_WIZNEXT message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      -1      Don't change the page.
//      0       Change the page.
//
//--
/////////////////////////////////////////////////////////////////////////////
LRESULT CBasePropertyPage::OnWizardNext( void )
{
    ASSERT( BWizard() );

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    CWaitCursor _wc;

    // Update the data in the class from the page.
    // This necessary because, while OnKillActive() will call UpdateData(),
    // it is called after this method is called, and we need to be sure that
    // data has been saved before we apply them.
    if ( ! UpdateData( TRUE /*bSaveAndValidate*/ ) )
    {
        return -1;
    } // if: error updating data

    // Save the data in the sheet.
    if ( ! BApplyChanges() )
    {
        return -1;
    } // if: error applying changes

    // Create the object.

    return CPropertyPage::OnWizardNext();

}  //*** CBasePropertyPage::OnWizardNext()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnWizardFinish
//
//  Routine Description:
//      Handler for the PSN_WIZFINISH message.
//
//  Arguments:
//      None.
//
//  Return Value:
//      FALSE   Don't change the page.
//      TRUE    Change the page.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::OnWizardFinish( void )
{
    ASSERT( BWizard() );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    CWaitCursor wc;

    // BUG! There should be no need to call UpdateData in this function.
    // See BUG: Finish Button Fails Data Transfer from Page to Variables
    // MSDN Article ID: Q150349

    // Update the data in the class from the page.
    if ( ! UpdateData( TRUE /*bSaveAndValidate*/ ) )
    {
        return FALSE;
    } // if: error updating data

    // Save the data in the sheet.
    if ( ! BApplyChanges() )
    {
        return FALSE;
    } // if: error applying changes

    return CPropertyPage::OnWizardFinish();

}  //*** CBasePropertyPage::OnWizardFinish()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnChangeCtrl
//
//  Routine Description:
//      Handler for the messages sent when a control is changed.  This
//      method can be specified in a message map if all that needs to be
//      done is enable the Apply button.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::OnChangeCtrl( void )
{
    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    SetModified( TRUE );

}  //*** CBasePropertyPage::OnChangeCtrl()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::EnableNext
//
//  Routine Description:
//      Enables or disables the NEXT or FINISH button.
//
//  Arguments:
//      bEnable     [IN] TRUE = enable the button, FALSE = disable the button.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::EnableNext( IN BOOL bEnable /*TRUE*/ )
{
    ASSERT( BWizard() );
    ASSERT( PiWizardCallback() );

    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    PiWizardCallback()->EnableNext( (LONG *) Hpage(), bEnable );

}  //*** CBasePropertyPage::EnableNext()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::BApplyChanges
//
//  Routine Description:
//      Apply changes made on the page.
//
//  Arguments:
//      None.
//
//  Return Value:
//      TRUE    Page successfully applied.
//      FALSE   Error applying page.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::BApplyChanges( void )
{
    AFX_MANAGE_STATE( AfxGetStaticModuleState() );

    BOOL        bSuccess;
    CWaitCursor wc;

    // Make sure required dependencies have been set.
    if ( ! BSetPrivateProps() )
    {
        bSuccess = FALSE;
    } // if: all required dependencies are not present
    else
    {
        // Save data.
        bSuccess = BRequiredDependenciesPresent();
    } // else: all required dependencies are present

    return bSuccess;

}  //*** CBasePropertyPage::BApplyChanges()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::BBuildPropList
//
//  Routine Description:
//      Build the property list.
//
//  Arguments:
//      rcpl        [IN OUT] Cluster property list.
//      bNoNewProps [IN] TRUE = exclude properties marked with opfNew.
//
//  Return Value:
//      TRUE        Property list built successfully.
//      FALSE       Error building property list.
//
//  Exceptions Thrown:
//      Any exceptions thrown by CClusPropList::ScAddProp().
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::BBuildPropList(
    IN OUT CClusPropList &  rcpl,
    IN BOOL                 bNoNewProps     // = FALSE
    )
{
    BOOL                    bNewPropsFound = FALSE;
    DWORD                   cprop;
    const CObjectProperty * pprop;

    for ( pprop = Pprops(), cprop = Cprops() ; cprop > 0 ; pprop++, cprop-- )
    {
        if ( bNoNewProps && ( pprop->m_fFlags & CObjectProperty::opfNew ) )
        {
            bNewPropsFound = TRUE;
            continue;
        } // if:  no new props allowed and this is a new property

        switch ( pprop->m_propFormat )
        {
            case CLUSPROP_FORMAT_SZ:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pstr,
                        *pprop->m_valuePrev.pstr
                        );
                break;
            case CLUSPROP_FORMAT_EXPAND_SZ:
                rcpl.ScAddExpandSzProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pstr,
                        *pprop->m_valuePrev.pstr
                        );
                break;
            case CLUSPROP_FORMAT_DWORD:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pdw,
                        *pprop->m_valuePrev.pdw
                        );
                break;
            case CLUSPROP_FORMAT_LONG:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pl,
                        *pprop->m_valuePrev.pl
                        );
                break;
            case CLUSPROP_FORMAT_BINARY:
            case CLUSPROP_FORMAT_MULTI_SZ:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.ppb,
                        *pprop->m_value.pcb,
                        *pprop->m_valuePrev.ppb,
                        *pprop->m_valuePrev.pcb
                        );
                break;
            default:
                ASSERT( 0 );  // don't know how to deal with this type
                return FALSE;
        }  // switch:  property format
    }  // for:  each property

    return ( ! bNoNewProps || bNewPropsFound );

}  //*** CBasePropertyPage::BBuildPropList()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::BSetPrivateProps
//
//  Routine Description:
//      Set the private properties for this object.
//
//  Arguments:
//      bValidateOnly   [IN] TRUE = only validate the data.
//      bNoNewProps     [IN] TRUE = exclude properties marked with opfNew.
//
//  Return Value:
//      ERROR_SUCCESS   The operation was completed successfully.
//      !0              Failure.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::BSetPrivateProps(
    IN BOOL bValidateOnly,  // = FALSE
    IN BOOL bNoNewProps     // = FALSE
    )
{
    BOOL            bSuccess   = TRUE;
    CClusPropList   cpl(BWizard() /*bAlwaysAddProp*/);

    ASSERT( Peo() != NULL );
    ASSERT( Peo()->PrdResData() );
    ASSERT( Peo()->PrdResData()->m_hresource );

    // Build the property list.
    try
    {
        bSuccess = BBuildPropList( cpl, bNoNewProps );
    }  // try
    catch ( CException * pe )
    {
        pe->ReportError();
        pe->Delete();
        bSuccess = FALSE;
    }  // catch:  CException

    // Set the data.
    if ( bSuccess )
    {
        if ( ( cpl.PbPropList() != NULL ) && ( cpl.CbPropList() > 0 ) )
        {
            DWORD       sc;
            DWORD       dwControlCode;
            DWORD       cbProps;

            // Determine which control code to use.
            if ( bValidateOnly )
            {
                dwControlCode = CLUSCTL_RESOURCE_VALIDATE_PRIVATE_PROPERTIES;
            } // if: only validating
            else
            {
                dwControlCode = CLUSCTL_RESOURCE_SET_PRIVATE_PROPERTIES;
            } // else: not just validating

            // Set private properties.
            sc = ClusterResourceControl(
                            Peo()->PrdResData()->m_hresource,
                            NULL,   // hNode
                            dwControlCode,
                            cpl.PbPropList(),
                            cpl.CbPropList(),
                            NULL,   // lpOutBuffer
                            0,      // nOutBufferSize
                            &cbProps
                            );
            if ( sc != ERROR_SUCCESS )
            {
                if ( sc == ERROR_INVALID_PARAMETER )
                {
                    if ( ! bNoNewProps )
                    {
                        bSuccess = BSetPrivateProps( bValidateOnly, TRUE /*bNoNewProps*/ );
                    } // if:  new props are allowed
                    else
                    {
                        bSuccess = FALSE;
                    } // else: new props are not allowed
                } // if:  invalid parameter error occurred
                else
                {
                    bSuccess = FALSE;
                } // else: some other error occurred
            }  // if:  error setting/validating data

            //
            // If an error occurred, display an error message.
            //
            if ( ! bSuccess )
            {
                DisplaySetPropsError( sc, bValidateOnly ? IDS_ERROR_VALIDATING_PROPERTIES : IDS_ERROR_SETTING_PROPERTIES );
                if ( sc == ERROR_RESOURCE_PROPERTIES_STORED )
                {
                    bSuccess = TRUE;
                } // if: properties only stored
            } // if:  error occurred
        }  // if:  there is data to set
    }  // if:  no errors building the property list

    // Save data locally.
    if ( ! bValidateOnly && bSuccess )
    {
        // Save new values as previous values.
        try
        {
            DWORD                   cprop;
            const CObjectProperty * pprop;

            for ( pprop = Pprops(), cprop = Cprops() ; cprop > 0 ; pprop++, cprop-- )
            {
                switch ( pprop->m_propFormat )
                {
                    case CLUSPROP_FORMAT_SZ:
                    case CLUSPROP_FORMAT_EXPAND_SZ:
                        ASSERT( pprop->m_value.pstr != NULL );
                        ASSERT( pprop->m_valuePrev.pstr != NULL );
                        *pprop->m_valuePrev.pstr = *pprop->m_value.pstr;
                        break;
                    case CLUSPROP_FORMAT_DWORD:
                    case CLUSPROP_FORMAT_LONG:
                        ASSERT( pprop->m_value.pdw != NULL );
                        ASSERT( pprop->m_valuePrev.pdw != NULL );
                        *pprop->m_valuePrev.pdw = *pprop->m_value.pdw;
                        break;
                    case CLUSPROP_FORMAT_BINARY:
                    case CLUSPROP_FORMAT_MULTI_SZ:
                        ASSERT( pprop->m_value.ppb != NULL );
                        ASSERT( *pprop->m_value.ppb != NULL );
                        ASSERT( pprop->m_value.pcb != NULL );
                        ASSERT( pprop->m_valuePrev.ppb != NULL );
                        ASSERT( *pprop->m_valuePrev.ppb != NULL );
                        ASSERT( pprop->m_valuePrev.pcb != NULL );
                        delete [] *pprop->m_valuePrev.ppb;
                        *pprop->m_valuePrev.ppb = new BYTE[ *pprop->m_value.pcb ];
                        if ( *pprop->m_valuePrev.ppb == NULL )
                        {
                            AfxThrowMemoryException();
                        } // if: error allocating memory
                        CopyMemory( *pprop->m_valuePrev.ppb, *pprop->m_value.ppb, *pprop->m_value.pcb );
                        *pprop->m_valuePrev.pcb = *pprop->m_value.pcb;
                        break;
                    default:
                        ASSERT( 0 );    // don't know how to deal with this type
                }  // switch:  property format
            }  // for:  each property
        }  // try
        catch ( CException * pe )
        {
            pe->ReportError();
            pe->Delete();
            bSuccess = FALSE;
        }  // catch:  CException
    }  // if:  not just validating and successful so far

    //
    // Indicate we successfully saved the properties.
    //
    if ( ! bValidateOnly && bSuccess )
    {
        m_bSaved = TRUE;
    } // if:  successfully saved data

    return bSuccess;

}  //*** CBasePropertyPage::BSetPrivateProps()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::DisplaySetPropsError
//
//  Routine Description:
//      Display an error caused by setting or validating properties.
//
//  Arguments:
//      sc      [IN] Status to display error on.
//      idsOper [IN] Operation message.
//
//  Return Value:
//      nStatus ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::DisplaySetPropsError(
    IN DWORD    sc,
    IN UINT     idsOper
    ) const
{
    CString strErrorMsg;
    CString strOperMsg;
    CString strMsgIdFmt;
    CString strMsgId;
    CString strMsg;

    strOperMsg.LoadString( IDS_ERROR_SETTING_PROPERTIES );
    FormatError( strErrorMsg, sc );
    strMsgIdFmt.LoadString( IDS_ERROR_MSG_ID );
    strMsgId.Format( strMsgIdFmt, sc, sc );
    strMsg.Format( _T("%s\n\n%s%s"), strOperMsg, strErrorMsg, strMsgId );
    AfxMessageBox( strMsg );

}  //*** CBasePropertyPage::DisplaySetPropsError()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::BRequiredDependenciesPresent
//
//  Routine Description:
//      Determine if the specified list contains each required resource
//      for this type of resource.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      Any exceptions thrown by CString::LoadString() or CString::operator=().
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::BRequiredDependenciesPresent( void )
{
    BOOL                        bFound = TRUE;
    DWORD                       sc;
    CClusPropValueList          pvl;
    HRESOURCE                   hres;
    PCLUS_RESOURCE_CLASS_INFO   prci = NULL;
    CString                     strMissing;

    do
    {
        // Collect the list of required dependencies.
        sc = pvl.ScGetResourceValueList(
                    Peo()->PrdResData()->m_hresource,
                    CLUSCTL_RESOURCE_GET_REQUIRED_DEPENDENCIES
                    );
        if ( sc != ERROR_SUCCESS )
        {
            CNTException nte( sc, 0, NULL, NULL, FALSE );
            nte.ReportError();
            break;
        } // if: error collecting required dependencies

        // Move to the first value.
        sc = pvl.ScMoveToFirstValue();

        while ( sc == ERROR_SUCCESS )
        {
            switch ( pvl.CptCurrentValueType() )
            {
                case CLUSPROP_TYPE_RESCLASS:
                    prci = reinterpret_cast< PCLUS_RESOURCE_CLASS_INFO >( &pvl.CbhCurrentValue().pResourceClassInfoValue->li );
                    hres = ResUtilGetResourceDependencyByClass(
                                Hcluster(),
                                Peo()->PrdResData()->m_hresource,
                                prci,
                                FALSE // bRecurse
                                );
                    if ( hres != NULL )
                    {
                        CloseClusterResource( hres );
                    } // if:  found the resource
                    else
                    {
                        if ( ! strMissing.LoadString( IDS_RESCLASS_UNKNOWN + prci->rc ) )
                        {
                            strMissing.LoadString( IDS_RESCLASS_UNKNOWN );
                        } // if: unknown resource class

                        bFound = FALSE;
                    } // else: resource not found
                    break;

                case CLUSPROP_TYPE_NAME:
                    hres = ResUtilGetResourceDependencyByName(
                                Hcluster(),
                                Peo()->PrdResData()->m_hresource,
                                pvl.CbhCurrentValue().pName->sz,
                                FALSE // bRecurse
                                );
                    if ( hres != NULL )
                    {
                        CloseClusterResource( hres );
                    } // if:  found the resource
                    else
                    {
                        GetResTypeDisplayOrTypeName( pvl.CbhCurrentValue().pName->sz, &strMissing );
                        bFound = FALSE;
                    } // else: resource not found
                    break;

            } // switch: value type

            // If a match was not found, changes cannot be applied.
            if ( ! bFound )
            {
                CExceptionWithOper ewo( IDS_REQUIRED_DEPENDENCY_NOT_FOUND, NULL, NULL, FALSE );

                ewo.SetOperation( IDS_REQUIRED_DEPENDENCY_NOT_FOUND, static_cast< LPCWSTR >( strMissing ) );
                ewo.ReportError();

                break;
            }  // if:  not found

            sc = pvl.ScMoveToNextValue();
        } // while: more values in the value list

    } while( 0 );

    return bFound;

} //*** CBasePropertyPage::BRequiredDependenciesPresent()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::GetResTypeDisplayOrTypeName
//
//  Routine Description:
//      Get the display name for a resource type if possible.  If any errors
//      occur, just return the type name.
//
//  Arguments:
//      pszResTypeNameIn
//          [IN] Name of resource type.
//
//      pstrResTypeDisplayNameInOut
//          [IN OUT] CString in which to return the display name.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::GetResTypeDisplayOrTypeName(
    IN      LPCWSTR     pszResTypeNameIn,
    IN OUT  CString *   pstrResTypeDisplayNameInOut
    )
{
    DWORD           sc;
    CClusPropList   cpl;

    // Get resource type properties.
    sc = cpl.ScGetResourceTypeProperties(
                Hcluster(),
                pszResTypeNameIn,
                CLUSCTL_RESOURCE_TYPE_GET_COMMON_PROPERTIES
                );
    if ( sc != ERROR_SUCCESS )
        goto Error;

    // Find the Name property.
    sc = cpl.ScMoveToPropertyByName( CLUSREG_NAME_RESTYPE_NAME );
    if ( sc != ERROR_SUCCESS )
        goto Error;

    // Move to the first value for the property.
    sc = cpl.ScMoveToFirstPropertyValue();
    if ( sc != ERROR_SUCCESS )
        goto Error;

    // Make sure the name is a string.
    if ( ( cpl.CpfCurrentValueFormat() != CLUSPROP_FORMAT_SZ )
      && ( cpl.CpfCurrentValueFormat() != CLUSPROP_FORMAT_EXPAND_SZ )
      && ( cpl.CpfCurrentValueFormat() != CLUSPROP_FORMAT_EXPANDED_SZ )
       )
       goto Error;

    // Copy the string into the output CString.
    *pstrResTypeDisplayNameInOut = cpl.CbhCurrentValue().pStringValue->sz;

Cleanup:
    return;

Error:
    *pstrResTypeDisplayNameInOut = pszResTypeNameIn;
    goto Cleanup;

} //*** CBasePropertyPage::GetResTypeDisplayOrTypeName()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScReadValue
//
//  Routine Description:
//      Read a REG_SZ value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      rstrValue       [OUT] String in which to return the value.
//      hkey            [IN] Handle to the registry key to read from.
//
//  Return Value:
//      sc ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScReadValue(
    IN LPCTSTR      pszValueName,
    OUT CString &   rstrValue,
    IN HKEY         hkey
    )
{
    DWORD       sc;
    LPWSTR      pwszValue  = NULL;
    DWORD       dwValueLen;
    DWORD       dwValueType;

    ASSERT( pszValueName != NULL );
    ASSERT( hkey != NULL );

    rstrValue.Empty();

    try
    {
        // Get the size of the value.
        dwValueLen = 0;
        sc = ::ClusterRegQueryValue(
                        hkey,
                        pszValueName,
                        &dwValueType,
                        NULL,
                        &dwValueLen
                        );
        if ( ( sc == ERROR_SUCCESS ) || ( sc == ERROR_MORE_DATA ) )
        {
            ASSERT( dwValueType == REG_SZ );

            // Allocate enough space for the data.
            pwszValue = rstrValue.GetBuffer( dwValueLen / sizeof( WCHAR ) );
            if ( pwszValue == NULL )
            {
                AfxThrowMemoryException();
            } // if: error getting the buffer
            ASSERT( pwszValue != NULL );
            dwValueLen += 1 * sizeof( WCHAR );  // Don't forget the final null-terminator.

            // Read the value.
            sc = ::ClusterRegQueryValue(
                            hkey,
                            pszValueName,
                            &dwValueType,
                            (LPBYTE) pwszValue,
                            &dwValueLen
                            );
            if ( sc == ERROR_SUCCESS )
            {
                ASSERT( dwValueType == REG_SZ );
            }  // if:  value read successfully
            rstrValue.ReleaseBuffer();
        }  // if:  got the size successfully
    }  // try
    catch ( CMemoryException * pme )
    {
        pme->Delete();
        sc = ERROR_NOT_ENOUGH_MEMORY;
    }  // catch:  CMemoryException

    return sc;

}  //*** CBasePropertyPage::ScReadValue(LPCTSTR, CString&)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScReadValue
//
//  Routine Description:
//      Read a REG_DWORD value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pdwValue        [OUT] DWORD in which to return the value.
//      hkey            [IN] Handle to the registry key to read from.
//
//  Return Value:
//      _sc ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScReadValue(
    IN LPCTSTR      pszValueName,
    OUT DWORD *     pdwValue,
    IN HKEY         hkey
    )
{
    DWORD       _sc;
    DWORD       _dwValue;
    DWORD       _dwValueLen;
    DWORD       _dwValueType;

    ASSERT(pszValueName != NULL);
    ASSERT(pdwValue != NULL);
    ASSERT(hkey != NULL);

    *pdwValue = 0;

    // Read the value.
    _dwValueLen = sizeof(_dwValue);
    _sc = ::ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &_dwValueType,
                    (LPBYTE) &_dwValue,
                    &_dwValueLen
                    );
    if (_sc == ERROR_SUCCESS)
    {
        ASSERT(_dwValueType == REG_DWORD);
        ASSERT(_dwValueLen == sizeof(_dwValue));
        *pdwValue = _dwValue;
    }  // if:  value read successfully

    return _sc;

}  //*** CBasePropertyPage::ScReadValue(LPCTSTR, DWORD*)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScReadValue
//
//  Routine Description:
//      Read a REG_BINARY value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      ppbValue        [OUT] Pointer in which to return the data.  Caller
//                          is responsible for deallocating the data.
//      hkey            [IN] Handle to the registry key to read from.
//
//  Return Value:
//      _sc ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScReadValue(
    IN LPCTSTR      pszValueName,
    OUT LPBYTE *    ppbValue,
    IN HKEY         hkey
    )
{
    DWORD       _sc;
    DWORD       _dwValueLen;
    DWORD       _dwValueType;

    ASSERT(pszValueName != NULL);
    ASSERT(ppbValue != NULL);
    ASSERT(hkey != NULL);

    *ppbValue = NULL;

    // Get the length of the value.
    _dwValueLen = 0;
    _sc = ::ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &_dwValueType,
                    NULL,
                    &_dwValueLen
                    );
    if (_sc != ERROR_SUCCESS)
        return _sc;

    ASSERT(_dwValueType == REG_BINARY);

    // Allocate a buffer,
    try
    {
        *ppbValue = new BYTE[_dwValueLen];
    }  // try
    catch (CMemoryException *)
    {
        _sc = ERROR_NOT_ENOUGH_MEMORY;
        return _sc;
    }  // catch:  CMemoryException

    // Read the value.
    _sc = ::ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &_dwValueType,
                    *ppbValue,
                    &_dwValueLen
                    );
    if (_sc != ERROR_SUCCESS)
    {
        delete [] *ppbValue;
        *ppbValue = NULL;
    }  // if:  value read successfully

    return _sc;

}  //*** CBasePropertyPage::ScReadValue(LPCTSTR, LPBYTE)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScWriteValue
//
//  Routine Description:
//      Write a REG_SZ value for this item if it hasn't changed.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      rstrValue       [IN] Value data.
//      rstrPrevValue   [IN OUT] Previous value.
//      hkey            [IN] Handle to the registry key to write to.
//
//  Return Value:
//      _sc
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScWriteValue(
    IN LPCTSTR          pszValueName,
    IN const CString &  rstrValue,
    IN OUT CString &    rstrPrevValue,
    IN HKEY             hkey
    )
{
    DWORD       _sc;

    ASSERT(pszValueName != NULL);
    ASSERT(hkey != NULL);

    // Write the value if it hasn't changed.
    if (rstrValue != rstrPrevValue)
    {
        _sc = ::ClusterRegSetValue(
                        hkey,
                        pszValueName,
                        REG_SZ,
                        (CONST BYTE *) (LPCTSTR) rstrValue,
                        (rstrValue.GetLength() + 1) * sizeof(TCHAR)
                        );
        if (_sc == ERROR_SUCCESS)
            rstrPrevValue = rstrValue;
    }  // if:  value changed
    else
        _sc = ERROR_SUCCESS;
    return _sc;

}  //*** CBasePropertyPage::ScWriteValue(LPCTSTR, CString&)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScWriteValue
//
//  Routine Description:
//      Write a REG_DWORD value for this item if it hasn't changed.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      dwValue         [IN] Value data.
//      pdwPrevValue    [IN OUT] Previous value.
//      hkey            [IN] Handle to the registry key to write to.
//
//  Return Value:
//      _sc
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScWriteValue(
    IN LPCTSTR          pszValueName,
    IN DWORD            dwValue,
    IN OUT DWORD *      pdwPrevValue,
    IN HKEY             hkey
    )
{
    DWORD       _sc;

    ASSERT(pszValueName != NULL);
    ASSERT(pdwPrevValue != NULL);
    ASSERT(hkey != NULL);

    // Write the value if it hasn't changed.
    if (dwValue != *pdwPrevValue)
    {
        _sc = ::ClusterRegSetValue(
                        hkey,
                        pszValueName,
                        REG_DWORD,
                        (CONST BYTE *) &dwValue,
                        sizeof(dwValue)
                        );
        if (_sc == ERROR_SUCCESS)
            *pdwPrevValue = dwValue;
    }  // if:  value changed
    else
        _sc = ERROR_SUCCESS;
    return _sc;

}  //*** CBasePropertyPage::ScWriteValue(LPCTSTR, DWORD)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ScWriteValue
//
//  Routine Description:
//      Write a REG_BINARY value for this item if it hasn't changed.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      pbValue         [IN] Value data.
//      cbValue         [IN] Size of value data.
//      ppbPrevValue    [IN OUT] Previous value.
//      cbPrevValue     [IN] Size of the previous data.
//      hkey            [IN] Handle to the registry key to write to.
//
//  Return Value:
//      _sc
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CBasePropertyPage::ScWriteValue(
    IN LPCTSTR          pszValueName,
    IN const LPBYTE     pbValue,
    IN DWORD            cbValue,
    IN OUT LPBYTE *     ppbPrevValue,
    IN DWORD            cbPrevValue,
    IN HKEY             hkey
    )
{
    DWORD       _sc;
    LPBYTE      _pbPrevValue    = NULL;

    ASSERT(pszValueName != NULL);
    ASSERT(pbValue != NULL);
    ASSERT(ppbPrevValue != NULL);
    ASSERT(cbValue > 0);
    ASSERT(hkey != NULL);

    // See if the data has changed.
    if (cbValue == cbPrevValue)
    {
        if (memcmp(pbValue, *ppbPrevValue, cbValue) == 0)
            return ERROR_SUCCESS;
    }  // if:  lengths are the same

    // Allocate a new buffer for the previous data pointer.
    try
    {
        _pbPrevValue = new BYTE[cbValue];
    }
    catch (CMemoryException *)
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }  // catch:  CMemoryException
    ::CopyMemory(_pbPrevValue, pbValue, cbValue);

    // Write the value if it hasn't changed.
    _sc = ::ClusterRegSetValue(
                    hkey,
                    pszValueName,
                    REG_BINARY,
                    pbValue,
                    cbValue
                    );
    if (_sc == ERROR_SUCCESS)
    {
        delete [] *ppbPrevValue;
        *ppbPrevValue = _pbPrevValue;
    }  // if:  set was successful
    else
        delete [] _pbPrevValue;

    return _sc;

}  //*** CBasePropertyPage::ScWriteValue(LPCTSTR, const LPBYTE)

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnContextMenu
//
//  Routine Description:
//      Handler for the WM_CONTEXTMENU message.
//
//  Arguments:
//      pWnd    Window in which user clicked the right mouse button.
//      point   Position of the cursor, in screen coordinates.
//
//  Return Value:
//      TRUE    Help processed.
//      FALSE   Help not processed.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::OnContextMenu(CWnd * pWnd, CPoint point)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    m_dlghelp.OnContextMenu(pWnd, point);

}  //*** CBasePropertyPage::OnContextMenu()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnHelpInfo
//
//  Routine Description:
//      Handler for the WM_HELPINFO message.
//
//  Arguments:
//      pHelpInfo   Structure containing info about displaying help.
//
//  Return Value:
//      TRUE        Help processed.
//      FALSE       Help not processed.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::OnHelpInfo(HELPINFO * pHelpInfo)
{
    BOOL    _bProcessed;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    _bProcessed = m_dlghelp.OnHelpInfo(pHelpInfo);
    if (!_bProcessed)
        _bProcessed = CPropertyPage::OnHelpInfo(pHelpInfo);
    return _bProcessed;

}  //*** CBasePropertyPage::OnHelpInfo()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::OnCommandHelp
//
//  Routine Description:
//      Handler for the WM_COMMANDHELP message.
//
//  Arguments:
//      wParam      [IN] WPARAM.
//      lParam      [IN] LPARAM.
//
//  Return Value:
//      TRUE    Help processed.
//      FALSE   Help not processed.
//
//--
/////////////////////////////////////////////////////////////////////////////
LRESULT CBasePropertyPage::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
    LRESULT _bProcessed;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    _bProcessed = m_dlghelp.OnCommandHelp(wParam, lParam);
    if (!_bProcessed)
        _bProcessed = CPropertyPage::OnCommandHelp(wParam, lParam);

    return _bProcessed;

}  //*** CBasePropertyPage::OnCommandHelp()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::ConstructDefaultDirectory
//
//  Routine Description:
//      Get the name of the first partition from the first storage-class
//      resource on which this resource is dependent.
//
//  Arguments:
//      rstrDir     [OUT] Directory string.
//      idsFormat   [IN] Resource ID for the format string.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CBasePropertyPage::ConstructDefaultDirectory(
    OUT CString &   rstrDir,
    IN IDS          idsFormat
    )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    HRESOURCE               _hres = NULL;
    DWORD                   _sc = ERROR_SUCCESS;
    DWORD                   _cbDiskInfo = sizeof(CLUSPROP_DWORD)
                                        + sizeof(CLUSPROP_SCSI_ADDRESS)
                                        + sizeof(CLUSPROP_DISK_NUMBER)
                                        + sizeof(CLUSPROP_PARTITION_INFO)
                                        + sizeof(CLUSPROP_SYNTAX);
    PBYTE                   _pbDiskInfo = NULL;
    CLUSPROP_BUFFER_HELPER  _cbh;

    // Get the first partition for the resource..
    try
    {
        // Get the storage-class resource on which we are dependent.
        _hres = GetDependentStorageResource();
        if (_hres == NULL)
            return;

        // Get disk info.
        _pbDiskInfo = new BYTE[_cbDiskInfo];
        _sc = ClusterResourceControl(
                        _hres,
                        NULL,
                        CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO,
                        NULL,
                        0,
                        _pbDiskInfo,
                        _cbDiskInfo,
                        &_cbDiskInfo
                        );
        if (_sc == ERROR_MORE_DATA)
        {
            delete [] _pbDiskInfo;
            _pbDiskInfo = new BYTE[_cbDiskInfo];
            _sc = ClusterResourceControl(
                            _hres,
                            NULL,
                            CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO,
                            NULL,
                            0,
                            _pbDiskInfo,
                            _cbDiskInfo,
                            &_cbDiskInfo
                            );
        }  // if:  buffer too small
        if (_sc == ERROR_SUCCESS)
        {
            // Find the first partition.
            _cbh.pb = _pbDiskInfo;
            while (_cbh.pSyntax->dw != CLUSPROP_SYNTAX_ENDMARK)
            {
                if (_cbh.pSyntax->dw == CLUSPROP_SYNTAX_PARTITION_INFO)
                {
                    rstrDir.FormatMessage(
                                        idsFormat,
                                        _cbh.pPartitionInfoValue->szDeviceName
                                        );
                    break;
                }  // if:  found a partition
                _cbh.pb += sizeof(*_cbh.pValue) + ALIGN_CLUSPROP(_cbh.pValue->cbLength);
            }  // while:  not at end of list
        } // if:  no error getting disk info
        else
        {
            CNTException nte( _sc, IDS_ERROR_CONSTRUCTING_DEF_DIR );
            nte.ReportError();
        } // else:  error getting disk info
    }  // try
    catch (CMemoryException * _pme)
    {
        _pme->Delete();
    }  // catch:  CMemoryException

    CloseClusterResource(_hres);
    delete [] _pbDiskInfo;

}  //*** CBasePropertyPage::ConstructDefaultDirectory()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::GetDependentStorageResource
//
//  Routine Description:
//      Construct a default spool directory based on the drive on which
//      this resource is dependent and a default value for the directory.
//
//  Arguments:
//      phres       [OUT] Handle to dependent resource.
//
//  Return Value:
//      HRESOURCE for the open dependent resource, or NULL if error.
//
//--
/////////////////////////////////////////////////////////////////////////////
HRESOURCE CBasePropertyPage::GetDependentStorageResource(void)
{
    DWORD                       _sc = ERROR_SUCCESS;
    HRESENUM                    _hresenum;
    HRESOURCE                   _hres = NULL;
    DWORD                       _ires;
    DWORD                       _dwType;
    DWORD                       _cchName;
    DWORD                       _cchNameSize;
    LPWSTR                      _pszName = NULL;
    CLUS_RESOURCE_CLASS_INFO    _classinfo;
    DWORD                       _cbClassInfo;

    // Open the dependency enumerator.
    _hresenum = ClusterResourceOpenEnum(
                        Peo()->PrdResData()->m_hresource,
                        CLUSTER_RESOURCE_ENUM_DEPENDS
                        );
    if (_hresenum == NULL)
        return NULL;

    // Allocate a default size name buffer.
    _cchNameSize = 512;
    _pszName = new WCHAR[_cchNameSize];

    for (_ires = 0 ; ; _ires++)
    {
        // Get the name of the next resource.
        _cchName = _cchNameSize;
        _sc = ClusterResourceEnum(
                            _hresenum,
                            _ires,
                            &_dwType,
                            _pszName,
                            &_cchName
                            );
        if (_sc == ERROR_MORE_DATA)
        {
            delete [] _pszName;
            _cchNameSize = _cchName;
            _pszName = new WCHAR[_cchNameSize];
            _sc = ClusterResourceEnum(
                                _hresenum,
                                _ires,
                                &_dwType,
                                _pszName,
                                &_cchName
                                );
        }  // if:  name buffer too small
        if (_sc != ERROR_SUCCESS)
            break;

        // Open the resource.
        _hres = OpenClusterResource(Hcluster(), _pszName);
        if (_hres == NULL)
        {
            _sc = GetLastError();
            break;
        }  // if:  error opening the resource

        // Get the class of the resource.
        _sc = ClusterResourceControl(
                            _hres,
                            NULL,
                            CLUSCTL_RESOURCE_GET_CLASS_INFO,
                            NULL,
                            0,
                            &_classinfo,
                            sizeof(_classinfo),
                            &_cbClassInfo
                            );
        if (_sc != ERROR_SUCCESS)
        {
            CNTException nte( _sc, IDS_ERROR_GET_CLASS_INFO, _pszName, NULL, FALSE /*bAutoDelete*/ );
            nte.ReportError();
            continue;
        }

        // If this is a storage-class resource, we're done.
        if (_classinfo.rc == CLUS_RESCLASS_STORAGE)
            break;

        // Not storage-class resource.
        CloseClusterResource(_hres);
        _hres = NULL;
    }  // for each resource on which we are dependent

    // Handle errors.
    if ((_sc != ERROR_SUCCESS) && (_hres != NULL))
    {
        CloseClusterResource(_hres);
        _hres = NULL;
    }  // if:  error getting resource

    // Cleanup.
    ClusterResourceCloseEnum(_hresenum);
    delete [] _pszName;

    return _hres;

}  //*** CBasePropertyPage::GetDependentStorageResource()

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CBasePropertyPage::BGetClusterNetworkNameNode
//
//  Routine Description:
//      Get the node hosting the Network Name resource.
//
//  Arguments:
//      rstrNode    [OUT] - receives the node name
//
//  Return Value:
//      BOOL -- TRUE for success, FALSE for error
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CBasePropertyPage::BGetClusterNetworkNameNode( OUT CString & rstrNode )
{
    BOOL                    _bSuccess = TRUE;
    DWORD                   _sc;
    DWORD                   _dwFlag;
    DWORD                   _cbData;
    DWORD                   _ires;
    DWORD                   _dwType;
    DWORD                   _cchName = 0;
    DWORD                   _cchNameCurrent;
    LPWSTR                  _pszName = NULL;
    WCHAR                   _szResType[sizeof(CLUS_RESTYPE_NAME_NETNAME) / sizeof(WCHAR)];
    WCHAR                   _szNode[MAX_COMPUTERNAME_LENGTH+1];
    HCLUSENUM               _hclusenum = NULL;
    HRESOURCE               _hresource = NULL;
    CLUSTER_RESOURCE_STATE  _crs;
    CWaitCursor             _wc;

    try
    {
        // Open a cluster enumerator.
        _hclusenum = ClusterOpenEnum( Hcluster(), CLUSTER_ENUM_RESOURCE );
        if (_hclusenum == NULL)
        {
            ThrowStaticException( GetLastError() );
        }

        // Allocate an initial buffer.
        _cchName = 256;
        _pszName = new WCHAR[_cchName];

        // Loop through each resource.
        for ( _ires = 0 ; ; _ires++ )
        {
            // Get the next resource.
            _cchNameCurrent = _cchName;
            _sc = ClusterEnum( _hclusenum, _ires, &_dwType, _pszName, &_cchNameCurrent );
            if ( _sc == ERROR_MORE_DATA )
            {
                delete [] _pszName;
                _cchName = ++_cchNameCurrent;
                _pszName = new WCHAR[_cchName];
                _sc = ClusterEnum(_hclusenum, _ires, &_dwType, _pszName, &_cchNameCurrent);
            }  // if:  buffer too small
            if (_sc == ERROR_NO_MORE_ITEMS)
                break;
            if (_sc != ERROR_SUCCESS)
                ThrowStaticException(_sc);

            // Open the resource.
            _hresource = OpenClusterResource(Hcluster(), _pszName);
            if (_hresource == NULL)
                ThrowStaticException(GetLastError());

            // Get its flags.
            _sc = ClusterResourceControl(
                                    _hresource,
                                    NULL,
                                    CLUSCTL_RESOURCE_GET_FLAGS,
                                    NULL,
                                    0,
                                    &_dwFlag,
                                    sizeof(DWORD),
                                    &_cbData
                                    );
            if (_sc != ERROR_SUCCESS)
            {
                CNTException nte( _sc, IDS_ERROR_GET_RESOURCE_FLAGS, _pszName, NULL, FALSE /*bAutoDelete*/ );
                nte.ReportError();
                continue;
            }

            // If this isn't a core resource, skip it.
            if ((_dwFlag & CLUS_FLAG_CORE) == 0)
                continue;

            // Get its resource type name.  If the buffer is too small,
            // it isn't a Network Name resource so skip it.
            _sc = ClusterResourceControl(
                                    _hresource,
                                    NULL,
                                    CLUSCTL_RESOURCE_GET_RESOURCE_TYPE,
                                    NULL,
                                    0,
                                    _szResType,
                                    sizeof(_szResType),
                                    &_cbData
                                    );
            if (_sc == ERROR_MORE_DATA)
                continue;
            if (_sc != ERROR_SUCCESS)
                ThrowStaticException(_sc);

            // If this is a Network Name resource, get which node it is online on.
            if (lstrcmpiW(_szResType, CLUS_RESTYPE_NAME_NETNAME) == 0)
            {
                // Get the state of the resource.
                _crs = GetClusterResourceState(
                                    _hresource,
                                    _szNode,
                                    &_cchName,
                                    NULL,
                                    NULL
                                    );
                if (_crs == ClusterResourceStateUnknown)
                    ThrowStaticException(GetLastError());

                // Save the node name in the return argument.
                rstrNode = _szNode;

                break;
            }  // if:  Network Name resource

            CloseClusterResource( _hresource );
            _hresource = NULL;
        }  // for:  each resource

        if (rstrNode[0] == _T('\0'))
            ThrowStaticException(ERROR_FILE_NOT_FOUND, (IDS) 0);
    }  // try
    catch (CException * _pe)
    {
        _pe->ReportError();
        _pe->Delete();
        _bSuccess = FALSE;
    }  // catch:  CException

    delete [] _pszName;

    if ( _hresource != NULL )
    {
        CloseClusterResource( _hresource );
    } // if: resource is open
    if ( _hclusenum != NULL )
    {
        ClusterCloseEnum( _hclusenum );
    } // if: enumerator is open

    return _bSuccess;

}  //*** CBasePropertyPage::BGetClusterNetworkNameNode()