Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1748 lines
54 KiB

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1999.
//
// File: M O D I F Y . C P P
//
// Contents: Routines used to setup modifications to the network
// configuration.
//
// Notes:
//
// Author: shaunco 15 Jan 1999
//
//----------------------------------------------------------------------------
#include <pch.h>
#pragma hdrstop
#include "classinst.h"
#include "filtdevs.h"
#include "guisetup.h"
#include "inetcfg.h"
#include "lockdown.h"
#include "ncmsz.h"
#include "ncreg.h"
#include "ncsvc.h"
#include "ndispnp.h"
#include "netcfg.h"
#include "persist.h"
#include "pnpbind.h"
#include "pszarray.h"
#include "util.h"
#include "wscfg.h"
#include "ncwins.h"
#include "ncperms.h"
CNetConfig*
CModifyContext::PNetConfig ()
{
TraceFileFunc(ttidNetcfgBase);
CNetConfig* pNetConfig;
Assert ((LONG_PTR)this > FIELD_OFFSET(CNetConfig, ModifyCtx));
// Get our containing CNetConfig pointer.
//
pNetConfig = CONTAINING_RECORD(this, CNetConfig, ModifyCtx);
pNetConfig->Core.DbgVerifyData ();
return pNetConfig;
}
HRESULT
CModifyContext::HrDirtyComponent (
IN const CComponent* pComponent)
{
TraceFileFunc(ttidNetcfgBase);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (pComponent);
m_hr = m_DirtyComponents.HrInsertComponent (pComponent,
INS_IGNORE_IF_DUP | INS_SORTED);
#if DBG
m_fComponentExplicitlyDirtied = TRUE;
#endif
TraceHr (ttidError, FAL, m_hr, FALSE,
"CModifyContext::HrDirtyComponentAndComponentsAbove");
return m_hr;
}
HRESULT
CModifyContext::HrDirtyComponentAndComponentsAbove (
IN const CComponent* pComponent)
{
TraceFileFunc(ttidNetcfgBase);
GCCONTEXT Ctx;
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (pComponent);
// And insert the component itself.
//
m_hr = HrDirtyComponent (pComponent);
// Only dirty the ones above if this component doesn't have the
// NCF_DONTEXPOSELOWER characteristic.
//
if ((S_OK == m_hr) && !(pComponent->m_dwCharacter & NCF_DONTEXPOSELOWER))
{
// Initialize the members of our context structure for recursion.
//
ZeroMemory (&Ctx, sizeof(Ctx));
Ctx.pStackTable = &(PNetConfig()->Core.StackTable);
Ctx.pComponents = &m_DirtyComponents;
// Insert all of the component above.
//
GetComponentsAboveComponent (pComponent, &Ctx);
m_hr = Ctx.hr;
}
TraceHr (ttidError, FAL, m_hr, FALSE,
"CModifyContext::HrDirtyComponentAndComponentsAbove");
return m_hr;
}
HRESULT
CModifyContext::HrApplyIfOkOrCancel (
IN BOOL fApply)
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
CNetConfig* pNetConfig;
Assert (m_fPrepared);
pNetConfig = PNetConfig();
// Only apply if the context result is S_OK.
//
if (fApply && (S_OK == m_hr))
{
// Setupapi calls that we make during ApplyChanges have the
// potential to return control to our clients windows message loop.
// When this happens, and our clients are poorly written, they
// may try to re-enter us on the same thread. This is disaster
// waiting to happen, so we need to prevent it by raising our
// reentrancy protection level before we start apply changes.
//
pNetConfig->Notify.PINetCfg()->RaiseRpl (RPL_DISALLOW);
ApplyChanges ();
pNetConfig->Notify.PINetCfg()->LowerRpl (RPL_DISALLOW);
// Delete those components from m_CoreStartedWith that are not
// in the current core and reset the modify context.
//
m_CoreStartedWith.Components.FreeComponentsNotInOtherComponentList (
&pNetConfig->Core.Components);
m_CoreStartedWith.Clear();
hr = S_OK;
// Return the correct HRESULT to the caller. If we've successfully
// applied, but need a reboot, return so.
//
if (m_fRebootRecommended || m_fRebootRequired)
{
hr = NETCFG_S_REBOOT;
}
}
else
{
// Cancel and release all notify objects. Do this for what is
// in the core as well as what we started with. (There will
// be some overlap, but they will only be released once.)
// We need to do both sets so we don't miss releasing components
// that have been removed. (Removed components won't be in
// current core, but they will be in the core that we started
// with.) Likewise, if we just released the core we started with,
// we'd miss releasing those components that were added.)
//
pNetConfig->Notify.ReleaseAllNotifyObjects (pNetConfig->Core.Components, TRUE);
pNetConfig->Notify.ReleaseAllNotifyObjects (m_CoreStartedWith.Components, TRUE);
// Delete those components from m_CoreStartedWith that are not
// in the current core. Then delete all the components in the
// current core and reload from our persistent storage.
// (This has the nice effect of invalidating all outstanding
// INetCfgComponent interfaces.)
//
m_CoreStartedWith.Components.FreeComponentsNotInOtherComponentList (
&pNetConfig->Core.Components);
pNetConfig->Core.Free ();
// Eject both cores (didn't you just know this metaphor was coming ;-)
// and reload the core from our persisted binary. This magically,
// and completely rolls everything back.
//
m_CoreStartedWith.Clear();
pNetConfig->Core.Clear();
// Return reason for failure through hr.
//
hr = m_hr;
// Reload the configuration and, if successful, it means m_hr
// will be S_OK. If unsuccessful, m_hr will be the error and will
// prevent subsequent operations.
//
m_hr = HrLoadNetworkConfigurationFromRegistry (KEY_READ, pNetConfig);
}
// Very important to set m_fPrepared back to FALSE so that HrPrepare gets
// called for the next modifcation and correctly copy the core etc.
//
m_fPrepared = FALSE;
m_AddedBindPaths.Clear();
m_DeletedBindPaths.Clear();
m_DirtyComponents.Clear();
#if DBG
m_fComponentExplicitlyDirtied = FALSE;
#endif
Assert (!m_fPrepared);
Assert (m_CoreStartedWith.FIsEmpty());
Assert (m_AddedBindPaths.FIsEmpty());
Assert (m_DeletedBindPaths.FIsEmpty());
Assert (m_DirtyComponents.FIsEmpty());
Assert (0 == m_ulRecursionDepth);
Assert (!m_fComponentExplicitlyDirtied);
Assert ((S_OK == hr) || (NETCFG_S_REBOOT == hr) || FAILED(hr));
TraceHr (ttidError, FAL, hr, NETCFG_S_REBOOT == hr,
"CModifyContext::HrApplyIfOkOrCancel");
return hr;
}
HRESULT
CModifyContext::HrPrepare ()
{
TraceFileFunc(ttidNetcfgBase);
Assert (S_OK == m_hr);
Assert (!m_fPrepared);
Assert (m_CoreStartedWith.FIsEmpty());
Assert (m_AddedBindPaths.FIsEmpty());
Assert (m_DeletedBindPaths.FIsEmpty());
Assert (m_DirtyComponents.FIsEmpty());
Assert (0 == m_ulRecursionDepth);
Assert (!m_fComponentExplicitlyDirtied);
CNetConfig* pThis;
pThis = PNetConfig();
// Prepare the bind context. This will ensure all of the external
// data for all components is loaded as well as all ensuring that
// all notify objects have been initialized.
//
m_hr = m_RegBindCtx.HrPrepare (pThis);
if (S_OK != m_hr)
{
goto finished;
}
// Snapshot the current core so that we know what we started with.
// We will use the differences when we apply (if we get that far).
//
m_hr = m_CoreStartedWith.HrCopyCore (&pThis->Core);
if (S_OK != m_hr)
{
goto finished;
}
// Reserve room for 64 components in the core.
// (64 * 4 = 256 bytes on 32-bit platforms)
//
m_hr = pThis->Core.Components.HrReserveRoomForComponents (64);
if (S_OK != m_hr)
{
goto finished;
}
// Reserve room for 64 stack entries in the core.
// (64 * (4 + 4) = 512 bytes on 32-bit platforms)
//
m_hr = pThis->Core.StackTable.HrReserveRoomForEntries (64);
if (S_OK != m_hr)
{
goto finished;
}
// Reserve room in our added list for 64 bindpaths of 8 components.
// (64 * 16 = 1K bytes on 32-bit platforms)
//
m_hr = m_AddedBindPaths.HrReserveRoomForBindPaths (64);
if (S_OK != m_hr)
{
goto finished;
}
// Reserve room in our deleted list for 64 bindpaths of 8 components.
// (64 * 16 = 1K bytes on 32-bit platforms)
//
m_hr = m_DeletedBindPaths.HrReserveRoomForBindPaths (64);
if (S_OK != m_hr)
{
goto finished;
}
// Reserve room for 64 components in the dirty component list.
// (64 * 4) = 256 bytes on 32-bit platforms)
//
m_hr = m_DirtyComponents.HrReserveRoomForComponents (64);
if (S_OK != m_hr)
{
goto finished;
}
m_fPrepared = TRUE;
finished:
TraceHr (ttidError, FAL, m_hr, FALSE, "CModifyContext::HrPrepare");
return m_hr;
}
HRESULT
CModifyContext::HrBeginBatchOperation ()
{
TraceFileFunc(ttidNetcfgBase);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (0 == m_ulRecursionDepth);
TraceTag (ttidBeDiag, "Begin batch operation...");
PushRecursionDepth();
return m_hr;
}
HRESULT
CModifyContext::HrEndBatchOperation (
IN EBO_FLAG Flag)
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
Assert (m_fPrepared);
Assert (1 == m_ulRecursionDepth);
if (EBO_COMMIT_NOW == Flag)
{
TraceTag (ttidBeDiag, "End batch (commiting changes)...");
hr = HrPopRecursionDepth ();
}
else
{
Assert (EBO_DEFER_COMMIT_UNTIL_APPLY == Flag);
TraceTag (ttidBeDiag, "End batch (deferring commit until Apply)...");
m_ulRecursionDepth = 0;
hr = S_OK;
}
Assert (0 == m_ulRecursionDepth);
TraceHr (ttidError, FAL, hr, FALSE,
"CModifyContext::HrEndBatchOperation");
return hr;
}
VOID
CModifyContext::PushRecursionDepth ()
{
TraceFileFunc(ttidNetcfgBase);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
m_ulRecursionDepth++;
}
HRESULT
CModifyContext::HrPopRecursionDepth ()
{
TraceFileFunc(ttidNetcfgBase);
Assert (m_fPrepared);
Assert (m_ulRecursionDepth > 0);
m_ulRecursionDepth--;
if (0 != m_ulRecursionDepth)
{
return m_hr;
}
// We're at the top-level of the install or remove modifcation so
// apply or cancel the changes depending on the state of the context
// result.
//
HRESULT hr;
hr = HrApplyIfOkOrCancel (S_OK == m_hr);
TraceHr (ttidError, FAL, hr, FALSE,
"CModifyContext::HrPopRecursionDepth");
return hr;
}
//----------------------------------------------------------------------------
// This is a convenience method to find and process Winsock Remove
// section for a component which is about to be removed.
HRESULT
CModifyContext::HrProcessWinsockRemove(IN const CComponent *pComponent)
{
TraceFileFunc(ttidNetcfgBase);
HINF hinf = NULL;
HKEY hkeyInstance = NULL;
HRESULT hr;
Assert(pComponent);
hr = pComponent->HrOpenInfFile(&hinf);
if (S_OK == hr)
{
static const WCHAR c_szRemoveSectionSuffix[] = L".Remove";
// We get the remove section name and process all relevant sections
WCHAR szRemoveSection[_MAX_PATH];
DWORD cbBuffer = sizeof (szRemoveSection);
hr = pComponent->HrOpenInstanceKey (KEY_READ,
&hkeyInstance, NULL, NULL);
if(S_OK == hr)
{
hr = HrRegQuerySzBuffer (hkeyInstance, REGSTR_VAL_INFSECTION,
szRemoveSection, &cbBuffer);
if (S_OK == hr)
{
//HrAddOrRemoveWinsockDependancy processes the winsock
//remove section in the given inf file and then calls
//MigrateWinsockConfiguration which will cause the
//necessary PnP notifications to be issued to the
//interested application.
wcscat (szRemoveSection, c_szRemoveSectionSuffix);
hr = HrAddOrRemoveWinsockDependancy (hinf, szRemoveSection);
}
RegSafeCloseKey (hkeyInstance);
}
}
TraceHr (ttidError, FAL, hr, FALSE,
"CModifyContext::HrProcessWinsockRemove (%S)",
pComponent->PszGetPnpIdOrInfId());
return hr;
}
VOID
CModifyContext::ApplyChanges ()
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
CNetConfig* pNetConfig;
CComponentList::const_iterator iter;
CComponent* pComponent;
CFilterDevices FilterDevices (&PNetConfig()->Core);
CPszArray ServiceNames;
CServiceManager ServiceManager;
PCWSTR pszService;
BOOL fRebootNeeded;
BOOL fMigrateWinsock;
BOOL fModifyFilterDevices;
BOOL fSignalNetworkProviderLoaded;
BOOL fUserIsNetConfigOps;
BOOL fCallCoFreeUnusedLibraries;
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (0 == m_ulRecursionDepth);
pNetConfig = PNetConfig();
fMigrateWinsock = FALSE;
fModifyFilterDevices = FALSE;
fSignalNetworkProviderLoaded = FALSE;
fUserIsNetConfigOps = FIsUserNetworkConfigOps();
fCallCoFreeUnusedLibraries = FALSE;
//+-----------------------------------------------------------------------
// Step 0: Prepare m_AddedBindPaths, m_DeletedBindPaths, and
// m_DirtyComponents.
//
// Add the bindpaths that were once disabled, but are now enabled, to
// m_AddedBindPaths. We do this so that PnP notifications are sent for
// them.
//
m_hr = m_AddedBindPaths.HrAddBindPathsInSet1ButNotInSet2 (
&m_CoreStartedWith.DisabledBindings,
&pNetConfig->Core.DisabledBindings);
if (S_OK != m_hr)
{
return;
}
// Add the bindpaths that were once enabled, but are now disabled, to
// m_DeletedBindPaths. We do this so that PnP notifications are sent for
// them.
//
m_hr = m_DeletedBindPaths.HrAddBindPathsInSet1ButNotInSet2 (
&pNetConfig->Core.DisabledBindings,
&m_CoreStartedWith.DisabledBindings);
if (S_OK != m_hr)
{
return;
}
// m_fDirtyComponents should be empty unless we've explicitly dirtied
// one or more. If m_fDirtyComponents were not empty, it would probably
// mean we forgot to clear it after the last Apply or Cancel.
// Conversely, m_DirtyComponents better not be empty if we've explicitly
// dirtied one or more.
//
Assert (FIff(!m_fComponentExplicitlyDirtied, m_DirtyComponents.FIsEmpty()));
// Dirty the affected components (owners and adapters in bindpaths of
// length 2) from the added and deleted bindpaths. We need to write
// bindings for these components.
//
m_hr = m_AddedBindPaths.HrGetAffectedComponentsInBindingSet (
&m_DirtyComponents);
if (S_OK != m_hr)
{
return;
}
m_hr = m_DeletedBindPaths.HrGetAffectedComponentsInBindingSet (
&m_DirtyComponents);
if (S_OK != m_hr)
{
return;
}
// Dirty components that exist in the current core, but not in the core
// we started with. (These are added components).
//
m_hr = m_DirtyComponents.HrAddComponentsInList1ButNotInList2 (
&pNetConfig->Core.Components,
&m_CoreStartedWith.Components);
if (S_OK != m_hr)
{
return;
}
// Dirty components that exist in the core we started with, but not in
// the current core. (These are removed components).
//
m_hr = m_DirtyComponents.HrAddComponentsInList1ButNotInList2 (
&m_CoreStartedWith.Components,
&pNetConfig->Core.Components);
if (S_OK != m_hr)
{
return;
}
g_pDiagCtx->Printf (ttidBeDiag,
"Step 0: The following components are dirty:\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
if (!pNetConfig->Core.Components.FComponentInList (pComponent))
{
g_pDiagCtx->Printf (ttidBeDiag, " %-12S (removed)\n",
pComponent->PszGetPnpIdOrInfId());
}
else if (!m_CoreStartedWith.Components.FComponentInList (pComponent))
{
g_pDiagCtx->Printf (ttidBeDiag, " %-12S (installed)\n",
pComponent->PszGetPnpIdOrInfId());
}
else
{
g_pDiagCtx->Printf (ttidBeDiag, " %S\n",
pComponent->PszGetPnpIdOrInfId());
}
}
// Reserve room for 32 pointers to service names. We use this buffer
// to start and stop services.
//
m_hr = ServiceNames.HrReserveRoomForPointers (32);
if (S_OK != m_hr)
{
return;
}
// See if we are going to modify any filter devices. If we are,
// we'll go through all of the steps of loading filter devices, removing
// any we don't need, installing any new ones, and binding them all up.
// We only modify filter devices if the user is a normal admin and not
// a netcfgop.
//
// This test could be further refined to see if we had any filters which
// were dirty or if we had any dirty adapters which are filtered.
//
if (!fUserIsNetConfigOps)
{
fModifyFilterDevices = pNetConfig->Core.FContainsFilterComponent() ||
m_CoreStartedWith.FContainsFilterComponent();
}
else
{
Assert(!fModifyFilterDevices);
}
if (fModifyFilterDevices)
{
// Allow the filter devices structure to reserve whatever memory it
// may need.
//
m_hr = FilterDevices.HrPrepare ();
if (S_OK != m_hr)
{
return;
}
}
pNetConfig->Core.DisabledBindings.Printf (ttidBeDiag,
" The following bindings are currently disabled:\n");
//+-----------------------------------------------------------------------
// Step 1: Save the network configuration binary.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 1: Save the network configuration binary.\n");
HrSaveNetworkConfigurationToRegistry (pNetConfig);
//+-----------------------------------------------------------------------
// Step 2: Write the static bindings for all changed components.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 2: Write the following static bindings.\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// If any protocols are dirty, we'll want to migrate winsock
// configuration later.
//
if (NC_NETTRANS == pComponent->Class())
{
fMigrateWinsock = TRUE;
}
// If the component is in the core, write its bindings.
// If it is not in the core, it means it has been removed and
// we should therefore remove its bindings.
//
if (pNetConfig->Core.Components.FComponentInList (pComponent))
{
hr = m_RegBindCtx.HrWriteBindingsForComponent (pComponent);
// Remember any errors, but continue.
//
if (S_OK != hr)
{
Assert (FAILED(hr));
m_hr = hr;
}
}
else
{
// Only delete if we're not installing another version of this
// component that has a duplicate PnpId. If we had already
// written the bindings for the newly installed one, and then
// deleted the ones for the removed (yet duplicate PnpId), we'd
// effectivly delete the bindings for the new one too. See the
// comments at step 6 for how we can get into this case.
//
if (!FIsEnumerated (pComponent->Class()) ||
!pNetConfig->Core.Components.PFindComponentByPnpId (
pComponent->m_pszPnpId))
{
// There's no reason to fail if we can't delete the bindings.
// The entire component is about to be tossed anyway.
//
(VOID) m_RegBindCtx.HrDeleteBindingsForComponent (pComponent);
}
}
}
//+-----------------------------------------------------------------------
// Step 3: Notify ApplyRegistryChanges
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 3: Notify: apply registry changes\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
pComponent->Notify.ApplyRegistryChanges (
pNetConfig->Notify.PINetCfg(),
&fRebootNeeded);
if (fRebootNeeded)
{
m_fRebootRecommended = TRUE;
g_pDiagCtx->Printf (ttidBeDiag, " %S notify object wants a reboot\n",
pComponent->m_pszInfId);
}
}
// Migrate Winsock configuration if needed.
// Important to do this after the LANA map is written, afer Notify Applys
// are called, but before any services are started.
//
if (fMigrateWinsock)
{
g_pDiagCtx->Printf (ttidBeDiag, "Migrating winsock configuration.\n");
(VOID) HrMigrateWinsockConfiguration ();
}
//+-----------------------------------------------------------------------
// Step 4: Unbind deleted bindpaths.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 4: Unbind the following deleted bindings:\n");
if (!m_DeletedBindPaths.FIsEmpty())
{
// We don't need to send UNBIND notifications for bindpaths that
// involve adapters that have been removed. They will be unbound
// automatically when the adapter is uninstalled. (For the case
// when the class installer is notifying us of a removed adapter,
// its important NOT to try to send an UNBIND notification because
// the adapter has already been uninstalled (hence unbound) and
// our notification might come back in error causing us to need
// a reboot uneccessary.
//
// So, remove the bindpaths in m_DeletedBindPaths that involve
// adapters that have been removed.
//
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// If its enumerated, and not in the current core, its a
// removed adapter.
//
if (FIsEnumerated (pComponent->Class()) &&
!pNetConfig->Core.Components.FComponentInList (pComponent))
{
m_DeletedBindPaths.RemoveBindPathsWithComponent (pComponent);
}
}
m_DeletedBindPaths.SortForPnpUnbind ();
m_RegBindCtx.PnpBindOrUnbindBindPaths (UNBIND,
&m_DeletedBindPaths,
&fRebootNeeded);
if (fRebootNeeded)
{
m_fRebootRecommended = TRUE;
}
}
//+-----------------------------------------------------------------------
// Step 5: Stop services for removed components.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 5: Stop the following services:\n");
Assert (0 == ServiceNames.Count());
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// Ignore enumerated components because they will have their drivers
// stopped automatically (if appropriate) when they are removed.
// Ignore components that are in the current core (not being removed).
//
if (FIsEnumerated (pComponent->Class()) ||
pNetConfig->Core.Components.FComponentInList (pComponent))
{
continue;
}
// Winsock remove section needs to be processed for every
// component that is being removed in order to update
// Transport key for the winsock registry settings
HrProcessWinsockRemove (pComponent);
// If its a protcol, send an UNLOAD before trying to stop the service.
//
if ((NC_NETTRANS == pComponent->Class()) || (NCF_NDIS_PROTOCOL & pComponent->m_dwCharacter))
{
// Unload can fail as a lot of drivers do not support it.
// Treat it as an 'FYI' indication and don't set the reboot
// flag if it fails.
//
(VOID) HrPnpUnloadDriver (NDIS, pComponent->Ext.PszBindName());
}
// Ignore components that don't have any services.
if (!pComponent->Ext.PszCoServices())
{
continue;
}
for (pszService = pComponent->Ext.PszCoServices();
*pszService;
pszService += wcslen(pszService) + 1)
{
(VOID)ServiceNames.HrAddPointer (pszService);
g_pDiagCtx->Printf (ttidBeDiag, " %S", pszService);
}
g_pDiagCtx->Printf (ttidBeDiag, "\n");
}
if (ServiceNames.Count() > 0)
{
static const CSFLAGS CsStopFlags =
{
FALSE, // FALSE means don't start
SERVICE_CONTROL_STOP, // use this control instead
15000, // wait up to 15 seconds...
SERVICE_STOPPED, // ... for service to reach this state
FALSE, //
};
hr = ServiceManager.HrControlServicesAndWait (
ServiceNames.Count(),
ServiceNames.begin(),
&CsStopFlags);
if (S_OK != hr)
{
m_fRebootRequired = TRUE;
g_pDiagCtx->Printf (ttidBeDiag, " some service failed to stop (hr = 0x%08X)\n",
hr);
// Unfortunately, there is no easy way to get back which service
// did not stop and then to figure out which component contains
// that service. Sooo, we'll just put every component that is
// being removed in lockdown. This isn't a big deal when the UI
// is doing the removal because it only removes things one at a
// time.
//
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
if (FIsEnumerated (pComponent->Class()) ||
!pComponent->Ext.PszCoServices() ||
pNetConfig->Core.Components.FComponentInList (pComponent))
{
continue;
}
LockdownComponentUntilNextReboot (pComponent->m_pszInfId);
}
}
ServiceNames.Clear();
}
//+-----------------------------------------------------------------------
// Step 5a: Uninstall filters first.
//
if (fModifyFilterDevices)
{
g_pDiagCtx->Printf(ttidBeDiag, "Step 5a: Remove filter devices:\n");
// Order is of utmost importance here. Remove must be called first
// because it initializes some state internal to FilterDevices. Start
// must come after Write and Write obviously has to come after all
// new filter devices are installed.
//
FilterDevices.LoadAndRemoveFilterDevicesIfNeeded ();
}
//+-----------------------------------------------------------------------
// Step 6: Uninstall removed components.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 6: Uninstall the following components:\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// If the component is in the core, ignore it.
// If it is not in the core, it means it has been removed and
// we should therefore remove its bindings.
//
if (pNetConfig->Core.Components.FComponentInList (pComponent))
{
continue;
}
// If this is an enumerated component whose PnpId matches that of
// a component in the current core, we've run into a special case.
// This can happen when the external data (like the NetCfgInstanceId)
// is corrupted and the class installer was told to update the
// component. The class installer is really told to "install" the
// component, but if it already exists as determined by the presence
// of NetCfgInstanceId, the class installer translates it to "update".
// Without the key, the class installer thinks its installing
// a new one. We detect the duplicate PnpId and remove the "prior"
// so we can install the "new". This "prior" instance is what we
// are finalizing the remove of, but if we call HrCiRemoveComponent,
// it just removes the same PnpId that the class installer told us
// to install. By not calling HrCiRemoveComponent for this case,
// the "prior" instance key gets reused implicitly by the "new"
// instance.
//
if (FIsEnumerated (pComponent->Class()) &&
pNetConfig->Core.Components.PFindComponentByPnpId (
pComponent->m_pszPnpId))
{
g_pDiagCtx->Printf (ttidBeDiag,
" Skip removal of %S because a duplicate was installed\n",
pComponent->m_pszPnpId);
continue;
}
g_pDiagCtx->Printf (ttidBeDiag,
" %S\n", pComponent->PszGetPnpIdOrInfId());
hr = HrCiRemoveComponent (pComponent, &pComponent->m_strRemoveSection);
// We can ignore SPAPI_E_NO_SUCH_DEVINST because the class installer
// may have already removed it and is just notifying us.
//
if ((S_OK != hr) && (SPAPI_E_NO_SUCH_DEVINST != hr))
{
m_fRebootRequired = TRUE;
g_pDiagCtx->Printf (ttidBeDiag, " ^^^ needs a reboot (hr = 0x%08X)\n",
hr);
}
}
//+-----------------------------------------------------------------------
// Step 6a: Modify filter devices.
//
if (fModifyFilterDevices)
{
g_pDiagCtx->Printf (ttidBeDiag, "Step 6a: Modify filter devices:\n");
FilterDevices.InstallFilterDevicesIfNeeded ();
(VOID)m_RegBindCtx.HrWriteBindingsForFilterDevices (&FilterDevices);
PruneNdisWanBindPathsIfActiveRasConnections (
&FilterDevices.m_BindPathsToRebind,
&fRebootNeeded);
if (fRebootNeeded)
{
m_fRebootRecommended = TRUE;
}
m_RegBindCtx.PnpBindOrUnbindBindPaths (UNBIND,
&FilterDevices.m_BindPathsToRebind,
&fRebootNeeded);
if (fRebootNeeded)
{
m_fRebootRecommended = TRUE;
}
g_pDiagCtx->Printf (ttidBeDiag, "Step 6b: Starting filter devices:\n");
FilterDevices.StartFilterDevices ();
FilterDevices.Free ();
}
//+-----------------------------------------------------------------------
// Step 7: Start services for added components.
//
Assert (0 == ServiceNames.Count());
g_pDiagCtx->Printf (ttidBeDiag,
"Step 7: Start the following drivers/services:\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// If the component is in the core we started with, ignore it.
// If it is not in the core we started with, it means it is newly
// installed we should therefore start its services.
//
if (m_CoreStartedWith.Components.FComponentInList (pComponent))
{
continue;
}
// If we've added a network client, we'll need to signal the
// network provider loaded event after we've started its service.
//
if (NC_NETCLIENT == pComponent->Class())
{
fSignalNetworkProviderLoaded = TRUE;
}
if (FIsEnumerated (pComponent->Class()))
{
g_pDiagCtx->Printf (ttidBeDiag, " %S\n", pComponent->m_pszPnpId);
hr = pComponent->HrStartOrStopEnumeratedComponent (DICS_START);
if (S_OK != hr)
{
m_fRebootRecommended = TRUE;
g_pDiagCtx->Printf (ttidBeDiag, " ^^^ needs a reboot (hr = 0x%08X)\n",
hr);
}
if (FIsPhysicalNetAdapter (pComponent->Class(),
pComponent->m_dwCharacter) && FInSystemSetup())
{
ProcessAdapterAnswerFileIfExists (pComponent);
}
}
else if (pComponent->Ext.PszCoServices())
{
for (pszService = pComponent->Ext.PszCoServices();
*pszService;
pszService += wcslen(pszService) + 1)
{
(VOID)ServiceNames.HrAddPointer (pszService);
g_pDiagCtx->Printf (ttidBeDiag, " %S", pszService);
}
g_pDiagCtx->Printf (ttidBeDiag, "\n");
// If we're in system setup, then exclude whatever services
// the component has marked as such.
//
if (FInSystemSetup())
{
ExcludeMarkedServicesForSetup (pComponent, &ServiceNames);
}
}
}
if ((ServiceNames.Count() > 0) &&
!(g_pDiagCtx->Flags() & DF_DONT_START_SERVICES))
{
static const CSFLAGS CsStartFlags =
{
TRUE, // TRUE means start
0,
20000, // wait up to 20 seconds...
SERVICE_RUNNING, // ... for service to reach this state
TRUE, // ignore demand-start and disabled
};
hr = ServiceManager.HrControlServicesAndWait (
ServiceNames.Count(),
ServiceNames.begin(),
&CsStartFlags);
if (S_OK != hr)
{
m_fRebootRecommended = TRUE;
g_pDiagCtx->Printf (ttidBeDiag, " some service failed to start (hr = 0x%08X)\n",
hr);
}
}
//+-----------------------------------------------------------------------
// Step 8: Bind added bindpaths.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 8: Bind the following added bindings:\n");
if (fModifyFilterDevices)
{
hr = m_AddedBindPaths.HrAppendBindingSet (
&FilterDevices.m_BindPathsToRebind);
if (S_OK != hr)
{
// Well, that's not good, but there is nothing we can do about
// it now. (Most likely we ran out of memory.)
}
}
if (!m_AddedBindPaths.FIsEmpty())
{
CBindPath* pBindPath;
// We don't need to send BIND notifications for bindpaths that
// involve adapters that have been newly installed. They will be
// bound automatically when the adapter is started.
//
// Update to the above comment: We THOUGHT that was correct, but it
// turns out it isn't. TDI isn't PNP (guess they must have missed
// THAT memo) and they are not re-reading the new bind strings
// from the registry when lower notifications bubble up. So, we
// have to send these BINDS for added adapters too.
//
// We should remove bindpaths that involve components that have
// been removed. These can end up in added bindpaths because way
// up in step 0, we added bindpaths that were disabled in the
// core we started with and that are no longer disabled in the
// current core. Well, when the component is removed, its disabled
// bindings are removed, so this case would have caused us to add
// the bindpath to this binding set.
//
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// If its been removed, remove any bindpaths that reference it.
//
if (!pNetConfig->Core.Components.FComponentInList (pComponent))
{
m_AddedBindPaths.RemoveBindPathsWithComponent (pComponent);
}
}
// To prevent TDI from sending duplicate BINDs to its clients, we
// have to do a little more work. We need to not send the TDI
// BINDs to components that are newly installed. This is because
// TDI sent them the BINDs when we started the driver above.
// So, for each added bindpath, if it's going to the TDI layer, and
// the owner (topmost) component of the bindpath is newly installed,
// remove it from the added binding so we won't send a notification
// for it below.
//
pBindPath = m_AddedBindPaths.begin();
while (pBindPath != m_AddedBindPaths.end())
{
UINT unLayer = GetPnpLayerForBindPath (pBindPath);
if ((TDI == unLayer) &&
!m_CoreStartedWith.Components.FComponentInList (
pBindPath->POwner()))
{
m_AddedBindPaths.erase (pBindPath);
}
else
{
pBindPath++;
}
}
m_AddedBindPaths.SortForPnpBind ();
m_RegBindCtx.PnpBindOrUnbindBindPaths (BIND,
&m_AddedBindPaths,
&fRebootNeeded);
if (fRebootNeeded)
{
// If BINDs fail, we should recommend a reboot, but one is
// not required for subsequent installs or removes.
//
m_fRebootRecommended = TRUE;
}
}
// Signal the network provider loaded event if needed.
// Probably best to do this after we've indiciated the PnP bindings
// (above) to the new clients.
//
if (fSignalNetworkProviderLoaded)
{
SignalNetworkProviderLoaded ();
}
//+-----------------------------------------------------------------------
// Step 9: Allow notify objects to apply PnP changes
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 9: Notify: apply PnP changes\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
pComponent->Notify.ApplyPnpChanges (
pNetConfig->Notify.PINetCfg(),
&fRebootNeeded);
if (fRebootNeeded)
{
g_pDiagCtx->Printf (ttidBeDiag,
" %S notify object wants a reboot\n",
pComponent->m_pszInfId);
// If the component has been removed, treat the reboot
// as mandatory. (The reason being that we cannot risk a
// failed re-install.) We put the component into lockdown
// in this situation.
//
if (!pNetConfig->Core.Components.FComponentInList (pComponent))
{
m_fRebootRequired = TRUE;
LockdownComponentUntilNextReboot (pComponent->m_pszInfId);
}
else
{
m_fRebootRecommended = TRUE;
}
}
}
//+-----------------------------------------------------------------------
// Step 10: Release notify objects for removed components and
// process any DelFiles in the remove section of their INF.
//
g_pDiagCtx->Printf (ttidBeDiag,
"Step 10: Release notify objects for removed components:\n");
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// Skip enumerated components (they don't have notify objects), and
// Skip components that were not removed.
// Skip components that don't have their INF open (like unsupported
// components that get removed during GUI setup.)
//
if (FIsEnumerated (pComponent->Class()) ||
pNetConfig->Core.Components.FComponentInList (pComponent) ||
!pComponent->GetCachedInfFile())
{
continue;
}
pComponent->Notify.ReleaseNotifyObject(NULL, FALSE);
fCallCoFreeUnusedLibraries = TRUE;
}
if (fCallCoFreeUnusedLibraries)
{
g_pDiagCtx->Printf (ttidBeDiag,
" calling CoFreeUnusedLibraries before running remove sections\n");
// Now ask COM to unload any DLLs hosting COM objects that are no longer
// in use. (This is a bit heavy-handed as it affects the entire process,
// but there is currently no other way to safely unload the DLLs hosting
// the notify objects of the removed components.)
//
CoFreeUnusedLibrariesEx(0, 0);
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// Skip enumerated components (they don't have notify objects), and
// Skip components that were not removed.
// Skip components that don't have their INF open (like unsupported
// components that get removed during GUI setup.)
//
if (FIsEnumerated (pComponent->Class()) ||
pNetConfig->Core.Components.FComponentInList (pComponent) ||
!pComponent->GetCachedInfFile())
{
continue;
}
g_pDiagCtx->Printf (ttidBeDiag,
" %S [%S]\n", pComponent->PszGetPnpIdOrInfId(),
pComponent->m_strRemoveSection.c_str());
(VOID) HrCiInstallFromInfSection(
pComponent->GetCachedInfFile(),
pComponent->m_strRemoveSection.c_str(),
NULL, NULL, SPINST_FILES);
}
}
/*
//+-----------------------------------------------------------------------
// Step 11: Reconfigure moved bindings
//
// If we changed binding order, Send PnP RECONFIGURE for all dirty
// components that are neither installed nor removed so we
// pickup these order changes.
//
for (iter = m_DirtyComponents.begin();
iter != m_DirtyComponents.end();
iter++)
{
pComponent = *iter;
Assert (pComponent);
// Skip components that have been newly installed or removed.
//
if (!pNetConfig->Core.Components.FComponentInList (pComponent) ||
!m_CoreStartedWith.Components.FComponentInList (pComponent))
{
continue;
}
// Note: send RECONFIGURE
}
*/
}
HRESULT
CModifyContext::HrEnableOrDisableBindPath (
IN DWORD dwChangeFlag,
IN CBindPath* pBindPath,
IN INetCfgBindingPath* pIPath OPTIONAL)
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
CNetConfig* pNetConfig;
UINT CountBefore;
Assert (this);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert ((dwChangeFlag == NCN_ENABLE) || (dwChangeFlag == NCN_DISABLE));
Assert (pBindPath);
hr = S_OK;
pNetConfig = PNetConfig();
// Get the count of bindpaths currently disabled. If it changes
// after we enable/disable this one, we'll inform notify objects
// about it. If the count does not change, it means the state
// of the bindpath has not changed.
//
CountBefore = pNetConfig->Core.DisabledBindings.CountBindPaths();
if (NCN_ENABLE == dwChangeFlag)
{
pNetConfig->Core.EnableBindPath (pBindPath);
Assert (S_OK == hr);
}
else
{
hr = pNetConfig->Core.HrDisableBindPath (pBindPath);
}
if ((S_OK == hr) &&
(pNetConfig->Core.DisabledBindings.CountBindPaths() != CountBefore))
{
// Note: Need to protect against bad notify objects that
// switch the state of a bindpath we are notifying for.
// This could cause an infinite loop. Solve by adding a
// recursion count and bindset to the modify context dedicated
// to bindpath enabling/disabling. When the count is zero,
// we clear the bindingset, add the bindpath we are about to
// notify for, increment the recursion count and call
// NotifyBindPath. When the call returns, we decrement the
// recursion count, remove the bindpath from the binding set,
// and return. Before we call NotifyBindPath when the recursion
// count is non-zero, if the bindpath is already in the
// bindingset, we don't call.
//
pNetConfig->Notify.NotifyBindPath (dwChangeFlag, pBindPath, pIPath);
}
TraceHr (ttidError, FAL, hr, FALSE,
"CModifyContext::HrEnableOrDisableBindPath");
return hr;
}
VOID
CModifyContext::InstallOrRemoveRequiredComponents (
IN CComponent* pComponent,
IN IOR_ACTION Action)
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
HKEY hkeyInstance;
PWSTR pszRequiredList;
const WCHAR szDelims[] = L", ";
Assert (this);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (pComponent);
Assert ((IOR_INSTALL == Action) || (IOR_REMOVE == Action));
pszRequiredList = NULL;
// Open the instance key of the component and read the RequireAll value.
// This value may not exist, which is okay, it means we have nothing
// to do.
//
hr = pComponent->HrOpenInstanceKey (KEY_READ, &hkeyInstance, NULL, NULL);
if (S_OK == hr)
{
HKEY hkeyNdi;
hr = HrRegOpenKeyEx (hkeyInstance, L"Ndi", KEY_READ, &hkeyNdi);
if (S_OK == hr)
{
hr = HrRegQuerySzWithAlloc (hkeyNdi, L"RequiredAll",
&pszRequiredList);
RegCloseKey (hkeyNdi);
}
if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
{
hr = S_OK;
Assert (!pszRequiredList);
}
RegCloseKey (hkeyInstance);
}
// If we have a list of required components, install or remove them.
//
if ((S_OK == hr) && pszRequiredList)
{
CNetConfig* pNetConfig;
INetCfgComponent* pIComp;
pNetConfig = PNetConfig();
hr = pComponent->HrGetINetCfgComponentInterface (
pNetConfig->Notify.PINetCfg(),
&pIComp);
if (S_OK == hr)
{
PCWSTR pszInfId;
PWSTR pszNextInfId;
OBO_TOKEN OboToken;
CComponent* pComponentToRemove;
WCHAR szInfFile [_MAX_PATH];
ZeroMemory (&OboToken, sizeof(OboToken));
OboToken.Type = OBO_COMPONENT;
OboToken.pncc = pIComp;
// For each INF ID in the comma separate list of required
// components...
//
for (pszInfId = GetNextStringToken (pszRequiredList, szDelims, &pszNextInfId);
pszInfId && *pszInfId;
pszInfId = GetNextStringToken (NULL, szDelims, &pszNextInfId))
{
if (IOR_INSTALL == Action)
{
NETWORK_INSTALL_PARAMS nip;
COMPONENT_INSTALL_PARAMS Params;
ZeroMemory (&Params, sizeof(Params));
// Get the Class corresponding to the INF ID.
//
m_hr = HrCiGetClassAndInfFileOfInfId (
pszInfId, &Params.Class, szInfFile);
if (S_OK != m_hr)
{
break;
}
//$REVIEW:Should we stick the filename in the
// COMPONENT_INSTALL_PARAMS so that we don't grovel
// the INF directory to find it again?
// If so, store the filename buffer in the modify
// context so we don't take up stack space or heap space.
// Just need to be sure that we use it to install the
// component before we recurse and overwrite it.
// Pack the network install parameters and call the common
// function.
//
//$REVIEW: we probably need dwSetupFlags and dwUpgradeFromBuildNo
// in the modify context saved when it was called at
// recursion depth 0. Otherwise, things installed here
// during GUI setup will have wrong parameters.
//
nip.dwSetupFlags = 0;
nip.dwUpgradeFromBuildNo = 0;
nip.pszAnswerFile = NULL;
nip.pszAnswerSection = NULL;
// Setup the component install parameters.
//
Params.pszInfId = pszInfId;
Params.pszInfFile = szInfFile;
Params.pOboToken = FIsEnumerated (Params.Class)
? NULL : &OboToken;
Params.pnip = &nip;
//
// THIS MAY CAUSE RECURSION
//
// (just using pComponentToRemove as a placeholder
// for a required parameter.)
//
HrInstallNewOrReferenceExistingComponent (
Params, &pComponentToRemove);
if (S_OK != m_hr)
{
break;
}
}
else
{
Assert (IOR_REMOVE == Action);
// Search for the component to remove using its INF ID.
//
pComponentToRemove = pNetConfig->Core.Components.
PFindComponentByInfId (pszInfId, NULL);
if (pComponentToRemove)
{
//
// THIS MAY CAUSE RECURSION
//
HrRemoveComponentIfNotReferenced (
pComponentToRemove,
FIsEnumerated (pComponentToRemove->Class())
? NULL
: &OboToken,
NULL);
if (S_OK != m_hr)
{
break;
}
}
}
}
ReleaseObj (pIComp);
}
MemFree (pszRequiredList);
}
}
//----------------------------------------------------------------------------
// Update a component. Do this by generating the bindings which involve
// the component and noting them as 'OldBindPaths'. The stack entries which
// involve the component are removed and re-generated and the bindings which
// involve the component are again noted as 'NewBindPaths'. The old bindings
// are compared to the new bindings and the differences are notified to
// notify objects as either being removed or added. For any removed bindings,
// we also remove them from the core's disabled set if they happen to exist
// there too.
//
// Assumptions:
// The INF for pComponent has already been re-run so that the potentially
// new values for UpperRange, LowerRange, etc. are present in the registry.
//
// pComponent has had its external data loaded already.
//
HRESULT
CModifyContext::HrUpdateComponent (
IN CComponent* pComponent,
IN DWORD dwSetupFlags,
IN DWORD dwUpgradeFromBuildNo)
{
TraceFileFunc(ttidNetcfgBase);
HRESULT hr;
CNetConfig* pNetConfig;
CBindingSet OldBindPaths;
CBindingSet NewBindPaths;
CBindPath* pScan;
Assert (this);
Assert (S_OK == m_hr);
Assert (m_fPrepared);
Assert (pComponent);
pNetConfig = PNetConfig();
// Now that we actually are going to modify something, push a new
// recursion depth.
//
PushRecursionDepth();
Assert (S_OK == m_hr);
// Generate the "old" bindings by noting those which involve the
// component.
//
hr = pNetConfig->Core.HrGetBindingsInvolvingComponent (
pComponent,
GBF_ONLY_WHICH_CONTAIN_COMPONENT,
&OldBindPaths);
if (S_OK != hr)
{
goto finished;
}
// Reload the external data so we pickup what the possibly updated
// INF has changed.
//
hr = pComponent->Ext.HrReloadExternalData ();
if (S_OK != hr)
{
goto finished;
}
// Update the stack table entries for the component.
//
hr = pNetConfig->Core.StackTable.HrUpdateEntriesForComponent (
pComponent,
&pNetConfig->Core.Components,
INS_SORTED);
if (S_OK != hr)
{
// This is not good. We ripped out the stack entries and failed
// putting them back in. The stack table now has no entries for
// this component. Prevent this from being applied by setting the
// modify context's HRESULT to the error.
//
m_hr = hr;
goto finished;
}
// Generate the "new" bindings by noting those which involve the
// component.
//
hr = pNetConfig->Core.HrGetBindingsInvolvingComponent (
pComponent,
GBF_ONLY_WHICH_CONTAIN_COMPONENT,
&NewBindPaths);
if (S_OK != hr)
{
// Probably out of memory. Prevent apply by setting the modify
// context's HRESULT to the error.
//
m_hr = hr;
goto finished;
}
// Notify any bindpaths which have been removed.
//
for (pScan = OldBindPaths.begin();
pScan != OldBindPaths.end();
pScan++)
{
if (NewBindPaths.FContainsBindPath (pScan))
{
continue;
}
m_DeletedBindPaths.HrAddBindPath (pScan, INS_IGNORE_IF_DUP | INS_APPEND);
pNetConfig->Core.DisabledBindings.RemoveBindPath (pScan);
pNetConfig->Notify.NotifyBindPath (NCN_REMOVE, pScan, NULL);
}
// Notify any bindpaths which have been added.
//
for (pScan = NewBindPaths.begin();
pScan != NewBindPaths.end();
pScan++)
{
if (OldBindPaths.FContainsBindPath (pScan))
{
continue;
}
m_AddedBindPaths.HrAddBindPath (pScan, INS_IGNORE_IF_DUP | INS_APPEND);
pNetConfig->Notify.NotifyBindPath (NCN_ADD | NCN_ENABLE, pScan, NULL);
}
// Notify that the component has been updated.
//
pNetConfig->Notify.ComponentUpdated (pComponent,
dwSetupFlags,
dwUpgradeFromBuildNo);
finished:
hr = HrPopRecursionDepth();
TraceHr (ttidError, FAL, hr, FALSE,
"CModifyContext::HrUpdateComponent (%S)",
pComponent->PszGetPnpIdOrInfId());
return hr;
}