//---------------------------------------------------------------------------
//
// 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;
}