Leaked source code of windows server 2003
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.

922 lines
30 KiB

  1. #include "shellprv.h"
  2. #include "commctrl.h"
  3. #include "comctrlp.h"
  4. #pragma hdrstop
  5. #include "netview.h"
  6. #include "msprintx.h"
  7. #include "setupapi.h"
  8. #include "ras.h"
  9. #include "ids.h"
  10. // a COM object to enumerate shares and printers in the shell, its acts
  11. // as a monitor for all that is going on.
  12. class CWorkgroupCrawler : public INetCrawler, IPersistPropertyBag
  13. {
  14. public:
  15. CWorkgroupCrawler();
  16. ~CWorkgroupCrawler();
  17. // IUnknown
  18. STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  19. STDMETHOD_(ULONG, AddRef)();
  20. STDMETHOD_(ULONG, Release)();
  21. // INetCrawler
  22. STDMETHOD(Update)(DWORD dwFlags);
  23. // IPersist
  24. STDMETHOD(GetClassID)(CLSID *pclsid)
  25. { *pclsid = CLSID_WorkgroupNetCrawler; return S_OK; }
  26. // IPersistPropertyBag
  27. STDMETHOD(InitNew)()
  28. { return S_OK; }
  29. STDMETHOD(Load)(IPropertyBag *ppb, IErrorLog *pel)
  30. { IUnknown_Set((IUnknown**)&_ppb, ppb); return S_OK; }
  31. STDMETHOD(Save)(IPropertyBag *ppb, BOOL fClearDirty, BOOL fSaveAll)
  32. { return S_OK; }
  33. private:
  34. HRESULT _GetMRUs();
  35. void _AgeOutShares(BOOL fDeleteAll);
  36. HRESULT _CreateShortcutToShare(LPCTSTR pszRemoteName);
  37. HRESULT _InstallPrinter(LPCTSTR pszRemoteName);
  38. BOOL _KeepGoing(int *pcMachines, int *pcShares, int *pcPrinters);
  39. void _EnumResources(LPNETRESOURCE pnr, int *pcMachines, HDPA hdaShares, HDPA hdaPrinters);
  40. HANDLE _AddPrinterConnectionNoUI(LPCWSTR pszRemoteName, BOOL *pfInstalled);
  41. static int CALLBACK _DiscardCB(void *pvItem, void *pv);
  42. static int CALLBACK _InstallSharesCB(void *pvItem, void *pv);
  43. static int CALLBACK _InstallPrinterCB(void *pvItem, void *pv);
  44. LONG _cRef; // reference count for the object
  45. HANDLE _hPrinters; // MRU for printers
  46. HKEY _hShares; // registry key for the printer shares
  47. HINSTANCE _hPrintUI; // instance handle for printui.dll
  48. IPropertyBag *_ppb; // property bag object for state
  49. };
  50. // constants for the MRU's and buffers
  51. #define WORKGROUP_PATH \
  52. REGSTR_PATH_EXPLORER TEXT("\\WorkgroupCrawler")
  53. #define PRINTER_SUBKEY \
  54. (WORKGROUP_PATH TEXT("\\Printers"))
  55. #define SHARE_SUBKEY \
  56. (WORKGROUP_PATH TEXT("\\Shares"))
  57. #define LAST_VISITED TEXT("DateLastVisited")
  58. #define SHORTCUT_NAME TEXT("Filename")
  59. #define MAX_MACHINES 32
  60. #define MAX_PRINTERS 10
  61. #define MAX_SHARES 10
  62. #define CB_WNET_BUFFER (8*1024)
  63. typedef HANDLE (* ADDPRINTCONNECTIONNOUI)(LPCWSTR, BOOL *);
  64. // construction and IUnknown
  65. CWorkgroupCrawler::CWorkgroupCrawler() :
  66. _cRef(1)
  67. {
  68. }
  69. CWorkgroupCrawler::~CWorkgroupCrawler()
  70. {
  71. if (_hPrinters)
  72. FreeMRUList(_hPrinters);
  73. if (_hShares)
  74. RegCloseKey(_hShares);
  75. if (_hPrintUI)
  76. FreeLibrary(_hPrintUI);
  77. if (_ppb)
  78. _ppb->Release();
  79. }
  80. STDMETHODIMP CWorkgroupCrawler::QueryInterface(REFIID riid, void **ppv)
  81. {
  82. static const QITAB qit[] =
  83. {
  84. QITABENT(CWorkgroupCrawler, INetCrawler), // IID_INetCrawler
  85. QITABENT(CWorkgroupCrawler, IPersist), // IID_IPersist
  86. QITABENT(CWorkgroupCrawler, IPersistPropertyBag), // IID_IPersistPropertyBag
  87. { 0 },
  88. };
  89. return QISearch(this, qit, riid, ppv);
  90. }
  91. STDMETHODIMP_(ULONG) CWorkgroupCrawler::AddRef()
  92. {
  93. return InterlockedIncrement(&_cRef);
  94. }
  95. STDMETHODIMP_(ULONG) CWorkgroupCrawler::Release()
  96. {
  97. ASSERT( 0 != _cRef );
  98. ULONG cRef = InterlockedDecrement(&_cRef);
  99. if ( 0 == cRef )
  100. {
  101. delete this;
  102. }
  103. return cRef;
  104. }
  105. STDAPI CWorkgroupCrawler_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppv)
  106. {
  107. CWorkgroupCrawler *pwgc = new CWorkgroupCrawler();
  108. if (!pwgc)
  109. return E_OUTOFMEMORY;
  110. HRESULT hr = pwgc->QueryInterface(riid, ppv);
  111. pwgc->Release();
  112. return hr;
  113. }
  114. // lets open the keys for the objects we are about to install
  115. HRESULT CWorkgroupCrawler::_GetMRUs()
  116. {
  117. // get the printers MRU if we need to allocate one
  118. if (!_hPrinters)
  119. {
  120. MRUINFO mi = { 0 };
  121. mi.cbSize = sizeof(mi);
  122. mi.hKey = HKEY_CURRENT_USER;
  123. mi.uMax = (MAX_PRINTERS * MAX_MACHINES);
  124. mi.lpszSubKey = PRINTER_SUBKEY;
  125. _hPrinters = CreateMRUList(&mi);
  126. if (!_hPrinters)
  127. return E_OUTOFMEMORY;
  128. }
  129. if (!_hShares)
  130. {
  131. DWORD dwres = RegCreateKeyEx(HKEY_CURRENT_USER, SHARE_SUBKEY,
  132. 0,
  133. TEXT(""),
  134. 0,
  135. MAXIMUM_ALLOWED,
  136. NULL,
  137. &_hShares,
  138. NULL);
  139. if (WN_SUCCESS != dwres)
  140. {
  141. return E_FAIL;
  142. }
  143. }
  144. return S_OK; // success
  145. }
  146. // lets create a folder shortcut to the object
  147. HRESULT CWorkgroupCrawler::_CreateShortcutToShare(LPCTSTR pszRemoteName)
  148. {
  149. HRESULT hr = S_OK;
  150. TCHAR szTemp[MAX_PATH];
  151. BOOL fCreateLink = FALSE;
  152. HKEY hk = NULL;
  153. // the share information is stored in the registry as follows:
  154. //
  155. // Shares
  156. // Remote Name
  157. // value: shortcut name
  158. // value: last seen
  159. //
  160. // as we add each share we update the information stored in this list in
  161. // the registry. for each entry we have the shortcut name (so we can remove it)
  162. // and the time and date we last visited the share.
  163. // determine if we need to recreate the object?
  164. StrCpyN(szTemp, pszRemoteName+2, ARRAYSIZE(szTemp));
  165. LPTSTR pszTemp = StrChr(szTemp, TEXT('\\'));
  166. if (pszTemp)
  167. {
  168. *pszTemp = TEXT('/'); // convert the \\...\... to .../...
  169. DWORD dwres = RegOpenKeyEx(_hShares, szTemp, 0, MAXIMUM_ALLOWED, &hk);
  170. if (WN_SUCCESS != dwres)
  171. {
  172. fCreateLink = TRUE;
  173. dwres = RegCreateKeyEx(_hShares, szTemp, 0, TEXT(""), 0, MAXIMUM_ALLOWED, NULL, &hk, NULL);
  174. }
  175. if (WN_SUCCESS == dwres)
  176. {
  177. // if we haven't already seen the link (eg. the key didn't exist in the registry
  178. // then lets create it now.
  179. if (fCreateLink)
  180. {
  181. // NOTE: we must use SHCoCreateInstance() here because we are being called from a thread
  182. // that intentionally did not initialize COM (see comment in Update())
  183. IShellLink *psl;
  184. hr = SHCoCreateInstance(NULL, &CLSID_FolderShortcut, NULL, IID_PPV_ARG(IShellLink, &psl));
  185. if (SUCCEEDED(hr))
  186. {
  187. psl->SetPath(pszRemoteName); // sotore the remote name, its kinda important
  188. // get a description for the link, this comes either from the desktop.ini or the
  189. // is a pretty version of teh remote name.
  190. if (GetShellClassInfo(pszRemoteName, TEXT("InfoTip"), szTemp, ARRAYSIZE(szTemp)))
  191. {
  192. psl->SetDescription(szTemp);
  193. }
  194. else
  195. {
  196. StrCpyN(szTemp, pszRemoteName, ARRAYSIZE(szTemp));
  197. PathMakePretty(szTemp);
  198. psl->SetDescription(szTemp);
  199. }
  200. // some links (shared documents) can specify a shortcut name, if this is specified
  201. // then use it, otherwise get a filename from the nethood folder (eg. foo on bah).
  202. //
  203. // we musst also record the name we save the shortcut as, this used when we
  204. // age out the links from the hood folder.
  205. if (!GetShellClassInfo(pszRemoteName, TEXT("NetShareDisplayName"), szTemp, ARRAYSIZE(szTemp)))
  206. {
  207. LPITEMIDLIST pidl;
  208. hr = SHILCreateFromPath(pszRemoteName, &pidl, NULL);
  209. if (SUCCEEDED(hr))
  210. {
  211. hr = SHGetNameAndFlags(pidl, SHGDN_NORMAL, szTemp, ARRAYSIZE(szTemp), NULL);
  212. ILFree(pidl);
  213. }
  214. }
  215. else
  216. {
  217. hr = S_OK;
  218. }
  219. // should we find a unique name (daviddv)
  220. if (SUCCEEDED(hr))
  221. {
  222. if (NO_ERROR == SHSetValue(hk, NULL, SHORTCUT_NAME, REG_SZ, szTemp, lstrlen(szTemp)*sizeof(TCHAR)))
  223. {
  224. hr = SaveShortcutInFolder(CSIDL_NETHOOD, szTemp, psl);
  225. }
  226. else
  227. {
  228. hr = E_FAIL;
  229. }
  230. }
  231. psl->Release();
  232. }
  233. }
  234. // lets update the time we last saw the link into the registry - this is used for the clean up
  235. // pass we will perform.
  236. if (SUCCEEDED(hr))
  237. {
  238. FILETIME ft;
  239. GetSystemTimeAsFileTime(&ft);
  240. dwres = SHSetValue(hk, NULL, LAST_VISITED, REG_BINARY, (void*)&ft, sizeof(ft));
  241. hr = (NO_ERROR != dwres) ? E_FAIL:S_OK;
  242. }
  243. }
  244. if (hk)
  245. RegCloseKey(hk);
  246. }
  247. else
  248. {
  249. hr = E_UNEXPECTED;
  250. }
  251. return hr;
  252. }
  253. // walk the list of shares stored in the registry to determine which ones should be
  254. // removed from the file system and the list. all files older than 7 days need to
  255. // be removed.
  256. #define FILETIME_SECOND_OFFSET (LONGLONG)((1 * 10 * 1000 * (LONGLONG)1000))
  257. void CWorkgroupCrawler::_AgeOutShares(BOOL fDeleteAll)
  258. {
  259. FILETIME ft;
  260. ULARGE_INTEGER ulTime;
  261. DWORD index = 0;
  262. TCHAR szFilesToDelete[1024];
  263. int cchFilesToDelete = 0;
  264. GetSystemTimeAsFileTime(&ft);
  265. ulTime = *((ULARGE_INTEGER*)&ft);
  266. ulTime.QuadPart -= FILETIME_SECOND_OFFSET*((60*60*24)*2);
  267. SHQueryInfoKey(_hShares, &index, NULL, NULL, NULL); // retrieve the count of the keys
  268. while (((LONG)(--index)) >= 0)
  269. {
  270. TCHAR szKey[MAX_PATH];
  271. DWORD cb = ARRAYSIZE(szKey);
  272. BOOL fRemoveKey = FALSE;
  273. if (WN_SUCCESS == SHEnumKeyEx(_hShares, index, szKey, &cb))
  274. {
  275. // we enumerated a key name, so lets open it so we can look around inside.
  276. HKEY hk;
  277. if (WN_SUCCESS == RegOpenKeyEx(_hShares, szKey, 0, MAXIMUM_ALLOWED, &hk))
  278. {
  279. ULARGE_INTEGER ulLastSeen;
  280. // when did we last crawl to this object, if it was less than the time we
  281. // have for our threshold, then we go through the process of cleaning up the
  282. // object.
  283. cb = sizeof(ulLastSeen);
  284. if (ERROR_SUCCESS == SHGetValue(hk, NULL, LAST_VISITED, NULL, (void*)&ulLastSeen, &cb))
  285. {
  286. if (fDeleteAll || (ulLastSeen.QuadPart <= ulTime.QuadPart))
  287. {
  288. TCHAR szName[MAX_PATH];
  289. cb = ARRAYSIZE(szName)*sizeof(TCHAR);
  290. if (ERROR_SUCCESS == SHGetValue(hk, NULL, SHORTCUT_NAME, NULL, &szName, &cb))
  291. {
  292. TCHAR szPath[MAX_PATH];
  293. // compose the path to the object we want to delete. if the buffer
  294. // is full (eg. this item would over run the size) then flush the
  295. // buffer.
  296. SHGetFolderPath(NULL, CSIDL_NETHOOD|CSIDL_FLAG_CREATE, NULL, 0, szPath);
  297. PathAppend(szPath, szName);
  298. if ((lstrlen(szPath)+cchFilesToDelete) >= ARRAYSIZE(szFilesToDelete))
  299. {
  300. SHFILEOPSTRUCT shfo = { NULL, FO_DELETE, szFilesToDelete, NULL,
  301. FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI, FALSE, NULL, NULL };
  302. szFilesToDelete[cchFilesToDelete] = 0; // double terminate
  303. SHFileOperation(&shfo);
  304. cchFilesToDelete = 0;
  305. }
  306. // add this name to the buffer
  307. StrCpyN(&szFilesToDelete[cchFilesToDelete], szPath, ARRAYSIZE(szFilesToDelete)-cchFilesToDelete);
  308. cchFilesToDelete += lstrlen(szPath)+1;
  309. }
  310. fRemoveKey = TRUE;
  311. }
  312. }
  313. RegCloseKey(hk);
  314. // we can only close the key once it has been closed
  315. if (fRemoveKey)
  316. SHDeleteKey(_hShares, szKey);
  317. }
  318. }
  319. }
  320. // are there any trailing files in the buffer? if so then lets nuke them also
  321. if (cchFilesToDelete)
  322. {
  323. SHFILEOPSTRUCT shfo = { NULL, FO_DELETE, szFilesToDelete, NULL,
  324. FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI, FALSE, NULL, NULL };
  325. szFilesToDelete[cchFilesToDelete] = 0; // double terminate
  326. SHFileOperation(&shfo);
  327. }
  328. }
  329. // silently install printers we have discovered. we have the remote name of the
  330. // printer share, so we then call printui to perform the printer installation
  331. // which it does without UI (hopefully).
  332. HANDLE CWorkgroupCrawler::_AddPrinterConnectionNoUI(LPCWSTR pszRemoteName, BOOL *pfInstalled)
  333. {
  334. HANDLE hResult = NULL;
  335. if (!_hPrintUI)
  336. _hPrintUI = LoadLibrary(TEXT("printui.dll"));
  337. if (_hPrintUI)
  338. {
  339. ADDPRINTCONNECTIONNOUI apc = (ADDPRINTCONNECTIONNOUI)GetProcAddress(_hPrintUI, (LPCSTR)200);
  340. if (apc)
  341. {
  342. hResult = apc(pszRemoteName, pfInstalled);
  343. }
  344. }
  345. return hResult;
  346. }
  347. HRESULT CWorkgroupCrawler::_InstallPrinter(LPCTSTR pszRemoteName)
  348. {
  349. if (-1 == FindMRUString(_hPrinters, pszRemoteName, NULL))
  350. {
  351. BOOL fInstalled;
  352. HANDLE hPrinter = _AddPrinterConnectionNoUI(pszRemoteName, &fInstalled);
  353. if (hPrinter)
  354. {
  355. ClosePrinter(hPrinter);
  356. hPrinter = NULL;
  357. }
  358. }
  359. AddMRUString(_hPrinters, pszRemoteName); // promote back to the top of the list
  360. return S_OK;
  361. }
  362. // check the counters, if we have max'd out then lets stop enumerating
  363. BOOL CWorkgroupCrawler::_KeepGoing(int *pcMachines, int *pcShares, int *pcPrinters)
  364. {
  365. if (pcMachines && (*pcMachines > MAX_MACHINES))
  366. return FALSE;
  367. if (pcShares && (*pcShares > MAX_SHARES))
  368. return FALSE;
  369. if (pcPrinters && (*pcPrinters > MAX_PRINTERS))
  370. return FALSE;
  371. return TRUE;
  372. }
  373. void CWorkgroupCrawler::_EnumResources(LPNETRESOURCE pnr, int *pcMachines, HDPA hdaShares, HDPA hdaPrinters)
  374. {
  375. HANDLE hEnum = NULL;
  376. int cPrinters = 0;
  377. int cShares = 0;
  378. DWORD dwScope = RESOURCE_GLOBALNET;
  379. // if no net resource structure passed then lets enumerate the workgroup
  380. // (this is used for debugging)
  381. NETRESOURCE nr = { 0 };
  382. if (!pnr)
  383. {
  384. pnr = &nr;
  385. dwScope = RESOURCE_CONTEXT;
  386. nr.dwType = RESOURCETYPE_ANY;
  387. nr.dwUsage = RESOURCEUSAGE_CONTAINER;
  388. }
  389. // open the enumerator
  390. DWORD dwres = WNetOpenEnum(dwScope, RESOURCETYPE_ANY, 0, pnr, &hEnum);
  391. if (NO_ERROR == dwres)
  392. {
  393. NETRESOURCE *pnrBuffer = (NETRESOURCE*)SHAlloc(CB_WNET_BUFFER); // avoid putting the buffer on the stack
  394. if (pnrBuffer)
  395. {
  396. while ((WN_SUCCESS == dwres) || (dwres == ERROR_MORE_DATA) && _KeepGoing(pcMachines, &cShares, &cPrinters))
  397. {
  398. DWORD cbEnumBuffer= CB_WNET_BUFFER;
  399. DWORD dwCount = -1;
  400. // enumerate the resources for this enum context and then lets
  401. // determine the objects which we should see.
  402. dwres = WNetEnumResource(hEnum, &dwCount, pnrBuffer, &cbEnumBuffer);
  403. if ((WN_SUCCESS == dwres) || (dwres == ERROR_MORE_DATA))
  404. {
  405. DWORD index;
  406. for (index = 0 ; (index != dwCount) && _KeepGoing(pcMachines, &cShares, &cPrinters) ; index++)
  407. {
  408. LPNETRESOURCE pnr = &pnrBuffer[index];
  409. LPTSTR pszRemoteName = pnr->lpRemoteName;
  410. switch (pnr->dwDisplayType)
  411. {
  412. case RESOURCEDISPLAYTYPE_ROOT: // ignore the entire network object
  413. default:
  414. break;
  415. case RESOURCEDISPLAYTYPE_NETWORK:
  416. {
  417. // ensure that we only crawl the local network providers (eg. Windows Networking)
  418. // crawling DAV, TSCLIENT etc can cause all sorts of random pop ups.
  419. DWORD dwType, cbProviderType = sizeof(dwType);
  420. if (WN_SUCCESS == WNetGetProviderType(pnr->lpProvider, &dwType))
  421. {
  422. if (dwType == WNNC_NET_LANMAN)
  423. {
  424. _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters);
  425. }
  426. }
  427. break;
  428. }
  429. case RESOURCEDISPLAYTYPE_DOMAIN:
  430. _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters);
  431. break;
  432. case RESOURCEDISPLAYTYPE_SERVER:
  433. {
  434. *pcMachines += 1; // another machine found
  435. if (!PathIsSlow(pszRemoteName, -1))
  436. {
  437. SHCacheComputerDescription(pszRemoteName, pnr->lpComment);
  438. _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters);
  439. }
  440. break;
  441. }
  442. case RESOURCEDISPLAYTYPE_SHARE:
  443. {
  444. HDPA hdpa = NULL;
  445. switch (pnr->dwType)
  446. {
  447. case RESOURCETYPE_PRINT:
  448. cPrinters++;
  449. hdpa = hdaPrinters;
  450. break;
  451. case RESOURCETYPE_DISK:
  452. cShares++;
  453. hdpa = hdaShares;
  454. break;
  455. default:
  456. break;
  457. }
  458. if (hdpa)
  459. {
  460. LPTSTR pszName = StrDup(pszRemoteName);
  461. if (pszName)
  462. {
  463. if (-1 == DPA_AppendPtr(hdpa, pszName))
  464. {
  465. LocalFree(pszName);
  466. }
  467. }
  468. }
  469. break;
  470. }
  471. }
  472. }
  473. }
  474. }
  475. SHFree(pnrBuffer);
  476. }
  477. WNetCloseEnum(hEnum);
  478. }
  479. }
  480. // handle the clean up of the DPA's, either we are installing or
  481. // we are releasing objects.
  482. int CALLBACK CWorkgroupCrawler::_DiscardCB(void *pvItem, void *pv)
  483. {
  484. LPTSTR pszRemoteName = (LPTSTR)pvItem;
  485. LocalFree(pszRemoteName);
  486. return 1;
  487. }
  488. int CALLBACK CWorkgroupCrawler::_InstallPrinterCB(void *pvItem, void *pv)
  489. {
  490. CWorkgroupCrawler* pnc = (CWorkgroupCrawler*)pv;
  491. if (pnc)
  492. {
  493. LPTSTR pszRemoteName = (LPTSTR)pvItem;
  494. pnc->_InstallPrinter(pszRemoteName);
  495. }
  496. return _DiscardCB(pvItem, pv);
  497. }
  498. int CALLBACK CWorkgroupCrawler::_InstallSharesCB(void *pvItem, void *pv)
  499. {
  500. CWorkgroupCrawler* pnc = (CWorkgroupCrawler*)pv;
  501. if (pnc)
  502. {
  503. LPTSTR pszRemoteName = (LPTSTR)pvItem;
  504. pnc->_CreateShortcutToShare(pszRemoteName);
  505. }
  506. return _DiscardCB(pvItem, pv);
  507. }
  508. HRESULT CWorkgroupCrawler::Update(DWORD dwFlags)
  509. {
  510. // don't crawl if we are logged in on a TS client, this will discover the shares and
  511. // printers local to the terminal server machine, rather than the ones local to the
  512. // users login domain - badness.
  513. if (SHGetMachineInfo(GMI_TSCLIENT))
  514. return S_OK;
  515. // by default we will only crawl if there isn't a RAS connection, therefore lets
  516. // check the status using RasEnumConnections.
  517. RASCONN rc = { 0 };
  518. DWORD cbConnections = sizeof(rc);
  519. DWORD cConnections = 0;
  520. rc.dwSize = sizeof(rc);
  521. if (!RasEnumConnections(&rc, &cbConnections, &cConnections) && cConnections)
  522. return S_OK;
  523. // check to see if we are in a domain or not, if we are then we shouldn't crawl. however
  524. // we do a provide a "WorkgroupOnly" policy which overrides this behaviour. setting
  525. // this causes us to skip the check, and perform a CONTEXT ENUM below...
  526. BOOL fWorkgroupOnly = (_ppb ? SHPropertyBag_ReadBOOLDefRet(_ppb, L"WorkgroupOnly", FALSE):FALSE);
  527. if (IsOS(OS_DOMAINMEMBER) && !fWorkgroupOnly)
  528. return S_OK;
  529. // populate the DPAs with shares and printer objects we find on the network, to
  530. // do this enumeration lets fake up a NETRESOURCE structure for entire network
  531. int cMachines = 0;
  532. HDPA hdaShares = DPA_Create(MAX_SHARES);
  533. HDPA hdaPrinters = DPA_Create(MAX_PRINTERS);
  534. if (hdaShares && hdaPrinters)
  535. {
  536. NETRESOURCE nr = { 0 };
  537. nr.dwDisplayType = RESOURCEDISPLAYTYPE_ROOT;
  538. nr.dwType = RESOURCETYPE_ANY;
  539. nr.dwUsage = RESOURCEUSAGE_CONTAINER;
  540. _EnumResources(fWorkgroupOnly ? NULL:&nr, &cMachines, hdaShares, hdaPrinters);
  541. }
  542. // now attempt to make connections to the shares and printers. to do this
  543. // we need to look at the number of machines we have visited, if its less
  544. // than our threshold then we can install.
  545. if (SUCCEEDED(_GetMRUs()) && (cMachines < MAX_MACHINES))
  546. {
  547. DPA_DestroyCallback(hdaShares, _InstallSharesCB, this);
  548. DPA_DestroyCallback(hdaPrinters, _InstallPrinterCB, this);
  549. _AgeOutShares(FALSE);
  550. }
  551. else
  552. {
  553. DPA_DestroyCallback(hdaShares, _DiscardCB, this);
  554. DPA_DestroyCallback(hdaPrinters, _DiscardCB, this);
  555. }
  556. return S_OK;
  557. }
  558. // this is the main crawler object, from this we create the protocol specific
  559. // crawlers which handle enumerating the resources for the various network types.
  560. #define CRAWLER_SUBKEY \
  561. TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\NetworkCrawler\\Objects")
  562. class CNetCrawler : public INetCrawler
  563. {
  564. public:
  565. CNetCrawler();
  566. // IUnknown
  567. STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  568. STDMETHOD_(ULONG, AddRef)();
  569. STDMETHOD_(ULONG, Release)();
  570. // INetCrawler
  571. STDMETHOD(Update)(DWORD dwFlags);
  572. private:
  573. static DWORD CALLBACK s_DoCrawl(void* pv);
  574. DWORD _DoCrawl();
  575. LONG _cRef;
  576. LONG _cUpdateLock; // > 0 then we are already spinning
  577. DWORD _dwFlags; // flags from update - passed to each of the crawler sub-objects
  578. };
  579. CNetCrawler* g_pnc = NULL; // there is a single instance of this object
  580. // construction / IUnknown
  581. CNetCrawler::CNetCrawler() :
  582. _cRef(1)
  583. {
  584. }
  585. STDMETHODIMP CNetCrawler::QueryInterface(REFIID riid, void **ppv)
  586. {
  587. static const QITAB qit[] =
  588. {
  589. QITABENT(CNetCrawler, INetCrawler),
  590. { 0 },
  591. };
  592. return QISearch(this, qit, riid, ppv);
  593. }
  594. STDMETHODIMP_(ULONG) CNetCrawler::AddRef()
  595. {
  596. return InterlockedIncrement(&_cRef);
  597. }
  598. STDMETHODIMP_(ULONG) CNetCrawler::Release()
  599. {
  600. ASSERT( 0 != _cRef );
  601. ULONG cRef = InterlockedDecrement(&_cRef);
  602. if ( 0 == cRef )
  603. {
  604. ENTERCRITICAL;
  605. g_pnc = NULL;
  606. LEAVECRITICAL;
  607. delete this;
  608. }
  609. return cRef;
  610. }
  611. // there is a single instance of the object, therefore in a critical section
  612. // lets check to see if the global exists, if so then QI it, otherwise
  613. // create a new one and QI that instead.
  614. STDAPI CNetCrawler_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppv)
  615. {
  616. HRESULT hr = E_OUTOFMEMORY;
  617. ENTERCRITICAL;
  618. if (g_pnc)
  619. {
  620. hr = g_pnc->QueryInterface(riid, ppv);
  621. }
  622. else
  623. {
  624. g_pnc = new CNetCrawler();
  625. if (g_pnc)
  626. {
  627. hr = g_pnc->QueryInterface(riid, ppv);
  628. g_pnc->Release();
  629. }
  630. }
  631. LEAVECRITICAL;
  632. return hr;
  633. }
  634. // this object has a execution count to inidicate if we are already crawling.
  635. // only if the count is 0 when the ::Update method is called, will create a thread
  636. // which inturn will create each of the crawler objects and allow them to
  637. // do their enumeration.
  638. DWORD CALLBACK CNetCrawler::s_DoCrawl(void* pv)
  639. {
  640. CNetCrawler *pnc = (CNetCrawler*)pv;
  641. return pnc->_DoCrawl();
  642. }
  643. DWORD CNetCrawler::_DoCrawl()
  644. {
  645. // enumrate all the keys under the crawler sub-key, from that we can then
  646. // create the individual crawler objects.
  647. HKEY hk;
  648. DWORD dwres = RegOpenKeyEx(HKEY_LOCAL_MACHINE, CRAWLER_SUBKEY, 0, KEY_READ, &hk);
  649. if (WN_SUCCESS == dwres)
  650. {
  651. DWORD index = 0;
  652. SHQueryInfoKey(hk, &index, NULL, NULL, NULL); // retrieve the count of the keys
  653. while (((LONG)(--index)) >= 0)
  654. {
  655. TCHAR szKey[MAX_PATH];
  656. DWORD cb = ARRAYSIZE(szKey);
  657. if (WN_SUCCESS == SHEnumKeyEx(hk, index, szKey, &cb))
  658. {
  659. // given the keyname, create a property bag so we can access
  660. IPropertyBag *ppb;
  661. HRESULT hr = SHCreatePropertyBagOnRegKey(hk, szKey, STGM_READ, IID_PPV_ARG(IPropertyBag, &ppb));
  662. if (SUCCEEDED(hr))
  663. {
  664. // we have a property bag mapped to the registry for the items we need
  665. // to read back, so lets get the CLSID and create a crawler from it.
  666. CLSID clsid;
  667. hr = SHPropertyBag_ReadGUID(ppb, L"CLSID", &clsid);
  668. if (SUCCEEDED(hr))
  669. {
  670. INetCrawler *pnc;
  671. hr = SHCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(INetCrawler, &pnc));
  672. if (SUCCEEDED(hr))
  673. {
  674. // if the crawler supports IPersistPropertyBag then lets allow it
  675. // to slurp its settings into from the registry.
  676. SHLoadFromPropertyBag(pnc, ppb);
  677. // allow it to update and load.
  678. pnc->Update(_dwFlags); // we don't care about the failure
  679. pnc->Release();
  680. }
  681. }
  682. ppb->Release();
  683. }
  684. }
  685. }
  686. RegCloseKey(hk);
  687. }
  688. ASSERT( 0 != _cUpdateLock );
  689. InterlockedDecrement(&_cUpdateLock); // release the lock that signifies that we're updating:
  690. Release();
  691. return 0;
  692. }
  693. STDMETHODIMP CNetCrawler::Update(DWORD dwFlags)
  694. {
  695. // we either have policy defined to disable the crawler, or the
  696. // users has selected that they don't want to be able to auto discover
  697. // the world.
  698. SHELLSTATE ss;
  699. SHGetSetSettings(&ss, SSF_NONETCRAWLING, FALSE);
  700. if (ss.fNoNetCrawling || SHRestricted(REST_NONETCRAWL))
  701. {
  702. return S_OK;
  703. }
  704. // increase the lock, if its >0 then we should not bother crawling again
  705. // as we already have it covered, therefore decrease the lock counter.
  706. //
  707. // if the lock is ==0 then create the thread which will do the crawling
  708. // and inturn create the objects.
  709. HRESULT hr = S_OK;
  710. if (InterlockedIncrement(&_cUpdateLock) == 1)
  711. {
  712. _dwFlags = dwFlags; // store the flags for use later
  713. AddRef();
  714. if (!SHCreateThread(s_DoCrawl, (void*)this, CTF_COINIT, NULL))
  715. {
  716. Release();
  717. hr = E_FAIL;
  718. }
  719. }
  720. else
  721. {
  722. ASSERT( 0 != _cUpdateLock );
  723. InterlockedDecrement(&_cUpdateLock);
  724. }
  725. return hr;
  726. }
  727. // helper function that will invoke the net crawler to perform a async refresh,
  728. // to ensure that we don't block we will create a thread which inturn will CoCreate
  729. // the net crawler and then call its refresh method.
  730. DWORD _RefreshCrawlerThreadProc(void *pv)
  731. {
  732. INetCrawler *pnc;
  733. if (SUCCEEDED(CoCreateInstance(CLSID_NetCrawler, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARG(INetCrawler, &pnc))))
  734. {
  735. pnc->Update(SNCF_REFRESHLIST);
  736. pnc->Release();
  737. }
  738. return 0;
  739. }
  740. STDAPI_(void) RefreshNetCrawler()
  741. {
  742. SHCreateThread(_RefreshCrawlerThreadProc, NULL, CTF_COINIT, NULL);
  743. }