From f6df4caaa62b639954fc4d85091be24675df2088 Mon Sep 17 00:00:00 2001 From: Matt Robineau Date: Sat, 30 Jul 2022 22:49:47 -0400 Subject: [PATCH] Add registration and event triggers, fix various issues --- CHANGELOG.md | 11 ++ Cargo.toml | 2 +- README.md | 12 ++- src/enums.rs | 2 +- src/schedule_builder.rs | 227 ++++++++++++++++++++++++++++++++++------ 5 files changed, 214 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e9ff3..9c36286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.1.0] - 2022-07-30 + +This release completes the available triggers in the Windows Task Scheduler. + ### Added * Add monthly dow trigger * Add monthly trigger * Add Changelog +* Add event trigger +* Add registration trigger ### Changed * Improve documentation @@ -16,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update cargo toml * Update readme +### Fixed +* Fix casting issues +* Removed mutability to parameters that do not need it + ## [0.0.1] - 2022-04-10 ### Added * Add ability to set settings on task diff --git a/Cargo.toml b/Cargo.toml index 4dd41b4..abd95ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ authors = ["Matt Robineau "] description = "planif is a builder pattern wrapper for the windows task scheduler API of windows-rs." license = "MIT" repository = "https://github.com/mattrobineau/planif" -version = "0.0.2" +version = "0.1.0" edition = "2021" keywords = ["scheduled", "task", "windows"] categories = ["os::windows-apis"] diff --git a/README.md b/README.md index dfb8c7c..78f5f72 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,21 @@ Please refer to the `examples` folder. The folder contains code for creating eac ### Create Triggers - [x] Boot - [x] Daily -- [ ] Event -- [ ] Idle +- [X] Event +- [X] Idle - [x] Logon - [X] MonthlyDOW - [x] Monthly -- [ ] Registration +- [X] Registration - [x] Time - [x] Weekly + +### Trigger settings - [ ] IdleSettings -### Other +Other settings may also be missing. + +### Other (maybe) - [ ] Delete triggers - [ ] List triggers diff --git a/src/enums.rs b/src/enums.rs index 375b96f..4ed8dce 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -3,7 +3,7 @@ pub enum DayOfMonth { Last, } -impl From for i32 { +impl From for u32 { fn from(day: DayOfMonth) -> Self { match day { DayOfMonth::Day(i) => 1 << i, diff --git a/src/schedule_builder.rs b/src/schedule_builder.rs index f1e0f7d..02749c9 100644 --- a/src/schedule_builder.rs +++ b/src/schedule_builder.rs @@ -1,8 +1,8 @@ use crate::{ enums::{DayOfMonth, DayOfWeek, Month, WeekOfMonth}, - error::{InitializationError, InvalidOperationError}, + error::InvalidOperationError, schedule::Schedule, - settings::{PrincipalSettings, RunLevel}, + settings::PrincipalSettings, }; use windows::core::Interface; use windows::Win32::Foundation::BSTR; @@ -11,14 +11,13 @@ use windows::Win32::System::Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_MULTITHREADED, }; use windows::Win32::System::TaskScheduler::{ - IAction, IActionCollection, IBootTrigger, IDailyTrigger, IEventTrigger, IEventTrigger, - IExecAction, IIdleTrigger, ILogonTrigger, IMonthlyDOWTrigger, IMonthlyTrigger, IPrincipal, - IRegistrationInfo, IRegistrationTrigger, IRepetitionPattern, ITaskDefinition, ITaskFolder, - ITaskService, ITaskSettings, ITimeTrigger, ITrigger, ITriggerCollection, IWeeklyTrigger, - TaskScheduler, TASK_ACTION_EXEC, TASK_LOGON_INTERACTIVE_TOKEN, TASK_LOGON_TYPE, - TASK_RUNLEVEL_TYPE, TASK_TRIGGER_BOOT, TASK_TRIGGER_DAILY, TASK_TRIGGER_EVENT, - TASK_TRIGGER_IDLE, TASK_TRIGGER_LOGON, TASK_TRIGGER_MONTHLY, TASK_TRIGGER_MONTHLYDOW, - TASK_TRIGGER_REGISTRATION, TASK_TRIGGER_TIME, TASK_TRIGGER_WEEKLY, + IAction, IActionCollection, IBootTrigger, IDailyTrigger, IEventTrigger, IExecAction, + IIdleTrigger, ILogonTrigger, IMonthlyDOWTrigger, IMonthlyTrigger, IPrincipal, + IRegistrationInfo, IRegistrationTrigger, IRepetitionPattern, ITaskDefinition, ITaskService, + ITaskSettings, ITimeTrigger, ITriggerCollection, IWeeklyTrigger, TaskScheduler, + TASK_ACTION_EXEC, TASK_LOGON_TYPE, TASK_RUNLEVEL_TYPE, TASK_TRIGGER_BOOT, TASK_TRIGGER_DAILY, + TASK_TRIGGER_EVENT, TASK_TRIGGER_IDLE, TASK_TRIGGER_LOGON, TASK_TRIGGER_MONTHLY, + TASK_TRIGGER_MONTHLYDOW, TASK_TRIGGER_REGISTRATION, TASK_TRIGGER_TIME, TASK_TRIGGER_WEEKLY, }; /* triggers */ @@ -112,6 +111,22 @@ impl ScheduleBuilder { } } + /// Creates a builder for an event trigger. + /// + /// # Example + /// + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_event(); + /// ``` + pub fn create_event(mut self) -> ScheduleBuilder { + self.schedule.force_start_boundary = true; + ScheduleBuilder:: { + frequency: std::marker::PhantomData::, + schedule: self.schedule, + } + } + /// Creates a builder for an idle trigger. /// /// # Example @@ -170,6 +185,20 @@ impl ScheduleBuilder { } } + /// Creates a builder for a trigger that starts a task when the task is registered or updated. + /// + /// # Example + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_registration(); + /// ``` + pub fn create_registration(self) -> ScheduleBuilder { + ScheduleBuilder:: { + frequency: std::marker::PhantomData::, + schedule: self.schedule, + } + } + /// Creates a builder for a time trigger. /// /// # Example @@ -612,8 +641,93 @@ impl ScheduleBuilder { } } +impl ScheduleBuilder { + /// Specifies a value that indicates the amount of time between when the user logs on and when the task is started. + /// The format for this string is PDTHMS (for example, P2DT5S is a 2 day, 5 second delay). + /// see https://docs.microsoft.com/en-us/windows/win32/taskschd/eventtrigger-delay + /// + /// # Example + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_event() + /// .trigger("MyTrigger", true) + /// .delay("P2DT5S"); + /// ``` + pub fn delay(self, delay: &str) -> Result> { + if let Some(trigger) = &self.schedule.trigger { + unsafe { + let i_event_trigger: IEventTrigger = trigger.cast::()?; + i_event_trigger.SetDelay(delay)?; + } + Ok(self) + } else { + self.uninitialize(); + Err(trigger_uninitialised_error()) + } + } + + /// Specifies a query string that identifies the event that fires the trigger. + /// see Event Selection: https://docs.microsoft.com/en-us/previous-versions//aa385231(v=vs.85) + /// see Subscribing to Events: https://docs.microsoft.com/en-us/windows/win32/wes/subscribing-to-events + pub fn subscription(self, query: &str) -> Result> { + if let Some(trigger) = &self.schedule.trigger { + unsafe { + let i_event_trigger: IEventTrigger = trigger.cast::()?; + i_event_trigger.SetSubscription(query)?; + } + Ok(self) + } else { + self.uninitialize(); + Err(trigger_uninitialised_error()) + } + } + + /// Create an event trigger. + /// + /// # Example + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_event() + /// .trigger("MyTrigger", true); + /// ``` + pub fn trigger(mut self, id: &str, enabled: bool) -> Result> { + unsafe { + let trigger = self.schedule.triggers.Create(TASK_TRIGGER_EVENT)?; + let i_event_trigger: IEventTrigger = trigger.cast::()?; + i_event_trigger.SetId(id)?; + i_event_trigger.SetEnabled(enabled.into())?; + self.schedule.trigger = Some(i_event_trigger.into()); + } + Ok(self) + } + + /// Specifies a collection of named XPath queries. Each name-value pair in the collection + /// defines a unique name for a property value of the event that triggers the event trigger. + /// The property value of the event is defined as an XPath event query. + /// see https://docs.microsoft.com/en-us/windows/win32/taskschd/eventtrigger-valuequeries + pub fn value_queries( + self, + queries: Vec<(&str, &str)>, + ) -> Result> { + if let Some(trigger) = &self.schedule.trigger { + unsafe { + let i_event_trigger: IEventTrigger = trigger.cast::()?; + let i_task_named_value_collection = i_event_trigger.ValueQueries()?; + + for (name, value) in queries { + i_task_named_value_collection.Create(name, value)?; + } + } + Ok(self) + } else { + self.uninitialize(); + Err(trigger_uninitialised_error()) + } + } +} + impl ScheduleBuilder { - /// Create an idle trigger + /// Create an idle trigger. /// /// # Example /// ``` @@ -716,24 +830,29 @@ impl ScheduleBuilder { /// .trigger("MyTrigger", true) /// .days_of_months(vec![DayOfMonth::Day(1), DayOfMonth::Day(15), DayOfMonth::Day(31)]); /// ``` - pub fn days_of_months( - mut self, - days: Vec, - ) -> Result> { + pub fn days_of_months(self, days: Vec) -> Result> { if let Some(i_trigger) = &self.schedule.trigger { - let result = days.iter().any(|&x| match &x { + let result = days.iter().any(|x| match &x { DayOfMonth::Day(int) => int < &1 || int > &31, DayOfMonth::Last => false, }); + if !result { + return Err(Box::new(InvalidOperationError { + message: + "Index out of bounds. Days of month must be between 1 and 31 inclusively." + .to_string(), + })); + } + let bitwise = days.into_iter().fold(0, |acc, item| { - let day: i32 = item.into(); + let day: u32 = item.into(); acc + (1 << day - 1) }); unsafe { let i_monthly_trigger: IMonthlyTrigger = i_trigger.cast::()?; - i_monthly_trigger.SetDaysOfMonth(bitwise); + i_monthly_trigger.SetDaysOfMonth(bitwise)?; } Ok(self) @@ -752,16 +871,13 @@ impl ScheduleBuilder { /// .trigger("MyTrigger", true) /// .months_of_year(vec![Month::January, Month::June, Month::December]); /// ``` - pub fn months_of_year( - mut self, - months: Vec, - ) -> Result> { + pub fn months_of_year(self, months: Vec) -> Result> { if let Some(i_trigger) = &self.schedule.trigger { let bitwise: i16 = months.into_iter().fold(0, |acc, item| acc + item as i16); unsafe { let i_monthly_trigger: IMonthlyTrigger = i_trigger.cast::()?; - i_monthly_trigger.SetMonthsOfYear(bitwise); + i_monthly_trigger.SetMonthsOfYear(bitwise)?; } Ok(self) @@ -808,7 +924,7 @@ impl ScheduleBuilder { if let Some(i_trigger) = &self.schedule.trigger { unsafe { let i_monthly_trigger: IMonthlyTrigger = i_trigger.cast::()?; - i_monthly_trigger.SetRunOnLastDayOfMonth(is_run as i16); + i_monthly_trigger.SetRunOnLastDayOfMonth(is_run as i16)?; } Ok(self) } else { @@ -871,17 +987,14 @@ impl ScheduleBuilder { /// .trigger("MonthlyDOWTrigger", true) /// .months_of_year(vec![Month::January, Month::June, Month::December]); /// ``` - pub fn months_of_year( - mut self, - months: Vec, - ) -> Result> { + pub fn months_of_year(self, months: Vec) -> Result> { if let Some(i_trigger) = &self.schedule.trigger { let bitwise: i16 = months.into_iter().fold(0, |acc, item| acc + item as i16); unsafe { let i_monthly_dow_trigger: IMonthlyDOWTrigger = i_trigger.cast::()?; - i_monthly_dow_trigger.SetMonthsOfYear(bitwise); + i_monthly_dow_trigger.SetMonthsOfYear(bitwise)?; } Ok(self) @@ -930,7 +1043,7 @@ impl ScheduleBuilder { unsafe { let i_monthly_dow_trigger: IMonthlyDOWTrigger = trigger.cast::()?; - i_monthly_dow_trigger.SetRunOnLastWeekOfMonth(is_run as i16); + i_monthly_dow_trigger.SetRunOnLastWeekOfMonth(is_run as i16)?; } Ok(self) } else { @@ -950,7 +1063,7 @@ impl ScheduleBuilder { /// .weeks_of_month(vec![WeekOfMonth::Third]); /// ``` pub fn weeks_of_month( - mut self, + self, weeks: Vec, ) -> Result> { if let Some(trigger) = &self.schedule.trigger { @@ -958,7 +1071,7 @@ impl ScheduleBuilder { unsafe { let i_monthly_dow_trigger: IMonthlyDOWTrigger = trigger.cast::()?; - i_monthly_dow_trigger.SetWeeksOfMonth(bitwise); + i_monthly_dow_trigger.SetWeeksOfMonth(bitwise)?; } Ok(self) } else { @@ -973,9 +1086,9 @@ impl ScheduleBuilder { /// ``` /// let schedule: Schedule = Schedule::builder().new() /// .create_monthly_dow() - /// .trigger("MonthlyDOWTrigger", true); + /// .trigger("MyTrigger", true); /// ``` - pub fn trigger(mut self, id: &str, enabled: bool) -> Result> { + pub fn trigger(self, id: &str, enabled: bool) -> Result> { unsafe { let trigger = self.schedule.triggers.Create(TASK_TRIGGER_MONTHLYDOW)?; let i_monthly_dow_trigger: IMonthlyDOWTrigger = trigger.cast::()?; @@ -986,6 +1099,52 @@ impl ScheduleBuilder { } } +impl ScheduleBuilder { + /// Specifies a value that indicates the amount of time between when the user logs on and when the task is started. + /// The format for this string is PDTHMS (for example, P2DT5S is a 2 day, 5 second delay). + /// see https://docs.microsoft.com/en-us/windows/win32/taskschd/logontrigger-delay + /// + /// # Example + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_registration() + /// .trigger("MyTrigger", true) + /// .delay("P2DT5S"); + /// ``` + pub fn delay(self, delay: &str) -> Result> { + if let Some(trigger) = &self.schedule.trigger { + unsafe { + let i_registration_trigger: IRegistrationTrigger = + trigger.cast::()?; + i_registration_trigger.SetDelay(delay)?; + } + Ok(self) + } else { + self.uninitialize(); + Err(trigger_uninitialised_error()) + } + } + + /// Creates a trigger that starts a task when the task is registered or updated. + /// + /// # Example + /// ``` + /// let schedule: Schedule = Schedule::builder().new() + /// .create_registration() + /// .trigger("MyTrigger", true); + /// ``` + pub fn trigger(self, id: &str, enabled: bool) -> Result> { + unsafe { + let trigger = self.schedule.triggers.Create(TASK_TRIGGER_REGISTRATION)?; + let i_registration_trigger: IRegistrationTrigger = + trigger.cast::()?; + i_registration_trigger.SetId(id)?; + i_registration_trigger.SetEnabled(enabled.into())?; + } + Ok(self) + } +} + impl ScheduleBuilder