Skip to content
Open
61 changes: 50 additions & 11 deletions public/Copy-DbaDatabase.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ function Copy-DbaDatabase {
Sets source databases to read-only before migration to prevent data changes during the process.
Use this to ensure data consistency when databases must remain accessible at the source during migration.

.PARAMETER SetSourceOffline
Sets source databases offline before migration to prevent any connections during the process.
Use this to ensure complete isolation when databases must be completely inaccessible at the source during migration.
When combined with -Reattach, databases are brought back online after being reattached to the source.

.PARAMETER ReuseSourceFolderStructure
Maintains the exact file path structure from the source instance on the destination.
Use this when destination servers have identical drive layouts or when preserving specific organizational folder structures.
Expand Down Expand Up @@ -130,10 +135,6 @@ function Copy-DbaDatabase {
Use this to distinguish migrated databases (e.g., 'DEV_' prefix for development copies).
Cannot be used together with -NewName parameter.

.PARAMETER SetSourceOffline
Sets source databases to offline status after successful migration.
Use this for cutover scenarios where source databases should be unavailable after migration.

.PARAMETER KeepCDC
Preserves Change Data Capture (CDC) configuration and data during migration.
Use this when destination databases need to maintain CDC tracking for auditing or replication.
Expand Down Expand Up @@ -258,6 +259,9 @@ function Copy-DbaDatabase {
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$SetSourceReadOnly,
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
[switch]$SetSourceOffline,
[Alias("ReuseFolderStructure")]
[parameter(ParameterSetName = "DbBackup")]
[parameter(ParameterSetName = "DbAttachDetach")]
Expand All @@ -276,7 +280,6 @@ function Copy-DbaDatabase {
[switch]$KeepCDC,
[parameter(ParameterSetName = "DbBackup")]
[switch]$KeepReplication,
[switch]$SetSourceOffline,
[string]$NewName,
[string]$Prefix,
[switch]$Force,
Expand Down Expand Up @@ -1172,6 +1175,7 @@ function Copy-DbaDatabase {
}

$sourceDbReadOnly = $sourceServer.Databases[$dbName].ReadOnly
$sourceDbOffline = $sourceServer.Databases[$dbName].Status -like "*Offline*"

if ($SetSourceReadOnly) {
If ($Pscmdlet.ShouldProcess($source, "Set $dbName to read-only")) {
Expand All @@ -1184,6 +1188,18 @@ function Copy-DbaDatabase {
}
}

if ($SetSourceOffline -and $DetachAttach) {
# For DetachAttach, set offline before detach to kill connections
If ($Pscmdlet.ShouldProcess($source, "Set $dbName to offline")) {
Write-Message -Level Verbose -Message "Setting database to offline."
try {
$result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -Offline -EnableException -Force
} catch {
Stop-Function -Continue -Message "Couldn't set database to offline. Aborting routine for this database" -ErrorRecord $_
}
}
}

if ($BackupRestore) {
if ($UseLastBackup) {
$whatifmsg = "Gathering last backup information for $dbName from $Source and restoring"
Expand Down Expand Up @@ -1235,6 +1251,17 @@ function Copy-DbaDatabase {
$backupCollection += $backupTmpResult
}
}

# For BackupRestore, set source offline after backup completes but before restore
if ($SetSourceOffline) {
Write-Message -Level Verbose -Message "Setting source database $dbName to offline after backup."
try {
$null = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -Offline -EnableException -Force
} catch {
Stop-Function -Continue -Message "Couldn't set database to offline after backup. Aborting routine for this database" -ErrorRecord $_
}
}

Write-Message -Level Verbose -Message "Reuse = $ReuseSourceFolderStructure."
try {
$msg = $null
Expand Down Expand Up @@ -1305,6 +1332,16 @@ function Copy-DbaDatabase {
}
}

if ($SetSourceOffline) {
If ($Pscmdlet.ShouldProcess($destServer.Name, "Set $dbName to online after source was set to offline")) {
try {
$null = Set-DbaDbState -SqlInstance $destServer -Database $dbName -Online -EnableException -Force
} catch {
Stop-Function -Message "Couldn't set $dbName to online on $($destserver.Name)" -ErrorRecord $_
}
}
}

$dbFinish = Get-Date
if ($NoRecovery -eq $false) {
If ($Pscmdlet.ShouldProcess($destServer.Name, "Setting db owner to $dbowner for $destinationDbName")) {
Expand Down Expand Up @@ -1361,6 +1398,14 @@ function Copy-DbaDatabase {
Stop-Function -Message "Couldn't set database to read-only" -ErrorRecord $_
}
}

if ($SetSourceOffline -or $sourceDbOffline) {
try {
$result = Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -Offline -EnableException -Force
} catch {
Stop-Function -Message "Couldn't set database to offline" -ErrorRecord $_
}
}
Write-Message -Level Verbose -Message "Successfully reattached $dbName to $source."
} else {
Write-Message -Level Verbose -Message "Could not reattach $dbName to $source."
Expand Down Expand Up @@ -1463,12 +1508,6 @@ function Copy-DbaDatabase {
$copyDatabaseStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject
}

if ($SetSourceOffline -and $copyDatabaseStatus.Status -eq "Successful" -and $sourceServer.databases[$dbName].status -notlike '*offline*') {
if ($Pscmdlet.ShouldProcess($source, "Setting $dbName offline")) {
Set-DbaDbState -SqlInstance $sourceServer -Database $dbName -Offline -Force
}
}

$dbTotalTime = $dbFinish - $dbStart
$dbTotalTime = ($dbTotalTime.ToString().Split(".")[0])

Expand Down
2 changes: 1 addition & 1 deletion public/Copy-DbaServerRole.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function Copy-DbaServerRole {
return
}

$sourceRoles = $sourceServer.Roles | Where-Object IsFixedRole -eq $false
$sourceRoles = $sourceServer.Roles | Where-Object { $PSItem.IsFixedRole -eq $false -and $PSItem.Name -ne "public" }

if ($Force) { $ConfirmPreference = "none" }
}
Expand Down
16 changes: 15 additions & 1 deletion public/Start-DbaMigration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ function Start-DbaMigration {
This prevents data changes during migration and helps ensure data consistency.
When combined with -Reattach, databases remain read-only after being reattached to the source.

.PARAMETER SetSourceOffline
Sets migrated databases offline on the source server before migration begins.
This prevents any connections to the source databases during migration, ensuring complete isolation.
When combined with -Reattach, databases are brought back online after being reattached to the source.

.PARAMETER AzureCredential
Specifies the name of a SQL Server credential for accessing Azure Storage when SharedPath points to an Azure Storage account.
The credential must already exist on both source and destination servers with proper access to the Azure Storage container.
Expand Down Expand Up @@ -201,6 +206,11 @@ function Start-DbaMigration {

Migrates databases using detach/copy/attach. Reattach at source and set source databases read-only. Also migrates everything else.

.EXAMPLE
PS C:\> Start-DbaMigration -Verbose -Source sqlcluster -Destination sql2016 -BackupRestore -SharedPath "\\fileserver\backups" -SetSourceOffline

Migrates databases using backup/restore method. Sets source databases offline before migration to prevent any connections during the process.

.EXAMPLE
PS C:\> $PSDefaultParameters = @{
>> "dbatools:Source" = "sqlcluster"
Expand All @@ -225,6 +235,7 @@ function Start-DbaMigration {
[switch]$WithReplace,
[switch]$NoRecovery,
[switch]$SetSourceReadOnly,
[switch]$SetSourceOffline,
[switch]$ReuseSourceFolderStructure,
[switch]$IncludeSupportDbs,
[PSCredential]$SourceSqlCredential,
Expand Down Expand Up @@ -392,6 +403,7 @@ function Start-DbaMigration {
Destination = $Destination
DestinationSqlCredential = $DestinationSqlCredential
SetSourceReadOnly = $SetSourceReadOnly
SetSourceOffline = $SetSourceOffline
ReuseSourceFolderStructure = $ReuseSourceFolderStructure
AllDatabases = $true
Force = $Force
Expand Down Expand Up @@ -424,7 +436,9 @@ function Start-DbaMigration {
}
}

Copy-DbaDatabase @CopyDatabaseSplat
Copy-DbaDatabase @CopyDatabaseSplat | ForEach-Object {
$PSItem
}
}

if ($Exclude -notcontains 'Logins') {
Expand Down
Loading