Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,15 @@
}
Unregister-ClusteredScheduledTask @unregisterClusteredScheduledTaskParameters
Write-Verbose "Verifying unregistration of clustered scheduled task '$TaskName'..."
# Use -EA Ignore: a missing task here means the unregister succeeded
# (Get-StmClusteredScheduledTask writes ClusteredScheduledTaskNotFound when
# -TaskName misses; with -EA Stop that would falsely flag success as failure).
$taskParameters = @{
TaskName = $TaskName
Cluster = $Cluster
CimSession = $clusterCimSession
ErrorAction = 'Stop'
WarningAction = 'SilentlyContinue' # Suppress the warning about the task not being found
ErrorAction = 'Ignore'
WarningAction = 'SilentlyContinue'
}
$task = Get-StmClusteredScheduledTask @taskParameters
$taskExists = $null -ne $task
Expand Down
61 changes: 55 additions & 6 deletions ScheduledTasksManager/Public/Get-StmClusteredScheduledTask.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,30 @@
Write-Verbose "Retrieving clustered scheduled tasks from cluster '$Cluster'"
$clusteredScheduledTasks = Get-ClusteredScheduledTask @clusteredScheduledTasksParameters
if ($clusteredScheduledTasks.Count -eq 0) {
Write-Warning (
"No clustered scheduled tasks found on cluster '$Cluster'. " +
'Ensure the cluster is properly configured.'
)
if ($taskNameProvided) {
$notFoundException = [System.InvalidOperationException]::new(
"Clustered scheduled task '$TaskName' not found on cluster '$Cluster'.")
$errorRecordParameters = @{
Exception = $notFoundException
ErrorId = 'ClusteredScheduledTaskNotFound'
ErrorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
TargetObject = $TaskName
Message = (
"Clustered scheduled task '$TaskName' was not found on cluster '$Cluster'."
)
RecommendedAction = (
'Verify the task name (including case) and that it is registered as a clustered task.'
)
}
$errorRecord = New-StmError @errorRecordParameters
$PSCmdlet.WriteError($errorRecord)
}
else {
Write-Warning (
"No clustered scheduled tasks found on cluster '$Cluster'. " +
'Ensure the cluster is properly configured.'
)
}
return
}
Write-Verbose "Found $($clusteredScheduledTasks.Count) clustered scheduled task(s) on cluster '$Cluster'"
Expand Down Expand Up @@ -208,12 +228,41 @@
# ScheduledTaskObject contains CIM instance references that depend on them
$taskOwnerCimSession = New-StmCimSession -ComputerName $taskOwner -Credential $Credential
Write-Verbose "Retrieving scheduled tasks from owner '$taskOwner' using CIM session"
# Suppress per-name cmdletization errors; we re-emit them as structured errors below
# so the user sees one diagnostic per missing task instead of a raw red leak.
$getScheduledTaskParameters = @{
TaskName = $taskNames
CimSession = $taskOwnerCimSession
TaskName = $taskNames
CimSession = $taskOwnerCimSession
ErrorAction = 'SilentlyContinue'
}
$scheduledTasksFromOwner = Get-ScheduledTask @getScheduledTaskParameters

# Diff requested vs returned: cluster registry references the task but the owner
# node didn't return it (task may have just failed over, or local copy is missing).
$foundTaskNames = @($scheduledTasksFromOwner | Select-Object -ExpandProperty 'TaskName')
foreach ($expectedTaskName in $taskNames) {
if ($foundTaskNames -notcontains $expectedTaskName) {
$ownerLookupException = [System.InvalidOperationException]::new(
"Owner '$taskOwner' did not return clustered task '$expectedTaskName'.")
$ownerLookupErrorParameters = @{
Exception = $ownerLookupException
ErrorId = 'ClusteredScheduledTaskOwnerLookupFailed'
ErrorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
TargetObject = $expectedTaskName
Message = (
"Clustered scheduled task '$expectedTaskName' is registered on cluster " +
"'$Cluster' but owner node '$taskOwner' did not return it. The task may " +
'have just failed over, or its local copy on the owner may be missing.'
)
RecommendedAction = (
"Verify the task exists on '$taskOwner' and that cluster ownership is consistent."
)
}
$ownerLookupErrorRecord = New-StmError @ownerLookupErrorParameters
$PSCmdlet.WriteError($ownerLookupErrorRecord)
}
}

if ($PSBoundParameters.ContainsKey('TaskState')) {
Write-Verbose "Filtering scheduled tasks by state '$TaskState' on owner '$taskOwner'"
$scheduledTasksFromOwner = $scheduledTasksFromOwner | Where-Object { $_.State -eq $TaskState }
Expand Down
25 changes: 23 additions & 2 deletions ScheduledTasksManager/Public/Get-StmClusteredScheduledTaskInfo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,29 @@
}

process {
if ($scheduledTask.Count -eq 0) {
Write-Warning "No scheduled tasks found on cluster '$Cluster' with the specified parameters."
if ($null -eq $scheduledTask -or $scheduledTask.Count -eq 0) {
if ($PSBoundParameters.ContainsKey('TaskName')) {
$unresolvedException = [System.InvalidOperationException]::new(
"Clustered scheduled task '$TaskName' could not be resolved on cluster '$Cluster'.")
$errorRecordParameters = @{
Exception = $unresolvedException
ErrorId = 'ClusteredScheduledTaskNotResolvable'
ErrorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
TargetObject = $TaskName
Message = (
"Clustered scheduled task '$TaskName' was not found on cluster '$Cluster', " +
'or its owner node could not return the task.'
)
RecommendedAction = (
'Run Get-StmClusteredScheduledTask with -Verbose to see which stage of the lookup failed.'
)
}
$errorRecord = New-StmError @errorRecordParameters
$PSCmdlet.WriteError($errorRecord)
}
else {
Write-Warning "No scheduled tasks found on cluster '$Cluster' with the specified parameters."
}
return
}
Write-Verbose "Retrieving scheduled task info for $($scheduledTask.Count) tasks on cluster '$Cluster'"
Expand Down
98 changes: 76 additions & 22 deletions tests/Get-StmClusteredScheduledTask.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -143,28 +143,6 @@ InModuleScope -ModuleName 'ScheduledTasksManager' {
}

Context 'Error handling' {
It 'Should warn when no matching clustered task found for scheduled task' {
Mock -CommandName 'Get-ClusteredScheduledTask' -MockWith {
return [PSCustomObject]@{
TaskName = 'ClusteredTask1'
CurrentOwner = 'OwnerNode1'
}
}
Mock -CommandName 'Get-ScheduledTask' -MockWith {
return [PSCustomObject]@{
TaskName = 'DifferentTask'
State = 'Ready'
}
}
Mock -CommandName 'Write-Warning' -MockWith {}

Get-StmClusteredScheduledTask -Cluster 'TestCluster'

Should -Invoke 'Write-Warning' -Times 1 -ParameterFilter {
$Message -like '*No matching clustered task found*'
}
}

It 'Should write error when retrieving tasks from owner fails' {
Mock -CommandName 'Get-ClusteredScheduledTask' -MockWith {
return [PSCustomObject]@{
Expand Down Expand Up @@ -234,5 +212,81 @@ InModuleScope -ModuleName 'ScheduledTasksManager' {
Should -Invoke 'Remove-CimSession' -Times 2 -Exactly
}
}

Context 'When a named task is not found in the cluster (Issue 1 / stage-A miss)' {
BeforeEach {
Mock -CommandName 'Get-ClusteredScheduledTask' -MockWith { return @() }
# The outer BeforeEach mocks New-StmCimSession to return a string sentinel,
# so Remove-CimSession in the end block would fail without this mock.
Mock -CommandName 'Remove-CimSession' -MockWith {}
}

It 'Should write a structured non-terminating error (not a warning) when -TaskName is given' {
$err = $null
$warn = $null
$namedParameters = @{
Cluster = 'TestCluster'
TaskName = 'NoSuchTask'
ErrorVariable = 'err'
WarningVariable = 'warn'
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
Get-StmClusteredScheduledTask @namedParameters

$err.Count | Should -Be 1
$err[0].FullyQualifiedErrorId | Should -Match 'ClusteredScheduledTaskNotFound'
$err[0].CategoryInfo.Category | Should -Be 'ObjectNotFound'
$err[0].TargetObject | Should -Be 'NoSuchTask'
$warn.Count | Should -Be 0
}

It 'Should still emit a warning (and no error) when -TaskName is omitted (bulk path)' {
$err = $null
$warn = $null
$bulkParameters = @{
Cluster = 'TestCluster'
ErrorVariable = 'err'
WarningVariable = 'warn'
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
Get-StmClusteredScheduledTask @bulkParameters

$warn.Count | Should -BeGreaterThan 0
$warn[0].Message | Should -Match 'No clustered scheduled tasks found'
$err.Count | Should -Be 0
}
}

Context 'When the cluster claims an owner but the owner does not return the task (Issue 1 / stage-B miss)' {
BeforeEach {
Mock -CommandName 'Get-ClusteredScheduledTask' -MockWith {
return [PSCustomObject]@{
TaskName = 'StragglerTask'
CurrentOwner = 'OwnerNode1'
}
}
# Owner returns no tasks for the requested name — the stage-B miss scenario
Mock -CommandName 'Get-ScheduledTask' -MockWith { return @() }
Mock -CommandName 'Remove-CimSession' -MockWith {}
}

It 'Should write a structured ClusteredScheduledTaskOwnerLookupFailed error' {
$err = $null
$stageBParameters = @{
Cluster = 'TestCluster'
TaskName = 'StragglerTask'
ErrorVariable = 'err'
ErrorAction = 'SilentlyContinue'
}
Get-StmClusteredScheduledTask @stageBParameters

$err.FullyQualifiedErrorId |
Should -Contain 'ClusteredScheduledTaskOwnerLookupFailed,Get-StmClusteredScheduledTask'
$matchingErr = $err | Where-Object { $_.FullyQualifiedErrorId -match 'OwnerLookupFailed' }
$matchingErr.TargetObject | Should -Be 'StragglerTask'
}
}
}
}
43 changes: 43 additions & 0 deletions tests/Get-StmClusteredScheduledTaskInfo.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -310,5 +310,48 @@ InModuleScope -ModuleName 'ScheduledTasksManager' {
$verboseOutput | Should -Match "Filtering tasks by name.*TestTask"
}
}

Context 'When a named task cannot be resolved (Issues 1 + 2)' {
BeforeEach {
Mock -CommandName 'Get-StmClusteredScheduledTask' -MockWith { return $null }
}

It 'Should write a structured non-terminating error (not a warning) when -TaskName is given' {
$err = $null
$warn = $null
$namedParameters = @{
Cluster = 'TestCluster'
TaskName = 'Missing'
ErrorVariable = 'err'
WarningVariable = 'warn'
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
Get-StmClusteredScheduledTaskInfo @namedParameters

$err.Count | Should -Be 1
$err[0].FullyQualifiedErrorId | Should -Match 'ClusteredScheduledTaskNotResolvable'
$err[0].CategoryInfo.Category | Should -Be 'ObjectNotFound'
$err[0].TargetObject | Should -Be 'Missing'
$warn.Count | Should -Be 0
}

It 'Should still emit a warning (and no error) when -TaskName is omitted (bulk path)' {
$err = $null
$warn = $null
$bulkParameters = @{
Cluster = 'TestCluster'
ErrorVariable = 'err'
WarningVariable = 'warn'
ErrorAction = 'SilentlyContinue'
WarningAction = 'SilentlyContinue'
}
Get-StmClusteredScheduledTaskInfo @bulkParameters

$warn.Count | Should -BeGreaterThan 0
$warn[0].Message | Should -Match 'No scheduled tasks found on cluster'
$err.Count | Should -Be 0
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ AfterAll {
$task = Get-StmClusteredScheduledTask `
-Cluster $ClusterName `
-TaskName $TaskName `
-ErrorAction SilentlyContinue `
-ErrorAction Ignore `
-WarningAction SilentlyContinue

if ($task) {
Expand Down Expand Up @@ -342,7 +342,7 @@ Describe 'Clustered Scheduled Task Integration Tests' -Skip:$script:SkipIntegrat
Get-StmClusteredScheduledTask `
-Cluster $ClusterName `
-TaskName $TaskName `
-ErrorAction SilentlyContinue `
-ErrorAction Ignore `
-WarningAction SilentlyContinue
} -ArgumentList @($script:LabModulePath, $script:ClusterName, $script:TestTaskName) -PassThru

Expand Down Expand Up @@ -411,7 +411,7 @@ Describe 'Clustered Scheduled Task Integration Tests' -Skip:$script:SkipIntegrat
$existing = Get-StmClusteredScheduledTask `
-Cluster $ClusterName `
-TaskName $TaskName `
-ErrorAction SilentlyContinue `
-ErrorAction Ignore `
-WarningAction SilentlyContinue
if ($existing) {
Unregister-StmClusteredScheduledTask `
Expand Down Expand Up @@ -546,7 +546,7 @@ Describe 'Clustered Scheduled Task Integration Tests' -Skip:$script:SkipIntegrat
Get-StmClusteredScheduledTask `
-Cluster $ClusterName `
-TaskName $TaskName `
-ErrorAction SilentlyContinue `
-ErrorAction Ignore `
-WarningAction SilentlyContinue
} -ArgumentList @($script:LabModulePath, $script:ClusterName, $script:TestTaskName) -PassThru

Expand Down
Loading