Skip to content

Commit 1f70b62

Browse files
Add Remove-DbaAgentJobSchedule cmdlet (#10273)
1 parent 99a4a90 commit 1f70b62

4 files changed

Lines changed: 298 additions & 0 deletions

File tree

dbatools.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@
540540
'Remove-DbaAgentAlert',
541541
'Remove-DbaAgentJob',
542542
'Remove-DbaAgentJobCategory',
543+
'Remove-DbaAgentJobSchedule',
543544
'Remove-DbaAgentJobStep',
544545
'Remove-DbaAgentOperator',
545546
'Remove-DbaAgentSchedule',

dbatools.psm1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ if ($PSVersionTable.PSVersion.Major -lt 5) {
555555
'Remove-DbaAgentJob',
556556
'New-DbaAgentJobStep',
557557
'Set-DbaAgentJobStep',
558+
'Remove-DbaAgentJobSchedule',
558559
'Remove-DbaAgentJobStep',
559560
'New-DbaAgentSchedule',
560561
'Set-DbaAgentSchedule',
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
function Remove-DbaAgentJobSchedule {
2+
<#
3+
.SYNOPSIS
4+
Detaches a schedule from a SQL Server Agent job without removing the schedule.
5+
6+
.DESCRIPTION
7+
Detaches one or more schedules from a SQL Server Agent job without deleting the schedule itself. This is equivalent to executing sp_detach_schedule in T-SQL.
8+
9+
This is particularly useful when a schedule is shared between multiple jobs and you need to stop a specific job from running on that schedule without affecting other jobs that use the same schedule. The schedule remains in SQL Server Agent and can be reattached to the job or attached to other jobs at any time.
10+
11+
Use Set-DbaAgentJob with the -Schedule parameter to reattach a schedule to a job after detaching it.
12+
13+
.PARAMETER SqlInstance
14+
The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances.
15+
16+
.PARAMETER SqlCredential
17+
Login to the target instance using alternative credentials. Accepts PowerShell credentials (Get-Credential).
18+
19+
Windows Authentication, SQL Server Authentication, Active Directory - Password, and Active Directory - Integrated are all supported.
20+
21+
For MFA support, please use Connect-DbaInstance.
22+
23+
.PARAMETER Job
24+
The name of the SQL Agent job(s) from which to detach the schedule. Required when using -SqlInstance.
25+
26+
.PARAMETER Schedule
27+
The name of the schedule(s) to detach from the job. The schedule itself is not deleted; only the association between the job and schedule is removed.
28+
29+
.PARAMETER InputObject
30+
Accepts job objects from the pipeline, typically from Get-DbaAgentJob output. Use this when you want to filter or retrieve jobs first, then pipe the results for schedule detachment.
31+
32+
.PARAMETER WhatIf
33+
If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
34+
35+
.PARAMETER Confirm
36+
If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
37+
38+
.PARAMETER EnableException
39+
By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
40+
This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
41+
Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
42+
43+
.OUTPUTS
44+
PSCustomObject
45+
46+
Returns one object per detach operation, containing the result and details.
47+
48+
Properties:
49+
- ComputerName: The computer name of the SQL Server instance
50+
- InstanceName: The SQL Server instance name
51+
- SqlInstance: The full SQL Server instance name (computer\instance)
52+
- Job: The name of the job from which the schedule was detached
53+
- Schedule: The name of the schedule that was detached
54+
- ScheduleId: The numeric ID of the schedule
55+
- ScheduleUid: The unique GUID identifier of the schedule
56+
- Status: Result of the operation ("Detached" for success, or error message for failures)
57+
- IsDetached: Boolean indicating if the schedule was successfully detached
58+
59+
.NOTES
60+
Tags: Agent, Job, JobSchedule, Schedule
61+
Author: the dbatools team + Claude
62+
63+
Website: https://dbatools.io
64+
Copyright: (c) 2018 by dbatools, licensed under MIT
65+
License: MIT https://opensource.org/licenses/MIT
66+
67+
.LINK
68+
https://dbatools.io/Remove-DbaAgentJobSchedule
69+
70+
.EXAMPLE
71+
PS C:\> Remove-DbaAgentJobSchedule -SqlInstance sql1 -Job Job1 -Schedule SharedSchedule
72+
73+
Detaches the schedule named 'SharedSchedule' from job 'Job1' on sql1. The schedule itself is not deleted and remains available for other jobs.
74+
75+
.EXAMPLE
76+
PS C:\> Remove-DbaAgentJobSchedule -SqlInstance sql1 -Job Job1 -Schedule Schedule1, Schedule2
77+
78+
Detaches multiple schedules from a single job on sql1.
79+
80+
.EXAMPLE
81+
PS C:\> Remove-DbaAgentJobSchedule -SqlInstance sql1, sql2 -Job Job1 -Schedule SharedSchedule
82+
83+
Detaches the schedule from job 'Job1' on multiple SQL Server instances.
84+
85+
.EXAMPLE
86+
PS C:\> Get-DbaAgentJob -SqlInstance sql1 -Job Job1 | Remove-DbaAgentJobSchedule -Schedule SharedSchedule
87+
88+
Detaches the schedule 'SharedSchedule' from job 'Job1' using pipeline input.
89+
90+
.EXAMPLE
91+
PS C:\> Get-DbaAgentJob -SqlInstance sql1 | Where-Object Name -like 'Maintenance*' | Remove-DbaAgentJobSchedule -Schedule SharedSchedule
92+
93+
Detaches 'SharedSchedule' from all jobs whose names start with 'Maintenance' on sql1.
94+
95+
#>
96+
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
97+
param (
98+
[DbaInstanceParameter[]]$SqlInstance,
99+
[PSCredential]$SqlCredential,
100+
[string[]]$Job,
101+
[Parameter(Mandatory)]
102+
[ValidateNotNullOrEmpty()]
103+
[string[]]$Schedule,
104+
[Parameter(ValueFromPipeline)]
105+
[Microsoft.SqlServer.Management.Smo.Agent.Job[]]$InputObject,
106+
[switch]$EnableException
107+
)
108+
begin {
109+
$jobs = @()
110+
}
111+
process {
112+
foreach ($instance in $SqlInstance) {
113+
if (-not (Test-Bound -ParameterName Job)) {
114+
Stop-Function -Message "Parameter -Job is required when using -SqlInstance" -Target $instance -Continue
115+
}
116+
117+
try {
118+
$server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential
119+
} catch {
120+
Stop-Function -Message "Failure" -Category ConnectionError -ErrorRecord $_ -Target $instance -Continue
121+
}
122+
123+
foreach ($jobName in $Job) {
124+
if ($server.JobServer.Jobs.Name -notcontains $jobName) {
125+
Stop-Function -Message "Job '$jobName' does not exist on $instance" -Target $instance -Continue
126+
}
127+
$jobs += $server.JobServer.Jobs[$jobName]
128+
}
129+
}
130+
131+
foreach ($jobObject in $InputObject) {
132+
$jobs += $jobObject
133+
}
134+
}
135+
end {
136+
# We process in the end block to prevent "Collection was modified; enumeration operation may not execute."
137+
# if job objects are directly piped from Get-DbaAgentJob.
138+
foreach ($jobObject in $jobs) {
139+
$server = $jobObject.Parent.Parent
140+
141+
foreach ($scheduleName in $Schedule) {
142+
$jobSchedule = $jobObject.JobSchedules | Where-Object { $_.Name -eq $scheduleName }
143+
144+
if (-not $jobSchedule) {
145+
Stop-Function -Message "Schedule '$scheduleName' is not attached to job '$($jobObject.Name)' on $($server.Name)" -Target $jobObject -Continue
146+
}
147+
148+
$output = [PSCustomObject]@{
149+
ComputerName = $server.ComputerName
150+
InstanceName = $server.ServiceName
151+
SqlInstance = $server.DomainInstanceName
152+
Job = $jobObject.Name
153+
Schedule = $scheduleName
154+
ScheduleId = $jobSchedule.Id
155+
ScheduleUid = $jobSchedule.ScheduleUid
156+
Status = $null
157+
IsDetached = $false
158+
}
159+
160+
if ($PSCmdlet.ShouldProcess($server, "Detaching schedule '$scheduleName' from job '$($jobObject.Name)'")) {
161+
try {
162+
Write-Message -Level Verbose -Message "Detaching schedule '$scheduleName' from job '$($jobObject.Name)' on $($server.Name)"
163+
$jobSchedule.Drop($true)
164+
$output.Status = "Detached"
165+
$output.IsDetached = $true
166+
} catch {
167+
Stop-Function -Message "Failed to detach schedule '$scheduleName' from job '$($jobObject.Name)' on $($server.Name)" -ErrorRecord $_ -Target $jobObject -Continue
168+
$output.Status = (Get-ErrorMessage -Record $_)
169+
}
170+
}
171+
172+
$output
173+
}
174+
}
175+
}
176+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0" }
2+
param(
3+
$ModuleName = "dbatools",
4+
$CommandName = "Remove-DbaAgentJobSchedule",
5+
$PSDefaultParameterValues = $TestConfig.Defaults
6+
)
7+
8+
Describe $CommandName -Tag UnitTests {
9+
Context "Parameter validation" {
10+
It "Should have the expected parameters" {
11+
$hasParameters = (Get-Command $CommandName).Parameters.Values.Name | Where-Object { $PSItem -notin ("WhatIf", "Confirm") }
12+
$expectedParameters = $TestConfig.CommonParameters
13+
$expectedParameters += @(
14+
"SqlInstance",
15+
"SqlCredential",
16+
"Job",
17+
"Schedule",
18+
"InputObject",
19+
"EnableException"
20+
)
21+
Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty
22+
}
23+
}
24+
}
25+
26+
Describe $CommandName -Tag IntegrationTests {
27+
BeforeAll {
28+
# We want to run all commands in the BeforeAll block with EnableException to ensure that the test fails if the setup fails.
29+
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
30+
31+
$jobName = "dbatoolsci_job_$(Get-Random)"
32+
$scheduleName = "dbatoolsci_schedule_$(Get-Random)"
33+
34+
$null = New-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $jobName
35+
36+
$splatSchedule = @{
37+
SqlInstance = $TestConfig.InstanceSingle
38+
Schedule = $scheduleName
39+
FrequencyType = "Daily"
40+
FrequencyInterval = 1
41+
StartTime = "010000"
42+
Force = $true
43+
}
44+
$null = New-DbaAgentSchedule @splatSchedule
45+
46+
$splatAttach = @{
47+
SqlInstance = $TestConfig.InstanceSingle
48+
Job = $jobName
49+
Schedule = $scheduleName
50+
}
51+
$null = Set-DbaAgentJob @splatAttach
52+
53+
# We want to run all commands outside of the BeforeAll block without EnableException to be able to test for specific warnings.
54+
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
55+
}
56+
57+
AfterAll {
58+
# We want to run all commands in the AfterAll block with EnableException to ensure that the test fails if the cleanup fails.
59+
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
60+
61+
$null = Remove-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $jobName
62+
$null = Remove-DbaAgentSchedule -SqlInstance $TestConfig.InstanceSingle -Schedule $scheduleName -Force
63+
64+
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
65+
}
66+
67+
Context "When detaching a schedule from a job" {
68+
It "Should detach the schedule and return the expected output" {
69+
$splatDetach = @{
70+
SqlInstance = $TestConfig.InstanceSingle
71+
Job = $jobName
72+
Schedule = $scheduleName
73+
}
74+
$result = Remove-DbaAgentJobSchedule @splatDetach
75+
$result.IsDetached | Should -Be $true
76+
$result.Job | Should -Be $jobName
77+
$result.Schedule | Should -Be $scheduleName
78+
$result.Status | Should -Be "Detached"
79+
}
80+
81+
It "Should not remove the schedule itself after detaching" {
82+
$schedule = Get-DbaAgentSchedule -SqlInstance $TestConfig.InstanceSingle -Schedule $scheduleName
83+
$schedule | Should -Not -BeNullOrEmpty
84+
}
85+
86+
It "Should warn when the schedule is not attached to the job" {
87+
$splatDetach = @{
88+
SqlInstance = $TestConfig.InstanceSingle
89+
Job = $jobName
90+
Schedule = $scheduleName
91+
WarningAction = "SilentlyContinue"
92+
}
93+
$result = Remove-DbaAgentJobSchedule @splatDetach
94+
$result | Should -BeNullOrEmpty
95+
$WarnVar | Should -BeLike "*Schedule '$scheduleName' is not attached to job '$jobName'*"
96+
}
97+
}
98+
99+
Context "When using pipeline input" {
100+
BeforeAll {
101+
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
102+
103+
# Reattach the schedule so we can test pipeline detachment
104+
$splatAttach = @{
105+
SqlInstance = $TestConfig.InstanceSingle
106+
Job = $jobName
107+
Schedule = $scheduleName
108+
}
109+
$null = Set-DbaAgentJob @splatAttach
110+
111+
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
112+
}
113+
114+
It "Should detach the schedule when job is piped in" {
115+
$result = Get-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $jobName | Remove-DbaAgentJobSchedule -Schedule $scheduleName
116+
$result.IsDetached | Should -Be $true
117+
$result.Job | Should -Be $jobName
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)