//////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 1999-2000 Microsoft Corporation
//
//  Module Name:
//      CClusDBForm.cpp
//
//  Description:
//      Contains the definition of the CClusDBForm class.
//
//  Documentation:
//      TODO: Add pointer to external documentation later.
//
//  Maintained By:
//      Vij Vasu (Vvasu) 08-MAR-2000
//
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
// Include Files
//////////////////////////////////////////////////////////////////////////////

// The precompiled header.
#include "pch.h"

// For cluster registry key and value names.
#include "clusudef.h"

// For ClRtlSetObjSecurityInfo() and other functions.
#include "clusrtl.h"

// The header for this file
#include "CClusDBForm.h"

// For the CBaseClusterForm class.
#include "CBaseClusterForm.h"

// For UUID related utilities.
#include "CUuid.h"

// For CEnableThreadPrivilege
#include "CEnableThreadPrivilege.h"

// For the CStr class.
#include "CStr.h"

// For sending status reports.
#include "CStatusReport.h"

// For DwRemoveDirectory()
#include "Common.h"

// For inet_ntoa
#include <winsock2.h>


//////////////////////////////////////////////////////////////////////////
// Macros definitions
//////////////////////////////////////////////////////////////////////////

// Section in the INF file that deals with populating the cluster hive.
#define CLUSDB_POPULATE_INF_SECTION_NAME                L"ClusDB_Form"

// A placeholder for the cluster group key name in the cluster registry.
#define CLUSREG_KEYNAME_CLUSTERGROUP_PLACEHOLDER        L"ClusterGroupGUIDPlaceholder"

// A placeholder for the cluster name resource key name in the cluster registry.
#define CLUSREG_KEYNAME_CLUSTERNAME_RES_PLACEHOLDER     L"ClusterNameResGUIDPlaceHolder"

// A placeholder for the cluster IP address resource key name in the cluster registry.
#define CLUSREG_KEYNAME_CLUSTERIP_RES_PLACEHOLDER       L"ClusterIPAddrResGUIDPlaceHolder"

// A placeholder for the local quorum resource key name in the cluster registry.
#define CLUSREG_KEYNAME_LOCALQUORUM_RES_PLACEHOLDER     L"LocalQuorumResGUIDPlaceHolder"


//////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusDBForm::CClusDBForm()
//
//  Description:
//      Constructor of the CClusDBForm class
//
//  Arguments:
//      pfaParentActionIn
//          Pointer to the base cluster action of which this action is a part.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      Any exceptions thrown by underlying functions
//
//--
//////////////////////////////////////////////////////////////////////////////
CClusDBForm::CClusDBForm( CBaseClusterForm * pfaParentActionIn )
    : BaseClass( pfaParentActionIn )
{

    BCATraceScope( "" );

    SetRollbackPossible( true );

} //*** CClusDBForm::CClusDBForm()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusDBForm::~CClusDBForm()
//
//  Description:
//      Destructor of the CClusDBForm class.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      Any exceptions thrown by underlying functions
//
//--
//////////////////////////////////////////////////////////////////////////////
CClusDBForm::~CClusDBForm( void )
{
    BCATraceScope( "" );

} //*** CClusDBForm::~CClusDBForm()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  void
//  CClusDBForm::Commit()
//
//  Description:
//      Create the cluster database. If anything goes wrong with the creation,
//      cleanup the tasks already done.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      Any that are thrown by the contained actions.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CClusDBForm::Commit( void )
{
    BCATraceScope( "" );

    // Call the base class commit method.
    BaseClass::Commit();

    //
    // Perform a ClusDB cleanup just to make sure that we do not use some files left over
    // from a previous install, aborted uninstall, etc.
    //

    LogMsg( "Cleaning up old cluster database files that may already exist before starting creation." );

    {
        CStatusReport   srCleanDB(
              PbcaGetParent()->PBcaiGetInterfacePointer()
            , TASKID_Major_Configure_Cluster_Services
            , TASKID_Minor_Cleaning_Up_Cluster_Database
            , 0, 1
            , IDS_TASK_CLEANINGUP_CLUSDB
            );

        // Send the next step of this status report.
        srCleanDB.SendNextStep( S_OK );

        CleanupHive();

        // Send the last step of this status report.
        srCleanDB.SendNextStep( S_OK );
    }

    try
    {
        // Create the cluster database
        Create();
        
    } // try:
    catch( ... )
    {
        // If we are here, then something went wrong with the create.

        BCATraceMsg( "Caught exception during commit." );

        //
        // Cleanup anything that the failed form might have done.
        // Catch any exceptions thrown during Cleanup to make sure that there 
        // is no collided unwind.
        //
        try
        {
            CleanupHive();
        }
        catch( ... )
        {
            //
            // The rollback of the committed action has failed.
            // There is nothing that we can do.
            // We certainly cannot rethrow this exception, since
            // the exception that caused the rollback is more important.
            //
            THR( E_UNEXPECTED );

            BCATraceMsg( "Caught exception during cleanup." );
            LogMsg( "THIS COMPUTER MAY BE IN AN INVALID STATE. An error has occurred during cleanup." );
        } // catch: all

        // Rethrow the exception thrown by commit.
        throw;

    } // catch: all

    // If we are here, then everything went well.
    SetCommitCompleted( true );

} //*** CClusDBForm::Commit()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  void
//  CClusDBForm::Rollback()
//
//  Description:
//      Unload the cluster hive and cleanup any associated files.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      Any that are thrown by the underlying functions.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CClusDBForm::Rollback( void )
{
    BCATraceScope( "" );

    // Call the base class rollback method. 
    BaseClass::Rollback();

    // Cleanup the cluster database.
    CleanupHive();

    SetCommitCompleted( false );

} //*** CClusDBForm::Rollback()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  void
//  CClusDBForm::Create()
//
//  Description:
//      Create the cluster database.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      CAssert
//          The parent action of this action is not CBaseClusterForm
//
//      CRuntimeError
//          If any of the APIs fail.
//
//      Any that are thrown by the called functions.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CClusDBForm::Create( void )
{
    BCATraceScope( "" );
    LogMsg( "Attempting to create the cluster database required to form a cluster." );

    DWORD               dwError = ERROR_SUCCESS;

    OBJECT_ATTRIBUTES   oaClusterHiveKey;
    OBJECT_ATTRIBUTES   oaClusterHiveFile;

    HRESULT             hrStatus = STATUS_SUCCESS;

    // Get the parent action pointer.
    CBaseClusterForm *  pcfClusterForm = dynamic_cast< CBaseClusterForm *>( PbcaGetParent() );

    CStatusReport       srCustomizingDB(
          PbcaGetParent()->PBcaiGetInterfacePointer()
        , TASKID_Major_Configure_Cluster_Services
        , TASKID_Minor_Form_Customizing_Cluster_Database
        , 0, 1
        , IDS_TASK_FORM_CUSTOMIZING_CLUSDB
        );

    // If the parent action of this action is not CBaseClusterForm
    if ( pcfClusterForm == NULL )
    {
        THROW_ASSERT( E_POINTER, "The parent action of this action is not CBaseClusterForm." );
    } // an invalid pointer was passed in.

    // Create the cluster hive.
    {
        CStatusReport   srCreatingDB(
              PbcaGetParent()->PBcaiGetInterfacePointer()
            , TASKID_Major_Configure_Cluster_Services
            , TASKID_Minor_Form_Creating_Cluster_Database
            , 0, 1
            , IDS_TASK_FORM_CREATING_CLUSDB
            );

        // Send the next step of this status report.
        srCreatingDB.SendNextStep( S_OK );

        // Create an empty cluster hive in the registry.
        CreateHive( pcfClusterForm );

        // Send the last step of this status report.
        srCreatingDB.SendNextStep( S_OK );
    }

    // Send the next step of this status report.
    srCustomizingDB.SendNextStep( S_OK );

    // Fill up the newly created hive.
    PopulateHive( pcfClusterForm );

    //
    // Create the quorum directory and set its security attributes.
    //
    do
    {
        HANDLE          hQuorumDirHandle;
        const WCHAR *   pcszQuorumDir = pcfClusterForm->RStrGetLocalQuorumDirectory().PszData();

        // First, remove the local quorum directory, if it exists.
        dwError = TW32( DwRemoveDirectory( pcszQuorumDir ) );
        if ( dwError != ERROR_SUCCESS )
        {
            BCATraceMsg2( "The local quorum directory '%s' already exists, but error %#x occurred trying to remove it.\n", pcszQuorumDir, dwError );
            LogMsg( "The local quorum directory '%s' already exists, but error %#x occurred trying to remove it.\n", pcszQuorumDir, dwError );
            break;
        } // if: we could not remove the local quorum directory

        if ( CreateDirectory( pcszQuorumDir, NULL ) == FALSE )
        {
            dwError = TW32( GetLastError() );
            BCATraceMsg1( "Failed to create directory '%ws'", pcszQuorumDir );
            break;
        } // if: the localquorum directory could not be created

        //
        // Enable the SE_BACKUP_PRIVILEGE and SE_RESTORE_PRIVILEGE.
        //
        // What we are doing here is that we are creating an object of
        // type CEnableThreadPrivilege. This object enables the privilege
        // in the constructor and restores it to its original state in the destructor.
        //

        CEnableThreadPrivilege etpAcquireBackupPrivilege( SE_BACKUP_NAME );
        CEnableThreadPrivilege etpAcquireRestorePrivilege( SE_RESTORE_NAME );

        //
        // Open a handle to the quorum directory. The calling thread should have SE_BACKUP_PRIVILEGE and
        // SE_RESTORE_PRIVILEGE enabled.
        //
        hQuorumDirHandle = CreateFile(
                              pcszQuorumDir
                            , GENERIC_ALL
                            , FILE_SHARE_WRITE
                            , NULL
                            , OPEN_EXISTING
                            , FILE_FLAG_BACKUP_SEMANTICS
                            , NULL 
                            );

        if ( hQuorumDirHandle == INVALID_HANDLE_VALUE )
        {
            // The directory does not exist. This is an error.
            dwError = TW32( GetLastError() );
            BCATraceMsg1( "The directory '%ws' does not exist.", pcszQuorumDir );
            break;
        } // if: the quorum directory does not exist.

        // Set the security for this directory.
        dwError = TW32( ClRtlSetObjSecurityInfo(
                              hQuorumDirHandle
                            , SE_FILE_OBJECT
                            , GENERIC_ALL
                            , GENERIC_ALL
                            , GENERIC_READ
                            ) );

        // First close the handle we opened.
        CloseHandle( hQuorumDirHandle );

        if ( dwError != ERROR_SUCCESS )
        {
            // ClRtlSetObjSecurityInfo() failed.
            BCATraceMsg( "ClRtlSetObjSecurityInfo() failed." );
            break;
        } // if: ClRtlSetObjSecurityInfo() failed
    }
    while( false ); // dummy do-while loop to avoid gotos.

    if ( dwError != ERROR_SUCCESS )
    {
        LogMsg( "Error %#08x occurred while trying to create the local quorum directory.", dwError );
        BCATraceMsg1( "Error %#08x occurred while trying to create the local quorum directory. Throwing exception.", dwError );
        THROW_RUNTIME_ERROR( HRESULT_FROM_WIN32( dwError ), IDS_ERROR_QUORUM_DIR_CREATE );
    } // if: something went wrong.

    // Send the last step of this status report.
    srCustomizingDB.SendNextStep( S_OK );

} //*** CClusDBForm::Create()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  void
//  CClusDBForm::PopulateHive()
//
//  Description:
//      Make the entries required by the cluster service in the hive.
//
//  Arguments:
//      pcfClusterFormIn
//          Pointer to the CBaseClusterForm object which contains this object.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      CRuntimeError
//          If any of the APIs fail.
//
//      Any that are thrown by the called functions.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CClusDBForm::PopulateHive( CBaseClusterForm * pcfClusterFormIn )
{
    BCATraceScope( "" );
    LogMsg( "Populating the cluster hive." );

    DWORD dwError = ERROR_SUCCESS;

    do
    {
        DWORD                   dwSDSize = 0;

        CRegistryKey rkClusterHiveRoot(
              HKEY_LOCAL_MACHINE
            , CLUSREG_KEYNAME_CLUSTER
            , KEY_ALL_ACCESS
            );

        if ( SetupInstallFromInfSection(
              NULL                                          // optional, handle of a parent window
            , pcfClusterFormIn->HGetMainInfFileHandle()     // handle to the INF file
            , CLUSDB_POPULATE_INF_SECTION_NAME              // name of the Install section
            , SPINST_REGISTRY                               // which lines to install from section
            , rkClusterHiveRoot.HGetKey()                   // optional, key for registry installs
            , NULL                                          // optional, path for source files
            , 0                                             // optional, specifies copy behavior
            , NULL                                          // optional, specifies callback routine
            , NULL                                          // optional, callback routine context
            , NULL                                          // optional, device information set
            , NULL                                          // optional, device info structure
            ) == FALSE
           )
        {
            dwError = TW32( GetLastError() );
            LogMsg( "Error %#08x occurred while trying to populate the cluster hive.", dwError );
            BCATraceMsg( "Setup API returned an error while trying to populate the cluster hive." );
            break;
        } // if: SetupInstallServicesFromInfSection failed

        LogMsg( "Basic hive structure created." );

        // Set the cluster name.
        rkClusterHiveRoot.SetValue(
              CLUSREG_NAME_CLUS_NAME
            , REG_SZ
            , reinterpret_cast< const BYTE * >( pcfClusterFormIn->RStrGetClusterNetBIOSName().PszData() )
            , ( pcfClusterFormIn->RStrGetClusterNetBIOSName().NGetLen() + 1 ) * sizeof( WCHAR )
            );

        //
        // Set the default cluster security descriptor.
        //
        {
            SECURITY_DESCRIPTOR *   psdSecurityDescriptor = NULL;

            // Form the security descriptor.
            dwError = TW32( ClRtlBuildDefaultClusterSD(
                                  pcfClusterFormIn->PSidGetServiceAccountSID()
                                , reinterpret_cast< void ** >( &psdSecurityDescriptor )
                                , &dwSDSize
                                ) );

            // Assign it to a smart pointer for safe release.
            CSmartResource<
                CHandleTrait< 
                      HLOCAL
                    , HLOCAL
                    , LocalFree
                    >
                >
                smartSD( reinterpret_cast< HLOCAL >( psdSecurityDescriptor ) );

            if ( dwError != ERROR_SUCCESS )
            {
                BCATraceMsg( "ClRtlBuildDefaultClusterSD() failed." );
                break;
            } // if: ClRtlBuildDefaultClusterSD() failed.

            // Set the security descriptor in the registry.
            rkClusterHiveRoot.SetValue(
                  CLUSREG_NAME_CLUS_SD
                , REG_BINARY
                , reinterpret_cast< const BYTE * >( psdSecurityDescriptor )
                , dwSDSize
                );

            // Set the NT4 version of the security descriptor in the registry.
            rkClusterHiveRoot.SetValue(
                  CLUSREG_NAME_CLUS_SECURITY
                , REG_BINARY
                , reinterpret_cast< const BYTE * >( psdSecurityDescriptor )
                , dwSDSize
                );
        }

        LogMsg( "Cluster common properties set." );

        //
        // Set the values under the HKLM\Cluster\Nodes key.
        //
        {
            DWORD   dwTemp;

            CRegistryKey rkNodesKey(
                  rkClusterHiveRoot.HGetKey()
                , CLUSREG_KEYNAME_NODES
                , KEY_WRITE
                );

            CRegistryKey rkThisNodeKey;

            // Create a subkey for this node.
            rkThisNodeKey.CreateKey(
                  rkNodesKey.HGetKey()
                , pcfClusterFormIn->PszGetNodeIdString()
                );

            // Set the node name.
            rkThisNodeKey.SetValue(
                  CLUSREG_NAME_NODE_NAME
                , REG_SZ
                , reinterpret_cast< const BYTE *>( pcfClusterFormIn->PszGetNodeName() )
                , ( pcfClusterFormIn->DwGetNodeNameLength() + 1 ) * sizeof( WCHAR )
                );

            // Set the node highest version.
            dwTemp = pcfClusterFormIn->DwGetNodeHighestVersion();
            rkThisNodeKey.SetValue(
                  CLUSREG_NAME_NODE_HIGHEST_VERSION
                , REG_DWORD
                , reinterpret_cast< const BYTE *>( &dwTemp )
                , sizeof( dwTemp )
                );

            // Set the node lowest version.
            dwTemp = pcfClusterFormIn->DwGetNodeLowestVersion();
            rkThisNodeKey.SetValue(
                  CLUSREG_NAME_NODE_LOWEST_VERSION
                , REG_DWORD
                , reinterpret_cast< const BYTE *>( &dwTemp )
                , sizeof( dwTemp )
                );
        }

        LogMsg( "Cluster node subtree customized." );

        // Customize the cluster group and the core resources.
        CustomizeClusterGroup( pcfClusterFormIn, rkClusterHiveRoot );

        // Flush the changes to the registry.
        RegFlushKey( rkClusterHiveRoot.HGetKey() );

        LogMsg( "Cluster hive successfully populated." );
    }
    while( false ); // dummy do-while loop to avoid gotos.

    if ( dwError != ERROR_SUCCESS )
    {
        LogMsg( "Error %#08x occurred while trying to populate the cluster hive.", dwError );
        BCATraceMsg1( "Error %#08x occurred while trying to populate the cluster hive. Throwing exception.", dwError );
        THROW_RUNTIME_ERROR( HRESULT_FROM_WIN32( dwError ), IDS_ERROR_CLUSDB_POPULATE_HIVE );
    } // if: something went wrong.

} //*** CClusDBForm::PopulateHive()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  void
//  CClusDBForm::CustomizeClusterGroup()
//
//  Description:
//      Customize the cluster group and the core resources.
//
//  Arguments:
//      pcfClusterFormIn
//          Pointer to the CBaseClusterForm object which contains this object.
//
//      rkClusterHiveRootIn
//          A CRegistryKey object representing the  root of the cluster hive.
//
//  Return Value:
//      None. 
//
//  Exceptions Thrown:
//      CRuntimeError
//          If any of the APIs fail.
//
//      Any that are thrown by the called functions.
//
//--
//////////////////////////////////////////////////////////////////////////////
void
CClusDBForm::CustomizeClusterGroup(
      CBaseClusterForm * pcfClusterFormIn
    , CRegistryKey &     rkClusterHiveRootIn
    )
{
    BCATraceScope( "" );

    // UUIDs of the cluster group and core resources.
    CUuid           uuidClusterGroupUuid;
    CUuid           uuidClusterIPAddressResourceUuid;
    CUuid           uuidClusterNameResourceUuid;
    CUuid           uuidLocalQuorumResourceUuid;

    // The lengths of the string versions of the above UUIDs.
    UINT            uiGroupUuidLen          = wcslen( uuidClusterGroupUuid.PszGetUuidString() );
    UINT            uiIPUuidLen             = wcslen( uuidClusterIPAddressResourceUuid.PszGetUuidString() );
    UINT            uiNameUuidLen           = wcslen( uuidClusterNameResourceUuid.PszGetUuidString() );
    UINT            uiLocalQuorumUuidLen    = wcslen( uuidLocalQuorumResourceUuid.PszGetUuidString() );

    UINT            uiUuidLen;

    // Length of the multisz string that can hold the above resource UUIDs.
    uiUuidLen = 
        ( ( uiIPUuidLen + 1 )
        + ( uiNameUuidLen + 1 )
        + ( uiLocalQuorumUuidLen + 1 )
        + 1
        );

    // Allocate a buffer for this multisz string.
    SmartSz  sszResourceUuids( new WCHAR[ uiUuidLen ] );

    // Was the memory successfully allocated?
    if ( sszResourceUuids.FIsEmpty() )
    {
        BCATraceMsg1( "Could not allocate %d character in memory. Throwing an exception.", uiUuidLen );
        THROW_RUNTIME_ERROR(
              E_OUTOFMEMORY
            , IDS_ERROR_CUSTOMIZE_CLUSTER_GROUP
            );
    } // if: memory allocation failed.

    //
    // Fill this buffer with the uuids of the core resources.
    //

    // Make sure that the IP address uuid is the first string in this multisz string.
    // This is buffer is reused during setting of the network name dependency on the 
    // IP address resource.
    CopyMemory( 
          sszResourceUuids.PMem()
        , uuidClusterIPAddressResourceUuid.PszGetUuidString()
        , ( uiIPUuidLen + 1 ) * sizeof( WCHAR )
        );

    CopyMemory( 
          sszResourceUuids.PMem() + uiIPUuidLen + 1
        , uuidClusterNameResourceUuid.PszGetUuidString()
        , ( uiNameUuidLen + 1 ) * sizeof( WCHAR )
        );

    CopyMemory( 
          sszResourceUuids.PMem() + uiIPUuidLen + uiNameUuidLen + 2 
        , uuidLocalQuorumResourceUuid.PszGetUuidString()
        , ( uiLocalQuorumUuidLen + 1 ) * sizeof( WCHAR )
        );

    ( sszResourceUuids.PMem() )[ uiUuidLen - 1 ] = L'\0';


    //
    // Customize the cluster group.
    //
    {
        CRegistryKey    rkClusterGroupKey(
              rkClusterHiveRootIn.HGetKey()
            , CLUSREG_KEYNAME_GROUPS L"\\" CLUSREG_KEYNAME_CLUSTERGROUP_PLACEHOLDER
            , KEY_WRITE
            );

        // Replace the placeholder for the cluster group key with an actual UUID.
        rkClusterGroupKey.RenameKey( uuidClusterGroupUuid.PszGetUuidString() );

        // Set the list of contained resources uuids.
        rkClusterGroupKey.SetValue(
              CLUSREG_NAME_GRP_CONTAINS
            , REG_MULTI_SZ
            , reinterpret_cast< const BYTE * >( sszResourceUuids.PMem() )
            , uiUuidLen * sizeof( WCHAR )
            );

        BCATraceMsg( "Cluster group customized." );
    }

    //
    // Customize the localquorum resource and update the HKLM\Quorum key.
    //
    {
        CRegistryKey    rkLocalQuorumResourceKey(
              rkClusterHiveRootIn.HGetKey()
            , CLUSREG_KEYNAME_RESOURCES L"\\" CLUSREG_KEYNAME_LOCALQUORUM_RES_PLACEHOLDER
            , KEY_WRITE
            );

        CRegistryKey    rkQuorumKey(
              rkClusterHiveRootIn.HGetKey()
            , CLUSREG_KEYNAME_QUORUM
            , KEY_WRITE
            );

        // Replace the placeholder for the localquorum resource key with an actual UUID.
        rkLocalQuorumResourceKey.RenameKey( uuidLocalQuorumResourceUuid.PszGetUuidString() );

        // Set the uuid of the localquorum resource under the HKLM\Quorum key
        rkQuorumKey.SetValue(
              CLUSREG_NAME_QUORUM_RESOURCE
            , REG_SZ
            , reinterpret_cast< const BYTE * >( uuidLocalQuorumResourceUuid.PszGetUuidString() )
            , ( uiLocalQuorumUuidLen + 1 ) * sizeof( WCHAR )
            );

        BCATraceMsg( "Localquorum resource customized." );
    }

    //
    // Set the cluster IP address resource private properties.
    //
    {
        CRegistryKey    rkClusterIPResourceKey(
              rkClusterHiveRootIn.HGetKey()
            , CLUSREG_KEYNAME_RESOURCES L"\\" CLUSREG_KEYNAME_CLUSTERIP_RES_PLACEHOLDER
            , KEY_WRITE
            );

        LPSTR           pszAddr;    // don't free!
        WCHAR           szIPBuffer[ 3 + 1 + 3 + 1 + 3 + 1 + 3 + 1 ]; // "xxx.xxx.xxx.xxx\0"
        DWORD           dwTemp;

        // Replace the placeholder for the cluster IP address resource key with an actual UUID.
        rkClusterIPResourceKey.RenameKey( uuidClusterIPAddressResourceUuid.PszGetUuidString() );

        // Create the cluster IP address parameters registry key.
        CRegistryKey    rkIPResParams;
        
        rkIPResParams.CreateKey(
              rkClusterIPResourceKey.HGetKey()
            , CLUSREG_KEYNAME_PARAMETERS
            );

        // Format the cluster IP address into a dotted quad.
        dwTemp = pcfClusterFormIn->DwGetIPAddress();
        pszAddr = inet_ntoa( * (struct in_addr *) &dwTemp );
        if ( pszAddr == NULL )
        {
            LogMsg( "inet_ntoa() returned NULL. Mapping it to E_OUTOFMEMORY." );
            THROW_RUNTIME_ERROR( E_OUTOFMEMORY, IDS_ERROR_CLUSDB_POPULATE_HIVE );
        }
        mbstowcs( szIPBuffer, pszAddr, strlen(pszAddr) + 1 );

        // Write the IP address to the registry.
        rkIPResParams.SetValue(
              CLUSREG_NAME_IPADDR_ADDRESS
            , REG_SZ
            , reinterpret_cast< const BYTE * >( szIPBuffer )
            , ( wcslen( szIPBuffer ) + 1 ) * sizeof(WCHAR)
            );

        // Format the cluster IP subnet mask into a dotted quad.
        dwTemp = pcfClusterFormIn->DwGetIPSubnetMask();
        pszAddr = inet_ntoa( * (struct in_addr *) &dwTemp );
        if ( pszAddr == NULL )
        {
            LogMsg( "inet_ntoa() returned NULL. Mapping it to E_OUTOFMEMORY." );
            THROW_RUNTIME_ERROR( E_OUTOFMEMORY, IDS_ERROR_CLUSDB_POPULATE_HIVE );
        }
        mbstowcs( szIPBuffer, pszAddr, strlen( pszAddr ) + 1 );

        // Write the IP subnet mask to the registry.
        rkIPResParams.SetValue(
              CLUSREG_NAME_IPADDR_SUBNET_MASK
            , REG_SZ
            , reinterpret_cast< const BYTE * >( szIPBuffer )
            , ( wcslen( szIPBuffer ) + 1 ) * sizeof(WCHAR)
            );

        // Write the IP address network to the registry.
        rkIPResParams.SetValue(
              CLUSREG_NAME_IPADDR_NETWORK
            , REG_SZ
            , reinterpret_cast< const BYTE * >( pcfClusterFormIn->RStrGetClusterIPNetwork().PszData() )
            , ( pcfClusterFormIn->RStrGetClusterIPNetwork().NGetLen() + 1 ) * sizeof( WCHAR )
            );

        BCATraceMsg( "IP address resource customized." );
    }

    //
    // Set the cluster network name resource private properties and dependencies.
    //
    {
        CRegistryKey    rkClusterNameResourceKey(
              rkClusterHiveRootIn.HGetKey()
            , CLUSREG_KEYNAME_RESOURCES L"\\" CLUSREG_KEYNAME_CLUSTERNAME_RES_PLACEHOLDER
            , KEY_WRITE
            );

        // Replace the placeholder for the network name resource key with an actual UUID.
        rkClusterNameResourceKey.RenameKey( uuidClusterNameResourceUuid.PszGetUuidString() );

        //
        // Indicate that the network name resource depends on the IP address resource.
        //
        ( sszResourceUuids.PMem() )[ uiIPUuidLen + 1 ] = L'\0';

        rkClusterNameResourceKey.SetValue(
              CLUSREG_NAME_RES_DEPENDS_ON
            , REG_MULTI_SZ
            , reinterpret_cast< const BYTE * >( sszResourceUuids.PMem() )
            , ( uiIPUuidLen + 2 ) * sizeof( WCHAR )
            );

        //
        // Create the cluster name parameters registry key.
        //
        CRegistryKey    rkNetNameResParams;
        
        rkNetNameResParams.CreateKey(
              rkClusterNameResourceKey.HGetKey()
            , CLUSREG_KEYNAME_PARAMETERS
            );

        // Write the cluster name to the registry.
        rkNetNameResParams.SetValue(
              CLUSREG_NAME_NETNAME_NAME
            , REG_SZ
            , reinterpret_cast< const BYTE * >( pcfClusterFormIn->RStrGetClusterNetBIOSName().PszData() )
            , ( pcfClusterFormIn->RStrGetClusterNetBIOSName().NGetLen() + 1 ) * sizeof( WCHAR )
            );

        // Store the UUID of the network name resource under HKLM\Cluster\ClusterNameResource.
        rkClusterHiveRootIn.SetValue(
              CLUSREG_NAME_CLUS_CLUSTER_NAME_RES
            , REG_SZ
            , reinterpret_cast< const BYTE * >( uuidClusterNameResourceUuid.PszGetUuidString() )
            , ( uiNameUuidLen + 1 ) * sizeof( WCHAR )
            );


        BCATraceMsg( "Network name resource customized." );
    }

    LogMsg( "Cluster group and core resources customized." );

} //*** CClusDBForm::CustomizeClusterGroup()