Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(807)

Side by Side Diff: chrome/installer/setup/install_worker.cc

Issue 749133003: Add fixing of MSI DisplayVersion to Chrome installs/updates. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Dear Greg Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // This file contains the definitions of the installer functions that build 5 // This file contains the definitions of the installer functions that build
6 // the WorkItemList used to install the application. 6 // the WorkItemList used to install the application.
7 7
8 #include "chrome/installer/setup/install_worker.h" 8 #include "chrome/installer/setup/install_worker.h"
9 9
10 #include <oaidl.h> 10 #include <oaidl.h>
11 #include <shlobj.h> 11 #include <shlobj.h>
12 #include <time.h> 12 #include <time.h>
13 13
14 #include <algorithm>
14 #include <vector> 15 #include <vector>
15 16
16 #include "base/bind.h" 17 #include "base/bind.h"
17 #include "base/command_line.h" 18 #include "base/command_line.h"
18 #include "base/files/file_path.h" 19 #include "base/files/file_path.h"
19 #include "base/files/file_util.h" 20 #include "base/files/file_util.h"
20 #include "base/logging.h" 21 #include "base/logging.h"
21 #include "base/memory/scoped_ptr.h" 22 #include "base/memory/scoped_ptr.h"
22 #include "base/path_service.h" 23 #include "base/path_service.h"
24 #include "base/strings/string_tokenizer.h"
23 #include "base/strings/string_util.h" 25 #include "base/strings/string_util.h"
26 #include "base/strings/stringprintf.h"
24 #include "base/strings/utf_string_conversions.h" 27 #include "base/strings/utf_string_conversions.h"
25 #include "base/version.h" 28 #include "base/version.h"
26 #include "base/win/registry.h" 29 #include "base/win/registry.h"
27 #include "base/win/scoped_comptr.h" 30 #include "base/win/scoped_comptr.h"
28 #include "base/win/windows_version.h" 31 #include "base/win/windows_version.h"
29 #include "chrome/common/chrome_constants.h" 32 #include "chrome/common/chrome_constants.h"
30 #include "chrome/common/chrome_switches.h" 33 #include "chrome/common/chrome_switches.h"
31 #include "chrome/installer/setup/install.h" 34 #include "chrome/installer/setup/install.h"
32 #include "chrome/installer/setup/setup_constants.h" 35 #include "chrome/installer/setup/setup_constants.h"
33 #include "chrome/installer/setup/setup_util.h" 36 #include "chrome/installer/setup/setup_util.h"
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 L"{6C288DD7-76FB-4721-B628-56FAC252E199}"; 74 L"{6C288DD7-76FB-4721-B628-56FAC252E199}";
72 75
73 const wchar_t kElevationPolicyKeyPath[] = 76 const wchar_t kElevationPolicyKeyPath[] =
74 L"SOFTWARE\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\"; 77 L"SOFTWARE\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\";
75 78
76 // The legacy command ids for installing an application or extension. These are 79 // The legacy command ids for installing an application or extension. These are
77 // only here so they can be removed from the registry. 80 // only here so they can be removed from the registry.
78 const wchar_t kLegacyCmdInstallApp[] = L"install-application"; 81 const wchar_t kLegacyCmdInstallApp[] = L"install-application";
79 const wchar_t kLegacyCmdInstallExtension[] = L"install-extension"; 82 const wchar_t kLegacyCmdInstallExtension[] = L"install-extension";
80 83
84 // The prefix of the ClientState value that contains the MSI product id in the
85 // format "<kMSIProductIdPrefix><PRODUCTID>".
86 const wchar_t kMSIProductIdPrefix[] = L"EnterpriseProduct";
87
88 // The common path for uninstall registry entries that show up in the
89 // "Add/Remove Programs" dialog and its later incarnations.
90 const wchar_t kUninstallRegPathRoot[] =
91 L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
92
93 // This is the path to the MSI user data cache, which should probably never be
94 // tampered with but which we'll try tweaking anyway. This is used as a printf
95 // format string with the token to be replaced by an MSI user data id generated
96 // by ConvertGUIDToMSIUserDataFormat(). Other items of note here are that this
97 // hard-codes the Local System SID meaning that this is strictly only usable for
98 // system-level installs. This would need to change for http://crbug.com/111058
99 // to be fixed.
100 const wchar_t kMSIUserDataCacheRoot[] =
101 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\"
102 L"S-1-5-18\\Products\\%ls\\InstallProperties";
103
81 void GetOldIELowRightsElevationPolicyKeyPath(base::string16* key_path) { 104 void GetOldIELowRightsElevationPolicyKeyPath(base::string16* key_path) {
82 key_path->assign(kElevationPolicyKeyPath, 105 key_path->assign(kElevationPolicyKeyPath,
83 arraysize(kElevationPolicyKeyPath) - 1); 106 arraysize(kElevationPolicyKeyPath) - 1);
84 key_path->append(kIELowRightsPolicyOldGuid, 107 key_path->append(kIELowRightsPolicyOldGuid,
85 arraysize(kIELowRightsPolicyOldGuid)- 1); 108 arraysize(kIELowRightsPolicyOldGuid)- 1);
86 } 109 }
87 110
88 // Local helper to call AddRegisterComDllWorkItems for all DLLs in a set of 111 // Local helper to call AddRegisterComDllWorkItems for all DLLs in a set of
89 // products managed by a given package. 112 // products managed by a given package.
90 // |old_version| can be NULL to indicate no Chrome is currently installed. 113 // |old_version| can be NULL to indicate no Chrome is currently installed.
(...skipping 574 matching lines...) Expand 10 before | Expand all | Expand 10 after
665 scoped_ptr<WorkItemList> no_rollback_list( 688 scoped_ptr<WorkItemList> no_rollback_list(
666 WorkItem::CreateNoRollbackWorkItemList()); 689 WorkItem::CreateNoRollbackWorkItemList());
667 AddUninstallDelegateExecuteWorkItems( 690 AddUninstallDelegateExecuteWorkItems(
668 HKEY_CURRENT_USER, google_chrome_delegate_execute_path, 691 HKEY_CURRENT_USER, google_chrome_delegate_execute_path,
669 no_rollback_list.get()); 692 no_rollback_list.get());
670 list->AddWorkItem(no_rollback_list.release()); 693 list->AddWorkItem(no_rollback_list.release());
671 VLOG(1) << "Added deletion items for bad Canary registrations."; 694 VLOG(1) << "Added deletion items for bad Canary registrations.";
672 } 695 }
673 } 696 }
674 697
698 // Returns the MSI product ID from the ClientState key that is populated for MSI
699 // installs. This property is encoded in a value name whose format is
700 // "EnterpriseId<GUID>" where <GUID> is the MSI product id. <GUID> is in the
701 // format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. The id will be returned if found
702 // otherwise this method will return an empty string.
703 //
704 // This format is strange and its provenance is shrouded in mystery but it has
705 // the data we need, so use it.
706 base::string16 ExtractMSIProductId(const InstallerState& installer_state,
707 const Product& product) {
708 HKEY reg_root = installer_state.root_key();
709 BrowserDistribution* dist = product.distribution();
710 DCHECK(dist);
711
712 base::win::RegistryValueIterator value_iter(reg_root,
713 dist->GetStateKey().c_str());
714 for (; value_iter.Valid(); ++value_iter) {
715 base::string16 value_name(value_iter.Name());
716 if (StartsWith(value_name, kMSIProductIdPrefix, false))
grt (UTC plus 2) 2014/12/01 16:19:30 uber-nit: "false /* !case_sensitive */))"
717 return value_name.substr(arraysize(kMSIProductIdPrefix) - 1);
718 }
719 return base::string16();
720 }
721
675 } // namespace 722 } // namespace
676 723
677 // This method adds work items to create (or update) Chrome uninstall entry in 724 // This method adds work items to create (or update) Chrome uninstall entry in
678 // either the Control Panel->Add/Remove Programs list or in the Omaha client 725 // either the Control Panel->Add/Remove Programs list or in the Omaha client
679 // state key if running under an MSI installer. 726 // state key if running under an MSI installer.
680 void AddUninstallShortcutWorkItems(const InstallerState& installer_state, 727 void AddUninstallShortcutWorkItems(const InstallerState& installer_state,
681 const base::FilePath& setup_path, 728 const base::FilePath& setup_path,
682 const Version& new_version, 729 const Version& new_version,
683 const Product& product, 730 const Product& product,
684 WorkItemList* install_list) { 731 WorkItemList* install_list) {
(...skipping 29 matching lines...) Expand all
714 true); 761 true);
715 install_list->AddSetRegValueWorkItem( 762 install_list->AddSetRegValueWorkItem(
716 reg_root, 763 reg_root,
717 update_state_key, 764 update_state_key,
718 KEY_WOW64_32KEY, 765 KEY_WOW64_32KEY,
719 installer::kUninstallArgumentsField, 766 installer::kUninstallArgumentsField,
720 uninstall_arguments.GetCommandLineString(), 767 uninstall_arguments.GetCommandLineString(),
721 true); 768 true);
722 769
723 // MSI installations will manage their own uninstall shortcuts. 770 // MSI installations will manage their own uninstall shortcuts.
724 if (!installer_state.is_msi() && product.ShouldCreateUninstallEntry()) { 771 if (product.ShouldCreateUninstallEntry()) {
725 // We need to quote the command line for the Add/Remove Programs dialog. 772 if (!installer_state.is_msi()) {
726 CommandLine quoted_uninstall_cmd(installer_path); 773 // We need to quote the command line for the Add/Remove Programs dialog.
727 DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"'); 774 CommandLine quoted_uninstall_cmd(installer_path);
728 quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false); 775 DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"');
776 quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false);
729 777
730 base::string16 uninstall_reg = browser_dist->GetUninstallRegPath(); 778 base::string16 uninstall_reg = browser_dist->GetUninstallRegPath();
731 install_list->AddCreateRegKeyWorkItem( 779 install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg,
732 reg_root, uninstall_reg, KEY_WOW64_32KEY); 780 KEY_WOW64_32KEY);
733 install_list->AddSetRegValueWorkItem(reg_root,
734 uninstall_reg,
735 KEY_WOW64_32KEY,
736 installer::kUninstallDisplayNameField,
737 browser_dist->GetDisplayName(),
738 true);
739 install_list->AddSetRegValueWorkItem(
740 reg_root,
741 uninstall_reg,
742 KEY_WOW64_32KEY,
743 installer::kUninstallStringField,
744 quoted_uninstall_cmd.GetCommandLineString(),
745 true);
746 install_list->AddSetRegValueWorkItem(reg_root,
747 uninstall_reg,
748 KEY_WOW64_32KEY,
749 L"InstallLocation",
750 install_path.value(),
751 true);
752
753 BrowserDistribution* dist = product.distribution();
754 base::string16 chrome_icon = ShellUtil::FormatIconLocation(
755 install_path.Append(dist->GetIconFilename()).value(),
756 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
757 install_list->AddSetRegValueWorkItem(reg_root,
758 uninstall_reg,
759 KEY_WOW64_32KEY,
760 L"DisplayIcon",
761 chrome_icon,
762 true);
763 install_list->AddSetRegValueWorkItem(reg_root,
764 uninstall_reg,
765 KEY_WOW64_32KEY,
766 L"NoModify",
767 static_cast<DWORD>(1),
768 true);
769 install_list->AddSetRegValueWorkItem(reg_root,
770 uninstall_reg,
771 KEY_WOW64_32KEY,
772 L"NoRepair",
773 static_cast<DWORD>(1),
774 true);
775
776 install_list->AddSetRegValueWorkItem(reg_root,
777 uninstall_reg,
778 KEY_WOW64_32KEY,
779 L"Publisher",
780 browser_dist->GetPublisherName(),
781 true);
782 install_list->AddSetRegValueWorkItem(reg_root,
783 uninstall_reg,
784 KEY_WOW64_32KEY,
785 L"Version",
786 ASCIIToUTF16(new_version.GetString()),
787 true);
788 install_list->AddSetRegValueWorkItem(reg_root,
789 uninstall_reg,
790 KEY_WOW64_32KEY,
791 L"DisplayVersion",
792 ASCIIToUTF16(new_version.GetString()),
793 true);
794 // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
795 // 64-bit installs place the uninstall information into the 64-bit registry.
796 install_list->AddSetRegValueWorkItem(reg_root,
797 uninstall_reg,
798 KEY_WOW64_32KEY,
799 L"InstallDate",
800 InstallUtil::GetCurrentDate(),
801 false);
802
803 const std::vector<uint16>& version_components = new_version.components();
804 if (version_components.size() == 4) {
805 // Our version should be in major.minor.build.rev.
806 install_list->AddSetRegValueWorkItem( 781 install_list->AddSetRegValueWorkItem(
807 reg_root, 782 reg_root, uninstall_reg, KEY_WOW64_32KEY,
808 uninstall_reg, 783 installer::kUninstallDisplayNameField, browser_dist->GetDisplayName(),
809 KEY_WOW64_32KEY,
810 L"VersionMajor",
811 static_cast<DWORD>(version_components[2]),
812 true); 784 true);
813 install_list->AddSetRegValueWorkItem( 785 install_list->AddSetRegValueWorkItem(
814 reg_root, 786 reg_root, uninstall_reg, KEY_WOW64_32KEY,
815 uninstall_reg, 787 installer::kUninstallStringField,
816 KEY_WOW64_32KEY, 788 quoted_uninstall_cmd.GetCommandLineString(), true);
817 L"VersionMinor", 789 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
818 static_cast<DWORD>(version_components[3]), 790 KEY_WOW64_32KEY, L"InstallLocation",
819 true); 791 install_path.value(), true);
792
793 BrowserDistribution* dist = product.distribution();
794 base::string16 chrome_icon = ShellUtil::FormatIconLocation(
795 install_path.Append(dist->GetIconFilename()).value(),
796 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
797 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
798 KEY_WOW64_32KEY, L"DisplayIcon",
799 chrome_icon, true);
800 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
801 KEY_WOW64_32KEY, L"NoModify",
802 static_cast<DWORD>(1), true);
803 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
804 KEY_WOW64_32KEY, L"NoRepair",
805 static_cast<DWORD>(1), true);
806
807 install_list->AddSetRegValueWorkItem(
808 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Publisher",
809 browser_dist->GetPublisherName(), true);
810 install_list->AddSetRegValueWorkItem(
811 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Version",
812 ASCIIToUTF16(new_version.GetString()), true);
813 install_list->AddSetRegValueWorkItem(
814 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"DisplayVersion",
815 ASCIIToUTF16(new_version.GetString()), true);
816 // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
817 // 64-bit installs place the uninstall information into the 64-bit
818 // registry.
819 install_list->AddSetRegValueWorkItem(
820 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"InstallDate",
821 InstallUtil::GetCurrentDate(), false);
822
823 const std::vector<uint16>& version_components = new_version.components();
824 if (version_components.size() == 4) {
825 // Our version should be in major.minor.build.rev.
826 install_list->AddSetRegValueWorkItem(
827 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMajor",
828 static_cast<DWORD>(version_components[2]), true);
829 install_list->AddSetRegValueWorkItem(
830 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMinor",
831 static_cast<DWORD>(version_components[3]), true);
832 }
833 } else {
834 // This is an MSI install. Largely, leave all uninstall shortcuts and the
835 // like up to the MSI machinery EXCEPT for the DisplayVersion property.
836 //
837 // Due to reasons, the Chrome version number is wider than can fit inside
838 // the <8bits>.<16bits>.<8bits> allotted to MSI version numbers. To make
839 // things work the A.B.C.D Chrome version is lossily encoded into an X.Y.Z
840 // version, dropping the unneeded A and B terms. For full details, see
841 // https://code.google.com/p/chromium/issues/detail?id=67348#c62
842 //
843 // The upshot of this is that Chrome MSIs have a different visible version
844 // than actual Chrome releases which causes trouble with some workflows.
845 // To help mitigate this, attempt to keep the DisplayVersion property in
846 // sync for MSI installs in both the Add/Remove Programs dialog and the
847 // corresponding MSI user data cache.
848
849 // Figure out MSI ProductID by looking in ClientState.
850 // The ProductID is encoded as a key named "EnterpriseProduct<ProductID>".
851 base::string16 msi_product_id(
852 ExtractMSIProductId(installer_state, product));
853 if (!msi_product_id.empty()) {
854 base::string16 uninstall_reg_path(kUninstallRegPathRoot);
855 uninstall_reg_path.append(L"{");
856 uninstall_reg_path.append(msi_product_id);
857 uninstall_reg_path.append(L"}");
858
859 install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg_path,
860 WorkItem::kWow64Default);
861 install_list->AddSetRegValueWorkItem(
862 reg_root, uninstall_reg_path, WorkItem::kWow64Default,
863 installer::kUninstallDisplayVersionField,
864 base::ASCIIToUTF16(new_version.GetString()), true)->
865 set_ignore_failure(true);
866
867 // Also fix up the MSI user data cache. This is a little hacky, there
868 // may be a better way and I am not certain this won't cause undesired
869 // side-effects. Note that the user data cache registry path includes
870 // the product id without dashes. Also note that the
871 // kMSIUserDataCacheRoot currently hardcodes the local system SID, which
872 // will need to be changed for user-level MSI installs to happen as
873 // tracked by http://crbug.com/111058. Leave a DCHECK here to warn those
874 // who next venture here to use a correct SID in kMSIUserDataCacheRoot.
875 DCHECK(installer_state.system_install());
876
877 base::string16 msi_user_data_product_id(
878 ConvertGUIDToMSIUserDataFormat(msi_product_id));
879 if (!msi_user_data_product_id.empty()) {
880 base::string16 msi_cache_reg_path(base::StringPrintf(
881 kMSIUserDataCacheRoot, msi_user_data_product_id.c_str()));
882
883 install_list->AddSetRegValueWorkItem(
884 reg_root, msi_cache_reg_path, KEY_WOW64_64KEY,
885 installer::kUninstallDisplayVersionField,
886 base::ASCIIToUTF16(new_version.GetString()), true)->
887 set_ignore_failure(true);
888 } else {
889 VLOG(1) << "Failed to convert MSI user data id from product id: "
890 << msi_product_id;
891 }
892 } else {
893 VLOG(1) << "Failed to extract MSI product id.";
894 }
820 } 895 }
821 } 896 }
822 } 897 }
823 898
824 // Create Version key for a product (if not already present) and sets the new 899 // Create Version key for a product (if not already present) and sets the new
825 // product version as the last step. 900 // product version as the last step.
826 void AddVersionKeyWorkItems(HKEY root, 901 void AddVersionKeyWorkItems(HKEY root,
827 const base::string16& version_key, 902 const base::string16& version_key,
828 const base::string16& product_name, 903 const base::string16& product_name,
829 const Version& new_version, 904 const Version& new_version,
(...skipping 833 matching lines...) Expand 10 before | Expand all | Expand 10 after
1663 1738
1664 // Unconditionally remove the legacy Quick Enable command from the binaries. 1739 // Unconditionally remove the legacy Quick Enable command from the binaries.
1665 // Do this even if multi-install Chrome isn't installed to ensure that it is 1740 // Do this even if multi-install Chrome isn't installed to ensure that it is
1666 // not left behind in any case. 1741 // not left behind in any case.
1667 work_item_list->AddDeleteRegKeyWorkItem( 1742 work_item_list->AddDeleteRegKeyWorkItem(
1668 installer_state.root_key(), cmd_key, KEY_WOW64_32KEY) 1743 installer_state.root_key(), cmd_key, KEY_WOW64_32KEY)
1669 ->set_log_message("removing " + base::UTF16ToASCII(kCmdQuickEnableCf) + 1744 ->set_log_message("removing " + base::UTF16ToASCII(kCmdQuickEnableCf) +
1670 " command"); 1745 " command");
1671 } 1746 }
1672 1747
1748 // Converts a correctly formatted guid thusly:
1749 // 12345678-90ab-cdef-ghij-klmnopqrstuv -> 87654321ba09fedchgjilknmporqtsvu
1750 // This reverses the string of the first three terms and inverts the bytes of
1751 // the last two. It also removes the dashes. It expects the input to NOT be
1752 // wrapped in braces.
1753 // Returns an empty string on failure.
1754 base::string16 ConvertGUIDToMSIUserDataFormat(const base::string16& guid) {
1755 // Perform some very mild validation of the input.
1756 if (guid.size() != 36)
1757 return base::string16();
1758
1759 base::string16 msi_formatted_guid;
1760 int token_count = 0;
1761 base::WStringTokenizer tokenizer(guid, L"-");
1762 while (tokenizer.GetNext()) {
1763 // There should be exactly five tokens, bail if not.
1764 if (token_count >= 5)
1765 return base::string16();
1766
1767 base::string16 token(tokenizer.token());
1768 if (token_count < 3) {
1769 // For the first three tokens, just reverse the characters.
1770 std::reverse(token.begin(), token.end());
1771 msi_formatted_guid += token;
1772 } else {
1773 // The last two tokens need to be of even length.
1774 if (token.size() % 2)
1775 return base::string16();
1776 // Swap the halves of each byte (i.e. pair of chars). I don't even.
1777 for (size_t i = 0; i < token.size(); i += 2) {
1778 msi_formatted_guid += token[i+1];
1779 msi_formatted_guid += token[i];
1780 }
1781 }
1782 token_count++;
1783 }
1784
1785 return (token_count == 5) ? msi_formatted_guid : base::string16();
1786 }
1787
1673 } // namespace installer 1788 } // namespace installer
OLDNEW
« no previous file with comments | « chrome/installer/setup/install_worker.h ('k') | chrome/installer/setup/install_worker_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698