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.
484 lines
13 KiB
484 lines
13 KiB
#include "stdafx.h"
|
|
#include "sfthost.h"
|
|
#include "uemapp.h"
|
|
#include <desktray.h>
|
|
#include "tray.h"
|
|
#include "rcids.h"
|
|
#include "mfulist.h"
|
|
#define STRSAFE_NO_CB_FUNCTIONS
|
|
#define STRSAFE_NO_DEPRECATE
|
|
#include <strsafe.h>
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Create the initial MFU.
|
|
//
|
|
// Due to the way sysprep works, we cannot do this work in
|
|
// per-user install because "reseal" copies the already-installed user
|
|
// to the default hive, so all new users will bypass per-user install
|
|
// since ActiveSetup thinks that they have already been installed...
|
|
//
|
|
|
|
//
|
|
// We need a parallel list of hard-coded English links so we can get
|
|
// the correct shortcut name on MUI systems.
|
|
//
|
|
|
|
#define MAX_MSMFUENTRIES 16
|
|
|
|
struct MFULIST {
|
|
UINT idsBase;
|
|
LPCTSTR rgpszEnglish[MAX_MSMFUENTRIES];
|
|
};
|
|
|
|
#define MAKEMFU(name) \
|
|
const MFULIST c_mfu##name = { IDS_MFU_##name##_00, { MFU_ENUMC(name) } };
|
|
|
|
#ifdef _WIN64
|
|
MAKEMFU(PRO64ALL)
|
|
MAKEMFU(SRV64ADM)
|
|
|
|
#define c_mfuPROALL c_mfuPRO64ALL
|
|
#define c_mfuSRVADM c_mfuSRV64ADM
|
|
|
|
#else
|
|
MAKEMFU(PRO32ALL)
|
|
MAKEMFU(SRV32ADM)
|
|
|
|
#define c_mfuPROALL c_mfuPRO32ALL
|
|
#define c_mfuSRVADM c_mfuSRV32ADM
|
|
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// _GetPinnedItemTarget
|
|
//
|
|
// Given a pidl, find the executable that will ultimately be launched.
|
|
//
|
|
// This tunnels through shortcuts and resolves the magical "Internet"
|
|
// and "Email" icons to the respective registered programs.
|
|
//
|
|
BOOL _GetPinnedItemTarget(LPCITEMIDLIST pidl, LPTSTR *ppszPath)
|
|
{
|
|
*ppszPath = NULL;
|
|
|
|
IShellFolder *psf;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
|
|
{
|
|
IShellLink *psl;
|
|
IExtractIcon *pxi;
|
|
if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlChild,
|
|
IID_PPV_ARG_NULL(IShellLink, &psl))))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR szPathExpanded[MAX_PATH];
|
|
if (psl->GetPath(szPath, ARRAYSIZE(szPath), 0, SLGP_RAWPATH) == S_OK &&
|
|
SHExpandEnvironmentStrings(szPath, szPathExpanded, ARRAYSIZE(szPathExpanded)))
|
|
{
|
|
SHStrDup(szPathExpanded, ppszPath);
|
|
}
|
|
psl->Release();
|
|
}
|
|
else if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlChild,
|
|
IID_PPV_ARG_NULL(IExtractIcon, &pxi))))
|
|
{
|
|
// There is no way to get the IAssociationElement directly, so
|
|
// we get the IExtractIcon and then ask him for the IAssociationElement.
|
|
IAssociationElement *pae;
|
|
if (SUCCEEDED(IUnknown_QueryService(pxi, IID_IAssociationElement, IID_PPV_ARG(IAssociationElement, &pae))))
|
|
{
|
|
pae->QueryString(AQVS_APPLICATION_PATH, L"open", ppszPath);
|
|
pae->Release();
|
|
}
|
|
pxi->Release();
|
|
}
|
|
psf->Release();
|
|
}
|
|
return *ppszPath != NULL;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// MFUExclusion
|
|
//
|
|
// Keep track of apps that should be excluded from the MFU.
|
|
|
|
class MFUExclusion
|
|
{
|
|
public:
|
|
MFUExclusion();
|
|
~MFUExclusion();
|
|
BOOL IsExcluded(LPCITEMIDLIST pidl) const;
|
|
|
|
private:
|
|
|
|
// worst-case default pin list size
|
|
enum {MAX_EXCLUDED = 3 };
|
|
|
|
PWSTR _rgpszExclude[MAX_EXCLUDED];
|
|
int _cExcluded;
|
|
};
|
|
|
|
MFUExclusion::MFUExclusion() : _cExcluded(0)
|
|
{
|
|
IStartMenuPin *psmpin;
|
|
HRESULT hr;
|
|
|
|
hr = CoCreateInstance(CLSID_StartMenuPin, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IStartMenuPin, &psmpin));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IEnumIDList *penum;
|
|
|
|
if (SUCCEEDED(psmpin->EnumObjects(&penum)))
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
while (_cExcluded < ARRAYSIZE(_rgpszExclude) &&
|
|
penum->Next(1, &pidl, NULL) == S_OK)
|
|
{
|
|
if (_GetPinnedItemTarget(pidl, &_rgpszExclude[_cExcluded]))
|
|
{
|
|
_cExcluded++;
|
|
}
|
|
ILFree(pidl);
|
|
}
|
|
|
|
penum->Release();
|
|
}
|
|
|
|
psmpin->Release();
|
|
}
|
|
}
|
|
|
|
MFUExclusion::~MFUExclusion()
|
|
{
|
|
for (int i = 0; i < _cExcluded; i++)
|
|
{
|
|
SHFree(_rgpszExclude[i]);
|
|
}
|
|
}
|
|
|
|
BOOL MFUExclusion::IsExcluded(LPCITEMIDLIST pidl) const
|
|
{
|
|
BOOL fRc = FALSE;
|
|
LPTSTR pszTarget;
|
|
|
|
if (_GetPinnedItemTarget(pidl, &pszTarget))
|
|
{
|
|
for (int i = 0; i < _cExcluded; i++)
|
|
{
|
|
if (lstrcmpi(_rgpszExclude[i], pszTarget) == 0)
|
|
{
|
|
fRc = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
SHFree(pszTarget);
|
|
}
|
|
return fRc;
|
|
}
|
|
|
|
extern "C" HKEY g_hkeyExplorer;
|
|
void ClearUEMData();
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// MFUEnumerator (and derived OEMMFUEnumerator, MSMFUEnumerator)
|
|
//
|
|
// Enumerate applications to be added to the default MFU.
|
|
|
|
#define MAX_OEMMFUENTRIES 4
|
|
|
|
class MFUEnumerator
|
|
{
|
|
public:
|
|
MFUEnumerator() : _dwIndex(0) { }
|
|
protected:
|
|
DWORD _dwIndex;
|
|
};
|
|
|
|
#define REGSTR_PATH_SMDEN TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\SMDEn")
|
|
|
|
class OEMMFUEnumerator : protected MFUEnumerator
|
|
{
|
|
public:
|
|
OEMMFUEnumerator() : _hk(NULL)
|
|
{
|
|
RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_SMDEN, 0, KEY_READ, &_hk);
|
|
}
|
|
~OEMMFUEnumerator()
|
|
{
|
|
if (_hk)
|
|
{
|
|
RegCloseKey(_hk);
|
|
}
|
|
}
|
|
|
|
LPITEMIDLIST Next(const MFUExclusion *pmex);
|
|
|
|
private:
|
|
HKEY _hk;
|
|
};
|
|
|
|
LPITEMIDLIST OEMMFUEnumerator::Next(const MFUExclusion *pmex)
|
|
{
|
|
if (!_hk)
|
|
{
|
|
return NULL; // No entries at all
|
|
}
|
|
|
|
restart:
|
|
if (_dwIndex >= MAX_OEMMFUENTRIES)
|
|
{
|
|
return NULL; // No more entries
|
|
}
|
|
|
|
TCHAR szKey[20];
|
|
DWORD dwCurrentIndex = _dwIndex++;
|
|
wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("OEM%d"), dwCurrentIndex);
|
|
|
|
TCHAR szPath[MAX_PATH];
|
|
HRESULT hr = SHLoadRegUIStringW(_hk, szKey, szPath, ARRAYSIZE(szPath));
|
|
if (FAILED(hr))
|
|
{
|
|
goto restart;
|
|
}
|
|
|
|
TCHAR szPathExpanded[MAX_PATH];
|
|
SHExpandEnvironmentStrings(szPath, szPathExpanded, ARRAYSIZE(szPathExpanded));
|
|
|
|
LPITEMIDLIST pidl = ILCreateFromPath(szPathExpanded);
|
|
if (!pidl)
|
|
{
|
|
goto restart;
|
|
}
|
|
|
|
if (pmex->IsExcluded(pidl))
|
|
{
|
|
// Excluded - skip it
|
|
ILFree(pidl);
|
|
goto restart;
|
|
}
|
|
|
|
return pidl;
|
|
}
|
|
|
|
class MSMFUEnumerator : protected MFUEnumerator
|
|
{
|
|
public:
|
|
LPITEMIDLIST Next(const MFULIST *pmfu, const MFUExclusion *pmex);
|
|
};
|
|
|
|
LPITEMIDLIST MSMFUEnumerator::Next(const MFULIST *pmfu, const MFUExclusion *pmex)
|
|
{
|
|
restart:
|
|
if (_dwIndex >= MAX_MSMFUENTRIES)
|
|
{
|
|
return NULL; // No more entries
|
|
}
|
|
|
|
DWORD dwCurrentIndex = _dwIndex++;
|
|
|
|
//
|
|
// If this is excluded by policy, then skip it.
|
|
//
|
|
if (StrCmpC(pmfu->rgpszEnglish[dwCurrentIndex], TEXT(MFU_SETDEFAULTS)) == 0 &&
|
|
SHRestricted(REST_NOSMCONFIGUREPROGRAMS))
|
|
{
|
|
goto restart;
|
|
}
|
|
|
|
//
|
|
// If this entry is blank, then skip it.
|
|
//
|
|
TCHAR szPath[MAX_PATH];
|
|
if (!LoadString(_Module.GetModuleInstance(), pmfu->idsBase + dwCurrentIndex,
|
|
szPath, ARRAYSIZE(szPath)))
|
|
{
|
|
goto restart;
|
|
}
|
|
|
|
TCHAR szPathExpanded[MAX_PATH];
|
|
SHExpandEnvironmentStrings(szPath, szPathExpanded, ARRAYSIZE(szPathExpanded));
|
|
|
|
LPITEMIDLIST pidl = ILCreateFromPath(szPathExpanded);
|
|
if (!pidl)
|
|
{
|
|
// Doesn't exist under localized name; try the English name
|
|
SHExpandEnvironmentStrings(pmfu->rgpszEnglish[dwCurrentIndex], szPathExpanded, ARRAYSIZE(szPathExpanded));
|
|
pidl = ILCreateFromPath(szPathExpanded);
|
|
}
|
|
|
|
if (!pidl)
|
|
{
|
|
// Doesn't exist at all - skip it
|
|
goto restart;
|
|
}
|
|
|
|
if (pmex->IsExcluded(pidl))
|
|
{
|
|
// Excluded - skip it
|
|
ILFree(pidl);
|
|
goto restart;
|
|
}
|
|
|
|
return pidl;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void ValidateMFUList(const MFULIST *pmfu)
|
|
{
|
|
for (int i = 0; i < MAX_MSMFUENTRIES; i++)
|
|
{
|
|
TCHAR szBuf[MAX_PATH];
|
|
LoadString(_Module.GetModuleInstance(), pmfu->idsBase + i, szBuf, ARRAYSIZE(szBuf));
|
|
ASSERT(StrCmpC(szBuf, pmfu->rgpszEnglish[i]) == 0);
|
|
}
|
|
}
|
|
|
|
void ValidateInitialMFUTables()
|
|
{
|
|
// If this is the English build, then validate that the resources match
|
|
// the hard-coded table.
|
|
|
|
if (GetUserDefaultUILanguage() == MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US))
|
|
{
|
|
ValidateMFUList(&c_mfuPROALL);
|
|
ValidateMFUList(&c_mfuSRVADM);
|
|
}
|
|
|
|
// The PRO list must contain a copy of MFU_SETDEFAULTS for the
|
|
// policy exclusion code to work.
|
|
BOOL fFound = FALSE;
|
|
for (int i = 0; i < MAX_MSMFUENTRIES; i++)
|
|
{
|
|
if (StrCmpC(c_mfuPROALL.rgpszEnglish[i], TEXT(MFU_SETDEFAULTS)) == 0)
|
|
{
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(fFound);
|
|
}
|
|
#endif
|
|
|
|
void CreateInitialMFU(BOOL fReset)
|
|
{
|
|
#ifdef DEBUG
|
|
ValidateInitialMFUTables();
|
|
#endif
|
|
|
|
HRESULT hrInit = SHCoInitialize();
|
|
|
|
// Delete any dregs left over from "sysprep -reseal".
|
|
// This also prevents OEMs from spamming the pin list.
|
|
SHDeleteKey(g_hkeyExplorer, TEXT("StartPage"));
|
|
SHDeleteValue(g_hkeyExplorer, TEXT("Advanced"), TEXT("StartButtonBalloonTip"));
|
|
|
|
// Start with a clean slate if so requested
|
|
if (fReset)
|
|
{
|
|
ClearUEMData();
|
|
}
|
|
|
|
// Okay now build the default MFU
|
|
{
|
|
// nested scope so MFUExclusion gets destructed before we
|
|
// SHCoUninitialize()
|
|
MFUExclusion mex;
|
|
int iSlot;
|
|
LPITEMIDLIST rgpidlMFU[REGSTR_VAL_DV2_MINMFU_DEFAULT] = { 0 };
|
|
|
|
// Assert that the slots are evenly shared between MSFT and the OEM
|
|
COMPILETIME_ASSERT(ARRAYSIZE(rgpidlMFU) % 2 == 0);
|
|
|
|
// The OEM can provide up to four apps, and we will put as many
|
|
// as fit into into bottom half.
|
|
{
|
|
OEMMFUEnumerator mfuOEM;
|
|
for (iSlot = ARRAYSIZE(rgpidlMFU)/2; iSlot < ARRAYSIZE(rgpidlMFU); iSlot++)
|
|
{
|
|
rgpidlMFU[iSlot] = mfuOEM.Next(&mex);
|
|
}
|
|
}
|
|
|
|
// The top half (and any unused slots in the bottom half)
|
|
// go to MSFT (up to MAX_MSMFUENTRIES MSFT apps); which list
|
|
// we use depends on the SKU and whether we are an administrator.
|
|
const MFULIST *pmfu = NULL;
|
|
|
|
if (IsOS(OS_ANYSERVER))
|
|
{
|
|
// On Server SKUs, only administrators get a default MFU
|
|
// and they get the special server administrator MFU
|
|
if (IsOS(OS_SERVERADMINUI))
|
|
{
|
|
pmfu = &c_mfuSRVADM;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// On Workstation SKUs, everybody gets a default MFU.
|
|
pmfu = &c_mfuPROALL;
|
|
}
|
|
|
|
if (pmfu)
|
|
{
|
|
MSMFUEnumerator mfuMSFT;
|
|
for (iSlot = 0; iSlot < ARRAYSIZE(rgpidlMFU); iSlot++)
|
|
{
|
|
if (!rgpidlMFU[iSlot])
|
|
{
|
|
rgpidlMFU[iSlot] = mfuMSFT.Next(pmfu, &mex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now build up the new MFU given this information
|
|
|
|
UEMINFO uei;
|
|
uei.cbSize = sizeof(uei);
|
|
uei.dwMask = UEIM_HIT | UEIM_FILETIME;
|
|
GetSystemTimeAsFileTime(&uei.ftExecute);
|
|
|
|
// All apps get the same timestamp of "now minus one UEM unit"
|
|
// 1 UEM unit = 1<<30 FILETIME units
|
|
DecrementFILETIME(&uei.ftExecute, 1 << 30);
|
|
|
|
for (iSlot = 0; iSlot < ARRAYSIZE(rgpidlMFU); iSlot++)
|
|
{
|
|
if (!rgpidlMFU[iSlot])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Number of points decrease as you go down the list, with
|
|
// the bottom slot getting 14 points.
|
|
uei.cHit = 14 + ARRAYSIZE(rgpidlMFU) - 1 - iSlot;
|
|
|
|
// Shortcut points are read via UEME_RUNPIDL so that's
|
|
// how we have to set them.
|
|
IShellFolder *psf;
|
|
LPCITEMIDLIST pidlChild;
|
|
if (SUCCEEDED(SHBindToIDListParent(rgpidlMFU[iSlot], IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
|
|
{
|
|
_SetUEMPidlInfo(psf, pidlChild, &uei);
|
|
psf->Release();
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
for (iSlot = 0; iSlot < ARRAYSIZE(rgpidlMFU); iSlot++)
|
|
{
|
|
ILFree(rgpidlMFU[iSlot]);
|
|
}
|
|
|
|
// MFUExclusion destructor runs here
|
|
}
|
|
|
|
SHCoUninitialize(hrInit);
|
|
|
|
}
|