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

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: rebase Created 5 years, 11 months 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 561 matching lines...) Expand 10 before | Expand all | Expand 10 after
652 scoped_ptr<WorkItemList> no_rollback_list( 675 scoped_ptr<WorkItemList> no_rollback_list(
653 WorkItem::CreateNoRollbackWorkItemList()); 676 WorkItem::CreateNoRollbackWorkItemList());
654 AddUninstallDelegateExecuteWorkItems( 677 AddUninstallDelegateExecuteWorkItems(
655 HKEY_CURRENT_USER, google_chrome_delegate_execute_path, 678 HKEY_CURRENT_USER, google_chrome_delegate_execute_path,
656 no_rollback_list.get()); 679 no_rollback_list.get());
657 list->AddWorkItem(no_rollback_list.release()); 680 list->AddWorkItem(no_rollback_list.release());
658 VLOG(1) << "Added deletion items for bad Canary registrations."; 681 VLOG(1) << "Added deletion items for bad Canary registrations.";
659 } 682 }
660 } 683 }
661 684
685 // Returns the MSI product ID from the ClientState key that is populated for MSI
686 // installs. This property is encoded in a value name whose format is
687 // "EnterpriseId<GUID>" where <GUID> is the MSI product id. <GUID> is in the
688 // format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. The id will be returned if found
689 // otherwise this method will return an empty string.
690 //
691 // This format is strange and its provenance is shrouded in mystery but it has
692 // the data we need, so use it.
693 base::string16 ExtractMSIProductId(const InstallerState& installer_state,
694 const Product& product) {
695 HKEY reg_root = installer_state.root_key();
696 BrowserDistribution* dist = product.distribution();
697 DCHECK(dist);
698
699 base::win::RegistryValueIterator value_iter(reg_root,
700 dist->GetStateKey().c_str());
701 for (; value_iter.Valid(); ++value_iter) {
702 base::string16 value_name(value_iter.Name());
703 if (StartsWith(value_name, kMSIProductIdPrefix, false))
704 return value_name.substr(arraysize(kMSIProductIdPrefix) - 1);
705 }
706 return base::string16();
707 }
708
662 } // namespace 709 } // namespace
663 710
664 // This method adds work items to create (or update) Chrome uninstall entry in 711 // This method adds work items to create (or update) Chrome uninstall entry in
665 // either the Control Panel->Add/Remove Programs list or in the Omaha client 712 // either the Control Panel->Add/Remove Programs list or in the Omaha client
666 // state key if running under an MSI installer. 713 // state key if running under an MSI installer.
667 void AddUninstallShortcutWorkItems(const InstallerState& installer_state, 714 void AddUninstallShortcutWorkItems(const InstallerState& installer_state,
668 const base::FilePath& setup_path, 715 const base::FilePath& setup_path,
669 const Version& new_version, 716 const Version& new_version,
670 const Product& product, 717 const Product& product,
671 WorkItemList* install_list) { 718 WorkItemList* install_list) {
(...skipping 29 matching lines...) Expand all
701 true); 748 true);
702 install_list->AddSetRegValueWorkItem( 749 install_list->AddSetRegValueWorkItem(
703 reg_root, 750 reg_root,
704 update_state_key, 751 update_state_key,
705 KEY_WOW64_32KEY, 752 KEY_WOW64_32KEY,
706 installer::kUninstallArgumentsField, 753 installer::kUninstallArgumentsField,
707 uninstall_arguments.GetCommandLineString(), 754 uninstall_arguments.GetCommandLineString(),
708 true); 755 true);
709 756
710 // MSI installations will manage their own uninstall shortcuts. 757 // MSI installations will manage their own uninstall shortcuts.
711 if (!installer_state.is_msi() && product.ShouldCreateUninstallEntry()) { 758 if (product.ShouldCreateUninstallEntry()) {
712 // We need to quote the command line for the Add/Remove Programs dialog. 759 if (!installer_state.is_msi()) {
713 base::CommandLine quoted_uninstall_cmd(installer_path); 760 // We need to quote the command line for the Add/Remove Programs dialog.
714 DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"'); 761 base::CommandLine quoted_uninstall_cmd(installer_path);
715 quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false); 762 DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"');
763 quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false);
716 764
717 base::string16 uninstall_reg = browser_dist->GetUninstallRegPath(); 765 base::string16 uninstall_reg = browser_dist->GetUninstallRegPath();
718 install_list->AddCreateRegKeyWorkItem( 766 install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg,
719 reg_root, uninstall_reg, KEY_WOW64_32KEY); 767 KEY_WOW64_32KEY);
720 install_list->AddSetRegValueWorkItem(reg_root,
721 uninstall_reg,
722 KEY_WOW64_32KEY,
723 installer::kUninstallDisplayNameField,
724 browser_dist->GetDisplayName(),
725 true);
726 install_list->AddSetRegValueWorkItem(
727 reg_root,
728 uninstall_reg,
729 KEY_WOW64_32KEY,
730 installer::kUninstallStringField,
731 quoted_uninstall_cmd.GetCommandLineString(),
732 true);
733 install_list->AddSetRegValueWorkItem(reg_root,
734 uninstall_reg,
735 KEY_WOW64_32KEY,
736 L"InstallLocation",
737 install_path.value(),
738 true);
739
740 BrowserDistribution* dist = product.distribution();
741 base::string16 chrome_icon = ShellUtil::FormatIconLocation(
742 install_path.Append(dist->GetIconFilename()),
743 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
744 install_list->AddSetRegValueWorkItem(reg_root,
745 uninstall_reg,
746 KEY_WOW64_32KEY,
747 L"DisplayIcon",
748 chrome_icon,
749 true);
750 install_list->AddSetRegValueWorkItem(reg_root,
751 uninstall_reg,
752 KEY_WOW64_32KEY,
753 L"NoModify",
754 static_cast<DWORD>(1),
755 true);
756 install_list->AddSetRegValueWorkItem(reg_root,
757 uninstall_reg,
758 KEY_WOW64_32KEY,
759 L"NoRepair",
760 static_cast<DWORD>(1),
761 true);
762
763 install_list->AddSetRegValueWorkItem(reg_root,
764 uninstall_reg,
765 KEY_WOW64_32KEY,
766 L"Publisher",
767 browser_dist->GetPublisherName(),
768 true);
769 install_list->AddSetRegValueWorkItem(reg_root,
770 uninstall_reg,
771 KEY_WOW64_32KEY,
772 L"Version",
773 ASCIIToUTF16(new_version.GetString()),
774 true);
775 install_list->AddSetRegValueWorkItem(reg_root,
776 uninstall_reg,
777 KEY_WOW64_32KEY,
778 L"DisplayVersion",
779 ASCIIToUTF16(new_version.GetString()),
780 true);
781 // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
782 // 64-bit installs place the uninstall information into the 64-bit registry.
783 install_list->AddSetRegValueWorkItem(reg_root,
784 uninstall_reg,
785 KEY_WOW64_32KEY,
786 L"InstallDate",
787 InstallUtil::GetCurrentDate(),
788 false);
789
790 const std::vector<uint16>& version_components = new_version.components();
791 if (version_components.size() == 4) {
792 // Our version should be in major.minor.build.rev.
793 install_list->AddSetRegValueWorkItem( 768 install_list->AddSetRegValueWorkItem(
794 reg_root, 769 reg_root, uninstall_reg, KEY_WOW64_32KEY,
795 uninstall_reg, 770 installer::kUninstallDisplayNameField, browser_dist->GetDisplayName(),
796 KEY_WOW64_32KEY,
797 L"VersionMajor",
798 static_cast<DWORD>(version_components[2]),
799 true); 771 true);
800 install_list->AddSetRegValueWorkItem( 772 install_list->AddSetRegValueWorkItem(
801 reg_root, 773 reg_root, uninstall_reg, KEY_WOW64_32KEY,
802 uninstall_reg, 774 installer::kUninstallStringField,
803 KEY_WOW64_32KEY, 775 quoted_uninstall_cmd.GetCommandLineString(), true);
804 L"VersionMinor", 776 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
805 static_cast<DWORD>(version_components[3]), 777 KEY_WOW64_32KEY, L"InstallLocation",
806 true); 778 install_path.value(), true);
779
780 BrowserDistribution* dist = product.distribution();
781 base::string16 chrome_icon = ShellUtil::FormatIconLocation(
782 install_path.Append(dist->GetIconFilename()).value(),
783 dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
784 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
785 KEY_WOW64_32KEY, L"DisplayIcon",
786 chrome_icon, true);
787 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
788 KEY_WOW64_32KEY, L"NoModify",
789 static_cast<DWORD>(1), true);
790 install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
791 KEY_WOW64_32KEY, L"NoRepair",
792 static_cast<DWORD>(1), true);
793
794 install_list->AddSetRegValueWorkItem(
795 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Publisher",
796 browser_dist->GetPublisherName(), true);
797 install_list->AddSetRegValueWorkItem(
798 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Version",
799 ASCIIToUTF16(new_version.GetString()), true);
800 install_list->AddSetRegValueWorkItem(
801 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"DisplayVersion",
802 ASCIIToUTF16(new_version.GetString()), true);
803 // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
804 // 64-bit installs place the uninstall information into the 64-bit
805 // registry.
806 install_list->AddSetRegValueWorkItem(
807 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"InstallDate",
808 InstallUtil::GetCurrentDate(), false);
809
810 const std::vector<uint16>& version_components = new_version.components();
811 if (version_components.size() == 4) {
812 // Our version should be in major.minor.build.rev.
813 install_list->AddSetRegValueWorkItem(
814 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMajor",
815 static_cast<DWORD>(version_components[2]), true);
816 install_list->AddSetRegValueWorkItem(
817 reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMinor",
818 static_cast<DWORD>(version_components[3]), true);
819 }
820 } else {
821 // This is an MSI install. Largely, leave all uninstall shortcuts and the
822 // like up to the MSI machinery EXCEPT for the DisplayVersion property.
823 //
824 // Due to reasons, the Chrome version number is wider than can fit inside
825 // the <8bits>.<16bits>.<8bits> allotted to MSI version numbers. To make
826 // things work the A.B.C.D Chrome version is lossily encoded into an X.Y.Z
827 // version, dropping the unneeded A and B terms. For full details, see
828 // https://code.google.com/p/chromium/issues/detail?id=67348#c62
829 //
830 // The upshot of this is that Chrome MSIs have a different visible version
831 // than actual Chrome releases which causes trouble with some workflows.
832 // To help mitigate this, attempt to keep the DisplayVersion property in
833 // sync for MSI installs in both the Add/Remove Programs dialog and the
834 // corresponding MSI user data cache.
835
836 // Figure out MSI ProductID by looking in ClientState.
837 // The ProductID is encoded as a key named "EnterpriseProduct<ProductID>".
838 base::string16 msi_product_id(
839 ExtractMSIProductId(installer_state, product));
840 if (!msi_product_id.empty()) {
841 base::string16 uninstall_reg_path(kUninstallRegPathRoot);
842 uninstall_reg_path.append(L"{");
843 uninstall_reg_path.append(msi_product_id);
844 uninstall_reg_path.append(L"}");
845
846 install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg_path,
847 WorkItem::kWow64Default);
848 install_list->AddSetRegValueWorkItem(
849 reg_root, uninstall_reg_path, WorkItem::kWow64Default,
850 installer::kUninstallDisplayVersionField,
851 base::ASCIIToUTF16(new_version.GetString()), true)->
852 set_ignore_failure(true);
853
854 // Also fix up the MSI user data cache. This is a little hacky, there
855 // may be a better way and I am not certain this won't cause undesired
856 // side-effects. Note that the user data cache registry path includes
857 // the product id without dashes. Also note that the
858 // kMSIUserDataCacheRoot currently hardcodes the local system SID, which
859 // will need to be changed for user-level MSI installs to happen as
860 // tracked by http://crbug.com/111058. Leave a DCHECK here to warn those
861 // who next venture here to use a correct SID in kMSIUserDataCacheRoot.
862 DCHECK(installer_state.system_install());
863
864 base::string16 msi_user_data_product_id(
865 ConvertGUIDToMSIUserDataFormat(msi_product_id));
866 if (!msi_user_data_product_id.empty()) {
867 base::string16 msi_cache_reg_path(base::StringPrintf(
868 kMSIUserDataCacheRoot, msi_user_data_product_id.c_str()));
869
870 install_list->AddSetRegValueWorkItem(
871 reg_root, msi_cache_reg_path, KEY_WOW64_64KEY,
872 installer::kUninstallDisplayVersionField,
873 base::ASCIIToUTF16(new_version.GetString()), true)->
874 set_ignore_failure(true);
875 } else {
876 LOG(DFATAL) << "Failed to convert MSI user data id from product id: "
877 << msi_product_id;
878 }
879 } else {
880 LOG(ERROR) << "Failed to extract MSI product id.";
881 }
807 } 882 }
808 } 883 }
809 } 884 }
810 885
811 // Create Version key for a product (if not already present) and sets the new 886 // Create Version key for a product (if not already present) and sets the new
812 // product version as the last step. 887 // product version as the last step.
813 void AddVersionKeyWorkItems(HKEY root, 888 void AddVersionKeyWorkItems(HKEY root,
814 const base::string16& version_key, 889 const base::string16& version_key,
815 const base::string16& product_name, 890 const base::string16& product_name,
816 const Version& new_version, 891 const Version& new_version,
(...skipping 833 matching lines...) Expand 10 before | Expand all | Expand 10 after
1650 1725
1651 // Unconditionally remove the legacy Quick Enable command from the binaries. 1726 // Unconditionally remove the legacy Quick Enable command from the binaries.
1652 // Do this even if multi-install Chrome isn't installed to ensure that it is 1727 // Do this even if multi-install Chrome isn't installed to ensure that it is
1653 // not left behind in any case. 1728 // not left behind in any case.
1654 work_item_list->AddDeleteRegKeyWorkItem( 1729 work_item_list->AddDeleteRegKeyWorkItem(
1655 installer_state.root_key(), cmd_key, KEY_WOW64_32KEY) 1730 installer_state.root_key(), cmd_key, KEY_WOW64_32KEY)
1656 ->set_log_message("removing " + base::UTF16ToASCII(kCmdQuickEnableCf) + 1731 ->set_log_message("removing " + base::UTF16ToASCII(kCmdQuickEnableCf) +
1657 " command"); 1732 " command");
1658 } 1733 }
1659 1734
1735 // Converts a correctly formatted guid thusly:
1736 // 12345678-90ab-cdef-ghij-klmnopqrstuv -> 87654321ba09fedchgjilknmporqtsvu
1737 // This reverses the string of the first three terms and inverts the bytes of
1738 // the last two. It also removes the dashes. It expects the input to NOT be
1739 // wrapped in braces.
1740 // Returns an empty string on failure.
1741 base::string16 ConvertGUIDToMSIUserDataFormat(const base::string16& guid) {
1742 // Perform some very mild validation of the input.
1743 if (guid.size() != 36)
1744 return base::string16();
1745
1746 base::string16 msi_formatted_guid;
1747 int token_count = 0;
1748 base::WStringTokenizer tokenizer(guid, L"-");
1749 while (tokenizer.GetNext()) {
1750 // There should be exactly five tokens, bail if not.
1751 if (token_count >= 5)
1752 return base::string16();
1753
1754 base::string16 token(tokenizer.token());
1755 if (token_count < 3) {
1756 // For the first three tokens, just reverse the characters.
1757 std::reverse(token.begin(), token.end());
1758 msi_formatted_guid += token;
1759 } else {
1760 // The last two tokens need to be of even length.
1761 if (token.size() % 2)
1762 return base::string16();
1763 // Swap the halves of each byte (i.e. pair of chars). I don't even.
1764 for (size_t i = 0; i < token.size(); i += 2) {
1765 msi_formatted_guid += token[i+1];
1766 msi_formatted_guid += token[i];
1767 }
1768 }
1769 token_count++;
1770 }
1771
1772 return (token_count == 5) ? msi_formatted_guid : base::string16();
1773 }
1774
1660 } // namespace installer 1775 } // 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