I had a requirement to script a repeatable SQL 2014 ent to SQL 2016 std migration. This was to be for up to 200 databases and therefore needed to be automated.
I chose to encapsulate a blend of TSQL and Powershell in a non-scheduled SQL Job. And as we were going UP a version but DOWN an edition, I felt log-shipping would be the best option.
I idea was to run the job a week or so before hand. Then at the time of the migration (a weekend), just 15 minutes of data (per database) would need to traverse the network.
The SETUP job had 9 steps :-
1. Create a control table
2. Backup (because you never know)
3. Decrypt
4. Move Logins and Fix Orphans
5. Shrink the log-file
6. Log-Ship: Initial Full backup to remote-server
7. Log-Shipping: Initial Restores on Remote in recovery mode.
8. Log-Shipping: Create BACKUP jobs locally
9. Log-Shipping: Create COPY and RESTORE jobs remotely.
Step-1
-- 1.ControlTable.sql
USE msdb;
GO
IF OBJECT_ID('[msdb].[dbo].[LSList]') IS NOT NULL
DROP TABLE [msdb].[dbo].[LSList];
GO
CREATE TABLE [msdb].[dbo].[LSList] ([database] NVARCHAR(255) NOT NULL,
backup_directory NVARCHAR(255) NOT NULL,
backup_share NVARCHAR(255) NOT NULL,
backup_destination_directory NVARCHAR(255) NOT NULL,
pre_mig_backup INT NOT NULL,
is_encrypted INT NULL,
LS_backup INT NULL,
start_time_offset INT NOT NULL);
INSERT INTO [msdb].[dbo].[LSList] ([database],
backup_directory,
backup_share,
backup_destination_directory,
pre_mig_backup,
start_time_offset)
VALUES (N'DatabaseName1', N'h:\shipping', N'\\LocalServerName\shipping', N'\\RemoteServerName\shipping', 0, 2);
INSERT INTO [msdb].[dbo].[LSList] ([database],
backup_directory,
backup_share,
backup_destination_directory,
pre_mig_backup,
start_time_offset)
VALUES (N'DatabaseName2', N'h:\shipping', N'\\LocalServerName\shipping', N'\\RemoteServerName\shipping',0, 4);
-- populate encryption flag
UPDATE [msdb].[dbo].[LSList]
SET is_encrypted = 1 -- yes
WHERE [database] IN (SELECT db.[name]
FROM sys.databases db
JOIN sys.dm_database_encryption_keys dm
ON db.database_id = dm.database_id );
-- select * FROM [msdb].[dbo].[LSList]
Step-2
-- 2.PreMigBackups.sql
-- select * from [msdb].[dbo].[LSList]
-- update [msdb].[dbo].[LSList] SET pre_mig_backup = 0
DECLARE @Query NVARCHAR(MAX),
@dbname VARCHAR(200);
WHILE (SELECT COUNT(*) FROM [msdb].[dbo].[LSList] WHERE pre_mig_backup = 0) > 0
BEGIN
SET @dbname = ( SELECT TOP 1 [database]
FROM [msdb].[dbo].[LSList]
WHERE pre_mig_backup = 0);
SET @Query = N'BACKUP DATABASE [' + @dbname + ']
TO DISK = N''H:\SQL Backup\' + @dbname + '_' + replace(convert(varchar(16), getdate(),126), ':','') + '.bak''
WITH COPY_ONLY, NOFORMAT, INIT, STATS = 10';
EXEC sp_executesql @Query;
UPDATE [msdb].[dbo].[LSList]
SET pre_mig_backup = 1
WHERE [database] = @dbname;
END;
Step-3
-- 3.decrypt.sql
DECLARE @Query NVARCHAR(MAX), @dbname VARCHAR(200);
/*
is_encrypted
null = no
1 = yes
0 = not any more
*/
WHILE (SELECT COUNT(*) FROM [msdb].[dbo].[LSList] WHERE is_encrypted = 1) > 0
BEGIN
SET @dbname = (SELECT TOP 1 [database] FROM [msdb].[dbo].[LSList] WHERE is_encrypted = 1);
/* 1 set encryption off */
SET @Query = N'ALTER DATABASE [' + @dbname + N'] SET ENCRYPTION OFF;';
EXEC sp_executesql @Query;
/* 2 pause until decrypted */
WHILE ( SELECT dm.encryption_state
FROM sys.databases db
LEFT JOIN sys.dm_database_encryption_keys dm
ON db.database_id = dm.database_id
WHERE [name] = @dbname) 1
BEGIN
WAITFOR DELAY '00:00:10';
END;
/*3 drop key */
SET @Query = 'USE [' + @dbname + ']; DROP DATABASE ENCRYPTION KEY';
EXEC sp_executesql @Query;
/* 4 log changes then move on */
UPDATE [msdb].[dbo].[LSList]
SET is_encrypted = 0
WHERE [database] = @dbname;
END;
-- Stop MLB
-- DECLARE @Query NVARCHAR(MAX),
-- @dbname VARCHAR(200);
IF OBJECT_ID('tempdb..#tlist') IS NOT NULL
DROP TABLE #tlist;
SELECT [database]
INTO #tlist
FROM [msdb].[dbo].[LSList];
WHILE (SELECT COUNT(*) FROM #tlist) > 0
BEGIN
SET @dbname = (SELECT TOP 1 [database] FROM #tlist);
SET @Query = N'EXEC [msdb].[smart_admin].[sp_set_db_backup]
@database_name = [' + @dbname + N'],
@enable_backup = 0'; -- off
EXEC sp_executesql @Query;
DELETE FROM #tlist
WHERE [database] = @dbname;
END;
Step-4
Powershell.exe "Export-DbaLogin -SqlInstance LocalServerName -Append -Path C:\temp\LocalServerName-logins.sql" Powershell.exe "Export-DbaUser -SqlInstance LocalServerName -Append -Path C:\temp\LocalServerName-users.sql" Powershell.exe "Copy-DbaLogin -Source LocalServerName -Destination RemoteServerName -ExcludeSystemLogins"
Step-5
Powershell.exe "Repair-DbaOrphanUser -SqlInstance RemoteServerName"