WindowsXP-SP1/shell/shell32/netcrawl.cpp

915 lines
30 KiB
C++

#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);
}