OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "remoting/host/plugin/daemon_installer_win.h" |
| 6 |
| 7 #include <windows.h> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/message_loop.h" |
| 11 #include "base/process_util.h" |
| 12 #include "base/string16.h" |
| 13 #include "base/stringize_macros.h" |
| 14 #include "base/stringprintf.h" |
| 15 #include "base/time.h" |
| 16 #include "base/timer.h" |
| 17 #include "base/win/object_watcher.h" |
| 18 #include "base/win/registry.h" |
| 19 #include "base/win/scoped_bstr.h" |
| 20 #include "base/win/scoped_comptr.h" |
| 21 #include "base/win/scoped_handle.h" |
| 22 |
| 23 namespace omaha { |
| 24 #include "google_update/google_update_idl.h" |
| 25 } // namespace omaha |
| 26 |
| 27 using base::win::ScopedBstr; |
| 28 using base::win::ScopedComPtr; |
| 29 |
| 30 namespace { |
| 31 |
| 32 // The COM elevation moniker for Omaha. |
| 33 const char16 kOmahaElevationMoniker[] = |
| 34 TO_L_STRING("Elevation:Administrator!new:GoogleUpdate.Update3WebMachine"); |
| 35 |
| 36 // The registry key where the configuration of Omaha is stored. |
| 37 const char16 kOmahaUpdateKeyName[] = TO_L_STRING("Software\\Google\\Update"); |
| 38 |
| 39 // The name of the value where the full path to GoogleUpdate.exe is stored. |
| 40 const char16 kOmahaPathValueName[] = TO_L_STRING("path"); |
| 41 |
| 42 // The command line format string for GoogleUpdate.exe |
| 43 const char16 kGoogleUpdateCommandLineFormat[] = |
| 44 TO_L_STRING("\"%ls\" /install \"bundlename=Chromoting%%20Host&appguid=%ls&") |
| 45 TO_L_STRING("appname=Chromoting%%20Host&needsadmin=True&lang=%ls\""); |
| 46 |
| 47 // The Omaha Appid of the host. |
| 48 const char16 kOmahaAppid[] = |
| 49 TO_L_STRING("{b210701e-ffc4-49e3-932b-370728c72662}"); |
| 50 |
| 51 // TODO(alexeypa): Get the desired laungage from the web app. |
| 52 const char16 kOmahaLanguage[] = TO_L_STRING("en"); |
| 53 |
| 54 // An empty string for optional parameters. |
| 55 const char16 kOmahaEmpty[] = TO_L_STRING(""); |
| 56 |
| 57 // The installation status polling interval. |
| 58 const int kOmahaPollIntervalMs = 500; |
| 59 |
| 60 } // namespace |
| 61 |
| 62 namespace remoting { |
| 63 |
| 64 // This class implements on-demand installation of the Chromoting Host via |
| 65 // per-machine Omaha instance. |
| 66 class DaemonComInstallerWin : public DaemonInstallerWin { |
| 67 public: |
| 68 DaemonComInstallerWin(const ScopedComPtr<omaha::IGoogleUpdate3Web>& update3, |
| 69 const CompletionCallback& done); |
| 70 |
| 71 // DaemonInstallerWin implementation. |
| 72 virtual void Install() OVERRIDE; |
| 73 |
| 74 private: |
| 75 // Polls the installation status performing state-specific actions (such as |
| 76 // starting installation once download has finished). |
| 77 void PollInstallationStatus(); |
| 78 |
| 79 // Omaha interfaces. |
| 80 ScopedComPtr<omaha::IAppWeb> app_; |
| 81 ScopedComPtr<omaha::IAppBundleWeb> bundle_; |
| 82 ScopedComPtr<omaha::IGoogleUpdate3Web> update3_; |
| 83 |
| 84 base::Timer polling_timer_; |
| 85 }; |
| 86 |
| 87 // This class implements on-demand installation of the Chromoting Host by |
| 88 // launching a per-user instance of Omaha and requesting elevation. |
| 89 class DaemonCommandLineInstallerWin |
| 90 : public DaemonInstallerWin, |
| 91 public base::win::ObjectWatcher::Delegate { |
| 92 public: |
| 93 DaemonCommandLineInstallerWin(const CompletionCallback& done); |
| 94 ~DaemonCommandLineInstallerWin(); |
| 95 |
| 96 // DaemonInstallerWin implementation. |
| 97 virtual void Install() OVERRIDE; |
| 98 |
| 99 // base::win::ObjectWatcher::Delegate implementation. |
| 100 virtual void OnObjectSignaled(HANDLE object) OVERRIDE; |
| 101 |
| 102 private: |
| 103 // Handle of the launched process. |
| 104 base::win::ScopedHandle process_; |
| 105 |
| 106 // Used to determine when the launched process terminates. |
| 107 base::win::ObjectWatcher process_watcher_; |
| 108 }; |
| 109 |
| 110 DaemonComInstallerWin::DaemonComInstallerWin( |
| 111 const ScopedComPtr<omaha::IGoogleUpdate3Web>& update3, |
| 112 const CompletionCallback& done) |
| 113 : DaemonInstallerWin(done), |
| 114 update3_(update3), |
| 115 ALLOW_THIS_IN_INITIALIZER_LIST( |
| 116 polling_timer_( |
| 117 FROM_HERE, |
| 118 base::TimeDelta::FromMilliseconds(kOmahaPollIntervalMs), |
| 119 base::Bind(&DaemonComInstallerWin::PollInstallationStatus, |
| 120 base::Unretained(this)), |
| 121 false)) { |
| 122 } |
| 123 |
| 124 void DaemonComInstallerWin::Install() { |
| 125 // Create an app bundle. |
| 126 ScopedComPtr<IDispatch> dispatch; |
| 127 HRESULT hr = update3_->createAppBundleWeb(dispatch.Receive()); |
| 128 if (FAILED(hr)) { |
| 129 Done(hr); |
| 130 return; |
| 131 } |
| 132 |
| 133 hr = dispatch.QueryInterface(omaha::IID_IAppBundleWeb, bundle_.ReceiveVoid()); |
| 134 if (FAILED(hr)) { |
| 135 Done(hr); |
| 136 return; |
| 137 } |
| 138 |
| 139 hr = bundle_->initialize(); |
| 140 if (FAILED(hr)) { |
| 141 Done(hr); |
| 142 return; |
| 143 } |
| 144 |
| 145 // Add Chromoting Host to the bundle. |
| 146 ScopedBstr appid(kOmahaAppid); |
| 147 ScopedBstr empty(kOmahaEmpty); |
| 148 ScopedBstr language(kOmahaLanguage); |
| 149 hr = bundle_->createApp(appid, empty, language, empty); |
| 150 if (FAILED(hr)) { |
| 151 Done(hr); |
| 152 return; |
| 153 } |
| 154 |
| 155 hr = bundle_->checkForUpdate(); |
| 156 if (FAILED(hr)) { |
| 157 Done(hr); |
| 158 return; |
| 159 } |
| 160 |
| 161 dispatch.Release(); |
| 162 hr = bundle_->get_appWeb(0, dispatch.Receive()); |
| 163 if (FAILED(hr)) { |
| 164 Done(hr); |
| 165 return; |
| 166 } |
| 167 |
| 168 hr = dispatch.QueryInterface(omaha::IID_IAppWeb, |
| 169 app_.ReceiveVoid()); |
| 170 if (FAILED(hr)) { |
| 171 Done(hr); |
| 172 return; |
| 173 } |
| 174 |
| 175 // Now poll for the installation status. |
| 176 PollInstallationStatus(); |
| 177 } |
| 178 |
| 179 void DaemonComInstallerWin::PollInstallationStatus() { |
| 180 // Get the current application installation state. |
| 181 // N.B. The object underlying the ICurrentState interface has static data that |
| 182 // does not get updated as the server state changes. To get the most "current" |
| 183 // state, the currentState property needs to be queried again. |
| 184 ScopedComPtr<IDispatch> dispatch; |
| 185 HRESULT hr = app_->get_currentState(dispatch.Receive()); |
| 186 if (FAILED(hr)) { |
| 187 Done(hr); |
| 188 return; |
| 189 } |
| 190 |
| 191 ScopedComPtr<omaha::ICurrentState> current_state; |
| 192 hr = dispatch.QueryInterface(omaha::IID_ICurrentState, |
| 193 current_state.ReceiveVoid()); |
| 194 if (FAILED(hr)) { |
| 195 Done(hr); |
| 196 return; |
| 197 } |
| 198 |
| 199 LONG state; |
| 200 hr = current_state->get_stateValue(&state); |
| 201 if (FAILED(hr)) { |
| 202 Done(hr); |
| 203 return; |
| 204 } |
| 205 |
| 206 // Perform state-specific actions. |
| 207 switch (state) { |
| 208 case omaha::STATE_INIT: |
| 209 case omaha::STATE_WAITING_TO_CHECK_FOR_UPDATE: |
| 210 case omaha::STATE_CHECKING_FOR_UPDATE: |
| 211 case omaha::STATE_WAITING_TO_DOWNLOAD: |
| 212 case omaha::STATE_RETRYING_DOWNLOAD: |
| 213 case omaha::STATE_DOWNLOADING: |
| 214 case omaha::STATE_WAITING_TO_INSTALL: |
| 215 case omaha::STATE_INSTALLING: |
| 216 case omaha::STATE_PAUSED: |
| 217 break; |
| 218 |
| 219 case omaha::STATE_UPDATE_AVAILABLE: |
| 220 hr = bundle_->download(); |
| 221 if (FAILED(hr)) { |
| 222 Done(hr); |
| 223 return; |
| 224 } |
| 225 break; |
| 226 |
| 227 case omaha::STATE_DOWNLOAD_COMPLETE: |
| 228 case omaha::STATE_EXTRACTING: |
| 229 case omaha::STATE_APPLYING_DIFFERENTIAL_PATCH: |
| 230 case omaha::STATE_READY_TO_INSTALL: |
| 231 hr = bundle_->install(); |
| 232 if (FAILED(hr)) { |
| 233 Done(hr); |
| 234 return; |
| 235 } |
| 236 break; |
| 237 |
| 238 case omaha::STATE_INSTALL_COMPLETE: |
| 239 case omaha::STATE_NO_UPDATE: |
| 240 // Installation complete or not required. Report success. |
| 241 Done(S_OK); |
| 242 return; |
| 243 |
| 244 case omaha::STATE_ERROR: { |
| 245 HRESULT error_code; |
| 246 hr = current_state->get_errorCode(&error_code); |
| 247 if (FAILED(hr)) { |
| 248 error_code = hr; |
| 249 } |
| 250 Done(error_code); |
| 251 return; |
| 252 } |
| 253 |
| 254 default: |
| 255 LOG(ERROR) << "Unknown bundle state: " << state << "."; |
| 256 Done(E_FAIL); |
| 257 return; |
| 258 } |
| 259 |
| 260 // Keep polling. |
| 261 polling_timer_.Reset(); |
| 262 } |
| 263 |
| 264 DaemonCommandLineInstallerWin::DaemonCommandLineInstallerWin( |
| 265 const CompletionCallback& done) : DaemonInstallerWin(done) { |
| 266 } |
| 267 |
| 268 DaemonCommandLineInstallerWin::~DaemonCommandLineInstallerWin() { |
| 269 process_watcher_.StopWatching(); |
| 270 } |
| 271 |
| 272 void DaemonCommandLineInstallerWin::Install() { |
| 273 // Get the full path to GoogleUpdate.exe from the registry. |
| 274 base::win::RegKey update_key; |
| 275 LONG result = update_key.Open(HKEY_CURRENT_USER, |
| 276 kOmahaUpdateKeyName, |
| 277 KEY_READ); |
| 278 if (result != ERROR_SUCCESS) { |
| 279 Done(HRESULT_FROM_WIN32(result)); |
| 280 return; |
| 281 } |
| 282 |
| 283 string16 google_update; |
| 284 result = update_key.ReadValue(kOmahaPathValueName, |
| 285 &google_update); |
| 286 if (result != ERROR_SUCCESS) { |
| 287 Done(HRESULT_FROM_WIN32(result)); |
| 288 return; |
| 289 } |
| 290 |
| 291 // Launch the updater process and wait for its termination. |
| 292 string16 command_line = |
| 293 StringPrintf(kGoogleUpdateCommandLineFormat, |
| 294 google_update.c_str(), |
| 295 kOmahaAppid, |
| 296 kOmahaLanguage); |
| 297 |
| 298 base::LaunchOptions options; |
| 299 if (!base::LaunchProcess(command_line, options, process_.Receive())) { |
| 300 result = GetLastError(); |
| 301 Done(HRESULT_FROM_WIN32(result)); |
| 302 return; |
| 303 } |
| 304 |
| 305 if (!process_watcher_.StartWatching(process_.Get(), this)) { |
| 306 result = GetLastError(); |
| 307 Done(HRESULT_FROM_WIN32(result)); |
| 308 return; |
| 309 } |
| 310 } |
| 311 |
| 312 void DaemonCommandLineInstallerWin::OnObjectSignaled(HANDLE object) { |
| 313 // Check if the updater process returned success. |
| 314 DWORD exit_code; |
| 315 if (GetExitCodeProcess(process_.Get(), &exit_code) && exit_code == 0) { |
| 316 Done(S_OK); |
| 317 } else { |
| 318 Done(E_FAIL); |
| 319 } |
| 320 } |
| 321 |
| 322 DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback& done) |
| 323 : done_(done) { |
| 324 } |
| 325 |
| 326 DaemonInstallerWin::~DaemonInstallerWin() { |
| 327 } |
| 328 |
| 329 void DaemonInstallerWin::Done(HRESULT result) { |
| 330 done_.Run(result); |
| 331 } |
| 332 |
| 333 // static |
| 334 scoped_ptr<DaemonInstallerWin> DaemonInstallerWin::Create( |
| 335 CompletionCallback done) { |
| 336 // Check if the machine instance of Omaha is available. |
| 337 BIND_OPTS3 bind_options; |
| 338 memset(&bind_options, 0, sizeof(bind_options)); |
| 339 bind_options.cbStruct = sizeof(bind_options); |
| 340 bind_options.hwnd = NULL; |
| 341 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; |
| 342 |
| 343 ScopedComPtr<omaha::IGoogleUpdate3Web> update3; |
| 344 HRESULT result = ::CoGetObject( |
| 345 kOmahaElevationMoniker, |
| 346 &bind_options, |
| 347 omaha::IID_IGoogleUpdate3Web, |
| 348 update3.ReceiveVoid()); |
| 349 if (SUCCEEDED(result)) { |
| 350 // The machine instance of Omaha is available and we successfully passed |
| 351 // the UAC prompt. |
| 352 return scoped_ptr<DaemonInstallerWin>( |
| 353 new DaemonComInstallerWin(update3, done)); |
| 354 } else if (result == CO_E_CLASSSTRING) { |
| 355 // The machine instance of Omaha is not available so we will have to run |
| 356 // GoogleUpdate.exe manually passing "needsadmin=True". This will cause |
| 357 // Omaha to install the machine instance first and then install Chromoting |
| 358 // Host. |
| 359 return scoped_ptr<DaemonInstallerWin>( |
| 360 new DaemonCommandLineInstallerWin(done)); |
| 361 } else { |
| 362 // The user declined the UAC prompt or some other error occured. |
| 363 done.Run(result); |
| 364 return scoped_ptr<DaemonInstallerWin>(); |
| 365 } |
| 366 } |
| 367 |
| 368 } // namespace remoting |
OLD | NEW |