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 |