install_worker.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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// This file contains the definitions of the installer functions that build
6// the WorkItemList used to install the application.
7
8#include "chrome/installer/setup/install_worker.h"
9
10#include <oaidl.h>
11#include <shlobj.h>
12#include <time.h>
13
14#include <vector>
15
16#include "base/bind.h"
17#include "base/command_line.h"
18#include "base/file_path.h"
19#include "base/file_util.h"
20#include "base/logging.h"
21#include "base/path_service.h"
22#include "base/string16.h"
23#include "base/string_util.h"
24#include "base/utf_string_conversions.h"
25#include "base/version.h"
26#include "base/win/registry.h"
27#include "base/win/scoped_comptr.h"
28#include "base/win/windows_version.h"
29#include "chrome/common/chrome_constants.h"
30#include "chrome/common/chrome_switches.h"
31#include "chrome/installer/setup/install.h"
32#include "chrome/installer/setup/setup_constants.h"
33#include "chrome/installer/setup/setup_util.h"
34#include "chrome/installer/util/browser_distribution.h"
35#include "chrome/installer/util/callback_work_item.h"
36#include "chrome/installer/util/conditional_work_item_list.h"
37#include "chrome/installer/util/create_reg_key_work_item.h"
38#include "chrome/installer/util/google_update_constants.h"
39#include "chrome/installer/util/helper.h"
40#include "chrome/installer/util/install_util.h"
41#include "chrome/installer/util/installation_state.h"
42#include "chrome/installer/util/installer_state.h"
43#include "chrome/installer/util/l10n_string_util.h"
44#include "chrome/installer/util/product.h"
45#include "chrome/installer/util/set_reg_value_work_item.h"
46#include "chrome/installer/util/shell_util.h"
47#include "chrome/installer/util/util_constants.h"
48#include "chrome/installer/util/work_item_list.h"
49
50#if !defined(OMIT_CHROME_FRAME)
51#include "chrome_frame/chrome_tab.h"
52#endif
53
54using base::win::RegKey;
55
56namespace installer {
57
58namespace {
59
60enum ElevationPolicyId {
61  CURRENT_ELEVATION_POLICY,
62  OLD_ELEVATION_POLICY,
63};
64
65// The version identifying the work done by setup.exe --configure-user-settings
66// on user login by way of Active Setup.  Increase this value if the work done
67// in setup_main.cc's handling of kConfigureUserSettings changes and should be
68// executed again for all users.
69const wchar_t kActiveSetupVersion[] = L"24,0,0,0";
70
71// Although the UUID of the ChromeFrame class is used for the "current" value,
72// this is done only as a convenience; there is no need for the GUID of the Low
73// Rights policies to match the ChromeFrame class's GUID.  Hence, it is safe to
74// use this completely unrelated GUID for the "old" policies.
75const wchar_t kIELowRightsPolicyOldGuid[] =
76    L"{6C288DD7-76FB-4721-B628-56FAC252E199}";
77
78#if defined(OMIT_CHROME_FRAME)
79// For historical reasons, this GUID is the same as CLSID_ChromeFrame. Included
80// here to break the dependency on Chrome Frame when Chrome Frame is not being
81// built.
82// TODO(robertshield): Remove this when Chrome Frame works with Aura.
83const wchar_t kIELowRightsPolicyCurrentGuid[] =
84    L"{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}";
85#endif
86
87const wchar_t kElevationPolicyKeyPath[] =
88    L"SOFTWARE\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\";
89
90void GetIELowRightsElevationPolicyKeyPath(ElevationPolicyId policy,
91                                          string16* key_path) {
92  DCHECK(policy == CURRENT_ELEVATION_POLICY || policy == OLD_ELEVATION_POLICY);
93  key_path->assign(kElevationPolicyKeyPath,
94                   arraysize(kElevationPolicyKeyPath) - 1);
95  if (policy == CURRENT_ELEVATION_POLICY) {
96#if defined(OMIT_CHROME_FRAME)
97    key_path->append(kIELowRightsPolicyCurrentGuid,
98                     arraysize(kIELowRightsPolicyCurrentGuid) - 1);
99#else
100    wchar_t cf_clsid[64];
101    int len = StringFromGUID2(__uuidof(ChromeFrame), &cf_clsid[0],
102                              arraysize(cf_clsid));
103    key_path->append(&cf_clsid[0], len - 1);
104#endif
105  } else {
106    key_path->append(kIELowRightsPolicyOldGuid,
107                     arraysize(kIELowRightsPolicyOldGuid)- 1);
108  }
109}
110
111// Local helper to call AddRegisterComDllWorkItems for all DLLs in a set of
112// products managed by a given package.
113// |old_version| can be NULL to indicate no Chrome is currently installed.
114void AddRegisterComDllWorkItemsForPackage(const InstallerState& installer_state,
115                                          const Version* old_version,
116                                          const Version& new_version,
117                                          WorkItemList* work_item_list) {
118  // First collect the list of DLLs to be registered from each product.
119  std::vector<FilePath> com_dll_list;
120  installer_state.AddComDllList(&com_dll_list);
121
122  // Then, if we got some, attempt to unregister the DLLs from the old
123  // version directory and then re-register them in the new one.
124  // Note that if we are migrating the install directory then we will not
125  // successfully unregister the old DLLs.
126  // TODO(robertshield): See whether we need to fix the migration case.
127  // TODO(robertshield): If we ever remove a DLL from a product, this will
128  // not unregister it on update. We should build the unregistration list from
129  // saved state instead of assuming it is the same as the registration list.
130  if (!com_dll_list.empty()) {
131    if (old_version) {
132      FilePath old_dll_path(installer_state.target_path().AppendASCII(
133          old_version->GetString()));
134
135      installer::AddRegisterComDllWorkItems(old_dll_path,
136                                            com_dll_list,
137                                            installer_state.system_install(),
138                                            false,  // Unregister
139                                            true,   // May fail
140                                            work_item_list);
141    }
142
143    FilePath dll_path(installer_state.target_path().AppendASCII(
144        new_version.GetString()));
145    installer::AddRegisterComDllWorkItems(dll_path,
146                                          com_dll_list,
147                                          installer_state.system_install(),
148                                          true,   // Register
149                                          false,  // Must succeed.
150                                          work_item_list);
151  }
152}
153
154void AddInstallerCopyTasks(const InstallerState& installer_state,
155                           const FilePath& setup_path,
156                           const FilePath& archive_path,
157                           const FilePath& temp_path,
158                           const Version& new_version,
159                           WorkItemList* install_list) {
160  DCHECK(install_list);
161  FilePath installer_dir(installer_state.GetInstallerDirectory(new_version));
162  install_list->AddCreateDirWorkItem(installer_dir);
163
164  FilePath exe_dst(installer_dir.Append(setup_path.BaseName()));
165  FilePath archive_dst(installer_dir.Append(archive_path.BaseName()));
166
167  if (exe_dst != setup_path) {
168    install_list->AddCopyTreeWorkItem(setup_path.value(), exe_dst.value(),
169                                      temp_path.value(), WorkItem::ALWAYS);
170  }
171
172  if (archive_path != archive_dst) {
173    // In the past, we copied rather than moved for system level installs so
174    // that the permissions of %ProgramFiles% would be picked up.  Now that
175    // |temp_path| is in %ProgramFiles% for system level installs (and in
176    // %LOCALAPPDATA% otherwise), there is no need to do this for the archive.
177    // Setup.exe, on the other hand, is created elsewhere so it must always be
178    // copied.
179#if !defined(COMPONENT_BUILD)
180    install_list->AddMoveTreeWorkItem(archive_path.value(), archive_dst.value(),
181                                      temp_path.value(), WorkItem::ALWAYS_MOVE);
182#else  // COMPONENT_BUILD
183    // The archive is usually extracted in |temp_path| in which case we want to
184    // move it as mentioned above; however in the component build, setup.exe
185    // uses chrome.7z directly from the build output, moving it means that
186    // setup.exe cannot be run again without regenerating the archive, so copy
187    // it instead in this case to save developer time.
188    install_list->AddCopyTreeWorkItem(archive_path.value(), archive_dst.value(),
189                                      temp_path.value(), WorkItem::ALWAYS);
190#endif  // COMPONENT_BUILD
191  }
192}
193
194void AddInstallAppCommandWorkItems(const InstallerState& installer_state,
195                                   const InstallationState& machine_state,
196                                   const FilePath& setup_path,
197                                   const Version& new_version,
198                                   const Product& product,
199                                   WorkItemList* work_item_list) {
200  DCHECK(product.is_chrome_app_host());
201
202  string16 cmd_key(product.distribution()->GetVersionKey());
203  cmd_key.append(1, L'\\').append(google_update::kRegCommandsKey)
204      .append(1, L'\\').append(kCmdInstallApp);
205
206  if (installer_state.operation() == InstallerState::UNINSTALL) {
207    work_item_list->AddDeleteRegKeyWorkItem(
208        installer_state.root_key(), cmd_key)->set_log_message(
209            "removing install-application command");
210  } else {
211    CommandLine cmd_line(
212        installer_state.target_path().Append(installer::kChromeAppHostExe));
213    cmd_line.AppendSwitchASCII(::switches::kInstallFromWebstore, "%1");
214
215    AppCommand cmd(cmd_line.GetCommandLineString());
216    cmd.set_sends_pings(true);
217    cmd.set_is_web_accessible(true);
218    cmd.AddWorkItems(installer_state.root_key(), cmd_key, work_item_list);
219  }
220}
221
222void AddProductSpecificWorkItems(const InstallationState& original_state,
223                                 const InstallerState& installer_state,
224                                 const FilePath& setup_path,
225                                 const Version& new_version,
226                                 WorkItemList* list) {
227  const Products& products = installer_state.products();
228  for (Products::const_iterator it = products.begin(); it < products.end();
229       ++it) {
230    const Product& p = **it;
231    if (p.is_chrome_frame()) {
232      AddChromeFrameWorkItems(original_state, installer_state, setup_path,
233                              new_version, p, list);
234    }
235    if (p.is_chrome_app_host()) {
236      AddInstallAppCommandWorkItems(installer_state, original_state,
237                                    setup_path, new_version, p, list);
238    }
239    if (p.is_chrome()) {
240      AddOsUpgradeWorkItems(installer_state, setup_path, new_version, p,
241                            list);
242    }
243  }
244}
245
246// This is called when an MSI installation is run. It may be that a user is
247// attempting to install the MSI on top of a non-MSI managed installation.
248// If so, try and remove any existing uninstallation shortcuts, as we want the
249// uninstall to be managed entirely by the MSI machinery (accessible via the
250// Add/Remove programs dialog).
251void AddDeleteUninstallShortcutsForMSIWorkItems(
252    const InstallerState& installer_state,
253    const Product& product,
254    const FilePath& temp_path,
255    WorkItemList* work_item_list) {
256  DCHECK(installer_state.is_msi())
257      << "This must only be called for MSI installations!";
258
259  // First attempt to delete the old installation's ARP dialog entry.
260  HKEY reg_root = installer_state.root_key();
261  string16 uninstall_reg(product.distribution()->GetUninstallRegPath());
262
263  WorkItem* delete_reg_key = work_item_list->AddDeleteRegKeyWorkItem(
264      reg_root, uninstall_reg);
265  delete_reg_key->set_ignore_failure(true);
266
267  // Then attempt to delete the old installation's start menu shortcut.
268  FilePath uninstall_link;
269  if (installer_state.system_install()) {
270    PathService::Get(base::DIR_COMMON_START_MENU, &uninstall_link);
271  } else {
272    PathService::Get(base::DIR_START_MENU, &uninstall_link);
273  }
274
275  if (uninstall_link.empty()) {
276    LOG(ERROR) << "Failed to get location for shortcut.";
277  } else {
278    uninstall_link = uninstall_link.Append(
279        product.distribution()->GetAppShortCutName());
280    uninstall_link = uninstall_link.Append(
281        product.distribution()->GetUninstallLinkName() + installer::kLnkExt);
282    VLOG(1) << "Deleting old uninstall shortcut (if present): "
283            << uninstall_link.value();
284    WorkItem* delete_link = work_item_list->AddDeleteTreeWorkItem(
285        uninstall_link, temp_path);
286    delete_link->set_ignore_failure(true);
287    delete_link->set_log_message(
288        "Failed to delete old uninstall shortcut.");
289  }
290}
291
292// Adds Chrome specific install work items to |install_list|.
293// |current_version| can be NULL to indicate no Chrome is currently installed.
294void AddChromeWorkItems(const InstallationState& original_state,
295                        const InstallerState& installer_state,
296                        const FilePath& setup_path,
297                        const FilePath& archive_path,
298                        const FilePath& src_path,
299                        const FilePath& temp_path,
300                        const Version* current_version,
301                        const Version& new_version,
302                        WorkItemList* install_list) {
303  const FilePath& target_path = installer_state.target_path();
304
305  if (current_version) {
306    // Delete the archive from an existing install to save some disk space.  We
307    // make this an unconditional work item since there's no need to roll this
308    // back; if installation fails we'll be moved to the "-full" channel anyway.
309    FilePath old_installer_dir(
310        installer_state.GetInstallerDirectory(*current_version));
311    FilePath old_archive(old_installer_dir.Append(installer::kChromeArchive));
312    // Don't delete the archive that we are actually installing from.
313    if (archive_path != old_archive) {
314      install_list->AddDeleteTreeWorkItem(old_archive, temp_path)->
315          set_ignore_failure(true);
316    }
317  }
318
319  // Delete any new_chrome.exe if present (we will end up creating a new one
320  // if required) and then copy chrome.exe
321  FilePath new_chrome_exe(target_path.Append(installer::kChromeNewExe));
322
323  install_list->AddDeleteTreeWorkItem(new_chrome_exe, temp_path);
324
325  if (installer_state.IsChromeFrameRunning(original_state)) {
326    VLOG(1) << "Chrome Frame in use. Copying to new_chrome.exe";
327    install_list->AddCopyTreeWorkItem(
328        src_path.Append(installer::kChromeExe).value(),
329        new_chrome_exe.value(),
330        temp_path.value(),
331        WorkItem::ALWAYS);
332  } else {
333    install_list->AddCopyTreeWorkItem(
334        src_path.Append(installer::kChromeExe).value(),
335        target_path.Append(installer::kChromeExe).value(),
336        temp_path.value(),
337        WorkItem::NEW_NAME_IF_IN_USE,
338        new_chrome_exe.value());
339  }
340
341  // Extra executable for 64 bit systems.
342  // NOTE: We check for "not disabled" so that if the API call fails, we play it
343  // safe and copy the executable anyway.
344  // NOTE: the file wow_helper.exe is only needed for Vista and below.
345  if (base::win::OSInfo::GetInstance()->wow64_status() !=
346      base::win::OSInfo::WOW64_DISABLED &&
347      base::win::GetVersion() <= base::win::VERSION_VISTA) {
348    install_list->AddMoveTreeWorkItem(
349        src_path.Append(installer::kWowHelperExe).value(),
350        target_path.Append(installer::kWowHelperExe).value(),
351        temp_path.value(),
352        WorkItem::ALWAYS_MOVE);
353  }
354
355  // Install kVisualElementsManifest if it is present in |src_path|. No need to
356  // make this a conditional work item as if the file is not there now, it will
357  // never be.
358  if (file_util::PathExists(
359          src_path.Append(installer::kVisualElementsManifest))) {
360    install_list->AddMoveTreeWorkItem(
361        src_path.Append(installer::kVisualElementsManifest).value(),
362        target_path.Append(installer::kVisualElementsManifest).value(),
363        temp_path.value(),
364        WorkItem::ALWAYS_MOVE);
365  } else {
366    // We do not want to have an old VisualElementsManifest pointing to an old
367    // version directory. Delete it as there wasn't a new one to replace it.
368    install_list->AddDeleteTreeWorkItem(
369        target_path.Append(installer::kVisualElementsManifest),
370        temp_path);
371  }
372
373  // For the component build to work with the installer, we need to drop a
374  // config file and a manifest by chrome.exe. These files are only found in
375  // the archive if this is a component build.
376#if defined(COMPONENT_BUILD)
377  static const FilePath::CharType kChromeExeConfig[] =
378      FILE_PATH_LITERAL("chrome.exe.config");
379  static const FilePath::CharType kChromeExeManifest[] =
380      FILE_PATH_LITERAL("chrome.exe.manifest");
381  install_list->AddMoveTreeWorkItem(
382      src_path.Append(kChromeExeConfig).value(),
383      target_path.Append(kChromeExeConfig).value(),
384      temp_path.value(),
385      WorkItem::ALWAYS_MOVE);
386  install_list->AddMoveTreeWorkItem(
387      src_path.Append(kChromeExeManifest).value(),
388      target_path.Append(kChromeExeManifest).value(),
389      temp_path.value(),
390      WorkItem::ALWAYS_MOVE);
391#endif  // defined(COMPONENT_BUILD)
392
393  // In the past, we copied rather than moved for system level installs so that
394  // the permissions of %ProgramFiles% would be picked up.  Now that |temp_path|
395  // is in %ProgramFiles% for system level installs (and in %LOCALAPPDATA%
396  // otherwise), there is no need to do this.
397  // Note that we pass true for check_duplicates to avoid failing on in-use
398  // repair runs if the current_version is the same as the new_version.
399  bool check_for_duplicates = (current_version &&
400                               current_version->Equals(new_version));
401  install_list->AddMoveTreeWorkItem(
402      src_path.AppendASCII(new_version.GetString()).value(),
403      target_path.AppendASCII(new_version.GetString()).value(),
404      temp_path.value(),
405      check_for_duplicates ? WorkItem::CHECK_DUPLICATES :
406                             WorkItem::ALWAYS_MOVE);
407
408  // Delete any old_chrome.exe if present (ignore failure if it's in use).
409  install_list->AddDeleteTreeWorkItem(
410      target_path.Append(installer::kChromeOldExe), temp_path)->
411          set_ignore_failure(true);
412
413  // Copy installer in install directory and
414  // add shortcut in Control Panel->Add/Remove Programs.
415  AddInstallerCopyTasks(installer_state, setup_path, archive_path, temp_path,
416                        new_version, install_list);
417}
418
419// Probes COM machinery to get an instance of delegate_execute.exe's
420// CommandExecuteImpl class.  This is required so that COM purges its cache of
421// the path to the binary, which changes on updates.  This callback
422// unconditionally returns true since an install should not be aborted if the
423// probe fails.
424bool ProbeCommandExecuteCallback(const string16& command_execute_id,
425                                 const CallbackWorkItem& work_item) {
426  // Noop on rollback.
427  if (work_item.IsRollback())
428    return true;
429
430  CLSID class_id = {};
431
432  HRESULT hr = CLSIDFromString(command_execute_id.c_str(), &class_id);
433  if (FAILED(hr)) {
434    LOG(DFATAL) << "Failed converting \"" << command_execute_id << "\" to "
435                   "CLSID; hr=0x" << std::hex << hr;
436  } else {
437    base::win::ScopedComPtr<IUnknown> command_execute_impl;
438    hr = command_execute_impl.CreateInstance(class_id, NULL,
439                                             CLSCTX_LOCAL_SERVER);
440    if (hr != REGDB_E_CLASSNOTREG) {
441      LOG(ERROR) << "Unexpected result creating CommandExecuteImpl; hr=0x"
442                 << std::hex << hr;
443    }
444  }
445
446  return true;
447}
448
449void AddGenericQuickEnableWorkItems(const InstallerState& installer_state,
450                                    const InstallationState& machine_state,
451                                    const FilePath& setup_path,
452                                    const Version& new_version,
453                                    bool have_child_product,
454                                    const CommandLine& child_product_switches,
455                                    const string16& command_id,
456                                    WorkItemList* work_item_list) {
457  DCHECK(work_item_list);
458
459  const bool system_install = installer_state.system_install();
460  bool have_chrome_binaries = false;
461
462  // STEP 1: Figure out the state of the machine before the operation.
463  const ProductState* product_state = NULL;
464
465  // Are the Chrome Binaries already on the machine?
466  product_state = machine_state.GetProductState(
467      system_install, BrowserDistribution::CHROME_BINARIES);
468  if (product_state != NULL && product_state->is_multi_install())
469    have_chrome_binaries = true;
470
471  // STEP 2: Now take into account the current operation.
472  const Product* product = NULL;
473
474  if (installer_state.operation() == InstallerState::UNINSTALL) {
475    // Forget about multi-install Chrome if it is being uninstalled.
476    product = installer_state.FindProduct(BrowserDistribution::CHROME_BINARIES);
477    if (product != NULL && installer_state.is_multi_install())
478      have_chrome_binaries = false;
479  } else {
480    // Check if we're installing Chrome Binaries
481    product = installer_state.FindProduct(BrowserDistribution::CHROME_BINARIES);
482    if (product != NULL && installer_state.is_multi_install())
483      have_chrome_binaries = true;
484  }
485
486  // STEP 3: Decide what to do based on the final state of things.
487  enum QuickEnableOperation {
488    DO_NOTHING,
489    ADD_COMMAND,
490    REMOVE_COMMAND
491  } operation = DO_NOTHING;
492  FilePath binaries_setup_path;
493
494  if (have_child_product) {
495    // Child product is being uninstalled. Unconditionally remove the Quick
496    // Enable command from the binaries. We do this even if multi-install Chrome
497    // isn't installed since we don't want them left behind in any case.
498    operation = REMOVE_COMMAND;
499  } else if (have_chrome_binaries) {
500    // Child product isn't (to be) installed while multi-install Chrome is (to
501    // be) installed.  Add the Quick Enable command to the binaries.
502    operation = ADD_COMMAND;
503    // The path to setup.exe contains the version of the Chrome binaries, so it
504    // takes a little work to get it right.
505    if (installer_state.operation() == InstallerState::UNINSTALL) {
506      // One or more products are being uninstalled, but not the binaries. Use
507      // the path to the currently installed Chrome setup.exe.
508      product_state = machine_state.GetProductState(
509          system_install, BrowserDistribution::CHROME_BINARIES);
510      DCHECK(product_state);
511      binaries_setup_path = product_state->uninstall_command().GetProgram();
512    } else {
513      // Chrome Binaries are being installed, updated, or otherwise operated on.
514      // Use the path to the given |setup_path| in the normal location of
515      // multi-install Chrome Binaries of the given |version|.
516      DCHECK(installer_state.is_multi_install());
517      binaries_setup_path =
518          installer_state.GetInstallerDirectory(new_version).Append(
519              setup_path.BaseName());
520    }
521  }
522
523  // STEP 4: Take action.
524  if (operation != DO_NOTHING) {
525    // Get the path to the quick-enable-cf command for the binaries.
526    BrowserDistribution* binaries =
527        BrowserDistribution::GetSpecificDistribution(
528            BrowserDistribution::CHROME_BINARIES);
529    string16 cmd_key(binaries->GetVersionKey());
530    cmd_key.append(1, L'\\').append(google_update::kRegCommandsKey)
531        .append(1, L'\\').append(command_id);
532
533    if (operation == ADD_COMMAND) {
534      DCHECK(!binaries_setup_path.empty());
535      CommandLine cmd_line(binaries_setup_path);
536      cmd_line.AppendArguments(child_product_switches,
537                               false);  // include_program
538      if (installer_state.verbose_logging())
539        cmd_line.AppendSwitch(switches::kVerboseLogging);
540      AppCommand cmd(cmd_line.GetCommandLineString());
541      cmd.set_sends_pings(true);
542      cmd.set_is_web_accessible(true);
543      cmd.AddWorkItems(installer_state.root_key(), cmd_key, work_item_list);
544    } else {
545      DCHECK(operation == REMOVE_COMMAND);
546      work_item_list->AddDeleteRegKeyWorkItem(
547          installer_state.root_key(), cmd_key)->set_log_message(
548              "removing " + WideToASCII(command_id) + " command");
549    }
550  }
551}
552
553}  // namespace
554
555// This method adds work items to create (or update) Chrome uninstall entry in
556// either the Control Panel->Add/Remove Programs list or in the Omaha client
557// state key if running under an MSI installer.
558void AddUninstallShortcutWorkItems(const InstallerState& installer_state,
559                                   const FilePath& setup_path,
560                                   const Version& new_version,
561                                   const Product& product,
562                                   WorkItemList* install_list) {
563  HKEY reg_root = installer_state.root_key();
564  BrowserDistribution* browser_dist = product.distribution();
565  DCHECK(browser_dist);
566
567  // When we are installed via an MSI, we need to store our uninstall strings
568  // in the Google Update client state key. We do this even for non-MSI
569  // managed installs to avoid breaking the edge case whereby an MSI-managed
570  // install is updated by a non-msi installer (which would confuse the MSI
571  // machinery if these strings were not also updated). The UninstallString
572  // value placed in the client state key is also used by the mini_installer to
573  // locate the setup.exe instance used for binary patching.
574  // Do not quote the command line for the MSI invocation.
575  FilePath install_path(installer_state.target_path());
576  FilePath installer_path(installer_state.GetInstallerDirectory(new_version));
577  installer_path = installer_path.Append(setup_path.BaseName());
578
579  CommandLine uninstall_arguments(CommandLine::NO_PROGRAM);
580  AppendUninstallCommandLineFlags(installer_state, product,
581                                  &uninstall_arguments);
582
583  // If Chrome Frame is installed in Ready Mode, add --chrome-frame to Chrome's
584  // uninstall entry. We skip this processing in case of uninstall since this
585  // means that Chrome Frame is being uninstalled, so there's no need to do any
586  // looping.
587  if (product.is_chrome() &&
588      installer_state.operation() != InstallerState::UNINSTALL) {
589    const Product* chrome_frame =
590        installer_state.FindProduct(BrowserDistribution::CHROME_FRAME);
591    if (chrome_frame && chrome_frame->HasOption(kOptionReadyMode))
592      chrome_frame->AppendProductFlags(&uninstall_arguments);
593  }
594
595  string16 update_state_key(browser_dist->GetStateKey());
596  install_list->AddCreateRegKeyWorkItem(reg_root, update_state_key);
597  install_list->AddSetRegValueWorkItem(reg_root, update_state_key,
598      installer::kUninstallStringField, installer_path.value(), true);
599  install_list->AddSetRegValueWorkItem(reg_root, update_state_key,
600      installer::kUninstallArgumentsField,
601      uninstall_arguments.GetCommandLineString(), true);
602
603  // MSI installations will manage their own uninstall shortcuts.
604  if (!installer_state.is_msi() && product.ShouldCreateUninstallEntry()) {
605    // We need to quote the command line for the Add/Remove Programs dialog.
606    CommandLine quoted_uninstall_cmd(installer_path);
607    DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"');
608    quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false);
609
610    string16 uninstall_reg = browser_dist->GetUninstallRegPath();
611    install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg);
612    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
613        installer::kUninstallDisplayNameField,
614        browser_dist->GetAppShortCutName(), true);
615    install_list->AddSetRegValueWorkItem(reg_root,
616        uninstall_reg, installer::kUninstallStringField,
617        quoted_uninstall_cmd.GetCommandLineString(), true);
618    install_list->AddSetRegValueWorkItem(reg_root,
619                                         uninstall_reg,
620                                         L"InstallLocation",
621                                         install_path.value(),
622                                         true);
623
624    // TODO(huangs): Generalize this, so app_host.exe can get its own icon,
625    // and not rely on chrome.exe's.
626    // DisplayIcon, NoModify and NoRepair
627    string16 chrome_icon = ShellUtil::GetChromeIcon(
628        product.distribution(),
629        install_path.Append(installer::kChromeExe).value());
630    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
631                                         L"DisplayIcon", chrome_icon, true);
632    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
633                                         L"NoModify", static_cast<DWORD>(1),
634                                         true);
635    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
636                                         L"NoRepair", static_cast<DWORD>(1),
637                                         true);
638
639    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
640                                         L"Publisher",
641                                         browser_dist->GetPublisherName(),
642                                         true);
643    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
644                                         L"Version",
645                                         ASCIIToWide(new_version.GetString()),
646                                         true);
647    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
648                                         L"DisplayVersion",
649                                         ASCIIToWide(new_version.GetString()),
650                                         true);
651    install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
652                                         L"InstallDate",
653                                         InstallUtil::GetCurrentDate(),
654                                         false);
655
656    const std::vector<uint16>& version_components = new_version.components();
657    if (version_components.size() == 4) {
658      // Our version should be in major.minor.build.rev.
659      install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
660          L"VersionMajor", static_cast<DWORD>(version_components[2]), true);
661      install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
662          L"VersionMinor", static_cast<DWORD>(version_components[3]), true);
663    }
664  }
665}
666
667// Create Version key for a product (if not already present) and sets the new
668// product version as the last step.
669void AddVersionKeyWorkItems(HKEY root,
670                            BrowserDistribution* dist,
671                            const Version& new_version,
672                            bool add_language_identifier,
673                            WorkItemList* list) {
674  // Create Version key for each distribution (if not already present) and set
675  // the new product version as the last step.
676  string16 version_key(dist->GetVersionKey());
677  list->AddCreateRegKeyWorkItem(root, version_key);
678
679  string16 product_name(dist->GetAppShortCutName());
680  list->AddSetRegValueWorkItem(root, version_key, google_update::kRegNameField,
681                               product_name, true);  // overwrite name also
682  list->AddSetRegValueWorkItem(root, version_key,
683                               google_update::kRegOopcrashesField,
684                               static_cast<DWORD>(1),
685                               false);  // set during first install
686  if (add_language_identifier) {
687    // Write the language identifier of the current translation.  Omaha's set of
688    // languages is a superset of Chrome's set of translations with this one
689    // exception: what Chrome calls "en-us", Omaha calls "en".  sigh.
690    string16 language(GetCurrentTranslation());
691    if (LowerCaseEqualsASCII(language, "en-us"))
692      language.resize(2);
693    list->AddSetRegValueWorkItem(root, version_key,
694                                 google_update::kRegLangField, language,
695                                 false);  // do not overwrite language
696  }
697  list->AddSetRegValueWorkItem(root, version_key,
698                               google_update::kRegVersionField,
699                               ASCIIToWide(new_version.GetString()),
700                               true);  // overwrite version
701}
702
703// Mirror oeminstall the first time anything is installed multi.  There is no
704// need to update the value on future install/update runs since this value never
705// changes.  Note that the value is removed by Google Update after EULA
706// acceptance is processed.
707void AddOemInstallWorkItems(const InstallationState& original_state,
708                            const InstallerState& installer_state,
709                            WorkItemList* install_list) {
710  DCHECK(installer_state.is_multi_install());
711  const bool system_install = installer_state.system_install();
712  if (!original_state.GetProductState(system_install,
713                                      BrowserDistribution::CHROME_BINARIES)) {
714    const HKEY root_key = installer_state.root_key();
715    string16 multi_key(
716        installer_state.multi_package_binaries_distribution()->GetStateKey());
717
718    // Copy the value from Chrome unless Chrome isn't installed or being
719    // installed.
720    BrowserDistribution::Type source_type;
721    if (installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER)) {
722      source_type = BrowserDistribution::CHROME_BROWSER;
723    } else if (!installer_state.products().empty()) {
724      // Pick a product, any product.
725      source_type = installer_state.products()[0]->distribution()->GetType();
726    } else {
727      // Nothing is being installed?  Entirely unexpected, so do no harm.
728      LOG(ERROR) << "No products found in AddOemInstallWorkItems";
729      return;
730    }
731    const ProductState* source_product =
732        original_state.GetNonVersionedProductState(system_install, source_type);
733
734    string16 oem_install;
735    if (source_product->GetOemInstall(&oem_install)) {
736      VLOG(1) << "Mirroring oeminstall=\"" << oem_install << "\" from "
737              << BrowserDistribution::GetSpecificDistribution(source_type)->
738                    GetAppShortCutName();
739      install_list->AddCreateRegKeyWorkItem(root_key, multi_key);
740      // Always overwrite an old value.
741      install_list->AddSetRegValueWorkItem(root_key, multi_key,
742                                           google_update::kRegOemInstallField,
743                                           oem_install, true);
744    } else {
745      // Clear any old value.
746      install_list->AddDeleteRegValueWorkItem(
747          root_key, multi_key, google_update::kRegOemInstallField);
748    }
749  }
750}
751
752// Mirror eulaaccepted the first time anything is installed multi.  There is no
753// need to update the value on future install/update runs since
754// GoogleUpdateSettings::SetEULAConsent will modify the value for both the
755// relevant product and for the binaries.
756void AddEulaAcceptedWorkItems(const InstallationState& original_state,
757                              const InstallerState& installer_state,
758                              WorkItemList* install_list) {
759  DCHECK(installer_state.is_multi_install());
760  const bool system_install = installer_state.system_install();
761  if (!original_state.GetProductState(system_install,
762                                      BrowserDistribution::CHROME_BINARIES)) {
763    const HKEY root_key = installer_state.root_key();
764    string16 multi_key(
765        installer_state.multi_package_binaries_distribution()->GetStateKey());
766
767    // Copy the value from the product with the greatest value.
768    bool have_eula_accepted = false;
769    BrowserDistribution::Type product_type;
770    DWORD eula_accepted;
771    const Products& products = installer_state.products();
772    for (Products::const_iterator it = products.begin(); it < products.end();
773         ++it) {
774      const Product& product = **it;
775      if (product.is_chrome_binaries())
776        continue;
777      DWORD dword_value = 0;
778      BrowserDistribution::Type this_type = product.distribution()->GetType();
779      const ProductState* product_state =
780          original_state.GetNonVersionedProductState(
781              system_install, this_type);
782      if (product_state->GetEulaAccepted(&dword_value) &&
783          (!have_eula_accepted || dword_value > eula_accepted)) {
784        have_eula_accepted = true;
785        eula_accepted = dword_value;
786        product_type = this_type;
787      }
788    }
789
790    if (have_eula_accepted) {
791      VLOG(1) << "Mirroring eulaaccepted=" << eula_accepted << " from "
792              << BrowserDistribution::GetSpecificDistribution(product_type)->
793                     GetAppShortCutName();
794      install_list->AddCreateRegKeyWorkItem(root_key, multi_key);
795      install_list->AddSetRegValueWorkItem(
796          root_key, multi_key, google_update::kRegEULAAceptedField,
797          eula_accepted, true);
798    } else {
799      // Clear any old value.
800      install_list->AddDeleteRegValueWorkItem(
801          root_key, multi_key, google_update::kRegEULAAceptedField);
802    }
803  }
804}
805
806// Adds work items that make registry adjustments for Google Update.
807void AddGoogleUpdateWorkItems(const InstallationState& original_state,
808                              const InstallerState& installer_state,
809                              WorkItemList* install_list) {
810  // Is a multi-install product being installed or over-installed?
811  if (installer_state.operation() != InstallerState::MULTI_INSTALL &&
812      installer_state.operation() != InstallerState::MULTI_UPDATE) {
813    VLOG(1) << "AddGoogleUpdateWorkItems noop: " << installer_state.operation();
814    return;
815  }
816
817  const bool system_install = installer_state.system_install();
818  const HKEY root_key = installer_state.root_key();
819  string16 multi_key(
820      installer_state.multi_package_binaries_distribution()->GetStateKey());
821
822  // For system-level installs, make sure the ClientStateMedium key for the
823  // binaries exists.
824  if (system_install) {
825    install_list->AddCreateRegKeyWorkItem(
826        root_key,
827        installer_state.multi_package_binaries_distribution()->
828            GetStateMediumKey().c_str());
829  }
830
831  // Creating the ClientState key for binaries, if we're migrating to multi then
832  // copy over Chrome's brand code if it has one. Chrome Frame currently never
833  // has a brand code.
834  if (installer_state.state_type() != BrowserDistribution::CHROME_BINARIES) {
835    const ProductState* chrome_product_state =
836        original_state.GetNonVersionedProductState(
837            system_install, BrowserDistribution::CHROME_BROWSER);
838
839    const string16& brand(chrome_product_state->brand());
840    if (!brand.empty()) {
841      install_list->AddCreateRegKeyWorkItem(root_key, multi_key);
842      // Write Chrome's brand code to the multi key. Never overwrite the value
843      // if one is already present (although this shouldn't happen).
844      install_list->AddSetRegValueWorkItem(root_key,
845                                           multi_key,
846                                           google_update::kRegBrandField,
847                                           brand,
848                                           false);
849    }
850  }
851
852  AddOemInstallWorkItems(original_state, installer_state, install_list);
853  AddEulaAcceptedWorkItems(original_state, installer_state, install_list);
854  AddUsageStatsWorkItems(original_state, installer_state, install_list);
855
856  // TODO(grt): check for other keys/values we should put in the package's
857  // ClientState and/or Clients key.
858}
859
860void AddUsageStatsWorkItems(const InstallationState& original_state,
861                            const InstallerState& installer_state,
862                            WorkItemList* install_list) {
863  DCHECK(installer_state.operation() == InstallerState::MULTI_INSTALL ||
864         installer_state.operation() == InstallerState::MULTI_UPDATE);
865
866  HKEY root_key = installer_state.root_key();
867  bool value_found = false;
868  DWORD usagestats = 0;
869  const Products& products = installer_state.products();
870
871  // Search for an existing usagestats value for any product.
872  for (Products::const_iterator scan = products.begin(), end = products.end();
873       !value_found && scan != end; ++scan) {
874    if ((*scan)->is_chrome_binaries())
875      continue;
876    BrowserDistribution* dist = (*scan)->distribution();
877    const ProductState* product_state =
878        original_state.GetNonVersionedProductState(
879            installer_state.system_install(), dist->GetType());
880    value_found = product_state->GetUsageStats(&usagestats);
881  }
882
883  // If a value was found, write it in the appropriate location for the
884  // binaries and remove all values from the products.
885  if (value_found) {
886    string16 state_key(
887        installer_state.multi_package_binaries_distribution()->GetStateKey());
888    install_list->AddCreateRegKeyWorkItem(root_key, state_key);
889    // Overwrite any existing value so that overinstalls (where Omaha writes a
890    // new value into a product's state key) pick up the correct value.
891    install_list->AddSetRegValueWorkItem(root_key, state_key,
892                                         google_update::kRegUsageStatsField,
893                                         usagestats, true);
894
895    for (Products::const_iterator scan = products.begin(), end = products.end();
896         scan != end; ++scan) {
897      if ((*scan)->is_chrome_binaries())
898        continue;
899      BrowserDistribution* dist = (*scan)->distribution();
900      if (installer_state.system_install()) {
901        install_list->AddDeleteRegValueWorkItem(
902            root_key, dist->GetStateMediumKey(),
903            google_update::kRegUsageStatsField);
904        // Previous versions of Chrome also wrote a value in HKCU even for
905        // system-level installs, so clean that up.
906        install_list->AddDeleteRegValueWorkItem(
907            HKEY_CURRENT_USER, dist->GetStateKey(),
908            google_update::kRegUsageStatsField);
909      }
910      install_list->AddDeleteRegValueWorkItem(
911          root_key, dist->GetStateKey(), google_update::kRegUsageStatsField);
912    }
913  }
914}
915
916bool AppendPostInstallTasks(const InstallerState& installer_state,
917                            const FilePath& setup_path,
918                            const Version* current_version,
919                            const Version& new_version,
920                            const FilePath& temp_path,
921                            WorkItemList* post_install_task_list) {
922  DCHECK(post_install_task_list);
923
924  HKEY root = installer_state.root_key();
925  const Products& products = installer_state.products();
926  FilePath new_chrome_exe(
927      installer_state.target_path().Append(installer::kChromeNewExe));
928
929  // Append work items that will only be executed if this was an update.
930  // We update the 'opv' value with the current version that is active,
931  // the 'cpv' value with the critical update version (if present), and the
932  // 'cmd' value with the rename command to run.
933  {
934    scoped_ptr<WorkItemList> in_use_update_work_items(
935        WorkItem::CreateConditionalWorkItemList(
936            new ConditionRunIfFileExists(new_chrome_exe)));
937    in_use_update_work_items->set_log_message("InUseUpdateWorkItemList");
938
939    // |critical_version| will be valid only if this in-use update includes a
940    // version considered critical relative to the version being updated.
941    Version critical_version(installer_state.DetermineCriticalVersion(
942        current_version, new_version));
943    FilePath installer_path(installer_state.GetInstallerDirectory(new_version)
944        .Append(setup_path.BaseName()));
945
946    CommandLine rename(installer_path);
947    rename.AppendSwitch(switches::kRenameChromeExe);
948    if (installer_state.system_install())
949      rename.AppendSwitch(switches::kSystemLevel);
950
951    if (installer_state.verbose_logging())
952      rename.AppendSwitch(switches::kVerboseLogging);
953
954    string16 version_key;
955    for (size_t i = 0; i < products.size(); ++i) {
956      BrowserDistribution* dist = products[i]->distribution();
957      version_key = dist->GetVersionKey();
958
959      if (current_version) {
960        in_use_update_work_items->AddSetRegValueWorkItem(root, version_key,
961            google_update::kRegOldVersionField,
962            ASCIIToWide(current_version->GetString()), true);
963      }
964      if (critical_version.IsValid()) {
965        in_use_update_work_items->AddSetRegValueWorkItem(root, version_key,
966            google_update::kRegCriticalVersionField,
967            ASCIIToWide(critical_version.GetString()), true);
968      } else {
969        in_use_update_work_items->AddDeleteRegValueWorkItem(root, version_key,
970            google_update::kRegCriticalVersionField);
971      }
972
973      // Adding this registry entry for all products (but the binaries) is
974      // overkill. However, as it stands, we don't have a way to know which
975      // product will check the key and run the command, so we add it for all.
976      // The first to run it will perform the operation and clean up the other
977      // values.
978      if (dist->GetType() != BrowserDistribution::CHROME_BINARIES) {
979        CommandLine product_rename_cmd(rename);
980        products[i]->AppendRenameFlags(&product_rename_cmd);
981        in_use_update_work_items->AddSetRegValueWorkItem(
982            root, version_key, google_update::kRegRenameCmdField,
983            product_rename_cmd.GetCommandLineString(), true);
984      }
985    }
986
987    if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME)) {
988      AddCopyIELowRightsPolicyWorkItems(installer_state,
989                                        in_use_update_work_items.get());
990    }
991
992    post_install_task_list->AddWorkItem(in_use_update_work_items.release());
993  }
994
995  // Append work items that will be executed if this was NOT an in-use update.
996  {
997    scoped_ptr<WorkItemList> regular_update_work_items(
998        WorkItem::CreateConditionalWorkItemList(
999            new Not(new ConditionRunIfFileExists(new_chrome_exe))));
1000    regular_update_work_items->set_log_message("RegularUpdateWorkItemList");
1001
1002    // Since this was not an in-use-update, delete 'opv', 'cpv', and 'cmd' keys.
1003    for (size_t i = 0; i < products.size(); ++i) {
1004      BrowserDistribution* dist = products[i]->distribution();
1005      string16 version_key(dist->GetVersionKey());
1006      regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key,
1007          google_update::kRegOldVersionField);
1008      regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key,
1009          google_update::kRegCriticalVersionField);
1010      regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key,
1011          google_update::kRegRenameCmdField);
1012    }
1013
1014    if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME)) {
1015      AddDeleteOldIELowRightsPolicyWorkItems(installer_state,
1016                                             regular_update_work_items.get());
1017    }
1018
1019    post_install_task_list->AddWorkItem(regular_update_work_items.release());
1020  }
1021
1022  AddRegisterComDllWorkItemsForPackage(installer_state, current_version,
1023                                       new_version, post_install_task_list);
1024
1025  // If we're told that we're an MSI install, make sure to set the marker
1026  // in the client state key so that future updates do the right thing.
1027  if (installer_state.is_msi()) {
1028    for (size_t i = 0; i < products.size(); ++i) {
1029      const Product* product = products[i];
1030      AddSetMsiMarkerWorkItem(installer_state, product->distribution(), true,
1031                              post_install_task_list);
1032
1033      // We want MSI installs to take over the Add/Remove Programs shortcut.
1034      // Make a best-effort attempt to delete any shortcuts left over from
1035      // previous non-MSI installations for the same type of install (system or
1036      // per user).
1037      if (product->ShouldCreateUninstallEntry()) {
1038        AddDeleteUninstallShortcutsForMSIWorkItems(installer_state, *product,
1039                                                   temp_path,
1040                                                   post_install_task_list);
1041      }
1042    }
1043  }
1044
1045  return true;
1046}
1047
1048void AddInstallWorkItems(const InstallationState& original_state,
1049                         const InstallerState& installer_state,
1050                         const FilePath& setup_path,
1051                         const FilePath& archive_path,
1052                         const FilePath& src_path,
1053                         const FilePath& temp_path,
1054                         const Version* current_version,
1055                         const Version& new_version,
1056                         WorkItemList* install_list) {
1057  DCHECK(install_list);
1058
1059  const FilePath& target_path = installer_state.target_path();
1060
1061  // A temp directory that work items need and the actual install directory.
1062  install_list->AddCreateDirWorkItem(temp_path);
1063  install_list->AddCreateDirWorkItem(target_path);
1064
1065  if (installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER) ||
1066      installer_state.FindProduct(BrowserDistribution::CHROME_FRAME) ||
1067      installer_state.FindProduct(BrowserDistribution::CHROME_BINARIES)) {
1068    AddChromeWorkItems(original_state,
1069                       installer_state,
1070                       setup_path,
1071                       archive_path,
1072                       src_path,
1073                       temp_path,
1074                       current_version,
1075                       new_version,
1076                       install_list);
1077  }
1078
1079  if (installer_state.FindProduct(BrowserDistribution::CHROME_APP_HOST)) {
1080    install_list->AddCopyTreeWorkItem(
1081        src_path.Append(installer::kChromeAppHostExe).value(),
1082        target_path.Append(installer::kChromeAppHostExe).value(),
1083        temp_path.value(),
1084        WorkItem::ALWAYS,
1085        L"");
1086  }
1087
1088  const HKEY root = installer_state.root_key();
1089  // Only set "lang" for user-level installs since for system-level, the install
1090  // language may not be related to a given user's runtime language.
1091  const bool add_language_identifier = !installer_state.system_install();
1092
1093  const Products& products = installer_state.products();
1094  for (Products::const_iterator it = products.begin(); it < products.end();
1095       ++it) {
1096    const Product& product = **it;
1097
1098    AddUninstallShortcutWorkItems(installer_state, setup_path, new_version,
1099                                  product, install_list);
1100
1101    AddVersionKeyWorkItems(root, product.distribution(), new_version,
1102                           add_language_identifier, install_list);
1103
1104    AddDelegateExecuteWorkItems(installer_state, target_path, new_version,
1105                                product, install_list);
1106
1107    AddActiveSetupWorkItems(installer_state, setup_path, new_version, product,
1108                            install_list);
1109  }
1110
1111  // Add any remaining work items that involve special settings for
1112  // each product.
1113  AddProductSpecificWorkItems(original_state, installer_state, setup_path,
1114                              new_version, install_list);
1115
1116  // Copy over brand, usagestats, and other values.
1117  AddGoogleUpdateWorkItems(original_state, installer_state, install_list);
1118
1119  AddQuickEnableApplicationLauncherWorkItems(installer_state, original_state,
1120                                             setup_path, new_version,
1121                                             install_list);
1122
1123  AddQuickEnableChromeFrameWorkItems(installer_state, original_state,
1124                                     setup_path, new_version, install_list);
1125
1126  // Append the tasks that run after the installation.
1127  AppendPostInstallTasks(installer_state,
1128                         setup_path,
1129                         current_version,
1130                         new_version,
1131                         temp_path,
1132                         install_list);
1133}
1134
1135void AddRegisterComDllWorkItems(const FilePath& dll_folder,
1136                                const std::vector<FilePath>& dll_list,
1137                                bool system_level,
1138                                bool do_register,
1139                                bool ignore_failures,
1140                                WorkItemList* work_item_list) {
1141  DCHECK(work_item_list);
1142  if (dll_list.empty()) {
1143    VLOG(1) << "No COM DLLs to register";
1144  } else {
1145    std::vector<FilePath>::const_iterator dll_iter(dll_list.begin());
1146    for (; dll_iter != dll_list.end(); ++dll_iter) {
1147      FilePath dll_path = dll_folder.Append(*dll_iter);
1148      WorkItem* work_item = work_item_list->AddSelfRegWorkItem(
1149          dll_path.value(), do_register, !system_level);
1150      DCHECK(work_item);
1151      work_item->set_ignore_failure(ignore_failures);
1152    }
1153  }
1154}
1155
1156void AddSetMsiMarkerWorkItem(const InstallerState& installer_state,
1157                             BrowserDistribution* dist,
1158                             bool set,
1159                             WorkItemList* work_item_list) {
1160  DCHECK(work_item_list);
1161  DWORD msi_value = set ? 1 : 0;
1162  WorkItem* set_msi_work_item = work_item_list->AddSetRegValueWorkItem(
1163      installer_state.root_key(), dist->GetStateKey(),
1164      google_update::kRegMSIField, msi_value, true);
1165  DCHECK(set_msi_work_item);
1166  set_msi_work_item->set_ignore_failure(true);
1167  set_msi_work_item->set_log_message("Could not write MSI marker!");
1168}
1169
1170void AddChromeFrameWorkItems(const InstallationState& original_state,
1171                             const InstallerState& installer_state,
1172                             const FilePath& setup_path,
1173                             const Version& new_version,
1174                             const Product& product,
1175                             WorkItemList* list) {
1176  DCHECK(product.is_chrome_frame());
1177  if (!installer_state.is_multi_install()) {
1178    VLOG(1) << "Not adding GCF specific work items for single install.";
1179    return;
1180  }
1181
1182  string16 version_key(product.distribution()->GetVersionKey());
1183  bool ready_mode = product.HasOption(kOptionReadyMode);
1184  HKEY root = installer_state.root_key();
1185  const bool is_install =
1186      (installer_state.operation() != InstallerState::UNINSTALL);
1187  bool update_chrome_uninstall_command = false;
1188  BrowserDistribution* dist =
1189      installer_state.multi_package_binaries_distribution();
1190  if (ready_mode) {
1191    // If GCF is being installed in ready mode, we write an entry to the
1192    // multi-install state key.  If the value already exists, we will not
1193    // overwrite it since the user might have opted out.
1194    list->AddCreateRegKeyWorkItem(root, dist->GetStateKey());
1195    list->AddSetRegValueWorkItem(root, dist->GetStateKey(),
1196        kChromeFrameReadyModeField,
1197        static_cast<int64>(is_install ? 1 : 0),  // The value we want to set.
1198        !is_install);  // Overwrite existing value.
1199    if (is_install) {
1200      FilePath installer_path(installer_state
1201          .GetInstallerDirectory(new_version).Append(setup_path.BaseName()));
1202
1203      CommandLine basic_cl(installer_path);
1204      basic_cl.AppendSwitch(switches::kChromeFrame);
1205      basic_cl.AppendSwitch(switches::kMultiInstall);
1206
1207      if (installer_state.system_install())
1208        basic_cl.AppendSwitch(switches::kSystemLevel);
1209
1210      CommandLine temp_opt_out(basic_cl);
1211      temp_opt_out.AppendSwitch(switches::kChromeFrameReadyModeTempOptOut);
1212
1213      CommandLine end_temp_opt_out(basic_cl);
1214      end_temp_opt_out.AppendSwitch(
1215          switches::kChromeFrameReadyModeEndTempOptOut);
1216
1217      CommandLine opt_out(installer_path);
1218      AppendUninstallCommandLineFlags(installer_state, product, &opt_out);
1219      // Force Uninstall silences the prompt to reboot to complete uninstall.
1220      opt_out.AppendSwitch(switches::kForceUninstall);
1221
1222      CommandLine opt_in(basic_cl);
1223      opt_in.AppendSwitch(switches::kChromeFrameReadyModeOptIn);
1224
1225      list->AddSetRegValueWorkItem(root, version_key,
1226                                   google_update::kRegCFTempOptOutCmdField,
1227                                   temp_opt_out.GetCommandLineString(), true);
1228      list->AddSetRegValueWorkItem(root, version_key,
1229                                   google_update::kRegCFEndTempOptOutCmdField,
1230                                   end_temp_opt_out.GetCommandLineString(),
1231                                   true);
1232      list->AddSetRegValueWorkItem(root, version_key,
1233                                   google_update::kRegCFOptOutCmdField,
1234                                   opt_out.GetCommandLineString(), true);
1235      list->AddSetRegValueWorkItem(root, version_key,
1236                                   google_update::kRegCFOptInCmdField,
1237                                   opt_in.GetCommandLineString(), true);
1238    } else {
1239      // If Chrome is not also being uninstalled, we need to update its command
1240      // line so that it doesn't include uninstalling Chrome Frame now.
1241      update_chrome_uninstall_command =
1242          (installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER) ==
1243           NULL);
1244    }
1245  } else {
1246    // It doesn't matter here if we're installing or uninstalling Chrome Frame.
1247    // If ready mode isn't specified on the command line for installs, we need
1248    // to delete the ready mode flag from the registry if it exists - this
1249    // constitutes an opt-in for the user.  If we're uninstalling CF and ready
1250    // mode isn't specified on the command line, that means that CF wasn't
1251    // installed with ready mode enabled (the --ready-mode switch should be set
1252    // in the registry) so deleting the value should have no effect.
1253    // In both cases (install/uninstall), we need to make sure that Chrome's
1254    // uninstallation command line does not include the --chrome-frame switch
1255    // so that uninstalling Chrome will no longer uninstall Chrome Frame.
1256
1257    list->AddDeleteRegValueWorkItem(root, dist->GetStateKey(),
1258        kChromeFrameReadyModeField);
1259
1260    const Product* chrome =
1261        installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER);
1262    if (chrome) {
1263      // Chrome is already a part of this installation run, so we can assume
1264      // that the uninstallation arguments will be updated correctly.
1265    } else {
1266      // Chrome is not a part of this installation run, so we have to explicitly
1267      // check if Chrome is installed, and if so, update its uninstallation
1268      // command lines.
1269      const ProductState* chrome_state = original_state.GetProductState(
1270          installer_state.system_install(),
1271          BrowserDistribution::CHROME_BROWSER);
1272      update_chrome_uninstall_command =
1273          (chrome_state != NULL) && chrome_state->is_multi_install();
1274    }
1275  }
1276
1277  if (!ready_mode || !is_install) {
1278    list->AddDeleteRegValueWorkItem(root, version_key,
1279                                    google_update::kRegCFTempOptOutCmdField);
1280    list->AddDeleteRegValueWorkItem(root, version_key,
1281                                    google_update::kRegCFEndTempOptOutCmdField);
1282    list->AddDeleteRegValueWorkItem(root, version_key,
1283                                    google_update::kRegCFOptOutCmdField);
1284    list->AddDeleteRegValueWorkItem(root, version_key,
1285                                    google_update::kRegCFOptInCmdField);
1286  }
1287
1288  if (update_chrome_uninstall_command) {
1289    // Chrome is not a part of this installation run, so we have to explicitly
1290    // check if Chrome is installed, and if so, update its uninstallation
1291    // command lines.
1292    const ProductState* chrome_state = original_state.GetProductState(
1293        installer_state.system_install(), BrowserDistribution::CHROME_BROWSER);
1294    if (chrome_state != NULL) {
1295      DCHECK(chrome_state->is_multi_install());
1296      Product chrome(BrowserDistribution::GetSpecificDistribution(
1297                         BrowserDistribution::CHROME_BROWSER));
1298      chrome.InitializeFromUninstallCommand(chrome_state->uninstall_command());
1299      AddUninstallShortcutWorkItems(installer_state, setup_path,
1300                                    chrome_state->version(), chrome, list);
1301    } else {
1302      NOTREACHED() << "What happened to Chrome?";
1303    }
1304  }
1305}
1306
1307void AddDelegateExecuteWorkItems(const InstallerState& installer_state,
1308                                 const FilePath& target_path,
1309                                 const Version& new_version,
1310                                 const Product& product,
1311                                 WorkItemList* list) {
1312  string16 handler_class_uuid;
1313  BrowserDistribution* distribution = product.distribution();
1314  if (!distribution->GetCommandExecuteImplClsid(&handler_class_uuid)) {
1315    VLOG(1) << "No DelegateExecute verb handler processing to do for "
1316            << distribution->GetAppShortCutName();
1317    return;
1318  }
1319
1320  HKEY root = installer_state.root_key();
1321  string16 delegate_execute_path(L"Software\\Classes\\CLSID\\");
1322  delegate_execute_path.append(handler_class_uuid);
1323
1324  // Unconditionally remove registration regardless of whether or not it is
1325  // needed since builds after r132190 included it when it wasn't strictly
1326  // necessary.  Do this removal before adding in the new key to ensure that
1327  // the COM probe/flush below does its job.
1328  VLOG(1) << "Adding unregistration items for DelegateExecute verb handler.";
1329  list->AddDeleteRegKeyWorkItem(root, delegate_execute_path);
1330
1331  // In the past, the ICommandExecuteImpl interface and a TypeLib were both
1332  // registered.  Remove these since this operation may be updating a machine
1333  // that had the old registrations.
1334  list->AddDeleteRegKeyWorkItem(root,
1335                                L"Software\\Classes\\Interface\\"
1336                                L"{0BA0D4E9-2259-4963-B9AE-A839F7CB7544}");
1337  list->AddDeleteRegKeyWorkItem(root,
1338                                L"Software\\Classes\\TypeLib\\"
1339#if defined(GOOGLE_CHROME_BUILD)
1340                                L"{4E805ED8-EBA0-4601-9681-12815A56EBFD}"
1341#else
1342                                L"{7779FB70-B399-454A-AA1A-BAA850032B10}"
1343#endif
1344                                );
1345
1346  // Add work items to register the handler iff it is present.
1347  // See also shell_util.cc's GetProgIdEntries.
1348  if (installer_state.operation() != InstallerState::UNINSTALL) {
1349    VLOG(1) << "Adding registration items for DelegateExecute verb handler.";
1350
1351    // Force COM to flush its cache containing the path to the old handler.
1352    list->AddCallbackWorkItem(base::Bind(&ProbeCommandExecuteCallback,
1353                                         handler_class_uuid));
1354
1355    // The path to the exe (in the version directory).
1356    FilePath delegate_execute(target_path);
1357    if (new_version.IsValid())
1358      delegate_execute = delegate_execute.AppendASCII(new_version.GetString());
1359    delegate_execute = delegate_execute.Append(kDelegateExecuteExe);
1360
1361    // Command-line featuring the quoted path to the exe.
1362    string16 command(1, L'"');
1363    command.append(delegate_execute.value()).append(1, L'"');
1364
1365    // Register the CommandExecuteImpl class in Software\Classes\CLSID\...
1366    list->AddCreateRegKeyWorkItem(root, delegate_execute_path);
1367    list->AddSetRegValueWorkItem(root, delegate_execute_path, L"",
1368                                 L"CommandExecuteImpl Class", true);
1369    string16 subkey(delegate_execute_path);
1370    subkey.append(L"\\LocalServer32");
1371    list->AddCreateRegKeyWorkItem(root, subkey);
1372    list->AddSetRegValueWorkItem(root, subkey, L"", command, true);
1373    list->AddSetRegValueWorkItem(root, subkey, L"ServerExecutable",
1374                                 delegate_execute.value(), true);
1375
1376    subkey.assign(delegate_execute_path).append(L"\\Programmable");
1377    list->AddCreateRegKeyWorkItem(root, subkey);
1378  }
1379}
1380
1381void AddActiveSetupWorkItems(const InstallerState& installer_state,
1382                             const FilePath& setup_path,
1383                             const Version& new_version,
1384                             const Product& product,
1385                             WorkItemList* list) {
1386  DCHECK(installer_state.operation() != InstallerState::UNINSTALL);
1387  BrowserDistribution* distribution = product.distribution();
1388
1389  if (!product.is_chrome() || !installer_state.system_install()) {
1390    const char* install_level =
1391        installer_state.system_install() ? "system" : "user";
1392    VLOG(1) << "No Active Setup processing to do for " << install_level
1393            << "-level " << distribution->GetAppShortCutName();
1394    return;
1395  }
1396
1397  const HKEY root = HKEY_LOCAL_MACHINE;
1398  const string16 active_setup_path(
1399      InstallUtil::GetActiveSetupPath(distribution));
1400
1401  VLOG(1) << "Adding registration items for Active Setup.";
1402  list->AddCreateRegKeyWorkItem(root, active_setup_path);
1403  list->AddSetRegValueWorkItem(root, active_setup_path, L"",
1404                               distribution->GetAppShortCutName(), true);
1405
1406  CommandLine cmd(installer_state.GetInstallerDirectory(new_version).
1407      Append(setup_path.BaseName()));
1408  cmd.AppendSwitch(installer::switches::kConfigureUserSettings);
1409  cmd.AppendSwitch(installer::switches::kVerboseLogging);
1410  cmd.AppendSwitch(installer::switches::kSystemLevel);
1411  product.AppendProductFlags(&cmd);
1412  list->AddSetRegValueWorkItem(root, active_setup_path, L"StubPath",
1413                               cmd.GetCommandLineString(), true);
1414
1415  // TODO(grt): http://crbug.com/75152 Write a reference to a localized
1416  // resource.
1417  list->AddSetRegValueWorkItem(root, active_setup_path, L"Localized Name",
1418                               distribution->GetAppShortCutName(), true);
1419
1420  list->AddSetRegValueWorkItem(root, active_setup_path, L"IsInstalled",
1421                               static_cast<DWORD>(1U), true);
1422
1423  list->AddSetRegValueWorkItem(root, active_setup_path, L"Version",
1424                               kActiveSetupVersion, true);
1425}
1426
1427void AddDeleteOldIELowRightsPolicyWorkItems(
1428    const InstallerState& installer_state,
1429    WorkItemList* install_list) {
1430  DCHECK(install_list);
1431
1432  string16 key_path;
1433  GetIELowRightsElevationPolicyKeyPath(OLD_ELEVATION_POLICY, &key_path);
1434  install_list->AddDeleteRegKeyWorkItem(installer_state.root_key(), key_path);
1435}
1436
1437// Adds work items to copy the chrome_launcher IE low rights elevation policy
1438// from the primary policy GUID to the "old" policy GUID.  Take care not to
1439// perform the copy if there is already an old policy present, as the ones under
1440// the main kElevationPolicyGuid would then correspond to an intermediate
1441// version (current_version < pv < new_version).
1442void AddCopyIELowRightsPolicyWorkItems(const InstallerState& installer_state,
1443                                       WorkItemList* install_list) {
1444  DCHECK(install_list);
1445
1446  string16 current_key_path;
1447  string16 old_key_path;
1448
1449  GetIELowRightsElevationPolicyKeyPath(CURRENT_ELEVATION_POLICY,
1450                                       &current_key_path);
1451  GetIELowRightsElevationPolicyKeyPath(OLD_ELEVATION_POLICY, &old_key_path);
1452  // Do not clobber existing old policies.
1453  install_list->AddCopyRegKeyWorkItem(installer_state.root_key(),
1454                                      current_key_path, old_key_path,
1455                                      WorkItem::IF_NOT_PRESENT);
1456}
1457
1458void AppendUninstallCommandLineFlags(const InstallerState& installer_state,
1459                                     const Product& product,
1460                                     CommandLine* uninstall_cmd) {
1461  DCHECK(uninstall_cmd);
1462
1463  uninstall_cmd->AppendSwitch(installer::switches::kUninstall);
1464
1465  // Append the product-specific uninstall flags.
1466  product.AppendProductFlags(uninstall_cmd);
1467  if (installer_state.is_msi()) {
1468    uninstall_cmd->AppendSwitch(installer::switches::kMsi);
1469    // See comment in uninstall.cc where we check for the kDeleteProfile switch.
1470    if (product.is_chrome_frame()) {
1471      uninstall_cmd->AppendSwitch(installer::switches::kDeleteProfile);
1472    }
1473  }
1474  if (installer_state.system_install())
1475    uninstall_cmd->AppendSwitch(installer::switches::kSystemLevel);
1476  if (installer_state.verbose_logging())
1477    uninstall_cmd->AppendSwitch(installer::switches::kVerboseLogging);
1478}
1479
1480void RefreshElevationPolicy() {
1481  const wchar_t kIEFrameDll[] = L"ieframe.dll";
1482  const char kIERefreshPolicy[] = "IERefreshElevationPolicy";
1483
1484  HMODULE ieframe = LoadLibrary(kIEFrameDll);
1485  if (ieframe) {
1486    typedef HRESULT (__stdcall *IERefreshPolicy)();
1487    IERefreshPolicy ie_refresh_policy = reinterpret_cast<IERefreshPolicy>(
1488        GetProcAddress(ieframe, kIERefreshPolicy));
1489
1490    if (ie_refresh_policy) {
1491      ie_refresh_policy();
1492    } else {
1493      VLOG(1) << kIERefreshPolicy << " not supported.";
1494    }
1495
1496    FreeLibrary(ieframe);
1497  } else {
1498    VLOG(1) << "Cannot load " << kIEFrameDll;
1499  }
1500}
1501
1502void AddQuickEnableChromeFrameWorkItems(const InstallerState& installer_state,
1503                                        const InstallationState& machine_state,
1504                                        const FilePath& setup_path,
1505                                        const Version& new_version,
1506                                        WorkItemList* work_item_list) {
1507  DCHECK(work_item_list);
1508
1509  const bool system_install = installer_state.system_install();
1510  bool have_chrome_frame = false;
1511
1512  // STEP 1: Figure out the state of the machine before the operation.
1513  const ProductState* product_state = NULL;
1514
1515  // Is Chrome Frame !ready-mode already on the machine?
1516  product_state = machine_state.GetProductState(
1517      system_install, BrowserDistribution::CHROME_FRAME);
1518  if (product_state != NULL &&
1519      !product_state->uninstall_command().HasSwitch(
1520          switches::kChromeFrameReadyMode))
1521    have_chrome_frame = true;
1522
1523  // STEP 2: Now take into account the current operation.
1524  const Product* product = NULL;
1525
1526  if (installer_state.operation() == InstallerState::UNINSTALL) {
1527    // Forget about Chrome Frame if it is being uninstalled.  Note that we don't
1528    // bother to check !HasOption(kOptionReadyMode) since have_chrome_frame
1529    // should have been false for that case in the first place.  It's odd if it
1530    // wasn't, but the right thing to do in that case is to proceed with the
1531    // thought that CF will not be installed in any sense when we reach the
1532    // finish line.
1533    if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME) != NULL)
1534      have_chrome_frame = false;
1535  } else {
1536    // Check if we're installing Chrome Frame !ready-mode.
1537    product = installer_state.FindProduct(BrowserDistribution::CHROME_FRAME);
1538    if (product != NULL && !product->HasOption(kOptionReadyMode))
1539      have_chrome_frame = true;
1540  }
1541
1542  CommandLine cmd_line(CommandLine::NO_PROGRAM);
1543  cmd_line.AppendSwitch(switches::kMultiInstall);
1544  if (installer_state.system_install())
1545    cmd_line.AppendSwitch(switches::kSystemLevel);
1546  cmd_line.AppendSwitch(switches::kChromeFrameQuickEnable);
1547
1548  AddGenericQuickEnableWorkItems(installer_state,
1549                                 machine_state,
1550                                 setup_path,
1551                                 new_version,
1552                                 have_chrome_frame,
1553                                 cmd_line,
1554                                 kCmdQuickEnableCf,
1555                                 work_item_list);
1556}
1557
1558void AddQuickEnableApplicationLauncherWorkItems(
1559    const InstallerState& installer_state,
1560    const InstallationState& machine_state,
1561    const FilePath& setup_path,
1562    const Version& new_version,
1563    WorkItemList* work_item_list) {
1564  DCHECK(work_item_list);
1565
1566  CommandLine cmd_line(CommandLine::NO_PROGRAM);
1567  cmd_line.AppendSwitch(switches::kMultiInstall);
1568  cmd_line.AppendSwitch(switches::kChromeAppLauncher);
1569  cmd_line.AppendSwitch(switches::kEnsureGoogleUpdatePresent);
1570
1571  // For system-level binaries there is no way to keep the command state in sync
1572  // with the installation/uninstallation of the Application Launcher (which is
1573  // always at user-level).
1574  // So we pass false for 'have_child_product' to cause this command to always
1575  // be installed if the Chrome Binaries are installed.
1576  AddGenericQuickEnableWorkItems(installer_state,
1577                                 machine_state,
1578                                 setup_path,
1579                                 new_version,
1580                                 false,  // have_child_product
1581                                 cmd_line,
1582                                 kCmdQuickEnableApplicationHost,
1583                                 work_item_list);
1584}
1585
1586void AddOsUpgradeWorkItems(const InstallerState& installer_state,
1587                           const FilePath& setup_path,
1588                           const Version& new_version,
1589                           const Product& product,
1590                           WorkItemList* install_list) {
1591  const HKEY root_key = installer_state.root_key();
1592  string16 cmd_key(product.distribution()->GetVersionKey());
1593  cmd_key.append(1, FilePath::kSeparators[0])
1594      .append(google_update::kRegCommandsKey)
1595      .append(1, FilePath::kSeparators[0])
1596      .append(kCmdOnOsUpgrade);
1597
1598  if (installer_state.operation() == InstallerState::UNINSTALL) {
1599    install_list->AddDeleteRegKeyWorkItem(root_key, cmd_key)->
1600        set_log_message("Removing OS upgrade command");
1601  } else {
1602    // Register with Google Update to have setup.exe --on-os-upgrade called on
1603    // OS upgrade.
1604    CommandLine cmd_line(installer_state
1605        .GetInstallerDirectory(new_version)
1606        .Append(setup_path.BaseName()));
1607    // Add the main option to indicate OS upgrade flow.
1608    cmd_line.AppendSwitch(installer::switches::kOnOsUpgrade);
1609    // Add product-specific options.
1610    product.AppendProductFlags(&cmd_line);
1611    if (installer_state.system_install())
1612      cmd_line.AppendSwitch(installer::switches::kSystemLevel);
1613    // Log everything for now.
1614    cmd_line.AppendSwitch(installer::switches::kVerboseLogging);
1615
1616    AppCommand cmd(cmd_line.GetCommandLineString());
1617    cmd.set_is_auto_run_on_os_upgrade(true);
1618    cmd.AddWorkItems(installer_state.root_key(), cmd_key, install_list);
1619  }
1620}
1621
1622}  // namespace installer
1623