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