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, &registered_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, &registered_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, &registered_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, &registered_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(&registration_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, &registered_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(&reg_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