| OLD | NEW |
| (Empty) |
| 1 // Copyright 2011 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 // | |
| 16 // Author: grt | |
| 17 // | |
| 18 // A Windows Installer custom action that displays and logs an app installer's | |
| 19 // InstallResultUIString via MsiProcessMessage. The app's guid must be provided | |
| 20 // by the MSI wrapper by way of the CustomActionData property. | |
| 21 | |
| 22 #include <windows.h> | |
| 23 #include <msi.h> | |
| 24 #include <msiquery.h> | |
| 25 #include <objbase.h> | |
| 26 #include <stdlib.h> | |
| 27 | |
| 28 #include <algorithm> | |
| 29 #include <limits> | |
| 30 #include <string> | |
| 31 | |
| 32 #define SOFTWARE_GOOGLE_UPDATE L"Software\\Google\\Update" | |
| 33 #define SOFTWARE_GOOGLE_UPDATE_CLIENTSTATE \ | |
| 34 SOFTWARE_GOOGLE_UPDATE L"\\ClientState" | |
| 35 | |
| 36 namespace { | |
| 37 | |
| 38 const int kGuidStringLength = 38; // 128/4 + 4 dashes + 2 braces. | |
| 39 const DWORD kInstallerResultFailedCustomError = 1; | |
| 40 const wchar_t kPropertyCustomActionData[] = L"CustomActionData"; | |
| 41 const wchar_t kRegKeyClientState[] = SOFTWARE_GOOGLE_UPDATE_CLIENTSTATE; | |
| 42 const wchar_t kRegKeyGoogleUpdate[] = SOFTWARE_GOOGLE_UPDATE; | |
| 43 const wchar_t kRegValueLastInstallerResult[] = L"LastInstallerResult"; | |
| 44 const wchar_t kRegValueLastInstallerResultUIString[] = | |
| 45 L"LastInstallerResultUIString"; | |
| 46 | |
| 47 // Gets the value of the property named |property_name|, putting it in | |
| 48 // |property_value|. The anticipated length of the value can be provided in | |
| 49 // |expected_length| to reduce overhead. Returns true if a (possibly empty) | |
| 50 // value is read, or false on error. | |
| 51 bool GetProperty(MSIHANDLE install, | |
| 52 const wchar_t* property_name, | |
| 53 int expected_length, | |
| 54 std::wstring* property_value) { | |
| 55 DWORD value_len = static_cast<DWORD>(std::max(0, expected_length)); | |
| 56 UINT result = ERROR_SUCCESS; | |
| 57 do { | |
| 58 // Make space to hold the string terminator. | |
| 59 property_value->resize(++value_len); | |
| 60 result = MsiGetProperty(install, property_name, &(*property_value)[0], | |
| 61 &value_len); | |
| 62 } while (result == ERROR_MORE_DATA && | |
| 63 value_len <= std::numeric_limits<DWORD>::max() - 1); | |
| 64 | |
| 65 if (result == ERROR_SUCCESS) | |
| 66 property_value->resize(value_len); | |
| 67 else | |
| 68 property_value->clear(); | |
| 69 | |
| 70 return result == ERROR_SUCCESS; | |
| 71 } | |
| 72 | |
| 73 // The type of a function that returns true if |c| is a valid char in a GUID. | |
| 74 typedef bool (*IsGuidCharFn)(wchar_t c); | |
| 75 | |
| 76 // A function template that returns true if |c| equals some constant |C|. | |
| 77 template<wchar_t C> | |
| 78 bool IsChar(wchar_t c) { | |
| 79 return c == C; | |
| 80 } | |
| 81 | |
| 82 // Returns true if |c| is a valid hex character. | |
| 83 bool IsHexDigit(wchar_t c) { | |
| 84 return ((c >= L'0' && c <= '9') || | |
| 85 (c >= L'a' && c <= 'f') || | |
| 86 (c >= L'A' && c <= 'F')); | |
| 87 } | |
| 88 | |
| 89 // Returns true if |guid| is a well-formed GUID. | |
| 90 bool IsGuid(const std::wstring& guid) { | |
| 91 static const IsGuidCharFn kGuidCharValidators[] = { | |
| 92 IsChar<L'{'>, | |
| 93 IsHexDigit, | |
| 94 IsHexDigit, | |
| 95 IsHexDigit, | |
| 96 IsHexDigit, | |
| 97 IsHexDigit, | |
| 98 IsHexDigit, | |
| 99 IsHexDigit, | |
| 100 IsHexDigit, | |
| 101 IsChar<L'-'>, | |
| 102 IsHexDigit, | |
| 103 IsHexDigit, | |
| 104 IsHexDigit, | |
| 105 IsHexDigit, | |
| 106 IsChar<L'-'>, | |
| 107 IsHexDigit, | |
| 108 IsHexDigit, | |
| 109 IsHexDigit, | |
| 110 IsHexDigit, | |
| 111 IsChar<L'-'>, | |
| 112 IsHexDigit, | |
| 113 IsHexDigit, | |
| 114 IsHexDigit, | |
| 115 IsHexDigit, | |
| 116 IsChar<L'-'>, | |
| 117 IsHexDigit, | |
| 118 IsHexDigit, | |
| 119 IsHexDigit, | |
| 120 IsHexDigit, | |
| 121 IsHexDigit, | |
| 122 IsHexDigit, | |
| 123 IsHexDigit, | |
| 124 IsHexDigit, | |
| 125 IsHexDigit, | |
| 126 IsHexDigit, | |
| 127 IsHexDigit, | |
| 128 IsHexDigit, | |
| 129 IsChar<L'}'>, | |
| 130 }; | |
| 131 | |
| 132 if (guid.size() != _countof(kGuidCharValidators)) | |
| 133 return false; | |
| 134 | |
| 135 for (size_t i = 0, end = guid.size(); i < end; ++i) { | |
| 136 if (!kGuidCharValidators[i](guid[i])) | |
| 137 return false; | |
| 138 } | |
| 139 | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 // Gets the app guid for the product being installed. Returns false if a value | |
| 144 // that doesn't look like a GUID is read. | |
| 145 bool GetProductGuid(MSIHANDLE install, std::wstring* guid) { | |
| 146 return GetProperty(install, kPropertyCustomActionData, kGuidStringLength, | |
| 147 guid) && | |
| 148 IsGuid(*guid); | |
| 149 } | |
| 150 | |
| 151 // Populates |key_name| with the full name of |app_guid|'s ClientState registry | |
| 152 // key. | |
| 153 void GetAppClientStateKey(const std::wstring& app_guid, | |
| 154 std::wstring* key_name) { | |
| 155 const size_t base_len = _countof(kRegKeyClientState) - 1; | |
| 156 key_name->reserve(base_len + 1 + app_guid.size()); | |
| 157 key_name->assign(kRegKeyClientState, base_len); | |
| 158 key_name->append(1, L'\\'); | |
| 159 key_name->append(app_guid); | |
| 160 } | |
| 161 | |
| 162 // Reads the string value named |value_name| in registry key |key| into | |
| 163 // |result_string|. Returns ERROR_NOT_SUPPORTED if the value exists but is not | |
| 164 // of type REG_SZ. | |
| 165 LONG ReadRegistryStringValue(HKEY key, const wchar_t* value_name, | |
| 166 std::wstring* result_string) { | |
| 167 LONG result; | |
| 168 DWORD type; | |
| 169 DWORD byte_length; | |
| 170 | |
| 171 // Use all of the provided buffer. | |
| 172 result_string->resize(result_string->capacity()); | |
| 173 | |
| 174 // Figure out how much we can hold there, being careful about overflow. | |
| 175 byte_length = static_cast<DWORD>(std::min( | |
| 176 result_string->size(), | |
| 177 static_cast<size_t>( | |
| 178 std::numeric_limits<DWORD>::max() / sizeof(wchar_t)))); | |
| 179 byte_length *= sizeof(wchar_t); | |
| 180 | |
| 181 do { | |
| 182 // Read into the provided buffer. | |
| 183 BYTE* buffer = reinterpret_cast<BYTE*>( | |
| 184 result_string->empty() ? NULL : &(*result_string)[0]); | |
| 185 result = RegQueryValueEx(key, value_name, NULL, &type, buffer, | |
| 186 &byte_length); | |
| 187 if (result == ERROR_SUCCESS) { | |
| 188 const size_t chars_read = byte_length / sizeof(wchar_t); | |
| 189 if (type != REG_SZ) { | |
| 190 // The value wasn't a string. | |
| 191 result = ERROR_NOT_SUPPORTED; | |
| 192 } else if (byte_length == 0) { | |
| 193 // The string was empty. | |
| 194 result_string->clear(); | |
| 195 } else if ((*result_string)[chars_read - 1] != L'\0') { | |
| 196 // The string was not terminated. Let std::basic_string do so. | |
| 197 result_string->resize(chars_read); | |
| 198 } else { | |
| 199 // The string was terminated. Trim off the terminator. | |
| 200 result_string->resize(chars_read - 1); | |
| 201 } | |
| 202 } else if (result == ERROR_MORE_DATA) { | |
| 203 // Increase the buffer and try again. | |
| 204 result_string->resize(byte_length / sizeof(wchar_t)); | |
| 205 } | |
| 206 } while (result == ERROR_MORE_DATA); | |
| 207 | |
| 208 return result; | |
| 209 } | |
| 210 | |
| 211 // Reads the DWORD value named |value_name| in registry key |key| into |value|. | |
| 212 // Returns ERROR_NOT_SUPPORTED if the value exists but is not of type REG_DWORD. | |
| 213 LONG ReadRegistryDwordValue(HKEY key, const wchar_t* value_name, DWORD* value) { | |
| 214 LONG result; | |
| 215 DWORD type; | |
| 216 DWORD byte_length = sizeof(*value); | |
| 217 | |
| 218 result = RegQueryValueEx(key, value_name, NULL, &type, | |
| 219 reinterpret_cast<BYTE*>(value), &byte_length); | |
| 220 if (result == ERROR_SUCCESS && type != REG_DWORD) { | |
| 221 // The value wasn't a DWORD. | |
| 222 result = ERROR_NOT_SUPPORTED; | |
| 223 } | |
| 224 | |
| 225 return result; | |
| 226 } | |
| 227 | |
| 228 // Checks to see if the app installer failed with a custom error and provided a | |
| 229 // UI string. If so, returns true and populates |result_string| with the UI | |
| 230 // string. Otherwise, returns false. | |
| 231 bool GetLastInstallerResultUIString(const std::wstring& app_guid, | |
| 232 std::wstring* result_string) { | |
| 233 std::wstring client_state_name; | |
| 234 HKEY key = NULL; | |
| 235 | |
| 236 GetAppClientStateKey(app_guid, &client_state_name); | |
| 237 | |
| 238 // First try looking in the app's ClientState key. Failing that, fall back to | |
| 239 // Google Update's own SOFTWARE\Google\Update key, into which GoogleUpdate.exe | |
| 240 // copies the app's value (see AppManager::ClearInstallerResultApiValues). | |
| 241 LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, client_state_name.c_str(), | |
| 242 NULL, KEY_QUERY_VALUE, &key); | |
| 243 if (result != ERROR_SUCCESS) { | |
| 244 result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, kRegKeyGoogleUpdate, NULL, | |
| 245 KEY_QUERY_VALUE, &key); | |
| 246 } | |
| 247 | |
| 248 if (result == ERROR_SUCCESS) { | |
| 249 // Is LastInstallerResult == INSTALLER_RESULT_FAILED_CUSTOM_ERROR? | |
| 250 DWORD last_installer_error = 0; | |
| 251 result = ReadRegistryDwordValue(key, kRegValueLastInstallerResult, | |
| 252 &last_installer_error); | |
| 253 if (result == ERROR_SUCCESS && | |
| 254 last_installer_error == kInstallerResultFailedCustomError) { | |
| 255 result = ReadRegistryStringValue( | |
| 256 key, kRegValueLastInstallerResultUIString, result_string); | |
| 257 } | |
| 258 | |
| 259 RegCloseKey(key); | |
| 260 } | |
| 261 | |
| 262 return result == ERROR_SUCCESS; | |
| 263 } | |
| 264 | |
| 265 } // namespace | |
| 266 | |
| 267 // A DLL custom action entrypoint that performs the work described at the top | |
| 268 // of this file. | |
| 269 extern "C" UINT __stdcall ShowInstallerResultUIString(MSIHANDLE install) { | |
| 270 std::wstring app_guid; | |
| 271 std::wstring result_string; | |
| 272 | |
| 273 if (GetProductGuid(install, &app_guid) && | |
| 274 GetLastInstallerResultUIString(app_guid, &result_string) && | |
| 275 !result_string.empty()) { | |
| 276 PMSIHANDLE record = MsiCreateRecord(0); | |
| 277 if (record != 0UL) { | |
| 278 UINT result = MsiRecordSetString(record, 0, result_string.c_str()); | |
| 279 if (result == ERROR_SUCCESS) | |
| 280 MsiProcessMessage(install, INSTALLMESSAGE_ERROR, record); | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 return ERROR_SUCCESS; | |
| 285 } | |
| OLD | NEW |