#ifndef __cplusplus #error Install stub code must be C++! #endif #ifndef HINST_THISDLL #error HINST_THISDLL must be defined! #endif #ifndef ARRAYSIZE #define ARRAYSIZE(a) (sizeof(a)/sizeof((a)[0])) #endif #include #include #include #ifdef __cplusplus extern "C" { #endif #include // shared runonce code for ShellExecuteRegApp() #ifdef __cplusplus }; #endif BOOL CheckWebViewShell(); /* This code runs the install/uninstall stubs recorded in the local-machine * part of the registry, iff the current user has not had them run in his * context yet. Used for populating the user's profile with things like * links to applications. */ //--------------------------------------------------------------------------- BOOL ProfilesEnabled(void) { BOOL fEnabled = FALSE; if (staticIsOS(OS_NT)) { fEnabled = TRUE; } else { HKEY hkeyLogon; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Network\\Logon"), 0, KEY_QUERY_VALUE, &hkeyLogon) == ERROR_SUCCESS) { DWORD fProfiles, cbData = sizeof(fProfiles), dwType; if (RegQueryValueEx(hkeyLogon, TEXT("UserProfiles"), NULL, &dwType, (LPBYTE)&fProfiles, &cbData) == ERROR_SUCCESS) { if (dwType == REG_DWORD || (dwType == REG_BINARY && cbData == sizeof(DWORD))) fEnabled = fProfiles; } RegCloseKey(hkeyLogon); } } return fEnabled; } // --------------------------------------------------------------------------- // %%Function: GetVersionFromString // // Snarfed from urlmon\download\helpers.cxx. // // converts version in text format (a,b,c,d) into two dwords (a,b), (c,d) // The printed version number is of format a.b.d (but, we don't care) // --------------------------------------------------------------------------- HRESULT GetVersionFromString(LPCTSTR szBuf, LPDWORD pdwFileVersionMS, LPDWORD pdwFileVersionLS) { LPCTSTR pch = szBuf; TCHAR ch; USHORT n = 0; USHORT a = 0; USHORT b = 0; USHORT c = 0; USHORT d = 0; enum HAVE { HAVE_NONE, HAVE_A, HAVE_B, HAVE_C, HAVE_D } have = HAVE_NONE; *pdwFileVersionMS = 0; *pdwFileVersionLS = 0; if (!pch) // default to zero if none provided return S_OK; if (lstrcmp(pch, TEXT("-1,-1,-1,-1")) == 0) { *pdwFileVersionMS = 0xffffffff; *pdwFileVersionLS = 0xffffffff; } for (ch = *pch++;;ch = *pch++) { if ((ch == ',') || (ch == '\0')) { switch (have) { case HAVE_NONE: a = n; have = HAVE_A; break; case HAVE_A: b = n; have = HAVE_B; break; case HAVE_B: c = n; have = HAVE_C; break; case HAVE_C: d = n; have = HAVE_D; break; case HAVE_D: return E_INVALIDARG; // invalid arg } if (ch == '\0') { // all done convert a,b,c,d into two dwords of version *pdwFileVersionMS = ((a << 16)|b); *pdwFileVersionLS = ((c << 16)|d); return S_OK; } n = 0; // reset } else if ( (ch < '0') || (ch > '9')) return E_INVALIDARG; // invalid arg else n = n*10 + (ch - '0'); } /* end forever */ // NEVERREACHED } // Reg keys and values for install/uninstall stub list. Each subkey under // HKLM\Software\InstalledComponents is a component identifier (GUID). // Each subkey has values "Path" for the EXE to run to install or uninstall; // IsInstalled (dword) indicating whether the component has been installed // or uninstalled; and an optional Revision (dword) used to refresh a // component without changing its GUID. Locale (string) is used to describe // the language/locale for the component; this string is not interpreted by // the install stub code, it is just compared between the HKLM and HKCU keys. // If it's different between the two, the stub is re-run. // // HKCU\Software\InstalledComponents contains similar GUID subkeys, but the // only values under each subkey are the optional Revision and Locale values, // and an optional DontAsk value (also DWORD). Presence of the subkey indicates // that the component is installed for that user. // // If the DontAsk value is present under an HKCU subkey and is non-zero, that // means that the user has decided to keep their settings for that component // on all machines, even those that have had the component uninstalled, and // that they don't want to be asked if they want to run the uninstall stub // every time they log on. This implies that for that user, the uninstall // stub will never be run for that component unless the user somehow clears // the flag. // // NOTE: mslocusr.dll also knows these registry paths. const TCHAR c_szRegInstalledComponentsKey[] = TEXT("Software\\Microsoft\\Active Setup\\Installed Components"); const TCHAR c_szRegInstallStubValue[] = TEXT("StubPath"); const TCHAR c_szRegIsInstalledValue[] = TEXT("IsInstalled"); const TCHAR c_szRegInstallSequenceValue[] = TEXT("Version"); const TCHAR c_szRegDontAskValue[] = TEXT("DontAsk"); const TCHAR c_szRegLocaleValue[] = TEXT("Locale"); UINT ConfirmUninstall(LPCTSTR pszDescription) { /* The only case where the user wouldn't want settings cleaned up on * uninstall would be if they'd roamed to a machine that had had this * component uninstalled. If user profiles aren't enabled (which is * the case on a fair number of customers' machines), they're certainly * not roaming, so there's not much point in asking them. Just pretend * they said YES, they want to clean up the settings. */ if (!ProfilesEnabled()) return IDYES; /* FEATURE - change to a dialog with a checkbox for * the don't-ask value. */ TCHAR szTitle[MAX_PATH]; #ifdef USERSTUB LoadString(HINST_THISDLL, IDS_DESKTOP, szTitle, ARRAYSIZE(szTitle)); #else MLLoadString(IDS_DESKTOP, szTitle, ARRAYSIZE(szTitle)); #endif TCHAR szMessageTemplate[MAX_PATH]; LPTSTR pszMessage = NULL; int cchMessage; #ifdef USERSTUB LoadString(HINST_THISDLL, IDS_UNINSTALL, szMessageTemplate, ARRAYSIZE(szMessageTemplate)); #else MLLoadString(IDS_UNINSTALL, szMessageTemplate, ARRAYSIZE(szMessageTemplate)); #endif cchMessage = (lstrlen(szMessageTemplate)+lstrlen(pszDescription)+4)*sizeof(TCHAR); pszMessage = (LPTSTR)LocalAlloc(LPTR, cchMessage); if (pszMessage) { #ifdef USERSTUB wsprintf(pszMessage, szMessageTemplate, pszDescription); #else wnsprintf(pszMessage, cchMessage, szMessageTemplate, pszDescription); #endif } else { pszMessage = szMessageTemplate; } // due to build in UNICODE the following call is broken under win95, user wsprintf above //if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | // FORMAT_MESSAGE_ARGUMENT_ARRAY, // (LPVOID)szMessageTemplate, // 0, // 0, // (LPTSTR)&pszMessage, // 0, /* min chars to allocate */ // (va_list *)&pszDescription)) { // pszMessage = szMessageTemplate; //} UINT idRet = MessageBox(NULL, pszMessage, szTitle, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_SETFOREGROUND | MB_TOPMOST); if (pszMessage != szMessageTemplate) LocalFree(pszMessage); return idRet; } HWND hwndProgress = NULL; BOOL fTriedProgressDialog = FALSE; INT_PTR ProgressDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE; case WM_SETCURSOR: SetCursor(LoadCursor(NULL, IDC_WAIT)); return TRUE; default: return FALSE; } return TRUE; } void SetProgressInfo(HWND hwndProgress, LPCTSTR pszFriendlyName, BOOL fInstalling) { HWND hwndInstalling = GetDlgItem(hwndProgress, IDC_RUNNING_INSTALL_STUB); HWND hwndUninstalling = GetDlgItem(hwndProgress, IDC_RUNNING_UNINSTALL_STUB); ShowWindow(hwndInstalling, fInstalling ? SW_SHOW : SW_HIDE); EnableWindow(hwndInstalling, fInstalling); ShowWindow(hwndUninstalling, fInstalling ? SW_HIDE : SW_SHOW); EnableWindow(hwndUninstalling, !fInstalling); SetDlgItemText(hwndProgress, IDC_INSTALL_STUB_NAME, pszFriendlyName); } void IndicateProgress(LPCTSTR pszFriendlyName, BOOL fInstalling) { if (hwndProgress == NULL && !fTriedProgressDialog) { hwndProgress = CreateDialog(HINST_THISDLL, MAKEINTRESOURCE(IDD_InstallStubProgress), NULL, ProgressDialogProc); } if (hwndProgress != NULL) { SetProgressInfo(hwndProgress, pszFriendlyName, fInstalling); if (!fTriedProgressDialog) { ShowWindow(hwndProgress, SW_RESTORE); SetForegroundWindow(hwndProgress); } } fTriedProgressDialog = TRUE; } void CleanupProgressDialog(void) { if (hwndProgress != NULL) { DestroyWindow(hwndProgress); hwndProgress = NULL; } } BOOL RunOneInstallStub( HKEY hklmList, HKEY hkcuList, LPCTSTR pszKeyName, LPCTSTR pszCurrentUsername, int iPass ) { BOOL bNextPassNeeded = FALSE; /* See if this component is installed or an uninstall tombstone. */ HKEY hkeyComponent; DWORD err = RegOpenKeyEx(hklmList, pszKeyName, 0, KEY_QUERY_VALUE, &hkeyComponent); if (err == ERROR_SUCCESS) { TCHAR szCmdLine[MAX_PATH]; DWORD fIsInstalled; DWORD dwType; DWORD cbData = sizeof(fIsInstalled); HKEY hkeyUser = NULL; /* Must have the stub path; if not there, skip this entry. */ cbData = sizeof(szCmdLine); if (SHQueryValueEx(hkeyComponent, c_szRegInstallStubValue, NULL, &dwType, (LPBYTE)szCmdLine, &cbData) != ERROR_SUCCESS || ((dwType != REG_SZ) && (dwType != REG_EXPAND_SZ)) ) { RegCloseKey(hkeyComponent); return bNextPassNeeded; } TCHAR szDescription[MAX_PATH]; LPTSTR pszDescription = szDescription; cbData = sizeof(szDescription); if (SHQueryValueEx(hkeyComponent, TEXT(""), NULL, &dwType, (LPBYTE)szDescription, &cbData) != ERROR_SUCCESS || dwType != REG_SZ) { pszDescription = szCmdLine; } if (RegQueryValueEx(hkeyComponent, c_szRegIsInstalledValue, NULL, &dwType, (LPBYTE)&fIsInstalled, &cbData) != ERROR_SUCCESS || (dwType != REG_DWORD && (dwType != REG_BINARY || cbData != sizeof(DWORD)))) fIsInstalled = TRUE; /* If it's installed, check the user's profile, and if the * component (or its current revision) isn't installed there, * run it. */ if (fIsInstalled) { DWORD dwRevisionHi, dwRevisionLo; DWORD dwUserRevisionHi = 0; DWORD dwUserRevisionLo = 0; BOOL fSetRevision; TCHAR szRevision[24], szUserRevision[24]; /* 65535,65535,65535,65535\0 */ TCHAR szLocale[10], szUserLocale[10]; /* usually not very big strings */ TCHAR szInstallUsername[128+1]; /* 128 is the win95 system username limit */ DWORD fIsCloneUser; cbData = sizeof(fIsCloneUser); if (RegQueryValueEx(hkeyComponent, TEXT("CloneUser"), NULL, &dwType, (LPBYTE)&fIsCloneUser, &cbData) != ERROR_SUCCESS || (dwType != REG_DWORD && (dwType != REG_BINARY || cbData != sizeof(DWORD)))) fIsCloneUser = FALSE; cbData = sizeof(szRevision); if (RegQueryValueEx(hkeyComponent, c_szRegInstallSequenceValue, NULL, &dwType, (LPBYTE)szRevision, &cbData) != ERROR_SUCCESS || dwType != REG_SZ || FAILED(GetVersionFromString(szRevision, &dwRevisionHi, &dwRevisionLo))) { fSetRevision = FALSE; dwRevisionHi = 0; dwRevisionLo = 0; } else { fSetRevision = TRUE; } cbData = sizeof(szLocale); err = RegQueryValueEx(hkeyComponent, c_szRegLocaleValue, NULL, &dwType, (LPBYTE)szLocale, &cbData); if (err != ERROR_SUCCESS || dwType != REG_SZ) { szLocale[0] = '\0'; } err = RegOpenKeyEx(hkcuList, pszKeyName, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hkeyUser); if (err == ERROR_SUCCESS) { cbData = sizeof(szUserRevision); if (RegQueryValueEx(hkeyUser, c_szRegInstallSequenceValue, NULL, &dwType, (LPBYTE)szUserRevision, &cbData) != ERROR_SUCCESS || dwType != REG_SZ || FAILED(GetVersionFromString(szUserRevision, &dwUserRevisionHi, &dwUserRevisionLo))) { dwUserRevisionHi = 0; dwUserRevisionLo = 0; } if (szLocale[0] != '\0') { cbData = sizeof(szUserLocale); err = RegQueryValueEx(hkeyUser, c_szRegLocaleValue, NULL, &dwType, (LPBYTE)szUserLocale, &cbData); /* If there's a locale string under the user key * and it's the same as the machine one, then we * blank out the machine one so we won't consider * that when running the stub. */ if (err == ERROR_SUCCESS && dwType == REG_SZ && !lstrcmp(szLocale, szUserLocale)) { szLocale[0] = '\0'; } } if (fIsCloneUser) { /* Clone-user install stub. We need to re-run it if the * username we used when we last installed to this profile, * or the one it was copied from, is different from the * current username. */ cbData = sizeof(szInstallUsername); if (RegQueryValueEx(hkeyUser, TEXT("Username"), NULL, &dwType, (LPBYTE)szInstallUsername, &cbData) != ERROR_SUCCESS || dwType != REG_SZ) { szInstallUsername[0] = '\0'; } } } else { hkeyUser = NULL; } /* Install if: * * - User doesn't have component installed, OR * - Component installed on machine has a revision AND * - Machine component revision greater than user's * - OR * - Component installed on machine has a locale AND * - Machine component locale different than user's * (this is actually checked above) * - OR * - Component is a clone-user install stub and the username * recorded for the stub is different from the current username */ if ((hkeyUser == NULL) || (fSetRevision && ((dwRevisionHi > dwUserRevisionHi) || ((dwRevisionHi == dwUserRevisionHi) && (dwRevisionLo > dwUserRevisionLo) ) ) ) || (szLocale[0] != '\0') || #ifdef UNICODE (fIsCloneUser && StrCmpI(szInstallUsername, pszCurrentUsername)) #else (fIsCloneUser && lstrcmpi(szInstallUsername, pszCurrentUsername)) #endif ) { if ( (iPass == -1 ) || ((iPass == 0) && (*pszKeyName == '<')) || ((iPass == 1) && (*pszKeyName != '<') && (*pszKeyName != '>')) || ((iPass == 2) && (*pszKeyName == '>')) ) { // the condition meets, run it now. #ifdef TraceMsg TraceMsg(TF_WARNING, "Running install stub ( %s )", szCmdLine); #endif IndicateProgress(pszDescription, TRUE); ShellExecuteRegApp(szCmdLine, RRA_WAIT | RRA_NOUI); if (hkeyUser == NULL) { RegCreateKey(hkcuList, pszKeyName, &hkeyUser); } if (hkeyUser != NULL) { if (fSetRevision) { RegSetValueEx(hkeyUser, c_szRegInstallSequenceValue, 0, REG_SZ, (LPBYTE)szRevision, (lstrlen(szRevision)+1)*sizeof(TCHAR)); } if (szLocale[0]) { RegSetValueEx(hkeyUser, c_szRegLocaleValue, 0, REG_SZ, (LPBYTE)szLocale, (lstrlen(szLocale)+1)*sizeof(TCHAR)); } if (fIsCloneUser) { RegSetValueEx(hkeyUser, TEXT("Username"), 0, REG_SZ, (LPBYTE)pszCurrentUsername, (lstrlen(pszCurrentUsername)+1)*sizeof(TCHAR)); } } } else { // decide if this belong to the next pass // if it is in Pass 2, should never get here if ( iPass == 0 ) bNextPassNeeded = TRUE; else if ( (iPass == 1 ) && (*pszKeyName == '>') ) bNextPassNeeded = TRUE; } } } else { /* Component is an uninstall stub. */ err = RegOpenKeyEx(hkcuList, pszKeyName, 0, KEY_QUERY_VALUE, &hkeyUser); if (err == ERROR_SUCCESS) { DWORD fDontAsk = 0; /* Check the "Don't Ask" value. If it's present, its value * is interpreted as follows: * * 0 --> ask the user * 1 --> do not run the stub * 2 --> always run the stub */ cbData = sizeof(fDontAsk); if (RegQueryValueEx(hkeyComponent, c_szRegDontAskValue, NULL, &dwType, (LPBYTE)&fDontAsk, &cbData) != ERROR_SUCCESS || (dwType != REG_DWORD && (dwType != REG_BINARY || cbData != sizeof(DWORD))) || fDontAsk != 1) { if ( (iPass == -1 ) || ((iPass == 0) && (*pszKeyName == '>')) || ((iPass == 1) && (*pszKeyName != '<') && (*pszKeyName != '>')) || ((iPass == 2) && (*pszKeyName == '<')) ) { // uninstall stub has the reversed order comparing with install stub if (fDontAsk == 2 || ConfirmUninstall(pszDescription) == IDYES) { #ifdef TraceMsg TraceMsg(TF_WARNING, "Running uninstall stub ( %s )", szCmdLine); #endif IndicateProgress(pszDescription, FALSE); ShellExecuteRegApp(szCmdLine, RRA_WAIT | RRA_NOUI); /* Component has been uninstalled. Forget that the * user ever had it installed. */ RegCloseKey(hkeyUser); hkeyUser = NULL; RegDeleteKey(hkcuList, pszKeyName); } } else { // decide if this belong to the next pass // if it is in Pass 2, should never get here if ( iPass == 0 ) bNextPassNeeded = TRUE; else if ( (iPass == 1 ) && (*pszKeyName == '<') ) bNextPassNeeded = TRUE; } } } } if (hkeyUser != NULL) { RegCloseKey(hkeyUser); } RegCloseKey(hkeyComponent); } return bNextPassNeeded; } const TCHAR c_szIE40GUID_STUB[] = TEXT("{89820200-ECBD-11cf-8B85-00AA005B4383}"); const TCHAR c_szBlockIE4Stub[] = TEXT("NoIE4StubProcessing"); extern "C" void RunInstallUninstallStubs2(LPCTSTR pszStubToRun) { HKEY hklmList = NULL, hkcuList = NULL; LONG err; TCHAR szUsername[128+1]; /* 128 is the win95 limit, good default */ LPTSTR pszCurrentUser = szUsername; /* As far as clone-user install stubs are concerned, we only want profile * usernames. */ if (!ProfilesEnabled()) { *pszCurrentUser = '\0'; } else { DWORD cbData = sizeof(szUsername); if (!GetUserName(szUsername, &cbData)) { if (cbData > sizeof(szUsername)) { cbData++; /* allow for null char just in case */ pszCurrentUser = (LPTSTR)LocalAlloc(LPTR, cbData+1); if (pszCurrentUser == NULL || !GetUserName(pszCurrentUser, &cbData)) { if (pszCurrentUser != NULL) LocalFree(pszCurrentUser); pszCurrentUser = szUsername; *pszCurrentUser = '\0'; } } else { szUsername[0] = '\0'; } } } #ifdef TraceMsg TraceMsg(TF_WARNING, "Running install/uninstall stubs."); #endif err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegInstalledComponentsKey, 0, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &hklmList); if (err == ERROR_SUCCESS) { DWORD dwDisp; err = RegCreateKeyEx(HKEY_CURRENT_USER, c_szRegInstalledComponentsKey, 0, TEXT(""), REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hkcuList, &dwDisp); } if (err == ERROR_SUCCESS) { if (pszStubToRun != NULL) { // here we call with pass number -1 means no pass order enforced RunOneInstallStub(hklmList, hkcuList, pszStubToRun, pszCurrentUser, -1); } else { DWORD cbKeyName, iKey, iPass; TCHAR szKeyName[80]; BOOL bNextPassNeeded = TRUE; HANDLE hMutex; // This mutex check is to ensure if explore restarted due abnormal active desktop shutdown, and setup resume // per-user stubs should not be processed till setup is done. if (CheckWebViewShell()) { hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, TEXT("Ie4Setup.Mutext")); if (hMutex) { CloseHandle(hMutex); goto done; } } // check if we want to block the stub processing cbKeyName = sizeof(szKeyName); if ((RegQueryValueEx(hklmList, c_szBlockIE4Stub, NULL, NULL, (LPBYTE)szKeyName, &cbKeyName) == ERROR_SUCCESS) && (*CharUpper(szKeyName) == 'Y') ) { goto done; } /* we will do TWO passes to meet the ordering requirement when run component stubs. Any KeyName with '*' as the first char, get run in the 1st Pass. the rest run 2nd pass */ for ( iPass = 0; ((iPass<3) && bNextPassNeeded); iPass++ ) { bNextPassNeeded = FALSE; // APPCOMPAT: in 2nd pass, we do want to special case of IE4.0 base browser stub // to run first. The reason we did not use '<' for this is to 1) reserve < stuff // for pre-ie4 stubs and this whole thing should redo in sorted fashion. For now, // we hard code this IE4.0 base browser GUID if ( iPass == 1 ) { if ( RunOneInstallStub(hklmList, hkcuList, c_szIE40GUID_STUB, pszCurrentUser, iPass) ) bNextPassNeeded = TRUE; } /* Enumerate components that are installed on the local machine. */ for (iKey = 0; ; iKey++) { LONG lEnum; cbKeyName = ARRAYSIZE(szKeyName); // WARNING (Unicode, Davepl) I'm assuming that the data is UNICODE, // but I'm not sure who put it there yet... double check. if ((lEnum = RegEnumKey(hklmList, iKey, szKeyName, cbKeyName)) == ERROR_MORE_DATA) { // ERROR_MORE_DATA means the value name or data was too large // skip to the next item #ifdef TraceMsg TraceMsg( DM_ERROR, "Cannot run oversize entry in InstalledComponents"); #endif continue; } else if( lEnum != ERROR_SUCCESS ) { // could be ERROR_NO_MORE_ENTRIES, or some kind of failure // we can't recover from any other registry problem, anyway break; } // in case the user say NO when we try to run the IE4 stub first time, // we should not re-process this stub again. if ( (iPass == 1) && (!lstrcmpi(szKeyName, c_szIE40GUID_STUB)) ) continue; if ( RunOneInstallStub(hklmList, hkcuList, szKeyName, pszCurrentUser, iPass) ) bNextPassNeeded = TRUE; } } } } done: if (hklmList != NULL) RegCloseKey(hklmList); if (hkcuList != NULL) RegCloseKey(hkcuList); if (pszCurrentUser != szUsername) LocalFree(pszCurrentUser); CleanupProgressDialog(); } // Check shell32.dll's version and see if it is the one which supports the integrated WebView BOOL CheckWebViewShell() { HINSTANCE hInstShell32; DLLGETVERSIONPROC fpGetDllVersion; BOOL pWebViewShell = FALSE; hInstShell32 = LoadLibrary(TEXT("Shell32.dll")); if (hInstShell32) { fpGetDllVersion = (DLLGETVERSIONPROC)GetProcAddress(hInstShell32, "DllGetVersion"); pWebViewShell = (fpGetDllVersion != NULL); FreeLibrary(hInstShell32); } return pWebViewShell; }