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 |