#include "shellprv.h" #include "intshcut.h" #include "ids.h" #include // defines some values used for fmtid and pid #include // For ConvertSidToStringSid() #include "prop.h" // SCID_ stuff #include "netview.h" // SHWNetGetConnection #include "clsobj.h" HRESULT ReadProperty(IPropertySetStorage *ppss, REFFMTID fmtid, PROPID pid, VARIANT *pVar) { VariantInit(pVar); IPropertyStorage *pps; HRESULT hr = ppss->Open(fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps); if (SUCCEEDED(hr)) { PROPSPEC PropSpec; PROPVARIANT PropVar = {0}; PropSpec.ulKind = PRSPEC_PROPID; PropSpec.propid = pid; hr = SHPropStgReadMultiple( pps, 0, 1, &PropSpec, &PropVar ); if (SUCCEEDED(hr)) { hr = PropVariantToVariant(&PropVar, pVar); PropVariantClear(&PropVar); } pps->Release(); } return hr; } BOOL IsSlowProperty(IPropertySetStorage *ppss, REFFMTID fmtid, PROPID pid) { IPropertyStorage *pps; BOOL bRet = FALSE; if (SUCCEEDED(ppss->Open(fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps))) { IQueryPropertyFlags *pqsp; if (SUCCEEDED(pps->QueryInterface(IID_PPV_ARG(IQueryPropertyFlags, &pqsp)))) { PROPSPEC PropSpec; PROPVARIANT PropVar = {0}; PropSpec.ulKind = PRSPEC_PROPID; PropSpec.propid = pid; SHCOLSTATEF csFlags; if (SUCCEEDED(pqsp->GetFlags(&PropSpec, &csFlags))) { bRet = ((csFlags & SHCOLSTATE_SLOW) == SHCOLSTATE_SLOW); } // If the property isn't part of this property set, IsSlowProperty will return fairlure, // which we'll treat as a fast property. pqsp->Release(); } pps->Release(); } return bRet; } class CBaseColumnProvider : public IPersist, public IColumnProvider { // IUnknown methods public: STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) { static const QITAB qit[] = { QITABENT(CBaseColumnProvider, IColumnProvider), // IID_IColumnProvider QITABENT(CBaseColumnProvider, IPersist), // IID_IPersist { 0 }, }; return QISearch(this, qit, riid, ppv); }; STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); }; STDMETHODIMP_(ULONG) Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }; // IPersist STDMETHODIMP GetClassID(CLSID *pClassID) { *pClassID = *_pclsid; return S_OK; }; // IColumnProvider STDMETHODIMP Initialize(LPCSHCOLUMNINIT psci) { return S_OK ; } STDMETHODIMP GetColumnInfo(DWORD dwIndex, LPSHCOLUMNINFO psci); CBaseColumnProvider(const CLSID *pclsid, const COLUMN_INFO rgColMap[], int iCount, const LPCWSTR rgExts[]) : _cRef(1), _pclsid(pclsid), _rgColumns(rgColMap), _iCount(iCount), _rgExts(rgExts) { DllAddRef(); } protected: virtual ~CBaseColumnProvider() { DllRelease(); } BOOL _IsHandled(LPCWSTR pszExt); int _iCount; const COLUMN_INFO *_rgColumns; private: long _cRef; const CLSID * _pclsid; const LPCWSTR *_rgExts; }; // the index is an arbitrary zero based index used for enumeration STDMETHODIMP CBaseColumnProvider::GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci) { ZeroMemory(psci, sizeof(*psci)); if (dwIndex < (UINT) _iCount) { psci->scid = *_rgColumns[dwIndex].pscid; psci->cChars = _rgColumns[dwIndex].cChars; psci->vt = _rgColumns[dwIndex].vt; psci->fmt = _rgColumns[dwIndex].fmt; psci->csFlags = _rgColumns[dwIndex].csFlags; TCHAR szTemp[MAX_COLUMN_NAME_LEN]; LoadString(HINST_THISDLL, _rgColumns[dwIndex].idTitle, szTemp, ARRAYSIZE(szTemp)); SHTCharToUnicode(szTemp, psci->wszTitle, ARRAYSIZE(psci->wszTitle)); return S_OK; } return S_FALSE; } // see if this file type is one we are interested in BOOL CBaseColumnProvider::_IsHandled(LPCWSTR pszExt) { if (_rgExts) { for (int i = 0; _rgExts[i]; i++) { if (0 == StrCmpIW(pszExt, _rgExts[i])) return TRUE; } return FALSE; } return TRUE; } // col handler that works over IPropertySetStorage handlers const COLUMN_INFO c_rgDocObjColumns[] = { DEFINE_COL_STR_ENTRY(SCID_Author, 20, IDS_EXCOL_AUTHOR), DEFINE_COL_STR_ENTRY(SCID_Title, 20, IDS_EXCOL_TITLE), DEFINE_COL_STR_DLG_ENTRY(SCID_Subject, 20, IDS_EXCOL_SUBJECT), DEFINE_COL_STR_DLG_ENTRY(SCID_Category, 20, IDS_EXCOL_CATEGORY), DEFINE_COL_INT_DLG_ENTRY(SCID_PageCount, 10, IDS_EXCOL_PAGECOUNT), DEFINE_COL_STR_ENTRY(SCID_Comment, 30, IDS_EXCOL_COMMENT), DEFINE_COL_STR_DLG_ENTRY(SCID_Copyright, 30, IDS_EXCOL_COPYRIGHT), DEFINE_COL_STR_ENTRY(SCID_MUSIC_Artist, 15, IDS_EXCOL_ARTIST), DEFINE_COL_STR_ENTRY(SCID_MUSIC_Album, 15, IDS_EXCOL_ALBUM), DEFINE_COL_STR_ENTRY(SCID_MUSIC_Year, 10, IDS_EXCOL_YEAR), DEFINE_COL_INT_ENTRY(SCID_MUSIC_Track, 5, IDS_EXCOL_TRACK), DEFINE_COL_STR_ENTRY(SCID_MUSIC_Genre, 20, IDS_EXCOL_GENRE), DEFINE_COL_STR_ENTRY(SCID_AUDIO_Duration, 15, IDS_EXCOL_DURATION), DEFINE_COL_STR_ENTRY(SCID_AUDIO_Bitrate, 15, IDS_EXCOL_BITRATE), DEFINE_COL_STR_ENTRY(SCID_DRM_Protected, 10, IDS_EXCOL_PROTECTED), DEFINE_COL_STR_ENTRY(SCID_CameraModel, 20, IDS_EXCOL_CAMERAMODEL), DEFINE_COL_STR_ENTRY(SCID_WhenTaken, 20, IDS_EXCOL_WHENTAKEN), DEFINE_COL_STR_ENTRY(SCID_ImageDimensions, 20, IDS_EXCOL_DIMENSIONS), DEFINE_COL_INT_HIDDEN_ENTRY(SCID_ImageCX), DEFINE_COL_INT_HIDDEN_ENTRY(SCID_ImageCY), DEFINE_COL_DATE_HIDDEN_ENTRY(SCID_DocCreated), }; class CPropStgColumns : public CBaseColumnProvider { STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData); private: // help on initializing base classes: mk:@ivt:vclang/FB/DD/S44B5E.HTM CPropStgColumns() : CBaseColumnProvider(&CLSID_DocFileColumnProvider, c_rgDocObjColumns, ARRAYSIZE(c_rgDocObjColumns), NULL) { ASSERT(_wszLastFile[0] == 0); ASSERT(_bSlowPropertiesCached == FALSE); }; ~CPropStgColumns() { _FreeCache(); } // for the cache VARIANT _rgvCache[ARRAYSIZE(c_rgDocObjColumns)]; // zero'ing allocator will fill with VT_EMPTY BOOL _rgbSlow[ARRAYSIZE(c_rgDocObjColumns)]; // Store if each property is "slow". WCHAR _wszLastFile[MAX_PATH]; HRESULT _hrCache; BOOL _bSlowPropertiesCached; #ifdef DEBUG int deb_dwTotal, deb_dwMiss; #endif void _FreeCache(); friend HRESULT CDocFileColumns_CreateInstance(IUnknown *punk, REFIID riid, void **ppv); }; void CPropStgColumns::_FreeCache() { for (int i = 0; i < ARRAYSIZE(_rgvCache); i++) VariantClear(&_rgvCache[i]); _hrCache = S_OK; } STDMETHODIMP CPropStgColumns::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData) { HRESULT hr; // VariantCopy requires input to be initialized, and we handle failure case VariantInit(pvarData); // is this even a property we support? for (int iProp = 0; iProp < _iCount; iProp++) { if (IsEqualSCID(*_rgColumns[iProp].pscid, *pscid)) { goto found; } } // Unknown property return S_FALSE; found: #ifdef DEBUG deb_dwTotal++; #endif // Three cases here: // 1) We need to update the cache. Fetch the properties again (and only get fast props if we asked for a fast prop) // 2) We've only cached fast properties so far, and we asked for a slow property, so now we need to get slow props. // 3) The property we want is cached. if ((pscd->dwFlags & SHCDF_UPDATEITEM) || (StrCmpW(_wszLastFile, pscd->wszFile) != 0)) { // 1) Cache is no good - item has been updated, or this is a different file. // SHCDF_UPDATEITEM flag is a hint // that the file for which we are getting data has changed since the last call. This flag // is only passed once per filename, not once per column per filename so update the entire // cache if this flag is set. // sanity check our caching. If the shell thread pool is > 1, we will thrash like mad, and should change this #ifdef DEBUG deb_dwMiss++; if ((deb_dwTotal > 3) && (deb_dwTotal / deb_dwMiss <= 3)) TraceMsg(TF_DEFVIEW, "Column data caching is ineffective (%d misses for %d access)", deb_dwMiss, deb_dwTotal); #endif _FreeCache(); hr = StringCchCopy(_wszLastFile, ARRAYSIZE(_wszLastFile), pscd->wszFile); if (SUCCEEDED(hr)) { IPropertySetStorage *ppss; hr = SHFileSysBindToStorage(pscd->wszFile, pscd->dwFileAttributes, STGM_READ | STGM_SHARE_DENY_WRITE, 0, IID_PPV_ARG(IPropertySetStorage, &ppss)); _hrCache = hr; if (SUCCEEDED(hr)) { // Did we ask for a slow property? BOOL bSlowProperty = IsSlowProperty(ppss, _rgColumns[iProp].pscid->fmtid, _rgColumns[iProp].pscid->pid); hr = E_INVALIDARG; // normally overwritten by hrT below for (int i = 0; i < _iCount; i++) { // For every property, take note if it is "slow" _rgbSlow[i] = IsSlowProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid); // Only retrieve a value right now if we asked for a slow property, or this is not a slow property. if (bSlowProperty || (!_rgbSlow[i])) { // it would be slightly more efficient, but more code, to set up the propid array to call ReadMultiple HRESULT hrT = ReadProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid, &_rgvCache[i]); if (i == iProp) { hr = (SUCCEEDED(hrT) ? VariantCopy(pvarData, &_rgvCache[i]) : hrT); } } } ppss->Release(); _bSlowPropertiesCached = bSlowProperty; } } } else if (_rgbSlow[iProp] && !_bSlowPropertiesCached) { // 2) We asked for a slow property, but slow properties haven't been cached yet. // Bind to the storage a second time. This is a perf hit, but should be // minor compared to getting slow properties. IPropertySetStorage *ppss; hr = SHFileSysBindToStorage(pscd->wszFile, pscd->dwFileAttributes, STGM_READ | STGM_SHARE_DENY_WRITE, 0, IID_PPV_ARG(IPropertySetStorage, &ppss)); _hrCache = hr; if (SUCCEEDED(hr)) { hr = E_INVALIDARG; // normally overwritten by hrT below for (int i = 0; i < _iCount; i++) { if (_rgbSlow[i]) // If it's slow, get it. { ASSERT(_rgvCache[i].vt == VT_EMPTY); // Because we haven't retrieved it yet. HRESULT hrT = ReadProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid, &_rgvCache[i]); if (i == iProp) { hr = (SUCCEEDED(hrT) ? VariantCopy(pvarData, &_rgvCache[i]) : hrT); } } } ppss->Release(); _bSlowPropertiesCached = TRUE; } } else { // 3) It's not a slow property, or slow properties are already cached. ASSERT(!_rgbSlow[iProp] || _bSlowPropertiesCached); hr = S_FALSE; // assume we don't have it if (SUCCEEDED(_hrCache)) { if (_rgvCache[iProp].vt != VT_EMPTY) { hr = VariantCopy(pvarData, &_rgvCache[iProp]); } } } return hr; } STDAPI CDocFileColumns_CreateInstance(IUnknown *punk, REFIID riid, void **ppv) { HRESULT hr; CPropStgColumns *pdocp = new CPropStgColumns; if (pdocp) { hr = pdocp->QueryInterface(riid, ppv); pdocp->Release(); } else { *ppv = NULL; hr = E_OUTOFMEMORY; } return hr; } // Shortcut handler // W because pidl is always converted to widechar filename const LPCWSTR c_szURLExtensions[] = { L".URL", L".LNK", NULL }; const COLUMN_INFO c_rgURLColumns[] = { DEFINE_COL_STR_ENTRY(SCID_Author, 20, IDS_EXCOL_AUTHOR), DEFINE_COL_STR_ENTRY(SCID_Title, 20, IDS_EXCOL_TITLE), DEFINE_COL_STR_ENTRY(SCID_Comment, 30, IDS_EXCOL_COMMENT), }; class CLinkColumnProvider : public CBaseColumnProvider { STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData); private: // help on initializing base classes: mk:@ivt:vclang/FB/DD/S44B5E.HTM CLinkColumnProvider() : CBaseColumnProvider(&CLSID_LinkColumnProvider, c_rgURLColumns, ARRAYSIZE(c_rgURLColumns), c_szURLExtensions) {}; // friends friend HRESULT CLinkColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv); }; const struct { DWORD dwSummaryPid; DWORD dwURLPid; } c_URLMap[] = { { PIDSI_AUTHOR, PID_INTSITE_AUTHOR }, { PIDSI_TITLE, PID_INTSITE_TITLE }, { PIDSI_COMMENTS, PID_INTSITE_COMMENT }, }; DWORD _MapSummaryToSitePID(DWORD pid) { for (int i = 0; i < ARRAYSIZE(c_URLMap); i++) { if (c_URLMap[i].dwSummaryPid == pid) return c_URLMap[i].dwURLPid; } return -1; } STDMETHODIMP CLinkColumnProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData) { HRESULT hr; const CLSID *pclsidLink = &CLSID_ShellLink; // Some of the code-paths below assume pvarData is initialized VariantInit(pvarData); // should we match against a list of known extensions, or always try to open? if (FILE_ATTRIBUTE_DIRECTORY & pscd->dwFileAttributes) { if (PathIsShortcut(pscd->wszFile, pscd->dwFileAttributes)) { pclsidLink = &CLSID_FolderShortcut; // we are dealing with a folder shortcut now } else { return S_FALSE; } } else { if (!_IsHandled(pscd->pwszExt)) { return S_FALSE; } } if (StrCmpIW(pscd->pwszExt, L".URL") == 0) { // // its a .URL so lets handle it by creating the Internet Shortcut object, loading // the file and then reading the properties from it. // IPropertySetStorage *ppss; hr = LoadFromFile(CLSID_InternetShortcut, pscd->wszFile, IID_PPV_ARG(IPropertySetStorage, &ppss)); if (SUCCEEDED(hr)) { UINT pid; GUID fmtid; if (IsEqualGUID(pscid->fmtid, FMTID_SummaryInformation)) { fmtid = FMTID_InternetSite; pid = _MapSummaryToSitePID(pscid->pid); } else { fmtid = pscid->fmtid; pid = pscid->pid; } hr = ReadProperty(ppss, fmtid, pid, pvarData); ppss->Release(); } } else { // // open the .LNK file, load it and then read the description for it. we then // return this a the comment for this object. // if (IsEqualSCID(*pscid, SCID_Comment)) { IShellLink *psl; hr = LoadFromFile(*pclsidLink, pscd->wszFile, IID_PPV_ARG(IShellLink, &psl)); if (SUCCEEDED(hr)) { TCHAR szBuffer[MAX_PATH]; hr = psl->GetDescription(szBuffer, ARRAYSIZE(szBuffer)); if (SUCCEEDED(hr) && szBuffer[0]) { hr = InitVariantFromStr(pvarData, szBuffer); } else { IQueryInfo *pqi; if (SUCCEEDED(psl->QueryInterface(IID_PPV_ARG(IQueryInfo, &pqi)))) { WCHAR *pwszTip; if (SUCCEEDED(pqi->GetInfoTip(0, &pwszTip)) && pwszTip) { hr = InitVariantFromStr(pvarData, pwszTip); SHFree(pwszTip); } pqi->Release(); } } psl->Release(); } } else hr = S_FALSE; } return hr; } STDAPI CLinkColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv) { HRESULT hr; CLinkColumnProvider *pdocp = new CLinkColumnProvider; if (pdocp) { hr = pdocp->QueryInterface(riid, ppv); pdocp->Release(); } else { *ppv = NULL; hr = E_OUTOFMEMORY; } return hr; } const COLUMN_INFO c_rgFileSysColumns[] = { DEFINE_COL_STR_ENTRY(SCID_OWNER, 20, IDS_EXCOL_OWNER), }; class COwnerColumnProvider : public CBaseColumnProvider { STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData); private: COwnerColumnProvider() : CBaseColumnProvider(&CLSID_FileSysColumnProvider, c_rgFileSysColumns, ARRAYSIZE(c_rgFileSysColumns), NULL) { ASSERT(_wszLastFile[0] == 0); ASSERT(_psid==NULL && _pwszName==NULL && _psd==NULL); LoadString(HINST_THISDLL, IDS_BUILTIN_DOMAIN, _szBuiltin, ARRAYSIZE(_szBuiltin)); }; ~COwnerColumnProvider() { _CacheSidName(NULL, NULL, NULL); } WCHAR _wszLastFile[MAX_PATH]; // Since we typically get pinged for files all in the same folder, // cache the "folder to server" mapping to avoid calling // WNetGetConnection five million times. // // Since files in the same directory tend to have the same owner, // we cache the SID/Name mapping. // // Column providers do not have to support multithreaded clients, // so we won't take any critical sections. // HRESULT _LookupOwnerName(LPCTSTR pszFile, VARIANT *pvar); void _CacheSidName(PSECURITY_DESCRIPTOR psd, void *psid, LPCWSTR pwszName); void *_psid; LPWSTR _pwszName; PSECURITY_DESCRIPTOR _psd; // _psid points into here int _iCachedDrive; // What drive letter is cached in _pszServer? LPTSTR _pszServer; // What server to use (NULL = local machine) TCHAR _szBuiltin[MAX_COMPUTERNAME_LENGTH + 1]; friend HRESULT CFileSysColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv); }; // // _CacheSidName takes ownership of the psd. (psid points into the psd) // void COwnerColumnProvider::_CacheSidName(PSECURITY_DESCRIPTOR psd, void *psid, LPCWSTR pwszName) { LocalFree(_psd); _psd = psd; _psid = psid; Str_SetPtrW(&_pwszName, pwszName); } // // Given a string of the form \\server\share\blah\blah, stomps the // inner backslash (if necessary) and returns a pointer to "server". // STDAPI_(LPTSTR) PathExtractServer(LPTSTR pszUNC) { if (PathIsUNC(pszUNC)) { pszUNC += 2; // Skip over the two leading backslashes LPTSTR pszEnd = StrChr(pszUNC, TEXT('\\')); if (pszEnd) *pszEnd = TEXT('\0'); // nuke the backslash } else { pszUNC = NULL; } return pszUNC; } HRESULT COwnerColumnProvider::_LookupOwnerName(LPCTSTR pszFile, VARIANT *pvar) { pvar->vt = VT_BSTR; pvar->bstrVal = NULL; PSECURITY_DESCRIPTOR psd; void *psid; DWORD err = GetNamedSecurityInfo(const_cast(pszFile), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &psid, NULL, NULL, NULL, &psd); if (err == ERROR_SUCCESS) { if (_psid && EqualSid(psid, _psid) && _pwszName) { pvar->bstrVal = SysAllocString(_pwszName); LocalFree(psd); err = ERROR_SUCCESS; } else { LPTSTR pszServer; TCHAR szServer[MAX_PATH]; // // Now go figure out which server to resolve the SID against. // if (PathIsUNC(pszFile)) { // don't care if it is truncated or not. StringCchCopy(szServer, ARRAYSIZE(szServer), pszFile); pszServer = PathExtractServer(szServer); } else if (pszFile[0] == _iCachedDrive) { // Local drive letter already in cache -- use it pszServer = _pszServer; } else { // Local drive not cached -- cache it _iCachedDrive = pszFile[0]; DWORD cch = ARRAYSIZE(szServer); if (SHWNetGetConnection(pszFile, szServer, &cch) == NO_ERROR) pszServer = PathExtractServer(szServer); else pszServer = NULL; Str_SetPtr(&_pszServer, pszServer); } TCHAR szName[MAX_PATH]; DWORD cchName = ARRAYSIZE(szName); TCHAR szDomain[MAX_COMPUTERNAME_LENGTH + 1]; DWORD cchDomain = ARRAYSIZE(szDomain); SID_NAME_USE snu; LPTSTR pszName; BOOL fFreeName = FALSE; // Do we need to LocalFree(pszName)? if (LookupAccountSid(pszServer, psid, szName, &cchName, szDomain, &cchDomain, &snu)) { // // If the domain is the bogus "BUILTIN" or we don't have a domain // at all, then just use the name. Otherwise, use domain\userid. // if (!szDomain[0] || StrCmpC(szDomain, _szBuiltin) == 0) { pszName = szName; } else { // Borrow szServer as a scratch buffer StringCchPrintf(szServer, ARRAYSIZE(szServer), TEXT("%s\\%s"), szDomain, szName); pszName = szServer; } err = ERROR_SUCCESS; } else { err = GetLastError(); // Couldn't map the SID to a name. Use the horrid raw version // if available. if (ConvertSidToStringSid(psid, &pszName)) { fFreeName = TRUE; err = ERROR_SUCCESS; } else pszName = NULL; } // Even on error, cache the result so we don't keep trying over and over // on the same SID. _CacheSidName(psd, psid, pszName); pvar->bstrVal = SysAllocString(pszName); if (fFreeName) LocalFree(pszName); } } if (err == ERROR_SUCCESS && pvar->bstrVal == NULL) err = ERROR_OUTOFMEMORY; return HRESULT_FROM_WIN32(err); } STDMETHODIMP COwnerColumnProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData) { HRESULT hr = S_FALSE; // return S_FALSE on failure VariantInit(pvarData); if (IsEqualSCID(SCID_OWNER, *pscid)) { hr = _LookupOwnerName(pscd->wszFile, pvarData); } return hr; } STDAPI CFileSysColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv) { HRESULT hr; COwnerColumnProvider *pfcp = new COwnerColumnProvider; if (pfcp) { hr = pfcp->QueryInterface(riid, ppv); pfcp->Release(); } else { *ppv = NULL; hr = E_OUTOFMEMORY; } return hr; } // FMTID_ExeDllInformation, //// {0CEF7D53-FA64-11d1-A203-0000F81FEDEE} #define PSFMTID_VERSION { 0xcef7d53, 0xfa64, 0x11d1, 0xa2, 0x3, 0x0, 0x0, 0xf8, 0x1f, 0xed, 0xee } #define PIDVSI_FileDescription 0x003 #define PIDVSI_FileVersion 0x004 #define PIDVSI_InternalName 0x005 #define PIDVSI_OriginalFileName 0x006 #define PIDVSI_ProductName 0x007 #define PIDVSI_ProductVersion 0x008 // Win32 PE (exe, dll) Version Information column identifier defs... DEFINE_SCID(SCID_FileDescription, PSFMTID_VERSION, PIDVSI_FileDescription); DEFINE_SCID(SCID_FileVersion, PSFMTID_VERSION, PIDVSI_FileVersion); DEFINE_SCID(SCID_InternalName, PSFMTID_VERSION, PIDVSI_InternalName); DEFINE_SCID(SCID_OriginalFileName, PSFMTID_VERSION, PIDVSI_OriginalFileName); DEFINE_SCID(SCID_ProductName, PSFMTID_VERSION, PIDVSI_ProductName); DEFINE_SCID(SCID_ProductVersion, PSFMTID_VERSION, PIDVSI_ProductVersion); const COLUMN_INFO c_rgExeDllColumns[] = { DEFINE_COL_STR_ENTRY(SCID_CompanyName, 30, IDS_VN_COMPANYNAME), DEFINE_COL_STR_ENTRY(SCID_FileDescription, 30, IDS_VN_FILEDESCRIPTION), DEFINE_COL_STR_ENTRY(SCID_FileVersion, 20, IDS_VN_FILEVERSION), DEFINE_COL_STR_MENU_ENTRY(SCID_ProductName, 30, IDS_VN_PRODUCTNAME), DEFINE_COL_STR_MENU_ENTRY(SCID_ProductVersion,20, IDS_VN_PRODUCTVERSION), }; class CVersionColProvider : public CBaseColumnProvider { STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData); private: CVersionColProvider() : CBaseColumnProvider(&CLSID_VersionColProvider, c_rgExeDllColumns, ARRAYSIZE(c_rgExeDllColumns), NULL) { _pvAllTheInfo = NULL; _szFileCache[0] = 0; }; virtual ~CVersionColProvider() { _ClearCache(); } FARPROC _GetVerProc(LPCSTR pszName); HRESULT _CacheFileVerInfo(LPCWSTR pszFile); void _ClearCache(); WCHAR _szFileCache[MAX_PATH]; void *_pvAllTheInfo; HRESULT _hrCache; friend HRESULT CVerColProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv); }; void CVersionColProvider::_ClearCache() { if (_pvAllTheInfo) { delete [] _pvAllTheInfo; _pvAllTheInfo = NULL; } _szFileCache[0] = 0; } HRESULT CVersionColProvider::_CacheFileVerInfo(LPCWSTR pszFile) { if (StrCmpW(_szFileCache, pszFile)) { HRESULT hr; _ClearCache(); DWORD dwVestigial; DWORD versionISize = GetFileVersionInfoSizeW((LPWSTR)pszFile, &dwVestigial); // cast for bad API design if (versionISize) { _pvAllTheInfo = new BYTE[versionISize]; if (_pvAllTheInfo) { // read the data if (GetFileVersionInfoW((LPWSTR)pszFile, dwVestigial, versionISize, _pvAllTheInfo)) { hr = S_OK; } else { _ClearCache(); hr = E_FAIL; } } else hr = E_OUTOFMEMORY; // error, out of memory. } else hr = S_FALSE; // don't care if it is truncated - will only cause a slight perf problem // on network shares. StringCchCopy(_szFileCache, ARRAYSIZE(_szFileCache), pszFile); _hrCache = hr; } return _hrCache; } STDMETHODIMP CVersionColProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData) { VariantInit(pvarData); if (pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) return S_FALSE; HRESULT hr = _CacheFileVerInfo(pscd->wszFile); if (hr != S_OK) return hr; TCHAR szString[128], *pszVersionInfo = NULL; //A pointer to the specific version info I am looking for LPCTSTR pszVersionField = NULL; switch (pscid->pid) { case PIDVSI_FileVersion: { VS_FIXEDFILEINFO *pffi; UINT uInfoSize; if (VerQueryValue(_pvAllTheInfo, TEXT("\\"), (void **)&pffi, &uInfoSize)) { StringCchPrintf(szString, ARRAYSIZE(szString), TEXT("%d.%d.%d.%d"), HIWORD(pffi->dwFileVersionMS), LOWORD(pffi->dwFileVersionMS), HIWORD(pffi->dwFileVersionLS), LOWORD(pffi->dwFileVersionLS)); pszVersionInfo = szString; } else pszVersionField = TEXT("FileVersion"); } break; case PIDDSI_COMPANY: pszVersionField = TEXT("CompanyName"); break; case PIDVSI_FileDescription: pszVersionField = TEXT("FileDescription"); break; case PIDVSI_InternalName: pszVersionField = TEXT("InternalName"); break; case PIDVSI_OriginalFileName: pszVersionField = TEXT("OriginalFileName"); break; case PIDVSI_ProductName: pszVersionField = TEXT("ProductName"); break; case PIDVSI_ProductVersion: pszVersionField = TEXT("ProductVersion"); break; default: return E_FAIL; } //look for the intended language in the examined object. if (pszVersionInfo == NULL) { struct _VERXLATE { WORD wLanguage; WORD wCodePage; } *pxlate; /* ptr to translations data */ //this is a fallthrough set of if statements. //on a failure, it just tries the next one, until it runs out of tries. UINT uInfoSize; if (VerQueryValue(_pvAllTheInfo, TEXT("\\VarFileInfo\\Translation"), (void **)&pxlate, &uInfoSize)) { TCHAR szVersionKey[60]; //a string to hold all the format string for VerQueryValue StringCchPrintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\%04X%04X\\%s"), pxlate[0].wLanguage, pxlate[0].wCodePage, pszVersionField); if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize)) { StringCchPrintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\040904B0\\%s"), pszVersionField); if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize)) { StringCchPrintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\040904E4\\%s"), pszVersionField); if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize)) { StringCchPrintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\04090000\\%s"), pszVersionField); if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize)) { pszVersionInfo = NULL; } } } } } } if (pszVersionInfo) { PathRemoveBlanks(pszVersionInfo); hr = InitVariantFromStr(pvarData, pszVersionInfo); } else { hr = E_FAIL; } return hr; } STDAPI CVerColProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv) { HRESULT hr; CVersionColProvider *pvcp = new CVersionColProvider; if (pvcp) { hr = pvcp->QueryInterface(riid, ppv); pvcp->Release(); } else { *ppv = NULL; hr = E_OUTOFMEMORY; } return hr; }