Skip to content
Merged
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
21 changes: 20 additions & 1 deletion public/Sync-DbaAvailabilityGroup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ function Sync-DbaAvailabilityGroup {
Disables all synchronized jobs on secondary replicas after copying them from the primary.
Use this when jobs should only run on the primary replica or when you need to manually control which jobs run on each replica after failover.

.PARAMETER UseJobLastModified
Passed through as -UseLastModified to Copy-DbaAgentJob for the AgentJob sync only; no other object type is affected.
Instead of skipping every job that already exists on a secondary (or recreating all of them with -Force), jobs are compared
by definition - properties, enabled state, steps and schedules - and only those that actually differ are updated, with the
primary winning when it is not older than the secondary. Jobs that differ only in enabled state are updated in place.
When combined with -Force, this switch takes precedence for jobs: existing jobs are still only updated when their definition
differs, and -Force only suppresses prompts and remaps missing job owners to sa, while -Force continues to drop and recreate
all other object types as usual.
See Copy-DbaAgentJob for the full behaviour.

.PARAMETER InputObject
Accepts availability group objects from Get-DbaAvailabilityGroup for pipeline processing.
Use this to sync multiple availability groups at once or to process specific AGs returned by filtering commands.
Expand All @@ -102,6 +112,7 @@ function Sync-DbaAvailabilityGroup {
.PARAMETER Force
Drops and recreates existing objects on secondary replicas instead of skipping them.
Use this when you need to update objects that already exist on secondaries or when objects have configuration differences that need to be synchronized.
For agent jobs, -UseJobLastModified takes precedence when both are specified.

.PARAMETER WhatIf
Shows what would happen if the command were to run. No actions are actually performed.
Expand Down Expand Up @@ -154,6 +165,12 @@ function Sync-DbaAvailabilityGroup {
PS C:\> Get-DbaAvailabilityGroup -SqlInstance sql2016a | Sync-DbaAvailabilityGroup -WhatIf

Shows what would happen if the command were to run but doesn't actually perform the action.

.EXAMPLE
PS C:\> Sync-DbaAvailabilityGroup -Primary sql2016a -AvailabilityGroup db3 -UseJobLastModified

Syncs all object types to the replicas in the db3 AG. Agent jobs that already exist on a secondary are only updated when their
definition differs from the primary; identical jobs are left untouched. All other object types sync as they normally would.
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
param (
Expand All @@ -171,6 +188,7 @@ function Sync-DbaAvailabilityGroup {
[string[]]$Job,
[string[]]$ExcludeJob,
[switch]$DisableJobOnDestination,
[switch]$UseJobLastModified,
[parameter(ValueFromPipeline)]
[Microsoft.SqlServer.Management.Smo.AvailabilityGroup[]]$InputObject,
[switch]$ExcludePassword,
Expand Down Expand Up @@ -419,6 +437,7 @@ function Sync-DbaAvailabilityGroup {
Destination = $secondaries
Force = $force
DisableOnDestination = $DisableJobOnDestination
UseLastModified = $UseJobLastModified
InputObject = $jobsToSync
}
Copy-DbaAgentJob @splatCopyJob
Expand All @@ -431,4 +450,4 @@ function Sync-DbaAvailabilityGroup {
Write-ProgressHelper -Activity $activity -Completed
}
}
}
}
118 changes: 117 additions & 1 deletion tests/Sync-DbaAvailabilityGroup.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Describe $CommandName -Tag UnitTests {
"Job",
"ExcludeJob",
"DisableJobOnDestination",
"UseJobLastModified",
"InputObject",
"ExcludePassword",
"Force",
Expand Down Expand Up @@ -380,6 +381,8 @@ Describe $CommandName -Tag UnitTests {
$Destination,
[switch]$Force,
[switch]$DisableOnDestination,
# Sync-DbaAvailabilityGroup always passes -UseLastModified from -UseJobLastModified
[switch]$UseLastModified,
$InputObject
)

Expand Down Expand Up @@ -418,4 +421,117 @@ Describe $CommandName -Tag UnitTests {
}
}
}
}
}

Describe $CommandName -Tag IntegrationTests {
BeforeAll {
# We want to run all commands in the BeforeAll block with EnableException to ensure that the test fails if the setup fails.
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true

# Sync-DbaAvailabilityGroup accepts -Primary and -Secondary without an availability group, so two standalone
# instances are enough to run the real AgentJob sync, including Copy-DbaAgentJob, against SQL Server.
$jobName = "dbatoolsci_syncag_job_$(Get-Random)"

$null = New-DbaAgentJob -SqlInstance $TestConfig.InstanceCopy1 -Job $jobName
$splatStep = @{
SqlInstance = $TestConfig.InstanceCopy1
Job = $jobName
StepName = "step1"
Subsystem = "TransactSql"
Database = "master"
Command = "SELECT 1"
}
$null = New-DbaAgentJobStep @splatStep

# Only the test job, and every object type except AgentJob, so nothing else is synced and no DAC is opened
$splatSync = @{
Primary = $TestConfig.InstanceCopy1
Secondary = $TestConfig.InstanceCopy2
Job = $jobName
UseJobLastModified = $true
Exclude = @(
"AgentAlert",
"AgentCategory",
"AgentOperator",
"AgentProxy",
"AgentSchedule",
"Credentials",
"CustomErrors",
"DatabaseMail",
"DatabaseOwner",
"LinkedServers",
"LoginPermissions",
"Logins",
"SpConfigure",
"SystemTriggers"
)
}

# Read msdb on the secondary directly so assertions don't depend on cached SMO objects
$splatDestJob = @{
SqlInstance = $TestConfig.InstanceCopy2
Database = "msdb"
Query = "
SELECT j.job_id, j.date_modified, s.command
FROM dbo.sysjobs AS j
INNER JOIN dbo.sysjobsteps AS s ON s.job_id = j.job_id
WHERE j.name = @jobName AND s.step_id = 1"
SqlParameter = @{ jobName = $jobName }
}

# We want to run all commands outside of the BeforeAll block without EnableException to be able to test for specific warnings.
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
}

AfterAll {
# We want to run all commands in the AfterAll block with EnableException to ensure that the test fails if the cleanup fails.
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true

# Pipe from Get so a job that never reached the secondary doesn't turn cleanup into a second failure
$null = Get-DbaAgentJob -SqlInstance $TestConfig.InstanceCopy1, $TestConfig.InstanceCopy2 -Job $jobName | Remove-DbaAgentJob

$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
}

Context "Agent job sync with -UseJobLastModified" {
It "creates the job on the secondary when it does not exist" {
$null = Sync-DbaAvailabilityGroup @splatSync

$WarnVar | Should -BeNullOrEmpty
$destJob = Invoke-DbaQuery @splatDestJob
$destJob.command | Should -Be "SELECT 1"
}

It "leaves an identical job untouched on a repeated sync" {
$before = Invoke-DbaQuery @splatDestJob
$before.job_id | Should -Not -BeNullOrEmpty

$null = Sync-DbaAvailabilityGroup @splatSync

$WarnVar | Should -BeNullOrEmpty
$after = Invoke-DbaQuery @splatDestJob
$after.job_id | Should -Be $before.job_id
$after.date_modified | Should -Be $before.date_modified
}

It "propagates a newer changed job definition from the primary" {
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
# Guarantee the primary's change lands after the secondary's copy was made
Start-Sleep -Seconds 2
$splatChangeStep = @{
SqlInstance = $TestConfig.InstanceCopy1
Job = $jobName
StepName = "step1"
Command = "SELECT 2"
}
$null = Set-DbaAgentJobStep @splatChangeStep
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")

$null = Sync-DbaAvailabilityGroup @splatSync

$WarnVar | Should -BeNullOrEmpty
$destJob = Invoke-DbaQuery @splatDestJob
$destJob.command | Should -Be "SELECT 2"
}
}
}
Loading