| OLD | NEW |
| (Empty) |
| 1 // Copyright 2007-2010 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 | |
| 16 #include "omaha/client/install.h" | |
| 17 #include "omaha/client/install_internal.h" | |
| 18 #include "omaha/base/app_util.h" | |
| 19 #include "omaha/base/const_object_names.h" | |
| 20 #include "omaha/base/debug.h" | |
| 21 #include "omaha/base/error.h" | |
| 22 #include "omaha/base/file.h" | |
| 23 #include "omaha/base/logging.h" | |
| 24 #include "omaha/base/omaha_version.h" | |
| 25 #include "omaha/base/path.h" | |
| 26 #include "omaha/base/process.h" | |
| 27 #include "omaha/base/reg_key.h" | |
| 28 #include "omaha/base/scoped_any.h" | |
| 29 #include "omaha/base/string.h" | |
| 30 #include "omaha/base/time.h" | |
| 31 #include "omaha/base/utils.h" | |
| 32 #include "omaha/base/vistautil.h" | |
| 33 #include "omaha/client/client_utils.h" | |
| 34 #include "omaha/client/install_self.h" | |
| 35 #include "omaha/client/resource.h" | |
| 36 #include "omaha/common/app_registry_utils.h" | |
| 37 #include "omaha/common/command_line_builder.h" | |
| 38 #include "omaha/common/config_manager.h" | |
| 39 #include "omaha/common/const_cmd_line.h" | |
| 40 #include "omaha/common/const_goopdate.h" | |
| 41 #include "omaha/common/goopdate_utils.h" | |
| 42 #include "omaha/common/oem_install_utils.h" | |
| 43 #include "omaha/common/ping.h" | |
| 44 #include "omaha/setup/setup_metrics.h" | |
| 45 #include "omaha/ui/splash_screen.h" | |
| 46 | |
| 47 namespace omaha { | |
| 48 | |
| 49 namespace { | |
| 50 | |
| 51 // Returns whether elevation is required. | |
| 52 bool IsElevationRequired(bool is_machine) { | |
| 53 return is_machine && !vista_util::IsUserAdmin(); | |
| 54 } | |
| 55 | |
| 56 } // namespace | |
| 57 | |
| 58 namespace internal { | |
| 59 | |
| 60 // TODO(omaha3): Make this elevate the metainstaller instead of | |
| 61 // GoogleUpdate.exe so the files are extracted to a secure location. | |
| 62 // May need to add all languages to the metainstaller's version resources so | |
| 63 // the user sees the localized string in the UAC. | |
| 64 // TODO(omaha3): We will need to save the metainstaller for OneClick | |
| 65 // cross-installs. We may need to change the metainstaller behavior to not | |
| 66 // use the tag if any command line args are provided. This will allow us to | |
| 67 // reuse a tagged metainstaller that we saved for other purposes, such as | |
| 68 // OneClick cross-installs. | |
| 69 // TODO(omaha3): The "metainstaller" may also be some type of wrapper around | |
| 70 // a differential update. We'll address that later. This wrapper should not | |
| 71 // need localized resource strings since it always runs silently. | |
| 72 HRESULT DoElevation(bool is_interactive, | |
| 73 bool is_install_elevated_instance, | |
| 74 const CString& cmd_line, | |
| 75 DWORD* exit_code) { | |
| 76 ASSERT1(exit_code); | |
| 77 ASSERT1(IsElevationRequired(true)); | |
| 78 | |
| 79 if (!is_interactive) { | |
| 80 return GOOPDATE_E_SILENT_INSTALL_NEEDS_ELEVATION; | |
| 81 } | |
| 82 | |
| 83 if (is_install_elevated_instance) { | |
| 84 // This can happen if UAC is disabled. See http://b/1187784. | |
| 85 CORE_LOG(LE, (_T("[Install elevated process requires elevation]"))); | |
| 86 return GOOPDATE_E_INSTALL_ELEVATED_PROCESS_NEEDS_ELEVATION; | |
| 87 } | |
| 88 | |
| 89 HRESULT hr = ElevateAndWait(cmd_line, exit_code); | |
| 90 if (FAILED(hr)) { | |
| 91 CORE_LOG(LE, (_T("[ElevateAndWait failed][%s][0x%08x]"), cmd_line, hr)); | |
| 92 } | |
| 93 | |
| 94 return hr; | |
| 95 } | |
| 96 | |
| 97 // Assumes it is running with the necessary privileges. | |
| 98 HRESULT DoInstall(bool is_machine, | |
| 99 bool is_app_install, | |
| 100 bool is_eula_required, | |
| 101 bool is_oem_install, | |
| 102 const CString& current_version, | |
| 103 const CommandLineArgs& args, | |
| 104 const CString& session_id, | |
| 105 SplashScreen* splash_screen, | |
| 106 int* extra_code1, | |
| 107 bool* has_setup_succeeded, | |
| 108 bool* has_launched_handoff, | |
| 109 bool* has_ui_been_displayed) { | |
| 110 ASSERT1(!IsElevationRequired(is_machine)); | |
| 111 ASSERT1(extra_code1); | |
| 112 ASSERT1(splash_screen); | |
| 113 ASSERT1(has_setup_succeeded); | |
| 114 ASSERT1(has_launched_handoff); | |
| 115 ASSERT1(has_ui_been_displayed); | |
| 116 | |
| 117 *extra_code1 = 0; | |
| 118 | |
| 119 // TODO(omaha3): We may need to take an "installing apps" lock here if | |
| 120 // is_app_install. There was code in Omaha 2 that relied on the fact that | |
| 121 // the Setup lock was held while the install worker was launched, even in | |
| 122 // handoff scenarios. This allowed checking for the Setup lock and running | |
| 123 // install workers to be sufficient to ensure that the number of Clients keys | |
| 124 // was stable. Note that Omaha 2 also held the shutdown event while the worker | |
| 125 // was launched. | |
| 126 // TODO(omaha3): Consider taking the Setup lock in this file (via a public | |
| 127 // method in Setup) and releasing it. This would allow us to address the above | |
| 128 // issue and maybe the EULA not accepted issue. | |
| 129 // TODO(omaha3): We may also want to call ShouldInstall after a Lock(0) to | |
| 130 // determine whether we even need to display the splash screen or we can skip | |
| 131 // it and Setup altogether and just launch the /handoff process. | |
| 132 | |
| 133 HRESULT hr = install_self::InstallSelf(is_machine, | |
| 134 is_eula_required, | |
| 135 is_oem_install, | |
| 136 current_version, | |
| 137 args.install_source, | |
| 138 args.extra, | |
| 139 session_id, | |
| 140 extra_code1); | |
| 141 *has_setup_succeeded = SUCCEEDED(hr); | |
| 142 if (FAILED(hr)) { | |
| 143 OPT_LOG(LE, (_T("[InstallSelf failed][0x%08x]"), hr)); | |
| 144 return hr; | |
| 145 } | |
| 146 | |
| 147 if (!is_app_install) { | |
| 148 return S_OK; | |
| 149 } | |
| 150 | |
| 151 hr = InstallApplications(is_machine, | |
| 152 is_eula_required, | |
| 153 args, | |
| 154 session_id, | |
| 155 splash_screen, | |
| 156 has_ui_been_displayed, | |
| 157 has_launched_handoff); | |
| 158 if (FAILED(hr)) { | |
| 159 OPT_LOG(LE, (_T("[InstallApplications failed][0x%08x]"), hr)); | |
| 160 return hr; | |
| 161 } | |
| 162 | |
| 163 return S_OK; | |
| 164 }; | |
| 165 | |
| 166 HRESULT InstallApplications(bool is_machine, | |
| 167 bool is_eula_required, | |
| 168 const CommandLineArgs& args, | |
| 169 const CString& session_id, | |
| 170 SplashScreen* splash_screen, | |
| 171 bool* has_ui_been_displayed, | |
| 172 bool* has_launched_handoff) { | |
| 173 ASSERT1(has_ui_been_displayed); | |
| 174 ASSERT1(has_launched_handoff); | |
| 175 | |
| 176 *has_launched_handoff = false; | |
| 177 | |
| 178 CString offline_dir; | |
| 179 const bool is_offline_install = CopyOfflineFiles(is_machine, | |
| 180 args.extra.apps, | |
| 181 &offline_dir); | |
| 182 if (!is_offline_install && oem_install_utils::IsOemInstalling(is_machine)) { | |
| 183 return GOOPDATE_E_OEM_WITH_ONLINE_INSTALLER; | |
| 184 } | |
| 185 if (!is_offline_install && is_eula_required) { | |
| 186 return GOOPDATE_E_EULA_REQURED_WITH_ONLINE_INSTALLER; | |
| 187 } | |
| 188 | |
| 189 // Start the handoff to install the app. | |
| 190 scoped_process handoff_process; | |
| 191 HRESULT hr = LaunchHandoffProcess(is_machine, | |
| 192 offline_dir, | |
| 193 args, | |
| 194 session_id, | |
| 195 address(handoff_process)); | |
| 196 if (FAILED(hr)) { | |
| 197 OPT_LOG(LE, (_T("[Failed to launch installed instance][0x%08x]"), hr)); | |
| 198 return hr; | |
| 199 } | |
| 200 | |
| 201 *has_launched_handoff = true; | |
| 202 | |
| 203 OPT_LOG(L1, (_T("[Waiting for application install to complete]"))); | |
| 204 uint32 exit_code(0); | |
| 205 hr = WaitForProcessExit(get(handoff_process), | |
| 206 splash_screen, | |
| 207 has_ui_been_displayed, | |
| 208 &exit_code); | |
| 209 | |
| 210 if (FAILED(hr)) { | |
| 211 OPT_LOG(LE, (_T("[Failed waiting for app install][0x%08x]"), hr)); | |
| 212 return hr; | |
| 213 } | |
| 214 if (exit_code) { | |
| 215 OPT_LOG(LE, (_T("[Handoff exited with error][0x%08x]"), exit_code)); | |
| 216 ASSERT1(FAILED(exit_code)); | |
| 217 return exit_code; | |
| 218 } | |
| 219 | |
| 220 return S_OK; | |
| 221 } | |
| 222 | |
| 223 // The behavior depends on the OS: | |
| 224 // 1. OS < Vista : Fail. | |
| 225 // 2. OS >= Vista : Try to elevate - causes a UAC dialog. | |
| 226 // We should be here only in case of initial machine installs when the user is | |
| 227 // not an elevated admin. | |
| 228 HRESULT ElevateAndWait(const CString& cmd_line, DWORD* exit_code) { | |
| 229 OPT_LOG(L1, (_T("[Elevating][%s]"), cmd_line)); | |
| 230 ASSERT1(!vista_util::IsUserAdmin()); | |
| 231 ASSERT1(exit_code); | |
| 232 | |
| 233 if (!vista_util::IsVistaOrLater()) { | |
| 234 // TODO(omaha): We could consider to ask for credentials here. | |
| 235 // This TODO existed in Omaha 1. How would we even do this? | |
| 236 CORE_LOG(LE, (_T("[Non Admin trying to install admin app]"))); | |
| 237 ++metric_setup_machine_app_non_admin; | |
| 238 return GOOPDATE_E_NONADMIN_INSTALL_ADMIN_APP; | |
| 239 } | |
| 240 | |
| 241 CString cmd_line_elevated(GetCmdLineTail(cmd_line)); | |
| 242 cmd_line_elevated.AppendFormat(_T(" /%s"), kCmdLineInstallElevated); | |
| 243 | |
| 244 HRESULT hr = goopdate_utils::StartElevatedSelfWithArgsAndWait( | |
| 245 cmd_line_elevated, exit_code); | |
| 246 if (FAILED(hr)) { | |
| 247 OPT_LOG(LE, (_T("[Starting elevated GoogleUpdate.exe failed][%s][0x%08x]"), | |
| 248 cmd_line, hr)); | |
| 249 | |
| 250 // TODO(omaha3): Report hr somehow. Was reported in extra code in Omaha 2. | |
| 251 if (vista_util::IsUserNonElevatedAdmin()) { | |
| 252 return GOOPDATE_E_ELEVATION_FAILED_ADMIN; | |
| 253 } else { | |
| 254 return GOOPDATE_E_ELEVATION_FAILED_NON_ADMIN; | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 return S_OK; | |
| 259 } | |
| 260 | |
| 261 bool CopyOfflineFiles(bool is_machine, | |
| 262 const std::vector<CommandLineAppArgs>& apps, | |
| 263 CString* offline_dir) { | |
| 264 ASSERT1(offline_dir); | |
| 265 offline_dir->Empty(); | |
| 266 | |
| 267 if (apps.empty()) { | |
| 268 return false; | |
| 269 } | |
| 270 | |
| 271 GUID guid(GUID_NULL); | |
| 272 VERIFY1(SUCCEEDED(::CoCreateGuid(&guid))); | |
| 273 CString parent_offline_dir( | |
| 274 is_machine ? | |
| 275 ConfigManager::Instance()->GetMachineSecureOfflineStorageDir() : | |
| 276 ConfigManager::Instance()->GetUserOfflineStorageDir()); | |
| 277 CString unique_offline_dir = | |
| 278 ConcatenatePath(parent_offline_dir, GuidToString(guid)); | |
| 279 VERIFY1(SUCCEEDED(CreateDir(unique_offline_dir, NULL))); | |
| 280 | |
| 281 HRESULT hr = CopyOfflineManifest(unique_offline_dir); | |
| 282 if (FAILED(hr)) { | |
| 283 CORE_LOG(L3, (_T("[CopyOfflineManifest failed][0x%08x]"), hr)); | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 for (size_t i = 0; i < apps.size(); ++i) { | |
| 288 const GUID& app_id = apps[i].app_guid; | |
| 289 HRESULT hr = CopyOfflineFilesForApp(GuidToString(app_id), | |
| 290 unique_offline_dir); | |
| 291 if (FAILED(hr)) { | |
| 292 ASSERT(false, (_T("[CopyOfflineFilesForApp failed][0x%08x]"), hr)); | |
| 293 return false; | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 CORE_LOG(L3, (_T("[CopyOfflineFiles done][%s]"), unique_offline_dir)); | |
| 298 *offline_dir = unique_offline_dir; | |
| 299 return true; | |
| 300 } | |
| 301 | |
| 302 HRESULT CopyOfflineManifest(const CString& offline_dir) { | |
| 303 CORE_LOG(L3, (_T("[CopyOfflineManifest][%s]"), offline_dir)); | |
| 304 | |
| 305 // Copy offline manifest into "<offline_dir>\<kOfflineManifestFileName>". | |
| 306 CString setup_temp_dir(app_util::GetCurrentModuleDirectory()); | |
| 307 CString source_manifest_path = ConcatenatePath(setup_temp_dir, | |
| 308 kOfflineManifestFileName); | |
| 309 if (!File::Exists(source_manifest_path)) { | |
| 310 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); | |
| 311 } | |
| 312 CString dest_manifest_path = ConcatenatePath(offline_dir, | |
| 313 kOfflineManifestFileName); | |
| 314 HRESULT hr = File::Copy(source_manifest_path, dest_manifest_path, true); | |
| 315 if (FAILED(hr)) { | |
| 316 CORE_LOG(LE, (_T("[File copy failed][%s][%s][0x%08x]"), | |
| 317 source_manifest_path, dest_manifest_path, hr)); | |
| 318 return hr; | |
| 319 } | |
| 320 | |
| 321 return S_OK; | |
| 322 } | |
| 323 | |
| 324 HRESULT CopyOfflineFilesForApp(const CString& app_id, | |
| 325 const CString& offline_dir) { | |
| 326 CORE_LOG(L3, (_T("[CopyOfflineFilesForApp][%s][%s]"), | |
| 327 app_id, offline_dir)); | |
| 328 | |
| 329 CString setup_temp_dir(app_util::GetCurrentModuleDirectory()); | |
| 330 CString pattern; | |
| 331 // Copy the installer files that are named with the pattern "*.<app_id>" to | |
| 332 // the directory "<offline_dir>\<app_id>". | |
| 333 pattern.Format(_T("*.%s"), app_id); | |
| 334 std::vector<CString> files; | |
| 335 HRESULT hr = FindFiles(setup_temp_dir, pattern, &files); | |
| 336 if (FAILED(hr)) { | |
| 337 CORE_LOG(LE, (_T("[FindFiles failed][0x%08x]"), hr)); | |
| 338 return hr; | |
| 339 } | |
| 340 if (files.empty()) { | |
| 341 CORE_LOG(LE, (_T("[FindFiles found no files]"))); | |
| 342 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); | |
| 343 } | |
| 344 | |
| 345 CString offline_app_dir = ConcatenatePath(offline_dir, app_id); | |
| 346 if (File::IsDirectory(offline_app_dir)) { | |
| 347 VERIFY1(SUCCEEDED(DeleteDirectoryFiles(offline_app_dir))); | |
| 348 } else { | |
| 349 hr = CreateDir(offline_app_dir, NULL); | |
| 350 if (FAILED(hr)) { | |
| 351 CORE_LOG(LE, (_T("[CreateDir failed][%s]"), offline_app_dir)); | |
| 352 return hr; | |
| 353 } | |
| 354 } | |
| 355 | |
| 356 for (size_t i = 0; i < files.size(); ++i) { | |
| 357 const CString& file_name = files[i]; | |
| 358 ASSERT1(file_name.GetLength() > app_id.GetLength()); | |
| 359 | |
| 360 CPath renamed_file_name(file_name); | |
| 361 renamed_file_name.RemoveExtension(); | |
| 362 ASSERT1(file_name.Left(file_name.GetLength() - app_id.GetLength() - 1) == | |
| 363 static_cast<const CString&>(renamed_file_name)); | |
| 364 CString new_file_path = ConcatenatePath(offline_app_dir, renamed_file_name); | |
| 365 CORE_LOG(L4, (_T("[new_file_path][%s]"), new_file_path)); | |
| 366 | |
| 367 CString source_file_path = ConcatenatePath(setup_temp_dir, file_name); | |
| 368 hr = File::Copy(source_file_path, new_file_path, true); | |
| 369 if (FAILED(hr)) { | |
| 370 CORE_LOG(LE, (_T("[Copy failed][%s][%s]"), | |
| 371 source_file_path, new_file_path)); | |
| 372 return hr; | |
| 373 } | |
| 374 } | |
| 375 | |
| 376 return S_OK; | |
| 377 } | |
| 378 | |
| 379 | |
| 380 // TODO(omaha3): This implementation requires updating this code whenever a | |
| 381 // new option is added. We could do a string replace of "/install" with | |
| 382 // "/handoff", but we'd need to remove things such as /oem. Alternatively, | |
| 383 // allow a CommandLineBuilder to be populated with existing args. Doing | |
| 384 // replacement would allow us to only pass one copy of the command line to | |
| 385 // Install() instead of passing both the string and struct. Be sure to handle | |
| 386 // /appargs when making any changes. | |
| 387 // TODO(omaha): Extract the command line building and unit test it. | |
| 388 HRESULT LaunchHandoffProcess(bool is_machine, | |
| 389 const CString& offline_dir, | |
| 390 const CommandLineArgs& install_args, | |
| 391 const CString& session_id, | |
| 392 HANDLE* process) { // process can be NULL. | |
| 393 CORE_LOG(L2, (_T("[LaunchHandoffProcess]"))); | |
| 394 | |
| 395 // The install source has been either specified or set to a default value by | |
| 396 // the time the execution flow reaches this function. The install source is | |
| 397 // propagated to the handoff process except in certain offline install cases, | |
| 398 // such as a tagged offline installer or an offline installer that did not | |
| 399 // have an install source. | |
| 400 // | |
| 401 // TODO(omaha): refactor so that the handling of the install source is | |
| 402 // encapsulated in just one module. | |
| 403 ASSERT1(!install_args.install_source.IsEmpty()); | |
| 404 ASSERT1(!install_args.extra_args_str.IsEmpty()); | |
| 405 | |
| 406 CommandLineBuilder builder(COMMANDLINE_MODE_HANDOFF_INSTALL); | |
| 407 | |
| 408 builder.set_is_silent_set(install_args.is_silent_set); | |
| 409 builder.set_is_eula_required_set(install_args.is_eula_required_set); | |
| 410 builder.set_extra_args(install_args.extra_args_str); | |
| 411 builder.set_app_args(install_args.app_args_str); | |
| 412 builder.set_install_source(install_args.install_source); | |
| 413 builder.set_session_id(session_id); | |
| 414 | |
| 415 if (!offline_dir.IsEmpty()) { | |
| 416 builder.SetOfflineDir(offline_dir); | |
| 417 const CString& install_source(install_args.install_source); | |
| 418 if (install_source == kCmdLineInstallSource_InstallDefault || | |
| 419 install_source == kCmdLineInstallSource_TaggedMetainstaller) { | |
| 420 builder.set_install_source(kCmdLineInstallSource_Offline); | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 CString cmd_line = builder.GetCommandLineArgs(); | |
| 425 | |
| 426 HRESULT hr = goopdate_utils::StartGoogleUpdateWithArgs(is_machine, | |
| 427 cmd_line, | |
| 428 process); | |
| 429 if (FAILED(hr)) { | |
| 430 OPT_LOG(LE, (_T("[Google Update hand off failed][%s][0x%08x]"), | |
| 431 cmd_line, hr)); | |
| 432 // TODO(omaha3): Report hr somehow. Was reported in extra code in Omaha 2. | |
| 433 return GOOPDATE_E_HANDOFF_FAILED; | |
| 434 } | |
| 435 | |
| 436 return S_OK; | |
| 437 } | |
| 438 | |
| 439 HRESULT WaitForProcessExit(HANDLE process, | |
| 440 SplashScreen* splash_screen, | |
| 441 bool* has_ui_been_displayed, | |
| 442 uint32* exit_code) { | |
| 443 CORE_LOG(L3, (_T("[WaitForProcessExit]"))); | |
| 444 ASSERT1(process); | |
| 445 ASSERT1(exit_code); | |
| 446 ASSERT1(has_ui_been_displayed); | |
| 447 *exit_code = 0; | |
| 448 | |
| 449 int res = ::WaitForInputIdle(process, INFINITE); | |
| 450 if (res == 0) { | |
| 451 *has_ui_been_displayed = true; | |
| 452 } | |
| 453 if (splash_screen) { | |
| 454 splash_screen->Dismiss(); | |
| 455 } | |
| 456 | |
| 457 res = ::WaitForSingleObject(process, INFINITE); | |
| 458 ASSERT1(WAIT_OBJECT_0 == res); | |
| 459 if (WAIT_FAILED == res) { | |
| 460 DWORD error = ::GetLastError(); | |
| 461 CORE_LOG(LE, (_T("[::WaitForMultipleObjects failed][%u]"), error)); | |
| 462 return HRESULT_FROM_WIN32(error); | |
| 463 } | |
| 464 | |
| 465 // Get the exit code. | |
| 466 DWORD local_exit_code = 0; | |
| 467 if (::GetExitCodeProcess(process, &local_exit_code)) { | |
| 468 CORE_LOG(L2, (_T("[process exited][PID %u][exit code 0x%08x]"), | |
| 469 Process::GetProcessIdFromHandle(process), local_exit_code)); | |
| 470 *exit_code = local_exit_code; | |
| 471 } else { | |
| 472 DWORD error = ::GetLastError(); | |
| 473 CORE_LOG(LE, (_T("[::GetExitCodeProcess failed][%u]"), error)); | |
| 474 return HRESULT_FROM_WIN32(error); | |
| 475 } | |
| 476 | |
| 477 return S_OK; | |
| 478 } | |
| 479 | |
| 480 // TODO(omaha): needs to handle errors when loading the strings. | |
| 481 CString GetErrorText(HRESULT error, const CString& bundle_name) { | |
| 482 ASSERT1(!bundle_name.IsEmpty()); | |
| 483 | |
| 484 CString error_text; | |
| 485 | |
| 486 switch (error) { | |
| 487 case GOOPDATE_E_INSTALL_ELEVATED_PROCESS_NEEDS_ELEVATION: | |
| 488 case GOOPDATE_E_NONADMIN_INSTALL_ADMIN_APP: | |
| 489 error_text.FormatMessage(IDS_NEED_ADMIN_TO_INSTALL, bundle_name); | |
| 490 break; | |
| 491 case GOOPDATE_E_ELEVATION_FAILED_ADMIN: | |
| 492 case GOOPDATE_E_ELEVATION_FAILED_NON_ADMIN: | |
| 493 error_text.FormatMessage(IDS_ELEVATION_FAILED, bundle_name); | |
| 494 break; | |
| 495 case GOOPDATE_E_FAILED_TO_GET_LOCK: | |
| 496 case GOOPDATE_E_FAILED_TO_GET_LOCK_MATCHING_INSTALL_PROCESS_RUNNING: | |
| 497 case GOOPDATE_E_FAILED_TO_GET_LOCK_NONMATCHING_INSTALL_PROCESS_RUNNING: | |
| 498 case GOOPDATE_E_FAILED_TO_GET_LOCK_UPDATE_PROCESS_RUNNING: | |
| 499 { | |
| 500 CString company_name; | |
| 501 VERIFY1(company_name.LoadString(IDS_FRIENDLY_COMPANY_NAME)); | |
| 502 error_text.FormatMessage(IDS_APPLICATION_INSTALLING_GOOGLE_UPDATE, | |
| 503 company_name, | |
| 504 bundle_name); | |
| 505 } | |
| 506 break; | |
| 507 case GOOPDATE_E_INSTANCES_RUNNING: | |
| 508 { | |
| 509 CString company_name; | |
| 510 VERIFY1(company_name.LoadString(IDS_FRIENDLY_COMPANY_NAME)); | |
| 511 error_text.FormatMessage(IDS_INSTANCES_RUNNING_AFTER_SHUTDOWN, | |
| 512 company_name, | |
| 513 bundle_name); | |
| 514 } | |
| 515 break; | |
| 516 case GOOPDATE_E_RUNNING_INFERIOR_MSXML: | |
| 517 error_text.FormatMessage(IDS_WINDOWS_IS_NOT_UP_TO_DATE, bundle_name); | |
| 518 break; | |
| 519 case GOOPDATE_E_HANDOFF_FAILED: | |
| 520 error_text.FormatMessage(IDS_HANDOFF_FAILED, bundle_name); | |
| 521 break; | |
| 522 default: | |
| 523 { | |
| 524 CString product_name; | |
| 525 VERIFY1(product_name.LoadString(IDS_PRODUCT_DISPLAY_NAME)); | |
| 526 error_text.FormatMessage(IDS_SETUP_FAILED, product_name, error); | |
| 527 } | |
| 528 break; | |
| 529 } | |
| 530 | |
| 531 return error_text; | |
| 532 } | |
| 533 | |
| 534 // Displays an error in the Google Update UI and sends a ping if allowed. | |
| 535 void HandleInstallError(HRESULT error, | |
| 536 int extra_code1, | |
| 537 const CString& session_id, | |
| 538 bool is_machine, | |
| 539 bool is_interactive, | |
| 540 bool is_eula_required, | |
| 541 bool is_oem_install, | |
| 542 const CString& current_version, | |
| 543 const CString& install_source, | |
| 544 const CommandLineExtraArgs& extra_args, | |
| 545 bool has_setup_succeeded, | |
| 546 bool has_launched_handoff, | |
| 547 bool* has_ui_been_displayed) { | |
| 548 ASSERT1(FAILED(error)); | |
| 549 ASSERT1(has_ui_been_displayed); | |
| 550 | |
| 551 const CString& bundle_name(extra_args.bundle_name); | |
| 552 const CString error_text(GetErrorText(error, bundle_name)); | |
| 553 | |
| 554 OPT_LOG(LE, (_T("[Failed to install][0x%08x][%s]"), error, error_text)); | |
| 555 | |
| 556 if (is_interactive && !*has_ui_been_displayed) { | |
| 557 CString primary_app_id; | |
| 558 if (!extra_args.apps.empty()) { | |
| 559 primary_app_id = GuidToString(extra_args.apps[0].app_guid); | |
| 560 } | |
| 561 *has_ui_been_displayed = client_utils::DisplayError( | |
| 562 is_machine, | |
| 563 bundle_name, | |
| 564 error, | |
| 565 extra_code1, | |
| 566 error_text, | |
| 567 primary_app_id, | |
| 568 extra_args.language, | |
| 569 extra_args.installation_id, | |
| 570 extra_args.brand_code); | |
| 571 } | |
| 572 | |
| 573 // Send an EVENT_INSTALL_COMPLETE ping for Omaha and wait for the ping to be | |
| 574 // sent. This ping may cause a firewall prompt since the process sending the | |
| 575 // ping may run from a temporary directory. | |
| 576 // | |
| 577 // An EVENT_INSTALL_COMPLETE ping for the apps is also sent in the case the | |
| 578 /// handoff process did not launch successfully, otherwise, the handoff | |
| 579 // process is responsible for sending this ping. | |
| 580 // | |
| 581 // Setup can fail before setting either eula or oem states in the | |
| 582 // registry. Therefore, ConfigManager::CanUseNetwork can't be called yet. | |
| 583 if (is_eula_required || is_oem_install) { | |
| 584 return; | |
| 585 } | |
| 586 Ping ping(is_machine, session_id, install_source); | |
| 587 ping.LoadAppDataFromExtraArgs(extra_args); | |
| 588 if (!has_setup_succeeded) { | |
| 589 PingEventPtr setup_install_complete_ping_event( | |
| 590 new PingEvent(PingEvent::EVENT_INSTALL_COMPLETE, // Type 2. | |
| 591 PingEvent::EVENT_RESULT_ERROR, | |
| 592 error, | |
| 593 extra_code1)); | |
| 594 const CString next_version(GetVersionString()); | |
| 595 ping.BuildOmahaPing(current_version, | |
| 596 next_version, | |
| 597 setup_install_complete_ping_event); | |
| 598 } | |
| 599 if (!has_launched_handoff) { | |
| 600 PingEventPtr install_complete_ping_event( | |
| 601 new PingEvent(PingEvent::EVENT_INSTALL_COMPLETE, // Type 2. | |
| 602 PingEvent::EVENT_RESULT_ERROR, | |
| 603 error, | |
| 604 extra_code1)); | |
| 605 ping.BuildAppsPing(install_complete_ping_event); | |
| 606 } | |
| 607 HRESULT hr = ping.Send(false); | |
| 608 if (FAILED(hr)) { | |
| 609 CORE_LOG(LW, (_T("[Ping::Send failed][0x%x]"), hr)); | |
| 610 } | |
| 611 } | |
| 612 | |
| 613 } // namespace internal | |
| 614 | |
| 615 // This function handles command-line installs. This is the only function that | |
| 616 // can install Omaha in non-update cases. If elevation is required, the function | |
| 617 // elevates an instance of Omaha and waits for that instance to exit before | |
| 618 // returning. If needed, the function starts a handoff process to install the | |
| 619 // applications and waits for the handoff process to exit. | |
| 620 // | |
| 621 // 'install_cmd_line' parameter is the command line for the current instance and | |
| 622 // used to elevate if necessary. | |
| 623 // 'args' parameter is the parsed args corresponding to install_cmd_line and it | |
| 624 // is used to build the handoff command line if necessary. | |
| 625 HRESULT Install(bool is_interactive, | |
| 626 bool is_app_install, | |
| 627 bool is_eula_required, | |
| 628 bool is_oem_install, | |
| 629 bool is_install_elevated_instance, | |
| 630 const CString& install_cmd_line, | |
| 631 const CommandLineArgs& args, | |
| 632 bool* is_machine, | |
| 633 bool* has_ui_been_displayed) { | |
| 634 ASSERT1(!install_cmd_line.IsEmpty()); | |
| 635 ASSERT1(is_machine); | |
| 636 ASSERT1(has_ui_been_displayed); | |
| 637 | |
| 638 CORE_LOG(L2, (_T("[Install][%d][%d][%s]"), | |
| 639 *is_machine, is_interactive, install_cmd_line)); | |
| 640 ++metric_setup_install_total; | |
| 641 if (is_install_elevated_instance) { | |
| 642 ++metric_setup_uac_succeeded; | |
| 643 } | |
| 644 | |
| 645 if (!*is_machine && | |
| 646 vista_util::IsVistaOrLater() && | |
| 647 vista_util::IsUserAdmin()) { | |
| 648 ++metric_setup_user_app_admin; | |
| 649 } | |
| 650 | |
| 651 // Allocate a session ID to connect all update checks and pings involved | |
| 652 // with this run of Omaha. This will need to be passed along to any child | |
| 653 // processes we create. | |
| 654 CString session_id; | |
| 655 VERIFY1(SUCCEEDED(GetGuid(&session_id))); | |
| 656 | |
| 657 // 'current_version' corresponds to the value of 'pv' read from | |
| 658 // registry. This value is either empty, in the case of a new install or | |
| 659 // the version of the installed Omaha before the setup code ran. | |
| 660 CString current_version; | |
| 661 app_registry_utils::GetAppVersion(*is_machine, | |
| 662 kGoogleUpdateAppId, | |
| 663 ¤t_version); | |
| 664 | |
| 665 bool has_setup_succeeded = false; | |
| 666 bool has_launched_handoff = false; | |
| 667 | |
| 668 if (IsElevationRequired(*is_machine)) { | |
| 669 ASSERT1(!ConfigManager::Instance()->IsWindowsInstalling()); | |
| 670 | |
| 671 DWORD exit_code = 0; | |
| 672 HRESULT hr = internal::DoElevation(is_interactive, | |
| 673 is_install_elevated_instance, | |
| 674 install_cmd_line, | |
| 675 &exit_code); | |
| 676 | |
| 677 if (FAILED(hr)) { | |
| 678 CORE_LOG(LE, (_T("[DoElevation failed][%s][0x%08x]"), | |
| 679 install_cmd_line, hr)); | |
| 680 | |
| 681 bool attempt_per_user_install = false; | |
| 682 if (is_interactive && | |
| 683 args.extra.apps[0].needs_admin == NEEDS_ADMIN_PREFERS) { | |
| 684 if (hr == GOOPDATE_E_NONADMIN_INSTALL_ADMIN_APP || | |
| 685 hr == GOOPDATE_E_INSTALL_ELEVATED_PROCESS_NEEDS_ELEVATION) { | |
| 686 attempt_per_user_install = true; | |
| 687 } else { | |
| 688 *has_ui_been_displayed = client_utils::DisplayContinueAsNonAdmin( | |
| 689 args.extra.bundle_name, &attempt_per_user_install); | |
| 690 } | |
| 691 } | |
| 692 | |
| 693 if (attempt_per_user_install) { | |
| 694 *is_machine = false; | |
| 695 CString no_admin_cmd_line(String_ReplaceIgnoreCase(install_cmd_line, | |
| 696 kNeedsAdminPrefers, | |
| 697 kNeedsAdminNo)); | |
| 698 CommandLineArgs no_admin_args = args; | |
| 699 no_admin_args.extra.apps[0].needs_admin = NEEDS_ADMIN_NO; | |
| 700 no_admin_args.extra_args_str = String_ReplaceIgnoreCase( | |
| 701 no_admin_args.extra_args_str, kNeedsAdminPrefers, kNeedsAdminNo); | |
| 702 | |
| 703 return Install(is_interactive, | |
| 704 is_app_install, | |
| 705 is_eula_required, | |
| 706 is_oem_install, | |
| 707 is_install_elevated_instance, | |
| 708 no_admin_cmd_line, | |
| 709 no_admin_args, | |
| 710 is_machine, | |
| 711 has_ui_been_displayed); | |
| 712 } | |
| 713 | |
| 714 internal::HandleInstallError(hr, | |
| 715 0, | |
| 716 session_id, | |
| 717 *is_machine, | |
| 718 is_interactive, | |
| 719 is_eula_required, | |
| 720 is_oem_install, | |
| 721 current_version, | |
| 722 args.install_source, | |
| 723 args.extra, | |
| 724 has_setup_succeeded, | |
| 725 has_launched_handoff, | |
| 726 has_ui_been_displayed); | |
| 727 return hr; | |
| 728 } | |
| 729 | |
| 730 // TODO(omaha): waiting for input idle of an elevated process fails if the | |
| 731 // caller is not elevated. The code assumes that the elevated process | |
| 732 // displays UI if the elevation succeeded. It is possible that the elevated | |
| 733 // process ran but had terminated before displaying UI. This is an uncommon | |
| 734 // case and for simplicity, it is not handled here. | |
| 735 *has_ui_been_displayed = true; | |
| 736 | |
| 737 return exit_code; | |
| 738 } | |
| 739 | |
| 740 SplashScreen splash_screen(args.extra.bundle_name); | |
| 741 if (is_interactive) { | |
| 742 splash_screen.Show(); | |
| 743 } | |
| 744 | |
| 745 int extra_code1 = 0; | |
| 746 HRESULT hr = internal::DoInstall(*is_machine, | |
| 747 is_app_install, | |
| 748 is_eula_required, | |
| 749 is_oem_install, | |
| 750 current_version, | |
| 751 args, | |
| 752 session_id, | |
| 753 &splash_screen, | |
| 754 &extra_code1, | |
| 755 &has_setup_succeeded, | |
| 756 &has_launched_handoff, | |
| 757 has_ui_been_displayed); | |
| 758 if (is_interactive) { | |
| 759 splash_screen.Dismiss(); | |
| 760 } | |
| 761 | |
| 762 if (FAILED(hr)) { | |
| 763 CORE_LOG(LE, (_T("[DoInstall failed][0x%08x]"), hr)); | |
| 764 internal::HandleInstallError(hr, | |
| 765 extra_code1, | |
| 766 session_id, | |
| 767 *is_machine, | |
| 768 is_interactive, | |
| 769 is_eula_required, | |
| 770 is_oem_install, | |
| 771 current_version, | |
| 772 args.install_source, | |
| 773 args.extra, | |
| 774 has_setup_succeeded, | |
| 775 has_launched_handoff, | |
| 776 has_ui_been_displayed); | |
| 777 return hr; | |
| 778 } | |
| 779 | |
| 780 if (!is_app_install) { | |
| 781 // TODO(omaha): Display UI if we want to support interactive Omaha-only | |
| 782 // installs. | |
| 783 ASSERT1(!is_interactive); | |
| 784 // TODO(omaha3): Figure out a way to send a ping from the installed location | |
| 785 // for Omaha-only install success. | |
| 786 } | |
| 787 | |
| 788 ++metric_setup_install_succeeded; | |
| 789 return S_OK; | |
| 790 } | |
| 791 | |
| 792 HRESULT OemInstall(bool is_interactive, | |
| 793 bool is_app_install, | |
| 794 bool is_eula_required, | |
| 795 bool is_install_elevated_instance, | |
| 796 const CString& install_cmd_line, | |
| 797 const CommandLineArgs& args, | |
| 798 bool* is_machine, | |
| 799 bool* has_ui_been_displayed) { | |
| 800 ASSERT1(is_machine); | |
| 801 ASSERT1(has_ui_been_displayed); | |
| 802 | |
| 803 // OEM is handled as a special case, and the state must be correct before | |
| 804 // calling Install(). | |
| 805 HRESULT hr = oem_install_utils::SetOemInstallState(*is_machine); | |
| 806 if (FAILED(hr)) { | |
| 807 CORE_LOG(LE, (_T("[SetOemInstallState failed][0x%x]"), hr)); | |
| 808 return hr; | |
| 809 } | |
| 810 | |
| 811 hr = Install(is_interactive, | |
| 812 is_app_install, | |
| 813 is_eula_required, | |
| 814 true, | |
| 815 is_install_elevated_instance, | |
| 816 install_cmd_line, | |
| 817 args, | |
| 818 is_machine, | |
| 819 has_ui_been_displayed); | |
| 820 | |
| 821 if (FAILED(hr)) { | |
| 822 VERIFY1(SUCCEEDED(oem_install_utils::ResetOemInstallState(*is_machine))); | |
| 823 return hr; | |
| 824 } | |
| 825 | |
| 826 ASSERT1(oem_install_utils::IsOemInstalling(*is_machine)); | |
| 827 return S_OK; | |
| 828 } | |
| 829 | |
| 830 } // namespace omaha | |
| OLD | NEW |