Capita di dover migrare il contenuto di una istanza Ms SQL da un server all'altro.
Spesso non conosciamo le credenziali dei singoli database da migrare.
A volte è necessario creare un secondo server, in linea assieme all'originale, e cambiare all'ultimo minuto il nome del server e dell'istanza per renderla identica al precedente.
Mediante alcuni script è possibile realizzare facilmente tutto questo.
Ci colleghiamo all'istanza da migrare con SQL Management Studio con l'utente sa.
Puliamo il log dei database prima di iniziare le operazioni di migrazione.
USE [myDbName] GO DBCC SHRINKDATABASE (N'myDbName' ) GO DBCC SHRINKFILE (N'myDbName' , 0 , TRUNCATEONLY ) GO DBCC SHRINKFILE (N'myDbName_log' , 0 , TRUNCATEONLY ) GO
In alcuni casi, la dimensione del file di log del database non si riduce. In questo caso, possiamo forzarla con questo comando:
USE [myDbName] GO ALTER DATABASE [myDbName] SET RECOVERY SIMPLE; GO DBCC SHRINKFILE( 2, 200 )
Effettuiamo il backup di tutti i database presenti nell'istanza utilizzando questo script:
DECLARE @name VARCHAR(50) -- database name DECLARE @path VARCHAR(256) -- path for backup files DECLARE @fileName VARCHAR(256) -- filename for backup DECLARE @fileDate VARCHAR(20) -- used for file name -- Specify database backup directory SET @path = 'C:\Backup\' -- Specify filename format SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112) DECLARE db_cursor CURSOR FOR SELECT name FROM master.dbo.sysdatabases WHERE name NOT IN ('master','model','msdb','tempdb') -- Exclude these databases OPEN db_cursor FETCH NEXT FROM db_cursor INTO @name WHILE @@FETCH_STATUS = 0 BEGIN SET @fileName = @path + @name + '_' + @fileDate + '.BAK' BACKUP DATABASE @name TO DISK = @fileName FETCH NEXT FROM db_cursor INTO @name END CLOSE db_cursor DEALLOCATE db_cursor
Non è possibile estrarre le password degli utenti di login.
Possiamo però esportare il loro hash in uno script.
Usando questo, possiamo inserirli nel nuovo server, in modo che funzionino esattamente come nella vecchia istanza.
IF OBJECT_ID ('sp_hexadecimal') IS NOT NULL DROP PROCEDURE sp_hexadecimal GO CREATE PROCEDURE sp_hexadecimal @binvalue varbinary(256), @hexvalue varchar (514) OUTPUT AS DECLARE @charvalue varchar (514) DECLARE @i int DECLARE @length int DECLARE @hexstring char(16) SELECT @charvalue = '0x' SELECT @i = 1 SELECT @length = DATALENGTH (@binvalue) SELECT @hexstring = '0123456789ABCDEF' WHILE (@i <= @length) BEGIN DECLARE @tempint int DECLARE @firstint int DECLARE @secondint int SELECT @tempint = CONVERT(int, SUBSTRING(@binvalue,@i,1)) SELECT @firstint = FLOOR(@tempint/16) SELECT @secondint = @tempint - (@firstint*16) SELECT @charvalue = @charvalue + SUBSTRING(@hexstring, @firstint+1, 1) + SUBSTRING(@hexstring, @secondint+1, 1) SELECT @i = @i + 1 END SELECT @hexvalue = @charvalue GO IF OBJECT_ID ('sp_export_login') IS NOT NULL DROP PROCEDURE sp_export_login GO CREATE PROCEDURE sp_export_login @login_name sysname = NULL AS DECLARE @name sysname DECLARE @type varchar (1) DECLARE @hasaccess int DECLARE @denylogin int DECLARE @is_disabled int DECLARE @PWD_varbinary varbinary (256) DECLARE @PWD_string varchar (514) DECLARE @SID_varbinary varbinary (85) DECLARE @SID_string varchar (514) DECLARE @tmpstr varchar (1024) DECLARE @is_policy_checked varchar (3) DECLARE @is_expiration_checked varchar (3) DECLARE @defaultdb sysname IF (@login_name IS NULL) DECLARE login_curs CURSOR FOR SELECT p.sid, p.name, p.type, p.is_disabled, p.default_database_name, l.hasaccess, l.denylogin FROM sys.server_principals p LEFT JOIN sys.syslogins l ON ( l.name = p.name ) WHERE p.type IN ( 'S', 'G', 'U' ) AND p.name <> 'sa' ELSE DECLARE login_curs CURSOR FOR SELECT p.sid, p.name, p.type, p.is_disabled, p.default_database_name, l.hasaccess, l.denylogin FROM sys.server_principals p LEFT JOIN sys.syslogins l ON ( l.name = p.name ) WHERE p.type IN ( 'S', 'G', 'U' ) AND p.name = @login_name OPEN login_curs FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @type, @is_disabled, @defaultdb, @hasaccess, @denylogin IF (@@fetch_status = -1) BEGIN PRINT 'No login(s) found.' CLOSE login_curs DEALLOCATE login_curs RETURN -1 END SET @tmpstr = '/* sp_export_login script ' PRINT @tmpstr SET @tmpstr = '** Generated ' + CONVERT (varchar, GETDATE()) + ' on ' + @@SERVERNAME + ' */' PRINT @tmpstr PRINT '' WHILE (@@fetch_status <> -1) BEGIN IF (@@fetch_status <> -2) BEGIN PRINT '' SET @tmpstr = '-- Login: ' + @name PRINT @tmpstr IF (@type IN ( 'G', 'U')) BEGIN -- NT authenticated account/group SET @tmpstr = 'CREATE LOGIN ' + QUOTENAME( @name ) + ' FROM WINDOWS WITH DEFAULT_DATABASE = [' + @defaultdb + ']' END ELSE BEGIN -- SQL Server authentication -- obtain password and sid SET @PWD_varbinary = CAST( LOGINPROPERTY( @name, 'PasswordHash' ) AS varbinary (256) ) EXEC sp_hexadecimal @PWD_varbinary, @PWD_string OUT EXEC sp_hexadecimal @SID_varbinary,@SID_string OUT -- obtain password policy state SELECT @is_policy_checked = CASE is_policy_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' ELSE NULL END FROM sys.sql_logins WHERE name = @name SELECT @is_expiration_checked = CASE is_expiration_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' ELSE NULL END FROM sys.sql_logins WHERE name = @name SET @tmpstr = 'CREATE LOGIN ' + QUOTENAME( @name ) + ' WITH PASSWORD = ' + @PWD_string + ' HASHED, SID = ' + @SID_string + ', DEFAULT_DATABASE = [' + @defaultdb + ']' IF ( @is_policy_checked IS NOT NULL ) BEGIN SET @tmpstr = @tmpstr + ', CHECK_POLICY = ' + @is_policy_checked END IF ( @is_expiration_checked IS NOT NULL ) BEGIN SET @tmpstr = @tmpstr + ', CHECK_EXPIRATION = ' + @is_expiration_checked END END IF (@denylogin = 1) BEGIN -- login is denied access SET @tmpstr = @tmpstr + '; DENY CONNECT SQL TO ' + QUOTENAME( @name ) END ELSE IF (@hasaccess = 0) BEGIN -- login exists but does not have access SET @tmpstr = @tmpstr + '; REVOKE CONNECT SQL TO ' + QUOTENAME( @name ) END IF (@is_disabled = 1) BEGIN -- login is disabled SET @tmpstr = @tmpstr + '; ALTER LOGIN ' + QUOTENAME( @name ) + ' DISABLE' END PRINT @tmpstr END FETCH NEXT FROM login_curs INTO @SID_varbinary, @name, @type, @is_disabled, @defaultdb, @hasaccess, @denylogin END CLOSE login_curs DEALLOCATE login_curs RETURN 0 GO
Nel nuovo SQL server, carichiamo i backup dei database effettuati precedentemente.
Eseguiamo lo script creato per l'inserimento degli utenti di login.
Molto probabilmente gli utenti saranno presenti ma non legati ai loro database.
Sono degli orphan user.
Con lo script seguente possiamo unire nuovamente gli utenti ai propri database.
SET NOCOUNT ON USE myDbName GO DECLARE @loop INT DECLARE @USER sysname IF OBJECT_ID ('tempdb..#Orphaned') IS NOT NULL BEGIN DROP TABLE #orphaned END CREATE TABLE #Orphaned (UserName sysname, UserSID VARBINARY (85), IDENT INT IDENTITY(1 ,1)) INSERT INTO #Orphaned EXEC SP_CHANGE_USERS_LOGIN 'report'; IF(SELECT COUNT(*) FROM #Orphaned) > 0 BEGIN SET @loop = 1 WHILE @loop <= (SELECT MAX( IDENT) FROM #Orphaned ) BEGIN SET @USER = (SELECT UserName FROM #Orphaned WHERE IDENT = @loop) IF( SELECT COUNT (*) FROM sys. server_principals WHERE [Name] = @USER ) <= 0 BEGIN EXEC SP_ADDLOGIN @USER, 'password' END EXEC SP_CHANGE_USERS_LOGIN 'update_one' ,@USER, @USER PRINT @USER + ' link to DB user reset'; SET @loop = @loop + 1 END END SET NOCOUNT OFF
Dopo aver completato le verifiche sui database migrati, possiamo spegnere il vecchio server e modificare nome e ip del nuovo per subentrare ad esso.
Come ultima attività, dobbiamo aggiornare i dati nell'istanza con il nuovo nome del server, utilizzando questi comandi:
-- Show current server name SELECT @@SERVERNAME AS 'Server Name' -- Delete old server name EXEC Sp_dropserver 'OLDSERVERNAME\SQLEXPRESS' -- Insert new server name EXEC Sp_addserver 'NEWSERVERNAME\SQLEXPRESS' , 'local'
Riavviare infine il servizio SQL Server.