You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #42 brought Get-StmClusteredScheduledTask -TaskName 'X' in line with the Microsoft cmdlet convention: when the named task doesn't exist, it now writes a structured non-terminating ObjectNotFound error (FQEI ClusteredScheduledTaskNotFound). This matches Get-Service -Name, Get-Process -Name, Get-Item, and the native Get-ScheduledTask -TaskName.
This issue is the symmetric cleanup on the write side. The Unregister-* (and likely Disable-*) cmdlets currently throw terminating errors when their target is missing, which creates an asymmetric and unfriendly contract.
The wrapper is more aggressive than the primitive it wraps. Same input ("not found"), opposite contract.
Concrete consequences:
Idempotent cleanup is impossible without try/catch boilerplate:
# Today: probe-then-act (TOCTOU window on a cluster)$task=Get-StmClusteredScheduledTask-Cluster $C-TaskName $T-EA Ignore
if ($task) { Unregister-StmClusteredScheduledTask-Cluster $C-TaskName $T }
# Desired: one lineUnregister-StmClusteredScheduledTask-Cluster $C-TaskName $T-EA Ignore
The integration test in tests/Integration/ClusteredScheduledTask.Integration.Tests.ps1 carries the probe pattern in 4 places (AfterAll at line 150, and the after-disable / before-Set / after-unregister verifications). The -EA Ignore flips in PR fix(cluster): surface named-task lookup failures as errors instead of silent partial results #42 were a workaround precisely because Unregister isn't idempotent.
Callers cleaning up unknown state must wrap every Unregister/Disable call in try/catch.
Proposed change
In the "target missing" path of each ensure-absent cmdlet, replace $PSCmdlet.ThrowTerminatingError(...) with $PSCmdlet.WriteError(...) using [ErrorCategory]::ObjectNotFound. Other failure modes (CIM session failure, permission denied, etc.) remain terminating.
For Unregister-StmClusteredScheduledTask specifically, this means catching the native cmdlet's CmdletizationQuery_NotFound_TaskName (or its wrapped form) in the existing catch block at Unregister-StmClusteredScheduledTask.ps1:126–143 and downgrading only that specific case, leaving the generic-failure path terminating.
Cmdlets to audit
Cmdlet
Current "missing target" behavior
Should be
Unregister-StmClusteredScheduledTask
Terminating ClusteredTaskUnregistrationFailed
Non-terminating ObjectNotFound
Unregister-StmScheduledTask
TBD — verify
Non-terminating ObjectNotFound if currently terminating
Disable-StmClusteredScheduledTask
TBD — verify
Non-terminating ObjectNotFound if currently terminating
Disable-StmScheduledTask
TBD — verify
Non-terminating ObjectNotFound if currently terminating
The Stop-* case is genuinely debatable: "stop X" implies a runtime state change, where "X doesn't exist" might legitimately be a user error worth surfacing terminating. Remove-* / Unregister-* / Disable-* are state-assertions ("ensure absent" / "ensure disabled"), which is the strongest case for idempotency. Decide per-cmdlet, not as a blanket policy.
Background
PR #42 brought
Get-StmClusteredScheduledTask -TaskName 'X'in line with the Microsoft cmdlet convention: when the named task doesn't exist, it now writes a structured non-terminatingObjectNotFounderror (FQEIClusteredScheduledTaskNotFound). This matchesGet-Service -Name,Get-Process -Name,Get-Item, and the nativeGet-ScheduledTask -TaskName.This issue is the symmetric cleanup on the write side. The
Unregister-*(and likelyDisable-*) cmdlets currently throw terminating errors when their target is missing, which creates an asymmetric and unfriendly contract.Problem
-EA Ignore?Unregister-ScheduledTask -TaskName 'missing'ObjectNotFound, FQEICmdletizationQuery_NotFound_TaskName,Unregister-ScheduledTaskUnregister-StmClusteredScheduledTask -TaskName 'missing'ClusteredTaskUnregistrationFailed,Unregister-StmClusteredScheduledTaskThe wrapper is more aggressive than the primitive it wraps. Same input ("not found"), opposite contract.
Concrete consequences:
try/catchboilerplate:tests/Integration/ClusteredScheduledTask.Integration.Tests.ps1carries the probe pattern in 4 places (AfterAllat line 150, and the after-disable / before-Set / after-unregister verifications). The-EA Ignoreflips in PR fix(cluster): surface named-task lookup failures as errors instead of silent partial results #42 were a workaround precisely because Unregister isn't idempotent.try/catch.Proposed change
In the "target missing" path of each ensure-absent cmdlet, replace
$PSCmdlet.ThrowTerminatingError(...)with$PSCmdlet.WriteError(...)using[ErrorCategory]::ObjectNotFound. Other failure modes (CIM session failure, permission denied, etc.) remain terminating.For
Unregister-StmClusteredScheduledTaskspecifically, this means catching the native cmdlet'sCmdletizationQuery_NotFound_TaskName(or its wrapped form) in the existing catch block atUnregister-StmClusteredScheduledTask.ps1:126–143and downgrading only that specific case, leaving the generic-failure path terminating.Cmdlets to audit
Unregister-StmClusteredScheduledTaskClusteredTaskUnregistrationFailedObjectNotFoundUnregister-StmScheduledTaskObjectNotFoundif currently terminatingDisable-StmClusteredScheduledTaskObjectNotFoundif currently terminatingDisable-StmScheduledTaskObjectNotFoundif currently terminatingStop-StmClusteredScheduledTaskTaskNotFound(Stop-StmClusteredScheduledTask.ps1:131–139)Stop-StmScheduledTaskThe
Stop-*case is genuinely debatable: "stop X" implies a runtime state change, where "X doesn't exist" might legitimately be a user error worth surfacing terminating.Remove-*/Unregister-*/Disable-*are state-assertions ("ensure absent" / "ensure disabled"), which is the strongest case for idempotency. Decide per-cmdlet, not as a blanket policy.Acceptance criteria
Unregister-StmClusteredScheduledTask -TaskName <missing> -Cluster <real> -EA Ignorereturns silently, no exception,\$Error.Count == 0.-EA) writes a single non-terminating error: FQEI matchesClusteredScheduledTaskNotFound,Unregister-StmClusteredScheduledTask(or agreed equivalent),CategoryInfo.Category == ObjectNotFound,TargetObject == \$TaskName.Unregister-X -EA Ignoreat all 4 sites.References
Get-*side of this contract.Ignoreonly suppresses non-terminating errors;ThrowTerminatingErrorignores it.Unregister-ScheduledTask— verified to write non-terminatingObjectNotFoundfor missing tasks.Remove-Item -Force— established precedent for idempotent removal.Out of scope