/////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1996-1999 Microsoft Corporation
//
// Module Name:
//    clusocm.cpp
//
//  Abstract:
//    CLUSOCM.DLL is an Optional Components Manager DLL for installation of
//    Microsoft Cluster Server. This file is the class implementation file
//    for the CClusocmApp class which implements the component setup procedure.
//
//  Author:
//    C. Brent Thomas (a-brentt)
//
//  Revision History:
//    1/29/98  original
//
// Notes:
//
//      If this DLL is dynamically linked against the MFC
//      DLLs, any functions exported from this DLL which
//      call into MFC must have the AFX_MANAGE_STATE macro
//      added at the very beginning of the function.
//
//      For example:
//
//      extern "C" BOOL PASCAL EXPORT ExportedFunction()
//      {
//          AFX_MANAGE_STATE(AfxGetStaticModuleState());
//          // normal function body here
//      }
//
//      It is very important that this macro appear in each
//      function, prior to any calls into MFC.  This means that
//      it must appear as the first statement within the
//      function, even before any object variable declarations
//      as their constructors may generate calls into the MFC
//      DLL.
//
//      Please see MFC Technical Notes 33 and 58 for additional
//      details.
//
//
/////////////////////////////////////////////////////////////////////////////
//

#include "stdafx.h"
#include "clusocm.h"
#include <regcomobj.h>
#include <StopService.h>
#include <RemoveNetworkProvider.h>
#include <IsClusterServiceRegistered.h>

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


/////////////////////////////////////////////////////////////////////////////
// Global Data


/////////////////////////////////////////////////////////////////////////////
// The one and only CClusocmApp object

CClusocmApp theApp;

/////////////////////////////////////////////////////////////////////////////
// Global Functions

// This is the function that OC Manager will call.
/////////////////////////////////////////////////////////////////////////////
//++
//
// ClusOcmSetupProc
//
// Routine Description:
//    This is the exported function that OC Manager calls. It merely passes
//    its' parameters to the CClusocmApp object and returns the results to
//    OC Manager.
//
// Arguments:
//    pvComponentId - points to a string that uniquely identifies the component
//                    to be set up to OC Manager.
//    pvSubComponentId - points to a string that uniquely identifies a sub-
//                       component in the component's hiearchy.
//    uxFunction - A numeric value indicating which function is to be perfomed.
//                 See ocmanage.h for the macro definitions.
//    uxParam1 - supplies a function specific parameter.
//    pvParam2 - points to a function specific parameter (which may be an
//               output).
//
// Return Value:
//    A function specific value is returned to OC Manager.
//
//--
/////////////////////////////////////////////////////////////////////////////

extern "C" DWORD WINAPI ClusOcmSetupProc( IN LPCVOID pvComponentId,
                                          IN LPCVOID pvSubComponentId,
                                          IN UINT uxFunction,
                                          IN UINT uxParam1,
                                          IN OUT PVOID pvParam2 )
{
	return theApp.ClusOcmSetupProc( pvComponentId,
                                   pvSubComponentId,
                                   uxFunction,
                                   uxParam1,
                                   pvParam2 );

} //*** exported ClusOcmSetupProc()


/////////////////////////////////////////////////////////////////////////////
// CClusocmApp

BEGIN_MESSAGE_MAP(CClusocmApp, CWinApp)
	//{{AFX_MSG_MAP(CClusocmApp)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CClusocmApp construction

// Default constructor

CClusocmApp::CClusocmApp()
{
   // TODO: add construction code here,
   // Place all significant initialization in InitInstance

   // Initialize the values of the HINF members in the SETUP_INIT_COMPONENT
   // structure data member.

   m_SetupInitComponent.OCInfHandle = (HINF) INVALID_HANDLE_VALUE;
   m_SetupInitComponent.ComponentInfHandle = (HINF) INVALID_HANDLE_VALUE;

   m_dwStepCount = 0L;
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// InitInstance
//
// Routine Description:
//
//
// Arguments:
//
//
//
// Return Value:
//
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::InitInstance( void )
{
   BOOL  fReturnValue = (BOOL) TRUE;

   m_hInstance = AfxGetInstanceHandle();

	// Initialize OLE libraries

	if (!AfxOleInit())
	{
      if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) ==
           (DWORDLONG) 0L )
      {
         AfxMessageBox( IDS_OLE_INIT_FAILED );
      }

      ClRtlLogPrint( "OLE initialization failed.  Make sure that the OLE libraries are the correct version.\n" );

		fReturnValue = (BOOL) FALSE;
	}

   return ( fReturnValue );
}


/////////////////////////////////////////////////////////////////////////////
//++
//
// CClusocmApp::ClusOcmSetupProc
//
//	Routine Description:
//    This function implements the Optional Components Manager component
//    setup procedure. This function is called by the exported function
//    of the same name, which in turn is called by OC Manager.
//
// Arguments:
//    pvComponentId - points to a string that uniquely identifies the component
//                    to be set up to OC Manager.
//    pvSubComponentId - points to a string that uniquely identifies a sub-
//                       component in the component's hiearchy.
//    uxFunction - A numeric value indicating which function is to be perfomed.
//                 See ocmanage.h for the macro definitions.
//    uxParam1 - supplies a function specific parameter.
//    pvParam2 - points to a function specific parameter (which may be an
//               output).
//
//
//	Return Value:
//    A function specific value is returned to OC Manager.
//
//--
/////////////////////////////////////////////////////////////////////////////

#pragma  warning ( disable : 4100 )     // disable "unreferenced formal parameter" warning
                                        // At this time (2/26/98) pvComponentId is unused.

DWORD CClusocmApp::ClusOcmSetupProc( IN LPCVOID pvComponentId,
                                     IN LPCVOID pvSubComponentId,
                                     IN UINT uxFunction,
                                     IN UINT uxParam1,
                                     IN OUT PVOID pvParam2 )
{
   AFX_MANAGE_STATE( AfxGetStaticModuleState() );     // As per the note in file header

   DWORD dwReturnValue;

   switch ( uxFunction )
   {
      case OC_PREINITIALIZE:

         // This is the first interface function that OC Manager calls.

         // OnOcPreinitialize enables the log file for clusocm.dll, verifies
         // that the OS version and Product Suite are correct, etc.

         dwReturnValue = OnOcPreinitialize();

         ClRtlLogPrint( "OnOcPreinitialize returned 0x%1!x!\n\n", dwReturnValue );

         break;

      case OC_INIT_COMPONENT:

         // This function is called soon after the component's setup dll is
         // loaded. This function provides initialization information to the
         // dll, instructs the dll to initialize itself, and provides a
         // mechanism for the dll to return information to OC Manager.

         // It may be desirable to verify that Microsoft Cluster Server is being
         // installed on NT Enterprise and to authenticate the user at this point.

         dwReturnValue = OnOcInitComponent((PSETUP_INIT_COMPONENT) pvParam2 );

         ClRtlLogPrint( "OnOcInitComponent returned 0x%1!x!\n\n", dwReturnValue );

         break;

      case OC_REQUEST_PAGES:

         // OC Manager is requesting a set of wizard pages from the dll.
         // As of 2/16/98 there are no wizard pages to supply.

         dwReturnValue = (DWORD) 0L;

         ClRtlLogPrint( "ClusOcmSetupProc is returning 0x%1!x! in response to OC_REQUEST_PAGES.\n\n",
                        dwReturnValue );

         break;

      case OC_QUERY_STATE:

         // Oc Manager is requesting the selection state of the component.
         // It does that at least three times.

         dwReturnValue = OnOcQueryState( (LPCTSTR) pvSubComponentId, uxParam1 );

         ClRtlLogPrint( "OnQueryState returned 0x%1!x!\n\n", dwReturnValue );

         break;

      case OC_SET_LANGUAGE:

         // OC Manager documentation states:
         //
         // "If a component does not support multiple languages, or if it does
         // not support the requested language, then it may ignore OC_SET_LANGUAGE."

         // I have replicated the functionality in Pat Styles' generic sample, ocgen.cpp.

         dwReturnValue = OnOcSetLanguage();

         ClRtlLogPrint( "OnOcSetLanguage returned 0x%1!x!.\n\n", dwReturnValue );

         break;

      case OC_CALC_DISK_SPACE:

         dwReturnValue = OnOcCalcDiskSpace( (LPCTSTR) pvSubComponentId,
                                            uxParam1,
                                            (HDSKSPC) pvParam2 );

         ClRtlLogPrint( "OnOcCalcDiskSpace returned 0x%1!x!.\n\n", dwReturnValue );

         break;

      case OC_QUEUE_FILE_OPS:

         if ( (pvSubComponentId != (PVOID) NULL) &&
              (*((LPCTSTR) pvSubComponentId) != _T('\0')) )
         {
            dwReturnValue = OnOcQueueFileOps( (LPCTSTR) pvSubComponentId,
                                              (HSPFILEQ) pvParam2 );

            ClRtlLogPrint( "OnOcQueueFileOps returned 0x%1!x!.\n\n", dwReturnValue );
         }
         else
         {
            dwReturnValue = (DWORD) NO_ERROR;
         }

         break;

      case OC_QUERY_CHANGE_SEL_STATE:

         dwReturnValue = OnOcQueryChangeSelState( (LPCTSTR) pvSubComponentId,
                                                  (UINT) uxParam1,
                                                  (DWORD) ((DWORD_PTR)pvParam2) );

         ClRtlLogPrint( "OnOnQueryChangeSelState returned 0x%1!x!.\n\n", dwReturnValue );

         break;

      case OC_COMPLETE_INSTALLATION:

         if ( (pvSubComponentId != (PVOID) NULL) &&
              (*((LPCTSTR) pvSubComponentId) != _T('\0')) )
         {
            dwReturnValue = OnOcCompleteInstallation( (LPCTSTR) pvSubComponentId );

            ClRtlLogPrint( "OnOcCompleteInstallation returned 0x%1!x!.\n\n", dwReturnValue );
         }
         else
         {
            dwReturnValue = (DWORD) NO_ERROR;
         }

         break;

      case OC_WIZARD_CREATED:

         // This interface function code is not documented. I have replicated the
         // functionality of Pat Styles' generic sample, ocgen.cpp.

         dwReturnValue = (DWORD) NO_ERROR;

         ClRtlLogPrint( "returning 0x%1!x! for OC_WIZARD_CREATED.\n\n", dwReturnValue );

         break;

      case OC_NEED_MEDIA:

         // This interface function code is not documented. I have replicated the
         // functionality of Pat Styles' generic sample, ocgen.cpp.

         dwReturnValue = (DWORD) FALSE;

         ClRtlLogPrint( "returning 0x%1!x! for OC_NEED_MEDIA.\n\n" );

         break;

      case OC_QUERY_SKIP_PAGE:

         // This interface function code is not documented. I have replicated the
         // functionality of Pat Styles' generic sample, ocgen.cpp.

         dwReturnValue = (DWORD) FALSE;

         ClRtlLogPrint( "returning 0x%1!x! for OC_QUERY_SKIP_PAGE.\n\n", dwReturnValue );

         break;

      case OC_QUERY_STEP_COUNT:

         // OC Manager uses this interface function code to request the number
         // of "steps", other than file operations, that must be performed to
         // install the component.

         if ( (pvSubComponentId != (PVOID) NULL) &&
              (*((LPCTSTR) pvSubComponentId) != _T('\0')) )
         {
            dwReturnValue = CalculateStepCount( (LPCTSTR) pvSubComponentId );

            if ( dwReturnValue != 0L )
            {
               // Update the data member.

               SetStepCount( dwReturnValue );
            }
         }
         else
         {
            dwReturnValue = (DWORD) NO_ERROR;
         }

         ClRtlLogPrint( "returning 0x%1!x! for OC_QUERY_STEP_COUNT.\n\n", dwReturnValue );

         break;

      case OC_CLEANUP:

         // This interface function code is the last to be processed.

         // OC Manager documentation states that the return value from processing
         // this interface function code is ignored.

         dwReturnValue = OnOcCleanup();

         break;

      case OC_ABOUT_TO_COMMIT_QUEUE:

         if ( (pvSubComponentId != (PVOID) NULL) &&
              (*((LPCTSTR) pvSubComponentId) != _T('\0')) )
         {
            dwReturnValue = OnOcAboutToCommitQueue( (LPCTSTR) pvSubComponentId );

            ClRtlLogPrint( "OnOcAboutToCommitQueue returned 0x%1!x!.\n\n", dwReturnValue );
         }
         else
         {
            dwReturnValue = (DWORD) NO_ERROR;
         }

         break;

      case OC_EXTRA_ROUTINES:

         dwReturnValue = (DWORD) NO_ERROR;

         ClRtlLogPrint( "returning 0x%1!x! for OC_EXTRA_ROUTINES.\n\n", dwReturnValue );

         break;

      default:

         dwReturnValue = (DWORD) NO_ERROR;

         ClRtlLogPrint( "ClusOcmSetupProc received an unknown function code, 0x%1!x!.\n",
                        uxFunction );
         ClRtlLogPrint( "returning 0x%1!x!.\n", dwReturnValue );

         break;
   }  // end of switch( uxFunction )

   return ( (DWORD) dwReturnValue );
}

#pragma warning ( default : 4100 )  // default behavior "unreferenced formal parameter" warning



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcPreinitialize
//
// Routine Description:
//    This funcion processes OC_PREINITIALIZE "messages" from Optional
//    Components Manager. This is the first function in the component
//    setup dll that OC Manager calls.
//
//    This function prevents clusocm.dll from running on down level operating
//    systems and unuthorized platforms.
//
//    Since Microsoft Cluster Server can only be installed on NT this function
//    selects the UNICODE character set.
//
// Arguments:
//    None
//
// Return Value:
//    (DWORD) OCFLAG_UNICODE - indicates success
//    (DWORD) 0 - indicates that the component cannot be initialized.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcPreinitialize( void )
{
   DWORD dwReturnValue;

   // The ClRtlInitialize function that opens a log file reads the ClusterLog
   // environment variable. This code segment reads that environment variable
   // and saves it so that it can be restored on exit.

   char *   pszOriginalLogFileName;

   pszOriginalLogFileName = getenv( "ClusterLog" );

   if ( pszOriginalLogFileName != (char *) NULL )
   {
      strcpy( m_szOriginalLogFileName, pszOriginalLogFileName );
   }
   else
   {
      *m_szOriginalLogFileName = (char) '\0';
   }

   // Build the string used to set the ClusterLog environment variable to point
   // to the log for clusocm.dll.

   char szClusterLogEnvString[MAX_PATH];

   // Initialize the string to be empty.

   *szClusterLogEnvString = '\0';

   // The log file for clusocm.dll will be located in the windows directory.
   // Note that if the path to the Windows directory cannot be obtained the
   // ClusterLog environment variable will be set empty. Then function ClRtlInitialize
   // will use the default log file name and location.

   TCHAR tszWindowsDirectory[MAX_PATH];

   UINT  uxReturnValue;

   uxReturnValue = GetWindowsDirectory( tszWindowsDirectory, (UINT) MAX_PATH );

   if ( uxReturnValue > 0 )
   {
      // The first part of the string to set the ClusterLog environment variable is
      // "ClusterLog=".

      strcpy( szClusterLogEnvString, "ClusterLog=" );

      // Convert the path to the Windows directory to MBCS.

      char  szWindowsDirectory[MAX_PATH];

      wcstombs( szWindowsDirectory, tszWindowsDirectory, MAX_PATH );

      // Append the path to the Windows directory.

      strcat( szClusterLogEnvString, szWindowsDirectory );

      // Append the log file name.

      strcat( szClusterLogEnvString, "\\clusocm.log" );
   }  // Was the path to the Windows directory obtained?

   // Set the ClusterLog environment variable. Note that _putenv returns 0
   // on success.

   if ( _putenv( szClusterLogEnvString ) != 0 )
   {
      dwReturnValue = GetLastError();  // just here for debugging
   }

   // Initialize the cluster runtime library. FALSE indicates that debug
   // output should not be redirected to a console window. If ClRtlInitialize
   // fails the calls to ClRtlLogPrint will be benign.

   ClRtlInitialize( FALSE );

   // Note: The date in the following statement is hardcoded on purpose. It is to
   //       indicate when the last time the logging statements were revised.

   ClRtlLogPrint( "\n\nCLUSOCM.LOG version 04/09/99 13:20.\n" );

   // Microsoft Cluster Server can only be installed on Windows NT Server Enterprise 5.0+.

   // This code segment checks the operating system version.

   // Since Microsoft Cluster Server can only be installed on NT indicate
   // to OC Manager that the UNICODE character set should be used.

   dwReturnValue = (DWORD) OCFLAG_UNICODE;

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcInitComponent
//
// Routine Description:
//    This funcion processes OC_INIT_COMPONENT "messages" from Optional
//    Components Manager.
//
//    This function is called soon after the component's setup dll is
//    loaded. This function provides initialization information to the
//    dll, instructs the dll to initialize itself, and provides a
//    mechanism for the dll to return information to OC Manager.
//
// Arguments:
//    pSetupInitComponent - points to a SETUP_INIT_COMPONENT structure
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
// Note:
//    The SETUP_INIT_COMPONENT structure pointed to by pSetupInitComponent
//    DOES NOT PERSIST. It is ncessary to save a copy in the CClusocmApp object.
//    This function initializes the SETUP_INIT_COMPONENT data member,
//    m_SetupInitComponent.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcInitComponent( IN OUT PSETUP_INIT_COMPONENT pSetupInitComponent )
{
   DWORD dwReturnValue;

   if ( pSetupInitComponent != (PSETUP_INIT_COMPONENT) NULL )
   {
      // Save the pointer to the SETUP_INIT_COMPONENT structure.

      m_SetupInitComponent = *pSetupInitComponent;

      // This code segment determines whether the version of OC Manager is
      // correct.

      if ( OCMANAGER_VERSION <= m_SetupInitComponent.OCManagerVersion )
      {
         // Indicate to OC Manager which version of OC Manager this dll expects.

         pSetupInitComponent->ComponentVersion = OCMANAGER_VERSION;

         // Update CClusocmApp's copy of the SETUP_INIT_COMPONENT structure.

         m_SetupInitComponent.ComponentVersion = OCMANAGER_VERSION;

         // Is the handle to the component INF valid ?

         if ( (m_SetupInitComponent.ComponentInfHandle !=
               (HINF) INVALID_HANDLE_VALUE) &&
              (m_SetupInitComponent.ComponentInfHandle !=
               (HINF) NULL) )
         {
            // The following call to SetupOpenAppendInfFile ensures that layout.inf
            // gets appended to clusocm.inf. This is required for several reasons
            // dictated by the Setup API. In theory OC Manager should do this, but
            // as per Andrew Ritz, 8/24/98, OC manager neglects to do it and it is
            // harmless to do it here after OC Manager is revised.

            // Note that passing NULL as the first parameter causes SetupOpenAppendInfFile
            // to append the file(s) listed on the LayoutFile entry in clusocm.inf.

            UINT uxStatus;

            SetupOpenAppendInfFile( NULL, m_SetupInitComponent.ComponentInfHandle,
                                    &uxStatus );

            if ( ClRtlIsOSValid() == TRUE )
            {
               // Since Microsoft Cluster Server can only be installed on NT indicate
               // to OC Manager that the UNICODE character set should be used.

               dwReturnValue = (DWORD) NO_ERROR;
            }
            else
            {
               // The operating system version is not correct.

               if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) ==
                    (DWORDLONG) 0L )
               {
                  CString  csMessage;

                  csMessage.LoadString( IDS_ERR_INCORRECT_OS_VERSION );

                  AfxMessageBox( csMessage );
               }

               DWORD dwErrorCode;

               dwErrorCode = GetLastError();

               ClRtlLogPrint( "ClRtlIsOSValid failed with error code %1!x!.\n",
                              dwErrorCode );

               dwReturnValue = (DWORD) ERROR_CALL_NOT_IMPLEMENTED;
            }
         }
         else
         {
            // Indicate failure.

            ClRtlLogPrint( "In OnOcInitComponent the ComponentInfHandle is bad.\n" );

            dwReturnValue = (DWORD) ERROR_CALL_NOT_IMPLEMENTED;
         }
      }
      else
      {
         // Indicate failure.

         ClRtlLogPrint( "An OC Manager version mismatch. Version %1!d! is required, version %2!d! was reported.\n",
                        OCMANAGER_VERSION, m_SetupInitComponent.OCManagerVersion );

         dwReturnValue = (DWORD) ERROR_CALL_NOT_IMPLEMENTED;
      }
   }
   else
   {
      ClRtlLogPrint( "In OnOcInitComponent the pointer to the SETUP_INIT_COMPONENT structure is NULL.\n" );

      dwReturnValue = (DWORD) ERROR_CALL_NOT_IMPLEMENTED;
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcQueryState
//
// Routine Description:
//    This funcion sets the original, current, and final selection states of the
//    Cluster service optional component.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    uxSelStateQueryType - indicates whether OC Manager is requesting the
//                          original, current, or final selection state of the
//                          component
//
// Return Value:
//    SubcompOn - indicates that the checkbox should be set
//    SubcompOff - indicates that the checkbox should be clear
//    SubcompUseOCManagerDefault - OC Manager should set the state of the checkbox
//                                 according to state information that is maintained
//                                 internally by OC Manager itself.
//
// Note:
//    By the time this function gets called OnOcInitComponent has already determined
//    that Terminal Services is not installed. It is only necessary to determine
//    whether Terminal Services is selected for installation.
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcQueryState( IN LPCTSTR ptszSubComponentId,
                                   IN UINT uxSelStateQueryType )
{
   DWORD dwReturnValue;
   eClusterInstallState eState;

   // For unattended setup the selection state of the Cluster service optional
   // component is specified in the answer file. Pat Styles recommends returning
   // SubcompUseOcManagerDefault in every case.

   // The following "if" statement forces SubcompUseOcManagerDefault to be returned
   // if the subcomponent name is invalid.

   if ( ptszSubComponentId != (LPCTSTR) NULL )
   {
      // The subcomponent ID is valid. For what purpose was this function called?

      switch ( uxSelStateQueryType )
      {
         case OCSELSTATETYPE_ORIGINAL:

            // OC Manager is requesting the intitial (original) selection state
            // of the component.

            // Is this a fresh install running under GUI mode setup ?

            if ( ( (m_SetupInitComponent.SetupData.OperationFlags &
                    (DWORDLONG) SETUPOP_STANDALONE) == (DWORDLONG) 0L ) &&
                 ( (m_SetupInitComponent.SetupData.OperationFlags &
                    (DWORDLONG) SETUPOP_NTUPGRADE) == (DWORDLONG) 0L ) )
            {
               // SETUPOP_STANDALONE flag clear means running under GUI mode setup.
               // SETUPOP_NTUPGRADE flag clear means not performing an upgrade.
               // Both flags clear means a fresh install in GUI MODE setup.

               // Return SubcompUseOcManagerDefault. As per AndrewR, for unattended
               // clean install return SubcompUseOcManagerDefault. For attended
               // clean install let OC Manager manage the selection state. Returning
               // SubcompUseOcManagerDefault will allow OC Manager to honor the
               // "modes" line in clusocm.inf if one is present.

               dwReturnValue = (DWORD) SubcompUseOcManagerDefault;

               ClRtlLogPrint( "OnOcQueryState is returning SubcompUseOcManagerDefault for ORIGINAL on a clean install.\n" );
            } // clean install?
            else
            {
               // This is standalone or upgrade. Is this an unattended clean install?

               if ( ( (m_SetupInitComponent.SetupData.OperationFlags &
                      (DWORDLONG) SETUPOP_BATCH) != (DWORDLONG) 0L ) &&
                    ( (m_SetupInitComponent.SetupData.OperationFlags &
                      (DWORDLONG) SETUPOP_NTUPGRADE) == (DWORDLONG) 0L ) )
               {
                  // This is UNATTENDED INSTALL with OCM running STANDALONE. Since
                  // this is STANDALONE, the MUST be an answer file.

                  // As per AndrewR for unattended clean install return SubcompUseOcManagerDefault.

                  dwReturnValue = (DWORD) SubcompUseOcManagerDefault;

                  ClRtlLogPrint( "ORIGINAL selection state: SubcompUseOcManagerDefault - UNATTENDED CLEAN install.\n" );
               }
               else
               {
                  // Either an upgrade is in progress or OC Manager is running standalone.
                  // The state of the checkbox depends on whether Cluster service has
                  // previously been installed.

                  // GetClusterInstallationState reports on the state of the registry value
                  // that records the state of the Cluster service installation on NT 5 machines.
                  // IsClusterServiceRegistered indicates whether the Cluster service is registered on
                  // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
                  // upgrading NT 4 machines, GetClusterInstallationState for NT 5 machines.

                  ClRtlGetClusterInstallState( NULL, &eState );

                  if ( ( eState != eClusterInstallStateUnknown ) ||
                       ( IsClusterServiceRegistered() == (BOOL) TRUE ) )
                  {
                     // No error was detected.

                     dwReturnValue = (DWORD) SubcompOn;

                     ClRtlLogPrint( "ORIGINAL selection state: SubcompOn - UPGRADE or STANDALONE - Cluster service previously installed.\n" );
                  }
                  else
                  {
                     // Some error occured.

                     dwReturnValue = (DWORD) SubcompOff;

                  ClRtlLogPrint( "ORIGINAL selection state: SubcompOff - UPGRADE or STANDALONE - Cluster service NOT previously installed.\n" );
                  }  // Were the Cluster service files installed?
               } // unattended?
            }  // Is this a fresh install?

            break;

         case OCSELSTATETYPE_CURRENT:

            // OC Manager is requesting the current selection state of the component.

            // For the cases of a "clean" install or when OC manager is running
            // standalone it is safe to let OC Manager use the default selection state
            // to determine the cuttent selection state. The UPGRADE case requires
            // specific handling. Is this an UPGRADE?

            if ( (m_SetupInitComponent.SetupData.OperationFlags &
                  (DWORDLONG) SETUPOP_NTUPGRADE) != (DWORDLONG) 0L )
            {
               // This is an upgrade. The state of the checkbox depends on whether
               // Cluster service has previously been installed.

               // GetClusterInstallationState reports on the state of the registry value
               // that records the state of the Cluster service installation on NT 5 machines.
               // IsClusterServiceRegistered indicates whether the Cluster service is registered on
               // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
               // upgrading NT 4 machines, GetClusterInstallationState for NT 5 machines.

               ClRtlGetClusterInstallState( NULL, &eState );

               if ( ( eState != eClusterInstallStateUnknown ) ||
                    ( IsClusterServiceRegistered() == (BOOL) TRUE ) )
               {
                  // No error was detected.

                  dwReturnValue = (DWORD) SubcompOn;

                  ClRtlLogPrint( "CURRENT selection state: SubcompOn - UPGRADE - Cluster service previously installed.\n" );
               }
               else
               {
               // Some error occured.

                  dwReturnValue = (DWORD) SubcompOff;

                  ClRtlLogPrint( "CURRENT selection state: SubcompOff - UPGRADE - Cluster service NOT previously installed.\n" );
               }  // Were the Cluster service files installed?
            }
            else
            {
               // This is either a "fresh" install or OC Manager is running standalone.
               dwReturnValue = (DWORD) SubcompUseOcManagerDefault;

               ClRtlLogPrint( "CURRENT selection state: SubcompUseOcManagerDefault - CLEAN install or STANDALONE.\n" );
            }  // Is this an UPGRADE?

            break;

         case OCSELSTATETYPE_FINAL:

            // OC Manager is requesting the final selection state of the component.

            // As per Pat Styles, this call to OnOcQueryState will occur AFTER
            // OnOcCompleteInstallation has been called. The registry operation that
            // creates the registry key that GetClusterInstallationState checks as
            // its' indicator of success is queued by OnOcCompleteInstallation.

            // Does this OC Manager Setup DLL believe it has completed without error?

            ClRtlGetClusterInstallState( NULL, &eState );
            if ( eState != eClusterInstallStateUnknown )
            {
               // No error was detected.

               dwReturnValue = (DWORD) SubcompOn;

               ClRtlLogPrint( "FINAL selection state: SubcompOn.\n" );
            }
            else
            {
               // Some error occured.

               dwReturnValue = (DWORD) SubcompOff;

               ClRtlLogPrint( "FINAL selection state: SubcompOff.\n" );
            }  // Were the Cluster service files installed?

            break;

         default:

            // This is an error condition.

            dwReturnValue = (DWORD) SubcompUseOcManagerDefault;

            ClRtlLogPrint( "Bad Param1 passed to OnOcQueryState.\n" );

            break;
      }  // end of switch ( uxSelStateQueryType )
   }
   else
   {
      // The subcomponent ID is invalid.

      dwReturnValue = (DWORD) SubcompUseOcManagerDefault;

      ClRtlLogPrint( "In OnOcQueryState the subcomponent ID is NULL.\n" );
   }  // Is the subcomponent ID legal?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcSetLanguage
//
// Routine Description:
//    This funcion indicates to OC Manager that clusocm.dll cannot handle
//    alternate langiages.
//
// Arguments:
//    None
//
// Return Value:
//    (DWORD) FALSE
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcSetLanguage( void )
{
   // Returning (DWORD) FALSE will indicate to OC Manager that the component
   // setup dll does not support the requested language.

   return (DWORD) FALSE;
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcCalcDiskSpace
//
// Routine Description:
//    This function processes the OC_CALC_DISK_SPACE "messages" from the
//    Optional Components Manager. It either adds or removes disk space
//    requirements from the Disk Space List maintained by OC Manager.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    uxAddOrRemoveFlag - Non-zero means that the component setup dll is being
//                        asked to add space requirements for a subcomponent to
//                        the disk space list. ZERO means that the component
//                        setup dll is being asked to remove space requirements
//                        for the subcomponent from the disk space list.
//    hDiskSpaceList - a HDSPSPC (typedefed in setupapi.h to PVOID)
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcCalcDiskSpace( IN LPCTSTR ptszSubComponentId,
                                      IN UINT uxAddOrRemoveFlag,
                                      IN OUT HDSKSPC hDiskSpaceList )
{
   DWORD dwReturnValue = NO_ERROR;

   // Is the subcomponent ID meaningfull?

   if ( ptszSubComponentId != (LPCTSTR) NULL )
   {
      // Is the handle to the component INF file meaningfull?

      if ( (m_SetupInitComponent.ComponentInfHandle !=
            (HINF) INVALID_HANDLE_VALUE) &&
           (m_SetupInitComponent.ComponentInfHandle !=
            (HINF) NULL) )
      {
         BOOL  fReturnValue;

         // The INF file is open. Now we can use it.

         // Associate the user defined Directory IDs in the [DestinationDirs] section
         // of clusocm.inf with actual directories.

         BOOL  fClusterServiceRegistered;

         fClusterServiceRegistered = IsClusterServiceRegistered();

         if ( SetDirectoryIds( fClusterServiceRegistered ) == (BOOL) TRUE )
         {
            if ( uxAddOrRemoveFlag != (UINT) 0 )
            {
               // Add disk space requirements to the file disk space list.

               fReturnValue =
                  SetupAddInstallSectionToDiskSpaceList( hDiskSpaceList,
                                                         m_SetupInitComponent.ComponentInfHandle,
                                                         NULL,
                                                         ptszSubComponentId,
                                                         0,
                                                         0 );

               if ( fReturnValue == (BOOL) FALSE )
               {
                  dwReturnValue = GetLastError();
               }

               ClRtlLogPrint( "SetupAddInstallSectionToDiskSpaceList returned 0x%1!x! to OnOcCalcDiskSpace.\n",
                              fReturnValue );
            }
            else
            {
               // Remove disk space requirements from the disk space list.

               fReturnValue =
                   SetupRemoveInstallSectionFromDiskSpaceList( hDiskSpaceList,
                                                               m_SetupInitComponent.ComponentInfHandle,
                                                               NULL,
                                                               ptszSubComponentId,
                                                               0,
                                                               0 );

               if ( fReturnValue == (BOOL) FALSE )
               {
                  dwReturnValue = GetLastError();
               }

               ClRtlLogPrint( "SetupRemoveInstallSectionFromDiskSpaceList returned 0x%1!x! to OnOcCalcDiskSpace.\n",
                              fReturnValue );
            }
         }
         else
         {
            dwReturnValue = GetLastError();

            ClRtlLogPrint( "In OnCalcDiskSpace the call to SetDirectoryIds failed.\n" );
            ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwReturnValue );
         }  // Were the directory Id associations made successfully?
      }
      else
      {
         ClRtlLogPrint( "In OnOcCalcDiskSpace ComponentInfHandle is bad.\n" );

         dwReturnValue = ERROR_FILE_NOT_FOUND;
      }
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcCompleteInstallation
//
// Routine Description:
//    This function processes the OC_COMPLETE_INSTALLATION "messages" from the
//    Optional Components Manager. It queues registry operations.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcCompleteInstallation( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue = NO_ERROR;

   if ( (m_SetupInitComponent.ComponentInfHandle != (HINF) INVALID_HANDLE_VALUE) &&
        (m_SetupInitComponent.ComponentInfHandle != (HINF) NULL) )
   {
      // The INF file handle is valid.

      BOOL  fOriginalSelectionState;
      BOOL  fCurrentSelectionState;

      // Is the subcomponent currently selected ?

      fCurrentSelectionState =
      m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                               (LPCTSTR) ptszSubComponentId,
                                                               (UINT) OCSELSTATETYPE_CURRENT );

      if ( fCurrentSelectionState == (BOOL) TRUE )
      {
         ClRtlLogPrint( "In OnOcCompleteInstallation the current selection state is TRUE.\n" );

         // The subcomponent is currently selected. Is this a fresh install ?

         if ( ( (m_SetupInitComponent.SetupData.OperationFlags &
                 (DWORDLONG) SETUPOP_STANDALONE) == (DWORDLONG) 0L ) &&
              ( (m_SetupInitComponent.SetupData.OperationFlags &
                 (DWORDLONG) SETUPOP_NTUPGRADE) == (DWORDLONG) 0L ) )
         {
            ClRtlLogPrint( "In OnOcCompleteInstallation this is a GUI mode clean install.\n" );

            // SETUPOP_STANDALONE flag clear means running under GUI mode setup.
            // SETUPOP_NTUPGRADE flag clear means not performing an upgrade.
            // Both flags clear means a fresh install. Do not check for a selection
            // state transition.

            dwReturnValue = CompleteInstallingClusteringService( (LPCTSTR) ptszSubComponentId );

            ClRtlLogPrint( "CompleteInstallingClusteringService returned 0x%1!x! to OnOcCompleteInstallation.\n",
                           dwReturnValue );
         }
         else
         {
            // This is not a GUI mode fresh install. Is it an UPGRADE ?

            if ( (m_SetupInitComponent.SetupData.OperationFlags &
                  (DWORDLONG) SETUPOP_NTUPGRADE) != (DWORDLONG) 0L )
            {
               ClRtlLogPrint( "In OnOcCompleteInstallation - This is an UPGRADE.\n" );

               // Complete the process of upgrading Cluster service.

               dwReturnValue = CompleteUpgradingClusteringService( (LPCTSTR) ptszSubComponentId );

               ClRtlLogPrint( "CompleteUpgradingClusteringService returned 0x%1!x! to OnOnCompleteInstallation.\n",
                              dwReturnValue );
            }
            else
            {
               ClRtlLogPrint( "In OnOcCompleteinstallation - this is STANDALONE.\n" );

               if ( (m_SetupInitComponent.SetupData.OperationFlags &
                     (DWORDLONG) SETUPOP_BATCH) != (DWORDLONG) 0L )
               {
                  ClRtlLogPrint( "In OnOcCompleteInstallation this is UNATTENDED STANDALONE and Cluster service is selected.\n" );
               }

               // This is not an upgrade. That means Add/Remove Programs must be
               // running.

               // Check for a selection state transition.

               fOriginalSelectionState =
               m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                        ptszSubComponentId,
                                                                        (UINT) OCSELSTATETYPE_ORIGINAL );

               if ( fCurrentSelectionState != fOriginalSelectionState )
               {
                   ClRtlLogPrint( "In OnOcCompleteInstallation a selection state transition has been detected.\n" );

                   dwReturnValue = CompleteStandAloneInstallationOfClusteringService( (LPCTSTR) ptszSubComponentId );

                   ClRtlLogPrint( "CompleteStandAloneInstallationOfClusteringService returned 0x%1!x! to OnOcCompleteInstallation.\n",
                                  dwReturnValue );
               }
               else
               {
                  ClRtlLogPrint( "In OnOcCompleteInstallation since no selection state transition was detected there is nothing to do.\n" );

                  // The selection state has not been changed. Perform no action.
                  dwReturnValue = (DWORD) NO_ERROR;
               } // was there a selection state transition?
            }  // is this an UPGRADE?
         }  // is this a fresh install ?
      }
      else
      {
         ClRtlLogPrint( "In OnOcCompleteInstallation the current selection state is FALSE.\n" );

         // The subcomponent is not selected. Is OC Manager running stand-alone ?
         // If not, i.e. if GUI mode setup is running, there is nothing to do.

         if ( (m_SetupInitComponent.SetupData.OperationFlags &
               (DWORDLONG) SETUPOP_STANDALONE) != (DWORDLONG) 0L )
         {
            ClRtlLogPrint( "In OnOcCompleteInstallation this is STANDALONE.\n" );

            // SETUPOP_STANDALONE set implies GUI mode setup is not running. If
            // there was a selection state change (to unselected) then delete
            // registry entries.

            fOriginalSelectionState =
            m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                     ptszSubComponentId,
                                                                     (UINT) OCSELSTATETYPE_ORIGINAL );

            // Has there been a selection state transition ?

            if ( fCurrentSelectionState != fOriginalSelectionState )
            {
               ClRtlLogPrint( "In OnOcCompleteInstallation a selection state transition has been detected.\n" );

               // Complete the process of uninstalling Cluster service.

               dwReturnValue = CompleteUninstallingClusteringService( (LPCTSTR) ptszSubComponentId );

               ClRtlLogPrint( "CompleteUninstallingClusteringService returned 0x%1!x! to OnOcCompleteInstallation.\n",
                              dwReturnValue );
            }
            else
            {
               // The selection state has not been changed. Perform no action.

               dwReturnValue = (DWORD) NO_ERROR;

               ClRtlLogPrint( "In OnOcCompleteInstallation a selection state transition was not detected\n" );
               ClRtlLogPrint( "so there is nothing to do.\n" );
            }     // Was there a selection state transition ?
         }
         else
         {
            // GUI mode setup is running. Perform no action.

            dwReturnValue = (DWORD) NO_ERROR;

            ClRtlLogPrint( "In OnOcCompleteInstallation GUI mode setup is running and the\n" );
            ClRtlLogPrint( "component is not selected so there is nothing to do.\n" );
         }     // Is GUI mode setup running ?
      }     // Is the subcomponent currently selected ?
   }
   else
   {
      dwReturnValue = ERROR_FILE_NOT_FOUND;

      ClRtlLogPrint( "In OnOcCompleteInstallation the handle to the component INF file is bad.\n" );
   }     // Is the handle to the component INF file valid ?

   ClRtlLogPrint( "OnOcCompleteInstallation is preparing to return 0x%1!x!.\n", dwReturnValue );

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcQueryChangeSelState
//
// Routine Description:
//    This function processes the OC_QUERY_CHANGE_SEL_STATE "messages" from the
//    Optional Components Manager.
//
// Arguments:
//    pvComponentId - points to a string that uniquely identifies the component
//                    to be set up to OC Manager.
//    pvSubComponentId - points to a string that uniquely identifies a sub-
//                       component in the component's hiearchy.
//    uxFunction - OC_QUERY_CHANGE_SEL_STATE - See ocmanage.h for the macro definitions.
//    uxParam1 - proposed state of the subcomponent
//               zero means unselected
//               non-zero means selected
//    pvParam2 - Flags
//
// Return Value:
//    (DWORD) TRUE - the proposed selection state should be accepted
//    (DWORD) FALSE - the proposed selection state should be rejected
//
// Note:
//    As currently implemented on 2/25/1998, this function will disallow all
//    changes to the selection state of Cluster service during an upgrade to a
//    machine on which Cluster service has previously been installed.
//
//    I have assumed that if the SETUPOP_NTUPGRADE flag is set that GUI mode
//    setup is running because there is no way to perform and upgrade other
//    than running NT setup.
//
//    Cluster service and Terminal Services are mutually exclusive. By the
//    time that this function gets called, OnOcInitComponent has already determined
//    that Terminal Services is not installed. Therefore it is only necessary to
//    determine whether Terminal Services is selected for installation.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcQueryChangeSelState( IN LPCTSTR ptszSubComponentId,
                                            IN UINT uxProposedSelectionState,
                                            IN DWORD dwFlags )
{
   DWORD dwReturnValue = (DWORD) TRUE;

   eClusterInstallState eState;

   // Is an upgrade in progress ?

   if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_NTUPGRADE) !=
        (DWORDLONG) 0L )
   {
      ClRtlLogPrint( "In OnOnQueryChangeSelState this is an UPGRADE. Selection state transitions are not allowed.\n" );

      // Since this is an UPGRADE the selection state cannot be changed.
      // Return FALSE to disallow selection state changes.

      dwReturnValue = (DWORD) FALSE;
   }
   else
   {
      if ( dwReturnValue == (DWORD) TRUE )
      {
         // Either Cluster service is being deselected, which implies that the
         // selection state of Terminal Services is inconsequential, or Terminal
         // Services is not selected for installation.

         // Is GUI mode setup running?

         if ( (m_SetupInitComponent.SetupData.OperationFlags &
              (DWORDLONG) SETUPOP_STANDALONE) == (DWORDLONG) 0L )
         {
            ClRtlLogPrint( "In OnOcQueryChangeSelState this is NOT STANDALONE.\n" );

            // SETUPOP_STANDALONE clear means GUI mode setup is running.
            // In conjunction with SETUPOP_NTUPGRADE clear it means that
            // a fresh NT installation is in progress.

            // It is possible for the user to request that a fresh install target
            // the directory for an existing NT installation. In that event, as per
            // David P., if Cluster Server has been installed, the user should
            // not be allowed to deselect the Cluster Server component.

            // GetClusterInstallationState reports on the state of the registry value
            // that records the state of the Cluster service installation on NT 5 machines.
            // IsClusterServiceRegistered indicates whether the Cluster service is registered on
            // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
            // upgrading NT 4 machines, GetClusterInstallationState for NT 5 machines.

            ClRtlGetClusterInstallState( NULL, &eState );
            if ( ( eState != eClusterInstallStateUnknown ) ||
                 ( IsClusterServiceRegistered() == (BOOL) TRUE ) )
            {
               // Disallow deselection of the cluster component(s).

               dwReturnValue = (DWORD) FALSE;

               ClRtlLogPrint( "In OnOcQueryChangeSelState since Clustering Server has previously\n" );
               ClRtlLogPrint( "been installed selection state transitions are not allowed.\n" );
            }
            else
            {
               // Allow the selection state to be changed.

               dwReturnValue = (DWORD) TRUE;

               ClRtlLogPrint( "In OnOcQueryChangeSelState since Clustering Server has never\n" );
               ClRtlLogPrint( "been installed selection state transitions are allowed.\n" );
            }
         } // GUI mode setup running?
         else
         {
            // This is a standalone operation. Allow the selection state to be changed.

            dwReturnValue = (DWORD) TRUE;

            ClRtlLogPrint( "In OnOcQueryChangeSelState this is STANDALONE so selection state transitions are allowed.\n" );
         } // GUI mode setup running?
      }
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// PerformRegistryOperations
//
// Routine Description:
//    This function performs the registry operations, both the AddReg and DelReg
//    in the section indicated by ptszSectionName are processed.
//    registry entries.
//
// Arguments:
//    hInfHandle - a handle to the component INF file.
//    ptszSectionName - points to a string containing the name of a section in
//                      the INF file.
//
// Return Value:
//    (DWORD) NO_ERROR - indicated success
//    Any other retuen value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::PerformRegistryOperations( HINF hInfHandle,
                                              LPCTSTR ptszSectionName )
{
   DWORD dwReturnValue;

   // Install ... perform the registry operations.

   dwReturnValue = SetupInstallFromInfSection( NULL,                 // hwndOwner
                                               m_SetupInitComponent.ComponentInfHandle,     // inf handle
                                               ptszSectionName,      // name of section
                                               SPINST_REGISTRY,      // operation flags
                                               NULL,                 // relative key root
                                               NULL,                 // source root path -
                                                                     // irrelevant for registry operations
                                               0,                    // copy flags
                                               NULL,                 // callback routine
                                               NULL,                 // callback routine context
                                               NULL,                 // device info set
                                               NULL );               // device info struct

   // Were the registry operations performed successfully?

   if ( dwReturnValue == (DWORD) TRUE )
   {
      dwReturnValue = (DWORD) NO_ERROR;
   }
   else
   {
      dwReturnValue = GetLastError();
   }  // Were the registry operations performed successfully?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// UninstallRegistryOperations
//
// Routine Description:
//    This function queues the registry operations that delete cluster related
//    registry entries on UNINSTALL.
//
// Arguments:
//    hInfHandle - a handle to the component INF file.
//    ptszSubComponentId - points to a string containing the name of the subcomponent.
//
//
// Return Value:
//    (DWORD) NO_ERROR - indicated success
//    Any other retuen value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::UninstallRegistryOperations( IN HINF hInfHandle,
                                                IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;

   BOOL        fReturnValue;

   INFCONTEXT  InfContext;

   // There is an entry called "Uninstall" in the [Cluster] section of
   // cluster.inf. That entry provides the name of an "install" section
   // that should be substituted for the [Cluster] section when uninstalling.
   // The following function locates that line so it can be read.

   CString  csSectionName;

   csSectionName = UNINSTALL_INF_KEY;

   fReturnValue = SetupFindFirstLine( m_SetupInitComponent.ComponentInfHandle,
                                      ptszSubComponentId,
                                      csSectionName,
                                      &InfContext );

   if ( fReturnValue == (BOOL) TRUE )
   {
      // Read the name of the replacement section.

      TCHAR  tszReplacementSection[256];     // Receives the section name

      fReturnValue = SetupGetStringField( &InfContext,
                                          1,       // there should be a single field
                                          tszReplacementSection,
                                          sizeof( tszReplacementSection ) / sizeof( TCHAR ),
                                          NULL );

      if ( fReturnValue == (BOOL) TRUE )
      {
         // Remove the registry keys.

         dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                    tszReplacementSection );

         ClRtlLogPrint( "PerformRegistryOperations returned 0x%1!x! to UninstallRegistryOperations.\n",
                        dwReturnValue );
      }
      else
      {
         ClRtlLogPrint( "UninstallDelRegistryOperations could not read the INF file.\n" );

         dwReturnValue = (DWORD) ERROR_FILE_NOT_FOUND;
      }
   }
   else
   {
      ClRtlLogPrint( "UninstallDelRegistryOperations could not read the INF file.\n" );

      dwReturnValue = (DWORD) ERROR_FILE_NOT_FOUND;
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// QueueInstallFileOperations
//
// Routine Description:
//    This function queues the file operations that install cluster related files.
//
// Arguments:
//    hInfHandle - a handle to the component INF file.
//    ptszSubComponentId - points to a string containing the name of the subcomponent.
//
//
// Return Value:
//    (DWORD) NO_ERROR - indicated success
//    Any other retuen value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::QueueInstallFileOperations( IN HINF hInfHandle,
                                               IN LPCTSTR ptszSubComponentId,
                                               IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue;
   CString  csSectionName;


   // Dummy do-while loop to avoid gotos.
   do
   {
       // As per Pat Styles on 7/16/98 pass NULL in the SourcePath parameter.
       dwReturnValue = SetupInstallFilesFromInfSection(
                          hInfHandle,               // handle to the INF file
                          NULL,                     // optional, layout INF handle
                          hSetupFileQueue,          // handle to the file queue
                          ptszSubComponentId,       // name of the Install section
                          NULL,                     // optional, root path to source files
                          SP_COPY_NEWER             // optional, specifies copy behavior
                          );

       ClRtlLogPrint( "The first call to SetupInstallFilesFromInfSection returned 0x%1!x! to QueueInstallFileOperations.\n",
                      dwReturnValue );

       // Was the operation successful ?
       if ( dwReturnValue == (DWORD) FALSE )
       {
           dwReturnValue = GetLastError();
           break;
       }

       if ( (m_SetupInitComponent.SetupData.OperationFlags &
          (DWORDLONG) SETUPOP_NTUPGRADE) == (DWORDLONG) 0L )
       {
           dwReturnValue = (DWORD) NO_ERROR;
           // This is not an UPGRADE.
           break;
       }

       // If this is an UPGRADE (from NT 4.0) then queue the file operations
       // specified in the [Upgrade] section.

       ClRtlLogPrint( "In QueueInstallFileOperations this is an UPGRADE.\n" );


       // Copy the replace-only files. Some files like IISCLUS3.DLL need to be copied
       // on an upgrade only if they already existed before the upgrade.
       // This is what I learnt from Brent (a-brentt) on 7/15/1999.
       csSectionName = UPGRADE_REPLACEONLY_INF_KEY;

       // As per Pat Styles on 7/16/98 pass NULL in the SourcePath parameter.
       dwReturnValue = SetupInstallFilesFromInfSection(
                          hInfHandle,               // handle to the INF file
                          NULL,                     // optional, layout INF handle
                          hSetupFileQueue,          // handle to the file queue
                          csSectionName,            // name of the Install section
                          NULL,                     // optional, root path to source files
                          SP_COPY_REPLACEONLY       // optional, specifies copy behavior
                          );

       ClRtlLogPrint( 
            "The first call to SetupInstallFilesFromInfSection returned 0x%1!x! to QueueInstallFileOperations.\n",
             dwReturnValue 
             );

       if ( dwReturnValue == (DWORD) FALSE )
       {
           dwReturnValue = GetLastError();
           break;
       }

       // The replace only copy was successful. Now do the normal CopyFiles and DelFiles
       // subsections.
       csSectionName = UPGRADE_INF_KEY;


       // This function processes the CopyFiles subsection under the UPGRADE_INF_KEY section.
       dwReturnValue = SetupInstallFilesFromInfSection(
                          hInfHandle,               // handle to the INF file
                          NULL,                     // optional, layout INF handle
                          hSetupFileQueue,          // handle to the file queue
                          csSectionName,            // name of the Install section
                          NULL,                     // optional, root path to source files
                          SP_COPY_NEWER             // optional, specifies copy behavior
                          );

       ClRtlLogPrint( 
            "The second call to SetupInstallFilesFromInfSection returned 0x%1!x! to QueueInstallFileOperations.\n",
             dwReturnValue 
             );

       if ( dwReturnValue == (DWORD) FALSE )
       {
           dwReturnValue = GetLastError();
           break;
       }

       // This function processes the DelFiles subsection under the UPGRADE_INF_KEY section.
       dwReturnValue = SetupQueueDeleteSection(
                            hSetupFileQueue,        // handle to the file queue
                            hInfHandle,             // handle to the INF file containing the [DestinationDirs] section
                            hInfHandle,             // handle to the INF file
                            csSectionName           // INF section that lists the files to delete
                            );

       ClRtlLogPrint( 
            "The call to SetupQueueDeleteSection returned 0x%1!x! to QueueInstallFileOperations.\n", dwReturnValue 
             );

       if ( dwReturnValue == (DWORD) FALSE )
       {
           dwReturnValue = GetLastError();
           break;
       }

       dwReturnValue = (DWORD) NO_ERROR;
   }
   while ( FALSE ); // dummy do-while loop to avoid gotos.

   if ( dwReturnValue == (DWORD) NO_ERROR )
   {
       ClRtlLogPrint( "QueueInstallFileOperations compeleted successfully.\n" );
   }
   else
   {
       ClRtlLogPrint( "Error in QueueInstallFileOperations.\n" );
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// QueueRemoveFileOperations
//
// Routine Description:
//    This function queues the file operations that delete cluster related files.
//
// Arguments:
//    hInfHandle - a handle to the component INF file.
//    ptszSubComponentId - points to a string containing the name of the subcomponent.
//
//
// Return Value:
//    (DWORD) NO_ERROR - indicated success
//    Any other return value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::QueueRemoveFileOperations( IN HINF hInfHandle,
                                              IN LPCTSTR ptszSubComponentId,
                                              IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue;

   BOOL        fReturnValue;

   INFCONTEXT  InfContext;

   // There is an entry called "Uninstall" in the [Cluster] section of
   // cluster.inf. That entry provides the name of an "install" section
   // that should be substituted for the [Cluster] section when uninstalling.
   // The following function locates that line so it can be read.

   CString  csSectionName;

   csSectionName = UNINSTALL_INF_KEY;

   fReturnValue = SetupFindFirstLine( hInfHandle,
                                      ptszSubComponentId,
                                      csSectionName,
                                      &InfContext );

   if ( fReturnValue == (BOOL) TRUE )
   {
      // Read the name of the replacement section.

      TCHAR  tszReplacementSection[256];     // Receives the section name

      fReturnValue = SetupGetStringField( &InfContext,
                                          1,       // there should be a single field
                                          tszReplacementSection,
                                          sizeof( tszReplacementSection ) / sizeof( TCHAR ),
                                          NULL );

      if ( fReturnValue == (BOOL) TRUE )
      {
         // Remove the files.

         dwReturnValue = SetupInstallFilesFromInfSection( hInfHandle,
                                                          (HINF) NULL,              // No layout file
                                                          hSetupFileQueue,
                                                          (LPCTSTR) tszReplacementSection,
                                                          (LPCTSTR) NULL,  // SourceRootPath is irrelevant
                                                          (UINT) 0 );

         ClRtlLogPrint( "SetupInstallFilesFromInfSection returned 0x%1!x! to QueueRemoveFilesOperations.\n",
                        dwReturnValue );

         // Was the operation successfull ?

         if ( dwReturnValue == (DWORD) TRUE )
         {
            dwReturnValue = (DWORD) NO_ERROR;
         }
         else
         {
            dwReturnValue = GetLastError();
         }
      }
      else
      {
         dwReturnValue = (DWORD) ERROR_FILE_NOT_FOUND;

         ClRtlLogPrint( "QueueRemoveFileOperations was unable to read the [Uninstall] section in the INF file.\n" );
      }
   }
   else
   {
      dwReturnValue = (DWORD) ERROR_FILE_NOT_FOUND;

      ClRtlLogPrint( "QueueRemoveFileOperations was unable to locate the [Uninstall] section in the INF file.\n" );
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcAboutToCommitQueue
//
// Routine Description:
//    If clusocm.dll is performing an uninstall, this function unregisters the
//    cluster services.
//
//    if clusocm.dll is performing an install or an upgrade this function does
//    nothing.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcAboutToCommitQueue( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;

   if ( (m_SetupInitComponent.ComponentInfHandle != (HINF) INVALID_HANDLE_VALUE) &&
        (m_SetupInitComponent.ComponentInfHandle != (HINF) NULL) )
   {
      // The INF file handle is valid.

      BOOL  fOriginalSelectionState;
      BOOL  fCurrentSelectionState;

      // Is the subcomponent currently selected ?

      fCurrentSelectionState =
          m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                   (LPCTSTR) ptszSubComponentId,
                                                                   (UINT) OCSELSTATETYPE_CURRENT );

      if ( fCurrentSelectionState != (BOOL) TRUE )
      {
         // The subcomponent is not selected. Is OC Manager running stand-alone ?
         // If not, i.e. if GUI mode setup is running, there is nothing to do.

         if ( (m_SetupInitComponent.SetupData.OperationFlags &
              (DWORDLONG) SETUPOP_STANDALONE) != (DWORDLONG) 0L )
         {
            ClRtlLogPrint( "In OnOcAboutToCommitQueue this is STANDALONE and the component is not selected.\n" );
            ClRtlLogPrint( "So, this is an uninstall operation.\n" );

            // SETUPOP_STANDALONE set implies GUI mode setup is not running. If
            // there was a selection state change (to unselected) then delete
            // registry entries.

            fOriginalSelectionState =
               m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                        ptszSubComponentId,
                                                                        (UINT) OCSELSTATETYPE_ORIGINAL );

            // Has there been a selection state transition ?

            if ( fCurrentSelectionState != fOriginalSelectionState )
            {
               CString  csTemp;

               ClRtlLogPrint( "In OnOcAboutToCommitQueue a selection state transition has been detected.\n" );

               // At this point in cluscfg.exe file utils.cpp function DoUninstall
               // called function IsOtherSoftwareInstalled. That function apparently
               // checked for the presence of custom cluster resources and warned the
               // user to handle them before proceeding. (at least that is what David
               // told me). So, that logic needs to be replicated here.

               // NOTE: According to Andrew Ritz (AndrewR), there is no way to abort the
               // installation at this point. So the user is just shown the list of
               // custom resource types and is prompted to remove them after the cluster 
               // service has been uninstalled. The user is not given an option to abort
               // the installation.
               // (Vvasu 14-DEC-1999)

               if ( ( m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH ) ==
                    (DWORDLONG) 0L )
               {
                   // If this is not an unattended operation.

                   BOOL fReturnValue = CheckForCustomResourceTypes();

                   ClRtlLogPrint( "CheckForCustomResourceTypes returned 0x%1!x! to OnOcAboutToCommitQueue.\n",
                                  fReturnValue );
               }
               else
               {
                   ClRtlLogPrint( "CheckForCustomResourceTypes not called in OnOcAboutToCommitQueue as this is an unattended operation.\n" );
               }

               // Stop ClusSvc. Note that if ClusDisk is someday revised so that
               // it will unload, it will be appropriate to stop ClusDisk here
               // as well.

               csTemp = CLUSTER_SERVICE_NAME;

               // I'm going to assume that, since UNICODE is defined, the casts
               // in the calls to function StopService are OK, even though there
               // is probably a better approach.

               StopService( (LPCWSTR) (LPCTSTR) csTemp );

               csTemp =  TIME_SERVICE_NAME;

               StopService( (LPCWSTR) (LPCTSTR) csTemp );

               ClRtlLogPrint( "OnOcAboutToCommitQueue has attempted to stop ClusSvc and TimeServ.\n" );

               //
               // Unregister the COM objects that may previously have been registered.
               //
               // Note that since msclus.dll is registered as part of NT setup
               // it is not unregistered here.
               //

               csTemp =  CLUSTER_DIRECTORY;

               TCHAR tszPathToClusterDir[_MAX_PATH];

               if ( ExpandEnvironmentStrings( (LPCTSTR) csTemp,
                                              tszPathToClusterDir, _MAX_PATH ) > 0L )
               {
                  BOOL bUnregisterResult = UnRegisterClusterCOMObjects( m_hInstance, tszPathToClusterDir );

                  ClRtlLogPrint( "OnOcAboutToCommitQueue has unregistered the COM objects. The return value is %1!d!\n", bUnregisterResult );
               }
               else
               {
                  // Couldn't expand the environment string.

                  ClRtlLogPrint( "OnOcAboutToCommitQueue could not unregister the COM objects\n" );
                  ClRtlLogPrint( "because it could not locate the cluster directory.\n" );
               }

               // Unload the Cluster hive so that the Cluster hive file can be removed.

               UnloadClusDB();

               ClRtlLogPrint( "OnOcAbouToCommitQueue has unloaded the Cluster hive.\n" );

               dwReturnValue = (DWORD) NO_ERROR;
            }
            else
            {
               // The selection state has not been changed. Perform no action.

               dwReturnValue = (DWORD) NO_ERROR;

               ClRtlLogPrint("In OnOcAboutToCommitQueue no selection state transition was detected.\n" );
            }     // Was there a selection state transition ?
         }
         else
         {
            // OC Manager is NOT running stand-alone. This CANNOT be an uninstall
            // operation. There is nothing for this function to do.

            dwReturnValue = (DWORD) NO_ERROR;
         }     // Is GUI mode setup running ?
      }     // Is the subcomponent cuttently selected ?
      else
      {
         // Since the component is selected this CANNOT be an uninstall operation.
         // There is nothing for this function to do.

         dwReturnValue = (DWORD) NO_ERROR;

         ClRtlLogPrint( "In OnOcAboutToCommitQueue the component is selected so there is nothing to do.\n" );
      }
   }
   else
   {
      dwReturnValue = ERROR_FILE_NOT_FOUND;

      ClRtlLogPrint( "In OnOcAboutToCommitQueue the handle to the INF file is bad.\n" );
   }     // Is the handle to the component INF file valid ?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// LocateClusterHiveFile
//
// Routine Description:
//    This function attempts to locate the Cluster hive file and supply the
//    path to the calling function.
//
// Arguments:
//    rcsClusterHiveFilePath - a reference to a the CString to receive the
//                             path to the Cluster hive file.
//
// Return Value:
//    TRUE - incicates that the Cluster hive file was located and that
//           rcsClusterHiveFilePath is meaningfull.
//    FALSE - indicates that the Cluster hive file was not located and
//            rcsClusterHiveFilePath is empty.
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::LocateClusterHiveFile( CString & rcsClusterHiveFilePath )
{
   BOOL  fReturnValue;

   // The path to the Cluster hive file may be deduced by reading the
   // [DestinationDirs] section of the INF file. The ClusterFiles entry
   // will specify the file's location.

   if ( (m_SetupInitComponent.ComponentInfHandle != (HINF) INVALID_HANDLE_VALUE) &&
        (m_SetupInitComponent.ComponentInfHandle != (HINF) NULL) )
   {
      // The handle to the INF file is valid.

      TCHAR tszPathToClusterDirectory[MAX_PATH];

      // First, get the path to the cluster directory.

      DWORD    dwRequiredSize;

      CString  csSectionName;

      csSectionName =  CLUSTER_FILES_INF_KEY;

      fReturnValue = SetupGetTargetPath( m_SetupInitComponent.ComponentInfHandle,
                                         (PINFCONTEXT) NULL,
                                         (PCTSTR) csSectionName,
                                         tszPathToClusterDirectory,
                                         (DWORD) MAX_PATH,
                                         (PDWORD) &dwRequiredSize );

      if ( fReturnValue == (BOOL) TRUE )
      {
         rcsClusterHiveFilePath = (CString) tszPathToClusterDirectory;

         // Append the name of the Cluster hive file to the path.

         CString  csClusterDatabaseName;

         csClusterDatabaseName =  CLUSTER_DATABASE_NAME;

         rcsClusterHiveFilePath += (CString) _T("\\") + csClusterDatabaseName;

         ClRtlLogPrint( "LocateClusterHiveFile has deduced that the cluster hive is in %1!s!.\n",
                        rcsClusterHiveFilePath );

         // The path to the Cluster hive file has been built. Now, make
         // sure that the file is present.

         HANDLE            hSearchHandle;

         WIN32_FIND_DATA   FindData;

         hSearchHandle = FindFirstFile( rcsClusterHiveFilePath,
                                        &FindData);

         if( hSearchHandle != (HANDLE) INVALID_HANDLE_VALUE )
         {
             FindClose( hSearchHandle );

             // A file with the correct name was located. Is it the cluster hive
             // file? If it is a directory it is not.

             if( (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) !=
                  (DWORD) 0L )
             {
                // The file is a directory. That means that the Cluster
                // hive file could not be located.

                ClRtlLogPrint( "LocateClusterHiveFile could not find file %1!s!.\n", 
                               rcsClusterHiveFilePath );

                rcsClusterHiveFilePath.Empty();

                fReturnValue = (BOOL)  FALSE;
             }
             else
             {
                // The Cluster hive file was found.

                ClRtlLogPrint( "LocateClusterHiveFile found the Cluster hive at %1!s!.\n",
                               rcsClusterHiveFilePath );

                fReturnValue = (BOOL) TRUE;
             }
         }
         else
         {
            // The cluster hive file was not located.

            ClRtlLogPrint( "LocateClusterHiveFile could not find file %1!s!.\n", 
                           rcsClusterHiveFilePath );

            rcsClusterHiveFilePath.Empty();

            fReturnValue = (BOOL)  FALSE;
         }
      }
      else
      {
         rcsClusterHiveFilePath.Empty();

         ClRtlLogPrint( "SetupGetTargetPath failed in LocateClusterHiveFile.\n" );
      }
   }
   else
   {
      // The handle to the INF file is not valid. That means that the Cluster
      // hive file could not be located.

      ClRtlLogPrint( "LocateClusterHiveFile could not locate the cluster hive because the\n" );
      ClRtlLogPrint( "handle to the INF file is bad.\n" );

      rcsClusterHiveFilePath.Empty();

      fReturnValue = (BOOL) FALSE;
   }

   return ( fReturnValue );
}


/////////////////////////////////////////////////////////////////////////////
//++
//
// CheckForCustomResourceTypes
//
// Routine Description:
//    This function examines the ResourceTypes subkey of the Cluster key to
//    determine whether custom resource types have been installed.
//
// Arguments:
//    None
//
//
// Return Value:
//    TRUE - indicates that either no custom resource types have been detected
//           or the user chooses to continue uninstalling Cluster Server without
//           first uninstalling the software packages associated with the custom
//           resource types.
//    FALSE - indicates that custom resource types were detected and the user
//            wishes to terminate uninstallation of Cluster Server.
//
// Note:
//    This function was originally called IsOtherSoftwareInstalled in the old
//    cluster\newsetup project in the file called utils.cpp.
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::CheckForCustomResourceTypes( void )
{
   BOOL  fReturnValue = (BOOL) TRUE;
   BOOL  fEnumerateResourceTypesKeys = (BOOL) TRUE;
   BOOL  fClusterHiveLoadedByThisFunction = (BOOL) FALSE;

   HKEY  hClusterKey;

   LONG lReturnValue;

   // Attempt to open the Cluster Registry key.

   CString  csClusterRegKey;

   csClusterRegKey = CLUSREG_KEYNAME_CLUSTER;

   lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE, csClusterRegKey,
                                0, KEY_READ, &hClusterKey);

   if ( lReturnValue != ERROR_SUCCESS )
   {
      ClRtlLogPrint( "In CheckForCustomResourceTypes the first attempt to open the Cluster key failed.\n" );

      // The Cluster hive is not currently loaded. This condition means that
      // the cluster service has not been started. Attempt to load the Cluster
      // hive so that it can be read.

      // First, locate the Cluster hive file. It should be in the location
      // specified for the ClusterFiles entry in the [DestinationDirs] section
      // of clusocm.inf.

      CString  csClusterHiveFilePath;

      fReturnValue = LocateClusterHiveFile( (CString &) csClusterHiveFilePath );

      ClRtlLogPrint( "LocateClusterHiveFile returned 0x%1!x! to CheckForCustomResourceTypes.\n",
                     fReturnValue );

      // Was the Cluster hive file located?

      if ( fReturnValue == (BOOL) TRUE )
      {
         // The Cluster hive file was located. Custom resource types may exist.
         // Attempt to load the cluster hive.

         BOOLEAN  OriginalState;

         // I'm not sure what the following function does, but the prototype is
         // in sdk\inc\ntrtl.h. Look in stdafx.h for the inclusion of ntrtl.h. I
         // replicated the logic that was used in newsetup.h to make it work.

         // RtlAdjustPrivilege returns NTSTATUS.

         NTSTATUS Status;

         Status = RtlAdjustPrivilege( SE_RESTORE_PRIVILEGE,
                                      TRUE,
                                      FALSE,
                                      &OriginalState );

         if ( NT_SUCCESS( Status ) )
         {
            // Attempt to Load the Cluster Hive.

            lReturnValue = RegLoadKey( HKEY_LOCAL_MACHINE,
                                       csClusterRegKey,
                                       csClusterHiveFilePath );

            if ( lReturnValue == ERROR_SUCCESS )
            {
               fClusterHiveLoadedByThisFunction = (BOOL) TRUE;

               // Now that the Cluster hive has been loaded, attempt to open the
               // Cluster registry key.

               lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                            csClusterRegKey,
                                            0, KEY_READ,
                                            &hClusterKey );

               // lReturnValue will be tested by the next BLOCK of code.
            } // cluster hive loaded?
            else
            {
               // The Cluster hive could not be loaded. Treat that as if there
               // are no custom resource types.

               // A return value of TRUE will allow the uninstall operation to
               // continue. Since lReturnValue is NOT ERROR_SUCCESS no additional
               // processing will be performed by this function.

               fReturnValue = (BOOL) TRUE;
            }  // Was the cluster hive loaded successfully?

            // Undo whatever the preceding call to RtlAdjustPrivilege() did.

            RtlAdjustPrivilege( SE_RESTORE_PRIVILEGE,
                                OriginalState,
                                FALSE,
                                &OriginalState );
         } // Initial call to RtlAdjustPrivilege succeeded?
         else
         {
            // The initial call to RtlAdjustPrivilege FAILED.

            ClRtlLogPrint( "In CheckForCustomResources the initial call to RtlAdjustPrivileges failed with status %1!d!.\n",
                           Status );

            // A return value of TRUE will allow the uninstall operation to
            // continue. Since lReturnValue is NOT ERROR_SUCCESS no additional
            // processing will be performed by this function.

            fReturnValue = (BOOL) TRUE;
         }  // Did RtlAdjustPrivilege succeed?
      } // cluster hive file located?
      else
      {
         // The Cluster hive file was not located. That implies that there can be
         // no custom resources. Since lReturnValue is NOT ERROR_SUCCESS no
         // additional processing will be done by this function. A return value
         // of TRUE will allow the uninstall operation to continue.

         ClRtlLogPrint( "Since the Cluster hive file could not be located CheckForCustomResources will\n" );
         ClRtlLogPrint( "report that the uninstall operation may continue.\n" );

         fReturnValue = (BOOL) TRUE;
      }  // Was the cluster hive file located?
   }  // Was the Cluster registry key opened successfully?

   // The preceeding code segment may have loaded the cluster hive and
   // attempted to open the cluster key. Was it sucessful?

   if ( lReturnValue == ERROR_SUCCESS )
   {
      // At this point, the Cluster registry key was opened successfully. Now, attempt
      // to open the Resource Type subkey.

      HKEY  hResourceTypesKey;

      CString  csResourceTypesRegKey;

      csResourceTypesRegKey = CLUSREG_KEYNAME_RESOURCE_TYPES;

      lReturnValue = RegOpenKeyEx( hClusterKey,
                                   csResourceTypesRegKey,
                                   0, KEY_READ, &hResourceTypesKey );

      if ( lReturnValue == ERROR_SUCCESS )
      {
         // The ResourceTypes sub key is open.
         // Enumerate the subkeys of the REG_RESTYPES.

         FILETIME t_LastWriteTime;
         WCHAR wszSubKeyName[256];
         DWORD dwCharCount;
         DWORD dwIndex = 0;   // index of the first sub-key to enumerate

         dwCharCount = sizeof( wszSubKeyName ) / sizeof( wszSubKeyName[0] );
         lReturnValue = RegEnumKeyEx( hResourceTypesKey,
                                      dwIndex,
                                      wszSubKeyName,
                                      &dwCharCount,
                                      NULL,
                                      NULL,// Class of the Key. Not Reqd.
                                      NULL,// Size of the above param.
                                      &t_LastWriteTime );

         // RegEnumKeyEx returns ERROR_NO_MORE_ITEMS when there are
         // no additional sub-keys to enumerate.

         if ( lReturnValue == ERROR_SUCCESS )
         {
            // The initial call to RegEnumKeyEx succeeded. Determine whether
            // the sub-key is associated with a non-standard resource type.

            // Function TryToRecognizeResourceType builds a list of non-standard resource types
            // in the CString variable csNonStandardResourceTypeList.

            CString  csNonStandardResourceTypeList;

            TryToRecognizeResourceType( csNonStandardResourceTypeList, wszSubKeyName );

            // The following loop checks the remaining sub-keys in the
            // Resource Types registry key.

            while ( lReturnValue == ERROR_SUCCESS )
            {
               dwIndex++;

               dwCharCount = sizeof( wszSubKeyName ) / sizeof( wszSubKeyName[0] );
               lReturnValue = RegEnumKeyEx( hResourceTypesKey,
                                            dwIndex,
                                            wszSubKeyName,
                                            &dwCharCount,
                                            NULL,
                                            NULL,// Class of the Key. Not Reqd.
                                            NULL,// Size of the above param.
                                            &t_LastWriteTime );

               TryToRecognizeResourceType( csNonStandardResourceTypeList, wszSubKeyName );
            }

            // Were all subkeys of the ResourceTypes registry key "verified"?

            if( lReturnValue == ERROR_NO_MORE_ITEMS )
            {
               CString csMessage;

               fReturnValue = (BOOL) TRUE;

               RegCloseKey( hResourceTypesKey );
               RegCloseKey( hClusterKey );

               // Were there any non-standard subkeys in the Resource Types registry key?

               if ( csNonStandardResourceTypeList.IsEmpty() == (BOOL) FALSE )
               {
                  // Non-standard subkeys were found. Ask the user about removal.

                  // Build and present a message of the form:
                  //
                  // The following software packages should be removed before removing
                  // the Microsoft Cluster Server software.
                  //
                  // <list of packages>
                  //
                  // Do you want to continue with uninstall?

                  CString csLastPartOfMessage;
                  CString csMessageBoxTitle;

                  csLastPartOfMessage.LoadString(IDS_ERR_UNINSTALL_OTHER_SW_EXT);
                  csNonStandardResourceTypeList += csLastPartOfMessage;

                   // BUGBUG - The next two statements are commented out temporarily because the localization
                   // changes needed for this have not been approved. Uncomment after NT 5.0.
                   // Also restore clusocm.rc@v2 and resource.h@v2
                   // (Vvasu 05-Jan-2000
                  
                  //  csMessageBoxTitle.LoadString(IDS_TITLE_CUSTOM_RESTYPES);
                  //::MessageBox( NULL,
                  //              csNonStandardResourceTypeList,
                  //              csMessageBoxTitle,
                  //              MB_OK | MB_ICONINFORMATION 
                  //            );

                  ClRtlLogPrint( "CheckForClusterResourceTypes detected custom resources.\n" );
               }  // Were non-standard resource typed detected?
               else
               {
                  ClRtlLogPrint( "CheckForCustomResourceTypes did not detect any custom resource types,\n" );
               }
            } // Was there an error enumerating the ResourceTypes key?
            else
            {
               ClRtlLogPrint( "CheckForCustomResourceTypes was unable to enumerate the resource types,\n" );
               ClRtlLogPrint( "implying that no custom resource types exist, so the uninstall will continue.\n" );
               ClRtlLogPrint( "The error code is %1!d!.\n", lReturnValue );

               // An error occured while enumerating the ResourceTypes sub-keys.

               RegCloseKey( hResourceTypesKey );
               RegCloseKey( hClusterKey );

               // The uninstall operation should continue because the installation
               // is apparently defective.

               fReturnValue = (BOOL) TRUE;
            } // Was there an error enumerating the ResourceTypes key?
         }
         else
         {
            // The initial call to RegEnumKeyEx failed.
            // Issue an error message and exit.

            RegCloseKey( hClusterKey );

            ClRtlLogPrint( "CheckForCustomResourceTypes was unable to enumerate the resource types,\n" );
            ClRtlLogPrint( "implying that no custom resource types exist, so the uninstall will continue.\n" );
            ClRtlLogPrint( "The error code is %1!d!.\n", lReturnValue );

            // A return value of TRUE will allow the uninstall operation to
            // continue.

            fReturnValue = (BOOL) TRUE;

         }  // Did RegEnumKeyEx open the ResourceTypes key?
      }  // Was the ResourceTypes sub key opened?
      else
      {
         // The ResourceTypes sub key could not be opened.
         // Issue an error message and exit.

         RegCloseKey( hClusterKey );

         ClRtlLogPrint( "CheckForCustomResourceTypes was unable to open the Cluster\\ResourceTypes key,\n" );
         ClRtlLogPrint( "implying that no custom resource types exist, so the uninstall will continue.\n" );

         // A return value of TRUE will allow the uninstall operation to
         // continue.

         fReturnValue = (BOOL) TRUE;
      }  // Was the ResourceTypes sub key opened?
   } // cluster hive opened?
   else
   {
      // The Cluster registry key could not be opened, even after possibly
      // attempting to load the cluster hive.

      ClRtlLogPrint( "CheckForCustomResourceTypes was unable to open the Cluster key,\n" );
      ClRtlLogPrint( "implying that no custom resource types exist, so the uninstall will continue.\n" );
      ClRtlLogPrint( "The error code is %1!d!.\n", lReturnValue );

      // A return value of TRUE will allow the uninstall operation to
      // continue.

      fReturnValue = (BOOL) TRUE;
   }  // Second test whether the Cluster registry key was opened successfully.

   if ( fClusterHiveLoadedByThisFunction == (BOOL) TRUE )
   {
      UnloadClusDB();
   }

   return ( fReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// UnloadClusDB
//
// Routine Description:
//    This function unloads the Cluster hive.
//
// Arguments:
//    None
//
// Return Value:
//    None
//
// Note:
//    This function was originally in newsetup\utils.cpp.
//
//--
/////////////////////////////////////////////////////////////////////////////

VOID CClusocmApp::UnloadClusDB( VOID )
{
   DWORD Status;
   BOOLEAN  WasEnabled;

   Status = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE,
                               TRUE,
                               FALSE,
                               &WasEnabled);

   if ( Status == ERROR_SUCCESS )
   {
      LONG  lReturnValue;

      CString  csClusterRegKey;

      csClusterRegKey = CLUSREG_KEYNAME_CLUSTER;

      lReturnValue = RegUnLoadKeyW(HKEY_LOCAL_MACHINE, csClusterRegKey );

      RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE,
                         WasEnabled,
                         FALSE,
                         &WasEnabled);
   }
}


PWCHAR WellKnownResourceTypes[] = {
    CLUS_RESTYPE_NAME_GENAPP,
    CLUS_RESTYPE_NAME_GENSVC,
    CLUS_RESTYPE_NAME_FTSET,
    CLUS_RESTYPE_NAME_PHYS_DISK,
    CLUS_RESTYPE_NAME_IPADDR,
    CLUS_RESTYPE_NAME_NETNAME,
    CLUS_RESTYPE_NAME_FILESHR,
    CLUS_RESTYPE_NAME_PRTSPLR,
    CLUS_RESTYPE_NAME_TIMESVC,
    CLUS_RESTYPE_NAME_LKQUORUM,
    CLUS_RESTYPE_NAME_DHCP,
    CLUS_RESTYPE_NAME_MSMQ,
    CLUS_RESTYPE_NAME_NEW_MSMQ,
    CLUS_RESTYPE_NAME_MSDTC,
    CLUS_RESTYPE_NAME_WINS,
    CLUS_RESTYPE_NAME_IIS4,
    CLUS_RESTYPE_NAME_SMTP,
    CLUS_RESTYPE_NAME_NNTP,
    (PWCHAR) UNICODE_NULL
};



/////////////////////////////////////////////////////////////////////////////
//++
//
// TryToRecognizeResourceType
//
// Routine Description:
//    This function determines whether the resource type whose name is in
//    parameter "keyname" is recognized as a standard resource type as defined
//    in clusudef.h.
//
//    This function builds a list of unrecognized reaource types in the CString
//    referenced by parameter "str".
//
// Arguments:
//    str - a reference to a CString in which to build a list of unrecognized
//          resource types.
//    keyname - points to a string that contains the resource type name.
//
// Return Value:
//    None
//
// Note:
//    This function was excerpted verbatim from newsetup\utils.cpp.
//
//--
/////////////////////////////////////////////////////////////////////////////

VOID CClusocmApp::TryToRecognizeResourceType( CString& str, LPTSTR keyname )
{
   PWCHAR * ppName = WellKnownResourceTypes;

   while ( *ppName != (PWCHAR)UNICODE_NULL )
   {
      if ( lstrcmp( keyname, *ppName) == 0 )
         return;
      ++ppName;
   }

   if ( !str.IsEmpty() )
      str += L", ";
   else
   {
      str.LoadString(IDS_ERR_UNINSTALL_OTHER_SW);

      str += _T('\n');
   }
   str += keyname;
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// GetServiceBinaryPath
//
// Routine Description:
//    This function retrieves the fully qualified path to a Service
//    from the Service Control Manager.
//
// Arguments:
//    lpwszServiceName - points to a wide character string containing the service name
//    lptszBinaryPathName - points to a string to receive the fully qualified
//                          path to the Cluster Service.
//
// Return Value:
//    TRUE - The path to the Cluster Service was obtained successfully.
//    FALSE - The path to the Cluster Service was not obtained.
//
// Note:
//    Calling this function makes sense IFF the Cluster Service is registered
//    with the Service Control Manager. Call IsClusterServiceRegistered() to
//    ascertain that BEFORE calling GetServiceBinaryPath.
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::GetServiceBinaryPath( IN LPWSTR lpwszServiceName,
                                        OUT LPTSTR lptszBinaryPathName )
{
   BOOL  fReturnValue;

   DWORD dwErrorCode = (DWORD) ERROR_SUCCESS;

   if ( lpwszServiceName != (LPWSTR) NULL )
   {
      SC_HANDLE hscServiceMgr;
      SC_HANDLE hscService;

      // Connect to the Service Control Manager and open the specified service
      // control manager database.

      hscServiceMgr = OpenSCManager( NULL, NULL, GENERIC_READ | GENERIC_WRITE );

      // Was the service control manager database opened successfully?

      if ( hscServiceMgr != NULL )
      {
         // The service control manager database is open.
         // Open a handle to the Service.

         hscService = OpenService( hscServiceMgr,
                                   lpwszServiceName,
                                   GENERIC_READ );

         // Was the handle to the service opened?

         if ( hscService != NULL )
         {
            // A valid handle to the Service was obtained.

            DWORD dwBufferSize;

            // Note that the size of the buffer required to get the service configuration
            // information was determined empherically to be 240 bytes.

            LPQUERY_SERVICE_CONFIG lpServiceConfig;

            dwBufferSize = (DWORD) sizeof( QUERY_SERVICE_CONFIG ) + 256;   // The delta from the
                                                                           // size of the structure
                                                                           // was chosen arbitrarily.

            // Attempt to allocate the buffer.

            lpServiceConfig = (LPQUERY_SERVICE_CONFIG) LocalAlloc( LMEM_ZEROINIT, dwBufferSize );

            // Was the bufer allocated successfully?

            if ( lpServiceConfig != (LPQUERY_SERVICE_CONFIG) NULL )
            {
               // The following call to QueryServiceConfig should return something
               // usefull. If it fails it is probably because the guess at the size
               // of the buffer is too small.

               fReturnValue = QueryServiceConfig( hscService,
                                                  lpServiceConfig,
                                                  dwBufferSize,
                                                  (LPDWORD) &dwBufferSize );

               // Was the Service configuration info obtained?

               if ( fReturnValue == (BOOL) TRUE )
               {
                  // Update the output parameter.

                  _tcscpy( lptszBinaryPathName, lpServiceConfig->lpBinaryPathName );

                  // lptszBinaryPathName includes the service name. Strip that off.

                  LPTSTR   ptszTemp;

                  ptszTemp = _tcsrchr( (const wchar_t *) lptszBinaryPathName,
                                       (int) _T('\\') );

                  *ptszTemp = _T('\0');

                  ClRtlLogPrint( "In GetServiceBinaryPath the first call to QueryServiceConfig succeeded.\n" );
               }
               else
               {
                  // Was the buffer too small?

                  dwErrorCode = GetLastError();

                  if ( dwErrorCode == (DWORD) ERROR_INSUFFICIENT_BUFFER )
                  {
                     ClRtlLogPrint( "GetServiceBinaryPath is enlarging the buffer for the QUERY_SERVICE_CONFIG structure.\n" );

                     // Increase the size of the buffer.

                     HLOCAL   hLocalBlock;

                     // As per RodGa LocalReAlloc is not always reliable. Here that functionality
                     // is implemented by freeing the buffer for the QUERY_SERVICE_CONFIG struct
                     // and allocating a nre block using LocalFree and LocalAlloc.

                     hLocalBlock = LocalFree( lpServiceConfig );

                     if ( hLocalBlock == (HLOCAL) NULL )
                     {
                        // The previously allocated block has been released. Now,
                        // allocate a block of the proper size.

                        lpServiceConfig = (LPQUERY_SERVICE_CONFIG) LocalAlloc( LMEM_ZEROINIT,
                                                                               (UINT) dwBufferSize );

                        // Was the larger buffer allocated successfully?

                        if ( lpServiceConfig != (LPQUERY_SERVICE_CONFIG) NULL )
                        {
                           // The following call to QueryServiceConfig should return something
                           // usefull. If it fails it is not because the buffer is too small.

                           fReturnValue = QueryServiceConfig( hscService,
                                                              lpServiceConfig,
                                                              dwBufferSize,
                                                              (LPDWORD) &dwBufferSize );

                           // Was the Service configuration info obtained this time.

                           if ( fReturnValue == (BOOL) TRUE )
                           {
                              // Update the output parameter.

                              _tcscpy( lptszBinaryPathName, lpServiceConfig->lpBinaryPathName );

                              // lptszBinaryPathName includes the service name. Strip that off.

                              LPTSTR   ptszTemp;

                              ptszTemp = _tcsrchr( (const wchar_t *) lptszBinaryPathName,
                                                   (int) _T('\\') );

                              *ptszTemp = _T('\0');

                              ClRtlLogPrint( "In GetServiceBinaryPath the second call to QueryServiceConfig succeeded.\n" );
                           }
                           else
                           {
                              // The Service configuration info was not obtained.

                              dwErrorCode = GetLastError();

                              ClRtlLogPrint( "In GetServiceBinaryPath the second call to QueryServiceConfig failed\n" );
                              ClRtlLogPrint( "with error code 0x%1!x!,\n", dwErrorCode );

                              fReturnValue = (BOOL) FALSE;
                           }  // second call to QueryServiceConfig succeeded?
                        }
                        else
                        {
                           // The attempt to enlarge the buffer failed.

                           dwErrorCode = GetLastError();

                           ClRtlLogPrint( "GetServiceBinaryPath was unable to enlarge the buffer for the QUERY_SERVICE_CONFIG structure.\n" );
                           ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwErrorCode );

                           fReturnValue = (BOOL) FALSE;
                        }
                     }
                     else
                     {
                        dwErrorCode = GetLastError();

                        ClRtlLogPrint( "In GetServiceBinaryPath the call to LocalFree failed with error code 0x%1!x!.\n",
                                       dwErrorCode );

                        fReturnValue = (BOOL) FALSE;
                     }
                  }
                  else
                  {
                     // Some other error occured.

                     ClRtlLogPrint( "In GetServiceBinaryPath the first call to QueryServiceConfig failed\n" );
                     ClRtlLogPrint( "with error code 0x%1!x!.\n", dwErrorCode );

                     fReturnValue = (BOOL) FALSE;
                  }  // Was the buffer to small?
               }  // Service config info obtained from first call to QueryServiceConfig?

               // Free the buffer if it was ever allocated successfully.

               if ( lpServiceConfig != (LPQUERY_SERVICE_CONFIG) NULL )
               {
                  LocalFree( lpServiceConfig );
               }
            }
            else
            {
               // Could not allocate the buffer.

               dwErrorCode = GetLastError();

               ClRtlLogPrint( "GetServiceBinaryPath could not allocate a buffer for the QUERY_SERVICE_CONFIG structure.\n" );
               ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwErrorCode );

               fReturnValue = (BOOL) FALSE;
            }  // Was the buffer allocated successfully?

            // Close the handle to the Cluster Service.

            CloseServiceHandle( hscService );
         }
         else
         {
            // The Service could not be opened.

            dwErrorCode = GetLastError();

            ClRtlLogPrint( "GetServiceBinaryPath could not open an handle to %1!ws!.\n",
                           lpwszServiceName );
            ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwErrorCode );

            fReturnValue = (BOOL) FALSE;
         }  // Was the Cluster Service opened?

         // Close the handle to the Service Control Manager.

         CloseServiceHandle( hscServiceMgr );
      }
      else
      {
         // The Service Control Manager could not be opened.

         dwErrorCode = GetLastError();

         ClRtlLogPrint( "GetServiceBinaryPath could not open the Service Control Manager.\n" );
         ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwErrorCode );

         fReturnValue = (BOOL) FALSE;
      }  // Was the Service Control Manager opened?
   }
   else
   {
      // The service name pointer was bogus.

      ClRtlLogPrint( "The service name passed to GetServiceBinaryPath is invalid.\n" );

      fReturnValue = FALSE;
   }  // Is the service name legal?

   if ( fReturnValue == (BOOL) FALSE )
   {
      // Set the binary path invalid.

      *lptszBinaryPathName = _T('\0');
   }
   else
   {
      ClRtlLogPrint( "GetServiceBinaryPath located %1!ws! at %2!s!.\n",
                     lpwszServiceName, lptszBinaryPathName );
   }

   SetLastError( dwErrorCode );     // Set the "last" error code (which may be ERROR_SUCCESS)
                                    // because this function's caller is likely to call GetLastError().
   return ( fReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// SetDirectoryIds
//
// Routine Description:
//    This function associates the user defined Directory Identifiers in the
//    [DestinationDirs] section of the component INF file with particular
//    directories, either the default location for NT 5 installations,
//    %windir%\cluster, or the location of a previous installation.
//
// Arguments:
//    fClusterServiceRegistered - TRUE indicates that Cluster service has
//                                previously been installed and the files should
//                                be updated in place.
//
//                                FALSE - indicates that the Cluster service
//                                files should be installed into the default
//                                location.
//
// Return Value:
//    TRUE - indicates success
//    FALSE - indicates error
//
// Note:
//    The [DestinationDirs] section in clusocm.inf contains the following keys:
//
//          ClusterFiles               = 33001
//          ClusterUpgradeFiles        = 33002
//          ClusterAdminFiles          = 33003
//          ClusterUninstallFiles      = 33004
//
//    Those directory IDs were chosen to be larger than DIRID_USER.
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::SetDirectoryIds( BOOL fClusterServiceRegistered )
{
   BOOL  fReturnValue;

   TCHAR tszClusterServiceBinaryPath[MAX_PATH];

   // Are Cluster service files already present?

   if ( fClusterServiceRegistered == (BOOL) TRUE )
   {
      // Cluster service files should be upgraded in place.

      // Query the path to the Cluster Service executable from the Service Control Manager.

      CString  csClusterService;

      csClusterService = CLUSTER_SERVICE_NAME;

      fReturnValue = GetServiceBinaryPath( (LPWSTR) (LPCTSTR) csClusterService,
                                            tszClusterServiceBinaryPath );

      if ( fReturnValue == (BOOL) FALSE )
      {
         DWORD dwErrorCode;

         dwErrorCode = GetLastError();

         ClRtlLogPrint( "In SetDirectoryIds the call to GetServiceBinaryPath failed with error code 0x%1!x!.\n",
                        dwErrorCode );
      }
   }
   else
   {
      // Cluster service files should be installed in the default location.

      CString  csClusterDirectory;

      csClusterDirectory =  CLUSTER_DIRECTORY;

      if ( ExpandEnvironmentStrings( (LPCTSTR) csClusterDirectory,
                                     tszClusterServiceBinaryPath, MAX_PATH ) > 0L )
      {
         fReturnValue = (BOOL) TRUE;
      }
      else
      {
         // Could not expand the enviornment string. The default location for the
         // Cluster service could not be determined.

         fReturnValue = (BOOL) FALSE;

         DWORD dwErrorCode;

         dwErrorCode = GetLastError();

         ClRtlLogPrint( "ExpandEnvironmentString returned 0x%1!x! to SetDirectoryIds.\n",
                        dwErrorCode );
      }  // Was the default location for the Cluster service determined?
   }  // Where should Cluster service files be installed?

   // Was the location into which Cluster service files should be copied obtained?

   if ( fReturnValue == (BOOL) TRUE )
   {
      // Associate selected Directory Ids with the path in tszClusterServiceBinaryPath.

      // Set the Directory Id for the ClusterFiles key in [DestinationDirs].

      fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                          33001,
                                          (PCTSTR) tszClusterServiceBinaryPath );

      // Was the Directory Id for the ClusterFiles key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33001 was set to %1!s!.\n", tszClusterServiceBinaryPath );

         // Set the Directory Id for the ClusterUpgradeFiles key in [DestinationDirs].

         fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                             33002,
                                             (PCTSTR) tszClusterServiceBinaryPath );

      }  // Was the Directory Id for the ClusterFiles key set successfully?

      // Was the Directory Id for the ClusterUpgradeFiles key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33002 was set to %1!s!.\n", tszClusterServiceBinaryPath );

         // Set the Directory Id for the ClusterAdminFiles key in [DestinationDirs].

         fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                             33003,
                                             (PCTSTR) tszClusterServiceBinaryPath );

      }  // Was the Directory Id for the ClusterUpgradeFiles key set successfully?

      // Was the Directory Id for the ClusterAdminFiles key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33003 was set to %1!s!.\n", tszClusterServiceBinaryPath );

         // Set the Directory Id for the ClusterUninstallFiles key in [DestinationDirs].

         fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                             33004,
                                             (PCTSTR) tszClusterServiceBinaryPath );

      }  // Was the Directory Id for the ClusterAdminFiles key set successfully?

      // Was the Directory Id for the ClusterUninstallFiles key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33004 was set to %1!s!.\n", tszClusterServiceBinaryPath );

         // Set the Directory Id for the NT4.files.root key in [DestinationDirs].

         fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                             33005,
                                             (PCTSTR) tszClusterServiceBinaryPath );

      }  // Was the Directory Id for the ClusterUninstallFiles key set successfully?

      // Was the Directory Id for NT4.files.root key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33005 was set to %1!s!.\n", tszClusterServiceBinaryPath );

         // Append the "private" directory to the path.

         // Note, I didn't put this string in the stringtable because it is
         // not localizable, and will never change.

         _tcscat( tszClusterServiceBinaryPath, _T("\\private") );

         // Set the Directory Id for the NT4.files.private key in [DestinationDirs].

         fReturnValue = SetupSetDirectoryId( m_SetupInitComponent.ComponentInfHandle,
                                             33006,
                                             (PCTSTR) tszClusterServiceBinaryPath );

      }  // Was the Directory Id for the NT4.files.root key set successfully?

      // Was the Directory Id for NT4.files.private key set successfully?

      if ( fReturnValue = (BOOL) TRUE )
      {
         ClRtlLogPrint( "Directory Id 33006 was set to %1!s!.\n", tszClusterServiceBinaryPath );
      } // Was the Directory Id for NT4.files.private key set successfully?
   }  // Was the path to the Cluster files determined?
   else
   {
      ClRtlLogPrint( "SetDirectoryIds could not locate the cluster directory, so it failed.\n" );
   }

   return ( fReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// UpgradeClusterServiceImagePath
//
// Routine Description:
//    This function "upgrades" the ImagePath value in the Cluster Service registry
//    key to the location queried from the Service Control Manager.
//
// Arguments:
//    None
//
// Return Value:
//    NO_ERROR - indicates success
//    Any other value is a Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::UpgradeClusterServiceImagePath( void )
{
   BOOL  fReturnValue;

   DWORD dwReturnValue;

   // Query the path to the Cluster Service from the Service Control Manager.

   TCHAR tszClusterServiceBinaryPath[MAX_PATH];

   CString  csClusterService;

   csClusterService = CLUSTER_SERVICE_NAME;

   fReturnValue = GetServiceBinaryPath( (LPWSTR) (LPCTSTR) csClusterService,
                                         tszClusterServiceBinaryPath );

   // Was the path to the Cluster Service obtained?

   if ( fReturnValue == (BOOL) TRUE )
   {
      // Set the ImagePath value in the Cluster Service reg key to the location
      // obtained from the Service Control Manager.

      LONG lReturnValue;

      HKEY hClusterServiceKey;

      DWORD dwType;
      DWORD dwSize;

      // Attempt to open the Cluster Service reg key.

      CString  csClusterServiceRegKey;

      csClusterServiceRegKey = CLUSREG_KEYNAME_CLUSSVC;

      lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                   csClusterServiceRegKey,
                                   (DWORD) 0L,           // reserved
                                   (REGSAM) KEY_SET_VALUE,
                                   &hClusterServiceKey );

      // Was the Cluster Service reg key opened?

      if ( lReturnValue == (LONG) ERROR_SUCCESS )
      {
         TCHAR tszClusterServicePath[MAX_PATH];

         _tcscpy( tszClusterServicePath, tszClusterServiceBinaryPath );

         _tcscat( tszClusterServicePath, _T("\\") );

         // Append the name of the Cluster Service.

         CString  csClusterService;

         csClusterService = CLUSTER_SERVICE_NAME;

         csClusterService += (CString) _T(".exe");

         _tcscat( tszClusterServicePath, csClusterService );

         DWORD dwImagePathValueLength;

         dwImagePathValueLength = (DWORD) ((_tcslen( tszClusterServicePath ) + 1) * sizeof( TCHAR ));

         CString  csImagePath;

         csImagePath = CLUSREG_KEYNAME_IMAGE_PATH;

         lReturnValue = RegSetValueEx( hClusterServiceKey,
                                       csImagePath,
                                       (DWORD) 0L,                      // reserved
                                       (DWORD) REG_EXPAND_SZ,
                                       (CONST BYTE *) tszClusterServicePath,
                                       dwImagePathValueLength );

         // Was the ImagePath written successfully?

         if ( lReturnValue == (LONG) ERROR_SUCCESS )
         {
            dwReturnValue = (DWORD) NO_ERROR;

            ClRtlLogPrint( "UpgradeClusterServiceImagePath succeeded.\n" );
         }
         else
         {
            dwReturnValue = GetLastError();

            ClRtlLogPrint( "UpgradeClusterServiceImagePath failed with error code 0x%1!x!.\n",
                           dwReturnValue );
         }  // Was the ImagePath written successfully?

         // Close the Cluster Service registry key.

         RegCloseKey( hClusterServiceKey );        // do we care about the return value?
      }
      else
      {
         dwReturnValue = GetLastError();

         ClRtlLogPrint( "UpgradeClusterServiceImagePath failed with error code 0x%1!x!.\n",
                        dwReturnValue );
      }  // Was the Cluster Service reg key opened?
   }  // Was the path to the Cluster Service obtained?
   else
   {
      // Indicate error.

      dwReturnValue = GetLastError();

      ClRtlLogPrint( "UpgradeClusterServiceImagePath failed with error code 0x%1!x!.\n",
                     dwReturnValue );
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcQueueFileOps
//
// Routine Description:
//    This function processes the OC_QUEUE_FILE_OPS "messages" from the
//    Optional Components Manager.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    hSetupFileQueue - a HSPFILEQ (typedefed in setupapi.h to PVOID)
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcQueueFileOps( IN LPCTSTR ptszSubComponentId,
                                     IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue = NO_ERROR;

   // Is the handle to the component INF file valid?

   if ( (m_SetupInitComponent.ComponentInfHandle != (HINF) INVALID_HANDLE_VALUE) &&
        (m_SetupInitComponent.ComponentInfHandle != (HINF) NULL) )
   {
      // Is this UNATTENDED or ATTENDED?

      if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) !=
           (DWORDLONG) 0L )
      {
         // This is UNATTENDED.

         ClRtlLogPrint( "In OnOcQueueFileOps this is an UNATTENDED operation.\n" );

         dwReturnValue = OnOcQueueFileOpsUnattended( (LPCTSTR) ptszSubComponentId,
                                                     hSetupFileQueue );
      }
      else
      {
         // This is ATTENDED.

         ClRtlLogPrint( "In OnOcQueueFileOps this is an ATTENDED operation.\n" );

         dwReturnValue = OnOcQueueFileOpsAttended( (LPCTSTR) ptszSubComponentId,
                                                   hSetupFileQueue );
      } // Is this UNATTENDED or ATTENDED?
   }
   else
   {
      dwReturnValue = ERROR_FILE_NOT_FOUND;

      ClRtlLogPrint( "In OnOcQueueFileOps the handle to the component INF file is bad.\n" );
   } // Is the handle to the component INF file valid?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcQueueFileOpsUnattended
//
// Routine Description:
//    This function processes the OC_QUEUE_FILE_OPS "messages" from the
//    Optional Components Manager during UNATTENDED operations.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    hSetupFileQueue - a HSPFILEQ (typedefed in setupapi.h to PVOID)
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
// Note:
//    OC Manager sends OC_QUEUE_FILE_OPS to the component DLL when the Components
//    List wizard page is dismissed.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcQueueFileOpsUnattended( IN LPCTSTR ptszSubComponentId,
                                               IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue;

   // Is this an UPGRADE?

   if ( (m_SetupInitComponent.SetupData.OperationFlags &
         (DWORDLONG) SETUPOP_NTUPGRADE) != (DWORDLONG) 0L )
   {
      // This is an unattended UPGRADE.

      dwReturnValue = QueueFileOpsUnattendedUpgrade( (LPCTSTR) ptszSubComponentId,
                                                     hSetupFileQueue );

      ClRtlLogPrint( "QueueFileOpsUnattendedUpgrade returned 0x%1!x!.\n", dwReturnValue );
   }
   else
   {
      HINF  hAnswerFile;      // WARNING: NEVER close this handle because clusocm.dll
                              //          did not open it.

      // Get a handle to the answer file. WARNING: NEVER close this handle because clusocm.dll
      // did not open it.

      hAnswerFile = m_SetupInitComponent.HelperRoutines.GetInfHandle( INFINDEX_UNATTENDED,
                                                                      m_SetupInitComponent.HelperRoutines.OcManagerContext );

      if ( (hAnswerFile != (HINF) NULL) && (hAnswerFile != (HINF) INVALID_HANDLE_VALUE) )
      {
         ClRtlLogPrint( "In OnOcQueueFileOpsUnattended this is a CLEAN install.\n" );

            // Is Cluster service selected? It is probably overkill to check, but
            // it is safer to check than to be sorry later.

         BOOL  fCurrentSelectionState;
         BOOL  fOriginalSelectionState;

         fCurrentSelectionState =
            m_SetupInitComponent.HelperRoutines.QuerySelectionState( 
                m_SetupInitComponent.HelperRoutines.OcManagerContext,
                (LPCTSTR) ptszSubComponentId,
                (UINT) OCSELSTATETYPE_CURRENT 
                );

         fOriginalSelectionState = 
             m_SetupInitComponent.HelperRoutines.QuerySelectionState( 
                 m_SetupInitComponent.HelperRoutines.OcManagerContext,
                 ptszSubComponentId,
                 (UINT) OCSELSTATETYPE_ORIGINAL 
                 );

         if ( fCurrentSelectionState == (BOOL) TRUE )
         {
            // Was there a selection state transition?
            if ( fCurrentSelectionState != fOriginalSelectionState )
            {
               ClRtlLogPrint( "A selection state transition was detected.\n" );

               // A selection state transition has occured. Install files.
               dwReturnValue = QueueInstallFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                           ptszSubComponentId,
                                                           hSetupFileQueue );

               ClRtlLogPrint( "QueueInstallFileOperations returned 0x%1!x! to OnOcQueueFileOpsAttended,\n",
                              dwReturnValue );
            }
            else
            {
               ClRtlLogPrint( "NO selection state transition was detected.\n" );

               // The selection state has not been changed. Perform no action.

               dwReturnValue = (DWORD) NO_ERROR;
            }  // Was there a selection state transition?
         }
         else
         {
             // Has there been a selection state transition ?
             ClRtlLogPrint( "In OnOcQueueFileOpsUnattended Cluster service is not selected for installation.\n" );

             if ( fCurrentSelectionState != fOriginalSelectionState )
             {
                // Remove files.

                dwReturnValue = QueueRemoveFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                           ptszSubComponentId,
                                                           hSetupFileQueue );

                ClRtlLogPrint( "QueueRemoveFileOperations returned 0x%1!x! to OnOcQueueFileOpsUnattended.\n",
                               dwReturnValue );
             }
             else
             {
                ClRtlLogPrint( "In OnOcQueueFileOpsUnattended NO selection state transition was detected.\n" );

                // The selection state has not been changed. Perform no action.

                dwReturnValue = (DWORD) NO_ERROR;
             }  // Was there a selection state transition ?
         }
      }
      else
      {
         // A handle to the answer file could not be obtained. Treat it as a UPGRADE.

         ClRtlLogPrint( "InOnOcQueueFileOpsUnattended the handle to the answer file could not be obtained.\n" );

         dwReturnValue = QueueFileOpsUnattendedUpgrade( (LPCTSTR) ptszSubComponentId,
                                                        hSetupFileQueue );

         ClRtlLogPrint( "QueueFileOpsUnattendedUpgrade returned 0x%1!x!.\n", dwReturnValue );
      }
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// QueueFileOpsUnattendedUpgrade
//
// Routine Description:
//    This function queues the file operations appropriate for an unattended
//    upgrade operation.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    hSetupFileQueue - a HSPFILEQ (typedefed in setupapi.h to PVOID)
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::QueueFileOpsUnattendedUpgrade( IN LPCTSTR ptszSubComponentId,
                                                  IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue;
   eClusterInstallState eState;

   // Has Cluster service previously been installed?

   // GetClusterInstallationState reports on the state of the registry value
   // that records the state of the Cluster service installation on NT 5 machines.
   // IsClusterServiceRegistered indicates whether the Cluster service is registered on
   // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
   // upgrading NT 4 machines, GetClusterInstallationState for NT 5 machines.

   BOOL  fClusteringServicePreviouslyInstalled;

   ClRtlGetClusterInstallState( NULL, &eState );
   if ( ( eState != eClusterInstallStateUnknown ) ||
        ( IsClusterServiceRegistered() == (BOOL) TRUE ) )
   {
      fClusteringServicePreviouslyInstalled = (BOOL) TRUE;
   }
   else
   {
      fClusteringServicePreviouslyInstalled = (BOOL) FALSE;
   }

   if ( fClusteringServicePreviouslyInstalled == (BOOL) TRUE )
   {
      // Upgrade the ClusteringService files.

      dwReturnValue = QueueInstallFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                  ptszSubComponentId,
                                                  hSetupFileQueue );

      ClRtlLogPrint( "QueueInstallFileOperations returned 0x%1!x! to QueueFileOpsUnattendedUpgrade.\n",
                     dwReturnValue );
   } // Has Cluster service previously been installed?
   else
   {
      // Since Cluster service has not been previously installed there is
      // nothing to do.

      ClRtlLogPrint( "In QueueFileOpsUnattendedUpgrade Cluster service has never been installed.\n" );

      dwReturnValue = (DWORD) NO_ERROR;
   } // Has Cluster service previously been installed?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcQueueFileOpsAttended
//
// Routine Description:
//    This function processes the OC_QUEUE_FILE_OPS "messages" from the
//    Optional Components Manager during ATTENDED operations.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//    hSetupFileQueue - a HSPFILEQ (typedefed in setupapi.h to PVOID)
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcQueueFileOpsAttended( IN LPCTSTR ptszSubComponentId,
                                             IN OUT HSPFILEQ hSetupFileQueue )
{
   DWORD dwReturnValue;
   eClusterInstallState eState;

   // Is Cluster service selected?

   BOOL  fCurrentSelectionState;
   BOOL  fOriginalSelectionState;

   fCurrentSelectionState =
   m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                            (LPCTSTR) ptszSubComponentId,
                                                            (UINT) OCSELSTATETYPE_CURRENT );

   if ( fCurrentSelectionState == (BOOL) TRUE )
   {
      // The subcomponent is selected. Is this a fresh install ?

      ClRtlLogPrint( "In OnOcQueueFileOpsAttended the current selection state is TRUE.\n" );

      if ( ( (m_SetupInitComponent.SetupData.OperationFlags &
              (DWORDLONG) SETUPOP_STANDALONE) == (DWORDLONG) 0L ) &&
           ( (m_SetupInitComponent.SetupData.OperationFlags &
              (DWORDLONG) SETUPOP_NTUPGRADE) == (DWORDLONG) 0L ) )
      {
         // SETUPOP_STANDALONE flag clear means running under GUI mode setup.
         // SETUPOP_NTUPGRADE flag clear means not performing an upgrade.
         // Both flags clear means a fresh install. Do not check for a selection
         // state transition.

         ClRtlLogPrint( "In OnOcQueueFileOpsAttended this is a CLEAN install.\n" );

         dwReturnValue = QueueInstallFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                     ptszSubComponentId,
                                                     hSetupFileQueue );

         ClRtlLogPrint( "QueueInstallFileOperations returned 0x%1!x! to OnOcQueueFileOpsAttended.\n",
                        dwReturnValue );
      }
      else
      {
         // This is either an upgrade or OC Manager is running stand-alone.

         if ( (m_SetupInitComponent.SetupData.OperationFlags &
               (DWORDLONG) SETUPOP_NTUPGRADE) != (DWORDLONG) 0L )
         {
            ClRtlLogPrint( "In OnOcQueueFileOpsAttended this is an UPGRADE.\n" );

            // This is an upgrade, if Cluster Server has previously been
            // been installed then queue the file copies.

            // Has Cluster service perviously been installed? Ask the Service Control
            // Manager whether the Cluster Service is registered.

            // GetClusterInstallationState reports on the state of the registry value
            // that records the state of the Cluster service installation on NT 5 machines.
            // IsClusterServiceRegistered indicates whether the Cluster service is registered on
            // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
            // upgrading NT 4 machines, GetClusterInstallationState for NT 5 machines.

            BOOL  fClusterServiceRegistered;

            fClusterServiceRegistered = IsClusterServiceRegistered();

            BOOL  fClusterServicePreviouslyInstalled;

            ClRtlGetClusterInstallState( NULL, &eState );
            if ( ( eState != eClusterInstallStateUnknown ) ||
                 ( fClusterServiceRegistered == (BOOL) TRUE ) )
            {
               fClusterServicePreviouslyInstalled = (BOOL) TRUE;
            }
            else
            {
               fClusterServicePreviouslyInstalled = (BOOL) FALSE;
            }

            if ( fClusterServicePreviouslyInstalled == (BOOL) TRUE )
            {
               // Since this is an UPGRADE and Cluster service has been
               // installed there is no need to test for a selection state transition.

               dwReturnValue = QueueInstallFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                           ptszSubComponentId,
                                                           hSetupFileQueue );

               ClRtlLogPrint( "QueueInstallFileOperations returned 0x%1!x! to OnOcQueueFileOpsAttended.\n",
                              dwReturnValue );
            }
            else
            {
               ClRtlLogPrint( "In OnOcQueueFileOps attempted an UPGRADE but Cluster service has never been installed.\n" );

               dwReturnValue = (DWORD) NO_ERROR;
            }  // Was Cluster service perviously installed?
         }
         else
         {
            ClRtlLogPrint( "In OnOcQueueFileOpsAttended this is STANDALONE.\n" );

            // This is not an upgrade. That means Add/Remove Programs must be
            // running. It is necessary to test for a selection state transition.

            fOriginalSelectionState =
            m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                     ptszSubComponentId,
                                                                     (UINT) OCSELSTATETYPE_ORIGINAL );

            // Was there a selection state transition?

            if ( fCurrentSelectionState != fOriginalSelectionState )
            {
               ClRtlLogPrint( "A selection state transition was detected.\n" );

               // A selection state transition has occured. Install files.

               dwReturnValue = QueueInstallFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                           ptszSubComponentId,
                                                           hSetupFileQueue );

               ClRtlLogPrint( "QueueInstallFileOperations returned 0x%1!x! to OnOcQueueFileOpsAttended,\n",
                              dwReturnValue );
            }
            else
            {
               ClRtlLogPrint( "NO selection state transition was detected.\n" );

               // The selection state has not been changed. Perform no action.

               dwReturnValue = (DWORD) NO_ERROR;
            }  // Was there a selection state transition?
         }  // Is this an UPGRADE?
      }  // Is this a clean install?
   }  // Is Cluster service currently selected ?
   else
   {
      ClRtlLogPrint( "In OnOcQueueFileOpsAttended the current selection state is FALSE.\n" );

      // Cluster service is not selected. Is OC Manager running stand-alone ?
      // If not, i.e. if GUI mode setup is running, there is nothing to do.

      if ( (m_SetupInitComponent.SetupData.OperationFlags &
            (DWORDLONG) SETUPOP_STANDALONE) != (DWORDLONG) 0L )
      {
         ClRtlLogPrint( "In OnOcQueueFileOpsAttended this is STANDALONE.\n" );

         // SETUPOP_STANDALONE set implies GUI mode setup is not running. If
         // there was a selection state change (to unselected) then remove files.

         fOriginalSelectionState =
         m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                  ptszSubComponentId,
                                                                  (UINT) OCSELSTATETYPE_ORIGINAL );

         // Has there been a selection state transition ?

         if ( fCurrentSelectionState != fOriginalSelectionState )
         {
            // Remove files.

            dwReturnValue = QueueRemoveFileOperations( m_SetupInitComponent.ComponentInfHandle,
                                                       ptszSubComponentId,
                                                       hSetupFileQueue );

            ClRtlLogPrint( "QueueRemoveFileOperations returned 0x%1!x! to OnOcQueueFileOpsAttended.\n",
                           dwReturnValue );
         }
         else
         {
            ClRtlLogPrint( "NO selection state transition was detected.\n" );

            // The selection state has not been changed. Perform no action.

            dwReturnValue = (DWORD) NO_ERROR;
         }  // Was there a selection state transition ?
      }
      else
      {
         // GUI mode setup is running and Cluster service is not selected.
         // There is nothing to do.

         dwReturnValue = (DWORD) NO_ERROR;
      }  // Is GUI mode setup running ?
   }  // Is Cluster service currently selected ?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// CompleteUninstallingClusteringService
//
// Routine Description:
//    This function completes uninstalling ClusteringService by queuing the
//    registry operations to delete the Cluster service registry keys,
//    cleaning up the Start Menu, removing the Network Provider, and requesting
//    a reboot.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CompleteUninstallingClusteringService( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;
   eClusterInstallState ecisInstallState;
   BOOL bRebootRequired = FALSE;

   // Update the "progress text"

   CString  csProgressText;

   csProgressText.LoadString( IDS_REMOVING_CLUS_SERVICE );

   m_SetupInitComponent.HelperRoutines.SetProgressText( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                        (LPCTSTR) csProgressText );

   // Request that the system reboot only if clusdisk has been started.
   // We can deduce this by looking at the cluster installation state.
   // If the state is not eClusterInstallStateFilesCopied then it means that
   // ClusCfg may have run successfully and therefore, clusdisk may have been started.

   // We must call ClRtlGetClusterInstallState before calling UninstallRegistryOperations.
   // Otherwise it will always return eClusterInstallStateUnknown!

   // If ClRtlGetClusterInstallState fails, reboot anyway.
   // If it succeeds, reboot only if ClusCfg has completed successfully.
   if (    ( ClRtlGetClusterInstallState( NULL, &ecisInstallState ) != ERROR_SUCCESS ) 
        || ( ecisInstallState != eClusterInstallStateFilesCopied ) 
      )
   {
       bRebootRequired = TRUE;
   }

   // Delete registry entries. Queue the base registry operations.

   dwReturnValue = UninstallRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                ptszSubComponentId );

   ClRtlLogPrint( "UninstallRegistryOperations returned 0x%1!x! to CompleteUninstallingClusteringService.\n",
                  dwReturnValue );

   //
   // Remove the cluster item from the start menu
   //

   CString csGroupName;
   CString csItemName;

   csGroupName.LoadString( IDS_START_GROUP_NAME );
   csItemName.LoadString( IDS_START_ITEM_NAME );

   DeleteLinkFile( CSIDL_COMMON_PROGRAMS,
                   (LPCWSTR) csGroupName,
                   (LPCWSTR) csItemName,
                   (BOOL) FALSE );

   // Delete the cluster directory. BUGBUG

   //
   // Remove the cluster network provider
   //

   dwReturnValue = RemoveNetworkProvider();

   if ( bRebootRequired )
   {
       BOOL  fRebootRequestStatus;

       // In the following call the value passed in the second parameter
       // was chosen arbitrarily. Ocmanage.h implies that the parameter is not used.

       fRebootRequestStatus =
          m_SetupInitComponent.HelperRoutines.SetReboot( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                         (BOOL) TRUE );
   }

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OpenClusterRegistryRoot
//
// Routine Description:
//    This function retuns a handle to the root key of the Cluster hive.
//    It will load the hive if necessary.
//
// Arguments:
//    none
//
//
// Return Value:
//    If success, handle to the cluster root key. Otherwise, NULL
//
//--
/////////////////////////////////////////////////////////////////////////////

HKEY CClusocmApp::OpenClusterRegistryRoot( void )
{
   BOOL  fReturnValue;

   HKEY hClusterKey = NULL;
   LONG lReturnValue;

   // Attempt to open the Cluster Registry key.

   CString  csClusterRegKey;

   csClusterRegKey = CLUSREG_KEYNAME_CLUSTER;

   lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE, csClusterRegKey,
                                0, KEY_READ, &hClusterKey);

   if ( lReturnValue != ERROR_SUCCESS )
   {
      ClRtlLogPrint( "In OpenClusterRegistryRoot, the first attempt to open the Cluster key failed.\n" );

      // The Cluster hive is not currently loaded. This condition means that
      // the cluster service has not been started. Attempt to load the Cluster
      // hive so that it can be read.

      // First, locate the Cluster hive file. It should be in the location
      // specified for the ClusterFiles entry in the [DestinationDirs] section
      // of clusocm.inf.

      CString  csClusterHiveFilePath;

      fReturnValue = LocateClusterHiveFile( (CString &) csClusterHiveFilePath );

      ClRtlLogPrint( "LocateClusterHiveFile returned 0x%1!x! to OpenClusterRegistryRoot.\n",
                     fReturnValue );

      // Was the Cluster hive file located?

      if ( fReturnValue == (BOOL) TRUE )
      {
         // The Cluster hive file was located.
         // Attempt to load the cluster hive.

         BOOLEAN  OriginalState;

         // I'm not sure what the following function does, but the prototype is
         // in sdk\inc\ntrtl.h. Look in stdafx.h for the inclusion of ntrtl.h. I
         // replicated the logic that was used in newsetup.h to make it work.

         lReturnValue = RtlAdjustPrivilege( SE_RESTORE_PRIVILEGE,
                                             TRUE,
                                             FALSE,
                                             &OriginalState );

         if ( lReturnValue == ERROR_SUCCESS )
         {
            // Attempt to Load the Cluster Hive.

            lReturnValue = RegLoadKey( HKEY_LOCAL_MACHINE,
                                       csClusterRegKey,
                                       csClusterHiveFilePath );

            if ( lReturnValue == ERROR_SUCCESS )
            {

               // Now that the Cluster hive has been loaded, attempt to open the
               // Cluster registry key.

               lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                            csClusterRegKey,
                                            0, KEY_READ,
                                            &hClusterKey );

               // lReturnValue will be tested by the next BLOCK of code.
            }

            // Undo whatever the preceding call to RtlAdjustPrivilege() did.

            RtlAdjustPrivilege( SE_RESTORE_PRIVILEGE,
                                OriginalState,
                                FALSE,
                                &OriginalState );

            if ( lReturnValue != ERROR_SUCCESS )
            {
                // Set the error code.
                SetLastError( lReturnValue );
            }

         }
         else
         {
            // The initial call to RtlAdjustPrivilege FAILED.
            SetLastError( lReturnValue );

            // A return value of TRUE will allow the uninstall operation to
            // continue. Since lReturnValue is NOT ERROR_SUCCESS no additional
            // processing will be performed by this function.

         }  // Did RtlAdjustPrivilege succeed?
      }
      else
      {
         // The Cluster hive file was not located. Set last error and return
         // a null handle

         ClRtlLogPrint( "OpenClusterRegistryRoot couldn't locate the hive files\n" );

         SetLastError( ERROR_FILE_NOT_FOUND );

      }  // Was the cluster hive file located?
   }  // Was the Cluster registry key opened successfully?

   return ( hClusterKey );
} // OpenClusterRegistryRoot

/////////////////////////////////////////////////////////////////////////////
//++
//
// FindNodeNumber
//
// Routine Description:
//    This function looks through the cluster's Node key and returns a pointer to
//    the node number string of the current node.
//
// Arguments:
//    ClusterKey - handle to cluster root key
//
//    NodeNumberString - pointer to location that receives the node number
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

PWSTR CClusocmApp::FindNodeNumber( HKEY ClusterKey )
{
   WCHAR nodeName[ MAX_COMPUTERNAME_LENGTH + 1 ];
   DWORD nodeNameLength = sizeof( nodeName );
   WCHAR registryNodeName[ MAX_COMPUTERNAME_LENGTH + 1 ];
   DWORD registryNodeNameLength;
   HKEY nodesKey = NULL;
   HKEY nodesEnumKey = NULL;
   DWORD status;
   PWSTR nodeNumberString;
   DWORD nodeNumberStringLength;
   DWORD index = 0;
   DWORD dataType;

   //
   // allocate space for the node number string
   //
   nodeNumberString = (PWSTR)LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT,
                                         ( CS_MAX_NODE_ID_LENGTH + 1 ) * sizeof( WCHAR ));
   if ( nodeNumberString == NULL ) {
       status = GetLastError();
       ClRtlLogPrint( "FindNodeNumber couldn't allocate node number buffer: %1!u!\n",
                      status );
       return NULL;
   }

   //
   // get our node name and a handle to the root key in the cluster hive
   //

   if ( !GetComputerName( nodeName, &nodeNameLength )) {
      status = GetLastError();
      ClRtlLogPrint( "FindNodeNumber failed to get computer name: %1!u!\n",
                     status );

      goto error_exit;
   }

   status = RegOpenKeyEx( ClusterKey,
                          CLUSREG_KEYNAME_NODES,
                          0,
                          KEY_READ,
                          &nodesKey );

   if ( status != ERROR_SUCCESS ) {
      ClRtlLogPrint( "FindNodeName failed to open Nodes key: %1!u!\n",
                     status );
      goto error_exit;
   }

   //
   // enum the entries under the Nodes key
   //
   do {
       nodeNumberStringLength = sizeof( nodeNumberString );
       status = RegEnumKeyEx( nodesKey,
                              index,
                              nodeNumberString,
                              &nodeNumberStringLength,
                              NULL,
                              NULL,
                              NULL,
                              NULL );

       if ( status == ERROR_NO_MORE_ITEMS ) {
           ClRtlLogPrint( "FindNodeNumber finished enum'ing the Nodes key\n" );
           break;
       } else if ( status != ERROR_SUCCESS ) {
           ClRtlLogPrint( "FindNodeNumber failed to enum Nodes key: %1!u!\n",
                          status );
           goto error_exit;
       }

       //
       // open this key and get the Name value
       //
       status = RegOpenKeyEx( nodesKey,
                              nodeNumberString,
                              0,
                              KEY_READ,
                              &nodesEnumKey );

       if ( status != ERROR_SUCCESS ) {
           ClRtlLogPrint( "FindNodeNumber failed to open Nodes key %1!ws!: %2!u!\n",
                          nodeNumberString,
                          status );
           goto error_exit;

       }

       registryNodeNameLength = sizeof( registryNodeName );
       status = RegQueryValueEx( nodesEnumKey,
                                 CLUSREG_NAME_NODE_NAME,
                                 NULL,
                                 &dataType,
                                 (LPBYTE)registryNodeName,
                                 &registryNodeNameLength );

       RegCloseKey( nodesEnumKey );
       nodesEnumKey = NULL;

       if ( status != ERROR_SUCCESS || dataType != REG_SZ ) {
           ClRtlLogPrint( "FindNodeNumber failed to get NodeName value: "
                          "status %1!u!, data type %2!u!\n",
                          status, dataType );
           goto error_exit;

       }

       //
       // finally, we get to see if this is our node
       //
       if ( lstrcmpiW( nodeName, registryNodeName ) == 0 ) {
           break;
       }

       ++index;
   } while ( TRUE );

error_exit:
   if ( nodesKey != NULL ) {
       RegCloseKey( nodesKey );
   }

   if ( nodesEnumKey != NULL ) {
       RegCloseKey( nodesEnumKey );
   }

   if ( status != ERROR_SUCCESS || lstrlenW( nodeNumberString ) == 0 ) {
       LocalFree( nodeNumberString );
       nodeNumberString = NULL;
   }

   return nodeNumberString;
} // FindNodeNumber

/////////////////////////////////////////////////////////////////////////////
//++
//
// GetConnectionName
//
// Routine Description:
//    This function finds the matching connection object based on the IP address
//    of the network interface. NOTE that the return value is also pointed to in
//    the Adapter Info of the Adapter enum struct. This return value must not be
//    freed with LocalFree.
//
// Arguments:
//    NetInterfacesKey - handle to key of network interface
//
//    AdapterEnum - pointer to struct with adapter/IP interface info
//
// Return Value:
//    If successful, pointer to connectoid name. Otherwise, NULL with Win32
//    error available from GetLastError().
//
//--
/////////////////////////////////////////////////////////////////////////////

PWSTR CClusocmApp::GetConnectionName( HKEY NetInterfacesGuidKey,
                                      PCLRTL_NET_ADAPTER_ENUM AdapterEnum )
{
    WCHAR                     ipAddress[ 16 ];
    DWORD                     ipAddressLength = sizeof( ipAddress );
    DWORD                     status;
    PCLRTL_NET_ADAPTER_INFO   adapterInfo;
    PCLRTL_NET_INTERFACE_INFO interfaceInfo;
    PWSTR                     connectoidName = NULL;
    DWORD                     dataType;

    //
    // query for the netIf's IP address
    //
    status = RegQueryValueEx( NetInterfacesGuidKey,
                              CLUSREG_NAME_NETIFACE_ADDRESS,
                              NULL,
                              &dataType,
                              (LPBYTE)ipAddress,
                              &ipAddressLength );


    if ( status != ERROR_SUCCESS || dataType != REG_SZ ) {
        ClRtlLogPrint( "GetConnectionName failed to get Address value: "
                       "status %1!u!, data type %2!u!\n",
                       status, dataType );

        SetLastError( ERROR_INVALID_DATA );
        goto error_exit;
    }

    adapterInfo = ClRtlFindNetAdapterByInterfaceAddress( AdapterEnum,
                                                         ipAddress,
                                                         &interfaceInfo );
    if ( adapterInfo != NULL ) {
        connectoidName = adapterInfo->ConnectoidName;
    }

error_exit:
    return connectoidName;
} // GetConnectionName

/////////////////////////////////////////////////////////////////////////////
//++
//
// GetTCPAdapterInfo
//
// Routine Description:
//    This function opens a handle to the service controller and tries to
//    start the DHCP service. If successful, then TCP is queried for its
//    NIC and IP interface info.
//
// Arguments:
//    none
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

PCLRTL_NET_ADAPTER_ENUM CClusocmApp::GetTCPAdapterInfo( void )
{
    SC_HANDLE schSCManager;
    SC_HANDLE serviceHandle;
    PCLRTL_NET_ADAPTER_ENUM adapterEnum = NULL;
    BOOL success;
    DWORD retryCount;
    DWORD status;

    schSCManager = OpenSCManager(NULL,                   // machine (NULL == local)
                                 NULL,                   // database (NULL == default)
                                 SC_MANAGER_CONNECT); // access required

    if ( schSCManager ) {

        //
        // start DHCP for nodes that are using it. it will cause TCP to start
        // as well. For statically configured nodes, starting DHCP causes no
        // harm.
        //
        serviceHandle = OpenService(schSCManager,
                                    L"DHCP",
                                    SERVICE_START );

        if ( serviceHandle ) {
            success = StartService( serviceHandle, 0, NULL );

            if ( !success ) {
                status = GetLastError();
                if ( status == ERROR_SERVICE_ALREADY_RUNNING ) {
                    success = TRUE;
                }
            }

            if ( success ) {

                //
                // wait while TCP gets bound to all its adapters and discovers
                // its IP interfaces.
                //
                retryCount = 3;
                do {
                    Sleep( 2000 );
                    adapterEnum = ClRtlEnumNetAdapters();

                    if (adapterEnum != NULL) {
                        break;
                    }
                } while ( --retryCount != 0 );

                if (adapterEnum == NULL) {
                    status = GetLastError();
                    ClRtlLogPrint( "Failed to obtain local system network config. "
                                   "status %1!u! retryCount %2!u!\n",
                                   status, retryCount);
                }
            } else {
                ClRtlLogPrint( "StartService returned %1!u! to StartTCP\n", status);
            }
            CloseServiceHandle( serviceHandle );
        } else {
            status = GetLastError();
            ClRtlLogPrint( "OpenService returned %1!u! to StartTCP\n", status);
        }

        CloseServiceHandle( schSCManager );
    } else {
        status = GetLastError();
        ClRtlLogPrint("OpenSCManager returned %1!u! to StartTCP\n", status);
    }

    return adapterEnum;
}

/////////////////////////////////////////////////////////////////////////////
//++
//
// RenameConnectionObjects
//
// Routine Description:
//    This function walks through the node's network interfaces and changes,
//    if necessary, their associated connection objects.
//
// Arguments:
//    none
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::RenameConnectionObjects( void )
{
   HKEY                       clusterKey;
   HKEY                       netInterfacesKey = NULL;
   HKEY                       netInterfacesGuidKey = NULL;
   HKEY                       networkGuidKey = NULL;
   HKEY                       networksKey = NULL;
   DWORD                      status;
   PCLRTL_NET_ADAPTER_ENUM    adapterEnum = NULL;
   PCLRTL_NET_ADAPTER_INFO    adapterInfo = NULL;
   PCLRTL_NET_INTERFACE_INFO  adapterIfInfo = NULL;
   DWORD                      hiddenAdapterCount = 0;
   PWSTR                      ourNodeNumber = NULL;
   DWORD                      index = 0;
   WCHAR                      netIfGuidString[ CS_NETINTERFACE_ID_LENGTH + 1 ];
   DWORD                      netIfGuidStringLength;
   WCHAR                      networkGuidString[ CS_NETWORK_ID_LENGTH + 1 ];
   DWORD                      networkGuidStringLength;
   WCHAR                      netIfNodeNumber[ CS_MAX_NODE_ID_LENGTH + 1 ];
   DWORD                      netIfNodeNumberLength;
   DWORD                      dataType;
   PWSTR                      connectoidName;
   PWSTR                      networkName = NULL;
   DWORD                      networkNameLength;

   //
   // initialize COM
   //
   status = CoInitializeEx( NULL, COINIT_DISABLE_OLE1DDE | COINIT_MULTITHREADED );
   if ( !SUCCEEDED( status )) {
      ClRtlLogPrint( "RenameConnectionObjects Couldn't init COM %1!08X!\n", status );
      return status;
   }

   //
   // get a handle to the root key in the cluster hive
   //
   clusterKey = OpenClusterRegistryRoot();

   if ( clusterKey == NULL ) {
      status = GetLastError();
      ClRtlLogPrint( "OpenClusterRegistryRoot returned %1!u! to RenameConnectionObjects\n",
                     status );
      goto error_exit;
   }

   //
   // start TCP if necessary
   //
   adapterEnum = GetTCPAdapterInfo();
   if ( adapterEnum == NULL ) {
      status = GetLastError();

      if ( status == ERROR_SUCCESS ) {
          ClRtlLogPrint( "No usable network adapters are installed in this system.\n" );
      } else {
          ClRtlLogPrint( "GetTCPAdapterInfo returned %1!u! to RenameConnectionObjects\n",
                         status );
      }
      goto error_exit;
   }

   //
   // Ignore all adapters which are hidden or have an address of 0.0.0.0.
   //
   for (adapterInfo = adapterEnum->AdapterList;
        adapterInfo != NULL;
        adapterInfo = adapterInfo->Next
        )
   {
       if (adapterInfo->Flags & CLRTL_NET_ADAPTER_HIDDEN) {
           ClRtlLogPrint( "Ignoring hidden adapter '%1!ws!'.\n",
                          adapterInfo->DeviceName );
           adapterInfo->Ignore = TRUE;
           hiddenAdapterCount++;
       }
       else {
           adapterIfInfo = ClRtlGetPrimaryNetInterface(adapterInfo);

           if (adapterIfInfo->InterfaceAddress == 0) {
               ClRtlLogPrint( "Ignoring adapter '%1!ws!' because primary address is 0.0.0.0.\n",
                              adapterInfo->DeviceName );
               adapterInfo->Ignore = TRUE;
               hiddenAdapterCount++;
           }
       }
   }

   if ((adapterEnum->AdapterCount - hiddenAdapterCount) == 0) {
       ClRtlLogPrint( "No usable network adapters are installed in this system.\n" );
       status = ERROR_SUCCESS;
       goto error_exit;
   }

   ClRtlLogPrint( "RenameConnectionObject found %1!u! adapters to process.\n",
                  adapterEnum->AdapterCount - hiddenAdapterCount );

   //
   // now find our node number by looking through the node key in the cluster
   // registry and comparing our netbios name with the names in the registry
   //
   ourNodeNumber = FindNodeNumber( clusterKey );
   if ( ourNodeNumber == NULL ) {
       status = GetLastError();
       ClRtlLogPrint( "FindNodeNumber failed: status %1!u!\n",
                      status );
       goto error_exit;
   }

   //
   // open the network and network interface keys
   //
   status = RegOpenKeyEx( clusterKey,
                          CLUSREG_KEYNAME_NETWORKS,
                          0,
                          KEY_READ,
                          &networksKey );

   if ( status != ERROR_SUCCESS ) {
       ClRtlLogPrint( "RenameConnectionObjects failed to open Networks key: %1!u!\n",
                      status );
       goto error_exit;

   }

   status = RegOpenKeyEx( clusterKey,
                          CLUSREG_KEYNAME_NETINTERFACES,
                          0,
                          KEY_READ,
                          &netInterfacesKey );

   if ( status != ERROR_SUCCESS ) {
       ClRtlLogPrint( "RenameConnectionObjects failed to open NetworkInterfaces key: %1!u!\n",
                      status );
       goto error_exit;

   }

   //
   // enum the NetworkInterfaces key, looking for interfaces on this node. If
   // we find one, get their IP address and find the corresponding network and
   // connection object.
   //
   do {
       netIfGuidStringLength = sizeof( netIfGuidString );
       status = RegEnumKeyEx( netInterfacesKey,
                              index,
                              netIfGuidString,
                              &netIfGuidStringLength,
                              NULL,
                              NULL,
                              NULL,
                              NULL );

       if ( status == ERROR_NO_MORE_ITEMS ) {
           ClRtlLogPrint( "RenameConnectionObjects finished enum'ing the "
                          "NetworkInterfaces key\n" );

           status = ERROR_SUCCESS;
           break;
       } else if ( status != ERROR_SUCCESS ) {
           ClRtlLogPrint( "RenameConnectionObjects failed to enum NetworkInterfaces "
                          "key: %1!u!\n",
                          status );
           goto error_exit;
       }

       //
       // open this key and get the Node value
       //
       status = RegOpenKeyEx( netInterfacesKey,
                              netIfGuidString,
                              0,
                              KEY_READ,
                              &netInterfacesGuidKey );

       if ( status != ERROR_SUCCESS ) {
           ClRtlLogPrint( "RenameConnectionObjects failed to open NetworkIntefaces "
                          "subkey %1!ws!: %2!u!\n",
                          netIfGuidString,
                          status );
           goto error_exit;

       }

       netIfNodeNumberLength = sizeof( netIfNodeNumber );
       status = RegQueryValueEx( netInterfacesGuidKey,
                                 CLUSREG_NAME_NETIFACE_NODE,
                                 NULL,
                                 &dataType,
                                 (LPBYTE)netIfNodeNumber,
                                 &netIfNodeNumberLength );


       if ( status != ERROR_SUCCESS || dataType != REG_SZ ) {
           ClRtlLogPrint( "RenameConnectionObjects failed to get Node value: "
                          "status %1!u!, data type %2!u!\n",
                          status, dataType );
           status = ERROR_INVALID_DATA;
           goto error_exit;

       }

       //
       // if this is one our interfaces, then adjust the name of the
       // associated connection object
       //
       if ( lstrcmpiW( ourNodeNumber, netIfNodeNumber ) == 0 ) {
           ClRtlLogPrint( "Net Interface %1!ws! is on this node.\n",
                          netIfGuidString );

           connectoidName = GetConnectionName( netInterfacesGuidKey,
                                               adapterEnum );

           if ( connectoidName != NULL ) {

               ClRtlLogPrint( "Associated connectoid name is '%1!ws!'.\n",
                              connectoidName);

               //
               // look up network GUID and open its Key to get the network name
               //
               networkGuidStringLength = sizeof( networkGuidString );
               status = RegQueryValueEx( netInterfacesGuidKey,
                                         CLUSREG_NAME_NETIFACE_NETWORK,
                                         NULL,
                                         &dataType,
                                         (LPBYTE)networkGuidString,
                                         &networkGuidStringLength );

               if ( status != ERROR_SUCCESS || dataType != REG_SZ ) {
                   ClRtlLogPrint( "RenameConnectionObjects failed to get Network value: "
                                  "status %1!u!, data type %2!u!\n",
                                  status, dataType );
                   status = ERROR_INVALID_DATA;
                   goto error_exit;
               }

               status = RegOpenKeyEx( networksKey,
                                      networkGuidString,
                                      0,
                                      KEY_READ,
                                      &networkGuidKey );

               if ( status != ERROR_SUCCESS ) {
                   ClRtlLogPrint( "RenameConnectionObjects failed to open networks "
                                  "subkey %1!ws!: %2!u!\n",
                                  networkGuidString,
                                  status );
                   goto error_exit;

               }

               //
               // query the Name value to get its size, allocate a buffer large
               // enough to hold it and request the data contained in the value.
               //
               networkNameLength = 0;
               status = RegQueryValueEx( networkGuidKey,
                                         CLUSREG_NAME_NET_NAME,
                                         NULL,
                                         &dataType,
                                         (LPBYTE)NULL,
                                         &networkNameLength );

               if ( status != ERROR_SUCCESS || dataType != REG_SZ ) {
                   ClRtlLogPrint( "RenameConnectionObjects failed to get Network Name "
                                  "size: status %1!u!, data type %2!u!\n",
                                  status, dataType );
                   status = ERROR_INVALID_DATA;
                   goto error_exit;
               }

               networkName = (PWCHAR)LocalAlloc( LMEM_FIXED, networkNameLength );
               if ( networkName == NULL ) {
                   status = GetLastError();
                   ClRtlLogPrint( "RenameConnectionObjects failed to alloc space "
                                  "for network name: %1!u!\n",
                                  status );
                   goto error_exit;
               }

               status = RegQueryValueEx( networkGuidKey,
                                         CLUSREG_NAME_NET_NAME,
                                         NULL,
                                         NULL,
                                         (LPBYTE)networkName,
                                         &networkNameLength );

               RegCloseKey( networkGuidKey );
               networkGuidKey = NULL;

               if ( status != ERROR_SUCCESS ) {
                   ClRtlLogPrint( "RenameConnectionObjects failed to get Network "
                                  "Name: status %1!u!\n",
                                  status );
                   goto error_exit;
               }

               ClRtlLogPrint( "Network name is '%1!ws!'.\n", networkName );

               if ( lstrcmpiW( connectoidName, networkName ) != 0 ) {
                   ClRtlLogPrint( "Changing connectoid name to '%1!ws!'.\n", networkName );
                   ClRtlFindConnectoidByNameAndSetName(
                       connectoidName,
                       networkName
                       );
               }
           } else {
               ClRtlLogPrint( "RenameConnectionObjects failed to get connection name.\n" );
           }
       }

       RegCloseKey( netInterfacesGuidKey );
       netInterfacesGuidKey = NULL;

       ++index;
   } while ( TRUE );

error_exit:
   if ( clusterKey != NULL ) {
      RegCloseKey( clusterKey );
   }

   if ( netInterfacesKey != NULL ) {
      RegCloseKey( netInterfacesKey );
   }

   if ( netInterfacesGuidKey != NULL ) {
      RegCloseKey( netInterfacesGuidKey );
   }

   if ( networksKey != NULL ) {
      RegCloseKey( networksKey );
   }

   if ( networkGuidKey != NULL ) {
      RegCloseKey( networkGuidKey );
   }

   if ( ourNodeNumber != NULL ) {
       LocalFree( ourNodeNumber );
   }

   if ( networkName != NULL ) {
       LocalFree( networkName );
   }

   if ( adapterEnum != NULL ) {
       ClRtlFreeNetAdapterEnum( adapterEnum );
   }

   CoUninitialize();

   return status;
} // RenameConnectionObjects



/////////////////////////////////////////////////////////////////////////////
//++
//
// CompleteUpgradingClusteringService
//
// Routine Description:
//    This function completes the process of upgrading a Cluster service installation.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CompleteUpgradingClusteringService( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;
   eClusterInstallState eState;

   // This is an upgrade, if Cluster Server has previously been
   // been installed then queue the registry additions.

   // GetClusterInstallationState reports on the state of the registry value
   // that records the state of the Cluster service installation on NT 5 machines.
   // IsClusterServiceRegistered indicates whether the Cluster service is registered on
   // BOTH NT 4 and NT 5 machines. Both tests are required: IsClusterServiceRegistered for
   // upgrading NT 4 machines, ClRtlGetClusterInstallState for NT 5 machines.

   BOOL  fClusterServiceRegistered;

   fClusterServiceRegistered = IsClusterServiceRegistered();

   ClRtlGetClusterInstallState( NULL, &eState );

   if ( ( eState != eClusterInstallStateUnknown ) ||
        ( fClusterServiceRegistered == (BOOL) TRUE ) )
   {
      // Update the progress bar.

      m_SetupInitComponent.HelperRoutines.TickGauge( m_SetupInitComponent.HelperRoutines.OcManagerContext );

      // Update the "progress text"

      CString  csProgressText;

      csProgressText.LoadString( IDS_UPGRADING_CLUS_SERVICE );

      m_SetupInitComponent.HelperRoutines.SetProgressText( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                           (LPCTSTR) csProgressText );
      ClRtlLogPrint( "In CompleteUpgradingClusteringService Cluster service has previously been installed.\n" );

      // Perform the base registry operations.

      dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                 ptszSubComponentId );

      ClRtlLogPrint( "The base call to PerformRegistryOperations returned 0x%1!x! to "
                     "CompleteUpgradingClusteringService.\n",
                     dwReturnValue );

      // Were the base registry operations performed successfully?

      CString  csUpgrade;

      if ( dwReturnValue == (DWORD) NO_ERROR )
      {
         // Perform the registry operations that pertain to UPGRADE.

         csUpgrade = UPGRADE_INF_KEY;

         dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                    csUpgrade );

         ClRtlLogPrint( "The UPGRADE call to PerformRegistryOperations returned 0x%1!x! "
                        "to CompleteUpgradingClusteringService.\n",
                        dwReturnValue );
      }  // Were the base registry operations performed successfully?

      // Were the UPGRADE registry operations performed successfully?

      if ( dwReturnValue == (DWORD) NO_ERROR )
      {
         // If the Cluster Service is registered with the Service Control Manager then
         // the registry keys that add "Configure Cluster service" to the Welcome UI
         // should be eliminated because the service has already been configured.

         if ( fClusterServiceRegistered == (BOOL) TRUE )
         {
            CString  csClusterService;

            csClusterService = CLUSTER_SERVICE_NAME;

            CString  csRegistered;

            csRegistered = REGISTERED_INF_KEY_FRAGMENT;

            CString  csSectionName;

            csSectionName = csUpgrade + (CString) _T(".") +
                            csClusterService + (CString) _T(".") +
                            csRegistered;

            dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                       csSectionName );

            ClRtlLogPrint( "The REGISTERED call to PerformRegistryOperations returned 0x%1!x! "
                           "to CompleteUpgradingClusteringService.\n",
                           dwReturnValue );
         }

         // Were the Welcome UI registry operations performed successfully?

         if ( dwReturnValue == (DWORD) NO_ERROR )
         {
            ClRtlLogPrint( "In CompleteUpgradingClusteringService the registry operations "
                           "were performed successfully.\n" );

            // In NT 4 the ImagePath value in HKLM\System\CurrentControlSet\Services\ClusSvc
            // is set to reference clusprxy.exe. In NT 5 it must reference clussvc.exe.

            // The ImagePath value in the Cluster Service registry key must
            // be set programmatically because the path to the service
            // executable image is not known to the component INF file. That
            // is because in NT 4 "Cluster Server" as it was known, could be
            // installed in an arbitrary location.

            // There is no straightforward way to determine whether this upgrade
            // is from NT 4 or NT 5. The assumption made here is that if the
            // Cluster Service is registered with the Service Control Manager
            // then it was upgraded in place, regardless of the OS level. In that
            // case it will be safe to set the ImagePath value to reference clussvc.exe
            // in that location. If the Cluster Service is not registered with the
            // SC Manager then it is safe to leave that reg value alone because when
            // cluscfg.exe configures the Cluster service the ImagePath value will
            // get written correctly.

            if ( fClusterServiceRegistered == (BOOL) TRUE )
            {
               dwReturnValue = UpgradeClusterServiceImagePath();

               ClRtlLogPrint( "UpgradeClusterServiceImagePath returned 0x%1!x! to "
                              "CompleteUpgradingClusteringService.\n",
                              dwReturnValue );
            }

            // Was the ImagePath set successfully?

            if ( dwReturnValue == (DWORD) NO_ERROR )
            {
               // did the connection objects get renamed?

               dwReturnValue = RenameConnectionObjects();

               ClRtlLogPrint( "RenameConnectionObjects returned 0x%1!x! to "
                              "CompleteUpgradingClusteringService.\n",
                              dwReturnValue );

               if ( dwReturnValue == (DWORD) NO_ERROR )
               {
                  //
                  // set the service controller default failure actions
                  //
                  dwReturnValue = ClRtlSetSCMFailureActions( NULL );

                  ClRtlLogPrint( "ClRtlSetDefaultFailureActions returned 0x%1!x! to "
                                 "CompleteUpgradingClusteringService.\n",
                                 dwReturnValue );

                  if ( dwReturnValue == (DWORD) NO_ERROR )
                  {
                     // In NT4 the "Display Name" for the Cluster service was
                     // "Cluster Server". It must be programatically changed to
                     // "Cluster service".

                     if ( fClusterServiceRegistered == (BOOL) TRUE )
                     {
                        CString  csClusterService;

                        csClusterService = CLUSTER_SERVICE_NAME;

                        SC_HANDLE hscServiceMgr;
                        SC_HANDLE hscService;

                        // Connect to the Service Control Manager and open the specified service
                        // control manager database.

                        hscServiceMgr = OpenSCManager( NULL, NULL, GENERIC_READ | GENERIC_WRITE );

                        // Was the service control manager database opened successfully?

                        if ( hscServiceMgr != NULL )
                        {
                           // The service control manager database is open.
                           // Get the current display name of the service.

                           TCHAR tszDisplayName[50];     // arbitrary size
                           DWORD dwBufferSize = 50;

                           BOOL fStatus;

                           fStatus = GetServiceDisplayName( hscServiceMgr,
                                                            (LPCTSTR) csClusterService,
                                                            tszDisplayName,
                                                            &dwBufferSize );

                           if ( fStatus == (BOOL) TRUE )
                           {
                              // Is the service name "Cluster service"?

                              if ( _tcscmp( tszDisplayName, _T("Cluster service") ) != 0 )
                              {
                                 // The display name is not correct. Change it.
                                 // Open a handle to the Service. Allow configuration changes.

                                 hscService = OpenService( hscServiceMgr,
                                                           (LPWSTR) (LPCTSTR) csClusterService,
                                                           SERVICE_CHANGE_CONFIG );

                                 // Was the handle to the service opened?

                                 if ( hscService != NULL )
                                 {
                                    // A valid handle to the Service was obtained.

                                    fStatus = ChangeServiceConfig( hscService,
                                                                   SERVICE_NO_CHANGE,
                                                                   SERVICE_NO_CHANGE,
                                                                   SERVICE_NO_CHANGE,
                                                                   NULL,
                                                                   NULL,
                                                                   NULL,
                                                                   NULL,
                                                                   NULL,
                                                                   NULL,
                                                                   _T("Cluster service") );

                                    // Was the change succesful?

                                    if ( fStatus != (BOOL) TRUE )
                                    {
                                       dwReturnValue = GetLastError();

                                       ClRtlLogPrint( "Could not change the cluster service display name"
                                                      " in CompleteUpgradingClusteringService."
                                                      " Error: 0x%1!x!.\n",
                                                      dwReturnValue );
                                    }

                                    // Close the handle to the Cluster Service.

                                    CloseServiceHandle( hscService );
                                 } // service opened?
                              } // display name correct?
                           }
                           else
                           {
                              // Couldn't get the display name.

                              dwReturnValue = GetLastError();

                              ClRtlLogPrint( "Could not query the cluster service display name"
                                             " in CompleteUpgradingClusteringService."
                                             " Error: 0x%1!x!.\n",
                                             dwReturnValue );
                           }// got display name?

                           // Close the handle to the Service Control Manager.

                           CloseServiceHandle( hscServiceMgr );
                        }
                     } // cluster service registered?

                     if ( dwReturnValue == (DWORD) NO_ERROR )
                     {
                        // Register ClAdmWiz.

                        // First, get the path to the cluster directory.

                        CString  csTemp;

                        csTemp =  CLUSTER_DIRECTORY;
   
                        TCHAR tszPathToClusterDir[_MAX_PATH];
   
                        if ( ExpandEnvironmentStrings( (LPCTSTR) csTemp,
                                                       tszPathToClusterDir, _MAX_PATH ) > 0L )
                        {
                           HRESULT  hResult;

                           // Attempt to initialize the COM library.

                           hResult = CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );

                           if ( (hResult == S_OK) ||                 // success
                                (hResult == S_FALSE) ||              // already initialized
                                (hResult == RPC_E_CHANGED_MODE) )    // previous initialization specified 
                                                                     // a different concurrency model
                           {
                              DWORD dwRCOrv;
   
                              // RegisterCOMObject returns ERROR_SUCCESS on success.
   
                              dwRCOrv = RegisterCOMObject( TEXT("ClAdmWiz.dll"), tszPathToClusterDir );
   
                              if ( dwRCOrv != (DWORD) ERROR_SUCCESS )
                              {
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService failed to register ClAdmWiz.dll with error %1!d!.\n", dwRCOrv );
                              } // ClAdmWiz registered?
                              else
                              {
                                 // ERROR_SUCCESS and NO_ERROR are coincidentally the same.
   
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService has registered ClAdmWiz.dll.\n" );
                              } // ClAdmWiz registered?
   
                              // RegisterCOMObject returns ERROR_SUCCESS on success.
   
                              dwRCOrv = RegisterCOMObject( TEXT("ClNetREx.dll"), tszPathToClusterDir );
   
                              if ( dwRCOrv != (DWORD) ERROR_SUCCESS )
                              {
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService failed to register ClNetREx.dll with error %1!d!.\n", dwRCOrv );
                              } // ClNetREx registered?
                              else
                              {
                                 // ERROR_SUCCESS and NO_ERROR are coincidentally the same.
   
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService has registered ClNetREx.dll.\n" );
                              } // ClNetREx registered?

                              // RegisterCOMObject returns ERROR_SUCCESS on success.
   
                              dwRCOrv = RegisterCOMObject( TEXT("CluAdMMC.dll"), tszPathToClusterDir );
   
                              if ( dwRCOrv != (DWORD) ERROR_SUCCESS )
                              {
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService failed to register CluAdMMC.dll with error %1!d!.\n", dwRCOrv );
                              } // CluAdMMC registered?
                              else
                              {
                                 // ERROR_SUCCESS and NO_ERROR are coincidentally the same.
   
                                 ClRtlLogPrint( "CompleteUpgradingClusteringService has registered CluAdMMC.dll.\n" );
                              } // CluAdMMC registered?

                              // Close the COM library. As per MSDN, each successfull call
                              // to CoInitializeEx() must be balanced ba a call to CoUnitialize().

                              CoUninitialize();

                           } // COM library initialized?
                           else
                           {
                              // Could not inititlize the COM library.

                              ClRtlLogPrint( "CompleteUpgradingClusteringService could not initialize the COM library. Error %1!d!.\n", hResult );
                           } // COM library initialized?
                        } // path to cluster directory obtained?
                        else
                        {
                           // Couldn't expand the environment string.

                           dwReturnValue = GetLastError();
   
                           ClRtlLogPrint( "CompleteUpgradingClusteringService could not register ClAdmWiz.dll\n" );
                           ClRtlLogPrint( "because it could not locate the cluster directory. Error %1!d!.\n", dwReturnValue );
                        } // path to cluster directory obtained?
                     }

                     if ( dwReturnValue == (DWORD) NO_ERROR )
                     {
                        // Set the ClusterInstallationState reg value to "UPGRADED"

                        ClRtlSetClusterInstallState( eClusterInstallStateUpgraded );
                     }
                  } // did the default failure actions get set?
               } // did the connection objects get renamed?
            }  // Was the ImagePath set successfully?
         } // Were the Welcome UI registry operations performed successfully?
      }  // Were the UPGRADE registry operations performed successfully?
   }
   else
   {
      ClRtlLogPrint( "In CompleteUpgradingClusteringService since Cluster service "
                     "has never been installed there is nothing to do.\n" );

      dwReturnValue = (DWORD) NO_ERROR;
   }  // Has Cluster service previously been installed?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// CompleteStandAloneInstallationOfClusteringService
//
// Routine Description:
//    This function completes the process of installing Cluster service when
//    either Add/Remove Programs is running or sysocmgr.exe was launched from
//    a command prompt.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CompleteStandAloneInstallationOfClusteringService( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;
   DWORD_PTR dwResult;

   // Update the progress bar.

   m_SetupInitComponent.HelperRoutines.TickGauge( m_SetupInitComponent.HelperRoutines.OcManagerContext );

   // Update the "progress text"

   CString  csProgressText;

   csProgressText.LoadString( IDS_INSTALLING_CLUS_SERVICE );

   m_SetupInitComponent.HelperRoutines.SetProgressText( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                        (LPCTSTR) csProgressText );
   // A selection state transition has occured. Perform the base
   // registry operations.

   dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                              ptszSubComponentId );


   // On a standalone installation, we need to send a WM_SETTINGCHANGE message to all top level windows
   // so that any changes we have made to the system environment variables take effect.
   // During fresh installation, this happens automatically since there is a reboot.
   // See Knowledge Base article Q104011.
   SendMessageTimeout( 
       HWND_BROADCAST,                      // handle to window
       WM_SETTINGCHANGE,                    // message
       0,                                   // wparam
       (LPARAM) _T("Environment"),          // lparam
       SMTO_ABORTIFHUNG | SMTO_NORMAL,      // send options
       100,                                 // time-out duration (ms). Note, each top level window can wait upto this time
       &dwResult                            // return value for synchronous call - we ignore this
       );

   ClRtlLogPrint( "The base call to PerformRegistryOperations returned 0x%1!x! to CompleteStandAloneInstallationOfClusteringService.\n",
                  dwReturnValue );

   // Were the base registry operations performed successfully?

   if ( dwReturnValue == (DWORD) NO_ERROR )
   {
      // Queue the registry operations to add "Configure Cluster service"
      // to the Welcome UI task list. As per Sharon Montgomery on 12/18/98 the
      // Server Solutions will use those same keys.

      CString  csSectionName;

      csSectionName = CLUSREG_KEYNAME_WELCOME_UI;

      dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                 csSectionName );

      ClRtlLogPrint( "The WelcomeUI call to PerformRegistryOperations returned 0x%1!x! to CompleteStandAloneInstallationOfClusteringService.\n",
                     dwReturnValue );

      if ( dwReturnValue == (DWORD) NO_ERROR )
      {
         ClRtlLogPrint( "In CompleteStandAloneInstallationOfClusteringService the registry operations were queued successfully.\n" );

         // Since this was a standalone installation launch the Cluster Configuration
         // program, cluscfg.exe.

         // First, build the command line used to launch cluscfg. The specific
         // command depends on whether this is an unattended operation. Regardless
         // of whether this is unattended, the first part of the command line is
         // the fully qualified path to cluscfg.

         BOOL  fReturnValue;

         TCHAR tszClusCfgCommandLine[MAX_PATH];

         fReturnValue = GetPathToClusCfg( tszClusCfgCommandLine );

         // Was the path to cluscfg.exe deduced?

         if ( fReturnValue == (BOOL) TRUE )
         {
            // Update the progress bar.

            m_SetupInitComponent.HelperRoutines.TickGauge( m_SetupInitComponent.HelperRoutines.OcManagerContext );

            // Update the "progress text"

            CString  csProgressText;

            csProgressText.LoadString( IDS_CONFIGURING_CLUS_SERVICE );

            m_SetupInitComponent.HelperRoutines.SetProgressText( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                 (LPCTSTR) csProgressText );

            // Is this an unattended operation?

            if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) !=
                 (DWORDLONG) 0L )
            {
               // This is unattended.

               HINF  hAnswerFile;      // WARNING: NEVER close this handle because clusocm.dll
                                       //          did not open it.
      
               // Get a handle to the answer file. WARNING: NEVER close this handle because clusocm.dll
               // did not open it.
      
               hAnswerFile = m_SetupInitComponent.HelperRoutines.GetInfHandle( INFINDEX_UNATTENDED,
                                                                               m_SetupInitComponent.HelperRoutines.OcManagerContext );
      
               // Is the handle to the answer file OK?

               if ( (hAnswerFile != (HINF) NULL) && (hAnswerFile != (HINF) INVALID_HANDLE_VALUE) )
               {
                  // The handle to the answer file is legal.

                  // tszClusCfgCommandLine is the path to cluscfg.exe. Append the "-UNATTEND"
                  // command line option.

                  CString  csUnattendCommandLineOption;

                  csUnattendCommandLineOption = UNATTEND_COMMAND_LINE_OPTION;

                  _tcscat( tszClusCfgCommandLine, _T(" ") );
                  _tcscat( tszClusCfgCommandLine, csUnattendCommandLineOption );

                  // Append the path to the answer file.

                  _tcscat( tszClusCfgCommandLine, _T(" ") );
                  _tcscat( tszClusCfgCommandLine, m_SetupInitComponent.SetupData.UnattendFile );
               } // handle to answer file OK?
            } // unattended?

            // Launch cluscfg - tszClusCfgCommandLine contains the command, either
            //                  attended or unattended.

            STARTUPINFO StartupInfo;

            ZeroMemory( &StartupInfo, sizeof( StartupInfo ) );
            StartupInfo.cb = sizeof( StartupInfo );

            PROCESS_INFORMATION  ProcessInformation;

            fReturnValue = CreateProcess( (LPCTSTR) NULL,
                                          (LPTSTR) tszClusCfgCommandLine,
                                          (LPSECURITY_ATTRIBUTES) NULL,
                                          (LPSECURITY_ATTRIBUTES) NULL,
                                          (BOOL) FALSE,
                                          (DWORD) (CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT),
                                          (LPVOID) GetEnvironmentStrings(),
                                          (LPCTSTR) NULL,
                                          (LPSTARTUPINFO) &StartupInfo,
                                          (LPPROCESS_INFORMATION) &ProcessInformation );

            // Was the process created?

            if ( fReturnValue == (BOOL) TRUE )
            {
               // Wait for cluscfg.exe to complete. As per Pat Styles on 7/6/98,
               // an OCM Setup DLL MUST wait for any process it spawns to complete.

               DWORD dwWFSOrv;

               dwWFSOrv = WaitForSingleObject( ProcessInformation.hProcess,
                                               INFINITE );
            }
            else
            {
               DWORD dwErrorCode;

               dwErrorCode = GetLastError();

               if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) ==
                    (DWORDLONG) 0L )
               {
                  CString  csMessage;

                  csMessage.Format( IDS_ERROR_SPAWNING_CLUSCFG, dwErrorCode );

                  AfxMessageBox( csMessage );
               }

               ClRtlLogPrint( "Error %1!d! occured attempting to spawn cluscfg.exe.\n", dwErrorCode );
            } // Was the process created?
         } // Path to cluscfg obtained?
      } // WelcomeUI registry operations succeeded?
   }  // Were the base registry operations performed successfully?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// CompleteInstallingClusteringService
//
// Routine Description:
//    This function performs much of the processing when OC_COMPLETE_INSTALLATION
//    has been received and a clean installation is being performed.
//
// Arguments:
//    ptszSubComponentId - points to a string that uniquely identifies a sub-
//                         component in the component's hiearchy.
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CompleteInstallingClusteringService( IN LPCTSTR ptszSubComponentId )
{
   DWORD dwReturnValue;

   // Update the progress bar.

   if ( GetStepCount() != 0L )
   {
      // Update the progress meter.

      m_SetupInitComponent.HelperRoutines.TickGauge( m_SetupInitComponent.HelperRoutines.OcManagerContext );
   }

   // Update the "progress text"

   CString  csProgressText;

   csProgressText.LoadString( IDS_INSTALLING_CLUS_SERVICE );

   m_SetupInitComponent.HelperRoutines.SetProgressText( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                        (LPCTSTR) csProgressText );

   // Perform the base registry operations.

   dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                              ptszSubComponentId );

   ClRtlLogPrint( "The base call to PerformRegistryOperations returned 0x%1!x! to CompleteInstallingClusteringService.\n",
                  dwReturnValue );

   // Were the base registry operations performed successfully?

   if ( dwReturnValue == (DWORD) NO_ERROR )
   {
      // Queue the registry operations to add "Configure Cluster service"
      // to the Welcome UI task list. As per Sharon Montgomery on 12/18/98, Server
      // Solutions will use those same keys.

      CString  csSectionName;

      csSectionName = CLUSREG_KEYNAME_WELCOME_UI;

      dwReturnValue = PerformRegistryOperations( m_SetupInitComponent.ComponentInfHandle,
                                                 csSectionName );

      ClRtlLogPrint( "The WelcomeUI call to PerformRegistryOperations returned 0x%1!x! to CompleteInstallingClusteringService.\n",
                     dwReturnValue );
   }  // Were the base registry operations performed successfully?

   // Were the Welcome UI registry operations performed successfully?

   if ( dwReturnValue == (DWORD) NO_ERROR )
   {
      ClRtlLogPrint( "In CompleteInstallingClusteringService for a clean install all registery operations were performed successfully.\n" );
   }
   else
   {
      ClRtlLogPrint( "In CompleteInstallingClusteringService for a clean install the registry operations were not performed successfully.\n" );
   }

   // For unattended setup this needs to write the WelcomeUI reg keys in a way that
   // will cause Configure Your Server to launch cluscfg.exe in unattended mode.

   // revisions to CompleteInstallingClusteringService may be appropriate prior to this code segment.

   // Were all registry operations performed correctly?

   if ( dwReturnValue == (DWORD) NO_ERROR )
   {
      LONG lReturnValue;   // used below for registry operations

      HKEY hKey;           // used below for registry operations

      // Is this an unattended installation?

      if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) !=
           (DWORDLONG) 0L )
      {
         HINF  hAnswerFile;      // WARNING: NEVER close this handle because clusocm.dll
                                 //          did not open it.

         // Get a handle to the answer file. WARNING: NEVER close this handle because clusocm.dll
         // did not open it.

         hAnswerFile = m_SetupInitComponent.HelperRoutines.GetInfHandle( INFINDEX_UNATTENDED,
                                                                         m_SetupInitComponent.HelperRoutines.OcManagerContext );

         if ( (hAnswerFile != (HINF) NULL) && (hAnswerFile != (HINF) INVALID_HANDLE_VALUE) )
         {
            // Write the WelcomeUI registry entry to launch cluscfg in unattended mode.
            // Note that the registry operations performed when CLUSREG_KEYNAME_WELCOME_UI
            // was passed to PerformRegistryOperations() above included creation of
            // HKLM\Software\Microsoft\Windows NT\CurrentVersion\Setup\OCManager\ToDoList\Cluster\ConfigCommand.
            // All that is necessary is to create the ConfigArgs value with the
            // appropriate command line arguments to execute cluscfg.exe in unattended mode.

            HKEY hKey;

            DWORD dwType;

            // Attempt to open an existing key in the registry.

            lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                         _T( "Software\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\OCManager\\ToDoList\\Cluster" ),
                                         (DWORD) 0,         // reserved
                                         KEY_SET_VALUE,
                                         &hKey );

            // Was the Cluster "to do list" registry key opened?

            if ( lReturnValue == (LONG) ERROR_SUCCESS )
            {
               // The key is open.

               TCHAR tszValue[MAX_PATH];

               _tcscpy( tszValue, UNATTEND_COMMAND_LINE_OPTION );

               // Append the path to the answer file.

               _tcscat( tszValue, _T(" ") );
               _tcscat( tszValue, m_SetupInitComponent.SetupData.UnattendFile );

               // Set the key.

               DWORD dwValueLength;

               dwValueLength = (DWORD) ((_tcslen( tszValue ) + 1) * sizeof( TCHAR ));

               lReturnValue = RegSetValueEx( hKey,
                                             _T( "ConfigArgs" ),
                                             (DWORD) 0L,                            // reserved
                                             (DWORD) REG_EXPAND_SZ,
                                             (CONST BYTE *) tszValue,
                                             dwValueLength );

                  // Was the key written successfully?

               if ( lReturnValue == (LONG) ERROR_SUCCESS )
               {
                  dwReturnValue = (DWORD) NO_ERROR;

                  ClRtlLogPrint( "CompleteInstallingClusteringService created the ConfigArgs reg key for unattended operation of cluscfg.\n" );
               }
               else
               {
                  dwReturnValue = GetLastError();

                  ClRtlLogPrint( "CompleteInstallingClusteringService failed to create the ConfigArgs reg key with error code %1!d!.\n",
                                 dwReturnValue );
               } // Was the reg key written successfully?

               // Close the registry key.

               RegCloseKey( hKey );           // do we care about the return value?
            }
            else
            {
               // The key could not be opened.

               DWORD dwErrorCode;

               dwErrorCode = GetLastError();

               ClRtlLogPrint( "In CompleteInstallingClusteringService RegOpenKeyEx failed with error code 0x%1!x!.\n",
                              dwErrorCode );
            } // Was the key opened?
         } // handle to answer file OK?
      } // Is this unattended?
      else
      {
         // This is NOT unattended, so make sure the ConfigArgs value is removed.
         // That will prevent any value left from a previous unattended install
         // which was never successfully configured from corrupting the behavior
         // of Configure Your Server or Add/Remove Programs.

         // Attempt to open an existing key in the registry.

         lReturnValue = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                      TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\OCManager\\ToDoList\\Cluster"),
                                      (DWORD) 0,         // reserved
                                      KEY_READ,
                                      &hKey );

         // Was the Cluster "to do list" registry key opened?

         if ( lReturnValue == (long) ERROR_SUCCESS )
         {
            // Delete the values (which may not exist).

            RegDeleteValue( hKey, (LPCTSTR) TEXT("ConfigArgs") );
         } // if ToDoList opened
      } // if unattended?
   } // registry operations successfull?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// OnOcCleanup
//
// Routine Description:
//    This function processes the OC_CLEANUP messages by removing the "private"
//    subdirectory in the cluster directory if it is empty and the cluster directory
//    itself it it is empty.
//
// Arguments:
//    none
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    Any other value is a standard Win32 error code.
//
// Note:
//    The "private" subrirectory may exist in the cluster directory if the system
//    was upgraded from NT4.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::OnOcCleanup( void )
{
   ClRtlLogPrint( "Processing OC_CLEANUP.\n" );

   DWORD dwReturnValue;

   dwReturnValue = CleanupDirectories();

   ClRtlLogPrint( "\n\n\n\n" );

   // Close the log file.

   ClRtlCleanup();

   // Set the ClusterLog environment variable back to its' original state.

   char  szLogFileName[MAX_PATH];

   strcpy( szLogFileName, "ClusterLog=" );

   if ( *m_szOriginalLogFileName != (char) '\0' )
   {
      strcat( szLogFileName, m_szOriginalLogFileName );
   }

   _putenv( szLogFileName );

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// CleanupDirectories
//
// Routine Description:
//    This function removes the "private" subdirectory that may remain in the
//    cluster directory if the system has ever been upgraded from NT 4 if that
//    directory is empty. It will also remove the cluster directory itself if
//    it is empty.
//
// Arguments:
//    none
//
// Return Value:
//    (DWORD) NO_ERROR - indicates success
//    (DWORD) -1L - means that the cluster directory was not empty
//    Any other value is a standard Win32 error code.
//
// Note:
//    This function assumes that the path to the cluster directory is less than
//    MAX_PATH TCHARs long and that function SetupGetTargetPath expects the fifth
//    parameter to specify the number of TCHARS (not bytes) in the buffer.
//
//    No recovery is attempted if the call to SetupGetTargetPath fails.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CleanupDirectories( void )
{
   DWORD dwReturnValue = (DWORD) NO_ERROR;

   BOOL  fReturnValue;

   DWORD dwRequiredSize;

   TCHAR tszPathToClusterDirectory[MAX_PATH];

   // First, get the path to the cluster directory.

   CString  csSectionName;

   csSectionName =  CLUSTER_FILES_INF_KEY;

   fReturnValue = SetupGetTargetPath( m_SetupInitComponent.ComponentInfHandle,
                                      (PINFCONTEXT) NULL,
                                      (PCTSTR) csSectionName,
                                      tszPathToClusterDirectory,
                                      (DWORD) MAX_PATH,
                                      (PDWORD) &dwRequiredSize );

   // Was the path to the cluster directory obtained?

   if ( fReturnValue == (BOOL) TRUE )
   {
      // Test for the presence of a subdirectory named "private".

      TCHAR tszPathToPrivateDirectory[MAX_PATH];

      // Append "\private" to the path to the cluster directory that was obtained above.

      // Note, I didn't put this string in the stringtable because it is
      // not localizable, and will never change.

      _tcscpy( tszPathToPrivateDirectory, tszPathToClusterDirectory );
      _tcscat( tszPathToPrivateDirectory, _T("\\private") );

      HANDLE            hFindHandle;
      WIN32_FIND_DATA   FindData;

      // Does a file or directory named "private" exist?

      hFindHandle = FindFirstFile( (LPCTSTR) tszPathToPrivateDirectory, &FindData );

      if ( hFindHandle != (HANDLE) INVALID_HANDLE_VALUE )
      {
         // A file named "private" exists in the cluster directory. Is it a directory?

         if ( (FindData.dwFileAttributes & (DWORD) FILE_ATTRIBUTE_DIRECTORY) != (DWORD) 0L )
         {
            // The "private" directory has been located. Determine whether it is empty.

            if ( IsDirectoryEmpty( tszPathToPrivateDirectory ) == (BOOL) TRUE )
            {
               // No file was found in the "private" directory. Remove the "private" directory.

               if ( RemoveDirectory( (LPCTSTR) tszPathToPrivateDirectory ) == (BOOL) TRUE )
               {
                  dwReturnValue = (DWORD) NO_ERROR;
               }
               else
               {
                  // Could not delete the "private" directory even though it was empty.

                  dwReturnValue = GetLastError();

                  ClRtlLogPrint( "CleanupDirectories was unable to remove %1!s!. The error code is %2!x!.\n",
                                 tszPathToPrivateDirectory, dwReturnValue );
               } // Was the "private" directory removed successfully?
            }
            else
            {
               ClRtlLogPrint( "CleanupDirectories did not attempt to remove %1!s! because it is not empty.\n",
                              tszPathToPrivateDirectory );

               // An error code of -1 means the "private" directory, and thus the
               // cluster directory was not empty.

               dwReturnValue = (DWORD) -1L;
            } // Was any file found in the "private" directory?
         }
         else
         {
            ClRtlLogPrint( "In CleanupDirectories the file named private is not a directory.\n");

            dwReturnValue = (DWORD) ERROR_FILE_NOT_FOUND;
         } // Is it a directory?

         // Close the search that located the "private" directory.

         FindClose( hFindHandle );
      }
      else
      {
         dwReturnValue = GetLastError();

         ClRtlLogPrint( "CleanupDirectories was unable to find any file named private in the cluster directory.\n");
         ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwReturnValue );
      } // Does a file or directory named "private" exist?

      // Was the "private" directory absent or removed successfully?

      if ( (dwReturnValue == (DWORD) ERROR_FILE_NOT_FOUND) ||
           (dwReturnValue == (DWORD) NO_ERROR) )
      {
         // The "private" directory either did not exist or has been removed.
         // Now determine whether the cluster directory is empty and therefore should be removed.

         // This code segment assumes that the cluster directory, as specified
         // in tszPathToClusterDirectory exists.

         if ( IsDirectoryEmpty( tszPathToClusterDirectory ) == (BOOL) TRUE )
         {
            // The cluster directory is empty.

            if ( RemoveDirectory( tszPathToClusterDirectory ) == (BOOL) TRUE )
            {
               dwReturnValue = (DWORD) NO_ERROR;
            }
            else
            {
               // Could not delete the "cluster" directory even though it was empty.

               dwReturnValue = GetLastError();

               ClRtlLogPrint( "CleanupDirectories was unable to remove %1!s!. The error code is %2!x!.\n",
                              tszPathToClusterDirectory, dwReturnValue );
            } // Was the cluster directory removed?
         }
         else
         {
            // The cluster directory is not empty.

            ClRtlLogPrint( "CleanupDirectories did not attempt to remove %1!s! because it is not empty.\n",
                           tszPathToClusterDirectory );

            // An error code of -1 means the cluster directory was not empty.

            dwReturnValue = (DWORD) -1L;
         } // Is the cluster directory empty?
      } // Was the "private" directory absent or removed?
   }
   else
   {
      dwReturnValue = GetLastError();

      ClRtlLogPrint( "CleanupDirectories was unable to obtain the path to the cluster directory.\n");
      ClRtlLogPrint( "The error code is 0x%1!x!.\n", dwReturnValue );
   } // Was the path to the cluster directory obtained?

   return ( dwReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// IsDirectoryEmpty
//
// Routine Description:
//    This function determines whether a directory is empty.
//
// Arguments:
//    ptzDirectoryName - points to a string specifying the directory name.
//
// Return Value:
//    TRUE - indicates that the directory is empty
//    FALSE - indicates that the directory is not empty
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::IsDirectoryEmpty( LPCTSTR ptszDirectoryName )
{
   BOOL              fDirectoryEmpty;

   HANDLE            hFindHandle;

   WIN32_FIND_DATA   FindData;

   TCHAR             tszFindPath[MAX_PATH];

   // Append the file specification to the path supplied.

   _tcscpy( tszFindPath, ptszDirectoryName );
   _tcscat( tszFindPath, _T("\\*") );

   hFindHandle = FindFirstFile( tszFindPath, &FindData );

   // Was any file found in the directory?

   if ( hFindHandle != (HANDLE) INVALID_HANDLE_VALUE )
   {
      // A file was found. It could be "." or "..".

      if ( (_tcscmp( FindData.cFileName, _T(".") ) == 0) ||
           (_tcscmp( FindData.cFileName, _T("..") ) == 0) )
      {
         // The file was either "." or "..". Keep trying.

         DWORD dwErrorCode;

         if ( FindNextFile( hFindHandle, &FindData ) == (BOOL) TRUE )
         {
            // A file was found. It could be "." or "..".

            if ( (_tcscmp( FindData.cFileName, _T(".") ) == 0) ||
                 (_tcscmp( FindData.cFileName, _T("..") ) == 0) )
            {
               // The file was either "." or "..". Keep trying.

               if ( FindNextFile( hFindHandle, &FindData ) == (BOOL) TRUE )
               {
                  // The file cannot possibly be either "." or "..".

                  fDirectoryEmpty = (BOOL) FALSE;
               }
               else
               {
                  dwErrorCode = GetLastError();

                  if ( dwErrorCode == (DWORD) ERROR_NO_MORE_FILES )
                  {
                     fDirectoryEmpty = (BOOL) TRUE;
                  }
                  else
                  {
                     fDirectoryEmpty = (BOOL) FALSE;
                  }
               }
            }
            else
            {
               // The private directory is not empty.

               fDirectoryEmpty = (BOOL) FALSE;
            }
         }
         else
         {
            dwErrorCode = GetLastError();

            if ( dwErrorCode == ERROR_NO_MORE_FILES )
            {
               fDirectoryEmpty = (BOOL) TRUE;
            }
            else
            {
               fDirectoryEmpty = (BOOL) FALSE;
            }
         }
      }
      else
      {
         // The directory is not empty.

         fDirectoryEmpty = (BOOL) FALSE;
      }

      // Close the search.

      FindClose( hFindHandle );
   }
   else
   {
      fDirectoryEmpty = (BOOL) TRUE;
   }

   return ( fDirectoryEmpty );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// GetPathToClusCfg
//
// Routine Description:
//    This function deduces the path to the Cluster service configuration
//    program, cluscfg.exe.
//
//    It assumes that if the Cluster service is registered with the Service
//    COntrol Manager then cluscfg.exe is located in the directory with clussvc.exe.
//    Otherwise it expands %windir%\cluster and declares that to be the path.
//
//    The name of the Cluster service configuration pogram is then appended to
//    the path.
//
// Arguments:
//    lptszPathToClusCfg - points to a string in which the fully qualified path to
//                         the Cluster service configuration program is to be placed.
//
// Return Value:
//    TRUE - indicates that lptszPathToClusCfg is valid
//    FALSE - indicates that an error occured
//
//--
/////////////////////////////////////////////////////////////////////////////

BOOL CClusocmApp::GetPathToClusCfg( LPTSTR lptszPathToClusCfg )
{
   BOOL  fReturnValue;

   // If the Cluster service is registered assume that cluscfg is in
   // the same directory as clussvc.exe and get the path to cluscfg.exe
   // from the Service Control Mamager. If the Cluster service is not
   // registered, assume cluscfg.exe is in %windir%\cluster.

   if ( IsClusterServiceRegistered() == (BOOL) TRUE )
   {
      // Query the path to the Cluster Service executable from the Service Control Manager.

      CString  csClusterService;

      csClusterService = CLUSTER_SERVICE_NAME;

      fReturnValue = GetServiceBinaryPath( (LPWSTR) (LPCTSTR) csClusterService,
                                            lptszPathToClusCfg );
   }
   else
   {
      // Use the default location.

      CString  csClusterDirectory;

      csClusterDirectory =  CLUSTER_DIRECTORY;

      if ( ExpandEnvironmentStrings( (LPCTSTR) csClusterDirectory,
                                     lptszPathToClusCfg, MAX_PATH ) > 0L )
      {
         fReturnValue = (BOOL) TRUE;
      }
      else
      {
         // Could not expand the enviornment string. The default location for the
         // Cluster service could not be determined.

         fReturnValue = (BOOL) FALSE;
      }  // Was the default location for cluscfg.exe determined?
   }  // Where is cluscfg.exe?

   // Was the path to cluscfg.exe deduced?

   if ( fReturnValue == (BOOL) TRUE )
   {
      // tszPathToClusCfg is the path to cluscfg.exe. Append the program name.

      CString  csClusterConfigurationProgram;

      csClusterConfigurationProgram =  CLUSTER_CONFIGURATION_PGM;

      _tcscat( lptszPathToClusCfg, _T("\\") );
      _tcscat( lptszPathToClusCfg, csClusterConfigurationProgram );
   }
   else
   {
      // Set the target string empty.

      *lptszPathToClusCfg = _T( '\0' );
   }

   return ( fReturnValue );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// CalculateStepCount
//
// Routine Description:
//    This function calculates the "step count", i.e the number of non-file operations
//    to be performed when processing OC_COMPLETE_INSTALLATION.
//
// Arguments:
//    ptszSubComponentId
//
// Return Value:
//    The number of non-file operation (steps) to perform when processing
//    OC_COMPLETE_INSTALLATION
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::CalculateStepCount( LPCTSTR ptszSubComponentId )
{
   DWORD dwStepCount;

   // Is this UNATTENDED or ATTENDED?

   if ( (m_SetupInitComponent.SetupData.OperationFlags & (DWORDLONG) SETUPOP_BATCH) !=
        (DWORDLONG) 0L )
   {
      // This is UNATTENDED.

      if ( (m_SetupInitComponent.SetupData.OperationFlags &
           (DWORDLONG) SETUPOP_NTUPGRADE) != (DWORDLONG) 0L )
      {
         // This is UPGRADE. Cluscfg.exe will not be launched, so there is
         // one non-file operation to be performed, registry revisions.

         dwStepCount = 1L;
      }
      else
      {
         // This is a clean INSTALL. Cluscfg.exe may be launched.

         // Is there a [cluster] section in the answer file? BUGBUG

         //   so there
         // are two non-file operations to be performed, registry revisions
         // and execution of cluscfg.

         dwStepCount = 2L;
      } // unattended: upgrade or install?
   }
   else
   {
      // This is ATTENDED.

      if ( (m_SetupInitComponent.SetupData.OperationFlags &
           (DWORDLONG) SETUPOP_STANDALONE) != (DWORDLONG) 0L )
      {
         // This is STANDALONE. Is Cluster service selected?

         BOOL  fCurrentSelectionState;
         BOOL  fOriginalSelectionState;

         fCurrentSelectionState =
         m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                  (LPCTSTR) ptszSubComponentId,
                                                                  (UINT) OCSELSTATETYPE_CURRENT );
         fOriginalSelectionState =
         m_SetupInitComponent.HelperRoutines.QuerySelectionState( m_SetupInitComponent.HelperRoutines.OcManagerContext,
                                                                  ptszSubComponentId,
                                                                  (UINT) OCSELSTATETYPE_ORIGINAL );

         // Was there a selection state transition?

         if ( fCurrentSelectionState != fOriginalSelectionState )
         {
            if ( fCurrentSelectionState == (BOOL) TRUE )
            {
               // Currently selected - This is a clean install. There are two
               // non-file operations to be performed, registry revisions and
               // execution of cluscfg.exe.

               dwStepCount = 2L;
            }
            else
            {
               // Currently not selected - This is an uninstall. There is one
               // non-file operation to be performed, registry revisions.

               dwStepCount = 1L;
            } // currently selected?

            dwStepCount = 1L;
         }
         else
         {
            // There was no selection state transition, therefore there
            // are zero non-file operations to be performed.

            dwStepCount = 0L;
         } // selection state transition?

      }
      else
      {
         // This is GUI mode setup. The two operations that may be performed
         // during GUI mode setup are clean install and upgrade. In neither
         // case does cluscfg.exe get launched. Thus, there is one non-file
         // operation to be performed, registry revisions.

         dwStepCount = 1L;
      } // interactive: standalone or atttended?
   } // Is this UNATTENDED or ATTENDED?

   return ( dwStepCount );
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// SetStepCount
//
// Routine Description:
//    This function sets the value of the m_dwStepCount member of the CClusocmApp object.
//
// Arguments:
//    dwStepCount - the value to which the m_dwStepCount member should be set.
//
// Return Value:
//    None
//
//--
/////////////////////////////////////////////////////////////////////////////

void CClusocmApp::SetStepCount( DWORD dwStepCount )
{
   m_dwStepCount = dwStepCount;
}



/////////////////////////////////////////////////////////////////////////////
//++
//
// GetStepCount
//
// Routine Description:
//    This function returns the content of the m_dwStepCount member of the
//    CClusocmApp object.
//
// Arguments:
//    None
//
// Return Value:
//    The content of the m_dwStepCount member.
//
//--
/////////////////////////////////////////////////////////////////////////////

DWORD CClusocmApp::GetStepCount( void )
{
   return ( m_dwStepCount );
}