1 // Copyright 2019 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 "chrome/updater/win/task_scheduler.h"
6
7 #include <mstask.h>
8 #include <oleauto.h>
9 #include <security.h>
10 #include <taskschd.h>
11 #include <wrl/client.h>
12
13 #include <utility>
14
15 #include "base/command_line.h"
16 #include "base/files/file_path.h"
17 #include "base/logging.h"
18 #include "base/native_library.h"
19 #include "base/notreached.h"
20 #include "base/path_service.h"
21 #include "base/strings/strcat.h"
22 #include "base/strings/string16.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/time/time.h"
25 #include "base/win/scoped_bstr.h"
26 #include "base/win/scoped_co_mem.h"
27 #include "base/win/scoped_handle.h"
28 #include "base/win/scoped_variant.h"
29 #include "base/win/windows_version.h"
30 #include "chrome/updater/updater_version.h"
31 #include "chrome/updater/win/util.h"
32
33 namespace updater {
34
35 namespace {
36
37 // Names of the TaskSchedulerV2 libraries so we can pin them below.
38 const wchar_t kV2Library[] = L"taskschd.dll";
39
40 // Text for times used in the V2 API of the Task Scheduler.
41 const wchar_t kOneHourText[] = L"PT1H";
42 const wchar_t kFiveHoursText[] = L"PT5H";
43 const wchar_t kZeroMinuteText[] = L"PT0M";
44 const wchar_t kFifteenMinutesText[] = L"PT15M";
45 const wchar_t kTwentyFourHoursText[] = L"PT24H";
46
47 // Names of the folders used to group the scheduled tasks in.
48 const wchar_t kTaskCompanyFolder[] = L"\\" COMPANY_SHORTNAME_STRING;
49 const wchar_t kTaskSubfolderName[] =
50 L"\\" COMPANY_SHORTNAME_STRING L"\\" PRODUCT_FULLNAME_STRING;
51
52 // Most of the users with pending logs succeeds within 7 days, so no need to
53 // try for longer than that, especially for those who keep crashing.
54 const int kNumDaysBeforeExpiry = 7;
55 const size_t kNumDeleteTaskRetry = 3;
56 const size_t kDeleteRetryDelayInMs = 100;
57
58 // Return |timestamp| in the following string format YYYY-MM-DDTHH:MM:SS.
GetTimestampString(const base::Time & timestamp)59 base::string16 GetTimestampString(const base::Time& timestamp) {
60 base::Time::Exploded exploded_time;
61 // The Z timezone info at the end of the string means UTC.
62 timestamp.UTCExplode(&exploded_time);
63 return base::StringPrintf(L"%04d-%02d-%02dT%02d:%02d:%02dZ",
64 exploded_time.year, exploded_time.month,
65 exploded_time.day_of_month, exploded_time.hour,
66 exploded_time.minute, exploded_time.second);
67 }
68
LocalSystemTimeToUTCFileTime(const SYSTEMTIME & system_time_local,FILETIME * file_time_utc)69 bool LocalSystemTimeToUTCFileTime(const SYSTEMTIME& system_time_local,
70 FILETIME* file_time_utc) {
71 DCHECK(file_time_utc);
72 SYSTEMTIME system_time_utc = {};
73 if (!::TzSpecificLocalTimeToSystemTime(nullptr, &system_time_local,
74 &system_time_utc) ||
75 !::SystemTimeToFileTime(&system_time_utc, file_time_utc)) {
76 PLOG(ERROR) << "Failed to convert local system time to UTC file time.";
77 return false;
78 }
79 return true;
80 }
81
UTCFileTimeToLocalSystemTime(const FILETIME & file_time_utc,SYSTEMTIME * system_time_local)82 bool UTCFileTimeToLocalSystemTime(const FILETIME& file_time_utc,
83 SYSTEMTIME* system_time_local) {
84 DCHECK(system_time_local);
85 SYSTEMTIME system_time_utc = {};
86 if (!::FileTimeToSystemTime(&file_time_utc, &system_time_utc) ||
87 !::SystemTimeToTzSpecificLocalTime(nullptr, &system_time_utc,
88 system_time_local)) {
89 PLOG(ERROR) << "Failed to convert file time to UTC local system.";
90 return false;
91 }
92 return true;
93 }
94
GetCurrentUser(base::win::ScopedBstr * user_name)95 bool GetCurrentUser(base::win::ScopedBstr* user_name) {
96 DCHECK(user_name);
97 ULONG user_name_size = 256;
98 // Paranoia... ;-)
99 DCHECK_EQ(sizeof(OLECHAR), sizeof(WCHAR));
100 if (!::GetUserNameExW(
101 NameSamCompatible,
102 user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
103 &user_name_size)) {
104 if (::GetLastError() != ERROR_MORE_DATA) {
105 PLOG(ERROR) << "GetUserNameEx failed.";
106 return false;
107 }
108 if (!::GetUserNameExW(
109 NameSamCompatible,
110 user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
111 &user_name_size)) {
112 DCHECK_NE(DWORD{ERROR_MORE_DATA}, ::GetLastError());
113 PLOG(ERROR) << "GetUserNameEx failed.";
114 return false;
115 }
116 }
117 return true;
118 }
119
PinModule(const wchar_t * module_name)120 void PinModule(const wchar_t* module_name) {
121 // Force the DLL to stay loaded until program termination. We have seen
122 // cases where it gets unloaded even though we still have references to
123 // the objects we just CoCreated.
124 base::NativeLibrary module_handle = nullptr;
125 if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, module_name,
126 &module_handle)) {
127 PLOG(ERROR) << "Failed to pin '" << module_name << "'.";
128 }
129 }
130
GetTaskService()131 Microsoft::WRL::ComPtr<ITaskService> GetTaskService() {
132 Microsoft::WRL::ComPtr<ITaskService> task_service;
133 HRESULT hr =
134 ::CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER,
135 IID_PPV_ARGS(&task_service));
136 if (FAILED(hr)) {
137 PLOG(ERROR) << "CreateInstance failed for CLSID_TaskScheduler. " << std::hex
138 << hr;
139 return nullptr;
140 }
141 hr = task_service->Connect(base::win::ScopedVariant::kEmptyVariant,
142 base::win::ScopedVariant::kEmptyVariant,
143 base::win::ScopedVariant::kEmptyVariant,
144 base::win::ScopedVariant::kEmptyVariant);
145 if (FAILED(hr)) {
146 PLOG(ERROR) << "Failed to connect to task service. " << std::hex << hr;
147 return nullptr;
148 }
149
150 PinModule(kV2Library);
151 return task_service;
152 }
153
154 // A task scheduler class uses the V2 API of the task scheduler.
155 class TaskSchedulerV2 final : public TaskScheduler {
156 public:
TaskSchedulerV2()157 TaskSchedulerV2() {
158 task_service_ = GetTaskService();
159 DCHECK(task_service_);
160 task_folder_ = GetUpdaterTaskFolder();
161 DCHECK(task_folder_);
162 }
163 TaskSchedulerV2(const TaskSchedulerV2&) = delete;
164 TaskSchedulerV2& operator=(const TaskSchedulerV2&) = delete;
165
166 // TaskScheduler overrides.
IsTaskRegistered(const wchar_t * task_name)167 bool IsTaskRegistered(const wchar_t* task_name) override {
168 DCHECK(task_name);
169 if (!task_folder_)
170 return false;
171
172 return GetTask(task_name, nullptr);
173 }
174
GetNextTaskRunTime(const wchar_t * task_name,base::Time * next_run_time)175 bool GetNextTaskRunTime(const wchar_t* task_name,
176 base::Time* next_run_time) override {
177 DCHECK(task_name);
178 DCHECK(next_run_time);
179 if (!task_folder_)
180 return false;
181
182 Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
183 if (!GetTask(task_name, ®istered_task))
184 return false;
185
186 // We unfortunately can't use get_NextRunTime because of a known bug which
187 // requires hotfix: http://support.microsoft.com/kb/2495489/en-us. So fetch
188 // one of the run times in the next day.
189 // Also, although it's not obvious from MSDN, IRegisteredTask::GetRunTimes
190 // expects local time.
191 SYSTEMTIME start_system_time = {};
192 GetLocalTime(&start_system_time);
193
194 base::Time tomorrow(base::Time::NowFromSystemTime() +
195 base::TimeDelta::FromDays(1));
196 SYSTEMTIME end_system_time = {};
197 if (!UTCFileTimeToLocalSystemTime(tomorrow.ToFileTime(), &end_system_time))
198 return false;
199
200 DWORD num_run_times = 1;
201 SYSTEMTIME* raw_run_times = nullptr;
202 HRESULT hr = registered_task->GetRunTimes(
203 &start_system_time, &end_system_time, &num_run_times, &raw_run_times);
204 if (FAILED(hr)) {
205 PLOG(ERROR) << "Failed to GetRunTimes, " << std::hex << hr;
206 return false;
207 }
208
209 if (num_run_times == 0)
210 return false;
211
212 base::win::ScopedCoMem<SYSTEMTIME> run_times;
213 run_times.Reset(raw_run_times);
214 // Again, although unclear from MSDN, IRegisteredTask::GetRunTimes returns
215 // local times.
216 FILETIME file_time = {};
217 if (!LocalSystemTimeToUTCFileTime(run_times[0], &file_time))
218 return false;
219 *next_run_time = base::Time::FromFileTime(file_time);
220 return true;
221 }
222
SetTaskEnabled(const wchar_t * task_name,bool enabled)223 bool SetTaskEnabled(const wchar_t* task_name, bool enabled) override {
224 DCHECK(task_name);
225 if (!task_folder_)
226 return false;
227
228 Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
229 if (!GetTask(task_name, ®istered_task)) {
230 LOG(ERROR) << "Failed to find the task " << task_name
231 << " to enable/disable";
232 return false;
233 }
234
235 HRESULT hr;
236 hr = registered_task->put_Enabled(enabled ? VARIANT_TRUE : VARIANT_FALSE);
237 if (FAILED(hr)) {
238 PLOG(ERROR) << "Failed to set enabled status of task named " << task_name
239 << ". " << std::hex << hr;
240 return false;
241 }
242 return true;
243 }
244
IsTaskEnabled(const wchar_t * task_name)245 bool IsTaskEnabled(const wchar_t* task_name) override {
246 DCHECK(task_name);
247 if (!task_folder_)
248 return false;
249
250 Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
251 if (!GetTask(task_name, ®istered_task))
252 return false;
253
254 HRESULT hr;
255 VARIANT_BOOL is_enabled;
256 hr = registered_task->get_Enabled(&is_enabled);
257 if (FAILED(hr)) {
258 LOG(ERROR) << "Failed to get enabled status for task named " << task_name
259 << ". " << std::hex << hr << ": "
260 << logging::SystemErrorCodeToString(hr);
261 return false;
262 }
263
264 return is_enabled == VARIANT_TRUE;
265 }
266
GetTaskNameList(std::vector<base::string16> * task_names)267 bool GetTaskNameList(std::vector<base::string16>* task_names) override {
268 DCHECK(task_names);
269 if (!task_folder_)
270 return false;
271
272 for (TaskIterator it(task_folder_.Get()); !it.done(); it.Next())
273 task_names->push_back(it.name());
274 return true;
275 }
276
GetTaskInfo(const wchar_t * task_name,TaskInfo * info)277 bool GetTaskInfo(const wchar_t* task_name, TaskInfo* info) override {
278 DCHECK(task_name);
279 DCHECK(info);
280 if (!task_folder_)
281 return false;
282
283 Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
284 if (!GetTask(task_name, ®istered_task))
285 return false;
286
287 // Collect information into internal storage to ensure that we start with
288 // a clean slate and don't return partial results on error.
289 TaskInfo info_storage;
290 HRESULT hr =
291 GetTaskDescription(registered_task.Get(), &info_storage.description);
292 if (FAILED(hr)) {
293 LOG(ERROR) << "Failed to get description for task '" << task_name << "'. "
294 << std::hex << hr << ": "
295 << logging::SystemErrorCodeToString(hr);
296 return false;
297 }
298
299 if (!GetTaskExecActions(registered_task.Get(),
300 &info_storage.exec_actions)) {
301 LOG(ERROR) << "Failed to get actions for task '" << task_name << "'";
302 return false;
303 }
304
305 hr = GetTaskLogonType(registered_task.Get(), &info_storage.logon_type);
306 if (FAILED(hr)) {
307 LOG(ERROR) << "Failed to get logon type for task '" << task_name << "'. "
308 << std::hex << hr << ": "
309 << logging::SystemErrorCodeToString(hr);
310 return false;
311 }
312 info_storage.name = task_name;
313 std::swap(*info, info_storage);
314 return true;
315 }
316
HasTaskFolder(const wchar_t * folder_name)317 bool HasTaskFolder(const wchar_t* folder_name) override {
318 Microsoft::WRL::ComPtr<ITaskFolder> task_folder;
319 const HRESULT hr = task_service_->GetFolder(
320 base::win::ScopedBstr(folder_name).Get(), &task_folder);
321 return SUCCEEDED(hr);
322 }
323
DeleteTask(const wchar_t * task_name)324 bool DeleteTask(const wchar_t* task_name) override {
325 DCHECK(task_name);
326 if (!task_folder_)
327 return false;
328
329 VLOG(1) << "Delete Task '" << task_name << "'.";
330
331 HRESULT hr =
332 task_folder_->DeleteTask(base::win::ScopedBstr(task_name).Get(), 0);
333 // This can happen, e.g., while running tests, when the file system stresses
334 // quite a lot. Give it a few more chances to succeed.
335 size_t num_retries_left = kNumDeleteTaskRetry;
336
337 if (FAILED(hr)) {
338 while ((hr == HRESULT_FROM_WIN32(ERROR_TRANSACTION_NOT_ACTIVE) ||
339 hr == HRESULT_FROM_WIN32(ERROR_TRANSACTION_ALREADY_ABORTED)) &&
340 --num_retries_left && IsTaskRegistered(task_name)) {
341 LOG(WARNING) << "Retrying delete task because transaction not active, "
342 << std::hex << hr << ".";
343
344 hr =
345 task_folder_->DeleteTask(base::win::ScopedBstr(task_name).Get(), 0);
346 ::Sleep(kDeleteRetryDelayInMs);
347 }
348 if (!IsTaskRegistered(task_name))
349 hr = S_OK;
350 }
351
352 if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
353 PLOG(ERROR) << "Can't delete task. " << std::hex << hr;
354 return false;
355 }
356
357 DCHECK(!IsTaskRegistered(task_name));
358
359 // Try to delete \\Company\Product first and \\Company second
360 if (DeleteFolderIfEmpty(kTaskSubfolderName))
361 DeleteFolderIfEmpty(kTaskCompanyFolder);
362
363 return true;
364 }
365
RegisterTask(const wchar_t * task_name,const wchar_t * task_description,const base::CommandLine & run_command,TriggerType trigger_type,bool hidden)366 bool RegisterTask(const wchar_t* task_name,
367 const wchar_t* task_description,
368 const base::CommandLine& run_command,
369 TriggerType trigger_type,
370 bool hidden) override {
371 DCHECK(task_name);
372 DCHECK(task_description);
373 if (!DeleteTask(task_name))
374 return false;
375
376 // Create the task definition object to create the task.
377 Microsoft::WRL::ComPtr<ITaskDefinition> task;
378 DCHECK(task_service_);
379 HRESULT hr = task_service_->NewTask(0, &task);
380 if (FAILED(hr)) {
381 PLOG(ERROR) << "Can't create new task. " << std::hex << hr;
382 return false;
383 }
384
385 base::win::ScopedBstr user_name;
386 if (!GetCurrentUser(&user_name))
387 return false;
388
389 if (trigger_type != TRIGGER_TYPE_NOW) {
390 // Allow the task to run elevated on startup.
391 Microsoft::WRL::ComPtr<IPrincipal> principal;
392 hr = task->get_Principal(&principal);
393 if (FAILED(hr)) {
394 PLOG(ERROR) << "Can't get principal. " << std::hex << hr;
395 return false;
396 }
397
398 hr = principal->put_UserId(user_name.Get());
399 if (FAILED(hr)) {
400 PLOG(ERROR) << "Can't put user id. " << std::hex << hr;
401 return false;
402 }
403
404 hr = principal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
405 if (FAILED(hr)) {
406 PLOG(ERROR) << "Can't put logon type. " << std::hex << hr;
407 return false;
408 }
409 }
410
411 Microsoft::WRL::ComPtr<IRegistrationInfo> registration_info;
412 hr = task->get_RegistrationInfo(®istration_info);
413 if (FAILED(hr)) {
414 PLOG(ERROR) << "Can't get registration info. " << std::hex << hr;
415 return false;
416 }
417
418 hr = registration_info->put_Author(user_name.Get());
419 if (FAILED(hr)) {
420 PLOG(ERROR) << "Can't set registration info author. " << std::hex << hr;
421 return false;
422 }
423
424 base::win::ScopedBstr description(task_description);
425 hr = registration_info->put_Description(description.Get());
426 if (FAILED(hr)) {
427 PLOG(ERROR) << "Can't set description. " << std::hex << hr;
428 return false;
429 }
430
431 Microsoft::WRL::ComPtr<ITaskSettings> task_settings;
432 hr = task->get_Settings(&task_settings);
433 if (FAILED(hr)) {
434 PLOG(ERROR) << "Can't get task settings. " << std::hex << hr;
435 return false;
436 }
437
438 hr = task_settings->put_StartWhenAvailable(VARIANT_TRUE);
439 if (FAILED(hr)) {
440 PLOG(ERROR) << "Can't put 'StartWhenAvailable' to true. " << std::hex
441 << hr;
442 return false;
443 }
444
445 // TODO(csharp): Find a way to only set this for log upload retry.
446 hr = task_settings->put_DeleteExpiredTaskAfter(
447 base::win::ScopedBstr(kZeroMinuteText).Get());
448 if (FAILED(hr)) {
449 PLOG(ERROR) << "Can't put 'DeleteExpiredTaskAfter'. " << std::hex << hr;
450 return false;
451 }
452
453 hr = task_settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
454 if (FAILED(hr)) {
455 PLOG(ERROR) << "Can't put 'DisallowStartIfOnBatteries' to false. "
456 << std::hex << hr;
457 return false;
458 }
459
460 hr = task_settings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
461 if (FAILED(hr)) {
462 PLOG(ERROR) << "Can't put 'StopIfGoingOnBatteries' to false. " << std::hex
463 << hr;
464 return false;
465 }
466
467 if (hidden) {
468 hr = task_settings->put_Hidden(VARIANT_TRUE);
469 if (FAILED(hr)) {
470 PLOG(ERROR) << "Can't put 'Hidden' to true. " << std::hex << hr;
471 return false;
472 }
473 }
474
475 Microsoft::WRL::ComPtr<ITriggerCollection> trigger_collection;
476 hr = task->get_Triggers(&trigger_collection);
477 if (FAILED(hr)) {
478 PLOG(ERROR) << "Can't get trigger collection. " << std::hex << hr;
479 return false;
480 }
481
482 TASK_TRIGGER_TYPE2 task_trigger_type = TASK_TRIGGER_EVENT;
483 base::win::ScopedBstr repetition_interval;
484 switch (trigger_type) {
485 case TRIGGER_TYPE_POST_REBOOT:
486 task_trigger_type = TASK_TRIGGER_LOGON;
487 break;
488 case TRIGGER_TYPE_NOW:
489 task_trigger_type = TASK_TRIGGER_REGISTRATION;
490 break;
491 case TRIGGER_TYPE_HOURLY:
492 case TRIGGER_TYPE_EVERY_FIVE_HOURS:
493 task_trigger_type = TASK_TRIGGER_DAILY;
494 if (trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
495 repetition_interval.Reset(::SysAllocString(kFiveHoursText));
496 } else if (trigger_type == TRIGGER_TYPE_HOURLY) {
497 repetition_interval.Reset(::SysAllocString(kOneHourText));
498 } else {
499 NOTREACHED() << "Unknown TriggerType?";
500 }
501 break;
502 default:
503 NOTREACHED() << "Unknown TriggerType?";
504 }
505
506 Microsoft::WRL::ComPtr<ITrigger> trigger;
507 hr = trigger_collection->Create(task_trigger_type, &trigger);
508 if (FAILED(hr)) {
509 PLOG(ERROR) << "Can't create trigger of type " << task_trigger_type
510 << ". " << std::hex << hr;
511 return false;
512 }
513
514 if (trigger_type == TRIGGER_TYPE_HOURLY ||
515 trigger_type == TRIGGER_TYPE_EVERY_FIVE_HOURS) {
516 Microsoft::WRL::ComPtr<IDailyTrigger> daily_trigger;
517 hr = trigger.As(&daily_trigger);
518 if (FAILED(hr)) {
519 PLOG(ERROR) << "Can't Query for registration trigger. " << std::hex
520 << hr;
521 return false;
522 }
523
524 hr = daily_trigger->put_DaysInterval(1);
525 if (FAILED(hr)) {
526 PLOG(ERROR) << "Can't put 'DaysInterval' to 1, " << std::hex << hr;
527 return false;
528 }
529
530 Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
531 hr = trigger->get_Repetition(&repetition_pattern);
532 if (FAILED(hr)) {
533 PLOG(ERROR) << "Can't get 'Repetition'. " << std::hex << hr;
534 return false;
535 }
536
537 // The duration is the time to keep repeating until the next daily
538 // trigger.
539 hr = repetition_pattern->put_Duration(
540 base::win::ScopedBstr(kTwentyFourHoursText).Get());
541 if (FAILED(hr)) {
542 PLOG(ERROR) << "Can't put 'Duration' to " << kTwentyFourHoursText
543 << ". " << std::hex << hr;
544 return false;
545 }
546
547 hr = repetition_pattern->put_Interval(repetition_interval.Get());
548 if (FAILED(hr)) {
549 PLOG(ERROR) << "Can't put 'Interval' to " << repetition_interval.Get()
550 << ". " << std::hex << hr;
551 return false;
552 }
553
554 // Start now.
555 base::Time now(base::Time::NowFromSystemTime());
556 base::win::ScopedBstr start_boundary(GetTimestampString(now));
557 hr = trigger->put_StartBoundary(start_boundary.Get());
558 if (FAILED(hr)) {
559 PLOG(ERROR) << "Can't put 'StartBoundary' to " << start_boundary.Get()
560 << ". " << std::hex << hr;
561 return false;
562 }
563 }
564
565 if (trigger_type == TRIGGER_TYPE_POST_REBOOT) {
566 Microsoft::WRL::ComPtr<ILogonTrigger> logon_trigger;
567 hr = trigger.As(&logon_trigger);
568 if (FAILED(hr)) {
569 PLOG(ERROR) << "Can't query trigger for 'ILogonTrigger'. " << std::hex
570 << hr;
571 return false;
572 }
573
574 hr = logon_trigger->put_Delay(
575 base::win::ScopedBstr(kFifteenMinutesText).Get());
576 if (FAILED(hr)) {
577 PLOG(ERROR) << "Can't put 'Delay'. " << std::hex << hr;
578 return false;
579 }
580 }
581
582 // None of the triggers should go beyond kNumDaysBeforeExpiry.
583 base::Time expiry_date(base::Time::NowFromSystemTime() +
584 base::TimeDelta::FromDays(kNumDaysBeforeExpiry));
585 base::win::ScopedBstr end_boundary(GetTimestampString(expiry_date));
586 hr = trigger->put_EndBoundary(end_boundary.Get());
587 if (FAILED(hr)) {
588 PLOG(ERROR) << "Can't put 'EndBoundary' to " << end_boundary.Get() << ". "
589 << std::hex << hr;
590 return false;
591 }
592
593 Microsoft::WRL::ComPtr<IActionCollection> actions;
594 hr = task->get_Actions(&actions);
595 if (FAILED(hr)) {
596 PLOG(ERROR) << "Can't get actions collection. " << std::hex << hr;
597 return false;
598 }
599
600 Microsoft::WRL::ComPtr<IAction> action;
601 hr = actions->Create(TASK_ACTION_EXEC, &action);
602 if (FAILED(hr)) {
603 PLOG(ERROR) << "Can't create exec action. " << std::hex << hr;
604 return false;
605 }
606
607 Microsoft::WRL::ComPtr<IExecAction> exec_action;
608 hr = action.As(&exec_action);
609 if (FAILED(hr)) {
610 PLOG(ERROR) << "Can't query for exec action. " << std::hex << hr;
611 return false;
612 }
613
614 base::win::ScopedBstr path(run_command.GetProgram().value());
615 hr = exec_action->put_Path(path.Get());
616 if (FAILED(hr)) {
617 PLOG(ERROR) << "Can't set path of exec action. " << std::hex << hr;
618 return false;
619 }
620
621 base::win::ScopedBstr args(run_command.GetArgumentsString());
622 hr = exec_action->put_Arguments(args.Get());
623 if (FAILED(hr)) {
624 PLOG(ERROR) << "Can't set arguments of exec action. " << std::hex << hr;
625 return false;
626 }
627
628 Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
629 base::win::ScopedVariant user(user_name.Get());
630
631 DCHECK(task_folder_);
632 hr = task_folder_->RegisterTaskDefinition(
633 base::win::ScopedBstr(task_name).Get(), task.Get(), TASK_CREATE,
634 *user.AsInput(), // Not really input, but API expect non-const.
635 base::win::ScopedVariant::kEmptyVariant, TASK_LOGON_NONE,
636 base::win::ScopedVariant::kEmptyVariant, ®istered_task);
637 if (FAILED(hr)) {
638 LOG(ERROR) << "RegisterTaskDefinition failed. " << std::hex << hr << ": "
639 << logging::SystemErrorCodeToString(hr);
640 return false;
641 }
642
643 DCHECK(IsTaskRegistered(task_name));
644
645 VLOG(1) << "Successfully registered: "
646 << run_command.GetCommandLineString();
647 return true;
648 }
649
650 private:
651 // Helper class that lets us iterate over all registered tasks.
652 class TaskIterator {
653 public:
TaskIterator(ITaskFolder * task_folder)654 explicit TaskIterator(ITaskFolder* task_folder) {
655 DCHECK(task_folder);
656 HRESULT hr = task_folder->GetTasks(TASK_ENUM_HIDDEN, &tasks_);
657 if (FAILED(hr)) {
658 if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
659 LOG(ERROR) << "Failed to get tasks from folder." << std::hex << hr;
660
661 done_ = true;
662 return;
663 }
664 hr = tasks_->get_Count(&num_tasks_);
665 if (FAILED(hr)) {
666 LOG(ERROR) << "Failed to get the tasks count." << std::hex << hr;
667 done_ = true;
668 return;
669 }
670 Next();
671 }
672
673 // Increment to the next valid item in the task list. Skip entries for
674 // which we cannot retrieve a name.
Next()675 void Next() {
676 DCHECK(!done_);
677 task_.Reset();
678 name_.clear();
679 if (++task_index_ >= num_tasks_) {
680 done_ = true;
681 return;
682 }
683
684 // Note: get_Item uses 1 based indices.
685 HRESULT hr =
686 tasks_->get_Item(base::win::ScopedVariant(task_index_ + 1), &task_);
687 if (FAILED(hr)) {
688 PLOG(ERROR) << "Failed to get task at index: " << task_index_ << ". "
689 << std::hex << hr;
690 Next();
691 return;
692 }
693
694 base::win::ScopedBstr task_name_bstr;
695 hr = task_->get_Name(task_name_bstr.Receive());
696 if (FAILED(hr)) {
697 PLOG(ERROR) << "Failed to get name at index: " << task_index_ << ". "
698 << std::hex << hr;
699 Next();
700 return;
701 }
702 name_ = base::string16(task_name_bstr.Get() ? task_name_bstr.Get() : L"");
703 }
704
705 // Detach the currently active task and pass ownership to the caller.
706 // After this method has been called, the -> operator must no longer be
707 // used.
Detach()708 IRegisteredTask* Detach() { return task_.Detach(); }
709
710 // Provide access to the current task.
operator ->() const711 IRegisteredTask* operator->() const {
712 IRegisteredTask* result = task_.Get();
713 DCHECK(result);
714 return result;
715 }
716
name() const717 const base::string16& name() const { return name_; }
done() const718 bool done() const { return done_; }
719
720 private:
721 Microsoft::WRL::ComPtr<IRegisteredTaskCollection> tasks_;
722 Microsoft::WRL::ComPtr<IRegisteredTask> task_;
723 base::string16 name_;
724 long task_index_ = -1; // NOLINT, API requires a long.
725 long num_tasks_ = 0; // NOLINT, API requires a long.
726 bool done_ = false;
727 };
728
729 // Return the task with |task_name| and false if not found. |task| can be null
730 // when only interested in task's existence.
GetTask(const wchar_t * task_name,IRegisteredTask ** task)731 bool GetTask(const wchar_t* task_name, IRegisteredTask** task) {
732 for (TaskIterator it(task_folder_.Get()); !it.done(); it.Next()) {
733 if (::_wcsicmp(it.name().c_str(), task_name) == 0) {
734 if (task)
735 *task = it.Detach();
736 return true;
737 }
738 }
739 return false;
740 }
741
742 // Return the description of the task.
GetTaskDescription(IRegisteredTask * task,base::string16 * description)743 HRESULT GetTaskDescription(IRegisteredTask* task,
744 base::string16* description) {
745 DCHECK(task);
746 DCHECK(description);
747
748 base::win::ScopedBstr task_name_bstr;
749 HRESULT hr = task->get_Name(task_name_bstr.Receive());
750 base::string16 task_name =
751 base::string16(task_name_bstr.Get() ? task_name_bstr.Get() : L"");
752 if (FAILED(hr)) {
753 LOG(ERROR) << "Failed to get task name";
754 }
755
756 Microsoft::WRL::ComPtr<ITaskDefinition> task_info;
757 hr = task->get_Definition(&task_info);
758 if (FAILED(hr)) {
759 LOG(ERROR) << "Failed to get definition for task, " << task_name << ": "
760 << logging::SystemErrorCodeToString(hr);
761 return hr;
762 }
763
764 Microsoft::WRL::ComPtr<IRegistrationInfo> reg_info;
765 hr = task_info->get_RegistrationInfo(®_info);
766 if (FAILED(hr)) {
767 LOG(ERROR) << "Failed to get registration info, " << task_name << ": "
768 << logging::SystemErrorCodeToString(hr);
769 return hr;
770 }
771
772 base::win::ScopedBstr raw_description;
773 hr = reg_info->get_Description(raw_description.Receive());
774 if (FAILED(hr)) {
775 LOG(ERROR) << "Failed to get description, " << task_name << ": "
776 << logging::SystemErrorCodeToString(hr);
777 return hr;
778 }
779 *description =
780 base::string16(raw_description.Get() ? raw_description.Get() : L"");
781 return ERROR_SUCCESS;
782 }
783
784 // Return all executable actions associated with the given task. Non-exec
785 // actions are silently ignored.
GetTaskExecActions(IRegisteredTask * task,std::vector<TaskExecAction> * actions)786 bool GetTaskExecActions(IRegisteredTask* task,
787 std::vector<TaskExecAction>* actions) {
788 DCHECK(task);
789 DCHECK(actions);
790 Microsoft::WRL::ComPtr<ITaskDefinition> task_definition;
791 HRESULT hr = task->get_Definition(&task_definition);
792 if (FAILED(hr)) {
793 PLOG(ERROR) << "Failed to get definition of task, " << std::hex << hr;
794 return false;
795 }
796
797 Microsoft::WRL::ComPtr<IActionCollection> action_collection;
798 hr = task_definition->get_Actions(&action_collection);
799 if (FAILED(hr)) {
800 PLOG(ERROR) << "Failed to get action collection, " << std::hex << hr;
801 return false;
802 }
803
804 long actions_count = 0; // NOLINT, API requires a long.
805 hr = action_collection->get_Count(&actions_count);
806 if (FAILED(hr)) {
807 PLOG(ERROR) << "Failed to get number of actions, " << std::hex << hr;
808 return false;
809 }
810
811 // Find and return as many exec actions as possible in |actions| and return
812 // false if there were any errors on the way. Note that the indexing of
813 // actions is 1-based.
814 bool success = true;
815 for (long action_index = 1; // NOLINT
816 action_index <= actions_count; ++action_index) {
817 Microsoft::WRL::ComPtr<IAction> action;
818 hr = action_collection->get_Item(action_index, &action);
819 if (FAILED(hr)) {
820 PLOG(ERROR) << "Failed to get action at index " << action_index << ", "
821 << std::hex << hr;
822 success = false;
823 continue;
824 }
825
826 ::TASK_ACTION_TYPE action_type;
827 hr = action->get_Type(&action_type);
828 if (FAILED(hr)) {
829 PLOG(ERROR) << "Failed to get the type of action at index "
830 << action_index << ", " << std::hex << hr;
831 success = false;
832 continue;
833 }
834
835 // We only care about exec actions for now. The other types are
836 // TASK_ACTION_COM_HANDLER, TASK_ACTION_SEND_EMAIL,
837 // TASK_ACTION_SHOW_MESSAGE. The latter two are marked as deprecated in
838 // the Task Scheduler's GUI.
839 if (action_type != ::TASK_ACTION_EXEC)
840 continue;
841
842 Microsoft::WRL::ComPtr<IExecAction> exec_action;
843 hr = action.As(&exec_action);
844 if (FAILED(hr)) {
845 PLOG(ERROR) << "Failed to query from action, " << std::hex << hr;
846 success = false;
847 continue;
848 }
849
850 base::win::ScopedBstr application_path;
851 hr = exec_action->get_Path(application_path.Receive());
852 if (FAILED(hr)) {
853 PLOG(ERROR) << "Failed to get path from action, " << std::hex << hr;
854 success = false;
855 continue;
856 }
857
858 base::win::ScopedBstr working_dir;
859 hr = exec_action->get_WorkingDirectory(working_dir.Receive());
860 if (FAILED(hr)) {
861 PLOG(ERROR) << "Failed to get working directory for action, "
862 << std::hex << hr;
863 success = false;
864 continue;
865 }
866
867 base::win::ScopedBstr parameters;
868 hr = exec_action->get_Arguments(parameters.Receive());
869 if (FAILED(hr)) {
870 PLOG(ERROR) << "Failed to get arguments from action of task, "
871 << std::hex << hr;
872 success = false;
873 continue;
874 }
875
876 actions->push_back(
877 {base::FilePath(application_path.Get() ? application_path.Get()
878 : L""),
879 base::FilePath(working_dir.Get() ? working_dir.Get() : L""),
880 base::string16(parameters.Get() ? parameters.Get() : L"")});
881 }
882 return success;
883 }
884
885 // Return the log-on type required for the task's actions to be run.
GetTaskLogonType(IRegisteredTask * task,uint32_t * logon_type)886 HRESULT GetTaskLogonType(IRegisteredTask* task, uint32_t* logon_type) {
887 DCHECK(task);
888 DCHECK(logon_type);
889 Microsoft::WRL::ComPtr<ITaskDefinition> task_info;
890 HRESULT hr = task->get_Definition(&task_info);
891 if (FAILED(hr)) {
892 LOG(ERROR) << "Failed to get definition, " << std::hex << hr << ": "
893 << logging::SystemErrorCodeToString(hr);
894 return hr;
895 }
896
897 Microsoft::WRL::ComPtr<IPrincipal> principal;
898 hr = task_info->get_Principal(&principal);
899 if (FAILED(hr)) {
900 LOG(ERROR) << "Failed to get principal info, " << std::hex << hr << ": "
901 << logging::SystemErrorCodeToString(hr);
902 return hr;
903 }
904
905 TASK_LOGON_TYPE raw_logon_type;
906 hr = principal->get_LogonType(&raw_logon_type);
907 if (FAILED(hr)) {
908 LOG(ERROR) << "Failed to get logon type info, " << std::hex << hr << ": "
909 << logging::SystemErrorCodeToString(hr);
910 return hr;
911 }
912
913 switch (raw_logon_type) {
914 case TASK_LOGON_INTERACTIVE_TOKEN:
915 *logon_type = LOGON_INTERACTIVE;
916 break;
917 case TASK_LOGON_GROUP: // fall-thru
918 case TASK_LOGON_PASSWORD: // fall-thru
919 case TASK_LOGON_SERVICE_ACCOUNT:
920 *logon_type = LOGON_SERVICE;
921 break;
922 case TASK_LOGON_S4U:
923 *logon_type = LOGON_SERVICE | LOGON_S4U;
924 break;
925 case TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD:
926 *logon_type = LOGON_INTERACTIVE | LOGON_SERVICE;
927 break;
928 default:
929 *logon_type = LOGON_UNKNOWN;
930 break;
931 }
932 return ERROR_SUCCESS;
933 }
934
935 // Return the branded task folder (e.g. \\Google\Updater).
GetUpdaterTaskFolder()936 Microsoft::WRL::ComPtr<ITaskFolder> GetUpdaterTaskFolder() {
937 if (!task_service_)
938 return nullptr;
939
940 Microsoft::WRL::ComPtr<ITaskFolder> root_task_folder;
941 HRESULT hr = task_service_->GetFolder(base::win::ScopedBstr(L"\\").Get(),
942 &root_task_folder);
943 if (FAILED(hr)) {
944 LOG(ERROR) << "Can't get task service folder. " << std::hex << hr;
945 return nullptr;
946 }
947
948 // Try to find the folder first.
949 Microsoft::WRL::ComPtr<ITaskFolder> folder;
950 base::win::ScopedBstr company_folder_name(kTaskSubfolderName);
951 hr = root_task_folder->GetFolder(company_folder_name.Get(), &folder);
952
953 // Try creating the folder it wasn't there.
954 if (FAILED(hr) && (hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) ||
955 hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))) {
956 // Use default SDDL.
957 hr = root_task_folder->CreateFolder(
958 company_folder_name.Get(), base::win::ScopedVariant::kEmptyVariant,
959 &folder);
960
961 if (FAILED(hr)) {
962 LOG(ERROR) << "Failed to create the folder." << std::hex << hr;
963 return nullptr;
964 }
965 } else if (FAILED(hr)) {
966 LOG(ERROR) << "Failed to get the folder." << std::hex << hr;
967 return nullptr;
968 }
969
970 return folder;
971 }
972
973 // If the task folder specified by |folder_name| is empty, try to delete it.
974 // Ignore failures. Returns true if the folder is successfully deleted.
DeleteFolderIfEmpty(const wchar_t * folder_name)975 bool DeleteFolderIfEmpty(const wchar_t* folder_name) {
976 // Try deleting if empty. Race conditions here should be handled by the API.
977 Microsoft::WRL::ComPtr<ITaskFolder> root_task_folder;
978 HRESULT hr = task_service_->GetFolder(base::win::ScopedBstr(L"\\").Get(),
979 &root_task_folder);
980 if (FAILED(hr)) {
981 LOG(ERROR) << "Failed get root folder. " << std::hex << hr;
982 return false;
983 }
984
985 Microsoft::WRL::ComPtr<ITaskFolder> task_folder;
986 hr = root_task_folder->GetFolder(base::win::ScopedBstr(folder_name).Get(),
987 &task_folder);
988 if (FAILED(hr)) {
989 // If we failed because the task folder is not present our job is done.
990 if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
991 return true;
992 }
993 LOG(ERROR) << "Failed to get sub-folder. " << std::hex << hr;
994 return false;
995 }
996
997 // Check tasks and subfolders first to see non empty
998 Microsoft::WRL::ComPtr<ITaskFolderCollection> subfolders;
999 hr = task_folder->GetFolders(0, &subfolders);
1000 if (FAILED(hr)) {
1001 LOG(ERROR) << "Failed to enumerate sub-folders. " << std::hex << hr;
1002 return false;
1003 }
1004
1005 LONG item_count = 0;
1006 subfolders->get_Count(&item_count);
1007 if (FAILED(hr) || item_count > 0)
1008 return false;
1009
1010 Microsoft::WRL::ComPtr<IRegisteredTaskCollection> tasks;
1011 hr = task_folder->GetTasks(TASK_ENUM_HIDDEN, &tasks);
1012 if (FAILED(hr)) {
1013 LOG(ERROR) << "Failed to enumerate tasks. " << std::hex << hr;
1014 return false;
1015 }
1016
1017 item_count = 0;
1018 tasks->get_Count(&item_count);
1019 if (FAILED(hr) || item_count > 0)
1020 return false;
1021
1022 hr = root_task_folder->DeleteFolder(
1023 base::win::ScopedBstr(folder_name).Get(), 0);
1024 if (FAILED(hr)) {
1025 LOG(ERROR) << "Failed get delete the sub folder. " << std::hex << hr;
1026 return false;
1027 }
1028
1029 return true;
1030 }
1031
1032 Microsoft::WRL::ComPtr<ITaskService> task_service_;
1033
1034 // Folder in which all updater scheduled tasks are grouped in.
1035 Microsoft::WRL::ComPtr<ITaskFolder> task_folder_;
1036 };
1037
1038 } // namespace
1039
1040 TaskScheduler::TaskInfo::TaskInfo() = default;
1041
1042 TaskScheduler::TaskInfo::TaskInfo(const TaskScheduler::TaskInfo&) = default;
1043
1044 TaskScheduler::TaskInfo::TaskInfo(TaskScheduler::TaskInfo&&) = default;
1045
1046 TaskScheduler::TaskInfo& TaskScheduler::TaskInfo::operator=(
1047 const TaskScheduler::TaskInfo&) = default;
1048
1049 TaskScheduler::TaskInfo& TaskScheduler::TaskInfo::operator=(
1050 TaskScheduler::TaskInfo&&) = default;
1051
1052 TaskScheduler::TaskInfo::~TaskInfo() = default;
1053
1054 // static.
CreateInstance()1055 std::unique_ptr<TaskScheduler> TaskScheduler::CreateInstance() {
1056 return std::make_unique<TaskSchedulerV2>();
1057 }
1058
1059 TaskScheduler::TaskScheduler() = default;
1060
1061 } // namespace updater
1062