blob: 1ec77be5469b52e380e535efb2aa2d804d8a1375 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/chrome_cleaner/os/task_scheduler.h"
#include <mstask.h>
#include <oleauto.h>
#include <security.h>
#include <taskschd.h>
#include <wrl/client.h>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_variant.h"
#include "base/win/windows_version.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
namespace chrome_cleaner {
namespace {
// Names of the TaskSchedulerV2 libraries so we can pin them below.
const wchar_t kV2Library[] = L"taskschd.dll";
// Text for times used in the V2 API of the Task Scheduler.
const wchar_t kOneHourText[] = L"PT1H";
const wchar_t kSixHoursText[] = L"PT6H";
const wchar_t kZeroMinuteText[] = L"PT0M";
const wchar_t kFifteenMinutesText[] = L"PT15M";
const wchar_t kTwentyFourHoursText[] = L"PT24H";
// Most of the users with pending logs succeeds within 7 days, so no need to
// try for longer than that, especially for those who keep crashing.
const int kNumDaysBeforeExpiry = 7;
const size_t kNumDeleteTaskRetry = 3;
const size_t kDeleteRetryDelayInMs = 100;
// Return |timestamp| in the following string format YYYY-MM-DDTHH:MM:SS.
base::string16 GetTimestampString(const base::Time& timestamp) {
base::Time::Exploded exploded_time;
// The Z timezone info at the end of the string means UTC.
timestamp.UTCExplode(&exploded_time);
return base::StringPrintf(L"%04d-%02d-%02dT%02d:%02d:%02dZ",
exploded_time.year, exploded_time.month,
exploded_time.day_of_month, exploded_time.hour,
exploded_time.minute, exploded_time.second);
}
bool LocalSystemTimeToUTCFileTime(const SYSTEMTIME& system_time_local,
FILETIME* file_time_utc) {
DCHECK(file_time_utc);
SYSTEMTIME system_time_utc = {};
if (!::TzSpecificLocalTimeToSystemTime(nullptr, &system_time_local,
&system_time_utc) ||
!::SystemTimeToFileTime(&system_time_utc, file_time_utc)) {
PLOG(ERROR) << "Failed to convert local system time to UTC file time.";
return false;
}
return true;
}
bool UTCFileTimeToLocalSystemTime(const FILETIME& file_time_utc,
SYSTEMTIME* system_time_local) {
DCHECK(system_time_local);
SYSTEMTIME system_time_utc = {};
if (!::FileTimeToSystemTime(&file_time_utc, &system_time_utc) ||
!::SystemTimeToTzSpecificLocalTime(nullptr, &system_time_utc,
system_time_local)) {
PLOG(ERROR) << "Failed to convert file time to UTC local system.";
return false;
}
return true;
}
bool GetCurrentUser(base::win::ScopedBstr* user_name) {
DCHECK(user_name);
ULONG user_name_size = 256;
// Paranoia... ;-)
DCHECK_EQ(sizeof(OLECHAR), sizeof(WCHAR));
if (!::GetUserNameExW(
NameSamCompatible,
user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
&user_name_size)) {
if (::GetLastError() != ERROR_MORE_DATA) {
PLOG(ERROR) << "GetUserNameEx failed.";
return false;
}
if (!::GetUserNameExW(
NameSamCompatible,
user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
&user_name_size)) {
DCHECK_NE(static_cast<DWORD>(ERROR_MORE_DATA), ::GetLastError());
PLOG(ERROR) << "GetUserNameEx failed.";
return false;
}
}
return true;
}
void PinModule(const wchar_t* module_name) {
// Force the DLL to stay loaded until program termination. We have seen
// cases where it gets unloaded even though we still have references to
// the objects we just CoCreated.
base::NativeLibrary module_handle = nullptr;
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, module_name,
&module_handle)) {
PLOG(ERROR) << "Failed to pin '" << module_name << "'.";
}
}
// Another version of the TaskScheduler class that uses the V2 API of the task
// scheduler for Vista+.
class TaskSchedulerV2 : public TaskScheduler {
public:
static bool Initialize() {
DCHECK(!task_service_);
DCHECK(!root_task_folder_);
HRESULT hr =
::CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&task_service_));
if (FAILED(hr)) {
PLOG(ERROR) << "CreateInstance failed for CLSID_TaskScheduler. "
<< std::hex << hr;
return false;
}
hr = task_service_->Connect(base::win::ScopedVariant::kEmptyVariant,
base::win::ScopedVariant::kEmptyVariant,
base::win::ScopedVariant::kEmptyVariant,
base::win::ScopedVariant::kEmptyVariant);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to connect to task service. " << std::hex << hr;
return false;
}
hr = task_service_->GetFolder(base::win::ScopedBstr(L"\\"),
root_task_folder_.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "Can't get task service folder. " << std::hex << hr;
return false;
}
PinModule(kV2Library);
return true;
}
static void Terminate() {
root_task_folder_.Reset();
task_service_.Reset();
}
TaskSchedulerV2() {
DCHECK(task_service_);
DCHECK(root_task_folder_);
}
// TaskScheduler overrides.
bool IsTaskRegistered(const wchar_t* task_name) override {
DCHECK(task_name);
if (!root_task_folder_)
return false;
return GetTask(task_name, nullptr);
}
bool GetNextTaskRunTime(const wchar_t* task_name,
base::Time* next_run_time) override {
DCHECK(task_name);
DCHECK(next_run_time);
if (!root_task_folder_)
return false;
Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
if (!GetTask(task_name, registered_task.GetAddressOf())) {
LOG(ERROR) << "Failed to get task named " << task_name;
return false;
}
// We unfortunately can't use get_NextRunTime because of a known bug which
// requires hotfix: http://support.microsoft.com/kb/2495489/en-us. So fetch
// one of the run times in the next day.
// Also, although it's not obvious from MSDN, IRegisteredTask::GetRunTimes
// expects local time.
SYSTEMTIME start_system_time = {};
GetLocalTime(&start_system_time);
base::Time tomorrow(base::Time::NowFromSystemTime() +
base::TimeDelta::FromDays(1));
SYSTEMTIME end_system_time = {};
if (!UTCFileTimeToLocalSystemTime(tomorrow.ToFileTime(), &end_system_time))
return false;
DWORD num_run_times = 1;
SYSTEMTIME* raw_run_times = nullptr;
HRESULT hr = registered_task->GetRunTimes(
&start_system_time, &end_system_time, &num_run_times, &raw_run_times);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to GetRunTimes, " << std::hex << hr;
return false;
}
if (num_run_times == 0) {
LOG(WARNING) << "No more run times?";
return false;
}
base::win::ScopedCoMem<SYSTEMTIME> run_times;
run_times.Reset(raw_run_times);
// Again, although unclear from MSDN, IRegisteredTask::GetRunTimes returns
// local times.
FILETIME file_time = {};
if (!LocalSystemTimeToUTCFileTime(run_times[0], &file_time))
return false;
*next_run_time = base::Time::FromFileTime(file_time);
return true;
}
bool SetTaskEnabled(const wchar_t* task_name, bool enabled) override {
DCHECK(task_name);
if (!root_task_folder_)
return false;
Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
if (!GetTask(task_name, registered_task.GetAddressOf())) {
LOG(ERROR) << "Failed to find the task " << task_name
<< " to enable/disable";
return false;
}
HRESULT hr;
hr = registered_task->put_Enabled(enabled ? VARIANT_TRUE : VARIANT_FALSE);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to set enabled status of task named " << task_name
<< ". " << std::hex << hr;
return false;
}
return true;
}
bool IsTaskEnabled(const wchar_t* task_name) override {
DCHECK(task_name);
if (!root_task_folder_)
return false;
Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
if (!GetTask(task_name, registered_task.GetAddressOf()))
return false;
HRESULT hr;
VARIANT_BOOL is_enabled;
hr = registered_task->get_Enabled(&is_enabled);
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get enabled status for task named " << task_name
<< ". " << std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return false;
}
return is_enabled == VARIANT_TRUE;
}
bool GetTaskNameList(std::vector<base::string16>* task_names) override {
DCHECK(task_names);
if (!root_task_folder_)
return false;
for (TaskIterator it(root_task_folder_.Get()); !it.done(); it.Next())
task_names->push_back(it.name());
return true;
}
bool GetTaskInfo(const wchar_t* task_name, TaskInfo* info) override {
DCHECK(task_name);
DCHECK(info);
if (!root_task_folder_)
return false;
Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
if (!GetTask(task_name, registered_task.GetAddressOf())) {
LOG(ERROR) << "Failed to get task named " << task_name;
return false;
}
// Collect information into internal storage to ensure that we start with
// a clean slate and don't return partial results on error.
TaskInfo info_storage;
HRESULT hr =
GetTaskDescription(registered_task.Get(), &info_storage.description);
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get description for task '" << task_name << "'. "
<< std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return false;
}
if (!GetTaskExecActions(registered_task.Get(),
&info_storage.exec_actions)) {
LOG(ERROR) << "Failed to get actions for task '" << task_name << "'";
return false;
}
hr = GetTaskLogonType(registered_task.Get(), &info_storage.logon_type);
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get logon type for task '" << task_name << "'. "
<< std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return false;
}
info_storage.name = task_name;
std::swap(*info, info_storage);
return true;
}
bool DeleteTask(const wchar_t* task_name) override {
DCHECK(task_name);
if (!root_task_folder_)
return false;
LOG(INFO) << "Delete Task '" << task_name << "'.";
HRESULT hr =
root_task_folder_->DeleteTask(base::win::ScopedBstr(task_name), 0);
// This can happen, e.g., while running tests, when the file system stresses
// quite a lot. Give it a few more chances to succeed.
size_t num_retries_left = kNumDeleteTaskRetry;
if (FAILED(hr)) {
while ((hr == HRESULT_FROM_WIN32(ERROR_TRANSACTION_NOT_ACTIVE) ||
hr == HRESULT_FROM_WIN32(ERROR_TRANSACTION_ALREADY_ABORTED)) &&
--num_retries_left && IsTaskRegistered(task_name)) {
LOG(WARNING) << "Retrying delete task because transaction not active, "
<< std::hex << hr << ".";
hr = root_task_folder_->DeleteTask(base::win::ScopedBstr(task_name), 0);
::Sleep(kDeleteRetryDelayInMs);
}
if (!IsTaskRegistered(task_name))
hr = S_OK;
}
if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
PLOG(ERROR) << "Can't delete task. " << std::hex << hr;
return false;
}
DCHECK(!IsTaskRegistered(task_name));
return true;
}
bool RegisterTask(const wchar_t* task_name,
const wchar_t* task_description,
const base::CommandLine& run_command,
TriggerType trigger_type,
bool hidden) override {
DCHECK(task_name);
DCHECK(task_description);
if (!DeleteTask(task_name))
return false;
// Create the task definition object to create the task.
Microsoft::WRL::ComPtr<ITaskDefinition> task;
DCHECK(task_service_);
HRESULT hr = task_service_->NewTask(0, task.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't create new task. " << std::hex << hr;
return false;
}
base::win::ScopedBstr user_name;
if (!GetCurrentUser(&user_name))
return false;
if (trigger_type != TRIGGER_TYPE_NOW) {
// Allow the task to run elevated on startup.
Microsoft::WRL::ComPtr<IPrincipal> principal;
hr = task->get_Principal(principal.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get principal. " << std::hex << hr;
return false;
}
hr = principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't set highest run level. " << std::hex << hr;
return false;
}
hr = principal->put_UserId(user_name);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put user id. " << std::hex << hr;
return false;
}
hr = principal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put logon type. " << std::hex << hr;
return false;
}
}
Microsoft::WRL::ComPtr<IRegistrationInfo> registration_info;
hr = task->get_RegistrationInfo(registration_info.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get registration info. " << std::hex << hr;
return false;
}
hr = registration_info->put_Author(user_name);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't set registration info author. " << std::hex << hr;
return false;
}
base::win::ScopedBstr description(task_description);
hr = registration_info->put_Description(description);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't set description. " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<ITaskSettings> task_settings;
hr = task->get_Settings(task_settings.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get task settings. " << std::hex << hr;
return false;
}
hr = task_settings->put_StartWhenAvailable(VARIANT_TRUE);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'StartWhenAvailable' to true. " << std::hex
<< hr;
return false;
}
// TODO(csharp): Find a way to only set this for log upload retry.
hr = task_settings->put_DeleteExpiredTaskAfter(
base::win::ScopedBstr(kZeroMinuteText));
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'DeleteExpiredTaskAfter'. " << std::hex << hr;
return false;
}
hr = task_settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'DisallowStartIfOnBatteries' to false. "
<< std::hex << hr;
return false;
}
hr = task_settings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'StopIfGoingOnBatteries' to false. " << std::hex
<< hr;
return false;
}
if (hidden) {
hr = task_settings->put_Hidden(VARIANT_TRUE);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'Hidden' to true. " << std::hex << hr;
return false;
}
}
Microsoft::WRL::ComPtr<ITriggerCollection> trigger_collection;
hr = task->get_Triggers(trigger_collection.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get trigger collection. " << std::hex << hr;
return false;
}
TASK_TRIGGER_TYPE2 task_trigger_type = TASK_TRIGGER_EVENT;
base::win::ScopedBstr repetition_interval;
switch (trigger_type) {
case TRIGGER_TYPE_POST_REBOOT:
task_trigger_type = TASK_TRIGGER_LOGON;
break;
case TRIGGER_TYPE_NOW:
task_trigger_type = TASK_TRIGGER_REGISTRATION;
break;
case TRIGGER_TYPE_HOURLY:
case TRIGGER_TYPE_EVERY_SIX_HOURS:
task_trigger_type = TASK_TRIGGER_DAILY;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kLogUploadRetryIntervalSwitch)) {
// String format: PT%lsM
const base::string16 interval_switch = base::StrCat(
{L"PT",
base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
kLogUploadRetryIntervalSwitch),
L"M"});
LOG(WARNING) << "Command line switch overriding retry interval to: "
<< interval_switch;
repetition_interval.Reset(::SysAllocString(interval_switch.c_str()));
} else if (trigger_type == TRIGGER_TYPE_EVERY_SIX_HOURS) {
repetition_interval.Reset(::SysAllocString(kSixHoursText));
} else {
DCHECK_EQ(TRIGGER_TYPE_HOURLY, trigger_type);
repetition_interval.Reset(::SysAllocString(kOneHourText));
}
break;
default:
NOTREACHED() << "Unknown TriggerType?";
}
Microsoft::WRL::ComPtr<ITrigger> trigger;
hr = trigger_collection->Create(task_trigger_type, trigger.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't create trigger of type " << task_trigger_type
<< ". " << std::hex << hr;
return false;
}
if (trigger_type == TRIGGER_TYPE_HOURLY ||
trigger_type == TRIGGER_TYPE_EVERY_SIX_HOURS) {
Microsoft::WRL::ComPtr<IDailyTrigger> daily_trigger;
hr = trigger.CopyTo(daily_trigger.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't Query for registration trigger. " << std::hex
<< hr;
return false;
}
hr = daily_trigger->put_DaysInterval(1);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'DaysInterval' to 1, " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IRepetitionPattern> repetition_pattern;
hr = trigger->get_Repetition(repetition_pattern.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get 'Repetition'. " << std::hex << hr;
return false;
}
// The duration is the time to keep repeating until the next daily
// trigger.
hr = repetition_pattern->put_Duration(
base::win::ScopedBstr(kTwentyFourHoursText));
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'Duration' to " << kTwentyFourHoursText
<< ". " << std::hex << hr;
return false;
}
hr = repetition_pattern->put_Interval(repetition_interval);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'Interval' to " << repetition_interval << ". "
<< std::hex << hr;
return false;
}
// Start now.
base::Time now(base::Time::NowFromSystemTime());
base::win::ScopedBstr start_boundary(GetTimestampString(now));
hr = trigger->put_StartBoundary(start_boundary);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'StartBoundary' to " << start_boundary << ". "
<< std::hex << hr;
return false;
}
}
if (trigger_type == TRIGGER_TYPE_POST_REBOOT) {
Microsoft::WRL::ComPtr<ILogonTrigger> logon_trigger;
hr = trigger.CopyTo(logon_trigger.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't query trigger for 'ILogonTrigger'. " << std::hex
<< hr;
return false;
}
hr = logon_trigger->put_Delay(base::win::ScopedBstr(kFifteenMinutesText));
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'Delay'. " << std::hex << hr;
return false;
}
}
// None of the triggers should go beyond kNumDaysBeforeExpiry.
base::Time expiry_date(base::Time::NowFromSystemTime() +
base::TimeDelta::FromDays(kNumDaysBeforeExpiry));
base::win::ScopedBstr end_boundary(GetTimestampString(expiry_date));
hr = trigger->put_EndBoundary(end_boundary);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't put 'EndBoundary' to " << end_boundary << ". "
<< std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IActionCollection> actions;
hr = task->get_Actions(actions.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't get actions collection. " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IAction> action;
hr = actions->Create(TASK_ACTION_EXEC, action.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't create exec action. " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IExecAction> exec_action;
hr = action.CopyTo(exec_action.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Can't query for exec action. " << std::hex << hr;
return false;
}
base::win::ScopedBstr path(run_command.GetProgram().value());
hr = exec_action->put_Path(path);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't set path of exec action. " << std::hex << hr;
return false;
}
base::win::ScopedBstr args(run_command.GetArgumentsString());
hr = exec_action->put_Arguments(args);
if (FAILED(hr)) {
PLOG(ERROR) << "Can't set arguments of exec action. " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IRegisteredTask> registered_task;
base::win::ScopedVariant user(user_name);
DCHECK(root_task_folder_);
hr = root_task_folder_->RegisterTaskDefinition(
base::win::ScopedBstr(task_name), task.Get(), TASK_CREATE,
*user.AsInput(), // Not really input, but API expect non-const.
base::win::ScopedVariant::kEmptyVariant, TASK_LOGON_NONE,
base::win::ScopedVariant::kEmptyVariant,
registered_task.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "RegisterTaskDefinition failed. " << std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return false;
}
DCHECK(IsTaskRegistered(task_name));
LOG(INFO) << "Successfully registered: "
<< SanitizeCommandLine(run_command);
return true;
}
private:
// Helper class that lets us iterate over all registered tasks.
class TaskIterator {
public:
explicit TaskIterator(ITaskFolder* task_folder) {
DCHECK(task_folder);
HRESULT hr =
task_folder->GetTasks(TASK_ENUM_HIDDEN, tasks_.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get registered tasks from folder. "
<< std::hex << hr;
done_ = true;
return;
}
hr = tasks_->get_Count(&num_tasks_);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get registered tasks count. " << std::hex
<< hr;
done_ = true;
return;
}
Next();
}
// Increment to the next valid item in the task list. Skip entries for
// which we cannot retrieve a name.
void Next() {
DCHECK(!done_);
task_.Reset();
name_.clear();
if (++task_index_ >= num_tasks_) {
done_ = true;
return;
}
// Note: get_Item uses 1 based indices.
HRESULT hr = tasks_->get_Item(base::win::ScopedVariant(task_index_ + 1),
task_.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get task at index: " << task_index_ << ". "
<< std::hex << hr;
Next();
return;
}
base::win::ScopedBstr task_name_bstr;
hr = task_->get_Name(task_name_bstr.Receive());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get name at index: " << task_index_ << ". "
<< std::hex << hr;
Next();
return;
}
name_ = base::string16(task_name_bstr ? task_name_bstr : L"");
}
// Detach the currently active task and pass ownership to the caller.
// After this method has been called, the -> operator must no longer be
// used.
IRegisteredTask* Detach() { return task_.Detach(); }
// Provide access to the current task.
IRegisteredTask* operator->() const {
IRegisteredTask* result = task_.Get();
DCHECK(result);
return result;
}
const base::string16& name() const { return name_; }
bool done() const { return done_; }
private:
Microsoft::WRL::ComPtr<IRegisteredTaskCollection> tasks_;
Microsoft::WRL::ComPtr<IRegisteredTask> task_;
base::string16 name_;
long task_index_ = -1; // NOLINT, API requires a long.
long num_tasks_ = 0; // NOLINT, API requires a long.
bool done_ = false;
};
// Return the task with |task_name| and false if not found. |task| can be null
// when only interested in task's existence.
bool GetTask(const wchar_t* task_name, IRegisteredTask** task) {
for (TaskIterator it(root_task_folder_.Get()); !it.done(); it.Next()) {
if (::_wcsicmp(it.name().c_str(), task_name) == 0) {
if (task)
*task = it.Detach();
return true;
}
}
return false;
}
// Return the description of the task.
HRESULT GetTaskDescription(IRegisteredTask* task,
base::string16* description) {
DCHECK(task);
DCHECK(description);
base::win::ScopedBstr task_name_bstr;
HRESULT hr = task->get_Name(task_name_bstr.Receive());
base::string16 task_name =
base::string16(task_name_bstr ? task_name_bstr : L"");
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get task name";
}
Microsoft::WRL::ComPtr<ITaskDefinition> task_info;
hr = task->get_Definition(task_info.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get definition for task, " << task_name << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
Microsoft::WRL::ComPtr<IRegistrationInfo> reg_info;
hr = task_info->get_RegistrationInfo(reg_info.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get registration info, " << task_name << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
base::win::ScopedBstr raw_description;
hr = reg_info->get_Description(raw_description.Receive());
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get description, " << task_name << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
*description = base::string16(raw_description ? raw_description : L"");
return ERROR_SUCCESS;
}
// Return all executable actions associated with the given task. Non-exec
// actions are silently ignored.
bool GetTaskExecActions(IRegisteredTask* task,
std::vector<TaskExecAction>* actions) {
DCHECK(task);
DCHECK(actions);
Microsoft::WRL::ComPtr<ITaskDefinition> task_definition;
HRESULT hr = task->get_Definition(task_definition.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get definition of task, " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<IActionCollection> action_collection;
hr = task_definition->get_Actions(action_collection.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get action collection, " << std::hex << hr;
return false;
}
long actions_count = 0; // NOLINT, API requires a long.
hr = action_collection->get_Count(&actions_count);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get number of actions, " << std::hex << hr;
return false;
}
// Find and return as many exec actions as possible in |actions| and return
// false if there were any errors on the way. Note that the indexing of
// actions is 1-based.
bool success = true;
for (long action_index = 1; // NOLINT
action_index <= actions_count; ++action_index) {
Microsoft::WRL::ComPtr<IAction> action;
hr = action_collection->get_Item(action_index, action.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get action at index " << action_index << ", "
<< std::hex << hr;
success = false;
continue;
}
::TASK_ACTION_TYPE action_type;
hr = action->get_Type(&action_type);
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get the type of action at index "
<< action_index << ", " << std::hex << hr;
success = false;
continue;
}
// We only care about exec actions for now. The other types are
// TASK_ACTION_COM_HANDLER, TASK_ACTION_SEND_EMAIL,
// TASK_ACTION_SHOW_MESSAGE. The latter two are marked as deprecated in
// the Task Scheduler's GUI.
if (action_type != ::TASK_ACTION_EXEC)
continue;
Microsoft::WRL::ComPtr<IExecAction> exec_action;
hr = action.CopyTo(exec_action.GetAddressOf());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to query from action, " << std::hex << hr;
success = false;
continue;
}
base::win::ScopedBstr application_path;
hr = exec_action->get_Path(application_path.Receive());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get path from action, " << std::hex << hr;
success = false;
continue;
}
base::win::ScopedBstr working_dir;
hr = exec_action->get_WorkingDirectory(working_dir.Receive());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get working directory for action, "
<< std::hex << hr;
success = false;
continue;
}
base::win::ScopedBstr parameters;
hr = exec_action->get_Arguments(parameters.Receive());
if (FAILED(hr)) {
PLOG(ERROR) << "Failed to get arguments from action of task, "
<< std::hex << hr;
success = false;
continue;
}
actions->push_back(
{base::FilePath(application_path ? application_path : L""),
base::FilePath(working_dir ? working_dir : L""),
base::string16(parameters ? parameters : L"")});
}
return success;
}
// Return the log-on type required for the task's actions to be run.
HRESULT GetTaskLogonType(IRegisteredTask* task, uint32_t* logon_type) {
DCHECK(task);
DCHECK(logon_type);
Microsoft::WRL::ComPtr<ITaskDefinition> task_info;
HRESULT hr = task->get_Definition(task_info.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get definition, " << std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
Microsoft::WRL::ComPtr<IPrincipal> principal;
hr = task_info->get_Principal(principal.GetAddressOf());
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get principal info, " << std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
TASK_LOGON_TYPE raw_logon_type;
hr = principal->get_LogonType(&raw_logon_type);
if (FAILED(hr)) {
LOG(ERROR) << "Failed to get logon type info, " << std::hex << hr << ": "
<< logging::SystemErrorCodeToString(hr);
return hr;
}
switch (raw_logon_type) {
case TASK_LOGON_INTERACTIVE_TOKEN:
*logon_type = LOGON_INTERACTIVE;
break;
case TASK_LOGON_GROUP: // fall-thru
case TASK_LOGON_PASSWORD: // fall-thru
case TASK_LOGON_SERVICE_ACCOUNT:
*logon_type = LOGON_SERVICE;
break;
case TASK_LOGON_S4U:
*logon_type = LOGON_SERVICE | LOGON_S4U;
break;
case TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD:
*logon_type = LOGON_INTERACTIVE | LOGON_SERVICE;
break;
default:
*logon_type = LOGON_UNKNOWN;
break;
}
return ERROR_SUCCESS;
}
static Microsoft::WRL::ComPtr<ITaskService> task_service_;
static Microsoft::WRL::ComPtr<ITaskFolder> root_task_folder_;
DISALLOW_COPY_AND_ASSIGN(TaskSchedulerV2);
};
Microsoft::WRL::ComPtr<ITaskService> TaskSchedulerV2::task_service_;
Microsoft::WRL::ComPtr<ITaskFolder> TaskSchedulerV2::root_task_folder_;
// This wrapper class is needed so that CreateInstance below can instantiate a
// new one on every call, transferring ownership to the caller, and yet, we need
// to always get the same delegate to be called. So the temporary instantiated
// TaskScheduler is this mocked wrapper class, and each instance will delegate
// to the same mock, passed to the constructor.
// TODO(csharp): Simplify this.
class TaskSchedulerMock : public TaskScheduler {
public:
explicit TaskSchedulerMock(TaskScheduler* delegate) : delegate_(delegate) {
DCHECK(delegate);
}
// TaskScheduler.
bool IsTaskRegistered(const wchar_t* task_name) override {
return delegate_->IsTaskRegistered(task_name);
}
bool GetNextTaskRunTime(const wchar_t* task_name,
base::Time* next_run_time) override {
return delegate_->GetNextTaskRunTime(task_name, next_run_time);
}
bool DeleteTask(const wchar_t* task_name) override {
return delegate_->DeleteTask(task_name);
}
bool SetTaskEnabled(const wchar_t* task_name, bool enabled) override {
return delegate_->SetTaskEnabled(task_name, enabled);
}
bool IsTaskEnabled(const wchar_t* task_name) override {
return delegate_->IsTaskEnabled(task_name);
}
bool GetTaskNameList(std::vector<base::string16>* task_names) override {
return delegate_->GetTaskNameList(task_names);
}
bool GetTaskInfo(const wchar_t* task_name, TaskInfo* info) override {
return delegate_->GetTaskInfo(task_name, info);
}
bool RegisterTask(const wchar_t* task_name,
const wchar_t* task_description,
const base::CommandLine& run_command,
TriggerType trigger_type,
bool hidden) override {
return delegate_->RegisterTask(task_name, task_description, run_command,
trigger_type, hidden);
}
private:
TaskScheduler* delegate_;
};
} // namespace
TaskScheduler* TaskScheduler::mock_delegate_ = nullptr;
TaskScheduler::TaskInfo::TaskInfo() = default;
TaskScheduler::TaskInfo::TaskInfo(const TaskScheduler::TaskInfo&) = default;
TaskScheduler::TaskInfo::TaskInfo(TaskScheduler::TaskInfo&&) = default;
TaskScheduler::TaskInfo& TaskScheduler::TaskInfo::operator=(
const TaskScheduler::TaskInfo&) = default;
TaskScheduler::TaskInfo& TaskScheduler::TaskInfo::operator=(
TaskScheduler::TaskInfo&&) = default;
TaskScheduler::TaskInfo::~TaskInfo() = default;
// static.
bool TaskScheduler::Initialize() {
return TaskSchedulerV2::Initialize();
}
// static.
void TaskScheduler::Terminate() {
TaskSchedulerV2::Terminate();
}
// static.
TaskScheduler* TaskScheduler::CreateInstance() {
if (mock_delegate_)
return new TaskSchedulerMock(mock_delegate_);
return new TaskSchedulerV2();
}
TaskScheduler::TaskScheduler() = default;
} // namespace chrome_cleaner