Home | History | Annotate | Download | only in gcapi
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // NOTE: This code is a legacy utility API for partners to check whether
      6 //       Chrome can be installed and launched. Recent updates are being made
      7 //       to add new functionality. These updates use code from Chromium, the old
      8 //       coded against the win32 api directly. If you have an itch to shave a
      9 //       yak, feel free to re-write the old code too.
     10 
     11 #include "chrome/installer/gcapi/gcapi.h"
     12 
     13 #include <sddl.h>
     14 #define STRSAFE_NO_DEPRECATE
     15 #include <windows.h>
     16 #include <strsafe.h>
     17 #include <tlhelp32.h>
     18 
     19 #include <cstdlib>
     20 #include <iterator>
     21 #include <limits>
     22 #include <set>
     23 #include <string>
     24 
     25 #include "base/basictypes.h"
     26 #include "base/command_line.h"
     27 #include "base/files/file_path.h"
     28 #include "base/process/launch.h"
     29 #include "base/strings/string16.h"
     30 #include "base/strings/string_number_conversions.h"
     31 #include "base/strings/string_util.h"
     32 #include "base/time/time.h"
     33 #include "base/win/registry.h"
     34 #include "base/win/scoped_com_initializer.h"
     35 #include "base/win/scoped_comptr.h"
     36 #include "base/win/scoped_handle.h"
     37 #include "chrome/installer/gcapi/gcapi_omaha_experiment.h"
     38 #include "chrome/installer/gcapi/gcapi_reactivation.h"
     39 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
     40 #include "chrome/installer/util/google_update_constants.h"
     41 #include "chrome/installer/util/util_constants.h"
     42 #include "chrome/installer/util/wmi.h"
     43 #include "google_update/google_update_idl.h"
     44 
     45 using base::Time;
     46 using base::TimeDelta;
     47 using base::win::RegKey;
     48 using base::win::ScopedCOMInitializer;
     49 using base::win::ScopedComPtr;
     50 using base::win::ScopedHandle;
     51 
     52 namespace {
     53 
     54 const wchar_t kChromeRegClientsKey[] =
     55     L"Software\\Google\\Update\\Clients\\"
     56     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     57 const wchar_t kChromeRegClientStateKey[] =
     58     L"Software\\Google\\Update\\ClientState\\"
     59     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     60 const wchar_t kChromeRegClientStateMediumKey[] =
     61     L"Software\\Google\\Update\\ClientStateMedium\\"
     62     L"{8A69D345-D564-463c-AFF1-A69D9E530F96}";
     63 
     64 const wchar_t kGCAPITempKey[] = L"Software\\Google\\GCAPITemp";
     65 
     66 const wchar_t kChromeRegLaunchCmd[] = L"InstallerSuccessLaunchCmdLine";
     67 const wchar_t kChromeRegLastLaunchCmd[] = L"LastInstallerSuccessLaunchCmdLine";
     68 const wchar_t kChromeRegVersion[] = L"pv";
     69 const wchar_t kNoChromeOfferUntil[] =
     70     L"SOFTWARE\\Google\\No Chrome Offer Until";
     71 
     72 // Prefix used to match the window class for Chrome windows.
     73 const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";
     74 
     75 // Return the company name specified in the file version info resource.
     76 bool GetCompanyName(const wchar_t* filename, wchar_t* buffer, DWORD out_len) {
     77   wchar_t file_version_info[8192];
     78   DWORD handle = 0;
     79   DWORD buffer_size = 0;
     80 
     81   buffer_size = ::GetFileVersionInfoSize(filename, &handle);
     82   // Cannot stats the file or our buffer size is too small (very unlikely).
     83   if (buffer_size == 0 || buffer_size > _countof(file_version_info))
     84     return false;
     85 
     86   buffer_size = _countof(file_version_info);
     87   memset(file_version_info, 0, buffer_size);
     88   if (!::GetFileVersionInfo(filename, handle, buffer_size, file_version_info))
     89     return false;
     90 
     91   DWORD data_len = 0;
     92   LPVOID data = NULL;
     93   // Retrieve the language and codepage code if exists.
     94   buffer_size = 0;
     95   if (!::VerQueryValue(file_version_info, TEXT("\\VarFileInfo\\Translation"),
     96       reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
     97     return false;
     98   if (data_len != 4)
     99     return false;
    100 
    101   wchar_t info_name[256];
    102   DWORD lang = 0;
    103   // Formulate the string to retrieve the company name of the specific
    104   // language codepage.
    105   memcpy(&lang, data, 4);
    106   ::StringCchPrintf(info_name, _countof(info_name),
    107       L"\\StringFileInfo\\%02X%02X%02X%02X\\CompanyName",
    108       (lang & 0xff00)>>8, (lang & 0xff), (lang & 0xff000000)>>24,
    109       (lang & 0xff0000)>>16);
    110 
    111   data_len = 0;
    112   if (!::VerQueryValue(file_version_info, info_name,
    113       reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
    114     return false;
    115   if (data_len <= 0 || data_len >= (out_len / sizeof(wchar_t)))
    116     return false;
    117 
    118   memset(buffer, 0, out_len);
    119   ::StringCchCopyN(buffer,
    120                    (out_len / sizeof(wchar_t)),
    121                    reinterpret_cast<const wchar_t*>(data),
    122                    data_len);
    123   return true;
    124 }
    125 
    126 // Return true if we can re-offer Chrome; false, otherwise.
    127 // Each partner can only offer Chrome once every six months.
    128 bool CanReOfferChrome(BOOL set_flag) {
    129   wchar_t filename[MAX_PATH+1];
    130   wchar_t company[MAX_PATH];
    131 
    132   // If we cannot retrieve the version info of the executable or company
    133   // name, we allow the Chrome to be offered because there is no past
    134   // history to be found.
    135   if (::GetModuleFileName(NULL, filename, MAX_PATH) == 0)
    136     return true;
    137   if (!GetCompanyName(filename, company, sizeof(company)))
    138     return true;
    139 
    140   bool can_re_offer = true;
    141   DWORD disposition = 0;
    142   HKEY key = NULL;
    143   if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
    144       0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
    145       NULL, &key, &disposition) == ERROR_SUCCESS) {
    146     // Get today's date, and format it as YYYYMMDD numeric value.
    147     SYSTEMTIME now;
    148     GetLocalTime(&now);
    149     DWORD today = now.wYear * 10000 + now.wMonth * 100 + now.wDay;
    150 
    151     // Cannot re-offer, if the timer already exists and is not expired yet.
    152     DWORD value_type = REG_DWORD;
    153     DWORD value_data = 0;
    154     DWORD value_length = sizeof(DWORD);
    155     if (::RegQueryValueEx(key, company, 0, &value_type,
    156                           reinterpret_cast<LPBYTE>(&value_data),
    157                           &value_length) == ERROR_SUCCESS &&
    158         REG_DWORD == value_type &&
    159         value_data > today) {
    160       // The time has not expired, we cannot offer Chrome.
    161       can_re_offer = false;
    162     } else {
    163       // Delete the old or invalid value.
    164       ::RegDeleteValue(key, company);
    165       if (set_flag) {
    166         // Set expiration date for offer as six months from today,
    167         // represented as a YYYYMMDD numeric value.
    168         SYSTEMTIME timer = now;
    169         timer.wMonth = timer.wMonth + 6;
    170         if (timer.wMonth > 12) {
    171           timer.wMonth = timer.wMonth - 12;
    172           timer.wYear = timer.wYear + 1;
    173         }
    174         DWORD value = timer.wYear * 10000 + timer.wMonth * 100 + timer.wDay;
    175         ::RegSetValueEx(key, company, 0, REG_DWORD, (LPBYTE)&value,
    176                         sizeof(DWORD));
    177       }
    178     }
    179 
    180     ::RegCloseKey(key);
    181   }
    182 
    183   return can_re_offer;
    184 }
    185 
    186 // Helper function to read a value from registry. Returns true if value
    187 // is read successfully and stored in parameter value. Returns false otherwise.
    188 bool ReadValueFromRegistry(HKEY root_key, const wchar_t* sub_key,
    189                            const wchar_t* value_name, wchar_t* value,
    190                            size_t* size) {
    191   HKEY key;
    192   if ((::RegOpenKeyEx(root_key, sub_key, NULL,
    193                       KEY_READ, &key) == ERROR_SUCCESS) &&
    194       (::RegQueryValueEx(key, value_name, NULL, NULL,
    195                          reinterpret_cast<LPBYTE>(value),
    196                          reinterpret_cast<LPDWORD>(size)) == ERROR_SUCCESS)) {
    197     ::RegCloseKey(key);
    198     return true;
    199   }
    200   return false;
    201 }
    202 
    203 bool IsChromeInstalled(HKEY root_key) {
    204   wchar_t version[64];
    205   size_t size = _countof(version);
    206   return ReadValueFromRegistry(root_key, kChromeRegClientsKey,
    207                                kChromeRegVersion, version, &size);
    208 }
    209 
    210 enum WindowsVersion {
    211   VERSION_BELOW_XP_SP2,
    212   VERSION_XP_SP2_UP_TO_VISTA,  // "but not including"
    213   VERSION_VISTA_OR_HIGHER,
    214 };
    215 WindowsVersion GetWindowsVersion() {
    216   OSVERSIONINFOEX version_info = { sizeof version_info };
    217   GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info));
    218 
    219   // Windows Vista is version 6.0.
    220   if (version_info.dwMajorVersion >= 6)
    221     return VERSION_VISTA_OR_HIGHER;
    222 
    223   // Windows XP is version 5.1.  (5.2 is Windows Server 2003/XP Pro x64.)
    224   if ((version_info.dwMajorVersion < 5) || (version_info.dwMinorVersion < 1))
    225     return VERSION_BELOW_XP_SP2;
    226 
    227   // For XP itself, we only support SP2 and above.
    228   return ((version_info.dwMinorVersion > 1) ||
    229           (version_info.wServicePackMajor >= 2)) ?
    230       VERSION_XP_SP2_UP_TO_VISTA : VERSION_BELOW_XP_SP2;
    231 }
    232 
    233 // Note this function should not be called on old Windows versions where these
    234 // Windows API are not available. We always invoke this function after checking
    235 // that current OS is Vista or later.
    236 bool VerifyAdminGroup() {
    237   SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    238   PSID Group;
    239   BOOL check = ::AllocateAndInitializeSid(&NtAuthority, 2,
    240                                           SECURITY_BUILTIN_DOMAIN_RID,
    241                                           DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0,
    242                                           0, 0, 0,
    243                                           &Group);
    244   if (check) {
    245     if (!::CheckTokenMembership(NULL, Group, &check))
    246       check = FALSE;
    247   }
    248   ::FreeSid(Group);
    249   return (check == TRUE);
    250 }
    251 
    252 bool VerifyHKLMAccess() {
    253   wchar_t str[] = L"test";
    254   bool result = false;
    255   DWORD disposition = 0;
    256   HKEY key = NULL;
    257 
    258   if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kGCAPITempKey, 0, NULL,
    259                        REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL,
    260                        &key, &disposition) == ERROR_SUCCESS) {
    261     if (::RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
    262         (DWORD)lstrlen(str)) == ERROR_SUCCESS) {
    263       result = true;
    264       RegDeleteValue(key, str);
    265     }
    266 
    267     RegCloseKey(key);
    268 
    269     //  If we create the main key, delete the entire key.
    270     if (disposition == REG_CREATED_NEW_KEY)
    271       RegDeleteKey(HKEY_LOCAL_MACHINE, kGCAPITempKey);
    272   }
    273 
    274   return result;
    275 }
    276 
    277 bool IsRunningElevated() {
    278   // This method should be called only for Vista or later.
    279   if ((GetWindowsVersion() < VERSION_VISTA_OR_HIGHER) ||
    280       !VerifyAdminGroup())
    281     return false;
    282 
    283   HANDLE process_token;
    284   if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token))
    285     return false;
    286 
    287   TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
    288   DWORD size_returned = 0;
    289   if (!::GetTokenInformation(process_token, TokenElevationType,
    290                              &elevation_type, sizeof(elevation_type),
    291                              &size_returned)) {
    292     ::CloseHandle(process_token);
    293     return false;
    294   }
    295 
    296   ::CloseHandle(process_token);
    297   return (elevation_type == TokenElevationTypeFull);
    298 }
    299 
    300 bool GetUserIdForProcess(size_t pid, wchar_t** user_sid) {
    301   HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
    302   if (process_handle == NULL)
    303     return false;
    304 
    305   HANDLE process_token;
    306   bool result = false;
    307   if (::OpenProcessToken(process_handle, TOKEN_QUERY, &process_token)) {
    308     DWORD size = 0;
    309     ::GetTokenInformation(process_token, TokenUser, NULL, 0, &size);
    310     if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER ||
    311         ::GetLastError() == ERROR_SUCCESS) {
    312       DWORD actual_size = 0;
    313       BYTE* token_user = new BYTE[size];
    314       if ((::GetTokenInformation(process_token, TokenUser, token_user, size,
    315                                 &actual_size)) &&
    316           (actual_size <= size)) {
    317         PSID sid = reinterpret_cast<TOKEN_USER*>(token_user)->User.Sid;
    318         if (::ConvertSidToStringSid(sid, user_sid))
    319           result = true;
    320       }
    321       delete[] token_user;
    322     }
    323     ::CloseHandle(process_token);
    324   }
    325   ::CloseHandle(process_handle);
    326   return result;
    327 }
    328 
    329 struct SetWindowPosParams {
    330   int x;
    331   int y;
    332   int width;
    333   int height;
    334   DWORD flags;
    335   HWND window_insert_after;
    336   bool success;
    337   std::set<HWND> shunted_hwnds;
    338 };
    339 
    340 BOOL CALLBACK ChromeWindowEnumProc(HWND hwnd, LPARAM lparam) {
    341   wchar_t window_class[MAX_PATH] = {};
    342   SetWindowPosParams* params = reinterpret_cast<SetWindowPosParams*>(lparam);
    343 
    344   if (!params->shunted_hwnds.count(hwnd) &&
    345       ::GetClassName(hwnd, window_class, arraysize(window_class)) &&
    346       StartsWith(window_class, kChromeWindowClassPrefix, false) &&
    347       ::SetWindowPos(hwnd, params->window_insert_after, params->x,
    348                      params->y, params->width, params->height, params->flags)) {
    349     params->shunted_hwnds.insert(hwnd);
    350     params->success = true;
    351   }
    352 
    353   // Return TRUE to ensure we hit all possible top-level Chrome windows as per
    354   // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633498.aspx
    355   return TRUE;
    356 }
    357 
    358 // Returns true and populates |chrome_exe_path| with the path to chrome.exe if
    359 // a valid installation can be found.
    360 bool GetGoogleChromePath(base::FilePath* chrome_exe_path) {
    361   HKEY install_key = HKEY_LOCAL_MACHINE;
    362   if (!IsChromeInstalled(install_key)) {
    363     install_key = HKEY_CURRENT_USER;
    364     if (!IsChromeInstalled(install_key)) {
    365       return false;
    366     }
    367   }
    368 
    369   // Now grab the uninstall string from the appropriate ClientState key
    370   // and use that as the base for a path to chrome.exe.
    371   *chrome_exe_path =
    372       chrome_launcher_support::GetChromePathForInstallationLevel(
    373           install_key == HKEY_LOCAL_MACHINE ?
    374               chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION :
    375               chrome_launcher_support::USER_LEVEL_INSTALLATION);
    376   return !chrome_exe_path->empty();
    377 }
    378 
    379 }  // namespace
    380 
    381 BOOL __stdcall GoogleChromeCompatibilityCheck(BOOL set_flag,
    382                                               int shell_mode,
    383                                               DWORD* reasons) {
    384   DWORD local_reasons = 0;
    385 
    386   WindowsVersion windows_version = GetWindowsVersion();
    387   // System requirements?
    388   if (windows_version == VERSION_BELOW_XP_SP2)
    389     local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
    390 
    391   if (IsChromeInstalled(HKEY_LOCAL_MACHINE))
    392     local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT;
    393 
    394   if (IsChromeInstalled(HKEY_CURRENT_USER))
    395     local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
    396 
    397   if (shell_mode == GCAPI_INVOKED_UAC_ELEVATION) {
    398     // Only check that we have HKLM write permissions if we specify that
    399     // GCAPI is being invoked from an elevated shell, or in admin mode
    400     if (!VerifyHKLMAccess()) {
    401     local_reasons |= GCCC_ERROR_ACCESSDENIED;
    402     } else if ((windows_version == VERSION_VISTA_OR_HIGHER) &&
    403          !VerifyAdminGroup()) {
    404     // For Vista or later check for elevation since even for admin user we could
    405     // be running in non-elevated mode. We require integrity level High.
    406     local_reasons |= GCCC_ERROR_INTEGRITYLEVEL;
    407     }
    408   }
    409 
    410   // Then only check whether we can re-offer, if everything else is OK.
    411   if (local_reasons == 0 && !CanReOfferChrome(set_flag))
    412     local_reasons |= GCCC_ERROR_ALREADYOFFERED;
    413 
    414   // Done. Copy/return results.
    415   if (reasons != NULL)
    416     *reasons = local_reasons;
    417 
    418   return (local_reasons == 0);
    419 }
    420 
    421 BOOL __stdcall LaunchGoogleChrome() {
    422   base::FilePath chrome_exe_path;
    423   if (!GetGoogleChromePath(&chrome_exe_path))
    424     return false;
    425 
    426   ScopedCOMInitializer com_initializer;
    427   if (::CoInitializeSecurity(NULL, -1, NULL, NULL,
    428                              RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
    429                              RPC_C_IMP_LEVEL_IDENTIFY, NULL,
    430                              EOAC_DYNAMIC_CLOAKING, NULL) != S_OK) {
    431     return false;
    432   }
    433 
    434   bool impersonation_success = false;
    435   if (IsRunningElevated()) {
    436     wchar_t* curr_proc_sid;
    437     if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid)) {
    438       return false;
    439     }
    440 
    441     DWORD pid = 0;
    442     ::GetWindowThreadProcessId(::GetShellWindow(), &pid);
    443     if (pid <= 0) {
    444       ::LocalFree(curr_proc_sid);
    445       return false;
    446     }
    447 
    448     wchar_t* exp_proc_sid;
    449     if (GetUserIdForProcess(pid, &exp_proc_sid)) {
    450       if (_wcsicmp(curr_proc_sid, exp_proc_sid) == 0) {
    451         ScopedHandle process_handle(
    452             ::OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
    453                           TRUE,
    454                           pid));
    455         if (process_handle.IsValid()) {
    456           HANDLE process_token = NULL;
    457           HANDLE user_token = NULL;
    458           if (::OpenProcessToken(process_handle, TOKEN_DUPLICATE | TOKEN_QUERY,
    459                                  &process_token) &&
    460               ::DuplicateTokenEx(process_token,
    461                                  TOKEN_IMPERSONATE | TOKEN_QUERY |
    462                                      TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
    463                                  NULL, SecurityImpersonation,
    464                                  TokenPrimary, &user_token) &&
    465               (::ImpersonateLoggedOnUser(user_token) != 0)) {
    466             impersonation_success = true;
    467           }
    468           if (user_token)
    469             ::CloseHandle(user_token);
    470           if (process_token)
    471             ::CloseHandle(process_token);
    472         }
    473       }
    474       ::LocalFree(exp_proc_sid);
    475     }
    476 
    477     ::LocalFree(curr_proc_sid);
    478     if (!impersonation_success) {
    479       return false;
    480     }
    481   }
    482 
    483   bool ret = false;
    484   ScopedComPtr<IProcessLauncher> ipl;
    485   if (SUCCEEDED(ipl.CreateInstance(__uuidof(ProcessLauncherClass),
    486                                    NULL,
    487                                    CLSCTX_LOCAL_SERVER))) {
    488     if (SUCCEEDED(ipl->LaunchCmdLine(chrome_exe_path.value().c_str())))
    489       ret = true;
    490     ipl.Release();
    491   } else {
    492     // Couldn't get Omaha's process launcher, Omaha may not be installed at
    493     // system level. Try just running Chrome instead.
    494     ret = base::LaunchProcess(chrome_exe_path.value(),
    495                               base::LaunchOptions(),
    496                               NULL);
    497   }
    498 
    499   if (impersonation_success)
    500     ::RevertToSelf();
    501   return ret;
    502 }
    503 
    504 BOOL __stdcall LaunchGoogleChromeWithDimensions(int x,
    505                                                 int y,
    506                                                 int width,
    507                                                 int height,
    508                                                 bool in_background) {
    509   if (in_background) {
    510     base::FilePath chrome_exe_path;
    511     if (!GetGoogleChromePath(&chrome_exe_path))
    512       return false;
    513 
    514     // When launching in the background, use WMI to ensure that chrome.exe is
    515     // is not our child process. This prevents it from pushing itself to
    516     // foreground.
    517     CommandLine chrome_command(chrome_exe_path);
    518 
    519     ScopedCOMInitializer com_initializer;
    520     if (!installer::WMIProcess::Launch(chrome_command.GetCommandLineString(),
    521                                        NULL)) {
    522       // For some reason WMI failed. Try and launch the old fashioned way,
    523       // knowing that visual glitches will occur when the window pops up.
    524       if (!LaunchGoogleChrome())
    525         return false;
    526     }
    527 
    528   } else {
    529     if (!LaunchGoogleChrome())
    530       return false;
    531   }
    532 
    533   HWND hwnd_insert_after = in_background ? HWND_BOTTOM : NULL;
    534   DWORD set_window_flags = in_background ? SWP_NOACTIVATE : SWP_NOZORDER;
    535 
    536   if (x == -1 && y == -1)
    537     set_window_flags |= SWP_NOMOVE;
    538 
    539   if (width == -1 && height == -1)
    540     set_window_flags |= SWP_NOSIZE;
    541 
    542   SetWindowPosParams enum_params = { x, y, width, height, set_window_flags,
    543                                      hwnd_insert_after, false };
    544 
    545   // Chrome may have been launched, but the window may not have appeared
    546   // yet. Wait for it to appear for 10 seconds, but exit if it takes longer
    547   // than that.
    548   int ms_elapsed = 0;
    549   int timeout = 10000;
    550   bool found_window = false;
    551   while (ms_elapsed < timeout) {
    552     // Enum all top-level windows looking for Chrome windows.
    553     ::EnumWindows(ChromeWindowEnumProc, reinterpret_cast<LPARAM>(&enum_params));
    554 
    555     // Give it five more seconds after finding the first window until we stop
    556     // shoving new windows into the background.
    557     if (!found_window && enum_params.success) {
    558       found_window = true;
    559       timeout = ms_elapsed + 5000;
    560     }
    561 
    562     Sleep(10);
    563     ms_elapsed += 10;
    564   }
    565 
    566   return found_window;
    567 }
    568 
    569 BOOL __stdcall LaunchGoogleChromeInBackground() {
    570   return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
    571 }
    572 
    573 int __stdcall GoogleChromeDaysSinceLastRun() {
    574   int days_since_last_run = std::numeric_limits<int>::max();
    575 
    576   if (IsChromeInstalled(HKEY_LOCAL_MACHINE) ||
    577       IsChromeInstalled(HKEY_CURRENT_USER)) {
    578     RegKey client_state(
    579         HKEY_CURRENT_USER, kChromeRegClientStateKey, KEY_QUERY_VALUE);
    580     if (client_state.Valid()) {
    581       std::wstring last_run;
    582       int64 last_run_value = 0;
    583       if (client_state.ReadValue(google_update::kRegLastRunTimeField,
    584                                  &last_run) == ERROR_SUCCESS &&
    585           base::StringToInt64(last_run, &last_run_value)) {
    586         Time last_run_time = Time::FromInternalValue(last_run_value);
    587         TimeDelta difference = Time::NowFromSystemTime() - last_run_time;
    588 
    589         // We can end up with negative numbers here, given changes in system
    590         // clock time or due to TimeDelta's int64 -> int truncation.
    591         int new_days_since_last_run = difference.InDays();
    592         if (new_days_since_last_run >= 0 &&
    593             new_days_since_last_run < days_since_last_run) {
    594           days_since_last_run = new_days_since_last_run;
    595         }
    596       }
    597     }
    598   }
    599 
    600   if (days_since_last_run == std::numeric_limits<int>::max()) {
    601     days_since_last_run = -1;
    602   }
    603 
    604   return days_since_last_run;
    605 }
    606 
    607 BOOL __stdcall CanOfferReactivation(const wchar_t* brand_code,
    608                                     int shell_mode,
    609                                     DWORD* error_code) {
    610   DCHECK(error_code);
    611 
    612   if (!brand_code) {
    613     if (error_code)
    614       *error_code = REACTIVATE_ERROR_INVALID_INPUT;
    615     return FALSE;
    616   }
    617 
    618   int days_since_last_run = GoogleChromeDaysSinceLastRun();
    619   if (days_since_last_run >= 0 &&
    620       days_since_last_run < kReactivationMinDaysDormant) {
    621     if (error_code)
    622       *error_code = REACTIVATE_ERROR_NOTDORMANT;
    623     return FALSE;
    624   }
    625 
    626   // Only run the code below when this function is invoked from a standard,
    627   // non-elevated cmd shell.  This is because this section of code looks at
    628   // values in HKEY_CURRENT_USER, and we only want to look at the logged-in
    629   // user's HKCU, not the admin user's HKCU.
    630   if (shell_mode == GCAPI_INVOKED_STANDARD_SHELL) {
    631     if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
    632         !IsChromeInstalled(HKEY_CURRENT_USER)) {
    633       if (error_code)
    634         *error_code = REACTIVATE_ERROR_NOTINSTALLED;
    635       return FALSE;
    636     }
    637 
    638     if (HasBeenReactivated()) {
    639       if (error_code)
    640         *error_code = REACTIVATE_ERROR_ALREADY_REACTIVATED;
    641       return FALSE;
    642     }
    643   }
    644 
    645   return TRUE;
    646 }
    647 
    648 BOOL __stdcall ReactivateChrome(wchar_t* brand_code,
    649                                 int shell_mode,
    650                                 DWORD* error_code) {
    651   BOOL result = FALSE;
    652   if (CanOfferReactivation(brand_code,
    653                            shell_mode,
    654                            error_code)) {
    655     if (SetReactivationBrandCode(brand_code, shell_mode)) {
    656       // Currently set this as a best-effort thing. We return TRUE if
    657       // reactivation succeeded regardless of the experiment label result.
    658       SetReactivationExperimentLabels(brand_code, shell_mode);
    659 
    660       result = TRUE;
    661     } else {
    662       if (error_code)
    663         *error_code = REACTIVATE_ERROR_REACTIVATION_FAILED;
    664     }
    665   }
    666 
    667   return result;
    668 }
    669