From c8977b00d4ecdc8e9c4f380995d459b0988d35f5 Mon Sep 17 00:00:00 2001
From: Chris Hill <53898223+Borgquite@users.noreply.github.com>
Date: Sat, 31 Aug 2024 07:53:58 +0100
Subject: [PATCH] ScheduledTask: Add Event ValueQueries support, prevent
DaysOfWeek ordering causing drift (#431)
---
CHANGELOG.md | 15 +++
README.md | 2 +-
appveyor.yml | 2 +-
azure-pipelines.yml | 3 +-
.../DSC_ScheduledTask/DSC_ScheduledTask.psm1 | 95 ++++++++++++++++++-
.../DSC_ScheduledTask.schema.mof | 1 +
...ask_CreateScheduledTasksOnEvent_Config.ps1 | 7 +-
.../Integration/DSC_ScheduledTask.config.ps1 | 21 +++-
tests/Unit/DSC_ScheduledTask.Tests.ps1 | 44 +++++++++
9 files changed, 179 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f39c6d78..f5ffd9bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+
+- ScheduledTask
+ - Added support for configuring Event ValueQueries, allowing the triggering event to be
+ parsed for values which are sent to the scheduled task script.
+ Fixes [Issue #392](/~https://github.com/dsccommunity/ComputerManagementDsc/issues/392).
+
+### Fixed
+
+- ScheduledTask
+ - Resolved an issue where DaysOfWeek array ordering can cause configuration drift.
+ Fixes [Issue #354](/~https://github.com/dsccommunity/ComputerManagementDsc/issues/354).
+- Update build process to pin GitVersion to 5.* to resolve errors
+ (/~https://github.com/gaelcolas/Sampler/issues/477).
+
### Changed
- CI Pipeline
diff --git a/README.md b/README.md
index f3b14933..2a258312 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ The **ComputerManagementDsc** module contains the following resources:
settings on the local machine.
- **SmbShare**: This resource is used to manage SMB shares on a machine.
- **SystemLocale**: This resource is used to set the system locale on a
- Windows machine
+ Windows machine.
- **TimeZone**: This resource is used for setting the time zone on a machine.
- **UserAccountControl**: This resource allows you to configure the notification
level or granularly configure the User Account Control for the computer.
diff --git a/appveyor.yml b/appveyor.yml
index c4f83055..6c74f473 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -42,7 +42,7 @@ build_script:
if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return }
# Set module version using GitVersion
- dotnet tool install --global GitVersion.Tool
+ dotnet tool install --global GitVersion.Tool --version 5.*
$env:IGNORE_NORMALISATION_GIT_HEAD_MOVE = 1
dotnet-gitversion
$gitVersionObject = dotnet-gitversion | ConvertFrom-Json
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 4420aa40..ebc2919a 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -27,7 +27,7 @@ stages:
vmImage: 'windows-latest'
steps:
- pwsh: |
- dotnet tool install --global GitVersion.Tool
+ dotnet tool install --global GitVersion.Tool --version 5.*
$gitVersionObject = dotnet-gitversion | ConvertFrom-Json
$gitVersionObject.PSObject.Properties.ForEach{
Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'."
@@ -309,4 +309,3 @@ stages:
GitHubToken: $(GitHubToken)
ReleaseBranch: main
MainGitBranch: main
-
diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1 b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1
index 0e0694ea..29ea8db3 100644
--- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1
+++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.psm1
@@ -228,6 +228,12 @@ function Get-TargetResource
valid in combination with the OnEvent Schedule Type. For the query schema please check:
https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema
+ .PARAMETER EventValueQueries
+ Specifies event value queries. This parameter is only valid in combination with the OnEvent
+ Schedule Type. Receives a hashtable where the key is a property value for an event and
+ the value is an XPath event query. For more detailed syntax check:
+ https://learn.microsoft.com/en-us/windows/win32/taskschd/eventtrigger-valuequeries.
+
.PARAMETER Delay
The time to wait after an event based trigger was triggered. This parameter is only
valid in combination with the OnEvent Schedule Type.
@@ -424,6 +430,10 @@ function Set-TargetResource
[System.String]
$EventSubscription,
+ [Parameter()]
+ [Microsoft.Management.Infrastructure.CimInstance[]]
+ $EventValueQueries,
+
[Parameter()]
[System.String]
$Delay = '00:00:00'
@@ -689,6 +699,7 @@ function Set-TargetResource
$trigger = New-CimInstance -CimClass $cimTriggerClass -ClientOnly
$trigger.Enabled = $true
$trigger.Subscription = $EventSubscription
+ $trigger.ValueQueries = ConvertTo-TaskNamedValuePairCollectionFromKeyValuePairArray -Array $EventValueQueries
}
}
@@ -1120,6 +1131,12 @@ function Set-TargetResource
valid in combination with the OnEvent Schedule Type. For the query schema please check:
https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema
+ .PARAMETER EventValueQueries
+ Specifies event value queries. This parameter is only valid in combination with the OnEvent
+ Schedule Type. Receives a hashtable where the key is a property value for an event and
+ the value is an XPath event query. For more detailed syntax check:
+ https://learn.microsoft.com/en-us/windows/win32/taskschd/eventtrigger-valuequeries.
+
.PARAMETER Delay
The time to wait after an event based trigger was triggered. This parameter is only
valid in combination with the OnEvent Schedule Type.
@@ -1317,6 +1334,10 @@ function Test-TargetResource
[System.String]
$EventSubscription,
+ [Parameter()]
+ [Microsoft.Management.Infrastructure.CimInstance[]]
+ $EventValueQueries,
+
[Parameter()]
[System.String]
$Delay = '00:00:00'
@@ -1486,8 +1507,8 @@ function Test-TargetResource
The WeeksInterval parameter of this function defaults to 1,
even though the value of the WeeksInterval property maybe
unset/undefined in the object $currentValues returned from
- Get-TargetResouce. To avoid Test-TargetResouce returning false
- and generating spurious calls to Set-TargetResouce, default
+ Get-TargetResource. To avoid Test-TargetResource returning false
+ and generating spurious calls to Set-TargetResource, default
an undefined $currentValues.WeeksInterval to the value of
$WeeksInterval.
#>
@@ -1525,7 +1546,7 @@ function Test-TargetResource
{
<#
Initialise a missing or null Verbose to avoid spurious
- calls to Set-TargetResouce
+ calls to Set-TargetResource
#>
$currentValues.Add('Verbose', $desiredValues['Verbose'])
}
@@ -1535,6 +1556,7 @@ function Test-TargetResource
return Test-DscParameterState `
-CurrentValues $currentValues `
-DesiredValues $desiredValues `
+ -SortArrayValues `
-Verbose:$VerbosePreference
}
@@ -1922,6 +1944,7 @@ function Get-CurrentResource
RunLevel = [System.String] $task.Principal.RunLevel
LogonType = [System.String] $task.Principal.LogonType
EventSubscription = $trigger.Subscription
+ EventValueQueries = ConvertTo-HashtableFromTaskNamedValuePairCollection -Array $trigger.ValueQueries
Delay = ConvertTo-TimeSpanStringFromScheduledTaskString -TimeSpan $trigger.Delay
}
@@ -1943,6 +1966,72 @@ function Get-CurrentResource
return $result
}
+<#
+ .SYNOPSIS
+ Converts CimInstance array of type MSFT_TaskNamedValue to hashtable
+
+ .PARAMETER Array
+ The array of MSFT_TaskNamedValue to convert to a hashtable.
+#>
+function ConvertTo-HashtableFromTaskNamedValuePairCollection
+{
+ [CmdletBinding()]
+ [OutputType([System.Collections.Hashtable])]
+ param
+ (
+ [Parameter()]
+ [Microsoft.Management.Infrastructure.CimInstance[]]
+ $Array
+ )
+
+ $hashtable = @{}
+
+ foreach ($item in $Array)
+ {
+ $hashtable += @{
+ $item.Name = $item.Value
+ }
+ }
+
+ return $hashtable
+}
+
+<#
+ .SYNOPSIS
+ Converts CimInstance array of type MSFT_KeyValuePair to array of type MSFT_TaskNamedValue
+
+ .PARAMETER Array
+ The array of MSFT_KeyValuePair to convert to an array of type MSFT_TaskNamedValue.
+#>
+function ConvertTo-TaskNamedValuePairCollectionFromKeyValuePairArray
+{
+ [CmdletBinding()]
+ [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])]
+ param
+ (
+ [Parameter()]
+ [Microsoft.Management.Infrastructure.CimInstance[]]
+ $Array
+ )
+
+ $cimNamedValueClass = Get-CimClass -ClassName MSFT_TaskNamedValue -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskNamedValue
+
+ $namedValueArray = [Microsoft.Management.Infrastructure.CimInstance[]]@()
+
+ foreach ($item in $Array)
+ {
+ $namedValue = New-CimInstance -CimClass $cimNamedValueClass `
+ -Property @{
+ Name = $item.key
+ Value = $item.Value
+ } `
+ -ClientOnly
+ $namedValueArray += $namedValue
+ }
+
+ return $namedValueArray
+}
+
<#
.SYNOPSIS
Test if a date string contains a time zone.
diff --git a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof
index 0c6957ea..ac62fd48 100644
--- a/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof
+++ b/source/DSCResources/DSC_ScheduledTask/DSC_ScheduledTask.schema.mof
@@ -46,5 +46,6 @@ class DSC_ScheduledTask : OMI_BaseResource
[Write, Description("Specifies the level of user rights that Task Scheduler uses to run the tasks that are associated with the principal. Defaults to 'Limited'."), ValueMap{"Limited","Highest"}, Values{"Limited","Highest"}] String RunLevel;
[Write, Description("Specifies the security logon method that Task Scheduler uses to run the tasks that are associated with the principal."), ValueMap{"Group","Interactive","InteractiveOrPassword","None","Password","S4U","ServiceAccount"}, Values{"Group","Interactive","InteractiveOrPassword","None","Password","S4U","ServiceAccount"}] String LogonType;
[Write, Description("Specifies the EventSubscription in XML. This can be easily generated using the Windows Eventlog Viewer. For the query schema please check: https://docs.microsoft.com/en-us/windows/desktop/WES/queryschema-schema. Can only be used in combination with ScheduleType OnEvent.")] String EventSubscription;
+ [Write, Description("Specifies the EventValueQueries. Receives a hashtable where the key is a property value for an event and the value is an XPath event query. For more detailed syntax check: https://learn.microsoft.com/en-us/windows/win32/taskschd/eventtrigger-valuequeries."), EmbeddedInstance("MSFT_KeyValuePair")] String EventValueQueries[];
[Write, Description("Specifies a delay to the start of the trigger. The delay is a static delay before the task is executed. Can only be used in combination with ScheduleType OnEvent.")] String Delay;
};
diff --git a/source/Examples/Resources/ScheduledTask/13-ScheduledTask_CreateScheduledTasksOnEvent_Config.ps1 b/source/Examples/Resources/ScheduledTask/13-ScheduledTask_CreateScheduledTasksOnEvent_Config.ps1
index f606213e..5d6ad7bd 100644
--- a/source/Examples/Resources/ScheduledTask/13-ScheduledTask_CreateScheduledTasksOnEvent_Config.ps1
+++ b/source/Examples/Resources/ScheduledTask/13-ScheduledTask_CreateScheduledTasksOnEvent_Config.ps1
@@ -37,8 +37,13 @@ Configuration ScheduledTask_CreateScheduledTasksOnEvent_Config
Ensure = 'Present'
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
- ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''Worked!'''
+ ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''$(Service) $(DependsOnService) $(ErrorCode) Worked!'''
EventSubscription = ''
+ EventValueQueries = @{
+ "Service" = "Event/EventData/Data[@Name='param1']"
+ "DependsOnService" = "Event/EventData/Data[@Name='param2']"
+ "ErrorCode" = "Event/EventData/Data[@Name='param3']"
+ }
Delay = '00:00:30'
}
}
diff --git a/tests/Integration/DSC_ScheduledTask.config.ps1 b/tests/Integration/DSC_ScheduledTask.config.ps1
index df114a44..9cc1bac8 100644
--- a/tests/Integration/DSC_ScheduledTask.config.ps1
+++ b/tests/Integration/DSC_ScheduledTask.config.ps1
@@ -252,8 +252,13 @@ Configuration ScheduledTaskOnEventAdd
Ensure = 'Present'
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
- ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''Worked!'''
+ ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''$(Service) $(DependsOnService) $(ErrorCode) Worked!'''
EventSubscription = ''
+ EventValueQueries = @{
+ "Service" = "Event/EventData/Data[@Name='param1']"
+ "DependsOnService" = "Event/EventData/Data[@Name='param2']"
+ "ErrorCode" = "Event/EventData/Data[@Name='param3']"
+ }
Delay = '00:00:30'
}
}
@@ -456,8 +461,13 @@ Configuration ScheduledTaskOnEventMod
Ensure = 'Present'
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
- ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''Worked!'''
+ ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''$(Service) $(DependsOnService) $(ErrorCode) Worked!'''
EventSubscription = ''
+ EventValueQueries = @{
+ "Service" = "Event/EventData/Data[@Name='param1']"
+ "DependsOnService" = "Event/EventData/Data[@Name='param2']"
+ "ErrorCode" = "Event/EventData/Data[@Name='param3']"
+ }
Delay = '00:00:45'
}
}
@@ -653,8 +663,13 @@ Configuration ScheduledTaskOnEventDel
Ensure = 'Absent'
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
- ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''Worked!'''
+ ActionArguments = '-Command Set-Content -Path c:\temp\seeme.txt -Value ''$(Service) $(DependsOnService) $(ErrorCode) Worked!'''
EventSubscription = ''
+ EventValueQueries = @{
+ "Service" = "Event/EventData/Data[@Name='param1']"
+ "DependsOnService" = "Event/EventData/Data[@Name='param2']"
+ "ErrorCode" = "Event/EventData/Data[@Name='param3']"
+ }
Delay = '00:00:30'
}
}
diff --git a/tests/Unit/DSC_ScheduledTask.Tests.ps1 b/tests/Unit/DSC_ScheduledTask.Tests.ps1
index 95dc9bd0..3a86a241 100644
--- a/tests/Unit/DSC_ScheduledTask.Tests.ps1
+++ b/tests/Unit/DSC_ScheduledTask.Tests.ps1
@@ -1599,6 +1599,13 @@ try
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
EventSubscription = ''
+ EventValueQueries = [Microsoft.Management.Infrastructure.CimInstance[]] (
+ ConvertTo-CimInstance -Hashtable @{
+ Service = "Event/EventData/Data[@Name='param1']"
+ DependsOnService = "Event/EventData/Data[@Name='param2']"
+ ErrorCode = "Event/EventData/Data[@Name='param3']"
+ }
+ )
Delay = '00:01:00'
Enable = $true
Verbose = $true
@@ -1614,6 +1621,17 @@ try
Triggers = [pscustomobject] @{
Delay = 'PT1M'
Subscription = $testParameters.EventSubscription
+ ValueQueries = @(
+ $testParameters.EventValueQueries | ForEach-Object {
+ New-CimInstance -ClassName MSFT_TaskNamedValue `
+ -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskNamedValue `
+ -Property @{
+ Name = $_.Key
+ Value = $_.Value
+ } `
+ -ClientOnly
+ }
+ )
CimClass = @{
CimClassName = 'MSFT_TaskEventTrigger'
}
@@ -1631,6 +1649,7 @@ try
$result.Ensure | Should -Be 'Present'
$result.ScheduleType | Should -Be 'OnEvent'
$result.EventSubscription | Should -Be $testParameters.EventSubscription
+ Test-DscParameterState -CurrentValues $result.EventValueQueries -DesiredValues $testParameters.EventValueQueries | Should -BeTrue
$result.Delay | Should -Be $testParameters.Delay
}
@@ -1644,6 +1663,13 @@ try
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
EventSubscription = ''
+ EventValueQueries = [Microsoft.Management.Infrastructure.CimInstance[]] (
+ ConvertTo-CimInstance -Hashtable @{
+ Service = "Event/EventData/Data[@Name='param1']"
+ DependsOnService = "Event/EventData/Data[@Name='param2']"
+ ErrorCode = "Event/EventData/Data[@Name='param3']"
+ }
+ )
Delay = '00:01:00'
Enable = $true
Verbose = $true
@@ -1671,6 +1697,13 @@ try
ScheduleType = 'OnEvent'
ActionExecutable = 'C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe'
EventSubscription = ''
+ EventValueQueries = [Microsoft.Management.Infrastructure.CimInstance[]] (
+ ConvertTo-CimInstance -Hashtable @{
+ Service = "Event/EventData/Data[@Name='param1']"
+ DependsOnService = "Event/EventData/Data[@Name='param2']"
+ ErrorCode = "Event/EventData/Data[@Name='param3']"
+ }
+ )
Delay = '00:05:00'
Enable = $true
Verbose = $true
@@ -1686,6 +1719,17 @@ try
Triggers = [pscustomobject] @{
Delay = 'PT1M'
Subscription = ''
+ ValueQueries = @(
+ $testParameters.EventValueQueries | Select-Object -SkipLast 1 | ForEach-Object {
+ New-CimInstance -ClassName MSFT_TaskNamedValue `
+ -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskNamedValue `
+ -Property @{
+ Name = $_.Key
+ Value = $_.Value
+ } `
+ -ClientOnly
+ }
+ )
CimClass = @{
CimClassName = 'MSFT_TaskEventTrigger'
}