Verified issue does not already exist?
I have searched and found no existing issue
What error did you receive?
No error. $error[0] is empty. -Force just silently does nothing.
The help for -Force (lines 43-45) says it "Forces the overwrite of existing schedules on the destination instances by dropping and recreating them". The drop never runs. And if the existing destination schedule has no jobs attached, nothing is emitted for it either, so the no-op doesn't show up in the output you would check afterwards.
Steps to Reproduce
# Schedule 'NightlyETL' exists on both instances. On sql02 it has no jobs attached.
# Expected per .PARAMETER Force: dropped and recreated on sql02.
# Actual: nothing emitted for it, destination schedule unchanged.
Copy-DbaAgentSchedule -Source sql01 -Destination sql02 -Force
To be straight about provenance: I found this by reading the source, not from a live migration, so the block above is the scenario the code path describes rather than a transcript of a run I made. The control-flow walk in "Other details" is verifiable from a clone at the refs I list there. Happy to run it against a live pair if that would help confirm.
Please confirm that you are running the most recent version of dbatools
Get-Module dbatools -ListAvailable | Select -ExpandProperty Version
Major Minor Build Revision
----- ----- ----- --------
2 1 5 -1
So no, my installed copy is not the latest. I checked the source at the published refs rather than relying on it.
The defect is present on the current release. public/Copy-DbaAgentSchedule.ps1 is byte-identical on v2.8.1, v2.8.2 and v2.8.4 (current, published 2026-07-31) and on development at 79e624a. The line numbers I cite below are from that file.
It is also present in the 2.1.5 I have installed. That file is not identical to current (it predates the $destServer.JobServer.SharedSchedules.Refresh() call and some surrounding restructuring), but the -Force arm is the same shape: the ShouldProcess wrapper at line 151, the unconditional continue at 158 and the unreachable drop at 160-163 correspond to lines 168, 175 and 177-180 in the current file.
Happy to upgrade and re-confirm on the latest if that would help.
Other details or mentions
The relevant part of public/Copy-DbaAgentSchedule.ps1:
123 if ($Force) { $ConfirmPreference = 'none' } # begin block
158 if ($destSchedules.Name -contains $scheduleName) {
159 if ($Force -ne $true) { ...skip and report... continue }
167 } else { # -Force
168 if ($Pscmdlet.ShouldProcess($destinstance, "Schedule [$scheduleName] has associated jobs. Skipping.")) {
169 if (...JobSchedules.Name -contains $scheduleName) { # only reports if jobs exist
175 continue # always, jobs or not
176 } else { # only when ShouldProcess said no
177 if ($Pscmdlet.ShouldProcess($destinstance, "Dropping schedule $scheduleName and recreating")) {
180 ...Drop()
In a normal run ShouldProcess returns true, so line 175 continues and the drop block at 177-186 is never reached. Line 123 makes that certain: -Force sets $ConfirmPreference = 'none', so the line 168 ShouldProcess cannot prompt and cannot return false. The only way I can find to reach Drop() is -Force -Confirm, answering No to "has associated jobs. Skipping." and then Yes to "Dropping schedule and recreating", which nobody is going to do by accident.
I think this is a regression rather than something that was never written. Before 9f67479 (2018-07-18, "hid object output in whatif") the -Force arm was:
else { # -Force
if ($destServer.JobServer.Jobs.JobSchedules.Name -contains $scheduleName) {
...report Skipped...
continue # has jobs, skip
}
else { # no jobs
if ($Pscmdlet.ShouldProcess($destinstance, "Dropping schedule $scheduleName and recreating")) {
$destServer.JobServer.SharedSchedules[$scheduleName].Drop()
}
}
}
That commit wrapped the associated-jobs test in a ShouldProcess call. The else that had belonged to the jobs test ended up attached to the new ShouldProcess instead, so "no associated jobs, drop it" became "user declined the prompt, drop it". Given the commit title was about WhatIf output, I doubt that was intended.
Two smaller things from the same structure. Schedules with no attached jobs produce no MigrationObject at all, because the reporting block at 170-173 is inside the jobs check while the continue at 175 is outside it. That continue moved to its current position in 2af6205 ("Standardize Whatif Output", #8846, 2023-04-09). And under -WhatIf both ShouldProcess messages print, so the output claims the schedule "has associated jobs" before anything has checked whether it does, and then claims it is "Dropping schedule ... and recreating" when it isn't.
For a fix, restoring the pre-9f67479581 shape would do it: check for associated jobs first with no ShouldProcess around the read, report Skipped and continue if there are any (the "cannot be overwritten, even with Force" case in help line 45), otherwise ShouldProcess("Dropping schedule $scheduleName and recreating"), drop, and fall through to the existing create block at line 192.
For transparency: this was surfaced by static analysis of the module, then verified by hand against the source and the git history before reporting.
What PowerShell host was used when producing this error
Windows PowerShell (powershell.exe), Windows PowerShell ISE (powershell_ise.exe)
PowerShell Host Version
Name Value
---- -----
PSVersion 5.1.26100.9168
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.26100.9168
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
SQL Server Edition and Build number
SQL Server 2022.
I have not pasted SELECT @@VERSION output because no live repro was run for this report (see Steps to Reproduce) — 2022 is simply the version I have available. The behaviour described is in the PowerShell control flow of the command and does not depend on the SQL Server version, edition or build. Happy to provide full @@VERSION output from a repro pair if you would like this confirmed live.
.NET Framework Version
.NET Framework 4.8.9337.0
Verified issue does not already exist?
I have searched and found no existing issue
What error did you receive?
No error.
$error[0]is empty.-Forcejust silently does nothing.The help for
-Force(lines 43-45) says it "Forces the overwrite of existing schedules on the destination instances by dropping and recreating them". The drop never runs. And if the existing destination schedule has no jobs attached, nothing is emitted for it either, so the no-op doesn't show up in the output you would check afterwards.Steps to Reproduce
To be straight about provenance: I found this by reading the source, not from a live migration, so the block above is the scenario the code path describes rather than a transcript of a run I made. The control-flow walk in "Other details" is verifiable from a clone at the refs I list there. Happy to run it against a live pair if that would help confirm.
Please confirm that you are running the most recent version of dbatools
So no, my installed copy is not the latest. I checked the source at the published refs rather than relying on it.
The defect is present on the current release. public/Copy-DbaAgentSchedule.ps1 is byte-identical on v2.8.1, v2.8.2 and v2.8.4 (current, published 2026-07-31) and on development at 79e624a. The line numbers I cite below are from that file.
It is also present in the 2.1.5 I have installed. That file is not identical to current (it predates the
$destServer.JobServer.SharedSchedules.Refresh()call and some surrounding restructuring), but the-Forcearm is the same shape: theShouldProcesswrapper at line 151, the unconditionalcontinueat 158 and the unreachable drop at 160-163 correspond to lines 168, 175 and 177-180 in the current file.Happy to upgrade and re-confirm on the latest if that would help.
Other details or mentions
The relevant part of public/Copy-DbaAgentSchedule.ps1:
In a normal run
ShouldProcessreturns true, so line 175 continues and the drop block at 177-186 is never reached. Line 123 makes that certain:-Forcesets$ConfirmPreference = 'none', so the line 168ShouldProcesscannot prompt and cannot return false. The only way I can find to reachDrop()is-Force -Confirm, answering No to "has associated jobs. Skipping." and then Yes to "Dropping schedule and recreating", which nobody is going to do by accident.I think this is a regression rather than something that was never written. Before 9f67479 (2018-07-18, "hid object output in whatif") the
-Forcearm was:That commit wrapped the associated-jobs test in a
ShouldProcesscall. Theelsethat had belonged to the jobs test ended up attached to the newShouldProcessinstead, so "no associated jobs, drop it" became "user declined the prompt, drop it". Given the commit title was about WhatIf output, I doubt that was intended.Two smaller things from the same structure. Schedules with no attached jobs produce no MigrationObject at all, because the reporting block at 170-173 is inside the jobs check while the
continueat 175 is outside it. Thatcontinuemoved to its current position in 2af6205 ("Standardize Whatif Output", #8846, 2023-04-09). And under-WhatIfbothShouldProcessmessages print, so the output claims the schedule "has associated jobs" before anything has checked whether it does, and then claims it is "Dropping schedule ... and recreating" when it isn't.For a fix, restoring the pre-9f67479581 shape would do it: check for associated jobs first with no
ShouldProcessaround the read, report Skipped and continue if there are any (the "cannot be overwritten, even with Force" case in help line 45), otherwiseShouldProcess("Dropping schedule $scheduleName and recreating"), drop, and fall through to the existing create block at line 192.For transparency: this was surfaced by static analysis of the module, then verified by hand against the source and the git history before reporting.
What PowerShell host was used when producing this error
Windows PowerShell (powershell.exe), Windows PowerShell ISE (powershell_ise.exe)
PowerShell Host Version
SQL Server Edition and Build number
SQL Server 2022.
I have not pasted
SELECT @@VERSIONoutput because no live repro was run for this report (see Steps to Reproduce) — 2022 is simply the version I have available. The behaviour described is in the PowerShell control flow of the command and does not depend on the SQL Server version, edition or build. Happy to provide full@@VERSIONoutput from a repro pair if you would like this confirmed live..NET Framework Version