windows-nt/Source/XPSP1/NT/shell/cpls/appwzdui/instapp.cpp
2020-09-26 16:20:57 +08:00

1681 lines
54 KiB
C++

//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation
//
// File: instapp.cpp
//
// Installed applications
//
// History:
// 1-18-97 by dli
//------------------------------------------------------------------------
#include "priv.h"
#include "instapp.h"
#include "sccls.h"
#include "util.h"
#ifndef DOWNLEVEL_PLATFORM
#include "findapp.h"
#endif //DOWNLEVEL_PLATFORM
#include "tasks.h"
#include "slowfind.h"
#include "appsize.h"
#include "appwizid.h"
#include "resource.h"
#include "uemapp.h"
const TCHAR c_szInstall[] = TEXT("Software\\Installer\\Products\\%s");
const TCHAR c_szTSInstallMode[] = TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\Change User Option");
const TCHAR c_szUpdateInfo[] = TEXT("URLUpdateInfo");
const TCHAR c_szSlowInfoCache[] = TEXT("SlowInfoCache");
const TCHAR c_szRegstrARPCache[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\App Management\\ARPCache");
#ifdef WX86
EXTERN_C BOOL bWx86Enabled;
EXTERN_C BOOL bForceX86Env;
#endif
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
#include <tsappcmp.h> // for TermsrvAppInstallMode
#include "scripts.h"
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
#include <winsta.h> // WinStation* APIs
#include <allproc.h> // TS_COUNTER
#include <msginaexports.h> // ShellIsMultipleUsersEnabled, ShellSwitchUser
#define APPACTION_STANDARD (APPACTION_UNINSTALL | APPACTION_MODIFY | APPACTION_REPAIR)
// overloaded constructor (for legacy apps)
CInstalledApp::CInstalledApp(HKEY hkeySub, LPCTSTR pszKeyName, LPCTSTR pszProduct, LPCTSTR pszUninstall, DWORD dwCIA) : _cRef(1), _dwSource(IA_LEGACY), _dwCIA(dwCIA), _guid(GUID_NULL)
{
DWORD dwType;
ULONG cbModify;
LONG lRet;
ASSERT(IS_VALID_HANDLE(hkeySub, KEY));
ASSERT(_bTriedToFindFolder == FALSE);
TraceAddRef(CInstalledApp, _cRef);
DllAddRef();
TraceMsg(TF_INSTAPP, "(CInstalledApp) Legacy App Created key name = %s, product name = %s, uninstall string = %s",
pszKeyName, pszProduct, pszUninstall);
lstrcpy(_szKeyName, pszKeyName);
InsertSpaceBeforeVersion(_szKeyName, _szCleanedKeyName);
lstrcpy(_szProduct, pszProduct);
#ifdef FULL_DEBUG
if (_dwCIA & CIA_ALT)
{
StrCatBuff(_szProduct, TEXT(" (32-bit)"), ARRAYSIZE(_szProduct));
}
#endif
lstrcpy(_szUninstall, pszUninstall);
DWORD dwActionBlocked = _QueryBlockedActions(hkeySub);
if (dwActionBlocked != 0)
{
// NoRemove, NoModify, or NoRepair has been specified
_dwAction |= APPACTION_STANDARD & (~dwActionBlocked);
}
else
{
// Start with the basics. For legacy apps, we assume they don't distinguish between
// modify and remove functions.
_dwAction |= APPACTION_MODIFYREMOVE;
}
// If there is no "uninstall" key, we could try to find other hints as where
// this app lives, if we could find that hint, as the uninstall process, we could
// just delete that directory and the registry entry.
// What if we find no hints at all? Should we just delete this thing from the
// registry?
if (!(dwActionBlocked & APPACTION_UNINSTALL) && _szUninstall[0])
_dwAction |= APPACTION_UNINSTALL;
// Does this app have an explicit modify path?
cbModify = SIZEOF(_szModifyPath);
lRet = SHQueryValueEx(hkeySub, TEXT("ModifyPath"), 0, &dwType, (PBYTE)_szModifyPath, &cbModify);
if ((ERROR_SUCCESS == lRet) && (TEXT('\0') != _szModifyPath[0]))
{
// Yes; remove the legacy modify/remove combination.
_dwAction &= ~APPACTION_MODIFYREMOVE;
// Does policy prevent this?
if (!(dwActionBlocked & APPACTION_MODIFY))
_dwAction |= APPACTION_MODIFY; // No
}
_GetInstallLocationFromRegistry(hkeySub);
_GetUpdateUrl();
RegCloseKey(hkeySub);
}
// overloaded constructor (for darwin apps)
CInstalledApp::CInstalledApp(LPTSTR pszProductID) : _cRef(1), _dwSource(IA_DARWIN), _guid(GUID_NULL)
{
ASSERT(_bTriedToFindFolder == FALSE);
TraceAddRef(CInstalledApp, _cRef);
DllAddRef();
TraceMsg(TF_INSTAPP, "(CInstalledApp) Darwin app created product name = %s", pszProductID);
lstrcpy(_szProductID, pszProductID);
// Get the information from the ProductId
ULONG cchProduct = ARRAYSIZE(_szProduct);
MsiGetProductInfo(pszProductID, INSTALLPROPERTY_PRODUCTNAME, _szProduct, &cchProduct);
BOOL bMachineAssigned = FALSE;
// For Machine Assigned Darwin Apps, only admins should be allowed
// to modify the app
if (!IsUserAnAdmin())
{
TCHAR szAT[5];
DWORD cchAT = ARRAYSIZE(szAT);
// NOTE: according to chetanp, the first character of szAT should be "0" or "1"
// '0' means it's user assigned, '1' means it's machine assigned
if ((ERROR_SUCCESS == MsiGetProductInfo(pszProductID, INSTALLPROPERTY_ASSIGNMENTTYPE,
szAT, &cchAT))
&& (szAT[0] == TEXT('1')))
bMachineAssigned = TRUE;
}
// Query the install state and separate the cases where this app is
// installed on the machine or assigned...
// In the assigned case we allow only Uninstall operation.
if (INSTALLSTATE_ADVERTISED == MsiQueryProductState(pszProductID))
{
_dwAction |= APPACTION_UNINSTALL;
}
else
{
DWORD dwActionBlocked = 0;
HKEY hkeySub = _OpenUninstallRegKey(KEY_READ);
if (hkeySub)
{
dwActionBlocked = _QueryBlockedActions(hkeySub);
_GetInstallLocationFromRegistry(hkeySub);
RegCloseKey(hkeySub);
if (bMachineAssigned)
_dwAction |= APPACTION_REPAIR & (~dwActionBlocked);
else
{
_dwAction |= APPACTION_STANDARD & (~dwActionBlocked);
_GetUpdateUrl();
}
}
}
}
// destructor
CInstalledApp::~CInstalledApp()
{
if (_pszUpdateUrl)
{
ASSERT(_dwSource & IA_DARWIN);
LocalFree(_pszUpdateUrl);
}
DllRelease();
}
// The UpdateUrl info is optional for both Darwin and Legacy apps.
void CInstalledApp::_GetUpdateUrl()
{
TCHAR szInstall[MAX_PATH];
HKEY hkeyInstall;
wnsprintf(szInstall, ARRAYSIZE(szInstall), c_szInstall, _szProductID);
if (RegOpenKeyEx(_MyHkeyRoot(), szInstall, 0, KEY_READ, &hkeyInstall) == ERROR_SUCCESS)
{
ULONG cbUrl;
if (SHQueryValueEx(hkeyInstall, c_szUpdateInfo, NULL, NULL, NULL, &cbUrl) == ERROR_SUCCESS)
{
_pszUpdateUrl = (LPTSTR) LocalAlloc(LPTR, cbUrl);
if (ERROR_SUCCESS != SHQueryValueEx(hkeyInstall, TEXT(""), NULL, NULL, (PBYTE)_pszUpdateUrl, &cbUrl))
{
LocalFree(_pszUpdateUrl);
_pszUpdateUrl = NULL;
}
else
_dwAction |= APPACTION_UPGRADE;
}
RegCloseKey(hkeyInstall);
}
}
// Queries policy restrictions on the action info
DWORD CInstalledApp::_QueryActionBlockInfo(HKEY hkey)
{
DWORD dwRet = 0;
DWORD dwType = 0;
DWORD dwData = 0;
ULONG cbData = SIZEOF(dwData);
if ((ERROR_SUCCESS == SHQueryValueEx(hkey, TEXT("NoRemove"), 0, &dwType, (PBYTE)&dwData, &cbData))
&& (dwType == REG_DWORD) && (dwData == 1))
dwRet |= APPACTION_UNINSTALL;
if ((ERROR_SUCCESS == SHQueryValueEx(hkey, TEXT("NoModify"), 0, &dwType, (PBYTE)&dwData, &cbData))
&& (dwType == REG_DWORD) && (dwData == 1))
dwRet |= APPACTION_MODIFY;
if ((ERROR_SUCCESS == SHQueryValueEx(hkey, TEXT("NoRepair"), 0, &dwType, (PBYTE)&dwData, &cbData))
&& (dwType == REG_DWORD) && (dwData == 1))
dwRet |= APPACTION_REPAIR;
return dwRet;
}
DWORD CInstalledApp::_QueryBlockedActions(HKEY hkey)
{
DWORD dwRet = _QueryActionBlockInfo(hkey);
if (dwRet != APPACTION_STANDARD)
{
HKEY hkeyPolicy = _OpenRelatedRegKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"), KEY_READ, FALSE);
if (hkeyPolicy)
{
dwRet |= _QueryActionBlockInfo(hkeyPolicy);
RegCloseKey(hkeyPolicy);
}
}
return dwRet;
}
void CInstalledApp::_GetInstallLocationFromRegistry(HKEY hkeySub)
{
DWORD dwType;
ULONG cbInstallLocation = SIZEOF(_szInstallLocation);
LONG lRet = SHQueryValueEx(hkeySub, TEXT("InstallLocation"), 0, &dwType, (PBYTE)_szInstallLocation, &cbInstallLocation);
PathUnquoteSpaces(_szInstallLocation);
if (lRet == ERROR_SUCCESS)
{
ASSERT(IS_VALID_STRING_PTR(_szInstallLocation, -1));
_dwAction |= APPACTION_CANGETSIZE;
}
}
HKEY CInstalledApp::_OpenRelatedRegKey(HKEY hkey, LPCTSTR pszRegLoc, REGSAM samDesired, BOOL bCreate)
{
HKEY hkeySub = NULL;
LONG lRet;
TCHAR szRegKey[MAX_PATH];
RIP (pszRegLoc);
// For Darwin apps, use the ProductID as the key name
LPTSTR pszKeyName = (_dwSource & IA_DARWIN) ? _szProductID : _szKeyName;
wnsprintf(szRegKey, ARRAYSIZE(szRegKey), TEXT("%s\\%s"), pszRegLoc, pszKeyName, ARRAYSIZE(szRegKey));
// Open this key in the registry
lRet = RegOpenKeyEx(hkey, szRegKey, 0, samDesired, &hkeySub);
if (bCreate && (lRet == ERROR_FILE_NOT_FOUND))
{
lRet = RegCreateKeyEx(hkey, szRegKey, 0, NULL, REG_OPTION_NON_VOLATILE, samDesired,
NULL, &hkeySub, NULL);
}
if (lRet != ERROR_SUCCESS)
hkeySub = NULL;
return hkeySub;
}
HKEY CInstalledApp::_OpenUninstallRegKey(REGSAM samDesired)
{
LPCTSTR pszSubkey = (_dwCIA & CIA_ALT) ? REGSTR_PATH_ALTUNINSTALL : REGSTR_PATH_UNINSTALL;
return _OpenRelatedRegKey(_MyHkeyRoot(), pszSubkey, samDesired, FALSE);
}
// Helper function to query the registry for legacy app info strings
LPWSTR CInstalledApp::_GetLegacyInfoString(HKEY hkeySub, LPTSTR pszInfoName)
{
DWORD cbSize;
DWORD dwType;
LPWSTR pwszInfo = NULL;
if (SHQueryValueEx(hkeySub, pszInfoName, 0, &dwType, NULL, &cbSize) == ERROR_SUCCESS)
{
LPTSTR pszInfoT = (LPTSTR)LocalAlloc(LPTR, cbSize);
if (pszInfoT && (SHQueryValueEx(hkeySub, pszInfoName, 0, &dwType, (PBYTE)pszInfoT, &cbSize) == ERROR_SUCCESS))
{
if ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))
{
if (FAILED(SHStrDup(pszInfoT, &pwszInfo)))
{
ASSERT(pwszInfo == NULL);
}
// For the "DisplayIcon" case, we need to make sure the path of
// the icon actually exists.
if (pwszInfo && !lstrcmp(pszInfoName, TEXT("DisplayIcon")))
{
PathParseIconLocation(pszInfoT);
if (!PathFileExists(pszInfoT))
{
SHFree(pwszInfo);
pwszInfo = NULL;
}
}
}
LocalFree(pszInfoT);
}
}
return pwszInfo;
}
// IShellApps::GetAppInfo
STDMETHODIMP CInstalledApp::GetAppInfo(PAPPINFODATA pai)
{
ASSERT(pai);
if (pai->cbSize != SIZEOF(APPINFODATA))
return E_FAIL;
DWORD dwInfoFlags = pai->dwMask;
pai->dwMask = 0;
// We cache the product name in all cases(Legacy, Darwin, SMS).
if (dwInfoFlags & AIM_DISPLAYNAME)
{
if (SUCCEEDED(SHStrDup(_szProduct, &pai->pszDisplayName)))
pai->dwMask |= AIM_DISPLAYNAME;
}
if (dwInfoFlags & ~AIM_DISPLAYNAME)
{
HKEY hkeySub = _OpenUninstallRegKey(KEY_READ);
if (hkeySub != NULL)
{
const static struct {
DWORD dwBit;
LPTSTR szRegText;
DWORD ibOffset;
} s_rgInitAppInfo[] = {
//
// WARNING: If you add a new field that is not an LPWSTR type,
// revisit the loop below. It only knows about LPWSTR.
//
{AIM_VERSION, TEXT("DisplayVersion"), FIELD_OFFSET(APPINFODATA, pszVersion) },
{AIM_PUBLISHER, TEXT("Publisher"), FIELD_OFFSET(APPINFODATA, pszPublisher) },
{AIM_PRODUCTID, TEXT("ProductID"), FIELD_OFFSET(APPINFODATA, pszProductID) },
{AIM_REGISTEREDOWNER, TEXT("RegOwner"), FIELD_OFFSET(APPINFODATA, pszRegisteredOwner) },
{AIM_REGISTEREDCOMPANY, TEXT("RegCompany"), FIELD_OFFSET(APPINFODATA, pszRegisteredCompany) },
{AIM_SUPPORTURL, TEXT("UrlInfoAbout"), FIELD_OFFSET(APPINFODATA, pszSupportUrl) },
{AIM_SUPPORTTELEPHONE,TEXT("HelpTelephone"), FIELD_OFFSET(APPINFODATA, pszSupportTelephone) },
{AIM_HELPLINK, TEXT("HelpLink"), FIELD_OFFSET(APPINFODATA, pszHelpLink) },
{AIM_INSTALLLOCATION, TEXT("InstallLocation"), FIELD_OFFSET(APPINFODATA, pszInstallLocation) },
{AIM_INSTALLSOURCE, TEXT("InstallSource"), FIELD_OFFSET(APPINFODATA, pszInstallSource) },
{AIM_INSTALLDATE, TEXT("InstallDate"), FIELD_OFFSET(APPINFODATA, pszInstallDate) },
{AIM_CONTACT, TEXT("Contact"), FIELD_OFFSET(APPINFODATA, pszContact) },
{AIM_COMMENTS, TEXT("Comments"), FIELD_OFFSET(APPINFODATA, pszComments) },
{AIM_IMAGE, TEXT("DisplayIcon"), FIELD_OFFSET(APPINFODATA, pszImage) },
{AIM_READMEURL, TEXT("Readme"), FIELD_OFFSET(APPINFODATA, pszReadmeUrl) },
{AIM_UPDATEINFOURL, TEXT("UrlUpdateInfo"), FIELD_OFFSET(APPINFODATA, pszUpdateInfoUrl) },
};
ASSERT(IS_VALID_HANDLE(hkeySub, KEY));
int i;
for (i = 0; i < ARRAYSIZE(s_rgInitAppInfo); i++)
{
if (dwInfoFlags & s_rgInitAppInfo[i].dwBit)
{
LPWSTR pszInfo = _GetLegacyInfoString(hkeySub, s_rgInitAppInfo[i].szRegText);
if (pszInfo)
{
// We are assuming each field is a LPWSTR.
LPBYTE pbField = (LPBYTE)pai + s_rgInitAppInfo[i].ibOffset;
pai->dwMask |= s_rgInitAppInfo[i].dwBit;
*(LPWSTR *)pbField = pszInfo;
}
}
}
// If we want a image path but did not get it, and we are a darwin app
if ((dwInfoFlags & AIM_IMAGE) && !(pai->dwMask & AIM_IMAGE) && (_dwSource & IA_DARWIN))
{
TCHAR szProductIcon[MAX_PATH*2];
DWORD cchProductIcon = ARRAYSIZE(szProductIcon);
// Okay, call Darwin to get the image
if ((ERROR_SUCCESS == MsiGetProductInfo(_szProductID, INSTALLPROPERTY_PRODUCTICON, szProductIcon, &cchProductIcon))
&& szProductIcon[0])
{
// Expand any embedded environment strings while copying
// to return buffer.
TCHAR szTemp[1];
int cchExp = ExpandEnvironmentStrings(szProductIcon, szTemp, ARRAYSIZE(szTemp));
pai->pszImage = (TCHAR *)CoTaskMemAlloc(cchExp * sizeof(TCHAR));
if (NULL != pai->pszImage)
{
ExpandEnvironmentStrings(szProductIcon, pai->pszImage, cchExp);
pai->dwMask |= AIM_IMAGE;
}
}
}
RegCloseKey(hkeySub);
}
}
#ifndef DOWNLEVEL_PLATFORM
// Software installation policy settings can override the default display name
// and help link url which are authored into a windows installer package.
if ( (_dwSource & IA_DARWIN) && (dwInfoFlags & (AIM_DISPLAYNAME | AIM_HELPLINK)) )
{
LPWSTR pwszDisplayName = 0;
LPWSTR pwszSupportUrl = 0;
GetLocalManagedApplicationData( _szProductID, &pwszDisplayName, &pwszSupportUrl );
if ( pwszDisplayName && (dwInfoFlags & AIM_DISPLAYNAME) )
{
LPWSTR pwszNewDisplayName;
if ( SUCCEEDED(SHStrDup(pwszDisplayName, &pwszNewDisplayName)) )
{
if ( pai->dwMask & AIM_DISPLAYNAME )
SHFree( pai->pszDisplayName );
pai->pszDisplayName = pwszNewDisplayName;
pai->dwMask |= AIM_DISPLAYNAME;
}
}
if ( pwszSupportUrl && (dwInfoFlags & AIM_HELPLINK) )
{
LPWSTR pwszNewHelpLink;
if ( SUCCEEDED(SHStrDup(pwszSupportUrl, &pwszNewHelpLink)) )
{
if ( pai->dwMask & AIM_HELPLINK )
SHFree( pai->pszHelpLink );
pai->pszHelpLink = pwszNewHelpLink;
pai->dwMask |= AIM_HELPLINK;
}
}
LocalFree( pwszDisplayName );
LocalFree( pwszSupportUrl );
}
#endif // DOWNLEVEL_PLATFORM
TraceMsg(TF_INSTAPP, "(CInstalledApp) GetAppInfo with %x but got %x", dwInfoFlags, pai->dwMask);
return S_OK;
}
// IShellApps::GetPossibleActions
STDMETHODIMP CInstalledApp::GetPossibleActions(DWORD * pdwActions)
{
ASSERT(IS_VALID_WRITE_PTR(pdwActions, DWORD));
*pdwActions = _dwAction;
return S_OK;
}
#ifndef DOWNLEVEL_PLATFORM
/*-------------------------------------------------------------------------
Purpose: This method finds the application folder for this app. If a
possible folder is found, it is stored in the _szInstallLocation
member variable.
Returns TRUE if a possible path is found.
*/
BOOL CInstalledApp::_FindAppFolderFromStrings()
{
TraceMsg(TF_INSTAPP, "(CInstalledApp) FindAppFolderFromStrings ---- %s %s %s %s",
_szProduct, _szCleanedKeyName, _szUninstall, _szModifyPath);
// Try to determine from the "installlocation", "uninstall", or "modify"
// regvalues.
// Say we have tried
_bTriedToFindFolder = TRUE;
// First try out the location string, this is most likely to give us some thing
// and probably is the correct location for logo 5 apps.
if (_dwAction & APPACTION_CANGETSIZE)
{
if (!IsValidAppFolderLocation(_szInstallLocation))
{
// We got bad location string from the registry, set it to empty string
_dwAction &= ~APPACTION_CANGETSIZE;
_szInstallLocation[0] = 0;
}
else
// The string from the registry is fine
return TRUE;
}
// We didn't have a location string or failed to get anything from it.
// logo 3 apps are typically this case...
LPTSTR pszShortName = (_dwSource & IA_LEGACY) ? _szCleanedKeyName : NULL;
TCHAR szFolder[MAX_PATH];
// Let's take a look at the uninstall string, 2nd most likely to give hints
if ((_dwAction & APPACTION_UNINSTALL) &&
(ParseInfoString(_szUninstall, _szProduct, pszShortName, szFolder)))
{
// remember this string and set the Action bit to get size
lstrcpy(_szInstallLocation, szFolder);
_dwAction |= APPACTION_CANGETSIZE;
return TRUE;
}
// Now try the modify string
if ((_dwAction & APPACTION_MODIFY) &&
(ParseInfoString(_szModifyPath, _szProduct, pszShortName, szFolder)))
{
// remember this string and set the Action bit to get size
lstrcpy(_szInstallLocation, szFolder);
_dwAction |= APPACTION_CANGETSIZE;
return TRUE;
}
return FALSE;
}
/*-------------------------------------------------------------------------
Purpose: Persists the slow app info under the "uninstall" key in the registry
EX: HKLM\\...\\Uninstall\\Word\\ARPCache
Returns S_OK if successfully saved it to the registry
E_FAIL if failed.
*/
HRESULT CInstalledApp::_PersistSlowAppInfo(PSLOWAPPINFO psai)
{
HRESULT hres = E_FAIL;
ASSERT(psai);
HKEY hkeyARPCache = _OpenRelatedRegKey(_MyHkeyRoot(), c_szRegstrARPCache, KEY_SET_VALUE, TRUE);
if (hkeyARPCache)
{
PERSISTSLOWINFO psi = {0};
DWORD dwType = 0;
DWORD cbSize = SIZEOF(psi);
// Read in the old cached info, and try to preserve the DisplayIcon path
// Note if the PERSISTSLOWINFO structure is not what we are looking for, we
// ignore the old icon path.
if ((ERROR_SUCCESS != RegQueryValueEx(hkeyARPCache, c_szSlowInfoCache, 0, &dwType, (LPBYTE)&psi, &cbSize))
|| (psi.dwSize != SIZEOF(psi)))
ZeroMemory(&psi, SIZEOF(psi));
psi.dwSize = SIZEOF(psi);
psi.ullSize = psai->ullSize;
psi.ftLastUsed = psai->ftLastUsed;
psi.iTimesUsed = psai->iTimesUsed;
if (!(psi.dwMasks & PERSISTSLOWINFO_IMAGE) && psai->pszImage && psai->pszImage[0])
{
psi.dwMasks |= PERSISTSLOWINFO_IMAGE;
StrCpy(psi.szImage, psai->pszImage);
}
if (RegSetValueEx(hkeyARPCache, c_szSlowInfoCache, 0, REG_BINARY, (LPBYTE)&psi, sizeof(psi)) == ERROR_SUCCESS)
hres = S_OK;
_SetSlowAppInfoChanged(hkeyARPCache, 0);
RegCloseKey(hkeyARPCache);
}
return hres;
}
#endif //DOWNLEVEL_PLATFORM
/*-------------------------------------------------------------------------
Purpose: _SetSlowAppInfoChanged
Set in the registry that this app has been changed.
*/
HRESULT CInstalledApp::_SetSlowAppInfoChanged(HKEY hkeyARPCache, DWORD dwValue)
{
HRESULT hres = E_FAIL;
BOOL bNewKey = FALSE;
if (!hkeyARPCache)
{
hkeyARPCache = _OpenRelatedRegKey(_MyHkeyRoot(), c_szRegstrARPCache, KEY_READ, FALSE);
if (hkeyARPCache)
bNewKey = TRUE;
}
if (hkeyARPCache)
{
if (ERROR_SUCCESS == RegSetValueEx(hkeyARPCache, TEXT("Changed"), 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(dwValue)))
hres = S_OK;
if (bNewKey)
RegCloseKey(hkeyARPCache);
}
return hres;
}
// IShellApps::GetSlowAppInfo
/*-------------------------------------------------------------------------
Purpose: IShellApps::_IsSlowAppInfoChanged
Retrieve whether the slow app info has been changed from the registry
*/
HRESULT CInstalledApp::_IsSlowAppInfoChanged()
{
HRESULT hres = S_FALSE;
HKEY hkeyARPCache = _OpenRelatedRegKey(_MyHkeyRoot(), c_szRegstrARPCache, KEY_READ, FALSE);
if (hkeyARPCache)
{
DWORD dwValue;
DWORD dwType;
DWORD cbSize = SIZEOF(dwValue);
if (ERROR_SUCCESS == SHQueryValueEx(hkeyARPCache, TEXT("Changed"), 0, &dwType, &dwValue, &cbSize)
&& (dwType == REG_DWORD) && (dwValue == 1))
hres = S_OK;
RegCloseKey(hkeyARPCache);
}
else
hres = S_OK;
return hres;
}
BOOL CInstalledApp::_GetDarwinAppSize(ULONGLONG * pullTotal)
{
BOOL bRet = FALSE;
HKEY hkeySub = _OpenUninstallRegKey(KEY_READ);
RIP(pullTotal);
*pullTotal = 0;
if (hkeySub)
{
DWORD dwSize = 0;
DWORD dwType = 0;
DWORD cbSize = SIZEOF(dwSize);
if (ERROR_SUCCESS == SHQueryValueEx(hkeySub, TEXT("EstimatedSize"), 0, &dwType, &dwSize, &cbSize)
&& (dwType == REG_DWORD))
{
// NOTE: EstimatedSize is in "kb"
*pullTotal = dwSize * 1024;
bRet = TRUE;
}
RegCloseKey(hkeySub);
}
return bRet;
}
// IShellApps::GetSlowAppInfo
/*-------------------------------------------------------------------------
Purpose: IShellApps::GetSlowAppInfo
Gets the appinfo that may take awhile. This includes the amount
of diskspace that the app might take up, etc.
Returns S_OK if some valid info was obtained. S_FALSE is returned
if nothing useful was found. Errors may be returned as well.
*/
STDMETHODIMP CInstalledApp::GetSlowAppInfo(PSLOWAPPINFO psai)
{
#ifndef DOWNLEVEL_PLATFORM
HRESULT hres = E_INVALIDARG;
if (psai)
{
// Is this an app that we know we can't get info for?
// In this case this is a darwin app that has not changed
BOOL bFoundFolder = FALSE;
LPCTSTR pszShortName = NULL;
BOOL bSlowAppInfoChanged = (S_OK == _IsSlowAppInfoChanged());
// Nothing should have changed except for the usage info, so get the cached one first
if (FAILED(GetCachedSlowAppInfo(psai)))
{
ZeroMemory(psai, sizeof(*psai));
psai->iTimesUsed = -1;
psai->ullSize = (ULONGLONG) -1;
}
// No; have we tried to determine this app's installation location?
switch (_dwSource) {
case IA_LEGACY:
{
if (!_bTriedToFindFolder)
{
// No; try to find out now
BOOL bRet = _FindAppFolderFromStrings();
if (bRet)
TraceMsg(TF_ALWAYS, "(CInstalledApp) App Folder Found %s --- %s", _szProduct, _szInstallLocation);
else
{
ASSERT(!(_dwAction & APPACTION_CANGETSIZE));
ASSERT(_szInstallLocation[0] == 0);
}
}
pszShortName = _szCleanedKeyName;
bFoundFolder = _dwAction & APPACTION_CANGETSIZE;
if (!bFoundFolder)
bFoundFolder = SlowFindAppFolder(_szProduct, pszShortName, _szInstallLocation);
}
break;
case IA_DARWIN:
{
if (bSlowAppInfoChanged)
{
// Can we get the Darwin app size?
if (!_GetDarwinAppSize(&psai->ullSize))
// No, let's set it back to the default value
psai->ullSize = (ULONGLONG) -1;
}
// Get the "times used" info from UEM
UEMINFO uei = {0};
uei.cbSize = SIZEOF(uei);
uei.dwMask = UEIM_HIT | UEIM_FILETIME;
if(SUCCEEDED(UEMQueryEvent(&UEMIID_SHELL, UEME_RUNPATH, (WPARAM)-1, (LPARAM)_szProductID, &uei)))
{
// Is there a change to the times used?
if (uei.cHit > psai->iTimesUsed)
{
// Yes, then overwrite the times used field
psai->iTimesUsed = uei.cHit;
}
if (CompareFileTime(&(uei.ftExecute), &psai->ftLastUsed) > 0)
psai->ftLastUsed = uei.ftExecute;
}
}
break;
default:
break;
}
LPCTSTR pszInstallLocation = bFoundFolder ? _szInstallLocation : NULL;
hres = FindAppInfo(pszInstallLocation, _szProduct, pszShortName, psai, bSlowAppInfoChanged);
_PersistSlowAppInfo(psai);
}
#else
HRESULT hres = E_NOTIMPL;
#endif //DOWNLEVEL_PLATFORM
return hres;
}
// IShellApps::GetCachedSlowAppInfo
/*-------------------------------------------------------------------------
Purpose: IShellApps::GetCachedSlowAppInfo
Gets the cached appinfo, to get the real info might take a while
Returns S_OK if some valid info was obtained.
Returns E_FAIL if can't find the cached info.
*/
STDMETHODIMP CInstalledApp::GetCachedSlowAppInfo(PSLOWAPPINFO psai)
{
#ifndef DOWNLEVEL_PLATFORM
HRESULT hres = E_FAIL;
if (psai)
{
ZeroMemory(psai, sizeof(*psai));
HKEY hkeyARPCache = _OpenRelatedRegKey(_MyHkeyRoot(), c_szRegstrARPCache, KEY_READ, FALSE);
if (hkeyARPCache)
{
PERSISTSLOWINFO psi = {0};
DWORD dwType;
DWORD cbSize = SIZEOF(psi);
if ((RegQueryValueEx(hkeyARPCache, c_szSlowInfoCache, 0, &dwType, (LPBYTE)&psi, &cbSize) == ERROR_SUCCESS)
&& (psi.dwSize == SIZEOF(psi)))
{
psai->ullSize = psi.ullSize;
psai->ftLastUsed = psi.ftLastUsed;
psai->iTimesUsed = psi.iTimesUsed;
if (psi.dwMasks & PERSISTSLOWINFO_IMAGE)
SHStrDupW(psi.szImage, &psai->pszImage);
hres = S_OK;
}
RegCloseKey(hkeyARPCache);
}
}
#else
HRESULT hres = E_NOTIMPL;
#endif //DOWNLEVEL_PLATFORM
return hres;
}
// IShellApp::IsInstalled
STDMETHODIMP CInstalledApp::IsInstalled()
{
HRESULT hres = S_FALSE;
switch (_dwSource)
{
case IA_LEGACY:
{
// First Let's see if the reg key is still there
HKEY hkey = _OpenUninstallRegKey(KEY_READ);
if (hkey)
{
// Second we check the "DisplayName" and the "UninstallString"
LPWSTR pszName = _GetLegacyInfoString(hkey, REGSTR_VAL_UNINSTALLER_DISPLAYNAME);
if (pszName)
{
if (pszName[0])
{
LPWSTR pszUninstall = _GetLegacyInfoString(hkey, REGSTR_VAL_UNINSTALLER_COMMANDLINE);
if (pszUninstall)
{
if (pszUninstall[0])
hres = S_OK;
SHFree(pszUninstall);
}
}
SHFree(pszName);
}
RegCloseKey(hkey);
}
}
break;
case IA_DARWIN:
if (MsiQueryProductState(_szProductID) == INSTALLSTATE_DEFAULT)
hres = S_OK;
break;
case IA_SMS:
break;
default:
break;
}
return hres;
}
#ifdef DOWNLEVEL_PLATFORM
STDAPI_(BOOL) Old_CreateAndWaitForProcess(LPTSTR pszExeName)
{
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
BOOL fWorked = FALSE;
#ifdef WX86
DWORD cchArch;
WCHAR szArchValue[32];
#endif
DWORD dwCreationFlags = 0;
// Create the install process
si.cb = sizeof(si);
#ifdef WX86
if (bWx86Enabled && bForceX86Env) {
cchArch = GetEnvironmentVariableW(ProcArchName,
szArchValue,
sizeof(szArchValue)
);
if (!cchArch || cchArch >= sizeof(szArchValue)) {
szArchValue[0]=L'\0';
}
SetEnvironmentVariableW(ProcArchName, L"x86");
}
#endif
// Create the process
fWorked = CreateProcess(NULL, pszExeName, NULL, NULL, FALSE, dwCreationFlags, NULL, NULL,
&si, &pi);
if (fWorked)
{
//
// Wait for the install to finish.
//
HANDLE rghWait[1];
rghWait[0] = pi.hProcess;
DWORD dwWaitRet;
#ifdef WX86
if (ForceWx86) {
SetEnvironmentVariableW(ProcArchName, ProcArchValue);
}
#endif
do {
dwWaitRet = MsgWaitForMultipleObjects(1, rghWait, FALSE, INFINITE, QS_ALLINPUT);
if (dwWaitRet == WAIT_OBJECT_0 + 1) {
// block-local variable
MSG msg ;
// read all of the messages in this next loop
// removing each message as we read it
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} // end of PeekMessage while loop
}
} while ((dwWaitRet != WAIT_OBJECT_0) && (dwWaitRet != WAIT_ABANDONED_0));
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
return fWorked;
}
#endif // DOWNLEVEL_PLATFORM
/*-------------------------------------------------------------------------
Purpose: Creates a process and waits for it to finish
*/
STDAPI_(BOOL) CreateAndWaitForProcess(LPTSTR pszExeName)
{
#if defined(WINNT) && !defined(DOWNLEVEL_PLATFORM)
return NT5_CreateAndWaitForProcess(pszExeName);
#else
return Old_CreateAndWaitForProcess(pszExeName);
#endif
}
// Returns FALSE if "pszPath" contains a network app that can not be accessed
// TRUE for all other pathes
BOOL PathIsNetAndCreatable(LPCTSTR pszPath, LPTSTR pszErrExe, UINT cchErrExe)
{
ASSERT(IS_VALID_STRING_PTR(pszPath, -1));
BOOL bRet = TRUE;
TCHAR szExe[MAX_PATH];
lstrcpyn(szExe, pszPath, ARRAYSIZE(szExe));
LPTSTR pSpace = PathGetArgs(szExe);
if (pSpace)
*pSpace = 0;
if (!PathIsLocalAndFixed(szExe))
bRet = PathFileExists(szExe);
if (!bRet)
lstrcpyn(pszErrExe, szExe, cchErrExe);
return bRet;
}
EXTERN_C BOOL BrowseForExe(HWND hwnd, LPTSTR pszName, DWORD cchName,
LPCTSTR pszInitDir);
/*--------------------------------------------------------------------------*
*--------------------------------------------------------------------------*/
BOOL_PTR CALLBACK NewUninstallProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
LPTSTR pszExe = (LPTSTR) GetWindowLongPtr(hDlg, DWLP_USER);
switch (msg)
{
case WM_INITDIALOG:
RIP (lp);
if (lp != NULL)
{
pszExe = (LPTSTR)lp;
SetWindowText(GetDlgItem(hDlg, IDC_TEXT), pszExe);
pszExe[0] = 0;
SetWindowLongPtr(hDlg, DWLP_USER, lp);
}
else
EndDialog(hDlg, -1);
break;
case WM_COMMAND:
ASSERT(pszExe);
RIP (lp);
switch (GET_WM_COMMAND_ID(wp, lp))
{
case IDC_BROWSE:
if (BrowseForExe(hDlg, pszExe, MAX_PATH, NULL))
Edit_SetText(GetDlgItem(hDlg, IDC_COMMAND), pszExe);
break;
case IDOK:
// NOTE: we are assuming the size of the buffer is at least MAX_PATH
GetDlgItemText(hDlg, IDC_COMMAND, pszExe, MAX_PATH);
case IDCANCEL:
EndDialog(hDlg, (GET_WM_COMMAND_ID(wp, lp) == IDOK));
break;
default:
return FALSE;
}
break;
default:
return FALSE;
}
return TRUE;
}
// Assumes pszExePath is of size MAX_PATH
int GetNewUninstallProgram(HWND hwndParent, LPTSTR pszExePath, DWORD cchExePath)
{
int iRet = 0;
RIP(pszExePath);
if (cchExePath >= MAX_PATH)
{
iRet = (int)DialogBoxParam(g_hinst, MAKEINTRESOURCE(DLG_UNCUNINSTALLBROWSE),
hwndParent, NewUninstallProc, (LPARAM)(int *)pszExePath);
}
return iRet;
}
// CreateProcess the app modification of uninstall process
BOOL CInstalledApp::_CreateAppModifyProcessNative(HWND hwndParent, LPTSTR pszExePath)
{
BOOL bRet = FALSE;
TCHAR szModifiedExePath[MAX_PATH + MAX_INFO_STRING];
#ifndef DOWNLEVEL_PLATFORM
// PPCF_LONGESTPOSSIBLE does not exist on down level platforms
if (0 >= PathProcessCommand(pszExePath, szModifiedExePath,
ARRAYSIZE(szModifiedExePath), PPCF_ADDQUOTES | PPCF_NODIRECTORIES | PPCF_LONGESTPOSSIBLE))
#endif
lstrcpy(szModifiedExePath,pszExePath);
TCHAR szErrExe[MAX_PATH];
if (!PathIsNetAndCreatable(szModifiedExePath, szErrExe, ARRAYSIZE(szErrExe)))
{
TCHAR szExplain[MAX_PATH];
LoadString(g_hinst, IDS_UNINSTALL_UNCUNACCESSIBLE, szExplain, ARRAYSIZE(szExplain));
wnsprintf(szModifiedExePath, ARRAYSIZE(szModifiedExePath), szExplain, _szProduct, szErrExe, ARRAYSIZE(szModifiedExePath));
if (!GetNewUninstallProgram(hwndParent, szModifiedExePath, ARRAYSIZE(szModifiedExePath)))
return FALSE;
}
bRet = CreateAndWaitForProcess(szModifiedExePath);
if (!bRet)
{
if (ShellMessageBox( HINST_THISDLL, hwndParent, MAKEINTRESOURCE( IDS_UNINSTALL_FAILED ),
MAKEINTRESOURCE( IDS_UNINSTALL_ERROR ),
MB_YESNO | MB_ICONEXCLAMATION, _szProduct, _szProduct) == IDYES)
{
// If we are unable to uninstall the app, give the user the option of removing
// it from the Add/Remove programs list. Note that we only know an uninstall
// has failed if we are unable to execute its command line in the registry. This
// won't cover all possible failed uninstalls. InstallShield, for instance, passes
// an uninstall path to a generic C:\WINDOWS\UNINST.EXE application. If an
// InstallShield app has been blown away, UNINST will still launch sucessfully, but
// will bomb out when it can't find the path, and we have no way of knowing it failed
// because it always returns an exit code of zero.
// A future work item (which I doubt will ever be done) would be to investigate
// various installer apps and see if any of them do return error codes that we could
// use to be better at detecting failure cases.
HKEY hkUninstall;
if (RegOpenKey(_MyHkeyRoot(), REGSTR_PATH_UNINSTALL, &hkUninstall) == ERROR_SUCCESS)
{
if (ERROR_SUCCESS == SHDeleteKey(hkUninstall, _szKeyName))
bRet = TRUE;
else
{
ShellMessageBox( HINST_THISDLL, hwndParent, MAKEINTRESOURCE( IDS_CANT_REMOVE_FROM_REGISTRY ),
MAKEINTRESOURCE( IDS_UNINSTALL_ERROR ),
MB_OK | MB_ICONEXCLAMATION, _szProduct);
}
RegCloseKey(hkUninstall);
}
}
}
return bRet;
}
// CreateProcess the app modification of uninstall process
BOOL CInstalledApp::_CreateAppModifyProcess(HWND hwndParent, DWORD dwCAMP)
{
if (_dwCIA & CIA_ALT)
{
return _CreateAppModifyProcessWow64(hwndParent, dwCAMP);
}
else
{
switch (dwCAMP)
{
case CAMP_UNINSTALL:
return _CreateAppModifyProcessNative(hwndParent, _szUninstall);
case CAMP_MODIFY:
return _CreateAppModifyProcessNative(hwndParent, _szModifyPath);
}
return FALSE;
}
}
//
// Command line to the rundll32 is
//
// %SystemRoot%\SysWOW64\rundll32.exe %SystemRoot%\SysWOW64\appwiz.cpl,
// WOW64Uninstall_RunDLL,<hwnd>,<CIA>,<CAMP>,<KeyName>
//
// The KeyName must come last because it might contain a comma.
//
//
BOOL CInstalledApp::_CreateAppModifyProcessWow64(HWND hwndParent, DWORD dwCAMP)
{
TCHAR szSysWow64[MAX_PATH];
TCHAR szRundll32[MAX_PATH];
TCHAR szCmdLine[MAX_PATH * 2];
BOOL fSuccess = FALSE;
if (GetWindowsDirectory(szSysWow64, ARRAYSIZE(szSysWow64)) &&
PathAppend(szSysWow64, TEXT("SysWOW64")))
{
wnsprintf(szRundll32, ARRAYSIZE(szRundll32), TEXT("%s\\rundll32.exe"), szSysWow64);
wnsprintf(szCmdLine, ARRAYSIZE(szCmdLine),
TEXT("\"%s\" \"%s\\appwiz.cpl\",WOW64Uninstall_RunDLL %d,%d,%d,%s"),
szRundll32, szSysWow64, hwndParent, _dwCIA, dwCAMP, _szKeyName);
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
if (CreateProcess(szRundll32, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
SHProcessMessagesUntilEvent(NULL, pi.hProcess, INFINITE);
DWORD dwExitCode;
if (GetExitCodeProcess(pi.hProcess, &dwExitCode) && dwExitCode == 0)
{
fSuccess = TRUE;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
return fSuccess;
}
// Helper function for command line parsing...
int _ParseCmdLineIntegerAndComma(LPWSTR *ppwsz)
{
LPWSTR psz = *ppwsz;
if (!psz)
{
return -1;
}
int i = StrToInt(psz);
psz = StrChr(psz, TEXT(','));
if (!psz)
{
*ppwsz = NULL;
return -1;
}
*ppwsz = psz + 1;
return i;
}
//
// Special export that the 64-bit version of appwiz uses to force an app
// uninstaller to run in 32-bit mode.
//
// Command line arguments are as described above.
STDAPI_(void) WOW64Uninstall_RunDLLW(HWND hwnd, HINSTANCE hAppInstance, LPWSTR lpszCmdLine, int nCmdShow)
{
BOOL fSuccess = FALSE;
HWND hwndParent = (HWND)IntToPtr(_ParseCmdLineIntegerAndComma(&lpszCmdLine));
int dwCIA = _ParseCmdLineIntegerAndComma(&lpszCmdLine);
int dwCAMP = _ParseCmdLineIntegerAndComma(&lpszCmdLine);
if (lpszCmdLine && *lpszCmdLine)
{
dwCIA &= ~CIA_ALT; // We *are* the alternate platform
HKEY hkRoot = (dwCIA & CIA_CU) ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
HKEY hkSub;
TCHAR szBuf[MAX_PATH];
TCHAR szName[MAX_PATH];
// Note: This is running on the 32-bit side so we don't use ALT
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s\\%s"), REGSTR_PATH_UNINSTALL, lpszCmdLine);
if (ERROR_SUCCESS == RegOpenKeyEx(hkRoot, szBuf, 0, KEY_READ, &hkSub))
{
DWORD cb;
szBuf[0] = 0;
cb = SIZEOF(szBuf);
SHQueryValueEx(hkSub, REGSTR_VAL_UNINSTALLER_COMMANDLINE, 0, NULL, (PBYTE)szBuf, &cb);
szName[0] = 0;
cb = SIZEOF(szName);
SHQueryValueEx(hkSub, REGSTR_VAL_UNINSTALLER_DISPLAYNAME, 0, NULL, (PBYTE)szName, &cb);
CInstalledApp * pia = new CInstalledApp(hkSub, lpszCmdLine, szName, szBuf, dwCIA);
if (pia)
{
fSuccess = pia->_CreateAppModifyProcess(hwndParent, dwCAMP);
pia->Release();
}
RegCloseKey(hkSub);
}
}
// Let my parent regain foreground activation now that I'm finished
DWORD dwPid;
if (GetWindowThreadProcessId(hwndParent, &dwPid))
{
AllowSetForegroundWindow(dwPid);
}
// Return 0 on success, 1 on failure (exit codes are like that)
ExitProcess(!fSuccess);
}
// Uinstalls legacy apps
BOOL CInstalledApp::_LegacyUninstall(HWND hwndParent)
{
LPVOID pAppScripts = ScriptManagerInitScripts();
BOOL bRet = FALSE;
if (_dwAction & APPACTION_UNINSTALL)
bRet = _CreateAppModifyProcess(hwndParent, CAMP_UNINSTALL);
if(pAppScripts)
{
ScriptManagerRunScripts(&pAppScripts);
}
return bRet;
}
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
DWORD _QueryTSInstallMode(LPTSTR pszKeyName)
{
// NOTE: Terminal Server guys confirmed this, when this value is 0, it means
// we were installed in install mode. 1 means not installed in "Install Mode"
// Set default to "install mode"
DWORD dwVal = 0;
DWORD dwValSize = SIZEOF(dwVal);
if (ERROR_SUCCESS != SHGetValue(HKEY_LOCAL_MACHINE, c_szTSInstallMode, pszKeyName,
NULL, &dwVal, &dwValSize))
{
dwVal = 0;
}
return dwVal;
}
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
BOOL_PTR CALLBACK _MultiUserWarningProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_INITDIALOG:
SendDlgItemMessage(hDlg, IDC_ICON_WARNING, STM_SETICON, (WPARAM)LoadIcon(NULL, IDI_WARNING), 0);
return TRUE;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wp, lp))
{
case IDC_SWITCHUSER:
ShellSwitchUser(FALSE);
EndDialog(hDlg, IDCANCEL);
break;
case IDOK:
case IDCANCEL:
EndDialog(hDlg, GET_WM_COMMAND_ID(wp, lp));
break;
}
break;
}
return FALSE;
}
int _GetLoggedOnUserCount(void)
{
int iCount = 0;
HANDLE hServer;
// Open a connection to terminal services and get the number of sessions.
hServer = WinStationOpenServerW(reinterpret_cast<WCHAR*>(SERVERNAME_CURRENT));
if (hServer != NULL)
{
TS_COUNTER tsCounters[2] = {0};
tsCounters[0].counterHead.dwCounterID = TERMSRV_CURRENT_DISC_SESSIONS;
tsCounters[1].counterHead.dwCounterID = TERMSRV_CURRENT_ACTIVE_SESSIONS;
if (WinStationGetTermSrvCountersValue(hServer, ARRAYSIZE(tsCounters), tsCounters))
{
int i;
for (i = 0; i < ARRAYSIZE(tsCounters); i++)
{
if (tsCounters[i].counterHead.bResult)
{
iCount += tsCounters[i].dwValue;
}
}
}
WinStationCloseServer(hServer);
}
return iCount;
}
int _ShowMultiUserWarning(HWND hwndParent)
{
int iRet = IDOK;
if (ShellIsMultipleUsersEnabled() && _GetLoggedOnUserCount() > 1)
{
iRet = (int)DialogBoxParam(g_hinst, MAKEINTRESOURCE(DLG_MULTIUSERWARNING),
hwndParent, _MultiUserWarningProc, 0);
}
return iRet;
}
// IInstalledApps::Uninstall
STDMETHODIMP CInstalledApp::Uninstall(HWND hwndParent)
{
HRESULT hres = E_FAIL;
if (!_IsAppFastUserSwitchingCompliant() && (IDOK != _ShowMultiUserWarning(hwndParent)))
return hres;
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
// Default to turn install mode off (1 is off)
DWORD dwTSInstallMode = 1;
BOOL bPrevMode = FALSE;
if (IsTerminalServicesRunning())
{
// On NT, let Terminal Services know that we are about to uninstall an application.
dwTSInstallMode = _QueryTSInstallMode((_dwSource & IA_DARWIN) ? _szProductID : _szKeyName);
if (dwTSInstallMode == 0)
{
bPrevMode = TermsrvAppInstallMode();
SetTermsrvAppInstallMode(TRUE);
}
}
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
switch (_dwSource)
{
case IA_LEGACY:
if (_LegacyUninstall(hwndParent))
hres = S_OK;
break;
case IA_DARWIN:
{
TCHAR szFinal[512], szPrompt[256];
LoadString(g_hinst, IDS_CONFIRM_REMOVE, szPrompt, ARRAYSIZE(szPrompt));
wnsprintf(szFinal, ARRAYSIZE(szFinal), szPrompt, _szProduct);
if (ShellMessageBox(g_hinst, hwndParent, szFinal, MAKEINTRESOURCE(IDS_NAME),
MB_YESNO | MB_ICONQUESTION, _szProduct, _szProduct) == IDYES)
{
LONG lRet;
INSTALLUILEVEL OldUI = MsiSetInternalUI(INSTALLUILEVEL_BASIC, NULL);
lRet = MsiConfigureProduct(_szProductID, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT);
MsiSetInternalUI(OldUI, NULL);
hres = HRESULT_FROM_WIN32(lRet);
// Is this an ophaned assigned app? If so, say we succeeded and call
// Class Store to remove it.
// REARCHITECT: This is too Class Store specific, what if the app is from
// SMS?
if ((lRet == ERROR_INSTALL_SOURCE_ABSENT) &&
(INSTALLSTATE_ADVERTISED == MsiQueryProductState(_szProductID)))
{
hres = S_OK;
lRet = ERROR_SUCCESS;
}
// Tell the software installation service we are uninstalling a Darwin app
// NOTE: We call this function for every Darwin app, which is not right because
// some darwin apps could be from a different source, such as SMS, we need a better
// way to do this.
// We call this regardless of failure or success -- this is needed so that
// RSoP can record both success and failure status for this uninstall
WCHAR wszProductID[GUIDSTR_MAX];
#ifdef UNICODE
StrCpy(wszProductID, _szProductID);
#else
SHTCharToUnicode(_szProductID, wszProductID, ARRAYSIZE(wszProductID));
#endif
UninstallApplication(
wszProductID,
lRet);
if (FAILED(hres))
{
_ARPErrorMessageBox(lRet);
}
}
else
{
hres = E_ABORT; // works for user cancelled
}
break;
}
case IA_SMS:
break;
default:
break;
}
// Get rid of the ARP Cache for this app.
if (SUCCEEDED(hres))
{
HKEY hkeyARPCache;
if (ERROR_SUCCESS == RegOpenKey(_MyHkeyRoot(), c_szRegstrARPCache, &hkeyARPCache))
{
LPTSTR pszKeyName = (_dwSource & IA_DARWIN) ? _szProductID : _szKeyName;
SHDeleteKey(hkeyARPCache, pszKeyName);
RegCloseKey(hkeyARPCache);
}
}
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
if (dwTSInstallMode == 0)
SetTermsrvAppInstallMode(bPrevMode);
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
return hres;
}
BOOL CInstalledApp::_LegacyModify(HWND hwndParent)
{
ASSERT(_dwAction & APPACTION_MODIFY);
ASSERT(_dwSource & (IA_LEGACY | IA_DARWIN));
// ASSERT(IS_VALID_STRING_PTR(_szProductID, 39));
return _CreateAppModifyProcess(hwndParent, CAMP_MODIFY);
}
// IInstalledApps::Modify
STDMETHODIMP CInstalledApp::Modify(HWND hwndParent)
{
HRESULT hres = E_FAIL;
if (!_IsAppFastUserSwitchingCompliant() && (IDOK != _ShowMultiUserWarning(hwndParent)))
return hres;
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
// On NT, let Terminal Services know that we are about to modify an application.
DWORD dwTSInstallMode = _QueryTSInstallMode((_dwSource & IA_DARWIN) ? _szProductID : _szKeyName);
BOOL bPrevMode = FALSE;
if (dwTSInstallMode == 0)
{
bPrevMode = TermsrvAppInstallMode();
SetTermsrvAppInstallMode(TRUE);
}
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
if (_dwAction & APPACTION_MODIFY)
{
if ((_dwSource & IA_LEGACY) && _LegacyModify(hwndParent))
hres = S_OK;
else if (_dwSource & IA_DARWIN)
{
// For modify operations we need to use the FULL UI level to give user
// more choices
// NOTE: we are currently not setting this back to the original after the
// modify operation. This seems to be okay with the Darwin guys
INSTALLUILEVEL OldUI = MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
LONG lRet = MsiConfigureProduct(_szProductID, INSTALLLEVEL_DEFAULT,
INSTALLSTATE_DEFAULT);
MsiSetInternalUI(OldUI, NULL);
hres = HRESULT_FROM_WIN32(lRet);
if (FAILED(hres))
_ARPErrorMessageBox(lRet);
else
_SetSlowAppInfoChanged(NULL, 1);
}
}
#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
if (dwTSInstallMode == 0)
SetTermsrvAppInstallMode(bPrevMode);
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT
return hres;
}
// Repair Darwin apps.
LONG CInstalledApp::_DarRepair(BOOL bReinstall)
{
DWORD dwReinstall;
dwReinstall = REINSTALLMODE_USERDATA | REINSTALLMODE_MACHINEDATA |
REINSTALLMODE_SHORTCUT | REINSTALLMODE_FILEOLDERVERSION |
REINSTALLMODE_FILEVERIFY | REINSTALLMODE_PACKAGE;
return MsiReinstallProduct(_szProductID, dwReinstall);
}
// IInstalledApps::Repair
STDMETHODIMP CInstalledApp::Repair(BOOL bReinstall)
{
HRESULT hres = E_FAIL;
if (_dwSource & IA_DARWIN)
{
LONG lRet = _DarRepair(bReinstall);
hres = HRESULT_FROM_WIN32(lRet);
if (FAILED(hres))
_ARPErrorMessageBox(lRet);
else
_SetSlowAppInfoChanged(NULL, 1);
}
// don't know how to do SMS stuff
return hres;
}
// IInstalledApp::Upgrade
STDMETHODIMP CInstalledApp::Upgrade()
{
HRESULT hres = E_FAIL;
if ((_dwAction & APPACTION_UPGRADE) && (_dwSource & IA_DARWIN))
{
ShellExecute(NULL, NULL, _pszUpdateUrl, NULL, NULL, SW_SHOWDEFAULT);
hres = S_OK;
_SetSlowAppInfoChanged(NULL, 1);
}
return hres;
}
// IInstalledApp::QueryInterface
HRESULT CInstalledApp::QueryInterface(REFIID riid, LPVOID * ppvOut)
{
static const QITAB qit[] = {
QITABENT(CInstalledApp, IInstalledApp), // IID_IInstalledApp
QITABENTMULTI(CInstalledApp, IShellApp, IInstalledApp), // IID_IShellApp
{ 0 },
};
return QISearch(this, qit, riid, ppvOut);
}
// IInstalledApp::AddRef
ULONG CInstalledApp::AddRef()
{
InterlockedIncrement(&_cRef);
TraceAddRef(CInstalledApp, _cRef);
return _cRef;
}
// IInstalledApp::Release
ULONG CInstalledApp::Release()
{
InterlockedDecrement(&_cRef);
TraceRelease(CInstalledApp, _cRef);
if (_cRef > 0)
return _cRef;
delete this;
return 0;
}
//
// As of this first release of Windows XP, most applications are
// not going to be aware of Fast User Switching in the sense that if
// User 'A' is running the application and User 'B' tries to
// uninstall that application, the application may be damaged.
// To protect against this, we display a warning message informing
// the user of this potential problem. See function _ShowMultiUserWarning().
// If an application is aware of Fast User Switching, they make that
// indication by setting a registry value in their "Uninstall" key.
// This function queries for that value and returns TRUE/FALSE to indicate
// it's presence. So that we err on the conservative side, any
// failure to read this value is equivalent to it's absence.
//
BOOL
CInstalledApp::_IsAppFastUserSwitchingCompliant(
void
)
{
BOOL bCompliant = FALSE;
HKEY hkey = _OpenUninstallRegKey(KEY_QUERY_VALUE);
if (NULL != hkey)
{
DWORD dwType;
DWORD dwValue;
DWORD cbData = sizeof(dwValue);
DWORD dwResult = RegQueryValueEx(hkey,
TEXT("FastUserSwitchingCompliant"),
NULL,
&dwType,
(LPBYTE)&dwValue,
&cbData);
if (ERROR_SUCCESS == dwResult)
{
if (REG_DWORD == dwType)
{
if (1 == dwValue)
{
bCompliant = TRUE;
}
}
else
{
TraceMsg(TF_ERROR, "FastUserSwitchingCompliant reg value is invalid type (%d). Expected REG_DWORD.", dwType);
}
}
else if (ERROR_FILE_NOT_FOUND != dwResult)
{
TraceMsg(TF_ERROR, "Error %d reading FastUserSwitchingCompliant reg value.", dwResult);
}
RegCloseKey(hkey);
}
else
{
TraceMsg(TF_ERROR, "Error opening application Uninstall reg key");
}
return bCompliant;
}