在數據庫的管理過程中,除了要創建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,將有更多精彩。