#include "shellprv.h" #include "commctrl.h" #include "comctrlp.h" #pragma hdrstop #include "netview.h" #include "msprintx.h" #include "setupapi.h" #include "ras.h" #include "ids.h" // a COM object to enumerate shares and printers in the shell, its acts // as a monitor for all that is going on. class CWorkgroupCrawler : public INetCrawler, IPersistPropertyBag { public: CWorkgroupCrawler(); ~CWorkgroupCrawler(); // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // INetCrawler STDMETHOD(Update)(DWORD dwFlags); // IPersist STDMETHOD(GetClassID)(CLSID *pclsid) { *pclsid = CLSID_WorkgroupNetCrawler; return S_OK; } // IPersistPropertyBag STDMETHOD(InitNew)() { return S_OK; } STDMETHOD(Load)(IPropertyBag *ppb, IErrorLog *pel) { IUnknown_Set((IUnknown**)&_ppb, ppb); return S_OK; } STDMETHOD(Save)(IPropertyBag *ppb, BOOL fClearDirty, BOOL fSaveAll) { return S_OK; } private: HRESULT _GetMRUs(); void _AgeOutShares(BOOL fDeleteAll); HRESULT _CreateShortcutToShare(LPCTSTR pszRemoteName); HRESULT _InstallPrinter(LPCTSTR pszRemoteName); BOOL _KeepGoing(int *pcMachines, int *pcShares, int *pcPrinters); void _EnumResources(LPNETRESOURCE pnr, int *pcMachines, HDPA hdaShares, HDPA hdaPrinters); HANDLE _AddPrinterConnectionNoUI(LPCWSTR pszRemoteName, BOOL *pfInstalled); static int CALLBACK _DiscardCB(void *pvItem, void *pv); static int CALLBACK _InstallSharesCB(void *pvItem, void *pv); static int CALLBACK _InstallPrinterCB(void *pvItem, void *pv); LONG _cRef; // reference count for the object HANDLE _hPrinters; // MRU for printers HKEY _hShares; // registry key for the printer shares HINSTANCE _hPrintUI; // instance handle for printui.dll IPropertyBag *_ppb; // property bag object for state }; // constants for the MRU's and buffers #define WORKGROUP_PATH \ REGSTR_PATH_EXPLORER TEXT("\\WorkgroupCrawler") #define PRINTER_SUBKEY \ (WORKGROUP_PATH TEXT("\\Printers")) #define SHARE_SUBKEY \ (WORKGROUP_PATH TEXT("\\Shares")) #define LAST_VISITED TEXT("DateLastVisited") #define SHORTCUT_NAME TEXT("Filename") #define MAX_MACHINES 32 #define MAX_PRINTERS 10 #define MAX_SHARES 10 #define CB_WNET_BUFFER (8*1024) typedef HANDLE (* ADDPRINTCONNECTIONNOUI)(LPCWSTR, BOOL *); // construction and IUnknown CWorkgroupCrawler::CWorkgroupCrawler() : _cRef(1) { } CWorkgroupCrawler::~CWorkgroupCrawler() { if (_hPrinters) FreeMRUList(_hPrinters); if (_hShares) RegCloseKey(_hShares); if (_hPrintUI) FreeLibrary(_hPrintUI); if (_ppb) _ppb->Release(); } STDMETHODIMP CWorkgroupCrawler::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CWorkgroupCrawler, INetCrawler), // IID_INetCrawler QITABENT(CWorkgroupCrawler, IPersist), // IID_IPersist QITABENT(CWorkgroupCrawler, IPersistPropertyBag), // IID_IPersistPropertyBag { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CWorkgroupCrawler::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CWorkgroupCrawler::Release() { if (InterlockedDecrement(&_cRef)) return _cRef; delete this; return 0; } STDAPI CWorkgroupCrawler_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppv) { CWorkgroupCrawler *pwgc = new CWorkgroupCrawler(); if (!pwgc) return E_OUTOFMEMORY; HRESULT hr = pwgc->QueryInterface(riid, ppv); pwgc->Release(); return hr; } // lets open the keys for the objects we are about to install HRESULT CWorkgroupCrawler::_GetMRUs() { // get the printers MRU if we need to allocate one if (!_hPrinters) { MRUINFO mi = { 0 }; mi.cbSize = sizeof(mi); mi.hKey = HKEY_CURRENT_USER; mi.uMax = (MAX_PRINTERS * MAX_MACHINES); mi.lpszSubKey = PRINTER_SUBKEY; _hPrinters = CreateMRUList(&mi); if (!_hPrinters) return E_OUTOFMEMORY; } if (!_hShares) { DWORD dwres = RegCreateKeyEx(HKEY_CURRENT_USER, SHARE_SUBKEY, 0, TEXT(""), 0, MAXIMUM_ALLOWED, NULL, &_hShares, NULL); if (WN_SUCCESS != dwres) { return E_FAIL; } } return S_OK; // success } // lets create a folder shortcut to the object HRESULT CWorkgroupCrawler::_CreateShortcutToShare(LPCTSTR pszRemoteName) { HRESULT hr = S_OK; TCHAR szTemp[MAX_PATH]; BOOL fCreateLink = FALSE; HKEY hk = NULL; // the share information is stored in the registry as follows: // // Shares // Remote Name // value: shortcut name // value: last seen // // as we add each share we update the information stored in this list in // the registry. for each entry we have the shortcut name (so we can remove it) // and the time and date we last visited the share. // determine if we need to recreate the object? StrCpyN(szTemp, pszRemoteName+2, ARRAYSIZE(szTemp)); LPTSTR pszTemp = StrChr(szTemp, TEXT('\\')); if (pszTemp) { *pszTemp = TEXT('/'); // convert the \\...\... to .../... DWORD dwres = RegOpenKeyEx(_hShares, szTemp, 0, MAXIMUM_ALLOWED, &hk); if (WN_SUCCESS != dwres) { fCreateLink = TRUE; dwres = RegCreateKeyEx(_hShares, szTemp, 0, TEXT(""), 0, MAXIMUM_ALLOWED, NULL, &hk, NULL); } if (WN_SUCCESS == dwres) { // if we haven't already seen the link (eg. the key didn't exist in the registry // then lets create it now. if (fCreateLink) { // NOTE: we must use SHCoCreateInstance() here because we are being called from a thread // that intentionally did not initialize COM (see comment in Update()) IShellLink *psl; hr = SHCoCreateInstance(NULL, &CLSID_FolderShortcut, NULL, IID_PPV_ARG(IShellLink, &psl)); if (SUCCEEDED(hr)) { psl->SetPath(pszRemoteName); // sotore the remote name, its kinda important // get a description for the link, this comes either from the desktop.ini or the // is a pretty version of teh remote name. if (GetShellClassInfo(pszRemoteName, TEXT("InfoTip"), szTemp, ARRAYSIZE(szTemp))) { psl->SetDescription(szTemp); } else { StrCpyN(szTemp, pszRemoteName, ARRAYSIZE(szTemp)); PathMakePretty(szTemp); psl->SetDescription(szTemp); } // some links (shared documents) can specify a shortcut name, if this is specified // then use it, otherwise get a filename from the nethood folder (eg. foo on bah). // // we musst also record the name we save the shortcut as, this used when we // age out the links from the hood folder. if (!GetShellClassInfo(pszRemoteName, TEXT("NetShareDisplayName"), szTemp, ARRAYSIZE(szTemp))) { LPITEMIDLIST pidl; hr = SHILCreateFromPath(pszRemoteName, &pidl, NULL); if (SUCCEEDED(hr)) { hr = SHGetNameAndFlags(pidl, SHGDN_NORMAL, szTemp, ARRAYSIZE(szTemp), NULL); ILFree(pidl); } } else { hr = S_OK; } // should we find a unique name (daviddv) if (SUCCEEDED(hr)) { if (NO_ERROR == SHSetValue(hk, NULL, SHORTCUT_NAME, REG_SZ, szTemp, lstrlen(szTemp)*sizeof(TCHAR))) { hr = SaveShortcutInFolder(CSIDL_NETHOOD, szTemp, psl); } else { hr = E_FAIL; } } psl->Release(); } } // lets update the time we last saw the link into the registry - this is used for the clean up // pass we will perform. if (SUCCEEDED(hr)) { FILETIME ft; GetSystemTimeAsFileTime(&ft); dwres = SHSetValue(hk, NULL, LAST_VISITED, REG_BINARY, (void*)&ft, sizeof(ft)); hr = (NO_ERROR != dwres) ? E_FAIL:S_OK; } } if (hk) RegCloseKey(hk); } else { hr = E_UNEXPECTED; } return hr; } // walk the list of shares stored in the registry to determine which ones should be // removed from the file system and the list. all files older than 7 days need to // be removed. #define FILETIME_SECOND_OFFSET (LONGLONG)((1 * 10 * 1000 * (LONGLONG)1000)) void CWorkgroupCrawler::_AgeOutShares(BOOL fDeleteAll) { FILETIME ft; ULARGE_INTEGER ulTime; DWORD index = 0; TCHAR szFilesToDelete[1024]; int cchFilesToDelete = 0; GetSystemTimeAsFileTime(&ft); ulTime = *((ULARGE_INTEGER*)&ft); ulTime.QuadPart -= FILETIME_SECOND_OFFSET*((60*60*24)*2); SHQueryInfoKey(_hShares, &index, NULL, NULL, NULL); // retrieve the count of the keys while (((LONG)(--index)) >= 0) { TCHAR szKey[MAX_PATH]; DWORD cb = ARRAYSIZE(szKey); BOOL fRemoveKey = FALSE; if (WN_SUCCESS == SHEnumKeyEx(_hShares, index, szKey, &cb)) { // we enumerated a key name, so lets open it so we can look around inside. HKEY hk; if (WN_SUCCESS == RegOpenKeyEx(_hShares, szKey, 0, MAXIMUM_ALLOWED, &hk)) { ULARGE_INTEGER ulLastSeen; // when did we last crawl to this object, if it was less than the time we // have for our threshold, then we go through the process of cleaning up the // object. cb = sizeof(ulLastSeen); if (ERROR_SUCCESS == SHGetValue(hk, NULL, LAST_VISITED, NULL, (void*)&ulLastSeen, &cb)) { if (fDeleteAll || (ulLastSeen.QuadPart <= ulTime.QuadPart)) { TCHAR szName[MAX_PATH]; cb = ARRAYSIZE(szName)*sizeof(TCHAR); if (ERROR_SUCCESS == SHGetValue(hk, NULL, SHORTCUT_NAME, NULL, &szName, &cb)) { TCHAR szPath[MAX_PATH]; // compose the path to the object we want to delete. if the buffer // is full (eg. this item would over run the size) then flush the // buffer. SHGetFolderPath(NULL, CSIDL_NETHOOD|CSIDL_FLAG_CREATE, NULL, 0, szPath); PathAppend(szPath, szName); if ((lstrlen(szPath)+cchFilesToDelete) >= ARRAYSIZE(szFilesToDelete)) { SHFILEOPSTRUCT shfo = { NULL, FO_DELETE, szFilesToDelete, NULL, FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI, FALSE, NULL, NULL }; szFilesToDelete[cchFilesToDelete] = 0; // double terminate SHFileOperation(&shfo); cchFilesToDelete = 0; } // add this name to the buffer StrCpyN(&szFilesToDelete[cchFilesToDelete], szPath, ARRAYSIZE(szFilesToDelete) - cchFilesToDelete); cchFilesToDelete += lstrlen(szPath)+1; } fRemoveKey = TRUE; } } RegCloseKey(hk); // we can only close the key once it has been closed if (fRemoveKey) SHDeleteKey(_hShares, szKey); } } } // are there any trailing files in the buffer? if so then lets nuke them also if (cchFilesToDelete) { SHFILEOPSTRUCT shfo = { NULL, FO_DELETE, szFilesToDelete, NULL, FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI, FALSE, NULL, NULL }; szFilesToDelete[cchFilesToDelete] = 0; // double terminate SHFileOperation(&shfo); } } // silently install printers we have discovered. we have the remote name of the // printer share, so we then call printui to perform the printer installation // which it does without UI (hopefully). HANDLE CWorkgroupCrawler::_AddPrinterConnectionNoUI(LPCWSTR pszRemoteName, BOOL *pfInstalled) { HANDLE hResult = NULL; if (!_hPrintUI) _hPrintUI = LoadLibrary(TEXT("printui.dll")); if (_hPrintUI) { ADDPRINTCONNECTIONNOUI apc = (ADDPRINTCONNECTIONNOUI)GetProcAddress(_hPrintUI, (LPCSTR)200); if (apc) { hResult = apc(pszRemoteName, pfInstalled); } } return hResult; } HRESULT CWorkgroupCrawler::_InstallPrinter(LPCTSTR pszRemoteName) { if (-1 == FindMRUString(_hPrinters, pszRemoteName, NULL)) { BOOL fInstalled; HANDLE hPrinter = _AddPrinterConnectionNoUI(pszRemoteName, &fInstalled); if (hPrinter) { ClosePrinter(hPrinter); hPrinter = NULL; } } AddMRUString(_hPrinters, pszRemoteName); // promote back to the top of the list return S_OK; } // check the counters, if we have max'd out then lets stop enumerating BOOL CWorkgroupCrawler::_KeepGoing(int *pcMachines, int *pcShares, int *pcPrinters) { if (pcMachines && (*pcMachines > MAX_MACHINES)) return FALSE; if (pcShares && (*pcShares > MAX_SHARES)) return FALSE; if (pcPrinters && (*pcPrinters > MAX_PRINTERS)) return FALSE; return TRUE; } void CWorkgroupCrawler::_EnumResources(LPNETRESOURCE pnr, int *pcMachines, HDPA hdaShares, HDPA hdaPrinters) { HANDLE hEnum = NULL; int cPrinters = 0; int cShares = 0; DWORD dwScope = RESOURCE_GLOBALNET; // if no net resource structure passed then lets enumerate the workgroup // (this is used for debugging) NETRESOURCE nr = { 0 }; if (!pnr) { pnr = &nr; dwScope = RESOURCE_CONTEXT; nr.dwType = RESOURCETYPE_ANY; nr.dwUsage = RESOURCEUSAGE_CONTAINER; } // open the enumerator DWORD dwres = WNetOpenEnum(dwScope, RESOURCETYPE_ANY, 0, pnr, &hEnum); if (NO_ERROR == dwres) { NETRESOURCE *pnrBuffer = (NETRESOURCE*)SHAlloc(CB_WNET_BUFFER); // avoid putting the buffer on the stack if (pnrBuffer) { while ((WN_SUCCESS == dwres) || (dwres == ERROR_MORE_DATA) && _KeepGoing(pcMachines, &cShares, &cPrinters)) { DWORD cbEnumBuffer= CB_WNET_BUFFER; DWORD dwCount = -1; // enumerate the resources for this enum context and then lets // determine the objects which we should see. dwres = WNetEnumResource(hEnum, &dwCount, pnrBuffer, &cbEnumBuffer); if ((WN_SUCCESS == dwres) || (dwres == ERROR_MORE_DATA)) { DWORD index; for (index = 0 ; (index != dwCount) && _KeepGoing(pcMachines, &cShares, &cPrinters) ; index++) { LPNETRESOURCE pnr = &pnrBuffer[index]; LPTSTR pszRemoteName = pnr->lpRemoteName; switch (pnr->dwDisplayType) { case RESOURCEDISPLAYTYPE_ROOT: // ignore the entire network object default: break; case RESOURCEDISPLAYTYPE_NETWORK: { // ensure that we only crawl the local network providers (eg. Windows Networking) // crawling DAV, TSCLIENT etc can cause all sorts of random pop ups. DWORD dwType, cbProviderType = sizeof(dwType); if (WN_SUCCESS == WNetGetProviderType(pnr->lpProvider, &dwType)) { if (dwType == WNNC_NET_LANMAN) { _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters); } } break; } case RESOURCEDISPLAYTYPE_DOMAIN: _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters); break; case RESOURCEDISPLAYTYPE_SERVER: { *pcMachines += 1; // another machine found if (!PathIsSlow(pszRemoteName, -1)) { SHCacheComputerDescription(pszRemoteName, pnr->lpComment); _EnumResources(pnr, pcMachines, hdaShares, hdaPrinters); } break; } case RESOURCEDISPLAYTYPE_SHARE: { HDPA hdpa = NULL; switch (pnr->dwType) { case RESOURCETYPE_PRINT: cPrinters++; hdpa = hdaPrinters; break; case RESOURCETYPE_DISK: cShares++; hdpa = hdaShares; break; default: break; } if (hdpa) { LPTSTR pszName = StrDup(pszRemoteName); if (pszName) { if (-1 == DPA_AppendPtr(hdpa, pszName)) { LocalFree(pszName); } } } break; } } } } } SHFree(pnrBuffer); } WNetCloseEnum(hEnum); } } // handle the clean up of the DPA's, either we are installing or // we are releasing objects. int CALLBACK CWorkgroupCrawler::_DiscardCB(void *pvItem, void *pv) { LPTSTR pszRemoteName = (LPTSTR)pvItem; LocalFree(pszRemoteName); return 1; } int CALLBACK CWorkgroupCrawler::_InstallPrinterCB(void *pvItem, void *pv) { CWorkgroupCrawler* pnc = (CWorkgroupCrawler*)pv; if (pnc) { LPTSTR pszRemoteName = (LPTSTR)pvItem; pnc->_InstallPrinter(pszRemoteName); } return _DiscardCB(pvItem, pv); } int CALLBACK CWorkgroupCrawler::_InstallSharesCB(void *pvItem, void *pv) { CWorkgroupCrawler* pnc = (CWorkgroupCrawler*)pv; if (pnc) { LPTSTR pszRemoteName = (LPTSTR)pvItem; pnc->_CreateShortcutToShare(pszRemoteName); } return _DiscardCB(pvItem, pv); } HRESULT CWorkgroupCrawler::Update(DWORD dwFlags) { // don't crawl if we are logged in on a TS client, this will discover the shares and // printers local to the terminal server machine, rather than the ones local to the // users login domain - badness. if (SHGetMachineInfo(GMI_TSCLIENT)) return S_OK; // by default we will only crawl if there isn't a RAS connection, therefore lets // check the status using RasEnumConnections. RASCONN rc = { 0 }; DWORD cbConnections = sizeof(rc); DWORD cConnections = 0; rc.dwSize = sizeof(rc); if (!RasEnumConnections(&rc, &cbConnections, &cConnections) && cConnections) return S_OK; // check to see if we are in a domain or not, if we are then we shouldn't crawl. however // we do a provide a "WorkgroupOnly" policy which overrides this behaviour. setting // this causes us to skip the check, and perform a CONTEXT ENUM below... BOOL fWorkgroupOnly = (_ppb ? SHPropertyBag_ReadBOOLDefRet(_ppb, L"WorkgroupOnly", FALSE):FALSE); if (IsOS(OS_DOMAINMEMBER) && !fWorkgroupOnly) return S_OK; // populate the DPAs with shares and printer objects we find on the network, to // do this enumeration lets fake up a NETRESOURCE structure for entire network int cMachines = 0; HDPA hdaShares = DPA_Create(MAX_SHARES); HDPA hdaPrinters = DPA_Create(MAX_PRINTERS); if (hdaShares && hdaPrinters) { NETRESOURCE nr = { 0 }; nr.dwDisplayType = RESOURCEDISPLAYTYPE_ROOT; nr.dwType = RESOURCETYPE_ANY; nr.dwUsage = RESOURCEUSAGE_CONTAINER; _EnumResources(fWorkgroupOnly ? NULL:&nr, &cMachines, hdaShares, hdaPrinters); } // now attempt to make connections to the shares and printers. to do this // we need to look at the number of machines we have visited, if its less // than our threshold then we can install. if (SUCCEEDED(_GetMRUs()) && (cMachines < MAX_MACHINES)) { DPA_DestroyCallback(hdaShares, _InstallSharesCB, this); DPA_DestroyCallback(hdaPrinters, _InstallPrinterCB, this); _AgeOutShares(FALSE); } else { DPA_DestroyCallback(hdaShares, _DiscardCB, this); DPA_DestroyCallback(hdaPrinters, _DiscardCB, this); } return S_OK; } // this is the main crawler object, from this we create the protocol specific // crawlers which handle enumerating the resources for the various network types. #define CRAWLER_SUBKEY \ TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\NetworkCrawler\\Objects") class CNetCrawler : public INetCrawler { public: CNetCrawler(); // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // INetCrawler STDMETHOD(Update)(DWORD dwFlags); private: static DWORD CALLBACK s_DoCrawl(void* pv); DWORD _DoCrawl(); LONG _cRef; LONG _cUpdateLock; // > 0 then we are already spinning DWORD _dwFlags; // flags from update - passed to each of the crawler sub-objects }; CNetCrawler* g_pnc = NULL; // there is a single instance of this object // construction / IUnknown CNetCrawler::CNetCrawler() : _cRef(1) { } STDMETHODIMP CNetCrawler::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CNetCrawler, INetCrawler), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CNetCrawler::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CNetCrawler::Release() { if (InterlockedDecrement(&_cRef)) return _cRef; ENTERCRITICAL; g_pnc = NULL; LEAVECRITICAL; delete this; return 0; } // there is a single instance of the object, therefore in a critical section // lets check to see if the global exists, if so then QI it, otherwise // create a new one and QI that instead. STDAPI CNetCrawler_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppv) { HRESULT hr = E_OUTOFMEMORY; ENTERCRITICAL; if (g_pnc) { hr = g_pnc->QueryInterface(riid, ppv); } else { g_pnc = new CNetCrawler(); if (g_pnc) { hr = g_pnc->QueryInterface(riid, ppv); g_pnc->Release(); } } LEAVECRITICAL; return hr; } // this object has a execution count to inidicate if we are already crawling. // only if the count is 0 when the ::Update method is called, will create a thread // which inturn will create each of the crawler objects and allow them to // do their enumeration. DWORD CALLBACK CNetCrawler::s_DoCrawl(void* pv) { CNetCrawler *pnc = (CNetCrawler*)pv; return pnc->_DoCrawl(); } DWORD CNetCrawler::_DoCrawl() { // enumrate all the keys under the crawler sub-key, from that we can then // create the individual crawler objects. HKEY hk; DWORD dwres = RegOpenKeyEx(HKEY_LOCAL_MACHINE, CRAWLER_SUBKEY, 0, KEY_READ, &hk); if (WN_SUCCESS == dwres) { DWORD index = 0; SHQueryInfoKey(hk, &index, NULL, NULL, NULL); // retrieve the count of the keys while (((LONG)(--index)) >= 0) { TCHAR szKey[MAX_PATH]; DWORD cb = ARRAYSIZE(szKey); if (WN_SUCCESS == SHEnumKeyEx(hk, index, szKey, &cb)) { // given the keyname, create a property bag so we can access IPropertyBag *ppb; HRESULT hr = SHCreatePropertyBagOnRegKey(hk, szKey, STGM_READ, IID_PPV_ARG(IPropertyBag, &ppb)); if (SUCCEEDED(hr)) { // we have a property bag mapped to the registry for the items we need // to read back, so lets get the CLSID and create a crawler from it. CLSID clsid; hr = SHPropertyBag_ReadGUID(ppb, L"CLSID", &clsid); if (SUCCEEDED(hr)) { INetCrawler *pnc; hr = SHCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(INetCrawler, &pnc)); if (SUCCEEDED(hr)) { // if the crawler supports IPersistPropertyBag then lets allow it // to slurp its settings into from the registry. SHLoadFromPropertyBag(pnc, ppb); // allow it to update and load. pnc->Update(_dwFlags); // we don't care about the failure pnc->Release(); } } ppb->Release(); } } } RegCloseKey(hk); } InterlockedDecrement(&_cUpdateLock); // release the lock that signifies that we're updating: Release(); return 0; } STDMETHODIMP CNetCrawler::Update(DWORD dwFlags) { // we either have policy defined to disable the crawler, or the // users has selected that they don't want to be able to auto discover // the world. SHELLSTATE ss; SHGetSetSettings(&ss, SSF_NONETCRAWLING, FALSE); if (ss.fNoNetCrawling || SHRestricted(REST_NONETCRAWL)) { return S_OK; } // increase the lock, if its >0 then we should not bother crawling again // as we already have it covered, therefore decrease the lock counter. // // if the lock is ==0 then create the thread which will do the crawling // and inturn create the objects. HRESULT hr = S_OK; if (InterlockedIncrement(&_cUpdateLock) == 1) { _dwFlags = dwFlags; // store the flags for use later AddRef(); if (!SHCreateThread(s_DoCrawl, (void*)this, CTF_COINIT, NULL)) { Release(); hr = E_FAIL; } } else { InterlockedDecrement(&_cUpdateLock); } return hr; } // helper function that will invoke the net crawler to perform a async refresh, // to ensure that we don't block we will create a thread which inturn will CoCreate // the net crawler and then call its refresh method. DWORD _RefreshCrawlerThreadProc(void *pv) { INetCrawler *pnc; if (SUCCEEDED(CoCreateInstance(CLSID_NetCrawler, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARG(INetCrawler, &pnc)))) { pnc->Update(SNCF_REFRESHLIST); pnc->Release(); } return 0; } STDAPI_(void) RefreshNetCrawler() { SHCreateThread(_RefreshCrawlerThreadProc, NULL, CTF_COINIT, NULL); }