克隆 SQL Server 登陸名及權限

在數據庫的管理過程中,除了要創建SQL Server登陸賬戶,也會經常遇到需要克隆賬戶的情形,如服務器硬件老化,需要更新換代;或者需要SQL Server新版本的功能,將SQL Server更新到更高版本。這些都需要將一個服務器上的數據庫轉移到另外一個服務器上,像這樣的數據庫跨服務器的轉移,不可避免的涉及到登陸賬戶的轉移(數據庫用戶轉移隨着備份的還原直接轉移了),轉移不僅要保證賬戶相同,賬戶密碼、賬戶權限也要相同,這樣才能最大程度減少程序變更,減少停機時間。對於大量的登陸賬戶的轉移,這是一件較爲麻煩的事情,本文將給出克隆實例上登陸賬戶、密碼及權限的腳本,讓你一鍵解決這些煩惱。下面先給出克隆單個登陸賬戶、密碼及權限的腳本:

USE master
GO
/*
	功能:生成單個登陸帳戶及權限的創建腳本
	適用於SQL SERVER 2008/SQL SERVER 2008 R2/SQL SERVER 2012/SQL SERVER 2014/SQL SERVER 2016
	***其他版本暫未測試
	注意:1.適用於同版本或低版本升級高版本SQL Server 數據庫時登陸帳戶的遷移 
		  2.不能克隆憑據、非對稱密鑰和證書
	DECLARE @sql VARCHAR(max)
	EXEC sp_DBA_LoginClone [Jack],@sql OUTPUT
	SELECT @sql FOR XML PATH('')
*/
CREATE PROC sp_DBA_LoginClone
    @loginName sysname
    ,@loginCloneSQL VARCHAR(max) OUTPUT
AS 
BEGIN
	DECLARE @password_hash VARchar(256)
	DECLARE @sid VARchar(85)
	DECLARE @hasaccess int
	DECLARE @denylogin int
    DECLARE @is_disabled INT
    DECLARE @is_policy_checked VARCHAR(3)
    DECLARE @is_expiration_checked VARCHAR(3)
    DECLARE @defaultdb sysname
    DECLARE @type varchar (1)
 
	--判斷登陸名是否存在
    IF NOT EXISTS (
         SELECT 1 FROM sys.syslogins WHERE name= @loginName
       ) 
        BEGIN
            PRINT @loginName + ' 不存在'
            RETURN
        END
 
    
    SELECT  @type = type
    FROM    sys.server_principals
    WHERE name=@loginName
   SELECT @hasaccess=hasaccess, @denylogin=denylogin 
   FROM sys.syslogins
   WHERE name=@loginName
    SET @loginCloneSQL = 'USE master ' + CHAR(10) + 'GO ' + CHAR(10)

	--生成登陸名創建腳本
  
    IF (@type IN ( 'G', 'U'))
    BEGIN --NT authenticated account/group
		SELECT @defaultdb=default_database_name
		from sys.server_principals
		WHERE name=@loginName
      SET @loginCloneSQL =@loginCloneSQL+ 'Create LOGIN ' 
		+ QUOTENAME( @loginName,']' ) 
		+ ' FROM WINDOWS WITH DEFAULT_DATABASE = ' 
		+ QUOTENAME(@defaultdb , ']')+CHAR(10)
    END
    ELSE 
    BEGIN
		SELECT  @sid=CONVERT(VARCHAR(85),p.sid,1), @is_disabled= p.is_disabled
			, @defaultdb=p.default_database_name
			,@is_policy_checked= CASE is_policy_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' end
			,@is_expiration_checked= CASE is_expiration_checked WHEN 1 THEN 'ON' WHEN 0 THEN 'OFF' END
			,@password_hash=CONVERT(VARCHAR(256),password_hash , 1)
		FROM    sys.sql_logins p
		WHERE name=@loginName
		SET @loginCloneSQL=@loginCloneSQL+' CREATE LOGIN '+QUOTENAME(@loginName, ']')+CHAR(10)
			+' WITH PASSWORD='+@password_hash+ ' HASHED '+CHAR(10)
			+',SID='+@sid +CHAR(10)
			+', DEFAULT_DATABASE = ' + QUOTENAME(@defaultdb, ']')+CHAR(10)
		IF ( @is_policy_checked IS NOT NULL )
			BEGIN
			  SET @loginCloneSQL = @loginCloneSQL + ', CHECK_POLICY = ' + @is_policy_checked+CHAR(10)
			END
		IF ( @is_expiration_checked IS NOT NULL )
		BEGIN
		  SET @loginCloneSQL = @loginCloneSQL + ', CHECK_EXPIRATION = ' + @is_expiration_checked+CHAR(10)
		END
	END
	
    --IF (@denylogin = 1)
    --BEGIN --login is denied access
    --  SET @loginCloneSQL = @loginCloneSQL + ' DENY CONNECT SQL TO ' + QUOTENAME( @loginName )+CHAR(10)
    --END
    --ELSE IF (@hasaccess = 0)
    --BEGIN --login exists but does not have access
    --  SET @loginCloneSQL = @loginCloneSQL + ' REVOKE CONNECT SQL TO ' + QUOTENAME( @loginName )+CHAR(10)
    --END
    IF (@is_disabled = 1)
    BEGIN --login is disabled
      SET @loginCloneSQL = @loginCloneSQL + ' Alter LOGIN ' + QUOTENAME( @loginName ) + 'DISABLE' +CHAR(10)
    END
    
    DECLARE @versionNum AS INT
    SET @versionNum = SUBSTRING(@@VERSION, 22, 4)
    IF @versionNum >= 2012 
        BEGIN
            SELECT  @loginCloneSQL = ISNULL(@loginCloneSQL, '')
                    + ISNULL(STUFF((SELECT	
                            CASE WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NULL
                                      AND sp.state <> 'W'
                                 THEN sp.state_desc COLLATE Chinese_PRC_CI_AS + ' ' + sp.permission_name
                                      + ' TO ' + QUOTENAME(s.name, '[') + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NULL
                                      AND sp.state = 'W'
                                 THEN 'GRANT ' + sp.permission_name + ' TO ' + QUOTENAME(s.name, '[')
                                      + ' ' + ' WITH GRANT OPTION' + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 101
                                      AND sp.state <> 'W'
                                 THEN sp.state_desc COLLATE Chinese_PRC_CI_AS + ' ' + sp.permission_name
                                      + ' ON LOGIN::' + QUOTENAME(o.name, '[') + ' TO '
                                      + QUOTENAME(s.name, '[') + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 101
                                      AND sp.state = 'W'
                                 THEN 'GRANT ' + sp.permission_name + ' ON LOGIN::' + QUOTENAME(o.name,
                                                                                        '[') + ' TO '
                                      + QUOTENAME(s.name, '[') + ' ' + ' WITH GRANT OPTION' + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 105
                                      AND sp.state <> 'W'
                                 THEN sp.state_desc + ' ' + sp.permission_name + ' ON ENDPOINT::'
                                      + QUOTENAME(e.name, '[') + ' TO ' + QUOTENAME(s.name, '[')
                                      + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 105
                                      AND sp.state = 'W'
                                 THEN 'GRANT ' + sp.permission_name + ' ON ENDPOINT::'
                                      + QUOTENAME(e.name, '[') + ' TO ' + QUOTENAME(s.name, '[') + ' '
                                      + ' WITH GRANT OPTION' + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 108
                                      AND sp.state <> 'W'
                                 THEN sp.state_desc + ' ' + sp.permission_name + ' ON ENDPOINT::'
                                      + QUOTENAME(ag.name, '[') + ' TO ' + QUOTENAME(s.name, '[')
                                      + CHAR(10)
                                 WHEN ISNULL(ISNULL(o.name, e.name), ag.name) IS NOT NULL
                                      AND class = 108
                                      AND sp.state = 'W'
                                 THEN 'GRANT ' + sp.permission_name + ' ON AVAILABILITY GROUP::'
                                      + QUOTENAME(ag.name, '[') + ' TO ' + QUOTENAME(s.name, '[') + ' '
                                      + ' WITH GRANT OPTION' + CHAR(10)
                            END
                    FROM    sys.server_permissions sp
                    LEFT JOIN sys.server_principals p ON p.principal_id = sp.grantor_principal_id
                    LEFT JOIN sys.server_principals o ON sp.major_id = o.principal_id
                                                         AND class_desc = 'SERVER_PRINCIPAL'
                    LEFT JOIN sys.server_principals s ON s.principal_id = sp.grantee_principal_id
                    LEFT JOIN sys.endpoints e ON e.endpoint_id = sp.major_id
                                                 AND class_desc = 'ENDPOINT'
                    LEFT JOIN sys.availability_replicas ar ON ar.replica_metadata_id = sp.major_id
                                                              AND replica_metadata_id IS NOT NULL
                    LEFT JOIN sys.availability_groups ag ON ag.group_id = ar.group_id
                    WHERE   s.name = @loginName 
             FOR   XML PATH('') ,
                       TYPE).value('.', 'varchar(max)'), 1, 0, ''), '')
        END
    ELSE 
        BEGIN
            SELECT  @loginCloneSQL = ISNULL(@loginCloneSQL, '')
                    + ISNULL(STUFF((
					SELECT  CASE WHEN ISNULL(o.name, e.name) IS NULL
                                  AND sp.state <> 'W'
                             THEN sp.state_desc COLLATE Chinese_PRC_CI_AS + ' ' + sp.permission_name
                                  + ' TO ' + QUOTENAME(s.name, '[') + CHAR(10)
                             WHEN ISNULL(o.name, e.name) IS NULL
                                  AND sp.state = 'W'
                             THEN 'GRANT ' + sp.permission_name + ' TO ' + QUOTENAME(s.name, '[')
                                  + ' ' + ' WITH GRANT OPTION' + CHAR(10)
                             WHEN ISNULL(o.name, e.name) IS NOT NULL
                                  AND class = 101
                                  AND sp.state <> 'W'
                             THEN sp.state_desc COLLATE Chinese_PRC_CI_AS + ' ' + sp.permission_name
                                  + ' ON LOGIN::' + QUOTENAME(o.name, '[') + ' TO '
                                  + QUOTENAME(s.name, '[') + CHAR(10)
                             WHEN ISNULL(o.name, e.name) IS NOT NULL
                                  AND class = 101
                                  AND sp.state = 'W'
                             THEN 'GRANT ' + sp.permission_name + ' ON LOGIN::' 
								  + QUOTENAME(o.name,'[') + ' TO '
                                  + QUOTENAME(s.name, '[') + ' ' + ' WITH GRANT OPTION' + CHAR(10)
                             WHEN ISNULL(o.name, e.name) IS NOT NULL
                                  AND class = 105
                                  AND sp.state <> 'W'
                             THEN sp.state_desc + ' ' + sp.permission_name + ' ON ENDPOINT::'
                                  + QUOTENAME(e.name, '[') + ' TO ' + QUOTENAME(s.name, '[')
                                  + CHAR(10)
                             WHEN ISNULL(o.name, e.name) IS NOT NULL
                                  AND class = 105
                                  AND sp.state = 'W'
                             THEN 'GRANT ' + sp.permission_name + ' ON ENDPOINT::'
                                  + QUOTENAME(e.name, '[') + ' TO ' + QUOTENAME(s.name, '[') + ' '
                                  + ' WITH GRANT OPTION' + CHAR(10)
                        END	
                FROM    sys.server_permissions sp
                LEFT JOIN sys.server_principals p ON p.principal_id = sp.grantor_principal_id
                LEFT JOIN sys.server_principals o ON sp.major_id = o.principal_id
                                                     AND class_desc = 'SERVER_PRINCIPAL'
                LEFT JOIN sys.server_principals s ON s.principal_id = sp.grantee_principal_id
                LEFT JOIN sys.endpoints e ON e.endpoint_id = sp.major_id
                                             AND class_desc = 'ENDPOINT'
                WHERE   s.name = @loginName 
             FOR   XML PATH('') ,
                       TYPE).value('.', 'varchar(max)'), 1, 0, ''), '')
		
        END

	--登陸名所屬角色(包括用戶創建的服務器角色)
    SELECT  @loginCloneSQL = ISNULL(@loginCloneSQL, '')
            + ISNULL(STUFF((SELECT  'EXEC sp_addsrvrolemember ' + QUOTENAME(member.name,'[') + ',' + role.name + CHAR(10)
                            FROM    sys.server_role_members svrm
                            JOIN    sys.server_principals AS role ON svrm.role_principal_id = role.principal_id
                            JOIN    sys.server_principals AS member ON svrm.member_principal_id = member.principal_id
                            WHERE   member.name = @loginName 
                     FOR   XML PATH('') ,
                               TYPE).value('.', 'varchar(max)'), 1, 0, ''), '')
	SELECT @loginCloneSQL=@loginCloneSQL+' '
END

下面我們先在SQL Server 2008 上創建測試環境,測試​登陸賬戶克隆腳本的正確性:

--創建SQL Server 2008測試環境
--創建登陸名,並在給予其一些服務器角色權限,
--覆蓋服務器固定角色、服務器對象等
USE master;
GO
IF EXISTS ( SELECT  *
            FROM    sys.server_principals
            WHERE   name = 'Jack' ) 
    DROP LOGIN [Jack];
CREATE LOGIN [Jack] WITH PASSWORD = 'dMkdj!d8@j'
      ,check_expiration=off;
GO
EXEC sp_addsrvrolemember @loginame = 'Jack'
     , @rolename = 'securityadmin';
EXEC sp_addsrvrolemember @loginame = 'Jack'
    , @rolename = 'dbcreator';
GO
​
GRANT IMPERSONATE ON LOGIN::[sa] TO [Jack];
GRANT CONTROL SERVER TO [Jack];
GRANT ALTER ON ENDPOINT::[TSQL Default TCP] TO [Jack];
GRANT ALTER ANY LOGIN TO [Jack] WITH GRANT OPTION;
GRANT VIEW DEFINITION ON LOGIN::[sa] TO [Jack];

測試環境搭建完成後,我們執行如下測試腳本:

DECLARE @sql VARCHAR(max)
EXEC sp_DBA_LoginClone [Jack],@sql OUTPUT
SELECT @sql FOR XML PATH('')

點擊結果​:

對比測試環境和測試結果,發現多了一條GRANT CONNECT SQL TO [Jack]。​ 因爲在我們創建賬戶的時候,會默認給以賬戶連接實例的權限。將生成的腳本拷貝到其他SQL Server 2008 實例,可以創建Jack賬戶,並賦予指定的權限,使用測試環境的密碼,可以登錄新實例。​測試OK!

我們再創建SQL Server 2016 測試環境繼續測試:

--創建SQL Server 2016 測試環境
--創建登陸名,並在給予其一些服務器角色權限,
--覆蓋服務器固定角色、服務器對象等
USE master;
GO
IF EXISTS ( SELECT  *
            FROM    sys.server_principals
            WHERE   name = 'Jack' ) 
    DROP LOGIN [Jack];
CREATE LOGIN [Jack] WITH PASSWORD = 'dMkdj!d8@j'
       ,check_expiration=off;
GO
EXEC sp_addsrvrolemember @loginame = 'Jack'
     , @rolename = 'securityadmin';
EXEC sp_addsrvrolemember @loginame = 'Jack'
    , @rolename = 'dbcreator';
GO
GRANT IMPERSONATE ON LOGIN::[sa] TO [Jack];
GRANT CONTROL SERVER TO [Jack];
GRANT ALTER ON ENDPOINT::[TSQL Default TCP] TO [Jack];
GRANT ALTER ANY LOGIN TO [Jack] WITH GRANT OPTION;
GRANT VIEW DEFINITION ON LOGIN::[sa] TO [Jack];
--適用SQL Server 2012及以後版本
GRANT ALTER ANY SERVER ROLE TO [Jack];
CREATE SERVER ROLE srvRoleTest
GRANT ALTER ANY AVAILABILITY GROUP to srvRoleTest
GRANT ALTER ANY SERVER ROLE to srvRoleTest
EXEC sp_addsrvrolemember Jack, srvRoleTest

再次執行測試腳本​,結果如下:

對比測試環境和測試結果,發現權限一致,同時在其他SQL Server 2016上可以創建Jack 賬戶,並且在新實例上Jack賬戶擁有和原實例相同的權限,且我們使用測試環境Jack的密碼,可以在新實例上登陸​。測試​OK。

細心的朋友可能會發現,SQL Server 2008 生成的創建Jack賬戶的密碼長度明顯比SQL Server 2016中的長度短​。這也是爲什麼高版本的​賬戶不能克隆到低版本的原因所在。

最後將給出實例上所有賬戶克隆​腳本,如下:

DECLARE @loginName sysname
        ,@loginsCloneSQL VARCHAR(max)
        ,@loginCloneSQL VARCHAR(max)
SET @loginsCloneSQL=''
DECLARE cur CURSOR FOR(
select name FROM sys.server_principals 
Where type IN ( 'S', 'G', 'U' ) AND name <> 'sa'
)
OPEN cur
FETCH NEXT FROM cur INTO @loginName
WHILE @@FETCH_STATUS>=0
BEGIN
  EXEC sp_DBA_LoginClone @loginName,@loginCloneSQL OUTPUT
  SET @loginsCloneSQL=@loginsCloneSQL+@loginCloneSQL
  FETCH NEXT FROM cur INTO @loginName
END
SELECT @loginsCloneSQL FOR XML PATH('')
CLOSE cur
DEALLOCATE cur
​
DECLARE @sql VARCHAR(max)
EXEC sp_DBA_LoginClone [Jack],@sql OUTPUT
SELECT @sql FOR XML PATH('')

當然,你可以控制哪些賬戶需要克隆,僅僅需要改變​遊標中的查詢條件即可。

如果想更爲及時的獲取最新文章,可以搜索注公衆 MSQLServer,將有更多精彩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章