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.
5 #include "remoting/host/setup/daemon_installer_win.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/process/launch.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "base/timer/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 #include "base/win/scoped_variant.h"
23 #include "base/win/windows_version.h"
24 #include "google_update/google_update_idl.h"
25 #include "remoting/base/dispatch_win.h"
26 #include "remoting/host/win/omaha.h"
28 using base::win::ScopedBstr
;
29 using base::win::ScopedComPtr
;
30 using base::win::ScopedVariant
;
34 // ProgID of the per-machine Omaha COM server.
35 const wchar_t kGoogleUpdate
[] = L
"GoogleUpdate.Update3WebMachine";
37 // The COM elevation moniker for the per-machine Omaha COM server.
38 const wchar_t kGoogleUpdateElevationMoniker
[] =
39 L
"Elevation:Administrator!new:GoogleUpdate.Update3WebMachine";
41 // The registry key where the configuration of Omaha is stored.
42 const wchar_t kOmahaUpdateKeyName
[] = L
"Software\\Google\\Update";
44 // The name of the value where the full path to GoogleUpdate.exe is stored.
45 const wchar_t kOmahaPathValueName
[] = L
"path";
47 // The command line format string for GoogleUpdate.exe
48 const wchar_t kGoogleUpdateCommandLineFormat
[] =
49 L
"\"%ls\" /install \"bundlename=Chromoting%%20Host&appguid=%ls&"
50 L
"appname=Chromoting%%20Host&needsadmin=True&lang=%ls\"";
52 // TODO(alexeypa): Get the desired laungage from the web app.
53 const wchar_t kOmahaLanguage
[] = L
"en";
55 // An empty string for optional parameters.
56 const wchar_t kOmahaEmpty
[] = L
"";
58 // The installation status polling interval.
59 const int kOmahaPollIntervalMs
= 500;
65 // This class implements on-demand installation of the Chromoting Host via
66 // per-machine Omaha instance.
67 class DaemonComInstallerWin
: public DaemonInstallerWin
{
69 DaemonComInstallerWin(const ScopedComPtr
<IDispatch
>& update3
,
70 const CompletionCallback
& done
);
72 // DaemonInstallerWin implementation.
73 virtual void Install() OVERRIDE
;
76 // Polls the installation status performing state-specific actions (such as
77 // starting installation once download has finished).
78 void PollInstallationStatus();
82 ScopedVariant bundle_
;
83 ScopedComPtr
<IDispatch
> update3_
;
85 base::Timer polling_timer_
;
88 // This class implements on-demand installation of the Chromoting Host by
89 // launching a per-user instance of Omaha and requesting elevation.
90 class DaemonCommandLineInstallerWin
91 : public DaemonInstallerWin
,
92 public base::win::ObjectWatcher::Delegate
{
94 DaemonCommandLineInstallerWin(const CompletionCallback
& done
);
95 ~DaemonCommandLineInstallerWin();
97 // DaemonInstallerWin implementation.
98 virtual void Install() OVERRIDE
;
100 // base::win::ObjectWatcher::Delegate implementation.
101 virtual void OnObjectSignaled(HANDLE object
) OVERRIDE
;
104 // Handle of the launched process.
105 base::win::ScopedHandle process_
;
107 // Used to determine when the launched process terminates.
108 base::win::ObjectWatcher process_watcher_
;
111 DaemonComInstallerWin::DaemonComInstallerWin(
112 const ScopedComPtr
<IDispatch
>& update3
,
113 const CompletionCallback
& done
)
114 : DaemonInstallerWin(done
),
118 base::TimeDelta::FromMilliseconds(kOmahaPollIntervalMs
),
119 base::Bind(&DaemonComInstallerWin::PollInstallationStatus
,
120 base::Unretained(this)),
124 void DaemonComInstallerWin::Install() {
125 // Create an app bundle.
126 HRESULT hr
= dispatch::Invoke(update3_
.get(), L
"createAppBundleWeb",
127 DISPATCH_METHOD
, bundle_
.Receive());
132 if (bundle_
.type() != VT_DISPATCH
) {
133 Done(DISP_E_TYPEMISMATCH
);
137 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"initialize", DISPATCH_METHOD
,
144 // Add Chromoting Host to the bundle.
145 ScopedVariant
appid(kHostOmahaAppid
);
146 ScopedVariant
empty(kOmahaEmpty
);
147 ScopedVariant
language(kOmahaLanguage
);
148 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"createApp", DISPATCH_METHOD
,
149 appid
, empty
, language
, empty
, NULL
);
155 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"checkForUpdate",
156 DISPATCH_METHOD
, NULL
);
162 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"appWeb",
163 DISPATCH_PROPERTYGET
, ScopedVariant(0), app_
.Receive());
168 if (app_
.type() != VT_DISPATCH
) {
169 Done(DISP_E_TYPEMISMATCH
);
173 // Now poll for the installation status.
174 PollInstallationStatus();
177 void DaemonComInstallerWin::PollInstallationStatus() {
178 // Get the current application installation state.
179 // N.B. The object underlying the ICurrentState interface has static data that
180 // does not get updated as the server state changes. To get the most "current"
181 // state, the currentState property needs to be queried again.
182 ScopedVariant current_state
;
183 HRESULT hr
= dispatch::Invoke(V_DISPATCH(&app_
), L
"currentState",
184 DISPATCH_PROPERTYGET
, current_state
.Receive());
189 if (current_state
.type() != VT_DISPATCH
) {
190 Done(DISP_E_TYPEMISMATCH
);
195 hr
= dispatch::Invoke(V_DISPATCH(¤t_state
), L
"stateValue",
196 DISPATCH_PROPERTYGET
, state
.Receive());
197 if (state
.type() != VT_I4
) {
198 Done(DISP_E_TYPEMISMATCH
);
202 // Perform state-specific actions.
203 switch (V_I4(&state
)) {
205 case STATE_WAITING_TO_CHECK_FOR_UPDATE
:
206 case STATE_CHECKING_FOR_UPDATE
:
207 case STATE_WAITING_TO_DOWNLOAD
:
208 case STATE_RETRYING_DOWNLOAD
:
209 case STATE_DOWNLOADING
:
210 case STATE_WAITING_TO_INSTALL
:
211 case STATE_INSTALLING
:
215 case STATE_UPDATE_AVAILABLE
:
216 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"download",
217 DISPATCH_METHOD
, NULL
);
224 case STATE_DOWNLOAD_COMPLETE
:
225 case STATE_EXTRACTING
:
226 case STATE_APPLYING_DIFFERENTIAL_PATCH
:
227 case STATE_READY_TO_INSTALL
:
228 hr
= dispatch::Invoke(V_DISPATCH(&bundle_
), L
"install",
229 DISPATCH_METHOD
, NULL
);
236 case STATE_INSTALL_COMPLETE
:
237 case STATE_NO_UPDATE
:
238 // Installation complete or not required. Report success.
243 ScopedVariant error_code
;
244 hr
= dispatch::Invoke(V_DISPATCH(¤t_state
), L
"errorCode",
245 DISPATCH_PROPERTYGET
, error_code
.Receive());
250 if (error_code
.type() != VT_UI4
) {
251 Done(DISP_E_TYPEMISMATCH
);
254 Done(V_UI4(&error_code
));
259 LOG(ERROR
) << "Unknown bundle state: " << V_I4(&state
) << ".";
265 polling_timer_
.Reset();
268 DaemonCommandLineInstallerWin::DaemonCommandLineInstallerWin(
269 const CompletionCallback
& done
) : DaemonInstallerWin(done
) {
272 DaemonCommandLineInstallerWin::~DaemonCommandLineInstallerWin() {
273 process_watcher_
.StopWatching();
276 void DaemonCommandLineInstallerWin::Install() {
277 // Get the full path to GoogleUpdate.exe from the registry.
278 base::win::RegKey update_key
;
279 LONG result
= update_key
.Open(HKEY_CURRENT_USER
,
282 if (result
!= ERROR_SUCCESS
) {
283 Done(HRESULT_FROM_WIN32(result
));
287 // presubmit: allow wstring
288 std::wstring google_update
;
289 result
= update_key
.ReadValue(kOmahaPathValueName
, &google_update
);
290 if (result
!= ERROR_SUCCESS
) {
291 Done(HRESULT_FROM_WIN32(result
));
295 // Launch the updater process and wait for its termination.
296 string16 command_line
= WideToUTF16(
297 base::StringPrintf(kGoogleUpdateCommandLineFormat
,
298 google_update
.c_str(),
302 base::LaunchOptions options
;
303 if (!base::LaunchProcess(command_line
, options
, process_
.Receive())) {
304 result
= GetLastError();
305 Done(HRESULT_FROM_WIN32(result
));
309 if (!process_watcher_
.StartWatching(process_
.Get(), this)) {
310 result
= GetLastError();
311 Done(HRESULT_FROM_WIN32(result
));
316 void DaemonCommandLineInstallerWin::OnObjectSignaled(HANDLE object
) {
317 // Check if the updater process returned success.
319 if (GetExitCodeProcess(process_
.Get(), &exit_code
) && exit_code
== 0) {
326 DaemonInstallerWin::DaemonInstallerWin(const CompletionCallback
& done
)
330 DaemonInstallerWin::~DaemonInstallerWin() {
333 void DaemonInstallerWin::Done(HRESULT result
) {
334 CompletionCallback done
= done_
;
340 scoped_ptr
<DaemonInstallerWin
> DaemonInstallerWin::Create(
342 CompletionCallback done
) {
343 HRESULT result
= E_FAIL
;
344 ScopedComPtr
<IDispatch
> update3
;
346 // Check if the machine instance of Omaha is available. The COM elevation is
347 // supported on Vista+, so on XP/W2K3 we assume that we are running under
348 // a privileged user and get ACCESS_DENIED later if we are not.
349 if (base::win::GetVersion() < base::win::VERSION_VISTA
) {
351 result
= CLSIDFromProgID(kGoogleUpdate
, &class_id
);
352 if (SUCCEEDED(result
)) {
353 result
= CoCreateInstance(class_id
,
357 update3
.ReceiveVoid());
360 BIND_OPTS3 bind_options
;
361 memset(&bind_options
, 0, sizeof(bind_options
));
362 bind_options
.cbStruct
= sizeof(bind_options
);
363 bind_options
.hwnd
= GetTopLevelWindow(window_handle
);
364 bind_options
.dwClassContext
= CLSCTX_LOCAL_SERVER
;
365 result
= CoGetObject(kGoogleUpdateElevationMoniker
,
368 update3
.ReceiveVoid());
370 if (SUCCEEDED(result
)) {
371 // The machine instance of Omaha is available and we successfully passed
373 return scoped_ptr
<DaemonInstallerWin
>(
374 new DaemonComInstallerWin(update3
, done
));
375 } else if (result
== CO_E_CLASSSTRING
) {
376 // The machine instance of Omaha is not available so we will have to run
377 // GoogleUpdate.exe manually passing "needsadmin=True". This will cause
378 // Omaha to install the machine instance first and then install Chromoting
380 return scoped_ptr
<DaemonInstallerWin
>(
381 new DaemonCommandLineInstallerWin(done
));
383 // The user declined the UAC prompt or some other error occured.
385 return scoped_ptr
<DaemonInstallerWin
>();
389 HWND
GetTopLevelWindow(HWND window
) {
390 if (window
== NULL
) {
395 LONG style
= GetWindowLong(window
, GWL_STYLE
);
396 if ((style
& WS_OVERLAPPEDWINDOW
) == WS_OVERLAPPEDWINDOW
||
397 (style
& WS_POPUP
) == WS_POPUP
) {
401 HWND parent
= GetAncestor(window
, GA_PARENT
);
402 if (parent
== NULL
) {
410 } // namespace remoting