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

Unified 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: Pre-review cleanup. Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/installer/setup/install_worker.cc
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index cca3132c6606beb850f0a1e8213422f4a586343c..e6f74116aafc704ae47411ab93cf3e73fde65785 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -11,6 +11,7 @@
#include <shlobj.h>
#include <time.h>
+#include <algorithm>
#include <vector>
#include "base/bind.h"
@@ -20,7 +21,9 @@
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
+#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version.h"
#include "base/win/registry.h"
@@ -78,6 +81,24 @@ const wchar_t kElevationPolicyKeyPath[] =
const wchar_t kLegacyCmdInstallApp[] = L"install-application";
const wchar_t kLegacyCmdInstallExtension[] = L"install-extension";
+// The prefix of the ClientState value that contains the MSI product id in the
+// format "<kMSIProductIdPrefix><PRODUCTID>".
+const wchar_t kMSIProductIdPrefix[] = L"EnterpriseProduct";
+
+// The common path for uninstall registry entries that show up in the
+// "Add/Remove Programs" dialog and its later incarnations.
+const wchar_t kUninstallRegPathRoot[] =
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
+
+// This is the path to the MSI user data cache, which should probably never be
+// tampered with but which we'll try tweaking anyway. Other items of note here
+// are that this hard-codes the Local System SID meaning that this is strictly
+// only usable for system-level installs. This would need to change for
+// http://crbug.com/111058 to be fixed.
grt (UTC plus 2) 2014/11/27 19:42:04 please add to the doc comment that this is a forma
robertshield 2014/11/27 22:25:57 Done.
+const wchar_t kMSIUserDataCacheRoot[] =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\"
+ L"S-1-5-18\\Products\\%ls\\InstallProperties";
+
void GetOldIELowRightsElevationPolicyKeyPath(base::string16* key_path) {
key_path->assign(kElevationPolicyKeyPath,
arraysize(kElevationPolicyKeyPath) - 1);
@@ -672,8 +693,81 @@ void CleanupBadCanaryDelegateExecuteRegistration(
}
}
+// Returns the MSI product ID from the ClientState key that is populated for MSI
+// installs. This property is encoded in a value name whose format is
+// "EnterpriseId<GUID>" where <GUID> is the MSI product id. <GUID> is in the
+// format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. The id will be placed in
+// |product_id| if the latter is non-null.
+//
+// This format is strange and its provenance is shrouded in mystery but it has
+// the data we need, so use it.
+bool ExtractMSIProductId(const InstallerState& installer_state,
+ const Product& product,
+ base::string16* product_id) {
grt (UTC plus 2) 2014/11/27 19:42:04 why the out param for this? why not return the pro
robertshield 2014/11/27 22:25:57 The result of pruning an earlier API and not pruni
+ HKEY reg_root = installer_state.root_key();
+ BrowserDistribution* dist = product.distribution();
+ DCHECK(dist);
+
+ base::win::RegistryValueIterator value_iter(reg_root,
+ dist->GetStateKey().c_str());
+ while (value_iter.Valid()) {
grt (UTC plus 2) 2014/11/27 19:42:04 nit: for (; value_iter.Valid(); ++value_iter) {
robertshield 2014/11/27 22:25:57 Done.
+ base::string16 value_name(value_iter.Name());
+ if (StartsWith(value_name, kMSIProductIdPrefix, false)) {
+ if (product_id) {
+ *product_id += value_name.substr(arraysize(kMSIProductIdPrefix) - 1);
grt (UTC plus 2) 2014/11/27 19:42:04 why append?
robertshield 2014/11/27 22:25:57 Done.
+ }
+ return true;
+ }
+ ++value_iter;
+ }
+ return false;
+}
+
} // namespace
+// Converts a correctly formatted guid thusly:
+// 12345678-90ab-cdef-ghij-klmnopqrstuv -> 87654321ba09fedchgjilknmporqtsvu
+// This reverses the string of the first three terms and inverts the bytes of
+// the last two. It also removes the dashes. It expects the input to NOT be
+// wrapped in braces.
+// Returns an empty string on failure.
+base::string16 ConvertGUIDToMSIUserDataFormat(const base::string16& guid) {
grt (UTC plus 2) 2014/11/27 19:42:04 please keep the definition order here in the .cc f
robertshield 2014/11/27 22:25:57 Done.
+ // Perform some very mild validation of the input.
+ if (guid.size() != 36)
+ return base::string16();
+
+ base::string16 msi_formatted_guid;
+ int token_count = 0;
+ base::WStringTokenizer t(guid, L"-");
grt (UTC plus 2) 2014/11/27 19:42:04 t -> tokenizer ("Function names, variable names, a
robertshield 2014/11/27 22:25:57 Done.
+ while (t.GetNext()) {
+ // There should be exactly five tokens, bail if not.
+ if (token_count >= 5)
+ return base::string16();
+
+ base::string16 token(t.token());
+ if (token_count < 3) {
+ // For the first three tokens, just reverse the characters.
+ std::reverse(token.begin(), token.end());
+ msi_formatted_guid += token;
+ } else {
+ // The last two tokens need to be of even length.
+ if (token.size() % 2)
+ return base::string16();
+ // Swap the halves of each byte (i.e. pair of chars). I don't even.
+ for (size_t i = 0; i < token.size(); i += 2) {
+ msi_formatted_guid += token[i+1];
+ msi_formatted_guid += token[i];
+ }
+ }
+ token_count++;
+ }
+
+ if (token_count == 5)
grt (UTC plus 2) 2014/11/27 19:42:04 nit: more shorter? return (token_count == 5) ? m
robertshield 2014/11/27 22:25:57 Done.
+ return msi_formatted_guid;
+ else
+ return base::string16();
+}
+
// This method adds work items to create (or update) Chrome uninstall entry in
// either the Control Panel->Add/Remove Programs list or in the Omaha client
// state key if running under an MSI installer.
@@ -721,102 +815,127 @@ void AddUninstallShortcutWorkItems(const InstallerState& installer_state,
true);
// MSI installations will manage their own uninstall shortcuts.
- if (!installer_state.is_msi() && product.ShouldCreateUninstallEntry()) {
- // We need to quote the command line for the Add/Remove Programs dialog.
- CommandLine quoted_uninstall_cmd(installer_path);
- DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"');
- quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false);
-
- base::string16 uninstall_reg = browser_dist->GetUninstallRegPath();
- install_list->AddCreateRegKeyWorkItem(
- reg_root, uninstall_reg, KEY_WOW64_32KEY);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- installer::kUninstallDisplayNameField,
- browser_dist->GetDisplayName(),
- true);
- install_list->AddSetRegValueWorkItem(
- reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- installer::kUninstallStringField,
- quoted_uninstall_cmd.GetCommandLineString(),
- true);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"InstallLocation",
- install_path.value(),
- true);
-
- BrowserDistribution* dist = product.distribution();
- base::string16 chrome_icon = ShellUtil::FormatIconLocation(
- install_path.Append(dist->GetIconFilename()).value(),
- dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"DisplayIcon",
- chrome_icon,
- true);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"NoModify",
- static_cast<DWORD>(1),
- true);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"NoRepair",
- static_cast<DWORD>(1),
- true);
-
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"Publisher",
- browser_dist->GetPublisherName(),
- true);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"Version",
- ASCIIToUTF16(new_version.GetString()),
- true);
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"DisplayVersion",
- ASCIIToUTF16(new_version.GetString()),
- true);
- // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
- // 64-bit installs place the uninstall information into the 64-bit registry.
- install_list->AddSetRegValueWorkItem(reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"InstallDate",
- InstallUtil::GetCurrentDate(),
- false);
-
- const std::vector<uint16>& version_components = new_version.components();
- if (version_components.size() == 4) {
- // Our version should be in major.minor.build.rev.
+ if (product.ShouldCreateUninstallEntry()) {
+ if (!installer_state.is_msi()) {
+ // We need to quote the command line for the Add/Remove Programs dialog.
+ CommandLine quoted_uninstall_cmd(installer_path);
+ DCHECK_EQ(quoted_uninstall_cmd.GetCommandLineString()[0], '"');
+ quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false);
+
+ base::string16 uninstall_reg = browser_dist->GetUninstallRegPath();
+ install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg,
+ KEY_WOW64_32KEY);
install_list->AddSetRegValueWorkItem(
- reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"VersionMajor",
- static_cast<DWORD>(version_components[2]),
+ reg_root, uninstall_reg, KEY_WOW64_32KEY,
+ installer::kUninstallDisplayNameField, browser_dist->GetDisplayName(),
true);
install_list->AddSetRegValueWorkItem(
- reg_root,
- uninstall_reg,
- KEY_WOW64_32KEY,
- L"VersionMinor",
- static_cast<DWORD>(version_components[3]),
- true);
+ reg_root, uninstall_reg, KEY_WOW64_32KEY,
+ installer::kUninstallStringField,
+ quoted_uninstall_cmd.GetCommandLineString(), true);
+ install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
+ KEY_WOW64_32KEY, L"InstallLocation",
+ install_path.value(), true);
+
+ BrowserDistribution* dist = product.distribution();
+ base::string16 chrome_icon = ShellUtil::FormatIconLocation(
+ install_path.Append(dist->GetIconFilename()).value(),
+ dist->GetIconIndex(BrowserDistribution::SHORTCUT_CHROME));
+ install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
+ KEY_WOW64_32KEY, L"DisplayIcon",
+ chrome_icon, true);
+ install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
+ KEY_WOW64_32KEY, L"NoModify",
+ static_cast<DWORD>(1), true);
+ install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg,
+ KEY_WOW64_32KEY, L"NoRepair",
+ static_cast<DWORD>(1), true);
+
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Publisher",
+ browser_dist->GetPublisherName(), true);
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"Version",
+ ASCIIToUTF16(new_version.GetString()), true);
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"DisplayVersion",
+ ASCIIToUTF16(new_version.GetString()), true);
+ // TODO(wfh): Ensure that this value is preserved in the 64-bit hive when
+ // 64-bit installs place the uninstall information into the 64-bit
+ // registry.
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"InstallDate",
+ InstallUtil::GetCurrentDate(), false);
+
+ const std::vector<uint16>& version_components = new_version.components();
+ if (version_components.size() == 4) {
+ // Our version should be in major.minor.build.rev.
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMajor",
+ static_cast<DWORD>(version_components[2]), true);
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg, KEY_WOW64_32KEY, L"VersionMinor",
+ static_cast<DWORD>(version_components[3]), true);
+ }
+ } else {
+ // This is an MSI install. Largely, leave all uninstall shortcuts and the
+ // like up to the MSI machinery EXCEPT for the DisplayVersion property.
+ //
+ // Due to reasons, the Chrome version number is wider than can fit inside
+ // the <8bits>.<16bits>.<8bits> allotted to MSI version numbers. To make
+ // things work the A.B.C.D Chrome version is lossily encoded into an X.Y.Z
+ // version, dropping the unneeded A and B terms. For full details, see
+ // https://code.google.com/p/chromium/issues/detail?id=67348#c62
+ //
+ // The upshot of this is that Chrome MSIs have a different visible version
+ // than actual Chrome releases which causes trouble with some workflows.
+ // To help mitigate this, attempt to keep the DisplayVersion property in
+ // sync for MSI installs in both the Add/Remove Programs dialog and the
+ // corresponding MSI user data cache.
+
+ // Figure out MSI ProductID by looking in ClientState.
+ // The ProductID is encoded as a key named "EnterpriseProduct<ProductID>".
+ base::string16 msi_product_id;
+ if (ExtractMSIProductId(installer_state, product, &msi_product_id)) {
+ base::string16 uninstall_reg_path(kUninstallRegPathRoot);
+ uninstall_reg_path.append(L"{");
+ uninstall_reg_path.append(msi_product_id);
+ uninstall_reg_path.append(L"}");
+
+ install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg_path,
grt (UTC plus 2) 2014/11/27 19:42:04 this key is owned by Windows, no? if so, it isn't
robertshield 2014/11/27 22:25:57 Hrmm, that's not unreasonable: the key is owned by
+ WorkItem::kWow64Default);
+ install_list->AddSetRegValueWorkItem(
+ reg_root, uninstall_reg_path, WorkItem::kWow64Default,
+ installer::kUninstallDisplayVersionField,
+ base::ASCIIToWide(new_version.GetString()), true);
grt (UTC plus 2) 2014/11/27 19:42:04 please use base::ASCIIToUTF16 here and on line 931
grt (UTC plus 2) 2014/11/27 19:42:04 should failures here not also be ignored?
robertshield 2014/11/27 22:25:57 Done.
robertshield 2014/11/27 22:25:57 Ok. <grump> If ASCIIToWide is deprecated, it shou
grt (UTC plus 2) 2014/12/01 16:19:30 Agreed.
+
+ // Also fix up the MSI user data cache. This is a little hacky, there
+ // may be a better way and I am not certain this won't cause undesired
+ // side-effects. Note that the user data cache registry path includes
+ // the product id without dashes. Also note that the
+ // kMSIUserDataCacheRoot currently hardcodes the local system SID, which
+ // will need to be changed for user-level MSI installs to happen as
+ // tracked by http://crbug.com/111058. Leave a DCHECK here to warn those
+ // who next venture here to use a correct SID in kMSIUserDataCacheRoot.
+ DCHECK(installer_state.system_install());
+
+ base::string16 msi_user_data_product_id(
+ ConvertGUIDToMSIUserDataFormat(msi_product_id));
+ if (!msi_user_data_product_id.empty()) {
+ base::string16 msi_cache_reg_path(base::StringPrintf(
+ kMSIUserDataCacheRoot, msi_user_data_product_id.c_str()));
+
+ install_list->AddSetRegValueWorkItem(
+ reg_root, msi_cache_reg_path, KEY_WOW64_64KEY,
+ installer::kUninstallDisplayVersionField,
+ base::ASCIIToWide(new_version.GetString()), true)->
+ set_ignore_failure(true);
+ } else {
+ VLOG(1) << "Failed to convert MSI user data id.";
grt (UTC plus 2) 2014/11/27 19:42:04 suggestion: LOG(DFATAL) and include msi_product_id
robertshield 2014/11/27 22:25:57 I had picked VLOG since we always run the installe
grt (UTC plus 2) 2014/12/01 16:19:30 This new code runs on all updates following an MSI
+ }
+ } else {
+ VLOG(1) << "Failed to extract MSI product id.";
+ }
}
}
}
« 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