From ff670465be27ec198509de763cf83d8d88c0a91f Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Thu, 20 Jun 2024 16:12:56 +0800 Subject: [PATCH 1/7] update taskschd list --- .gitignore | 2 + src/action_collection.rs | 10 + src/com_macro.rs | 163 ++++++++++ src/exec_action.rs | 73 +++++ src/lib.rs | 38 ++- src/ole_utils.rs | 89 +++++- src/principal.rs | 68 ++++ src/registered_task.rs | 98 ++++++ src/registration_info.rs | 13 + src/repetition_pattern.rs | 30 ++ src/task_definition.rs | 234 ++++++++++++++ src/task_folder.rs | 194 ++++++++++++ src/task_folder_collection.rs | 2 + src/task_service.rs | 93 ++++++ src/task_settings.rs | 29 ++ src/taskschd.rs | 578 ---------------------------------- src/trigger_boot.rs | 41 +++ src/trigger_daily.rs | 70 ++++ src/trigger_event.rs | 51 +++ src/trigger_logon.rs | 44 +++ src/trigger_monthly.rs | 55 ++++ src/trigger_time.rs | 38 +++ src/trigger_weekly.rs | 50 +++ tests/main.rs | 16 +- 24 files changed, 1496 insertions(+), 583 deletions(-) create mode 100644 src/action_collection.rs create mode 100644 src/com_macro.rs create mode 100644 src/exec_action.rs create mode 100644 src/principal.rs create mode 100644 src/registered_task.rs create mode 100644 src/registration_info.rs create mode 100644 src/repetition_pattern.rs create mode 100644 src/task_definition.rs create mode 100644 src/task_folder.rs create mode 100644 src/task_folder_collection.rs create mode 100644 src/task_service.rs create mode 100644 src/task_settings.rs create mode 100644 src/trigger_boot.rs create mode 100644 src/trigger_daily.rs create mode 100644 src/trigger_event.rs create mode 100644 src/trigger_logon.rs create mode 100644 src/trigger_monthly.rs create mode 100644 src/trigger_time.rs create mode 100644 src/trigger_weekly.rs diff --git a/.gitignore b/.gitignore index 9750eb7..00233e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target taskschd.zip +/.cargo +/.idea \ No newline at end of file diff --git a/src/action_collection.rs b/src/action_collection.rs new file mode 100644 index 0000000..ae4d4fd --- /dev/null +++ b/src/action_collection.rs @@ -0,0 +1,10 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{IActionCollection}; +use crate::long_getter; + +pub struct ActionCollection(pub ComRef); + +impl ActionCollection { + long_getter!(IActionCollection::get_Count); + +} \ No newline at end of file diff --git a/src/com_macro.rs b/src/com_macro.rs new file mode 100644 index 0000000..b6e77cc --- /dev/null +++ b/src/com_macro.rs @@ -0,0 +1,163 @@ + +/// put a bool, converting to `VARIANT_BOOL` +#[macro_export] +macro_rules! bool_putter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self, v: bool) -> Result<(), comedy::HResult> { + use crate::ole_utils::IntoVariantBool; + let v = v.into_variant_bool(); + unsafe { + comedy::com_call!(self.0, $interface::$method(v))?; + } + Ok(()) + } + }; +} + +#[macro_export] +macro_rules! bool_getter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self) -> Result { + let mut v = 0; + unsafe { + comedy::com_call!(self.0, $interface::$method(&mut v))?; + } + if v == 0 { + Ok(false) + } else { + Ok(true) + } + } + }; +} + + +/// put a value that is already available as a `BString` +#[macro_export] +macro_rules! bstring_putter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self, v: &crate::ole_utils::BString) -> Result<(), comedy::HResult> { + unsafe { + comedy::com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; + } + Ok(()) + } + }; +} + +#[macro_export] +macro_rules! bstring_getter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self) -> Result { + use std::os::windows::ffi::OsStringExt; + unsafe { + let mut b = std::ptr::null_mut(); + let r = comedy::com_call!(self.0, $interface::$method(&mut b))?; + let s= std::ffi::OsString::from_wide(crate::ole_utils::BString::from_raw(b).ok_or_else(|| comedy::HResult::new(r))?.as_ref()); + Ok(s.to_string_lossy().to_string()) + } + } + }; +} + +#[macro_export] +macro_rules! short_getter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self) -> Result { + unsafe { + let mut i = 0; + comedy::com_call!(self.0, $interface::$method(&mut i))?; + Ok(i) + } + } + }; +} + +#[macro_export] +macro_rules! long_getter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self) -> Result { + unsafe { + let mut i = 0; + comedy::com_call!(self.0, $interface::$method(&mut i))?; + Ok(i) + } + } + }; +} +#[macro_export] +macro_rules! get_repetition { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self) -> Result { + use winapi::um::taskschd::{ITrigger}; + + unsafe { + let mut repeat = std::ptr::null_mut(); + let r = comedy::com_call!(self.0, ITrigger::get_Repetition(&mut repeat))?; + // 将 *mut IRepetitionPattern 转换为 NonNull + if let Some(nonnull) = std::ptr::NonNull::new(repeat) { + // 使用 NonNull 构造 ComRef + let com_ref = comedy::com::ComRef::from_raw(nonnull); + // 使用 ComRef 构造 RepetitionPattern + let mut repetition_pattern = crate::repetition_pattern::RepetitionPattern(com_ref); + Ok(repetition_pattern.to_string()) + // 现在你可以使用 repetition_pattern 了 + } else { + Err(comedy::HResult::new(r)) + } + } + } + }; +} +/// put a `chrono::DateTime` value +#[macro_export] +macro_rules! datetime_putter { + ($interface:ident :: $method:ident) => { + #[allow(non_snake_case)] + pub fn $method(&mut self, v: chrono::DateTime) -> Result<(), comedy::HResult> { + let v = crate::try_to_bstring!(v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))?; + unsafe { + comedy::com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; + } + Ok(()) + } + }; +} + + +/// put a value of type `$ty`, which implements `AsRef` +#[macro_export] +macro_rules! to_os_str_putter { + ($interface:ident :: $method:ident, $ty:ty) => { + #[allow(non_snake_case)] + pub fn $method(&mut self, v: $ty) -> Result<(), comedy::HResult> { + let v = crate::try_to_bstring!(v)?; + unsafe { + comedy::com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; + } + Ok(()) + } + }; +} + +/// put a value of type `$ty`, which implements `ToString` +#[macro_export] +macro_rules! to_string_putter { + ($interface:ident :: $method:ident, $ty:ty) => { + #[allow(non_snake_case)] + pub fn $method(&mut self, v: $ty) -> Result<(), comedy::HResult> { + let v = crate::try_to_bstring!(v.to_string())?; + unsafe { + comedy::com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; + } + Ok(()) + } + }; +} \ No newline at end of file diff --git a/src/exec_action.rs b/src/exec_action.rs new file mode 100644 index 0000000..64c2a1e --- /dev/null +++ b/src/exec_action.rs @@ -0,0 +1,73 @@ +use std::ffi::{OsStr, OsString}; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use comedy::com::ComRef; +use comedy::{com_call, HResult, Win32Error}; +use winapi::shared::winerror::ERROR_BAD_ARGUMENTS; +use winapi::um::taskschd::IExecAction; +use crate::ole_utils::BString; +use crate::{bstring_getter, to_os_str_putter}; + +pub struct ExecAction(pub ComRef); + +impl ExecAction { + bstring_getter!(IExecAction::get_Path); + bstring_getter!(IExecAction::get_Arguments); + + + to_os_str_putter!(IExecAction::put_Path, &Path); + to_os_str_putter!(IExecAction::put_WorkingDirectory, &Path); + + #[allow(non_snake_case)] + pub fn put_Arguments(&mut self, args: &[OsString]) -> Result<(), HResult> { + // based on `make_command_line()` from libstd + // https://github.com/rust-lang/rust/blob/37ff5d388f8c004ca248adb635f1cc84d347eda0/src/libstd/sys/windows/process.rs#L457 + + let mut s = Vec::new(); + + fn append_arg(cmd: &mut Vec, arg: &OsStr) -> Result<(), HResult> { + cmd.push('"' as u16); + + let mut backslashes: usize = 0; + for x in arg.encode_wide() { + if x == 0 { + return Err(HResult::from(Win32Error::new(ERROR_BAD_ARGUMENTS)) + .file_line(file!(), line!())); + } + + if x == '\\' as u16 { + backslashes += 1; + } else { + if x == '"' as u16 { + // Add n+1 backslashes for a total of 2n+1 before internal '"'. + cmd.extend((0..=backslashes).map(|_| '\\' as u16)); + } + backslashes = 0; + } + cmd.push(x); + } + + // Add n backslashes for a total of 2n before ending '"'. + cmd.extend((0..backslashes).map(|_| '\\' as u16)); + cmd.push('"' as u16); + + Ok(()) + } + + for arg in args { + if !s.is_empty() { + s.push(' ' as u16); + } + + // always quote args + append_arg(&mut s, arg.as_ref())?; + } + + let args = BString::from_slice(s).map_err(|e| e.file_line(file!(), line!()))?; + + unsafe { + com_call!(self.0, IExecAction::put_Arguments(args.as_raw_ptr()))?; + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 331868f..874ca0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,38 @@ -pub mod taskschd; +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A partial type-safe interface for Windows Task Scheduler 2.0 +//! +//! This provides structs thinly wrapping the taskschd interfaces, with methods implemented as +//! they've been needed for the update agent. +//! +//! If it turns out that much more flexibility is needed in task definitions, it may be worth +//! generating an XML string and using `ITaskFolder::RegisterTask` or +//! `ITaskDefinition::put_XmlText`, rather than adding more and more boilerplate here. +//! +//! See https://docs.microsoft.com/windows/win32/taskschd/task-scheduler-start-page for +//! Microsoft's documentation. + + + pub mod ole_utils; +pub mod com_macro; +pub mod task_settings; +pub mod task_definition; +pub mod registration_info; +pub mod trigger_daily; +pub mod exec_action; +pub mod registered_task; +pub mod task_folder; +pub mod task_service; +pub mod trigger_boot; +pub mod trigger_time; +pub mod repetition_pattern; +pub mod trigger_weekly; +pub mod trigger_monthly; +pub mod trigger_event; +pub mod trigger_logon; +mod action_collection; +mod principal; +mod task_folder_collection; diff --git a/src/ole_utils.rs b/src/ole_utils.rs index fa5d56b..2ca6331 100644 --- a/src/ole_utils.rs +++ b/src/ole_utils.rs @@ -8,11 +8,16 @@ use std::mem; use std::os::windows::ffi::OsStrExt; use std::ptr::NonNull; use std::slice; +use chrono::NaiveDateTime; use winapi::shared::{winerror, wtypes}; -use winapi::um::{oaidl, oleauto}; +use winapi::um::{oaidl, oleauto, taskschd}; -use comedy::HResult; +use comedy::{HResult, Win32Error}; +use failure::Fail; +use winapi::shared::winerror::{ERROR_ALREADY_EXISTS, ERROR_FILE_NOT_FOUND}; +use winapi::um::oaidl::VARIANT; +use winapi::um::oleauto::VariantInit; #[derive(Debug)] @@ -55,6 +60,18 @@ impl BString { v } } + // 添加一个方法来返回 BString 的长度 + pub fn len(&self) -> u32 { + unsafe { oleauto::SysStringLen(self.0.as_ptr()) } + } + + // 将 BString 转换回 Rust String + pub fn to_string(&self) -> String { + let len = self.len() as usize; + let slice = unsafe { slice::from_raw_parts(self.0.as_ptr(), len) }; + String::from_utf16_lossy(slice) + } + } impl Drop for BString { @@ -119,4 +136,72 @@ impl IntoVariantBool for bool { wtypes::VARIANT_FALSE } } +} + + +pub trait IntoVariantI32 { + fn into_variant_i32(self) -> VARIANT; +} +impl IntoVariantI32 for i32 { + fn into_variant_i32(self) -> VARIANT { + unsafe { + // 初始化 VARIANT + let mut var = mem::zeroed::(); + VariantInit(&mut var); + // 设置 VARIANT 为整数类型 (VT_I4) 并赋值 + (*var.n1.n2_mut()).vt = wtypes::VT_I4 as u16; // 设置 VARTYPE + *(*var.n1.n2_mut()).n3.lVal_mut() = self; + var + } + } +} + + +pub fn hr_is_not_found(hr: &HResult) -> bool { + hr.code() == HResult::from(Win32Error::new(ERROR_FILE_NOT_FOUND)).code() +} + +pub fn hr_is_already_exists(hr: &HResult) -> bool { + hr.code() == HResult::from(Win32Error::new(ERROR_ALREADY_EXISTS)).code() +} + +pub fn date_to_datetime(date: wtypes::DATE) -> String { + const OFFSET_DAYS: i32 = 25569; // 从 "1970-01-01" 到 "1899-12-30" 的天数 + const SECONDS_PER_DAY: i64 = 86_400; // 一天的秒数 + + // 将date转换为Unix时间戳(从 "1970-01-01" 开始) + let timestamp = (date - OFFSET_DAYS as f64) * SECONDS_PER_DAY as f64; + + // 创建一个 'NaiveDateTime' 的实例 + match NaiveDateTime::from_timestamp_opt(timestamp as i64, 0) { + None => {"".to_string()} + Some(val) => {val.to_string()} + } +} + + + +#[derive(Clone, Debug, Fail)] +pub enum ConnectTaskServiceError { + #[fail(display = "{}", _0)] + CreateInstanceFailed(#[fail(cause)] HResult), + #[fail(display = "Access is denied to connect to the Task Scheduler service")] + AccessDenied(#[fail(cause)] HResult), + #[fail(display = "The Task Scheduler service is not running")] + ServiceNotRunning(#[fail(cause)] HResult), + #[fail(display = "{}", _0)] + ConnectFailed(#[fail(cause)] HResult), +} + + + + + +#[derive(Clone, Copy, Debug)] +#[repr(u32)] +pub enum InstancesPolicy { + Parallel = taskschd::TASK_INSTANCES_PARALLEL, + Queue = taskschd::TASK_INSTANCES_QUEUE, + IgnoreNew = taskschd::TASK_INSTANCES_IGNORE_NEW, + StopExisting = taskschd::TASK_INSTANCES_STOP_EXISTING, } \ No newline at end of file diff --git a/src/principal.rs b/src/principal.rs new file mode 100644 index 0000000..4e345bb --- /dev/null +++ b/src/principal.rs @@ -0,0 +1,68 @@ +use comedy::com::ComRef; +use winapi::um::taskschd; +use winapi::um::taskschd::{IPrincipal, TASK_LOGON_TYPE, TASK_RUNLEVEL}; +use crate::{bstring_getter, short_getter}; + +pub struct Principal(pub ComRef); + +impl Principal { + bstring_getter!(IPrincipal::get_DisplayName); + bstring_getter!(IPrincipal::get_UserId); + + + #[allow(non_snake_case)] + pub fn get_logon_type(&mut self) -> Result { + unsafe { + let mut i = 0; + comedy::com_call!(self.0, IPrincipal::get_LogonType(&mut i))?; + Ok(i) + } + } + + #[allow(non_snake_case)] + pub fn get_run_level(&mut self) -> Result { + unsafe { + let mut i = 0; + comedy::com_call!(self.0, IPrincipal::get_RunLevel(&mut i))?; + Ok(i) + } + } + + pub fn to_string(&mut self) -> String { + let name = match self.get_DisplayName() { + Ok(val) => { format!(" [name] {}", val)} + Err(_) => {"".into()} + }; + let logon_type = match self.get_logon_type() { + Ok(val) => { + let logon_type = match val { + taskschd::TASK_LOGON_NONE => "none", + taskschd::TASK_LOGON_PASSWORD => "password", + taskschd::TASK_LOGON_S4U => "s4u", + taskschd::TASK_LOGON_INTERACTIVE_TOKEN => "interactive_token", + taskschd::TASK_LOGON_GROUP => "group", + taskschd::TASK_LOGON_SERVICE_ACCOUNT => "service_account", + taskschd::TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD => "interactive_token_or_password", + _ => "", + }; + format!(" [logon_type] {}", logon_type) + } + Err(_) => {"".into()} + }; + let run_level = match self.get_run_level() { + Ok(val) => { + let logon_type = match val { + taskschd::TASK_RUNLEVEL_LUA => "least", + taskschd::TASK_RUNLEVEL_HIGHEST => "highest", + _ => "", + }; + format!(" [run_level] {}", logon_type) + } + Err(_) => {"".into()} + }; + format!("[PRINCIPAL]{}{}{}",name,logon_type,run_level) + + } + + +} \ No newline at end of file diff --git a/src/registered_task.rs b/src/registered_task.rs new file mode 100644 index 0000000..98182c3 --- /dev/null +++ b/src/registered_task.rs @@ -0,0 +1,98 @@ +use std::ffi::{OsStr}; +use comedy::com::ComRef; +use comedy::{com_call, com_call_getter, HResult}; +use winapi::ctypes::c_double; +use winapi::shared::wtypes::DATE; +use winapi::um::taskschd; +use winapi::um::taskschd::{IRegisteredTask, IRunningTask, TASK_STATE}; +use crate::ole_utils::{BString, date_to_datetime, OptionBstringExt}; +use crate::task_definition::TaskDefinition; +use crate::{bstring_getter, long_getter, try_to_bstring}; +pub struct RegisteredTask(pub ComRef); + +impl RegisteredTask { + bstring_getter!(IRegisteredTask::get_Name); + bstring_getter!(IRegisteredTask::get_Path); + long_getter!(IRegisteredTask::get_LastTaskResult); + + + pub fn get_last_runtime(&mut self) -> Result { + unsafe { + let mut date: DATE = 0 as DATE; + com_call!(self.0, IRegisteredTask::get_LastRunTime(&mut date))?; + Ok(date_to_datetime(date)) + + } + } + + pub fn get_next_runtime(&mut self) -> Result { + unsafe { + let mut date: DATE = 0 as DATE; + com_call!(self.0, IRegisteredTask::get_NextRunTime(&mut date))?; + Ok(date_to_datetime(date)) + } + } + + pub fn set_sd(&mut self, sddl: &BString) -> Result<(), HResult> { + unsafe { + com_call!( + self.0, + IRegisteredTask::SetSecurityDescriptor( + sddl.as_raw_ptr(), + 0, // flags (none) + ) + )?; + } + Ok(()) + } + // #[allow(non_snake_case)] + // pub fn com_getter(&mut self) -> Result { + // unsafe { + // let mut b = ptr::null_mut(); + // let r = com_call!(self.0, IRegisteredTask::get_Name(&mut b))?; + // let s= OsString::from_wide(BString::from_raw(b).ok_or_else(|| HResult::new(r))?.as_ref()); + // Ok(s.to_string_lossy().to_string()) + // } + // } + #[allow(non_snake_case)] + pub fn get_state_string(&mut self) -> Result { + unsafe { + let mut b = 0; + com_call!(self.0, IRegisteredTask::get_State(&mut b))?; + let msg = match b { + taskschd::TASK_STATE_DISABLED => "disabled", + taskschd::TASK_STATE_QUEUED => "queue", + taskschd::TASK_STATE_READY => "ready", + taskschd::TASK_STATE_RUNNING => "running", + _ => "unknown", + }; + Ok(msg.to_string()) + } + } + pub fn get_definition(&mut self) -> Result { + unsafe { com_call_getter!(|tc| self.0, IRegisteredTask::get_Definition(tc)) } + .map(TaskDefinition) + } + + pub fn run(&self) -> Result<(), HResult> { + self.run_impl(Option::<&OsStr>::None)?; + Ok(()) + } + + fn run_impl(&self, param: Option>) -> Result, HResult> { + // Running with parameters isn't currently exposed. + // param can also be an array of strings, but that is not supported here + let param = if let Some(p) = param { + Some(try_to_bstring!(p)?) + } else { + None + }; + + unsafe { + com_call_getter!( + |rt| self.0, + IRegisteredTask::Run(param.as_ref().as_raw_variant(), rt) + ) + } + } +} \ No newline at end of file diff --git a/src/registration_info.rs b/src/registration_info.rs new file mode 100644 index 0000000..a303f81 --- /dev/null +++ b/src/registration_info.rs @@ -0,0 +1,13 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::IRegistrationInfo; +use crate::{bstring_getter, bstring_putter}; + +pub struct RegistrationInfo(pub ComRef); + +impl RegistrationInfo { + bstring_putter!(IRegistrationInfo::put_Author); + bstring_putter!(IRegistrationInfo::put_Description); + bstring_getter!(IRegistrationInfo::get_Author); + bstring_getter!(IRegistrationInfo::get_Description); + +} \ No newline at end of file diff --git a/src/repetition_pattern.rs b/src/repetition_pattern.rs new file mode 100644 index 0000000..ad3c4ba --- /dev/null +++ b/src/repetition_pattern.rs @@ -0,0 +1,30 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::IRepetitionPattern; +use crate::{bool_getter, bstring_getter}; + +pub struct RepetitionPattern(pub ComRef); + +impl RepetitionPattern { + bstring_getter!(IRepetitionPattern::get_Interval); + bstring_getter!(IRepetitionPattern::get_Duration); + bool_getter!(IRepetitionPattern::get_StopAtDurationEnd); + + pub fn to_string(&mut self) -> String { + let interval = match self.get_Interval() { + Ok(val) => { format!(" [interval] {}", val.to_string())} + Err(_) => {"".into()} + }; + let duration = match self.get_Duration() { + Ok(val) => { format!(" [duration] {}", val.to_string())} + Err(_) => {"".into()} + }; + let enabled = match self.get_StopAtDurationEnd() { + Ok(val) => { if val {" [STOP AT END]".to_string()} else {"".to_string()} }, + Err(_) => {"".to_string()} + }; + if interval.is_empty() && duration.is_empty() && enabled.is_empty() { + return "".into() + } + format!("<[REPEAT]{}{}{}>",interval,duration,enabled) + } +} diff --git a/src/task_definition.rs b/src/task_definition.rs new file mode 100644 index 0000000..52d89d1 --- /dev/null +++ b/src/task_definition.rs @@ -0,0 +1,234 @@ +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use std::ptr; +use comedy::com::ComRef; +use comedy::{com_call, com_call_getter, HResult}; +use winapi::shared::ntdef::LONG; +use winapi::um::taskschd; +use winapi::um::taskschd::{IAction, ITaskDefinition, ITaskFolder, ITrigger, ITriggerCollection}; +use crate::action_collection::ActionCollection; +use crate::trigger_boot::BootTrigger; +use crate::trigger_daily::DailyTrigger; +use crate::exec_action::ExecAction; +use crate::ole_utils::{BString, empty_variant, OptionBstringExt}; +use crate::principal::Principal; +use crate::registered_task::RegisteredTask; +use crate::registration_info::RegistrationInfo; +use crate::task_folder::TaskFolder; +use crate::task_settings::TaskSettings; +use crate::trigger_event::EventTrigger; +use crate::trigger_logon::LogonTrigger; +use crate::trigger_monthly::MonthlyTrigger; +use crate::trigger_time::TimeTrigger; +use crate::trigger_weekly::WeeklyTrigger; + +pub struct TaskDefinition(pub ComRef); + +impl TaskDefinition { + pub fn get_settings(&mut self) -> Result { + unsafe { com_call_getter!(|s| self.0, ITaskDefinition::get_Settings(s)) }.map(TaskSettings) + } + + pub fn get_registration_info(&mut self) -> Result { + unsafe { com_call_getter!(|ri| self.0, ITaskDefinition::get_RegistrationInfo(ri)) } + .map(RegistrationInfo) + } + + pub fn get_principal(&mut self) -> Result { + unsafe { com_call_getter!(|ri| self.0, ITaskDefinition::get_Principal(ri)) } + .map(Principal) + } + + unsafe fn add_action( + &mut self, + action_type: taskschd::TASK_ACTION_TYPE, + ) -> Result, HResult> { + use self::taskschd::IActionCollection; + + let actions = com_call_getter!(|ac| self.0, ITaskDefinition::get_Actions(ac))?; + let action = com_call_getter!(|a| actions, IActionCollection::Create(action_type, a))?; + action.cast() + } + + pub fn get_exec_actions(&mut self) -> Result, HResult> { + use self::taskschd::IActionCollection; + let mut exec_actions = Vec::::new(); + unsafe { + let mut actions = com_call_getter!(|ac| self.0, ITaskDefinition::get_Actions(ac)).map(ActionCollection)?; + let count = actions.get_Count()?; + for i in 1..=count { + let action = com_call_getter!(|a| actions.0, IActionCollection::get_Item(i, a))?; + let mut action_type = 0; + com_call!(action, IAction::get_Type(&mut action_type))?; + if action_type == taskschd::TASK_ACTION_EXEC { + let mut action_impl = ExecAction(action.cast()?); + let args = action_impl.get_Arguments().unwrap_or_default(); + let msg = format!("{} {}", action_impl.get_Path()?, args); + exec_actions.push(msg) + + } + } + Ok(exec_actions) + + } + } + + pub fn add_exec_action(&mut self) -> Result { + unsafe { self.add_action(taskschd::TASK_ACTION_EXEC) }.map(ExecAction) + } + + pub unsafe fn add_trigger( + &mut self, + trigger_type: taskschd::TASK_TRIGGER_TYPE2, + ) -> Result, HResult> { + let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?; + let trigger = com_call_getter!(|t| triggers, ITriggerCollection::Create(trigger_type, t))?; + trigger.cast() + } + + pub fn add_daily_trigger(&mut self) -> Result { + unsafe { self.add_trigger(taskschd::TASK_TRIGGER_DAILY) }.map(DailyTrigger) + } + + pub fn add_boot_trigger(&mut self) -> Result { + unsafe { self.add_trigger(taskschd::TASK_TRIGGER_BOOT) }.map(BootTrigger) + } + + pub fn get_all_triggers(&mut self) -> Result, HResult> { + let mut found_triggers = Vec::new(); + + unsafe { + let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?; + let mut count = 0; + com_call!(triggers, ITriggerCollection::get_Count(&mut count))?; + + // Item indexes start at 1 + for i in 1..=count { + let trigger = com_call_getter!(|t| triggers, ITriggerCollection::get_Item(i, t))?; + + let mut trigger_type = 0; + com_call!(trigger, ITrigger::get_Type(&mut trigger_type))?; + // println!("trigger {}", trigger_type); + let msg = match trigger_type { + taskschd::TASK_TRIGGER_EVENT => { + let mut trigger_impl = EventTrigger(trigger.cast()?); + trigger_impl.to_string() + } + taskschd::TASK_TRIGGER_TIME => { + let mut trigger_impl = TimeTrigger(trigger.cast()?); + trigger_impl.to_string() + }, + taskschd::TASK_TRIGGER_DAILY=> { + let mut trigger_impl = DailyTrigger(trigger.cast()?); + trigger_impl.to_string() + }, + taskschd::TASK_TRIGGER_WEEKLY=> { + let mut trigger_impl = WeeklyTrigger(trigger.cast()?); + trigger_impl.to_string() + }, + taskschd::TASK_TRIGGER_MONTHLY => { + let mut trigger_impl = MonthlyTrigger(trigger.cast()?); + trigger_impl.to_string() + } + taskschd::TASK_TRIGGER_BOOT => { + let mut trigger_impl = BootTrigger(trigger.cast()?); + trigger_impl.to_string() + }, + taskschd::TASK_TRIGGER_LOGON => { + let mut trigger_impl = LogonTrigger(trigger.cast()?); + trigger_impl.to_string() + }, + _ => { format!("unknown type:{}",trigger_type)} + }; + found_triggers.push(msg); + } + } + + Ok(found_triggers) + } + pub fn get_daily_triggers(&mut self) -> Result, HResult> { + let mut found_triggers = Vec::new(); + + unsafe { + let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?; + let mut count = 0; + com_call!(triggers, ITriggerCollection::get_Count(&mut count))?; + + // Item indexes start at 1 + for i in 1..=count { + let trigger = com_call_getter!(|t| triggers, ITriggerCollection::get_Item(i, t))?; + + let mut trigger_type = 0; + com_call!(trigger, ITrigger::get_Type(&mut trigger_type))?; + println!("trigger type: {}", trigger_type); + + if trigger_type == taskschd::TASK_TRIGGER_DAILY { + found_triggers.push(DailyTrigger(trigger.cast()?)) + } + } + } + + Ok(found_triggers) + } + + pub fn create( + &mut self, + folder: &mut TaskFolder, + task_name: &BString, + service_account: Option<&BString>, + ) -> Result { + self.register_impl(folder, task_name, service_account, taskschd::TASK_CREATE) + } + + fn register_impl( + &mut self, + folder: &mut TaskFolder, + task_name: &BString, + service_account: Option<&BString>, + creation_flags: taskschd::TASK_CREATION, + ) -> Result { + let task_definition = self.0.as_raw_ptr(); + + let password = empty_variant(); + + let logon_type = if service_account.is_some() { + taskschd::TASK_LOGON_SERVICE_ACCOUNT + } else { + taskschd::TASK_LOGON_INTERACTIVE_TOKEN + }; + + let sddl = empty_variant(); + + let registered_task = unsafe { + com_call_getter!( + |rt| folder.0, + ITaskFolder::RegisterTaskDefinition( + task_name.as_raw_ptr(), + task_definition, + creation_flags as LONG, + service_account.as_raw_variant(), + password, + logon_type, + sddl, + rt, + ) + )? + }; + + Ok(RegisteredTask(registered_task)) + } + + pub fn get_xml(task_definition: &ComRef) -> Result { + unsafe { + let mut xml = ptr::null_mut(); + com_call!(task_definition, ITaskDefinition::get_XmlText(&mut xml)) + .map_err(|e| format!("{}", e))?; + + Ok(OsString::from_wide( + BString::from_raw(xml) + .ok_or_else(|| "null xml".to_string())? + .as_ref(), + )) + } + } +} \ No newline at end of file diff --git a/src/task_folder.rs b/src/task_folder.rs new file mode 100644 index 0000000..2bda752 --- /dev/null +++ b/src/task_folder.rs @@ -0,0 +1,194 @@ +use comedy::com::ComRef; +use comedy::{com_call, com_call_getter, HResult}; +use comedy::error::{ErrorAndSource, HResultInner}; +use winapi::shared::ntdef::LONG; +use winapi::um::taskschd; +use winapi::um::taskschd::{ITaskFolder, ITaskFolderCollection}; +use crate::bstring_getter; +use crate::ole_utils::{BString, empty_variant, IntoVariantI32}; +use crate::registered_task::RegisteredTask; +use crate::registration_info::RegistrationInfo; +use crate::task_definition::TaskDefinition; + + +pub struct TaskInfo { + pub name: String, + pub path: String, + pub state: String, + pub description: String, + pub triggers: String, + + pub exec_actions: String, + + pub last_runtime: String, + pub next_runtime: String, + pub last_task_result: String, + + pub author: String, + pub principal: String, + pub xml: String, + +} + +pub struct TaskFolder(pub(crate) ComRef); + +impl TaskFolder { + bstring_getter!(ITaskFolder::get_Name); + bstring_getter!(ITaskFolder::get_Path); + + pub fn get_task(&mut self, task_name: &BString) -> Result { + unsafe { + com_call_getter!( + |task| self.0, + ITaskFolder::GetTask(task_name.as_raw_ptr(), task) + ) + } + .map(RegisteredTask) + } + + pub fn get_all_folders(&mut self) -> Result, HResult>{ + let mut found_folders = Vec::::with_capacity(1); + unsafe { + let mut folders = std::ptr::null_mut(); + com_call!(self.0, ITaskFolder::GetFolders(0, &mut folders))?; + let mut count = 0; + com_call!(folders, ITaskFolderCollection::get_Count(&mut count))?; + for i in 1..=count { + let mut folder = com_call_getter!(|t| folders, ITaskFolderCollection::get_Item(i.into_variant_i32(), t)).map(TaskFolder)?; + if let Ok(child_folders) = folder.get_all_folders() { + found_folders.extend(child_folders); + } + found_folders.insert(0, folder); + } + } + Ok(found_folders) + } + + pub fn get_task_count(&mut self, include_hidden: bool) -> Result { + use self::taskschd::IRegisteredTaskCollection; + + let flags = if include_hidden { + taskschd::TASK_ENUM_HIDDEN + } else { + 0 + }; + + unsafe { + let tasks = com_call_getter!(|t| self.0, ITaskFolder::GetTasks(flags as LONG, t))?; + + let mut count = 0; + com_call!(tasks, IRegisteredTaskCollection::get_Count(&mut count))?; + + Ok(count) + } + } + + pub fn get_tasks(&mut self, include_hidden: bool) -> Result, HResult> { + use self::taskschd::IRegisteredTaskCollection; + + let flags = if include_hidden { + taskschd::TASK_ENUM_HIDDEN + } else { + 0 + }; + let mut task_infos = Vec::::new(); + + + unsafe { + let tasks = com_call_getter!(|t| self.0, ITaskFolder::GetTasks(flags as LONG, t))?; + let mut count = 0; + com_call!(tasks, IRegisteredTaskCollection::get_Count(&mut count))?; + for i in 1..=count { + let mut task = match com_call_getter!(|t| tasks, IRegisteredTaskCollection::get_Item(i.into_variant_i32(), t)).map(RegisteredTask) { + Ok(val) => {val} + Err(_) => {continue} + }; + let mut definition = match task.get_definition() { + Ok(val) => {val} + Err(_) => {continue} + }; + let mut registration_info = match definition.get_registration_info() { + Ok(val) => {val} + Err(_) => {continue} + }; + let mut task_info = TaskInfo{ + name: task.get_Name().unwrap_or_default(), + path: task.get_Path().unwrap_or_default(), + state: task.get_state_string().unwrap_or_default(), + author: registration_info.get_Author().unwrap_or_default(), + description: registration_info.get_Description().unwrap_or_default(), + principal: "".to_string(), + triggers: "".to_string(), + exec_actions: "".to_string(), + last_runtime: task.get_last_runtime().unwrap_or_default(), + next_runtime: task.get_next_runtime().unwrap_or_default(), + xml: "".to_string(), + last_task_result: "".to_string(), + }; + + if let Ok(result) = task.get_LastTaskResult(){ + task_info.last_task_result = format!("0x{:X}",result) + } + if let Ok(mut principal) = definition.get_principal() { + task_info.principal = principal.to_string(); + + } + let xml = TaskDefinition::get_xml(&definition.0).unwrap_or_default(); + task_info.xml = xml.to_string_lossy().to_string(); + + + + + if let Ok(triggers) = definition.get_all_triggers() { + task_info.triggers = triggers.join("\n") + } + if let Ok(exec_actions) = definition.get_exec_actions() { + task_info.exec_actions = exec_actions.join("\n") + } + task_infos.push(task_info) + } + + Ok(task_infos) + } + } + + pub fn create_folder(&mut self, path: &BString) -> Result { + let sddl = empty_variant(); + unsafe { + com_call_getter!( + |folder| self.0, + ITaskFolder::CreateFolder(path.as_raw_ptr(), sddl, folder) + ) + } + .map(TaskFolder) + } + + pub fn delete_folder(&mut self, path: &BString) -> Result<(), HResult> { + unsafe { + com_call!( + self.0, + ITaskFolder::DeleteFolder( + path.as_raw_ptr(), + 0, // flags (reserved) + ) + )?; + } + + Ok(()) + } + + pub fn delete_task(&mut self, task_name: &BString) -> Result<(), HResult> { + unsafe { + com_call!( + self.0, + ITaskFolder::DeleteTask( + task_name.as_raw_ptr(), + 0, // flags (reserved) + ) + )?; + } + + Ok(()) + } +} + diff --git a/src/task_folder_collection.rs b/src/task_folder_collection.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/task_folder_collection.rs @@ -0,0 +1,2 @@ + + diff --git a/src/task_service.rs b/src/task_service.rs new file mode 100644 index 0000000..727295e --- /dev/null +++ b/src/task_service.rs @@ -0,0 +1,93 @@ +use comedy::com::{ComRef, create_instance_inproc_server, INIT_MTA}; +use comedy::{com_call, com_call_getter, HResult}; +use winapi::shared::winerror::{E_ACCESSDENIED, SCHED_E_SERVICE_NOT_RUNNING}; +use winapi::um::taskschd; +use winapi::um::taskschd::ITaskService; +use crate::ole_utils::{BString, ConnectTaskServiceError, empty_variant}; +use crate::task_definition::TaskDefinition; +use crate::task_folder::{TaskFolder, TaskInfo}; +use crate::try_to_bstring; + +pub struct TaskService(pub ComRef); + +impl TaskService { + pub fn connect_local() -> Result { + use self::ConnectTaskServiceError::*; + + INIT_MTA.with(|com| { + let _com = match com { + Err(e) => return Err(e.clone()), + Ok(ref _com) => _com, + }; + //do_com_stuff(com); + Ok(()) + }).map_err(CreateInstanceFailed)?; + + let task_service = create_instance_inproc_server::() + .map_err(CreateInstanceFailed)?; + + // Connect to local service with no credentials. + unsafe { + com_call!( + task_service, + ITaskService::Connect( + empty_variant(), + empty_variant(), + empty_variant(), + empty_variant() + ) + ) + } + .map_err(|hr| match hr.code() { + E_ACCESSDENIED => AccessDenied(hr), + SCHED_E_SERVICE_NOT_RUNNING => ServiceNotRunning(hr), + _ => ConnectFailed(hr), + })?; + + Ok(TaskService(task_service)) + } + + pub fn get_root_folder(&mut self) -> Result { + self.get_folder(&try_to_bstring!("\\")?) + } + + pub fn get_all_tasks(&mut self) -> Result, HResult> { + let mut task_infos = Vec::::new(); + let mut root = self.get_root_folder()?; + let mut folders = root.get_all_folders()?; + folders.insert(0, root); + for mut folder in folders { + if let Ok(infos) = folder.get_tasks(true) { + if infos.is_empty() { + continue + } + task_infos.extend(infos); + } + }; + Ok(task_infos) + + } + + pub fn get_folder(&mut self, path: &BString) -> Result { + unsafe { + com_call_getter!( + |folder| self.0, + ITaskService::GetFolder(path.as_raw_ptr(), folder) + ) + } + .map(TaskFolder) + } + + pub fn new_task_definition(&mut self) -> Result { + unsafe { + com_call_getter!( + |task_def| self.0, + ITaskService::NewTask( + 0, // flags (reserved) + task_def, + ) + ) + } + .map(TaskDefinition) + } +} diff --git a/src/task_settings.rs b/src/task_settings.rs new file mode 100644 index 0000000..68a7dc4 --- /dev/null +++ b/src/task_settings.rs @@ -0,0 +1,29 @@ +use comedy::com::ComRef; +use comedy::{com_call, HResult}; +use winapi::um::taskschd::ITaskSettings; +use crate::{bool_putter, to_string_putter}; +use crate::ole_utils::InstancesPolicy; + +pub struct TaskSettings(pub ComRef); + +impl TaskSettings { + bool_putter!(ITaskSettings::put_AllowDemandStart); + bool_putter!(ITaskSettings::put_DisallowStartIfOnBatteries); + to_string_putter!(ITaskSettings::put_ExecutionTimeLimit, chrono::Duration); + bool_putter!(ITaskSettings::put_Hidden); + + #[allow(non_snake_case)] + pub fn put_MultipleInstances(&mut self, v: InstancesPolicy) -> Result<(), HResult> { + unsafe { + com_call!(self.0, ITaskSettings::put_MultipleInstances(v as u32))?; + } + Ok(()) + } + + bool_putter!(ITaskSettings::put_RunOnlyIfIdle); + bool_putter!(ITaskSettings::put_RunOnlyIfNetworkAvailable); + bool_putter!(ITaskSettings::put_StartWhenAvailable); + bool_putter!(ITaskSettings::put_StopIfGoingOnBatteries); + bool_putter!(ITaskSettings::put_Enabled); + bool_putter!(ITaskSettings::put_WakeToRun); +} \ No newline at end of file diff --git a/src/taskschd.rs b/src/taskschd.rs index 55ced45..fd40910 100644 --- a/src/taskschd.rs +++ b/src/taskschd.rs @@ -1,582 +1,4 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! A partial type-safe interface for Windows Task Scheduler 2.0 -//! -//! This provides structs thinly wrapping the taskschd interfaces, with methods implemented as -//! they've been needed for the update agent. -//! -//! If it turns out that much more flexibility is needed in task definitions, it may be worth -//! generating an XML string and using `ITaskFolder::RegisterTask` or -//! `ITaskDefinition::put_XmlText`, rather than adding more and more boilerplate here. -//! -//! See https://docs.microsoft.com/windows/win32/taskschd/task-scheduler-start-page for -//! Microsoft's documentation. -use std::ffi::{OsStr, OsString}; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::path::Path; -use std::ptr; -use comedy::com::{create_instance_inproc_server, INIT_MTA, ComRef}; -use comedy::error::{HResult, Win32Error}; -use comedy::{com_call, com_call_getter}; -use failure::Fail; -use crate::ole_utils::{empty_variant, BString, IntoVariantBool, OptionBstringExt}; -use crate::try_to_bstring; - -use winapi::shared::{ - ntdef::{LONG, SHORT}, - winerror::{ - ERROR_ALREADY_EXISTS, ERROR_BAD_ARGUMENTS, ERROR_FILE_NOT_FOUND, E_ACCESSDENIED, - SCHED_E_SERVICE_NOT_RUNNING, - }, -}; - -use winapi::um::taskschd::{ - self, IDailyTrigger, IExecAction, IRegisteredTask, IRegistrationInfo, IRunningTask, - ITaskDefinition, ITaskFolder, ITaskService, ITaskSettings, ITrigger, ITriggerCollection, - IBootTrigger -}; - -pub fn hr_is_not_found(hr: &HResult) -> bool { - hr.code() == HResult::from(Win32Error::new(ERROR_FILE_NOT_FOUND)).code() -} - -pub fn hr_is_already_exists(hr: &HResult) -> bool { - hr.code() == HResult::from(Win32Error::new(ERROR_ALREADY_EXISTS)).code() -} - -/// put a bool, converting to `VARIANT_BOOL` -macro_rules! bool_putter { - ($interface:ident :: $method:ident) => { - #[allow(non_snake_case)] - pub fn $method(&mut self, v: bool) -> Result<(), HResult> { - let v = v.into_variant_bool(); - unsafe { - com_call!(self.0, $interface::$method(v))?; - } - Ok(()) - } - }; -} - -/// put a value that is already available as a `BString` -macro_rules! bstring_putter { - ($interface:ident :: $method:ident) => { - #[allow(non_snake_case)] - pub fn $method(&mut self, v: &BString) -> Result<(), HResult> { - unsafe { - com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; - } - Ok(()) - } - }; -} - -/// put a `chrono::DateTime` value -macro_rules! datetime_putter { - ($interface:ident :: $method:ident) => { - #[allow(non_snake_case)] - pub fn $method(&mut self, v: chrono::DateTime) -> Result<(), HResult> { - let v = try_to_bstring!(v.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))?; - unsafe { - com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; - } - Ok(()) - } - }; -} - -/// put a value of type `$ty`, which implements `AsRef` -macro_rules! to_os_str_putter { - ($interface:ident :: $method:ident, $ty:ty) => { - #[allow(non_snake_case)] - pub fn $method(&mut self, v: $ty) -> Result<(), HResult> { - let v = try_to_bstring!(v)?; - unsafe { - com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; - } - Ok(()) - } - }; -} - -/// put a value of type `$ty`, which implements `ToString` -macro_rules! to_string_putter { - ($interface:ident :: $method:ident, $ty:ty) => { - #[allow(non_snake_case)] - pub fn $method(&mut self, v: $ty) -> Result<(), HResult> { - let v = try_to_bstring!(v.to_string())?; - unsafe { - com_call!(self.0, $interface::$method(v.as_raw_ptr()))?; - } - Ok(()) - } - }; -} - -pub struct TaskService(ComRef); - -impl TaskService { - pub fn connect_local() -> Result { - use self::ConnectTaskServiceError::*; - - INIT_MTA.with(|com| { - let _com = match com { - Err(e) => return Err(e.clone()), - Ok(ref _com) => _com, - }; - //do_com_stuff(com); - Ok(()) - }).map_err(CreateInstanceFailed)?; - - let task_service = create_instance_inproc_server::() - .map_err(CreateInstanceFailed)?; - - // Connect to local service with no credentials. - unsafe { - com_call!( - task_service, - ITaskService::Connect( - empty_variant(), - empty_variant(), - empty_variant(), - empty_variant() - ) - ) - } - .map_err(|hr| match hr.code() { - E_ACCESSDENIED => AccessDenied(hr), - SCHED_E_SERVICE_NOT_RUNNING => ServiceNotRunning(hr), - _ => ConnectFailed(hr), - })?; - - Ok(TaskService(task_service)) - } - - pub fn get_root_folder(&mut self) -> Result { - self.get_folder(&try_to_bstring!("\\")?) - } - - pub fn get_folder(&mut self, path: &BString) -> Result { - unsafe { - com_call_getter!( - |folder| self.0, - ITaskService::GetFolder(path.as_raw_ptr(), folder) - ) - } - .map(TaskFolder) - } - - pub fn new_task_definition(&mut self) -> Result { - unsafe { - com_call_getter!( - |task_def| self.0, - ITaskService::NewTask( - 0, // flags (reserved) - task_def, - ) - ) - } - .map(TaskDefinition) - } -} - -#[derive(Clone, Debug, Fail)] -pub enum ConnectTaskServiceError { - #[fail(display = "{}", _0)] - CreateInstanceFailed(#[fail(cause)] HResult), - #[fail(display = "Access is denied to connect to the Task Scheduler service")] - AccessDenied(#[fail(cause)] HResult), - #[fail(display = "The Task Scheduler service is not running")] - ServiceNotRunning(#[fail(cause)] HResult), - #[fail(display = "{}", _0)] - ConnectFailed(#[fail(cause)] HResult), -} - -pub struct TaskFolder(ComRef); - -impl TaskFolder { - pub fn get_task(&mut self, task_name: &BString) -> Result { - unsafe { - com_call_getter!( - |task| self.0, - ITaskFolder::GetTask(task_name.as_raw_ptr(), task) - ) - } - .map(RegisteredTask) - } - - pub fn get_task_count(&mut self, include_hidden: bool) -> Result { - use self::taskschd::IRegisteredTaskCollection; - - let flags = if include_hidden { - taskschd::TASK_ENUM_HIDDEN - } else { - 0 - }; - - unsafe { - let tasks = com_call_getter!(|t| self.0, ITaskFolder::GetTasks(flags as LONG, t))?; - - let mut count = 0; - com_call!(tasks, IRegisteredTaskCollection::get_Count(&mut count))?; - - Ok(count) - } - } - - pub fn create_folder(&mut self, path: &BString) -> Result { - let sddl = empty_variant(); - unsafe { - com_call_getter!( - |folder| self.0, - ITaskFolder::CreateFolder(path.as_raw_ptr(), sddl, folder) - ) - } - .map(TaskFolder) - } - - pub fn delete_folder(&mut self, path: &BString) -> Result<(), HResult> { - unsafe { - com_call!( - self.0, - ITaskFolder::DeleteFolder( - path.as_raw_ptr(), - 0, // flags (reserved) - ) - )?; - } - - Ok(()) - } - - pub fn delete_task(&mut self, task_name: &BString) -> Result<(), HResult> { - unsafe { - com_call!( - self.0, - ITaskFolder::DeleteTask( - task_name.as_raw_ptr(), - 0, // flags (reserved) - ) - )?; - } - - Ok(()) - } -} - -pub struct TaskDefinition(ComRef); - -impl TaskDefinition { - pub fn get_settings(&mut self) -> Result { - unsafe { com_call_getter!(|s| self.0, ITaskDefinition::get_Settings(s)) }.map(TaskSettings) - } - - pub fn get_registration_info(&mut self) -> Result { - unsafe { com_call_getter!(|ri| self.0, ITaskDefinition::get_RegistrationInfo(ri)) } - .map(RegistrationInfo) - } - - unsafe fn add_action( - &mut self, - action_type: taskschd::TASK_ACTION_TYPE, - ) -> Result, HResult> { - use self::taskschd::IActionCollection; - - let actions = com_call_getter!(|ac| self.0, ITaskDefinition::get_Actions(ac))?; - let action = com_call_getter!(|a| actions, IActionCollection::Create(action_type, a))?; - action.cast() - } - - pub fn add_exec_action(&mut self) -> Result { - unsafe { self.add_action(taskschd::TASK_ACTION_EXEC) }.map(ExecAction) - } - - pub unsafe fn add_trigger( - &mut self, - trigger_type: taskschd::TASK_TRIGGER_TYPE2, - ) -> Result, HResult> { - let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?; - let trigger = com_call_getter!(|t| triggers, ITriggerCollection::Create(trigger_type, t))?; - trigger.cast() - } - - pub fn add_daily_trigger(&mut self) -> Result { - unsafe { self.add_trigger(taskschd::TASK_TRIGGER_DAILY) }.map(DailyTrigger) - } - - pub fn add_boot_trigger(&mut self) -> Result { - unsafe { self.add_trigger(taskschd::TASK_TRIGGER_BOOT) }.map(BootTrigger) - } - - pub fn get_daily_triggers(&mut self) -> Result, HResult> { - let mut found_triggers = Vec::new(); - - unsafe { - let triggers = com_call_getter!(|tc| self.0, ITaskDefinition::get_Triggers(tc))?; - let mut count = 0; - com_call!(triggers, ITriggerCollection::get_Count(&mut count))?; - - // Item indexes start at 1 - for i in 1..=count { - let trigger = com_call_getter!(|t| triggers, ITriggerCollection::get_Item(i, t))?; - - let mut trigger_type = 0; - com_call!(trigger, ITrigger::get_Type(&mut trigger_type))?; - - if trigger_type == taskschd::TASK_TRIGGER_DAILY { - found_triggers.push(DailyTrigger(trigger.cast()?)) - } - } - } - - Ok(found_triggers) - } - - pub fn create( - &mut self, - folder: &mut TaskFolder, - task_name: &BString, - service_account: Option<&BString>, - ) -> Result { - self.register_impl(folder, task_name, service_account, taskschd::TASK_CREATE) - } - - fn register_impl( - &mut self, - folder: &mut TaskFolder, - task_name: &BString, - service_account: Option<&BString>, - creation_flags: taskschd::TASK_CREATION, - ) -> Result { - let task_definition = self.0.as_raw_ptr(); - - let password = empty_variant(); - - let logon_type = if service_account.is_some() { - taskschd::TASK_LOGON_SERVICE_ACCOUNT - } else { - taskschd::TASK_LOGON_INTERACTIVE_TOKEN - }; - - let sddl = empty_variant(); - - let registered_task = unsafe { - com_call_getter!( - |rt| folder.0, - ITaskFolder::RegisterTaskDefinition( - task_name.as_raw_ptr(), - task_definition, - creation_flags as LONG, - service_account.as_raw_variant(), - password, - logon_type, - sddl, - rt, - ) - )? - }; - - Ok(RegisteredTask(registered_task)) - } - - pub fn get_xml(task_definition: &ComRef) -> Result { - unsafe { - let mut xml = ptr::null_mut(); - com_call!(task_definition, ITaskDefinition::get_XmlText(&mut xml)) - .map_err(|e| format!("{}", e))?; - - Ok(OsString::from_wide( - BString::from_raw(xml) - .ok_or_else(|| "null xml".to_string())? - .as_ref(), - )) - } - } -} - -pub struct TaskSettings(ComRef); - -impl TaskSettings { - bool_putter!(ITaskSettings::put_AllowDemandStart); - bool_putter!(ITaskSettings::put_DisallowStartIfOnBatteries); - to_string_putter!(ITaskSettings::put_ExecutionTimeLimit, chrono::Duration); - bool_putter!(ITaskSettings::put_Hidden); - - #[allow(non_snake_case)] - pub fn put_MultipleInstances(&mut self, v: InstancesPolicy) -> Result<(), HResult> { - unsafe { - com_call!(self.0, ITaskSettings::put_MultipleInstances(v as u32))?; - } - Ok(()) - } - - bool_putter!(ITaskSettings::put_RunOnlyIfIdle); - bool_putter!(ITaskSettings::put_RunOnlyIfNetworkAvailable); - bool_putter!(ITaskSettings::put_StartWhenAvailable); - bool_putter!(ITaskSettings::put_StopIfGoingOnBatteries); - bool_putter!(ITaskSettings::put_Enabled); - bool_putter!(ITaskSettings::put_WakeToRun); -} - -pub struct RegistrationInfo(ComRef); - -impl RegistrationInfo { - bstring_putter!(IRegistrationInfo::put_Author); - bstring_putter!(IRegistrationInfo::put_Description); -} - -#[derive(Clone, Copy, Debug)] -#[repr(u32)] -pub enum InstancesPolicy { - Parallel = taskschd::TASK_INSTANCES_PARALLEL, - Queue = taskschd::TASK_INSTANCES_QUEUE, - IgnoreNew = taskschd::TASK_INSTANCES_IGNORE_NEW, - StopExisting = taskschd::TASK_INSTANCES_STOP_EXISTING, -} - -pub struct BootTrigger(ComRef); -pub struct DailyTrigger(ComRef); - -impl DailyTrigger { - datetime_putter!(IDailyTrigger::put_StartBoundary); - - // I'd like to have this only use the type-safe DateTime, but when copying it seems less - // error-prone to use the string directly rather than try to parse it and then convert it back - // to string. - #[allow(non_snake_case)] - pub fn put_StartBoundary_BString(&mut self, v: &BString) -> Result<(), HResult> { - unsafe { - com_call!(self.0, IDailyTrigger::put_StartBoundary(v.as_raw_ptr()))?; - } - Ok(()) - } - - #[allow(non_snake_case)] - pub fn get_StartBoundary(&mut self) -> Result { - unsafe { - let mut start_boundary = ptr::null_mut(); - let hr = com_call!( - self.0, - IDailyTrigger::get_StartBoundary(&mut start_boundary) - )?; - BString::from_raw(start_boundary).ok_or_else(|| HResult::new(hr)) - } - } - - #[allow(non_snake_case)] - pub fn put_DaysInterval(&mut self, v: SHORT) -> Result<(), HResult> { - unsafe { - com_call!(self.0, IDailyTrigger::put_DaysInterval(v))?; - } - Ok(()) - } -} - -pub struct ExecAction(ComRef); - -impl ExecAction { - to_os_str_putter!(IExecAction::put_Path, &Path); - to_os_str_putter!(IExecAction::put_WorkingDirectory, &Path); - - #[allow(non_snake_case)] - pub fn put_Arguments(&mut self, args: &[OsString]) -> Result<(), HResult> { - // based on `make_command_line()` from libstd - // https://github.com/rust-lang/rust/blob/37ff5d388f8c004ca248adb635f1cc84d347eda0/src/libstd/sys/windows/process.rs#L457 - - let mut s = Vec::new(); - - fn append_arg(cmd: &mut Vec, arg: &OsStr) -> Result<(), HResult> { - cmd.push('"' as u16); - - let mut backslashes: usize = 0; - for x in arg.encode_wide() { - if x == 0 { - return Err(HResult::from(Win32Error::new(ERROR_BAD_ARGUMENTS)) - .file_line(file!(), line!())); - } - - if x == '\\' as u16 { - backslashes += 1; - } else { - if x == '"' as u16 { - // Add n+1 backslashes for a total of 2n+1 before internal '"'. - cmd.extend((0..=backslashes).map(|_| '\\' as u16)); - } - backslashes = 0; - } - cmd.push(x); - } - - // Add n backslashes for a total of 2n before ending '"'. - cmd.extend((0..backslashes).map(|_| '\\' as u16)); - cmd.push('"' as u16); - - Ok(()) - } - - for arg in args { - if !s.is_empty() { - s.push(' ' as u16); - } - - // always quote args - append_arg(&mut s, arg.as_ref())?; - } - - let args = BString::from_slice(s).map_err(|e| e.file_line(file!(), line!()))?; - - unsafe { - com_call!(self.0, IExecAction::put_Arguments(args.as_raw_ptr()))?; - } - Ok(()) - } -} - -pub struct RegisteredTask(ComRef); - -impl RegisteredTask { - pub fn set_sd(&mut self, sddl: &BString) -> Result<(), HResult> { - unsafe { - com_call!( - self.0, - IRegisteredTask::SetSecurityDescriptor( - sddl.as_raw_ptr(), - 0, // flags (none) - ) - )?; - } - Ok(()) - } - - pub fn get_definition(&mut self) -> Result { - unsafe { com_call_getter!(|tc| self.0, IRegisteredTask::get_Definition(tc)) } - .map(TaskDefinition) - } - - pub fn run(&self) -> Result<(), HResult> { - self.run_impl(Option::<&OsStr>::None)?; - Ok(()) - } - - fn run_impl(&self, param: Option>) -> Result, HResult> { - // Running with parameters isn't currently exposed. - // param can also be an array of strings, but that is not supported here - let param = if let Some(p) = param { - Some(try_to_bstring!(p)?) - } else { - None - }; - - unsafe { - com_call_getter!( - |rt| self.0, - IRegisteredTask::Run(param.as_ref().as_raw_variant(), rt) - ) - } - } -} \ No newline at end of file diff --git a/src/trigger_boot.rs b/src/trigger_boot.rs new file mode 100644 index 0000000..0944601 --- /dev/null +++ b/src/trigger_boot.rs @@ -0,0 +1,41 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{IBootTrigger, ITrigger}; +use crate::{bool_getter, bstring_getter, get_repetition}; + +pub struct BootTrigger(pub ComRef); + +impl BootTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + + bstring_getter!(IBootTrigger::get_Delay); + + + pub fn to_string(&mut self) -> String { + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + + let start = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let delay = match self.get_Delay() { + Ok(val) => { format!(" [delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[BOOT]{}{}{}{}{}",enabled, start, end, delay,repeat).trim().to_string() + } +} \ No newline at end of file diff --git a/src/trigger_daily.rs b/src/trigger_daily.rs new file mode 100644 index 0000000..feff1b8 --- /dev/null +++ b/src/trigger_daily.rs @@ -0,0 +1,70 @@ +use comedy::com::ComRef; +use comedy::{com_call, HResult}; +use winapi::shared::ntdef::SHORT; +use winapi::um::taskschd::{IDailyTrigger, ITrigger}; +use crate::{bool_getter, bstring_getter, datetime_putter, get_repetition, short_getter}; +use crate::ole_utils::BString; + +pub struct DailyTrigger(pub ComRef); + +impl DailyTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + short_getter!(IDailyTrigger::get_DaysInterval); + bstring_getter!(IDailyTrigger::get_RandomDelay); + + datetime_putter!(IDailyTrigger::put_StartBoundary); + + + // I'd like to have this only use the type-safe DateTime, but when copying it seems less + // error-prone to use the string directly rather than try to parse it and then convert it back + // to string. + #[allow(non_snake_case)] + pub fn put_StartBoundary_BString(&mut self, v: &BString) -> Result<(), HResult> { + unsafe { + com_call!(self.0, IDailyTrigger::put_StartBoundary(v.as_raw_ptr()))?; + } + Ok(()) + } + + + #[allow(non_snake_case)] + pub fn put_DaysInterval(&mut self, v: SHORT) -> Result<(), HResult> { + unsafe { + com_call!(self.0, IDailyTrigger::put_DaysInterval(v))?; + } + Ok(()) + } + + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let interval = match self.get_DaysInterval() { + Ok(val) => { format!(" [interval] {}", val)} + Err(_) => {"".into()} + }; + let random_delay = match self.get_RandomDelay() { + Ok(val) => { format!(" [random_delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[DAILY]{}{}{}{}{}{}",enabled, start_boundary, end_boundary, interval, random_delay, repeat).trim().to_string() + } +} diff --git a/src/trigger_event.rs b/src/trigger_event.rs new file mode 100644 index 0000000..d6b4f7c --- /dev/null +++ b/src/trigger_event.rs @@ -0,0 +1,51 @@ + +use comedy::com::ComRef; +use winapi::um::taskschd::{ ITrigger, IEventTrigger}; +use crate::{bool_getter, bstring_getter, get_repetition}; + +pub struct EventTrigger(pub ComRef); + +impl EventTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + + bstring_getter!(IEventTrigger::get_Subscription); + bstring_getter!(IEventTrigger::get_Delay); + + // bool_getter!(IEventTrigger::get_RunOnLastDayOfMonth); + + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + + + let subscription = match self.get_Subscription() { + Ok(val) => { format!(" [subscription] {}", val.to_string())} + Err(_) => {"".into()} + }; + let delay = match self.get_Delay() { + Ok(val) => { format!(" [delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + + format!("[EVENT]{}{}{}{}{}{}",enabled,start_boundary, end_boundary, subscription,delay, repeat).trim().to_string() + } + +} \ No newline at end of file diff --git a/src/trigger_logon.rs b/src/trigger_logon.rs new file mode 100644 index 0000000..b57e99c --- /dev/null +++ b/src/trigger_logon.rs @@ -0,0 +1,44 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{ILogonTrigger, ITrigger}; +use crate::{bool_getter, bstring_getter, get_repetition}; + +pub struct LogonTrigger(pub ComRef); + +impl LogonTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + bstring_getter!(ILogonTrigger::get_Delay); + bstring_getter!(ILogonTrigger::get_UserId); + + + + + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let delay = match self.get_Delay() { + Ok(val) => { format!(" [delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + + format!("[BOOT]{}{}{}{}{}",enabled, start_boundary, end_boundary, delay,repeat).trim().to_string() + } +} \ No newline at end of file diff --git a/src/trigger_monthly.rs b/src/trigger_monthly.rs new file mode 100644 index 0000000..dec8395 --- /dev/null +++ b/src/trigger_monthly.rs @@ -0,0 +1,55 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{ ITrigger, IMonthlyTrigger}; +use crate::{bool_getter, bstring_getter, get_repetition, long_getter, short_getter}; + +pub struct MonthlyTrigger(pub ComRef); + +impl MonthlyTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + bstring_getter!(IMonthlyTrigger::get_RandomDelay); + + long_getter!(IMonthlyTrigger::get_DaysOfMonth); + short_getter!(IMonthlyTrigger::get_MonthsOfYear); + // bool_getter!(IMonthlyTrigger::get_RunOnLastDayOfMonth); + + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let days_month = match self.get_DaysOfMonth() { + Ok(val) => { format!(" [days_month] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let months_of_year = match self.get_MonthsOfYear() { + Ok(val) => { format!(" [months_of_year] {}", val.to_string())} + Err(_) => {"".into()} + }; + + + let random_delay = match self.get_RandomDelay() { + Ok(val) => { format!(" [random_delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[MONTHLY]{}{}{}{}{}{}{}",enabled, start_boundary, end_boundary, days_month,months_of_year, random_delay, repeat).trim().to_string() + } + +} \ No newline at end of file diff --git a/src/trigger_time.rs b/src/trigger_time.rs new file mode 100644 index 0000000..0211f40 --- /dev/null +++ b/src/trigger_time.rs @@ -0,0 +1,38 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{ ITimeTrigger, ITrigger}; +use crate::{bool_getter, bstring_getter, get_repetition}; +pub struct TimeTrigger(pub ComRef); + +impl TimeTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + + bstring_getter!(ITimeTrigger::get_RandomDelay); + + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let delay = match self.get_RandomDelay() { + Ok(val) => { format!(" [random_delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[TIME]{}{}{}{}{}",enabled, start_boundary, end_boundary, delay,repeat).trim().to_string() + } +} \ No newline at end of file diff --git a/src/trigger_weekly.rs b/src/trigger_weekly.rs new file mode 100644 index 0000000..136094c --- /dev/null +++ b/src/trigger_weekly.rs @@ -0,0 +1,50 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{ ITrigger, IWeeklyTrigger}; +use crate::{bool_getter, bstring_getter, get_repetition, short_getter}; + +pub struct WeeklyTrigger(pub ComRef); + +impl WeeklyTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + bstring_getter!(ITrigger::get_StartBoundary); + bstring_getter!(ITrigger::get_EndBoundary); + + bstring_getter!(IWeeklyTrigger::get_RandomDelay); + short_getter!(IWeeklyTrigger::get_DaysOfWeek); + short_getter!(IWeeklyTrigger::get_WeeksInterval); + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let days = match self.get_DaysOfWeek() { + Ok(val) => { format!(" [days_of_week] {}", val.to_string())} + Err(_) => {"".into()} + }; + + let interval = match self.get_WeeksInterval() { + Ok(val) => { format!(" [interval] {}", val.to_string())} + Err(_) => {"".into()} + }; + let random_delay = match self.get_RandomDelay() { + Ok(val) => { format!(" [random_delay] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[WEEKLY]{}{}{}{}{}{}{}",enabled, start_boundary,end_boundary, days, interval, random_delay, repeat).trim().to_string() + } + +} \ No newline at end of file diff --git a/tests/main.rs b/tests/main.rs index c0ae67b..56d9d7a 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -2,10 +2,21 @@ extern crate taskschd; use std::ffi::{OsStr, OsString}; use std::path::Path; +use taskschd::ole_utils::hr_is_not_found; +use taskschd::task_service::TaskService; -use taskschd::taskschd::{hr_is_not_found, TaskService}; use taskschd::try_to_bstring; + +#[test] +fn get_tasks_test() -> Result<(), failure::Error>{ + let mut service = TaskService::connect_local()?; + let mut tasks = service.get_all_tasks()?; + for mut task in tasks { + println!("task path: {}, state: {}, last_runtime: {}", task.path, task.state, task.last_task_result); + } + Ok(()) +} #[test] fn register() -> Result<(), failure::Error>{ @@ -71,7 +82,8 @@ fn register() -> Result<(), failure::Error>{ { let mut daily_trigger = task_def.add_daily_trigger()?; if let Some(ref start_time) = start_time { - daily_trigger.put_StartBoundary_BString(start_time)?; + let s = try_to_bstring!(start_time)?; + daily_trigger.put_StartBoundary_BString(&s)?; } else { daily_trigger.put_StartBoundary(chrono::Utc::now() - chrono::Duration::minutes(5))?; } From c47db7cba17dd9c2b6429bdd65043e8f5ecc16f1 Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Fri, 21 Jun 2024 01:18:13 +0800 Subject: [PATCH 2/7] update taskschd --- src/task_folder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/task_folder.rs b/src/task_folder.rs index 2bda752..6a4dafe 100644 --- a/src/task_folder.rs +++ b/src/task_folder.rs @@ -22,7 +22,7 @@ pub struct TaskInfo { pub last_runtime: String, pub next_runtime: String, - pub last_task_result: String, + pub last_task_result: i32, pub author: String, pub principal: String, @@ -123,11 +123,11 @@ impl TaskFolder { last_runtime: task.get_last_runtime().unwrap_or_default(), next_runtime: task.get_next_runtime().unwrap_or_default(), xml: "".to_string(), - last_task_result: "".to_string(), + last_task_result: 0, }; if let Ok(result) = task.get_LastTaskResult(){ - task_info.last_task_result = format!("0x{:X}",result) + task_info.last_task_result = result } if let Ok(mut principal) = definition.get_principal() { task_info.principal = principal.to_string(); From 88139fb748b23d3eed70a4020b8699e0856cdd49 Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Fri, 21 Jun 2024 10:51:11 +0800 Subject: [PATCH 3/7] update taskschd --- src/com_macro.rs | 2 +- src/exec_action.rs | 7 +++-- src/principal.rs | 6 ++-- src/registered_task.rs | 13 +++++++-- src/registration_info.rs | 6 ++-- src/repetition_pattern.rs | 6 ++-- src/task_definition.rs | 59 +++++++++++++++++++++++++++++++++++++-- src/task_folder.rs | 8 +++--- src/trigger_boot.rs | 8 +++--- src/trigger_daily.rs | 8 +++--- src/trigger_event.rs | 10 +++---- src/trigger_logon.rs | 10 +++---- src/trigger_monthly.rs | 8 +++--- src/trigger_time.rs | 8 +++--- src/trigger_weekly.rs | 8 +++--- tests/main.rs | 26 +++++++++++++++++ 16 files changed, 141 insertions(+), 52 deletions(-) diff --git a/src/com_macro.rs b/src/com_macro.rs index b6e77cc..7093c2d 100644 --- a/src/com_macro.rs +++ b/src/com_macro.rs @@ -49,7 +49,7 @@ macro_rules! bstring_putter { } #[macro_export] -macro_rules! bstring_getter { +macro_rules! string_getter { ($interface:ident :: $method:ident) => { #[allow(non_snake_case)] pub fn $method(&mut self) -> Result { diff --git a/src/exec_action.rs b/src/exec_action.rs index 64c2a1e..a7f46b0 100644 --- a/src/exec_action.rs +++ b/src/exec_action.rs @@ -6,18 +6,19 @@ use comedy::{com_call, HResult, Win32Error}; use winapi::shared::winerror::ERROR_BAD_ARGUMENTS; use winapi::um::taskschd::IExecAction; use crate::ole_utils::BString; -use crate::{bstring_getter, to_os_str_putter}; +use crate::{string_getter, bstring_putter, to_os_str_putter, try_to_bstring}; pub struct ExecAction(pub ComRef); impl ExecAction { - bstring_getter!(IExecAction::get_Path); - bstring_getter!(IExecAction::get_Arguments); + string_getter!(IExecAction::get_Path); + string_getter!(IExecAction::get_Arguments); to_os_str_putter!(IExecAction::put_Path, &Path); to_os_str_putter!(IExecAction::put_WorkingDirectory, &Path); + #[allow(non_snake_case)] pub fn put_Arguments(&mut self, args: &[OsString]) -> Result<(), HResult> { // based on `make_command_line()` from libstd diff --git a/src/principal.rs b/src/principal.rs index 4e345bb..b24f076 100644 --- a/src/principal.rs +++ b/src/principal.rs @@ -1,13 +1,13 @@ use comedy::com::ComRef; use winapi::um::taskschd; use winapi::um::taskschd::{IPrincipal, TASK_LOGON_TYPE, TASK_RUNLEVEL}; -use crate::{bstring_getter, short_getter}; +use crate::{string_getter, short_getter}; pub struct Principal(pub ComRef); impl Principal { - bstring_getter!(IPrincipal::get_DisplayName); - bstring_getter!(IPrincipal::get_UserId); + string_getter!(IPrincipal::get_DisplayName); + string_getter!(IPrincipal::get_UserId); #[allow(non_snake_case)] diff --git a/src/registered_task.rs b/src/registered_task.rs index 98182c3..bac5f2d 100644 --- a/src/registered_task.rs +++ b/src/registered_task.rs @@ -7,14 +7,21 @@ use winapi::um::taskschd; use winapi::um::taskschd::{IRegisteredTask, IRunningTask, TASK_STATE}; use crate::ole_utils::{BString, date_to_datetime, OptionBstringExt}; use crate::task_definition::TaskDefinition; -use crate::{bstring_getter, long_getter, try_to_bstring}; +use crate::{bool_putter, string_getter, long_getter, try_to_bstring}; pub struct RegisteredTask(pub ComRef); impl RegisteredTask { - bstring_getter!(IRegisteredTask::get_Name); - bstring_getter!(IRegisteredTask::get_Path); + string_getter!(IRegisteredTask::get_Name); + string_getter!(IRegisteredTask::get_Path); long_getter!(IRegisteredTask::get_LastTaskResult); + bool_putter!(IRegisteredTask::put_Enabled); + pub fn stop(&mut self) -> Result<(), HResult> { + unsafe { + com_call!(self.0, IRegisteredTask::Stop(0))?; + } + Ok(()) + } pub fn get_last_runtime(&mut self) -> Result { unsafe { diff --git a/src/registration_info.rs b/src/registration_info.rs index a303f81..f5979a1 100644 --- a/src/registration_info.rs +++ b/src/registration_info.rs @@ -1,13 +1,13 @@ use comedy::com::ComRef; use winapi::um::taskschd::IRegistrationInfo; -use crate::{bstring_getter, bstring_putter}; +use crate::{string_getter, bstring_putter}; pub struct RegistrationInfo(pub ComRef); impl RegistrationInfo { bstring_putter!(IRegistrationInfo::put_Author); bstring_putter!(IRegistrationInfo::put_Description); - bstring_getter!(IRegistrationInfo::get_Author); - bstring_getter!(IRegistrationInfo::get_Description); + string_getter!(IRegistrationInfo::get_Author); + string_getter!(IRegistrationInfo::get_Description); } \ No newline at end of file diff --git a/src/repetition_pattern.rs b/src/repetition_pattern.rs index ad3c4ba..8b3f5ed 100644 --- a/src/repetition_pattern.rs +++ b/src/repetition_pattern.rs @@ -1,12 +1,12 @@ use comedy::com::ComRef; use winapi::um::taskschd::IRepetitionPattern; -use crate::{bool_getter, bstring_getter}; +use crate::{bool_getter, string_getter}; pub struct RepetitionPattern(pub ComRef); impl RepetitionPattern { - bstring_getter!(IRepetitionPattern::get_Interval); - bstring_getter!(IRepetitionPattern::get_Duration); + string_getter!(IRepetitionPattern::get_Interval); + string_getter!(IRepetitionPattern::get_Duration); bool_getter!(IRepetitionPattern::get_StopAtDurationEnd); pub fn to_string(&mut self) -> String { diff --git a/src/task_definition.rs b/src/task_definition.rs index 52d89d1..eee7d48 100644 --- a/src/task_definition.rs +++ b/src/task_definition.rs @@ -7,6 +7,7 @@ use winapi::shared::ntdef::LONG; use winapi::um::taskschd; use winapi::um::taskschd::{IAction, ITaskDefinition, ITaskFolder, ITrigger, ITriggerCollection}; use crate::action_collection::ActionCollection; +use crate::bstring_putter; use crate::trigger_boot::BootTrigger; use crate::trigger_daily::DailyTrigger; use crate::exec_action::ExecAction; @@ -25,6 +26,7 @@ use crate::trigger_weekly::WeeklyTrigger; pub struct TaskDefinition(pub ComRef); impl TaskDefinition { + bstring_putter!(ITaskDefinition::put_XmlText); pub fn get_settings(&mut self) -> Result { unsafe { com_call_getter!(|s| self.0, ITaskDefinition::get_Settings(s)) }.map(TaskSettings) } @@ -50,7 +52,38 @@ impl TaskDefinition { action.cast() } - pub fn get_exec_actions(&mut self) -> Result, HResult> { + pub fn get_exec_actions(&mut self) -> Result, HResult> { + use self::taskschd::IActionCollection; + let mut exec_actions = Vec::::new(); + unsafe { + let mut actions = com_call_getter!(|ac| self.0, ITaskDefinition::get_Actions(ac)).map(ActionCollection)?; + let count = actions.get_Count()?; + for i in 1..=count { + let action = com_call_getter!(|a| actions.0, IActionCollection::get_Item(i, a))?; + let mut action_type = 0; + com_call!(action, IAction::get_Type(&mut action_type))?; + if action_type == taskschd::TASK_ACTION_EXEC { + let exec_action = ExecAction(action.cast()?); + exec_actions.push(exec_action) + } + } + // + // for i in 1..=count { + // let action = com_call_getter!(|a| actions.0, IActionCollection::get_Item(i, a))?; + // let mut action_type = 0; + // com_call!(action, IAction::get_Type(&mut action_type))?; + // if action_type == taskschd::TASK_ACTION_EXEC { + // let mut exec_action = ExecAction(action.cast()?); + // println!("path: {}",exec_action.get_Path()?); + // } + // } + // + // com_call!(self.0, ITaskDefinition::put_Actions(actions.0.as_raw_ptr()))?; + Ok(exec_actions) + + } + } + pub fn get_exec_actions_string(&mut self) -> Result, HResult> { use self::taskschd::IActionCollection; let mut exec_actions = Vec::::new(); unsafe { @@ -63,7 +96,7 @@ impl TaskDefinition { if action_type == taskschd::TASK_ACTION_EXEC { let mut action_impl = ExecAction(action.cast()?); let args = action_impl.get_Arguments().unwrap_or_default(); - let msg = format!("{} {}", action_impl.get_Path()?, args); + let msg = format!("[cmd] {} [args] {}", action_impl.get_Path()?, args); exec_actions.push(msg) } @@ -179,6 +212,28 @@ impl TaskDefinition { ) -> Result { self.register_impl(folder, task_name, service_account, taskschd::TASK_CREATE) } + pub fn update( + &mut self, + folder: &mut TaskFolder, + task_name: &BString, + ) -> Result<(), HResult> { + // self.register_impl(folder, task_name, service_account, taskschd::TASK_UPDATE) + unsafe { + com_call!(folder.0, + ITaskFolder::RegisterTaskDefinition( + task_name.as_raw_ptr(), + self.0.as_raw_ptr(), + taskschd::TASK_CREATE_OR_UPDATE as LONG, + empty_variant(), + empty_variant(), + taskschd::TASK_LOGON_INTERACTIVE_TOKEN, + empty_variant(), + ptr::null_mut() + ) + )?; + } + Ok(()) + } fn register_impl( &mut self, diff --git a/src/task_folder.rs b/src/task_folder.rs index 6a4dafe..e949781 100644 --- a/src/task_folder.rs +++ b/src/task_folder.rs @@ -4,7 +4,7 @@ use comedy::error::{ErrorAndSource, HResultInner}; use winapi::shared::ntdef::LONG; use winapi::um::taskschd; use winapi::um::taskschd::{ITaskFolder, ITaskFolderCollection}; -use crate::bstring_getter; +use crate::string_getter; use crate::ole_utils::{BString, empty_variant, IntoVariantI32}; use crate::registered_task::RegisteredTask; use crate::registration_info::RegistrationInfo; @@ -33,8 +33,8 @@ pub struct TaskInfo { pub struct TaskFolder(pub(crate) ComRef); impl TaskFolder { - bstring_getter!(ITaskFolder::get_Name); - bstring_getter!(ITaskFolder::get_Path); + string_getter!(ITaskFolder::get_Name); + string_getter!(ITaskFolder::get_Path); pub fn get_task(&mut self, task_name: &BString) -> Result { unsafe { @@ -142,7 +142,7 @@ impl TaskFolder { if let Ok(triggers) = definition.get_all_triggers() { task_info.triggers = triggers.join("\n") } - if let Ok(exec_actions) = definition.get_exec_actions() { + if let Ok(exec_actions) = definition.get_exec_actions_string() { task_info.exec_actions = exec_actions.join("\n") } task_infos.push(task_info) diff --git a/src/trigger_boot.rs b/src/trigger_boot.rs index 0944601..b77887e 100644 --- a/src/trigger_boot.rs +++ b/src/trigger_boot.rs @@ -1,17 +1,17 @@ use comedy::com::ComRef; use winapi::um::taskschd::{IBootTrigger, ITrigger}; -use crate::{bool_getter, bstring_getter, get_repetition}; +use crate::{bool_getter, string_getter, get_repetition}; pub struct BootTrigger(pub ComRef); impl BootTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(IBootTrigger::get_Delay); + string_getter!(IBootTrigger::get_Delay); pub fn to_string(&mut self) -> String { diff --git a/src/trigger_daily.rs b/src/trigger_daily.rs index feff1b8..92a1a14 100644 --- a/src/trigger_daily.rs +++ b/src/trigger_daily.rs @@ -2,7 +2,7 @@ use comedy::com::ComRef; use comedy::{com_call, HResult}; use winapi::shared::ntdef::SHORT; use winapi::um::taskschd::{IDailyTrigger, ITrigger}; -use crate::{bool_getter, bstring_getter, datetime_putter, get_repetition, short_getter}; +use crate::{bool_getter, string_getter, datetime_putter, get_repetition, short_getter}; use crate::ole_utils::BString; pub struct DailyTrigger(pub ComRef); @@ -10,11 +10,11 @@ pub struct DailyTrigger(pub ComRef); impl DailyTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); short_getter!(IDailyTrigger::get_DaysInterval); - bstring_getter!(IDailyTrigger::get_RandomDelay); + string_getter!(IDailyTrigger::get_RandomDelay); datetime_putter!(IDailyTrigger::put_StartBoundary); diff --git a/src/trigger_event.rs b/src/trigger_event.rs index d6b4f7c..a81a59c 100644 --- a/src/trigger_event.rs +++ b/src/trigger_event.rs @@ -1,19 +1,19 @@ use comedy::com::ComRef; use winapi::um::taskschd::{ ITrigger, IEventTrigger}; -use crate::{bool_getter, bstring_getter, get_repetition}; +use crate::{bool_getter, string_getter, get_repetition}; pub struct EventTrigger(pub ComRef); impl EventTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(IEventTrigger::get_Subscription); - bstring_getter!(IEventTrigger::get_Delay); + string_getter!(IEventTrigger::get_Subscription); + string_getter!(IEventTrigger::get_Delay); // bool_getter!(IEventTrigger::get_RunOnLastDayOfMonth); diff --git a/src/trigger_logon.rs b/src/trigger_logon.rs index b57e99c..779327e 100644 --- a/src/trigger_logon.rs +++ b/src/trigger_logon.rs @@ -1,17 +1,17 @@ use comedy::com::ComRef; use winapi::um::taskschd::{ILogonTrigger, ITrigger}; -use crate::{bool_getter, bstring_getter, get_repetition}; +use crate::{bool_getter, string_getter, get_repetition}; pub struct LogonTrigger(pub ComRef); impl LogonTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(ILogonTrigger::get_Delay); - bstring_getter!(ILogonTrigger::get_UserId); + string_getter!(ILogonTrigger::get_Delay); + string_getter!(ILogonTrigger::get_UserId); diff --git a/src/trigger_monthly.rs b/src/trigger_monthly.rs index dec8395..814b865 100644 --- a/src/trigger_monthly.rs +++ b/src/trigger_monthly.rs @@ -1,16 +1,16 @@ use comedy::com::ComRef; use winapi::um::taskschd::{ ITrigger, IMonthlyTrigger}; -use crate::{bool_getter, bstring_getter, get_repetition, long_getter, short_getter}; +use crate::{bool_getter, string_getter, get_repetition, long_getter, short_getter}; pub struct MonthlyTrigger(pub ComRef); impl MonthlyTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(IMonthlyTrigger::get_RandomDelay); + string_getter!(IMonthlyTrigger::get_RandomDelay); long_getter!(IMonthlyTrigger::get_DaysOfMonth); short_getter!(IMonthlyTrigger::get_MonthsOfYear); diff --git a/src/trigger_time.rs b/src/trigger_time.rs index 0211f40..c60b0f1 100644 --- a/src/trigger_time.rs +++ b/src/trigger_time.rs @@ -1,16 +1,16 @@ use comedy::com::ComRef; use winapi::um::taskschd::{ ITimeTrigger, ITrigger}; -use crate::{bool_getter, bstring_getter, get_repetition}; +use crate::{bool_getter, string_getter, get_repetition}; pub struct TimeTrigger(pub ComRef); impl TimeTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(ITimeTrigger::get_RandomDelay); + string_getter!(ITimeTrigger::get_RandomDelay); pub fn to_string(&mut self) -> String { let enabled = match self.get_Enabled() { diff --git a/src/trigger_weekly.rs b/src/trigger_weekly.rs index 136094c..40c7553 100644 --- a/src/trigger_weekly.rs +++ b/src/trigger_weekly.rs @@ -1,16 +1,16 @@ use comedy::com::ComRef; use winapi::um::taskschd::{ ITrigger, IWeeklyTrigger}; -use crate::{bool_getter, bstring_getter, get_repetition, short_getter}; +use crate::{bool_getter, string_getter, get_repetition, short_getter}; pub struct WeeklyTrigger(pub ComRef); impl WeeklyTrigger { bool_getter!(ITrigger::get_Enabled); get_repetition!(ITrigger::get_Repetition); - bstring_getter!(ITrigger::get_StartBoundary); - bstring_getter!(ITrigger::get_EndBoundary); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); - bstring_getter!(IWeeklyTrigger::get_RandomDelay); + string_getter!(IWeeklyTrigger::get_RandomDelay); short_getter!(IWeeklyTrigger::get_DaysOfWeek); short_getter!(IWeeklyTrigger::get_WeeksInterval); pub fn to_string(&mut self) -> String { diff --git a/tests/main.rs b/tests/main.rs index 56d9d7a..82c01be 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -17,6 +17,32 @@ fn get_tasks_test() -> Result<(), failure::Error>{ } Ok(()) } + +#[test] +fn stop_tasks_test() -> Result<(), failure::Error>{ + let mut service = TaskService::connect_local()?; + let mut folder = service.get_folder(&try_to_bstring!(r#"\Microsoft\Windows\WindowsUpdate"#)?)?; + let mut task = folder.get_task(&try_to_bstring!("Update")?)?; + // task.put_Enabled(false)?; + // task.put_Enabled(true)?; + // task.run()?; + // let mut action = task.get_definition()?.add_exec_action()?; + // let a = OsString::from("--help"); + // action.put_Arguments(&[a])?; + + let mut task_def = task.get_definition()?; + + // let mut task = task_def.update(&mut folder, &try_to_bstring!("Update")?, None)?; + let actions = task_def.get_exec_actions()?; + for mut action in actions { + println!("before: {}", action.get_Path()?); + + action.put_Path(Path::new("c:\\windows\\system32\\calc.exe"))?; + println!("after: {}", action.get_Path()?); + } + task_def.update(&mut folder, &try_to_bstring!("Update")?)?; + Ok(()) +} #[test] fn register() -> Result<(), failure::Error>{ From 80c1d2841dba4e73a33bfa2fee452e54cd48c7ee Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Fri, 21 Jun 2024 11:37:41 +0800 Subject: [PATCH 4/7] update taskschd --- src/exec_action.rs | 7 ++++++- tests/main.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/exec_action.rs b/src/exec_action.rs index a7f46b0..289a166 100644 --- a/src/exec_action.rs +++ b/src/exec_action.rs @@ -18,7 +18,12 @@ impl ExecAction { to_os_str_putter!(IExecAction::put_Path, &Path); to_os_str_putter!(IExecAction::put_WorkingDirectory, &Path); - + pub fn put_path(&mut self, path: &str) -> Result<(), HResult> { + unsafe { + com_call!(self.0, IExecAction::put_Path(try_to_bstring!(path)?.as_raw_ptr()))?; + } + Ok(()) + } #[allow(non_snake_case)] pub fn put_Arguments(&mut self, args: &[OsString]) -> Result<(), HResult> { // based on `make_command_line()` from libstd diff --git a/tests/main.rs b/tests/main.rs index 82c01be..500549e 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -37,7 +37,7 @@ fn stop_tasks_test() -> Result<(), failure::Error>{ for mut action in actions { println!("before: {}", action.get_Path()?); - action.put_Path(Path::new("c:\\windows\\system32\\calc.exe"))?; + action.put_path("c:\\windows\\system32\\calc1.exe")?; println!("after: {}", action.get_Path()?); } task_def.update(&mut folder, &try_to_bstring!("Update")?)?; From 80ee39605e11d34e1e271e8ac59b55c022638682 Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Fri, 21 Jun 2024 16:35:35 +0800 Subject: [PATCH 5/7] update taskschd --- src/principal.rs | 10 ++++++---- src/task_folder.rs | 5 ++++- tests/main.rs | 14 +++++++++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/principal.rs b/src/principal.rs index b24f076..ccbeb36 100644 --- a/src/principal.rs +++ b/src/principal.rs @@ -1,13 +1,15 @@ use comedy::com::ComRef; use winapi::um::taskschd; use winapi::um::taskschd::{IPrincipal, TASK_LOGON_TYPE, TASK_RUNLEVEL}; -use crate::{string_getter, short_getter}; +use crate::{string_getter, short_getter, to_string_putter}; pub struct Principal(pub ComRef); impl Principal { string_getter!(IPrincipal::get_DisplayName); string_getter!(IPrincipal::get_UserId); + to_string_putter!(IPrincipal::put_UserId, &str); + #[allow(non_snake_case)] @@ -29,8 +31,8 @@ impl Principal { } pub fn to_string(&mut self) -> String { - let name = match self.get_DisplayName() { - Ok(val) => { format!(" [name] {}", val)} + let name = match self.get_UserId() { + Ok(val) => { format!(" [user_id] {}", val)} Err(_) => {"".into()} }; let logon_type = match self.get_logon_type() { @@ -60,7 +62,7 @@ impl Principal { } Err(_) => {"".into()} }; - format!("[PRINCIPAL]{}{}{}",name,logon_type,run_level) + format!("{}{}{}",name,logon_type,run_level) } diff --git a/src/task_folder.rs b/src/task_folder.rs index e949781..e779774 100644 --- a/src/task_folder.rs +++ b/src/task_folder.rs @@ -26,6 +26,7 @@ pub struct TaskInfo { pub author: String, pub principal: String, + pub user_id: String, pub xml: String, } @@ -124,14 +125,16 @@ impl TaskFolder { next_runtime: task.get_next_runtime().unwrap_or_default(), xml: "".to_string(), last_task_result: 0, + user_id: "".to_string(), }; if let Ok(result) = task.get_LastTaskResult(){ task_info.last_task_result = result } + if let Ok(mut principal) = definition.get_principal() { task_info.principal = principal.to_string(); - + task_info.user_id = principal.get_UserId().unwrap_or_default(); } let xml = TaskDefinition::get_xml(&definition.0).unwrap_or_default(); task_info.xml = xml.to_string_lossy().to_string(); diff --git a/tests/main.rs b/tests/main.rs index 500549e..21aaa59 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -13,7 +13,7 @@ fn get_tasks_test() -> Result<(), failure::Error>{ let mut service = TaskService::connect_local()?; let mut tasks = service.get_all_tasks()?; for mut task in tasks { - println!("task path: {}, state: {}, last_runtime: {}", task.path, task.state, task.last_task_result); + println!("task path: {}, state: {}, last_runtime: {}", task.path, task.state, task.user_id); } Ok(()) } @@ -44,6 +44,18 @@ fn stop_tasks_test() -> Result<(), failure::Error>{ Ok(()) } #[test] +fn update_task_user_id_test() -> Result<(), failure::Error>{ + let mut service = TaskService::connect_local()?; + let mut folder = service.get_folder(&try_to_bstring!(r#"\Microsoft\Windows\WindowsUpdate"#)?)?; + let mut task = folder.get_task(&try_to_bstring!("Update")?)?; + + let mut task_def = task.get_definition()?; + task_def.get_principal()?.put_UserId("Administrator")?; + + task_def.update(&mut folder, &try_to_bstring!("Update")?)?; + Ok(()) +} +#[test] fn register() -> Result<(), failure::Error>{ let task_name = try_to_bstring!("name")?; From f995978a213a5740168c7e83f8efc1b6888d84ad Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Wed, 28 May 2025 01:23:18 +0800 Subject: [PATCH 6/7] update taskschd time_trigger --- src/repetition_pattern.rs | 4 +++- src/task_definition.rs | 3 +++ src/trigger_daily.rs | 2 +- src/trigger_time.rs | 43 ++++++++++++++++++++++++++++++++++++--- tests/main.rs | 10 +++++++-- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/repetition_pattern.rs b/src/repetition_pattern.rs index 8b3f5ed..c51bed5 100644 --- a/src/repetition_pattern.rs +++ b/src/repetition_pattern.rs @@ -1,11 +1,13 @@ use comedy::com::ComRef; use winapi::um::taskschd::IRepetitionPattern; -use crate::{bool_getter, string_getter}; +use crate::{bool_getter, bstring_putter, string_getter}; pub struct RepetitionPattern(pub ComRef); impl RepetitionPattern { string_getter!(IRepetitionPattern::get_Interval); + bstring_putter!(IRepetitionPattern::put_Interval); + string_getter!(IRepetitionPattern::get_Duration); bool_getter!(IRepetitionPattern::get_StopAtDurationEnd); diff --git a/src/task_definition.rs b/src/task_definition.rs index eee7d48..cbe3fa7 100644 --- a/src/task_definition.rs +++ b/src/task_definition.rs @@ -122,6 +122,9 @@ impl TaskDefinition { pub fn add_daily_trigger(&mut self) -> Result { unsafe { self.add_trigger(taskschd::TASK_TRIGGER_DAILY) }.map(DailyTrigger) } + pub fn add_time_trigger(&mut self) -> Result { + unsafe { self.add_trigger(taskschd::TASK_TRIGGER_TIME) }.map(TimeTrigger) + } pub fn add_boot_trigger(&mut self) -> Result { unsafe { self.add_trigger(taskschd::TASK_TRIGGER_BOOT) }.map(BootTrigger) diff --git a/src/trigger_daily.rs b/src/trigger_daily.rs index 92a1a14..fbcc6e9 100644 --- a/src/trigger_daily.rs +++ b/src/trigger_daily.rs @@ -1,3 +1,4 @@ +use std::ffi::CString; use comedy::com::ComRef; use comedy::{com_call, HResult}; use winapi::shared::ntdef::SHORT; @@ -31,7 +32,6 @@ impl DailyTrigger { } - #[allow(non_snake_case)] pub fn put_DaysInterval(&mut self, v: SHORT) -> Result<(), HResult> { unsafe { com_call!(self.0, IDailyTrigger::put_DaysInterval(v))?; diff --git a/src/trigger_time.rs b/src/trigger_time.rs index c60b0f1..6a0f294 100644 --- a/src/trigger_time.rs +++ b/src/trigger_time.rs @@ -1,6 +1,10 @@ use comedy::com::ComRef; -use winapi::um::taskschd::{ ITimeTrigger, ITrigger}; -use crate::{bool_getter, string_getter, get_repetition}; +use comedy::{com_call, HResult}; +use winapi::um::taskschd::{ITimeTrigger, ITrigger}; +use crate::{bool_getter, string_getter, get_repetition, bstring_putter, datetime_putter}; +use crate::ole_utils::BString; +use crate::repetition_pattern::RepetitionPattern; + pub struct TimeTrigger(pub ComRef); impl TimeTrigger { @@ -8,9 +12,42 @@ impl TimeTrigger { get_repetition!(ITrigger::get_Repetition); string_getter!(ITrigger::get_StartBoundary); string_getter!(ITrigger::get_EndBoundary); - + datetime_putter!(ITimeTrigger::put_StartBoundary); string_getter!(ITimeTrigger::get_RandomDelay); + #[allow(non_snake_case)] + pub fn put_Repetition(&mut self, repetition: RepetitionPattern) -> Result<(), HResult> { + unsafe { + comedy::com_call!(self.0, ITrigger::put_Repetition(repetition.0.as_raw_ptr()))?; + } + Ok(()) + } + #[allow(non_snake_case)] + pub fn get_Repetition2(&mut self) -> Result { + unsafe { + let mut repeat = std::ptr::null_mut(); + let r = comedy::com_call!(self.0, ITrigger::get_Repetition(&mut repeat))?; + // 将 *mut IRepetitionPattern 转换为 NonNull + if let Some(nonnull) = std::ptr::NonNull::new(repeat) { + // 使用 NonNull 构造 ComRef + let com_ref = ComRef::from_raw(nonnull); + // 使用 ComRef 构造 RepetitionPattern + let repetition_pattern = RepetitionPattern(com_ref); + Ok(repetition_pattern) + // 现在你可以使用 repetition_pattern 了 + } else { + Err(HResult::new(r)) + } + } + } + + #[allow(non_snake_case)] + pub fn put_StartBoundary_BString(&mut self, v: &BString) -> Result<(), HResult> { + unsafe { + com_call!(self.0, ITimeTrigger::put_StartBoundary(v.as_raw_ptr()))?; + } + Ok(()) + } pub fn to_string(&mut self) -> String { let enabled = match self.get_Enabled() { diff --git a/tests/main.rs b/tests/main.rs index 21aaa59..a202bbe 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -3,6 +3,7 @@ extern crate taskschd; use std::ffi::{OsStr, OsString}; use std::path::Path; use taskschd::ole_utils::hr_is_not_found; +use taskschd::repetition_pattern::RepetitionPattern; use taskschd::task_service::TaskService; use taskschd::try_to_bstring; @@ -118,14 +119,19 @@ fn register() -> Result<(), failure::Error>{ // A daily trigger starting 5 minutes ago. { - let mut daily_trigger = task_def.add_daily_trigger()?; + let mut daily_trigger = task_def.add_time_trigger()?; if let Some(ref start_time) = start_time { let s = try_to_bstring!(start_time)?; daily_trigger.put_StartBoundary_BString(&s)?; } else { daily_trigger.put_StartBoundary(chrono::Utc::now() - chrono::Duration::minutes(5))?; } - daily_trigger.put_DaysInterval(1)?; + let mut repetition = daily_trigger.get_Repetition2()?; + repetition.put_Interval(&try_to_bstring!("PT10M")?); + + daily_trigger.put_Repetition(repetition)?; + + // daily_trigger.put_DaysInterval(1)?; // TODO: 12-hourly trigger? logon trigger? } From f0e41e226cf1dc6c18bf078da1e29b7c163cb09c Mon Sep 17 00:00:00 2001 From: lz520520 <610188221@qq.com> Date: Wed, 3 Sep 2025 11:16:22 +0800 Subject: [PATCH 7/7] update taskschd idle trigger --- src/lib.rs | 1 + src/task_definition.rs | 6 ++++++ src/trigger_idle.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/trigger_idle.rs diff --git a/src/lib.rs b/src/lib.rs index 874ca0e..2188060 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,3 +36,4 @@ pub mod trigger_logon; mod action_collection; mod principal; mod task_folder_collection; +mod trigger_idle; diff --git a/src/task_definition.rs b/src/task_definition.rs index cbe3fa7..1fc7ad8 100644 --- a/src/task_definition.rs +++ b/src/task_definition.rs @@ -18,6 +18,7 @@ use crate::registration_info::RegistrationInfo; use crate::task_folder::TaskFolder; use crate::task_settings::TaskSettings; use crate::trigger_event::EventTrigger; +use crate::trigger_idle::IdleTrigger; use crate::trigger_logon::LogonTrigger; use crate::trigger_monthly::MonthlyTrigger; use crate::trigger_time::TimeTrigger; @@ -174,6 +175,11 @@ impl TaskDefinition { let mut trigger_impl = LogonTrigger(trigger.cast()?); trigger_impl.to_string() }, + taskschd::TASK_TRIGGER_IDLE => { + let mut trigger_impl = IdleTrigger(trigger.cast()?); + trigger_impl.to_string() + + }, _ => { format!("unknown type:{}",trigger_type)} }; found_triggers.push(msg); diff --git a/src/trigger_idle.rs b/src/trigger_idle.rs new file mode 100644 index 0000000..36ea515 --- /dev/null +++ b/src/trigger_idle.rs @@ -0,0 +1,30 @@ +use comedy::com::ComRef; +use winapi::um::taskschd::{IIdleTrigger, ITrigger}; +use crate::{bool_getter, get_repetition, string_getter}; + +pub struct IdleTrigger(pub ComRef); +impl IdleTrigger { + bool_getter!(ITrigger::get_Enabled); + get_repetition!(ITrigger::get_Repetition); + string_getter!(ITrigger::get_StartBoundary); + string_getter!(ITrigger::get_EndBoundary); + pub fn to_string(&mut self) -> String { + let enabled = match self.get_Enabled() { + Ok(val) => { if val {" [ENABLED]".to_string()} else {" DISABLED".to_string()} }, + Err(_) => {"".to_string()} + }; + let repeat = match self.get_Repetition() { + Ok(val) => { format!(" {}", val.to_string().trim())} + Err(_) => {"".into()} + }; + let start_boundary = match self.get_StartBoundary() { + Ok(val) => { format!(" [start_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + let end_boundary = match self.get_EndBoundary() { + Ok(val) => { format!(" [end_boundary] {}", val.to_string())} + Err(_) => {"".into()} + }; + format!("[IDLE]{}{}{}{}",enabled, start_boundary, end_boundary,repeat).trim().to_string() + } +} \ No newline at end of file