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.

699 lines
22 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1997.
  5. //
  6. // File: S M L A N . C P P
  7. //
  8. // Contents: The LAN engine that provides statistics to the status monitor
  9. //
  10. // Notes:
  11. //
  12. // Author: CWill 12/02/1997
  13. //
  14. //----------------------------------------------------------------------------
  15. #include "pch.h"
  16. #pragma hdrstop
  17. #include "sminc.h"
  18. #include "smpsh.h"
  19. #include "ndispnp.h"
  20. #include "ntddndis.h"
  21. #include "ncnetcfg.h"
  22. #include "..\\folder\\cfutils.h"
  23. #include "..\\folder\\shutil.h"
  24. #include "naming.h"
  25. //
  26. // External data
  27. //
  28. extern const WCHAR c_szDevice[];
  29. extern const WCHAR c_szSpace[];
  30. extern SM_TOOL_FLAGS g_asmtfMap[];
  31. extern WCHAR c_szCmdLineFlagPrefix[];
  32. const ULONG c_aulConStateMap[] =
  33. {
  34. NCS_DISCONNECTED,
  35. NCS_CONNECTED
  36. };
  37. //+---------------------------------------------------------------------------
  38. //
  39. // Member: CLanStatEngine::CLanStatEngine
  40. //
  41. // Purpose: Creator
  42. //
  43. // Arguments: None
  44. //
  45. // Returns: Nil
  46. //
  47. CLanStatEngine::CLanStatEngine(VOID)
  48. {
  49. m_ncmType = NCM_LAN;
  50. m_ncsmType = NCSM_LAN;
  51. m_dwCharacter = 0;
  52. return;
  53. }
  54. //+---------------------------------------------------------------------------
  55. //
  56. // Member: CLanStatEngine::HrUpdateData
  57. //
  58. // Purpose: Get new statistics from the devices. This data is used to be
  59. // displayed in the UI.
  60. //
  61. // Arguments: pdwChangeFlags - Where to modify the changed flags. This
  62. // param may be NULL.
  63. //
  64. // Returns: Error code
  65. //
  66. HRESULT
  67. CLanStatEngine::HrUpdateData (
  68. DWORD* pdwChangeFlags,
  69. BOOL* pfNoLongerConnected)
  70. {
  71. HRESULT hr = S_OK;
  72. // Initialize the output parameter.
  73. //
  74. *pfNoLongerConnected = FALSE;
  75. UINT uiRet = 0;
  76. NIC_STATISTICS nsNewLanStats = { 0 };
  77. // Prime the structure
  78. //
  79. nsNewLanStats.Size = sizeof(NIC_STATISTICS);
  80. // Retrieve the statistics
  81. //
  82. uiRet = ::NdisQueryStatistics(&m_ustrDevice, &nsNewLanStats);
  83. EnterCriticalSection(&g_csStatmonData);
  84. // Make sure we have a statistics structure
  85. //
  86. if (!m_psmEngineData)
  87. {
  88. m_psmEngineData = new STATMON_ENGINEDATA;
  89. if (m_psmEngineData)
  90. {
  91. ZeroMemory(m_psmEngineData, sizeof(STATMON_ENGINEDATA));
  92. }
  93. }
  94. if (m_psmEngineData)
  95. {
  96. if (uiRet && (MEDIA_STATE_DISCONNECTED != nsNewLanStats.MediaState))
  97. {
  98. AssertSz((c_aulConStateMap[MEDIA_STATE_DISCONNECTED] == NCS_DISCONNECTED)
  99. && (c_aulConStateMap[MEDIA_STATE_CONNECTED] == NCS_CONNECTED),
  100. "Someone is messing around with NETCON_STATUS values");
  101. // Update the change flags if asked for
  102. //
  103. if (pdwChangeFlags)
  104. {
  105. *pdwChangeFlags = SMDCF_NULL;
  106. // Bytes Transmitting
  107. //
  108. if (m_psmEngineData->SMED_PACKETSTRANSMITTING
  109. != nsNewLanStats.PacketsSent)
  110. {
  111. *pdwChangeFlags |= SMDCF_TRANSMITTING;
  112. }
  113. // Bytes Received
  114. //
  115. if (m_psmEngineData->SMED_PACKETSRECEIVING
  116. != nsNewLanStats.DirectedPacketsReceived)
  117. {
  118. *pdwChangeFlags |= SMDCF_RECEIVING;
  119. }
  120. }
  121. // No Compression on LAN devices
  122. //
  123. m_psmEngineData->SMED_COMPRESSIONTRANSMITTING = 0;
  124. m_psmEngineData->SMED_COMPRESSIONRECEIVING = 0;
  125. //
  126. // Update the LAN statistics
  127. //
  128. // LinkSpeed is in 100 bps
  129. //
  130. m_psmEngineData->SMED_SPEEDTRANSMITTING = nsNewLanStats.LinkSpeed * 100;
  131. m_psmEngineData->SMED_SPEEDRECEIVING = nsNewLanStats.LinkSpeed * 100;
  132. Assert((nsNewLanStats.MediaState == MEDIA_STATE_CONNECTED) ||
  133. (nsNewLanStats.MediaState == MEDIA_STATE_DISCONNECTED));
  134. m_psmEngineData->SMED_CONNECTIONSTATUS = c_aulConStateMap[nsNewLanStats.MediaState];
  135. m_psmEngineData->SMED_DURATION = nsNewLanStats.ConnectTime;
  136. m_psmEngineData->SMED_BYTESTRANSMITTING = nsNewLanStats.BytesSent;
  137. m_psmEngineData->SMED_BYTESRECEIVING = nsNewLanStats.DirectedBytesReceived;
  138. m_psmEngineData->SMED_ERRORSTRANSMITTING = nsNewLanStats.PacketsSendErrors;
  139. m_psmEngineData->SMED_ERRORSRECEIVING = nsNewLanStats.PacketsReceiveErrors;
  140. m_psmEngineData->SMED_PACKETSTRANSMITTING = nsNewLanStats.PacketsSent;
  141. m_psmEngineData->SMED_PACKETSRECEIVING = nsNewLanStats.DirectedPacketsReceived;
  142. HrGetAutoNetSetting(m_guidId, &(m_psmEngineData->SMED_DHCP_ADDRESS_TYPE) );
  143. m_psmEngineData->SMED_INFRASTRUCTURE_MODE = IM_NOT_SUPPORTED;
  144. if (IsMediaWireless(NCM_LAN, m_guidId))
  145. {
  146. DWORD dwInfraStructureMode;
  147. DWORD dwInfraStructureModeSize = sizeof(DWORD);
  148. HRESULT hrT = HrQueryNDISAdapterOID(m_guidId,
  149. OID_802_11_INFRASTRUCTURE_MODE,
  150. &dwInfraStructureModeSize,
  151. &dwInfraStructureMode);
  152. if (SUCCEEDED(hrT))
  153. {
  154. switch (dwInfraStructureMode)
  155. {
  156. case Ndis802_11IBSS:
  157. m_psmEngineData->SMED_INFRASTRUCTURE_MODE = IM_NDIS802_11IBSS;
  158. break;
  159. case Ndis802_11Infrastructure:
  160. m_psmEngineData->SMED_INFRASTRUCTURE_MODE = IM_NDIS802_11INFRASTRUCTURE;
  161. break;
  162. case Ndis802_11AutoUnknown:
  163. m_psmEngineData->SMED_INFRASTRUCTURE_MODE = IM_NDIS802_11AUTOUNKNOWN;
  164. break;
  165. default:
  166. m_psmEngineData->SMED_INFRASTRUCTURE_MODE = IM_NOT_SUPPORTED;
  167. }
  168. }
  169. NDIS_802_11_SSID ndisSSID;
  170. DWORD dwndisSSIDSize = sizeof(NDIS_802_11_SSID);
  171. hrT = HrQueryNDISAdapterOID(m_guidId,
  172. OID_802_11_SSID,
  173. &dwndisSSIDSize,
  174. &ndisSSID);
  175. if (SUCCEEDED(hrT))
  176. {
  177. if (ndisSSID.SsidLength > 1)
  178. {
  179. DWORD dwLen = ndisSSID.SsidLength;
  180. if (dwLen > sizeof(ndisSSID.Ssid))
  181. {
  182. dwLen = sizeof(ndisSSID.Ssid);
  183. AssertSz(FALSE, "Unexpected SSID encountered");
  184. }
  185. ndisSSID.Ssid[dwLen] = 0;
  186. mbstowcs(m_psmEngineData->SMED_802_11_SSID, reinterpret_cast<LPSTR>(ndisSSID.Ssid), celems(m_psmEngineData->SMED_802_11_SSID));
  187. }
  188. }
  189. DWORD dwWepStatus;
  190. DWORD dwWepStatusSize = sizeof(DWORD);
  191. hrT = HrQueryNDISAdapterOID(m_guidId,
  192. OID_802_11_WEP_STATUS,
  193. &dwWepStatusSize,
  194. &dwWepStatus);
  195. if (SUCCEEDED(hrT))
  196. {
  197. if (Ndis802_11WEPEnabled == dwWepStatus)
  198. {
  199. m_psmEngineData->SMED_802_11_ENCRYPTION_ENABLED = TRUE;
  200. }
  201. }
  202. LONG lSignalStrength;
  203. DWORD dwSignalStrengthSize = sizeof(DWORD);
  204. hrT = HrQueryNDISAdapterOID(m_guidId,
  205. OID_802_11_RSSI,
  206. &dwSignalStrengthSize,
  207. &lSignalStrength);
  208. if (SUCCEEDED(hrT))
  209. {
  210. m_psmEngineData->SMED_802_11_SIGNAL_STRENGTH = lSignalStrength;
  211. }
  212. }
  213. }
  214. else
  215. {
  216. *pfNoLongerConnected = TRUE;
  217. // set the connection status to "disconnected" so we can close the UI
  218. m_psmEngineData->SMED_CONNECTIONSTATUS = c_aulConStateMap[NCS_DISCONNECTED];
  219. if (!uiRet)
  220. {
  221. TraceTag(ttidStatMon,
  222. "NdisQueryStatistics failed on %S. err=%u. "
  223. "Treating as disconnected.",
  224. m_strDevice.c_str(),
  225. GetLastError ());
  226. }
  227. else
  228. {
  229. TraceTag(ttidStatMon,
  230. "NdisQueryStatistics returned MediaState = MEDIA_STATE_DISCONNECTED on %S.",
  231. m_strDevice.c_str());
  232. }
  233. hr = S_OK;
  234. }
  235. }
  236. else
  237. {
  238. hr = E_OUTOFMEMORY;
  239. }
  240. LeaveCriticalSection(&g_csStatmonData);
  241. TraceError("CLanStatEngine::HrUpdateData", hr);
  242. return hr;
  243. }
  244. //+---------------------------------------------------------------------------
  245. //
  246. // Member: CLanStatEngine::put_Device
  247. //
  248. // Purpose: Sets the device that is associated with this device
  249. //
  250. // Arguments: pstrDevice - The name of the device
  251. //
  252. // Returns: Error code.
  253. //
  254. HRESULT CLanStatEngine::put_Device(tstring* pstrDevice)
  255. {
  256. HRESULT hr = S_OK;
  257. // Set the new device name
  258. if (pstrDevice)
  259. {
  260. CExceptionSafeComObjectLock EsLock(this);
  261. // Remember the name
  262. m_strDevice = *pstrDevice;
  263. // Make sure we have a nice UNICODE string as well
  264. ::RtlInitUnicodeString(&m_ustrDevice, m_strDevice.c_str());
  265. }
  266. else
  267. {
  268. hr = E_POINTER;
  269. }
  270. TraceError("CLanStatEngine::put_Device", hr);
  271. return hr;
  272. }
  273. //+---------------------------------------------------------------------------
  274. //
  275. // Member: CLanStatEngine::put_MediaType
  276. //
  277. // Purpose: Pass media type of LAN connection type to the LAN engine
  278. //
  279. // Arguments: ncmType - NETCON_MEDIATYPE being set
  280. // ncsmType - NETCON_SUBMEDIATYPE being set
  281. //
  282. // Returns:
  283. //
  284. VOID CLanStatEngine::put_MediaType(NETCON_MEDIATYPE ncmType, NETCON_SUBMEDIATYPE ncsmType)
  285. {
  286. m_ncmType = ncmType;
  287. m_ncsmType = ncsmType;
  288. }
  289. //////////////////////////////////////////////////////////////////////////////
  290. // //
  291. // CPspLanGen //
  292. // //
  293. //////////////////////////////////////////////////////////////////////////////
  294. CPspLanGen::CPspLanGen(VOID)
  295. {
  296. m_ncmType = NCM_LAN;
  297. m_ncsmType = NCSM_LAN;
  298. m_dwCharacter =0;
  299. m_adwHelpIDs = NULL;
  300. return;
  301. }
  302. //////////////////////////////////////////////////////////////////////////////
  303. // //
  304. // CPspLanGen //
  305. // //
  306. //////////////////////////////////////////////////////////////////////////////
  307. VOID CPspLanGen::put_MediaType(NETCON_MEDIATYPE ncmType, NETCON_SUBMEDIATYPE ncsmType)
  308. {
  309. m_ncmType = ncmType;
  310. m_ncsmType = ncsmType;
  311. return;
  312. }
  313. //////////////////////////////////////////////////////////////////////////////
  314. // //
  315. // CPspLanTool //
  316. // //
  317. //////////////////////////////////////////////////////////////////////////////
  318. CPspLanTool::CPspLanTool(VOID)
  319. {
  320. m_ncmType = NCM_LAN;
  321. m_ncsmType = NCSM_LAN;
  322. m_dwCharacter = 0;
  323. return;
  324. }
  325. //+---------------------------------------------------------------------------
  326. //
  327. // Member: CPspLanTool::HrInitToolPageType
  328. //
  329. // Purpose: Gets from the connection any information that is relevant to
  330. // this particular connection type.
  331. //
  332. // Arguments: pncInit - The connection assocatied with this dialog
  333. //
  334. // Returns: Error code
  335. //
  336. HRESULT CPspLanTool::HrInitToolPageType(INetConnection* pncInit)
  337. {
  338. HRESULT hr = S_OK;
  339. INetLanConnection* pnlcInit = NULL;
  340. // Get some LAN specific info
  341. //
  342. hr = HrQIAndSetProxyBlanket(pncInit, &pnlcInit);
  343. if (SUCCEEDED(hr))
  344. {
  345. GUID guidDevice = { 0 };
  346. // Find the component's GUID
  347. //
  348. hr = pnlcInit->GetDeviceGuid(&guidDevice);
  349. if (SUCCEEDED(hr))
  350. {
  351. WCHAR achGuid[c_cchGuidWithTerm];
  352. // Make the device name
  353. //
  354. ::StringFromGUID2(guidDevice, achGuid,
  355. c_cchGuidWithTerm);
  356. m_strDeviceName = c_szDevice;
  357. m_strDeviceName.append(achGuid);
  358. }
  359. ::ReleaseObj(pnlcInit);
  360. }
  361. TraceError("CPspLanTool::HrInitToolPageType", hr);
  362. return hr;
  363. }
  364. //+---------------------------------------------------------------------------
  365. //
  366. // Member: CPspLanTool::HrAddCommandLineFlags
  367. //
  368. // Purpose: Adds the flags for this selection to the command line for the
  369. // tool being launched.
  370. //
  371. // Arguments: pstrFlags - The command line that the flags have to be
  372. // appended to
  373. // psmteSel - The tool entry associated with this selection
  374. //
  375. // Returns: Error code
  376. //
  377. HRESULT CPspLanTool::HrAddCommandLineFlags(tstring* pstrFlags,
  378. CStatMonToolEntry* psmteSel)
  379. {
  380. HRESULT hr = S_OK;
  381. DWORD dwFlags = 0x0;
  382. // Same some indirections
  383. //
  384. dwFlags = psmteSel->dwFlags;
  385. //
  386. // Check what flags are asked for and provide them if we can
  387. //
  388. if (SCLF_ADAPTER & dwFlags)
  389. {
  390. pstrFlags->append(c_szCmdLineFlagPrefix);
  391. pstrFlags->append(g_asmtfMap[STFI_ADAPTER].pszFlag);
  392. pstrFlags->append(c_szSpace);
  393. pstrFlags->append(m_strDeviceName);
  394. }
  395. TraceError("CPspStatusMonitorTool::HrAddCommandLineFlags", hr);
  396. return hr;
  397. }
  398. HRESULT CPspLanTool::HrGetDeviceType(INetConnection* pncInit)
  399. {
  400. TraceFileFunc(ttidStatMon);
  401. UINT uiRet = 0;
  402. NIC_STATISTICS nsLanStats = { 0 };
  403. // Set the default type
  404. m_strDeviceType = L"Ethernet";
  405. // Prime the structure
  406. //
  407. nsLanStats.Size = sizeof(NIC_STATISTICS);
  408. // Retrieve the statistics
  409. //
  410. WCHAR szDeviceGuid[c_cchGuidWithTerm];
  411. ::StringFromGUID2(m_guidId, szDeviceGuid, c_cchGuidWithTerm);
  412. tstring strDeviceName = c_szDevice;
  413. strDeviceName.append(szDeviceGuid);
  414. UNICODE_STRING ustrDevice;
  415. ::RtlInitUnicodeString(&ustrDevice, strDeviceName.c_str());
  416. uiRet = ::NdisQueryStatistics(&ustrDevice, &nsLanStats);
  417. if (uiRet)
  418. {
  419. switch(nsLanStats.MediaType)
  420. {
  421. case NdisMedium802_3:
  422. TraceTag(ttidStatMon, "Medium type: NdisMedium802_3 - Ethernet");
  423. m_strDeviceType = L"Ethernet";
  424. break;
  425. case NdisMedium802_5:
  426. TraceTag(ttidStatMon, "Medium type: NdisMedium802_5 - Token Ring");
  427. m_strDeviceType = L"Token ring";
  428. break;
  429. case NdisMediumFddi:
  430. TraceTag(ttidStatMon, "Medium type: NdisMediumFddi - FDDI");
  431. m_strDeviceType = L"FDDI";
  432. break;
  433. case NdisMediumLocalTalk:
  434. TraceTag(ttidStatMon, "Medium type: NdisMediumLocalTalk - Local Talk");
  435. m_strDeviceType = L"Local Talk";
  436. break;
  437. case NdisMediumAtm:
  438. TraceTag(ttidStatMon, "Medium type: NdisMediumAtm - Atm");
  439. m_strDeviceType = L"Atm";
  440. break;
  441. case NdisMediumIrda:
  442. TraceTag(ttidStatMon, "Medium type: NdisMediumIrda - IRDA");
  443. m_strDeviceType = L"IRDA";
  444. break;
  445. case NdisMediumBpc:
  446. TraceTag(ttidStatMon, "Medium type: NdisMediumBpc - BPC");
  447. m_strDeviceType = L"BPC";
  448. break;
  449. case NdisMediumArcnetRaw:
  450. TraceTag(ttidStatMon, "Medium type: NdisMediumArcnetRaw - ArcnetRaw");
  451. m_strDeviceType = L"ArcnetRaw";
  452. break;
  453. case NdisMediumArcnet878_2:
  454. TraceTag(ttidStatMon, "Medium type: NdisMediumArcnet878_2 - MediumArcnet878_2");
  455. m_strDeviceType = L"MediumArcnet878_2";
  456. break;
  457. case NdisMediumWirelessWan:
  458. TraceTag(ttidStatMon, "Medium type: NdisMediumWirelessWan - WirelessWan");
  459. m_strDeviceType = L"WirelessWan";
  460. break;
  461. case NdisMediumWan:
  462. TraceTag(ttidStatMon, "Medium type: NdisMediumWan - Wan");
  463. m_strDeviceType = L"Wan";
  464. break;
  465. case NdisMediumCoWan:
  466. TraceTag(ttidStatMon, "Medium type: NdisMediumCoWan - CoWan");
  467. m_strDeviceType = L"CoWan";
  468. break;
  469. case NdisMediumMax:
  470. TraceTag(ttidStatMon, "Not real medium type ??? NdisMediumMax");
  471. break;
  472. case NdisMediumDix:
  473. TraceTag(ttidStatMon, "Not real medium type ??? NdisMediumDix");
  474. break;
  475. default:
  476. TraceTag(ttidStatMon, "Unknown medium type ??? %d", nsLanStats.MediaType);
  477. break;
  478. }
  479. }
  480. return S_OK;
  481. }
  482. HRESULT CPspLanTool::HrGetComponentList(INetConnection* pncInit)
  483. {
  484. // Get a readonly INetCfg, enumerate components bound to this adapter
  485. HRESULT hr = S_OK;
  486. INetCfg * pNetCfg = NULL;
  487. PWSTR pszClientDesc = NULL;
  488. BOOL fInitCom = TRUE;
  489. BOOL fWriteLock = FALSE;
  490. // Get a read-only INetCfg
  491. hr = HrCreateAndInitializeINetCfg(&fInitCom, &pNetCfg, fWriteLock, 0,
  492. SzLoadIds(IDS_STATMON_CAPTION),
  493. &pszClientDesc);
  494. if (SUCCEEDED(hr))
  495. {
  496. Assert(pNetCfg);
  497. if (pNetCfg)
  498. {
  499. // Get the INetCfgComponent for the adapter in this connection
  500. // ?? Has the GUID been set already ?
  501. INetCfgComponent * pnccAdapter = NULL;
  502. BOOL fFound = FALSE;
  503. CIterNetCfgComponent nccIter(pNetCfg, &GUID_DEVCLASS_NET);
  504. INetCfgComponent* pnccAdapterTemp = NULL;
  505. while (!fFound && SUCCEEDED(hr) &&
  506. (S_OK == (hr = nccIter.HrNext(&pnccAdapterTemp))))
  507. {
  508. GUID guidDev;
  509. hr = pnccAdapterTemp->GetInstanceGuid(&guidDev);
  510. if (S_OK == hr)
  511. {
  512. if (m_guidId == guidDev)
  513. {
  514. fFound = TRUE;
  515. pnccAdapter = pnccAdapterTemp;
  516. AddRefObj(pnccAdapter);
  517. }
  518. }
  519. ReleaseObj (pnccAdapterTemp);
  520. }
  521. // Enumerate the binding paths from the adapter
  522. // and fill in the components list m_lstpstrCompIds
  523. if (pnccAdapter)
  524. {
  525. HRESULT hrTmp;
  526. PWSTR pszCompId;
  527. // Add the adapter to our list
  528. hrTmp = pnccAdapter->GetId(&pszCompId);
  529. if (SUCCEEDED(hrTmp))
  530. {
  531. if (!FIsStringInList(&m_lstpstrCompIds, pszCompId))
  532. {
  533. m_lstpstrCompIds.push_back(new tstring(pszCompId));
  534. }
  535. CoTaskMemFree(pszCompId);
  536. }
  537. // Add other components to our list
  538. CIterNetCfgUpperBindingPath ncbpIter(pnccAdapter);
  539. INetCfgBindingPath * pncbp;
  540. while(SUCCEEDED(hr) && (hr = ncbpIter.HrNext(&pncbp)) == S_OK)
  541. {
  542. // Note: (tongl 9/17/98): should we only consider enabled paths ?
  543. // If we do, how does the tool list refresh when component bindings change ?
  544. // Also, what about component getting added\removed ? Does the tool list
  545. // need to refresh ??
  546. // Enumerate components on the path and add to our list
  547. CIterNetCfgBindingInterface ncbiIter(pncbp);
  548. INetCfgBindingInterface* pncbi;
  549. while(SUCCEEDED(hr) && (hr = ncbiIter.HrNext(&pncbi)) == S_OK)
  550. {
  551. INetCfgComponent * pnccUpper = NULL;
  552. hrTmp = pncbi->GetUpperComponent(&pnccUpper);
  553. if (SUCCEEDED(hrTmp))
  554. {
  555. PWSTR pszCompId;
  556. hrTmp = pnccUpper->GetId(&pszCompId);
  557. if(SUCCEEDED(hrTmp))
  558. {
  559. if (!FIsStringInList(&m_lstpstrCompIds, pszCompId))
  560. {
  561. m_lstpstrCompIds.push_back(new tstring(pszCompId));
  562. }
  563. CoTaskMemFree(pszCompId);
  564. }
  565. ReleaseObj(pnccUpper);
  566. }
  567. ReleaseObj (pncbi);
  568. }
  569. if (hr == S_FALSE) // We just got to the end of the loop
  570. hr = S_OK;
  571. ReleaseObj(pncbp);
  572. }
  573. if (hr == S_FALSE) // We just got to the end of the loop
  574. hr = S_OK;
  575. }
  576. }
  577. // Release the INetCfg
  578. (VOID) HrUninitializeAndReleaseINetCfg(fInitCom, pNetCfg, fWriteLock);
  579. CoTaskMemFree(pszClientDesc);
  580. }
  581. return hr;
  582. }