/******************************************************************
   Copyright (c) 1999 Microsoft Corporation

   NlbsNic.CPP -- WMI provider class implementation

   Generated by Microsoft WMI Code Generation Engine
  
   TO DO: - See individual function headers
          - When linking, make sure you link to framedyd.lib & 
            msvcrtd.lib (debug) or framedyn.lib & msvcrt.lib (retail).

   Description: 
   
  
  
******************************************************************/

// History:
// --------
// 
// Revised by : mhakim
// Date       : 02-12-01
// Reason     : Added password support.
//
// Revised by : mhakim
// Date       : 02-16-01
// Reason     : Added friendly name support.
//
// Reason     : filling out version info.  This was being not 
//              done previously in GetObject.

#include <fwcommon.h>  // This must be the first include.

#include "NlbsNic.h"
#include "NICCard.h"
#include "MNicInfo.h"
#include "MIPAddressAdmin.h"
#include "MNLBProviderSetting.h"
#include "Common.h"
#include "MUsingCom.h"
#include "WTokens.h"
#include "MNLBMachine.h"

#include <winbase.h> // For Sleep
#include <windows.h> // For Sleep

#include <string>
//#include <wlbsiocl.h>
#include "wlbsconfig.h"
#include "myntrtl.h"
#include "wlbsparm.h"
#include "cfgutils.h"
#include "updatecfg.h"
#include "nlbsnic.tmh"
using namespace std;

MUsingCom   com;

BOOL g_UpdateConfigurationEnabled = FALSE;


WBEMSTATUS
ProvGetClusterConfiguration(
           CInstance *pInParams,
           CInstance *pOutParams
           );

WBEMSTATUS
ProvUpdateClusterConfiguration(
           CInstance *pInParams,
           CInstance *pOutParams
           );

WBEMSTATUS
ProvQueryConfigurationUpdateStatus(
           CInstance *pInParams,
           CInstance *pOutParams
           );

WCHAR*
CNlbsNic::version = L"03-09-2001";

// TO DO: Replace "NameSpace" with the appropriate namespace for your
//        provider instance.  For instance:  "root\\default or "root\\cimv2".
// DONE : mhakim
//===================================================================
CNlbsNic MyNlbsNicSet (PROVIDER_NAME_NLBSNIC, L"root\\microsoftnlb") ;

// Property names
//===============
const static WCHAR* pAdapterGuid = L"AdapterGuid" ;
const static WCHAR* pDependent = L"Dependent" ;
const static WCHAR* pFriendlyName = L"FriendlyName" ;
const static WCHAR* pFullName = L"FullName" ;
const static WCHAR* pVersion = L"Version" ;

/*****************************************************************************
 *
 *  FUNCTION    :   CNlbsNic::CNlbsNic
 *
 *  DESCRIPTION :   Constructor
 *
 *  INPUTS      :   none
 *
 *  RETURNS     :   nothing
 *
 *  COMMENTS    :   Calls the Provider constructor.
 *
 *****************************************************************************/
CNlbsNic::CNlbsNic (LPCWSTR lpwszName, LPCWSTR lpwszNameSpace ) :
        Provider(lpwszName, lpwszNameSpace)
{
    //
    // Enable WMI event tracing
    //
    WPP_INIT_TRACING(L"Microsoft\\NLB\\TPROV");

    if (g_UpdateConfigurationEnabled)
    {
        //
        //    Initialize update config
        //
        NlbConfigurationUpdate::Initialize();
    }
    
}

/*****************************************************************************
 *
 *  FUNCTION    :   CNlbsNic::~CNlbsNic
 *
 *  DESCRIPTION :   Destructor
 *
 *  INPUTS      :   none
 *
 *  RETURNS     :   nothing
 *
 *  COMMENTS    : 
 *
 *****************************************************************************/
CNlbsNic::~CNlbsNic ()
{

    if (g_UpdateConfigurationEnabled)
    {
        //
        // Deinitialize update config code
        //
        NlbConfigurationUpdate::Deinitialize();
    }

    //
    // Disable WMI event tracing
    //
    WPP_CLEANUP();
}

/*****************************************************************************
*
*  FUNCTION    :    CNlbsNic::EnumerateInstances
*
*  DESCRIPTION :    Returns all the instances of this class.
*
*  INPUTS      :    A pointer to the MethodContext for communication with WinMgmt.
*                   A long that contains the flags described in 
*                   IWbemServices::CreateInstanceEnumAsync.  Note that the following
*                   flags are handled by (and filtered out by) WinMgmt:
*                       WBEM_FLAG_DEEP
*                       WBEM_FLAG_SHALLOW
*                       WBEM_FLAG_RETURN_IMMEDIATELY
*                       WBEM_FLAG_FORWARD_ONLY
*                       WBEM_FLAG_BIDIRECTIONAL
*
*  RETURNS     :    WBEM_S_NO_ERROR if successful
*
*  COMMENTS    : TO DO: All instances on the machine should be returned here and
*                       all properties that this class knows how to populate must 
*                       be filled in.  If there are no instances, return 
*                       WBEM_S_NO_ERROR.  It is not an error to have no instances.
*                       If you are implementing a 'method only' provider, you
*                       should remove this method.
*                DONE: mhakim
*
*****************************************************************************/
HRESULT CNlbsNic::EnumerateInstances ( MethodContext* pMethodContext, long lFlags )
{
    HRESULT hRes = WBEM_S_NO_ERROR;

    // get information about all nics on the machine.
    //
    NICCard::NICCard_Error errN;
    vector< NICCard::Info > nicList;
    errN = NICCard::getNics( &nicList );
    if( errN != NICCard::NICCard_SUCCESS )
    {
        return WBEM_E_NOT_FOUND;
    }

    // populate all instances.
    //
    for( int i = 0; i < nicList.size(); ++i )
    {
        CInstance* pInstance = CreateNewInstance(pMethodContext);

        pInstance->SetCHString(pAdapterGuid, nicList[i].guid.c_str()  );

        pInstance->SetCHString(pFriendlyName, nicList[i].friendlyName.c_str() );

        pInstance->SetCHString(pFullName, nicList[i].fullName.c_str() );

        pInstance->SetCHString(pVersion, version );

        hRes = pInstance->Commit();
        pInstance->Release();
    }
    
    hRes = WBEM_S_NO_ERROR;    
    return hRes ;
}

/*****************************************************************************
*
*  FUNCTION    :    CNlbsNic::GetObject
*
*  DESCRIPTION :    Find a single instance based on the key properties for the
*                   class. 
*
*  INPUTS      :    A pointer to a CInstance object containing the key properties. 
*                   A long that contains the flags described in 
*                   IWbemServices::GetObjectAsync.  
*
*  RETURNS     :    WBEM_S_NO_ERROR if the instance can be found
*                   WBEM_E_NOT_FOUND if the instance described by the key properties 
*                   could not be found
*                   WBEM_E_FAILED if the instance could be found but another error 
*                   occurred. 
*
*  COMMENTS    :    If you are implementing a 'method only' provider, you should
*                   remove this method.
*
*****************************************************************************/
HRESULT CNlbsNic::GetObject ( CInstance* pInstance, long lFlags )
{
    HRESULT hr = WBEM_E_NOT_FOUND;

    CHString     sTemp;
    pInstance->GetCHString( L"FullName",
                            sTemp );
    
    wstring fullName = sTemp;

    NICCard::NICCard_Error errN;
    vector< NICCard::Info > nicList;
    errN = NICCard::getNics( &nicList );
    if( errN != NICCard::NICCard_SUCCESS )
    {
        return WBEM_E_NOT_FOUND;
    }

    // populate all instances.
    //
    hr = WBEM_E_NOT_FOUND;
    for( int i = 0; i < nicList.size(); ++i )
    {
        if( nicList[i].fullName == fullName )
        {
            // found specific instance.
            //
            pInstance->SetCHString(pAdapterGuid, nicList[i].guid.c_str()  );

            pInstance->SetCHString(pFriendlyName, nicList[i].friendlyName.c_str() );

            pInstance->SetCHString(pFullName, nicList[i].fullName.c_str() );

            pInstance->SetCHString(pVersion, version );

            hr = WBEM_S_NO_ERROR;    
            break;
        }
    }

    return hr;
}

/*****************************************************************************
*
*  FUNCTION    :    CNlbsNic::ExecQuery
*
*  DESCRIPTION :    You are passed a method context to use in the creation of 
*                   instances that satisfy the query, and a CFrameworkQuery 
*                   which describes the query.  Create and populate all 
*                   instances which satisfy the query.  You may return more 
*                   instances or more properties than are requested and WinMgmt 
*                   will post filter out any that do not apply.
*
*  INPUTS      :    A pointer to the MethodContext for communication with WinMgmt.
*                   A query object describing the query to satisfy.
*                   A long that contains the flags described in 
*                   IWbemServices::CreateInstanceEnumAsync.  Note that the following
*                   flags are handled by (and filtered out by) WinMgmt:
*                       WBEM_FLAG_FORWARD_ONLY
*                       WBEM_FLAG_BIDIRECTIONAL
*                       WBEM_FLAG_ENSURE_LOCATABLE
*
*  RETURNS     :    WBEM_E_PROVIDER_NOT_CAPABLE if queries not supported for 
*                       this class or if the query is too complex for this class
*                       to interpret.  The framework will call the EnumerateInstances
*                       function instead and let Winmgmt post filter.
*                   WBEM_E_FAILED if the query failed
*                   WBEM_S_NO_ERROR if query was successful 
*
*  COMMENTS    : TO DO: Most providers will not need to implement this method.  If you don't, WinMgmt 
*                       will call your enumerate function to get all the instances and perform the 
*                       filtering for you.  Unless you expect SIGNIFICANT savings from implementing 
*                       queries, you should remove this method.  You should also remove this method
*                       if you are implementing a 'method only' provider.
*
*****************************************************************************/
HRESULT CNlbsNic::ExecQuery (MethodContext *pMethodContext, CFrameworkQuery& Query, long lFlags)
{
    return (WBEM_E_PROVIDER_NOT_CAPABLE);
}

/*****************************************************************************
*
*  FUNCTION    : CNlbsNic::PutInstance
*
*  DESCRIPTION :    PutInstance should be used in provider classes that can 
*                   write instance information back to the hardware or 
*                   software.  For example: Win32_Environment will allow a 
*                   PutInstance to create or update an environment variable.  
*                   However, a class like MotherboardDevice will not allow 
*                   editing of the number of slots, since it is difficult for 
*                   a provider to affect that number.
*
*  INPUTS      :    A pointer to a CInstance object containing the key properties. 
*                   A long that contains the flags described in 
*                   IWbemServices::PutInstanceAsync.  
*
*  RETURNS     :    WBEM_E_PROVIDER_NOT_CAPABLE if PutInstance is not available
*                   WBEM_E_FAILED if there is an error delivering the instance
*                   WBEM_E_INVALID_PARAMETER if any of the instance properties 
*                   are incorrect.
*                   WBEM_S_NO_ERROR if instance is properly delivered
*
*  COMMENTS    : TO DO: If you don't intend to support writing to your provider, 
*                       or are creating a 'method only' provider, remove this 
*                       method.
*
*****************************************************************************/
HRESULT CNlbsNic::PutInstance ( const CInstance &Instance, long lFlags)
{
    // Use the CInstance Get functions (for example, call 
    // GetCHString(L"Name", sTemp)) against Instance to see the key values 
    // the client requested.

    return (WBEM_E_PROVIDER_NOT_CAPABLE);
}

/*****************************************************************************
*
*  FUNCTION    :    CNlbsNic::DeleteInstance
*
*  DESCRIPTION :    DeleteInstance, like PutInstance, actually writes information
*                   to the software or hardware.  For most hardware devices, 
*                   DeleteInstance should not be implemented, but for software
*                   configuration, DeleteInstance implementation is plausible.
*
*  INPUTS      :    A pointer to a CInstance object containing the key properties. 
*                   A long that contains the flags described in 
*                   IWbemServices::DeleteInstanceAsync.  
*
*  RETURNS     :    WBEM_E_PROVIDER_NOT_CAPABLE if DeleteInstance is not available.
*                   WBEM_E_FAILED if there is an error deleting the instance.
*                   WBEM_E_INVALID_PARAMETER if any of the instance properties 
*                   are incorrect.
*                   WBEM_S_NO_ERROR if instance is properly deleted.
*
*  COMMENTS    : TO DO: If you don't intend to support deleting instances or are
*                       creating a 'method only' provider, remove this method.
*
*****************************************************************************/
HRESULT CNlbsNic::DeleteInstance ( const CInstance &Instance, long lFlags )
{
    // Use the CInstance Get functions (for example, call 
    // GetCHString(L"Name", sTemp)) against Instance to see the key values 
    // the client requested.

    return (WBEM_E_PROVIDER_NOT_CAPABLE);
}

/*****************************************************************************
*
*  FUNCTION    :    CNlbsNic::ExecMethod
*
*  DESCRIPTION :    Override this function to provide support for methods.  
*                   A method is an entry point for the user of your provider 
*                   to request your class perform some function above and 
*                   beyond a change of state.  (A change of state should be 
*                   handled by PutInstance() )
*
*  INPUTS      :    A pointer to a CInstance containing the instance the method was executed against.
*                   A string containing the method name
*                   A pointer to the CInstance which contains the IN parameters.
*                   A pointer to the CInstance to contain the OUT parameters.
*                   A set of specialized method flags
*
*  RETURNS     :    WBEM_E_PROVIDER_NOT_CAPABLE if not implemented for this class
*                   WBEM_S_NO_ERROR if method executes successfully
*                   WBEM_E_FAILED if error occurs executing method 
*
*  COMMENTS    : TO DO: If you don't intend to support Methods, remove this method.
*
*****************************************************************************/
HRESULT CNlbsNic::ExecMethod ( const CInstance& Instance,
                               const BSTR bstrMethodName,
                               CInstance *pInParams,
                               CInstance *pOutParams,
                               long lFlags)
{
    // For non-static methods, use the CInstance Get functions (for example, 
    // call GetCHString(L"Name", sTemp)) against Instance to see the key 
    // values the client requested.
    HRESULT hresult = WBEM_E_PROVIDER_NOT_CAPABLE;

    CHString     sTemp;

    wstring fullName;

    wstring clusterIPAddress;
    wstring clusterNetworkMask;
    wstring clusterName;
    wstring password;

    _variant_t  hostPriority;
    wstring dedicatedIPAddress;
    wstring dedicatedNetworkMask;

    bool retBool;

    Instance.GetCHString( L"FullName",
                          sTemp );
    fullName = sTemp;

    NICCard nic( NICCard::fullName,
                 fullName );

    NICCard::NICCard_Error err;

    DWORD retValue= 100;

    if (_wcsicmp(bstrMethodName, L"IsBound") == 0)
    {
        // check if bound or not.
        err = nic.isBoundTo( L"ms_wlbs");

        if( err == NICCard::BOUND )
        {
            retValue = 1;
        }
        else if( err == NICCard::UNBOUND )
        {
            retValue = 0;
        }
        else if( err == NICCard::NO_SUCH_NIC )
        {
            retValue = 20;
        }
        else if( err == NICCard::NO_SUCH_COMPONENT )
        {
            retValue = 30;
        }
        else
        {
            retValue = 40;
        }

        hresult = WBEM_S_NO_ERROR;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;

    }
    else if (_wcsicmp(bstrMethodName, L"Bind") == 0)
    {
        // bind nlbs to the nic.
        err = nic.bind( L"ms_wlbs" );
        if( err == NICCard::NICCard_SUCCESS )
        {
            retValue = 0;
        }
        else if( err == NICCard::NO_SUCH_NIC )
        {
            retValue = 20;
        }
        else if( err == NICCard::NO_SUCH_COMPONENT )
        {
            retValue = 30;
        }
        else
        {
            retValue = 40;
        }

        hresult = WBEM_S_NO_ERROR;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;

    }
    else if (_wcsicmp(bstrMethodName, L"Unbind") == 0)
    {
        // unbind nlbs from nic
        err = nic.unbind( L"ms_wlbs" );

        if( err == NICCard::NICCard_SUCCESS )
        {
            retValue = 0;
        }
        else if( err == NICCard::NO_SUCH_NIC )
        {
            retValue = 20;
        }
        else if( err == NICCard::NO_SUCH_COMPONENT )
        {
            retValue = 30;
        }
        else
        {
            retValue = 40;
        }

        hresult = WBEM_S_NO_ERROR;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;

    }
    else if (_wcsicmp(bstrMethodName, L"BindAndConfigure") == 0)
    {
        // here we need to be passed everything
        // required to configure cluster completely
        // on this machine.

        // bind nlbs to the nic.
        err = nic.bind( L"ms_wlbs" );

        if( err == NICCard::NICCard_SUCCESS )
        {
            MNLBProviderSetting nlbs( fullName.c_str() );

            // remove all old port rules.
            // removing LB port rules

            vector<MNLBPortRuleLoadBalanced> portLB;
            nlbs.getPortRulesLoadBalanced( &portLB );
            
            for( int i = 0; i < portLB.size(); ++i )
            {
                nlbs.removePortRuleLoadBalanced( portLB[i] );
            }            

            // removing D port rules

            vector<MNLBPortRuleDisabled> portD;
            nlbs.getPortRulesDisabled( &portD );
            
            for( int i = 0; i < portD.size(); ++i )
            {
                nlbs.removePortRuleDisabled( portD[i] );
            }            
            
            // removing Failover port rules
            vector<MNLBPortRuleFailover> portF;
            nlbs.getPortRulesFailover( &portF );

            for( int i = 0; i < portF.size(); ++i )
            {
                nlbs.removePortRuleFailover( portF[i] );
            }            

            //
            // get port rules to configure.
            //
            SAFEARRAY* portRulesArray;
            SAFEARRAYBOUND sb;
            sb.lLbound = 0;
            sb.cElements = 100;

            portRulesArray = SafeArrayCreate( VT_BSTR, 1, &sb );

            pInParams->GetStringArray( L"PortRules",
                                       portRulesArray );

            vector<_bstr_t> portRulesVector;
            GetVectorFromSafeArray( portRulesArray,
                                    portRulesVector );

            ClusterData clusterData;
            FillInPortRules( &clusterData,
                             L"rashuma",
                             portRulesVector );

            // add all new port rules.
            
            // equal load balanced.
            map< long, PortDataELB>::iterator topELB;

            for( topELB = clusterData.portELB.begin();
                 topELB != clusterData.portELB.end();
                 ++topELB )
            {
                nlbs.addPortRuleLoadBalanced(
                    (*topELB).second );
            }


            // unequal load balanced
            map< long, PortDataULB>::iterator topULB;
            for( topULB = clusterData.portULB.begin();
                 topULB != clusterData.portULB.end();
                 ++topULB )
            {
                MNLBPortRuleLoadBalanced portRuleULB = (*topULB).second;
                portRuleULB._load = 
                    (*topULB).second.machineMapToLoadWeight[L"rashuma"];
                
                nlbs.addPortRuleLoadBalanced(
                    portRuleULB );
            }

            // disabled
            map< long, PortDataD>::iterator topD;
            for( topD = clusterData.portD.begin();
                 topD != clusterData.portD.end();
                 ++topD )
            {
                nlbs.addPortRuleDisabled(
                    (*topD).second );
            }

            // failover
            map< long, PortDataF>::iterator topF;
            for( topF = clusterData.portF.begin();
                 topF != clusterData.portF.end();
                 ++topF )
            {
                MNLBPortRuleFailover portRuleF = (*topF).second;
                portRuleF._priority = 
                    (*topF).second.machineMapToPriority[ L"rashuma" ];
                
                nlbs.addPortRuleFailover( portRuleF );
                
            }

            //
            // set host properties.
            //
            HostProperties hp;

            // host priority
            pInParams->GetVariant( L"HostPriority",
                                   hostPriority );
            hp.hID = hostPriority;

            // dip
            pInParams->GetCHString( L"DedicatedIPAddress",
                                    sTemp );
            dedicatedIPAddress = sTemp;
            hp.hIP = dedicatedIPAddress.c_str();

            // dsn
            pInParams->GetCHString( L"DedicatedNetworkMask",
                                    sTemp );
            dedicatedNetworkMask = sTemp;
            hp.hSubnetMask = dedicatedNetworkMask.c_str();

            // initial state ?
            pInParams->Getbool( L"ClusterModeOnStart",
                                hp.initialClusterStateActive );

            unsigned long retVal;
            nlbs.setHostProperties( hp,
                                    &retVal );

            //
            // set cluster properties.
            //
            ClusterProperties cp;
            RetreiveAndSetClusterProperties(pInParams, nlbs, cp, fullName );

            //
            // if initial state active start cluster
            // else stop cluster.
            //
            MNLBMachine nlbMachine( cp.cIP );

            if( hp.initialClusterStateActive == true )
            {
                nlbMachine.start( Common::THIS_HOST, &retVal );
            }
            else
            {
                nlbMachine.stop( Common::THIS_HOST, &retVal );
            }

            retValue = 0;
        }
        else if( err == NICCard::NO_SUCH_NIC )
        {
            retValue = 20;
        }
        else if( err == NICCard::NO_SUCH_COMPONENT )
        {
            retValue = 30;
        }
        else
        {
            retValue = 40;
        }

        
        hresult = WBEM_S_NO_ERROR;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;

    }
    else if (_wcsicmp(bstrMethodName, L"ModifyClusterProperties") == 0)
    {
        // check if nic is bound to adapter or not.
        err = nic.isBoundTo( L"ms_wlbs" );
        if( err == NICCard::BOUND )
        {
            MNLBProviderSetting nlbs( fullName.c_str() );
            
            //
            // set cluster properties.
            //
            ClusterProperties cp;
            RetreiveAndSetClusterProperties(pInParams, nlbs, cp, fullName);

        }
        else if( err == NICCard::UNBOUND )
        {
            retValue = 10;
        }
        else if( err == NICCard::NO_SUCH_NIC )
        {
            retValue = 20;
        }
        else if( err == NICCard::NO_SUCH_COMPONENT )
        {
            retValue = 30;
        }
        else
        {
            retValue = 40;
        }

        hresult = WBEM_S_NO_ERROR;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;
    }
    else if (_wcsicmp(bstrMethodName, L"GetClusterConfiguration") == 0)
    {
        hresult = ProvGetClusterConfiguration(
                       pInParams,
                       pOutParams
                       );
        return hresult;
    }
    else if (_wcsicmp(bstrMethodName, L"UpdateClusterConfiguration") == 0)
    {
        hresult = ProvUpdateClusterConfiguration(
                       pInParams,
                       pOutParams
                       );
        return hresult;
    }
    else if (_wcsicmp(bstrMethodName, L"QueryConfigurationUpdateStatus") == 0)
    {
        hresult = ProvQueryConfigurationUpdateStatus(
                       pInParams,
                       pOutParams
                       );
        return hresult;
    }
    else
    {
        // unknown call, we do not support this.
        hresult = WBEM_E_PROVIDER_NOT_CAPABLE;
        pOutParams->SetDWORD(L"ReturnValue", retValue);
        return hresult;
    }
}

void
CNlbsNic::RetreiveAndSetClusterProperties(CInstance *pInParams, 
                                          MNLBProviderSetting &nlbs, 
                                          ClusterProperties& cp,
                                          const wstring&    fullName )
{
    CHString     sTemp;
    wstring clusterIPAddress;
    wstring clusterNetworkMask;
    wstring clusterName;
    wstring password;

    // cip
    pInParams->GetCHString( L"ClusterIPAddress", sTemp );
    clusterIPAddress = sTemp;
    cp.cIP = clusterIPAddress.c_str();

    // csn
    pInParams->GetCHString( L"ClusterNetworkMask", sTemp );
    clusterNetworkMask = sTemp;
    cp.cSubnetMask = clusterNetworkMask.c_str();

    // full internet name
    pInParams->GetCHString( L"ClusterName", sTemp );
    clusterName = sTemp;
    cp.cFullInternetName = clusterName.c_str();

    // igmp support?
    pInParams->Getbool( L"IGMPSupport", cp.igmpSupportEnabled );

    // multicast enabled?
    pInParams->Getbool( L"MulticastSupportEnabled", cp.multicastSupportEnabled );

    // remote control enabled?
    pInParams->Getbool( L"RemoteControlEnabled", cp.remoteControlEnabled );

    // password
    pInParams->GetCHString( L"Password", sTemp );
    password = sTemp;
    cp.password = password.c_str();

    unsigned long retVal;
    try
    {
        nlbs.setClusterProperties( cp, &retVal );
    }
    catch( _com_error e )
    {
    }

    // set password.
    try
    {
        if( cp.remoteControlEnabled == true )
        {
            nlbs.setPassword( cp.password,
                              &retVal );
        }
    }
    catch( _com_error e )
    {
    }
    
    //
    // add cluster ip
    //
    MIPAddressAdmin ipAdmin( fullName.c_str() );
    
    long SleepDuration = 0;
    while ((ipAdmin.addIPAddress(cp.cIP, cp.cSubnetMask) != MIPAddressAdmin::MIPAddressAdmin_SUCCESS)
           &&(SleepDuration < PROTOCOL_BIND_DELAY))
    {
        SleepDuration += PROTOCOL_BIND_WAIT_INCREMENT;
        Sleep(PROTOCOL_BIND_WAIT_INCREMENT);
    }
}

void
CNlbsNic::GetVectorFromSafeArray( SAFEARRAY*&  stringArray, 
                                  vector<_bstr_t>& strings )
{
    LONG count = stringArray->rgsabound[0].cElements;
    BSTR* pbstr;
    HRESULT hr;

    if( SUCCEEDED( SafeArrayAccessData( stringArray, ( void **) &pbstr)))
    {
        for( LONG x = 0; x < count; x++ )
        {
            strings.push_back( pbstr[x] );
        }

        hr = SafeArrayUnaccessData( stringArray );
    }
}    


void 
CNlbsNic::FillInPortRules( ClusterData*           p_clusterData,
                           const _bstr_t&         myMachine,
                           const vector<_bstr_t>& portRules )
{
    wchar_t portBuf[1000];
    wstring temp;
    WTokens tok;
    vector<wstring> tokens;

    for( int i = 0; i < portRules.size(); ++i )
    {
        wcscpy( portBuf, portRules[i] );

        tok.init( portBuf, L"\t");
        tokens = tok.tokenize();

        if( tokens[3] == L"Multiple" )
        {
            if( tokens[5] == L"Equal" )
            {
                p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._startPort = _wtoi( tokens[0].c_str() );
                p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._endPort = _wtoi( tokens[1].c_str() );
                if( tokens[2] == L"Both" )
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::both;
                }
                else if( tokens[2] == L"TCP" )
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::tcp;
                }
                else
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::udp;
                }
            
                if( tokens[6] == L"Single" )
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::single;
                }
                else if( tokens[6] == L"None" ) 
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::none;
                }
                else
                {
                    p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::classC;
                }

                p_clusterData->portELB[ _wtoi( tokens[0].c_str()) ]._isEqualLoadBalanced = true;
                
            }
            else
            {
                p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._startPort = _wtoi( tokens[0].c_str() );
                p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._endPort = _wtoi( tokens[1].c_str() );
                if( tokens[2] == L"Both" )
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::both;
                }
                else if( tokens[2] == L"TCP" )
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::tcp;
                }
                else
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::udp;
                }
                
                if( tokens[6] == L"Single" )
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::single;
                }
                else if( tokens[6] == L"None" ) 
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::none;
                }
                else
                {
                    p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._affinity = MNLBPortRule::classC;
                }

                p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ]._isEqualLoadBalanced = false;

                p_clusterData->portULB[ _wtoi( tokens[0].c_str()) ].machineMapToLoadWeight[myMachine] = 
                    _wtoi( tokens[5].c_str() );
                
            }
        }
        else if ( tokens[3] == L"Single" )
        {
            p_clusterData->portF[ _wtoi( tokens[0].c_str()) ]._startPort = _wtoi( tokens[0].c_str() );
            p_clusterData->portF[ _wtoi( tokens[0].c_str()) ]._endPort = _wtoi( tokens[1].c_str() );
            if( tokens[2] == L"Both" )
            {
                p_clusterData->portF[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::both;
            }
            else if( tokens[2] == L"TCP" )
            {
                p_clusterData->portF[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::tcp;
            }
            else
            {
                p_clusterData->portF[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::udp;
            }
            
            p_clusterData->portF[ _wtoi( tokens[0].c_str()) ].machineMapToPriority[myMachine] = 
                _wtoi( tokens[4].c_str() );
        }
        else
        {
            p_clusterData->portD[ _wtoi( tokens[0].c_str()) ]._startPort = _wtoi( tokens[0].c_str() );
            p_clusterData->portD[ _wtoi( tokens[0].c_str()) ]._endPort = _wtoi( tokens[1].c_str() );
            if( tokens[2] == L"Both" )
            {
                p_clusterData->portD[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::both;
            }
            else if( tokens[2] == L"TCP" )
            {
                p_clusterData->portD[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::tcp;
            }
            else
            {
                p_clusterData->portD[ _wtoi( tokens[0].c_str()) ]._trafficToHandle = MNLBPortRule::udp;
            }
        }
    }
}


WBEMSTATUS
ProvGetClusterConfiguration(
           CInstance *pInParams,
           CInstance *pOutParams
           )
/*++

    WMI provider wrapper around  NlbConfigurationUpdate::GetConfiguration

--*/
{
    if (!g_UpdateConfigurationEnabled) return WBEM_E_PROVIDER_NOT_CAPABLE;

    LPCWSTR pAdapterGuid = NULL;
    WBEMSTATUS Status =  WBEM_E_PROVIDER_NOT_CAPABLE;
    CHString     sTemp;
    bool         fRet;
    NLB_EXTENDED_CLUSTER_CONFIGURATION Cfg;
    SAFEARRAY   *pSA = NULL;

    /*
        [IN]  String  AdapterGuid,
        [OUT] uint32  Generation,
        [OUT] String  NetworkAddresses[], // "10.1.1.1/255.0.0.0"
        [OUT] Boolean NLBBound,
        [OUT] String  ClusterNetworkAddress, // "10.1.1.1/255.0.0.0"
        [OUT] String  ClusterName,
        [OUT] String  TrafficMode, // UNICAST MULTICAST IGMPMULTICAST
        [OUT] String  PortRules[],
        [OUT] uint32  HostPriority,
        [OUT] String  DedicatedNetworkAddress, // "10.1.1.1/255.0.0.0"
        [OUT] Boolean ClusterModeOnStart,
        [OUT] Boolean RemoteControlEnabled
    */

    fRet = pInParams->GetCHString( L"AdapterGuid", sTemp );
    if (!fRet)
    {
        TRACE_CRIT("->%!FUNC!: Missing adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }

    //
    // Note: (LPCWSTR) sTemp returns an internal pointer to sTemp's char 
    // buffer -- see operator LPCWSTR() of WString docs.
    //
    pAdapterGuid = (LPCWSTR) sTemp;

    if (pAdapterGuid == NULL || *pAdapterGuid == 0)
    {
        TRACE_CRIT("->%!FUNC!: Null of empty adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }
    else
    {
        TRACE_VERB(L"->%!FUNC!(Nic=%ws)", pAdapterGuid);
    }

    Status = NlbConfigurationUpdate::GetConfiguration(
                pAdapterGuid,
                &Cfg
                );

    if (FAILED(Status))
    {
       goto end; 
    }

    pOutParams->SetDWORD(L"ReturnValue", (DWORD) WBEM_NO_ERROR);
    pOutParams->SetDWORD(L"Generation", Cfg.GetGeneration());

    //
    // Fill in NetworkAddresses[]
    //
    {
        Status = Cfg.GetNetworkAddressesSafeArray(
                        &pSA
                        );
        if (FAILED(Status))
        {
            TRACE_CRIT(
                "%!FUNC!: couldn't extract network addresses from Cfg"
                " for NIC %ws",
                pAdapterGuid
                );
            goto end;
        }
        

        if (pSA!=NULL)
        {
            pOutParams->SetStringArray(
                    L"NetworkAddresses",
                    *pSA // pass by reference
                    );
            SafeArrayDestroy(pSA);
            pSA = NULL;
        }
    }

    if (!Cfg.IsNlbBound())
    {
        //
        // NLB is bound
        //

        pOutParams->Setbool(L"NLBBound", FALSE);
        Status = WBEM_NO_ERROR;
        goto end;
    }
    
    //
    // NLB is bound
    //

    pOutParams->Setbool(L"NLBBound", TRUE);

    if (!Cfg.IsValidNlbConfig())
    {
        TRACE_CRIT(
            "%!FUNC!: NLB-specific configuration on NIC %ws is invalid",
            pAdapterGuid
            );
        goto end;
    }

    //
    // Cluster name
    //
    {
        LPWSTR szName = NULL;
        Status = Cfg.GetClusterName(&szName);

        if (FAILED(Status))
        {
            TRACE_CRIT(
                "%!FUNC!: Could not extract cluster name for NIC %ws",
                pAdapterGuid
                );
            goto end;
        }
        pOutParams->SetCHString(L"ClusterName", szName);
        delete (szName);
        szName = NULL;
    }
    
    //
    // Cluster and dedicated network addresses
    //
    {
        LPWSTR szAddress = NULL;
        Status = Cfg.GetClusterNetworkAddress(&szAddress);

        if (FAILED(Status))
        {
            TRACE_CRIT(
                "%!FUNC!: Could not extract cluster address for NIC %ws",
                pAdapterGuid
                );
            goto end;
        }
        pOutParams->SetCHString(L"ClusterNetworkAddress", szAddress);
        delete (szAddress);
        szAddress = NULL;

        Status = Cfg.GetDedicatedNetworkAddress(&szAddress);

        if (FAILED(Status))
        {
            TRACE_CRIT(
                "%!FUNC!: Could not extract dedicated address for NIC %ws",
                pAdapterGuid
                );
            goto end;
        }
        pOutParams->SetCHString(L"DedicatedNetworkAddress", szAddress);
        delete (szAddress);
        szAddress = NULL;
    }

    //
    // TrafficMode
    //
    {
        LPCWSTR szMode = NULL;
        switch(Cfg.GetTrafficMode())
        {
        case NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_UNICAST:
            szMode = L"UNICAST";
            break;
        case NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_MULTICAST:
            szMode = L"MULTICAST";
            break;
        case NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_IGMPMULTICAST:
            szMode = L"IGMPMULTICAST";
            break;
        default:
            assert(FALSE);
            Status = WBEM_E_CRITICAL_ERROR;
            goto end;
        }
        pOutParams->SetCHString(L"TrafficMode", szMode);
    }

    pOutParams->SetDWORD(L"HostPriority", Cfg.GetHostPriority());

    if (Cfg.GetClusterModeOnStart() ==
        NLB_EXTENDED_CLUSTER_CONFIGURATION::START_MODE_STARTED)
    {
        pOutParams->Setbool(L"ClusterModeOnStart", TRUE);
    }
    else
    {
        pOutParams->Setbool(L"ClusterModeOnStart", FALSE);
    }

    pOutParams->Setbool(L"RemoteControlEnabled", Cfg.GetRemoteControlEnabled());
    

    //
    // TODO: get port rules
    // [OUT] String  PortRules[],
    //
    

    Status = WBEM_NO_ERROR;

end:

    if (pSA!=NULL)
    {
        SafeArrayDestroy(pSA);
        pSA = NULL;
    }

    TRACE_VERB(L"<-%!FUNC! returns 0x%08lx", (UINT) Status);

    return Status;

}


WBEMSTATUS
ProvUpdateClusterConfiguration(
           CInstance *pInParams,
           CInstance *pOutParams
           )
/*++

    WMI provider wrapper NlbConfigurationUpdate::UpdateConfiguration
    with some additional wrinkles:
    we selectively update the current version.

--*/
{
    if (!g_UpdateConfigurationEnabled) return WBEM_E_PROVIDER_NOT_CAPABLE;

    LPCWSTR pAdapterGuid = NULL;
    LPCWSTR pClientDescription = L"Unspecified WMI Client"; // TODO: localize
    WBEMSTATUS Status =  WBEM_E_PROVIDER_NOT_CAPABLE;
    CHString     sClientDescription;
    CHString     sAdapterGuid;
    CHString     sTemp;
    bool         fRet;
    NLB_EXTENDED_CLUSTER_CONFIGURATION Cfg;
    SAFEARRAY   *pSA = NULL;

    /*
        [IN] String  ClientDescription,
        [IN] String  AdapterGuid,
        [IN] uint32  Generation,
        [IN] Boolean PartialUpdate,
        [IN] String  NetworkAddresses[], // "10.1.1.1/255.255.255.255"
        [IN] Boolean NLBBound,
        [IN] String  ClusterNetworkAddress, // "10.1.1.1/255.0.0.0"
        [IN] String  ClusterName,
        [IN] String  TrafficMode, // UNICAST MULTICAST IGMPMULTICAST
        [IN] String  PortRules[],
        [IN] uint32  HostPriority,
        [IN] String  DedicatedNetworkAddress, // "10.1.1.1/255.0.0.0"
        [IN] Boolean ClusterModeOnStart,
        [IN] Boolean RemoteControlEnabled,
        [IN] String  Password,
        [OUT] uint32 NewGeneration,
        [OUT] String Log
    */

    fRet = pInParams->GetCHString( L"ClientDescription", sClientDescription);
    if (fRet)
    {
        // Note: (LPCWSTR) sTemp returns an internal pointer to sTemp's char 
        pClientDescription = (LPCWSTR) sClientDescription;
    }

    fRet = pInParams->GetCHString( L"AdapterGuid", sAdapterGuid);
    if (!fRet)
    {
        TRACE_CRIT("->%!FUNC!: Missing adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }

    //
    // Note: (LPCWSTR) sTemp returns an internal pointer to sTemp's char 
    // buffer -- see operator LPCWSTR() of WString docs.
    //
    pAdapterGuid = (LPCWSTR) sAdapterGuid;

    if (pAdapterGuid == NULL || *pAdapterGuid == 0)
    {
        TRACE_CRIT("->%!FUNC!: Null of empty adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }
    else
    {
        TRACE_VERB(L"->%!FUNC!(Nic=%ws)", pAdapterGuid);
    }

    //
    // Get the current configuration
    //
    Status = NlbConfigurationUpdate::GetConfiguration(
                pAdapterGuid,
                &Cfg
                );

    if (FAILED(Status))
    {
       goto end; 
    }


    //
    // Modify the snapshot of the current configuration with whatever
    // cluster configuration information is specified in the input
    // parameters
    //
    {
        DWORD       InGeneration    = 0;
        bool        NlbBound        = FALSE;
        bool        bResult         = FALSE;
        bool        bPartialUpdate   = FALSE;
    
        //
        // Determine if this is a partial or full update.
        // If partial update, we allow a subset of cluster configuration
        // parameters to be specified, but allow only a restricted set
        // of update operations.
        //
        // Disallowed partial update operations:
        //  - Transitions between bound and !bound 
        //  - Currently bound  but nlb parameters are invalid
        //
        // Some allowed partial updates:
        //  - Modifying IP address lists
        //  - Modifying cluster / dedicated addresses/subnets
        //  - Modifying existing portrules
        //  - Adding/deleting port rules
        //

        bResult = pInParams->GetDWORD(
                    L"Generation",      // <--------------------------------
                    InGeneration
                    );
        if (!bResult)
        {
            //
            // We allow generation to be unspecified.
            //
            InGeneration = 0;
        }
        else
        {
            //
            // If generation is specified,
            // we verify that the current generation matches the
            // specified generation.
            // TODO: this really must be done in the context of
            // mfn_Start update -- after we've acquired the global lock!
            //
            if (InGeneration != Cfg.GetGeneration())
            {
                    TRACE_CRIT("Partial update: input generation(%lu) != current generation(%lu)", InGeneration, Cfg.GetGeneration());
                    Status = WBEM_E_HANDLE_OUT_OF_DATE;
                    goto end;
            }
        }

        bResult = pInParams->Getbool(
                        L"NLBBound",    // <--------------------------------
                        NlbBound
                        );
    
        if (!bResult)
        {
            NlbBound = Cfg.IsNlbBound();
            TRACE_CRIT(L"Could not read NLBBound -- assuming current state %d.",
                 NlbBound);
        }

        bResult = pInParams->GetStringArray(
                    L"NetworkAddresses", // <--------------------------------
                    pSA
                    );
        if (!bResult)
        {
            //
            // We set pCfg to zero addresses, which causes update to
            // use it's own defaults...
            //
            TRACE_CRIT(L"Could not read Network addresses -- using defaults");
            Status = Cfg.SetNetworkAddresses(NULL, 0);
            pSA = NULL;
        }
        else
        {
            if (pSA != NULL)
            {
                Status = Cfg.SetNetworkAddressesSafeArray(pSA);
                SafeArrayDestroy(pSA);
                pSA = NULL;
            }
        }

        if (!NlbBound)
        {
            // NLB is not to be bound -- no need to read the input params.
            Cfg.fBound = FALSE;
            Cfg.fValidNlbCfg = FALSE;
        }
        else
        {
            BOOL fNewConfig = FALSE;

            if (!Cfg.fBound || Cfg.fValidNlbCfg == FALSE)
            {
                //
                // If we were previously unbound or we were bound but with
                // a bad configuration, we need to setup our
                // new cfg with good defaults
                //
                CfgUtilInitializeParams(&Cfg.NlbParams);
                Cfg.fBound = TRUE;
                Cfg.fValidNlbCfg = TRUE;
                fNewConfig = TRUE;
            }
        
            bResult = pInParams->GetCHString(
                            L"ClusterNetworkAddress", // <--------------------
                            sTemp
                            );
        
            if (!bResult)
            {
                if (fNewConfig)
                {
                    //
                    // Cluster address MUST be specified for new config.
                    //
                    TRACE_CRIT(L"ERROR: Could not read Cluster IP for new config.");
                    Status =  WBEM_E_INVALID_PARAMETER;
                    goto end;
                    
                }
                TRACE_CRIT(L"Could not read Cluster IP. Keeping existing.");
            }
            else
            {
                LPCWSTR      szClusterNetworkAddress = NULL;
                szClusterNetworkAddress = (LPCWSTR) sTemp; // no copies here.
                Cfg.SetClusterNetworkAddress(szClusterNetworkAddress);
                szClusterNetworkAddress = NULL;
            }
        
            bResult = pInParams->GetCHString(
                            L"ClusterName", // <-------------------------
                            sTemp
                            );
        
            if (!bResult)
            {
                TRACE_CRIT(L"Could not read Cluster Name. Keeping existing");
            }
            else
            {
                LPCWSTR      szClusterName = NULL;
                szClusterName = (LPCWSTR) sTemp; // no copies here.
                Cfg.SetClusterName(szClusterName);
                szClusterName = NULL;
            }
        
            //
            // Traffic mode
            //
            {
                bResult = pInParams->GetCHString(
                                L"TrafficMode", // <-------------------------
                                sTemp
                                );
            
                if (!bResult)
                {
                    TRACE_CRIT(L"Could not read TrafficMode. Keeping existing");
                }
                else
                {
                    LPCWSTR      szTrafficMode = NULL;
                    NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE TrafficMode
                    =  NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_UNICAST;
                    szTrafficMode = (LPCWSTR) sTemp; // no copies here.
        
                    if (!_wcsicmp(szTrafficMode, L"UNICAST"))
                    {
                        TrafficMode =
                        NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_UNICAST;
                    }
                    else if (!_wcsicmp(szTrafficMode, L"MULTICAST"))
                    {
                        TrafficMode =
                        NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_MULTICAST;
                    }
                    else if (!_wcsicmp(szTrafficMode, L"IGMPMULTICAST"))
                    {
                        TrafficMode =
                        NLB_EXTENDED_CLUSTER_CONFIGURATION::TRAFFIC_MODE_IGMPMULTICAST;
                    }
                    else
                    {
                        TRACE_CRIT("Invalid TrafficMode: %ws", szTrafficMode);
                        Status =  WBEM_E_INVALID_PARAMETER;
                        goto end;
                    }

                    Cfg.SetTrafficMode(TrafficMode);
                    szTrafficMode = NULL;
                }
            }
        
            //
            // TODO: process port rules.
            //          [OUT] String  PortRules[]
            //

            DWORD HostPriority = 0; 
            bResult = pInParams->GetDWORD(
                        L"HostPriority",      // <---------------------------
                        HostPriority
                        );
            if (!bResult)
            {
                TRACE_CRIT(L"Could not read HostPriority. Keeping existing");
            }
            else
            {
                Cfg.SetHostPriority(HostPriority);
            }
        
            bResult = pInParams->GetCHString(
                            L"DedicatedNetworkAddress", // <-----------------
                            sTemp
                            );
        
            if (!bResult)
            {
                TRACE_CRIT(L"Could not dedicated IP. Keeping existing");
            }
            else
            {
                LPCWSTR      szAddress = NULL;
                szAddress = (LPCWSTR) sTemp; // no copies here.
                Cfg.SetDedicatedNetworkAddress(szAddress);

                //
                // For now, we'll always try to  add the dedicated IP address
                // to the NIC.
                //
                Cfg.fAddDedicatedIp = TRUE;
                szAddress = NULL;
            }
            
            //
            // StartMode
            //
            {
                bool StartMode = FALSE;
                bResult = pInParams->Getbool(
                                L"ClusterModeOnStart",   // <-----------------
                                StartMode
                                );
            
                if (!bResult)
                {
                    TRACE_CRIT(L"Could not read StartMode. Keeping existing");
                }
                else
                {
                    NLB_EXTENDED_CLUSTER_CONFIGURATION::START_MODE
                    ClusterModeOnStart;
                    if (StartMode)
                    {
                        ClusterModeOnStart = 
                         NLB_EXTENDED_CLUSTER_CONFIGURATION::START_MODE_STARTED;
                    }
                    else
                    {
                        ClusterModeOnStart = 
                         NLB_EXTENDED_CLUSTER_CONFIGURATION::START_MODE_STOPPED;
                    }
                    Cfg.SetClusterModeOnStart(ClusterModeOnStart);
                }
            }
        
            //
            // Remote control enabled
            //
            {
                bool bRemoteControlEnabled;
                bResult = pInParams->Getbool(
                                L"RemoteControlEnabled",   // <---------------
                                bRemoteControlEnabled
                                );
            
                if (!bResult)
                {
                    TRACE_CRIT(L"Could not read RemoteControlEnabled. Keeping existing");
                }
                else
                {
                    Cfg.SetRemoteControlEnabled(bRemoteControlEnabled!=FALSE);
                }
            }

            //
            // TODO: if PartialUpdate is specified, we need to 
            // make sure that fValidNlbCfg is already set.
            //
            Cfg.fValidNlbCfg = TRUE;
    
        } while (FALSE) ;

    }

    //
    // Call NlbConfigurationUpdate::DuUpdate to do the actual work.
    //
    UINT NewGeneration = 0;
    LPWSTR pLog = NULL;

    Status = NlbConfigurationUpdate::DoUpdate(
                pAdapterGuid,
                pClientDescription,
                &Cfg,
                &NewGeneration,
                &pLog
                );

    //
    // Fill out the out parameters: status new generation and log
    //
    pOutParams->SetDWORD(L"ReturnValue", (DWORD) Status);
    pOutParams->SetDWORD(L"NewGeneration", (DWORD) NewGeneration);
    if (pLog != NULL)
    {
        pOutParams->SetCHString(L"Log", pLog);
        delete pLog;
        pLog = NULL;
    }

    //
    // If we've actually called DoUpdate,
    // we always return WBEM_NO_ERROR. The return value has the
    // real result.
    //
    Status = WBEM_NO_ERROR;

end:

    if (pSA!=NULL)
    {
        SafeArrayDestroy(pSA);
        pSA = NULL;
    }

    TRACE_VERB(L"<-%!FUNC! returns 0x%08lx", (UINT) Status);

    return Status;

}


WBEMSTATUS
ProvQueryConfigurationUpdateStatus(
           CInstance *pInParams,
           CInstance *pOutParams
           )
/*++

    WMI provider wrapper around NlbConfigurationUpdate::GetUpdateStatus

--*/
{
    if (!g_UpdateConfigurationEnabled) return WBEM_E_PROVIDER_NOT_CAPABLE;

    LPCWSTR pAdapterGuid = NULL;
    WBEMSTATUS Status =  WBEM_E_PROVIDER_NOT_CAPABLE;
    CHString     sTemp;
    bool         fRet;
    DWORD       Generation = 0;

    /*
        [IN] String  AdapterGuid,
        [IN] uint32  Generation,
        [OUT] String Log
    */

    fRet = pInParams->GetCHString( L"AdapterGuid", sTemp);
    if (!fRet)
    {
        TRACE_CRIT("->%!FUNC!: Missing adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }

    //
    // Note: (LPCWSTR) sTemp returns an internal pointer to sTemp's char 
    // buffer -- see operator LPCWSTR() of WString docs.
    //
    pAdapterGuid = (LPCWSTR) sTemp;

    if (pAdapterGuid == NULL || *pAdapterGuid == 0)
    {
        TRACE_CRIT("->%!FUNC!: Null of empty adapter guid!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }
    else
    {
        TRACE_VERB(L"->%!FUNC!(Nic=%ws)", pAdapterGuid);
    }

    fRet = pInParams->GetDWORD(
                L"Generation",      // <--------------------------------
                Generation
                );
    if (!fRet)
    {
        TRACE_CRIT("%!FUNC!: Missing generation!");
        Status =  WBEM_E_INVALID_PARAMETER;
        goto end;
    }

    //
    // Call NlbConfigurationUpdate::GetUpdateResult to do the actual work.
    //
    LPWSTR pLog = NULL;
    WBEMSTATUS CompletionStatus = WBEM_NO_ERROR;

    Status = NlbConfigurationUpdate::GetUpdateStatus(
                pAdapterGuid,
                Generation,
                FALSE,  // FALSE == Don't delete completion record
                &CompletionStatus,
                &pLog
                );

    if (!FAILED(Status))
    {
        //
        // Fill out the out parameters: status new generation and log
        //
        pOutParams->SetDWORD(L"ReturnValue", (DWORD) CompletionStatus);
        if (pLog != NULL)
        {
            pOutParams->SetCHString(L"Log", pLog);
            delete pLog;
            pLog = NULL;
        }
    }

end:

    TRACE_VERB(L"<-%!FUNC! returns 0x%08lx", (UINT) Status);

    return Status;

}
