How to transfer SQL logins between SQL Server 2005 instances
There are many ways to transfer SQL logins between SQL Server instances. Here are a few of them:
- sp_help_revlogin
- Transfer Logins Task in a DTS package
- Transfer Logins Task in an SSIS package
- SQL Server Magazine method
- Manually (like that's even a consideration for an experienced DBA)
In an ideal world, an application will connect to a SQL Server instance using one account (or perhaps a couple extra) and then "authenticate" individual users via application code and a SQL table.
We have a legacy system that has thousands of SQL accounts, one per user. We've got over 8,000 users now. Twice a year, we move all of our production applications and databases to our disaster recovery site to either test our DR readiness or so that we can perform wide scale maintenance at our primary site. Due to this, we must synchronize the users between the sites for this one particular system. Luckily, our other systems live in an ideal world.
All of the transfer logins methods listed above have issues (at least for us, your mileage may vary), so I decided to write my own.
SET NOCOUNT ONCREATE TABLE #Logins ( loginId int IDENTITY(1, 1) NOT NULL, loginName nvarchar(128) NOT NULL, passwordHash varbinary(256) NULL, sid varbinary(85) NOT NULL )
– openquery is used so that loginproperty function runs on the remote server, – otherwise we get back null INSERT INTO #Logins(loginName, passwordHash, sid) SELECT * FROM OPENQUERY([LinkedServerName], ' SELECT name, CONVERT(varbinary(256), LOGINPROPERTY(name, ''PasswordHash'')), sid FROM master.sys.server_principals WHERE type = ''S'' AND name NOT IN (''sa'', ''guest'') AND create_date >= ''12/31/2005'' ORDER BY name')
DECLARE @count int, @loginId int, @loginName nvarchar(128), @passwordHashOld varbinary(256), @passwordHashNew varbinary(256), @sid varbinary(85), @sql nvarchar(4000), @password varchar(514)
SELECT @loginId = 1, @count = COUNT(*) FROM #Logins
WHILE @loginId <= @count BEGIN SELECT @loginName = loginName, @passwordHashNew = passwordHash, @sid = sid FROM #Logins WHERE loginId = @loginId
<span class="rem">-- if the account doesn't exist, then we need to create it</span> <span class="kwrd">IF</span> <span class="kwrd">NOT</span> <span class="kwrd">EXISTS</span> (<span class="kwrd">SELECT</span> * <span class="kwrd">FROM</span> master.sys.server_principals <span class="kwrd">WHERE</span> name = @loginName) <span class="kwrd">BEGIN</span> <span class="kwrd">EXEC</span> master.dbo.sp_hexadecimal @passwordHashNew, @password <span class="kwrd">OUTPUT</span> <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'CREATE LOGIN '</span> + @loginName + <span class="str">' WITH PASSWORD = '</span> <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = @<span class="kwrd">sql</span> + <span class="kwrd">CONVERT</span>(nvarchar(512), <span class="kwrd">COALESCE</span>(@password, <span class="str">'NULL'</span>)) <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = @<span class="kwrd">sql</span> + <span class="str">' HASHED, CHECK_POLICY = OFF'</span> --<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span> <span class="kwrd">EXEC</span> (@<span class="kwrd">sql</span>) --<span class="kwrd">PRINT</span> <span class="str">'login created'</span> <span class="kwrd">END</span> <span class="rem">-- if the account does exist, then we need to drop/create to sync the password;</span> <span class="rem">-- can't alter as hashed isn't supported</span> <span class="kwrd">ELSE</span> <span class="kwrd">BEGIN</span> <span class="kwrd">SELECT</span> @passwordHashOld = <span class="kwrd">CONVERT</span>(varbinary(256), LOGINPROPERTY(@loginName, <span class="str">'PasswordHash'</span>)) <span class="rem">-- only bother updating if the password has changed since the last sync</span> <span class="kwrd">IF</span> @passwordHashOld <> @passwordHashNew <span class="kwrd">BEGIN</span> <span class="kwrd">EXEC</span> master.dbo.sp_hexadecimal @passwordHashOld, @password <span class="kwrd">OUTPUT</span> <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'DROP LOGIN '</span> + @loginName --<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span> <span class="kwrd">EXEC</span> (@<span class="kwrd">sql</span>) <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = <span class="str">'CREATE LOGIN '</span> + @loginName + <span class="str">' WITH PASSWORD = '</span> <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = @<span class="kwrd">sql</span> + <span class="kwrd">CONVERT</span>(nvarchar(512), <span class="kwrd">COALESCE</span>(@password, <span class="str">'NULL'</span>)) <span class="kwrd">SET</span> @<span class="kwrd">sql</span> = @<span class="kwrd">sql</span> + <span class="str">' HASHED, CHECK_POLICY = OFF'</span> --<span class="kwrd">PRINT</span> @<span class="kwrd">sql</span> <span class="kwrd">EXEC</span> (@<span class="kwrd">sql</span>) --<span class="kwrd">PRINT</span> <span class="str">'login "altered"'</span> <span class="kwrd">END</span> <span class="kwrd">END</span> <span class="kwrd">SET</span> @loginId = @loginId + 1
END
DROP TABLE #Logins
The script depends on sp_hexadecimal. You can get it here.
To transfer SQL logins between SQL Server 2005 instances using my script, run it on the server that will receive the updates. "LinkedServerName" in the OPENQUERY statement needs to be changed to the linked server that points to the source server.
Legacy Comments
Neil
2008-08-19 |
re: How to transfer SQL logins between SQL Server 2005 instances I tested your solution and it appears it only works for FULL Alpha user IDs. We use XXXX-NAME as out logins (For programattic reasons) and I get the following: Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '1000'. <------ This is the first portion of the user name 1000-admin Msg 319, Level 15, State 1, Line 1 Incorrect syntax near the keyword 'with'. If this statement is a common table expression or an xmlnamespaces clause, the previous statement must be terminated with a semicolon. Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '1000'. Msg 319, Level 15, State 1, Line 1 Incorrect syntax near the keyword 'with'. If this statement is a common table expression or an xmlnamespaces clause, the previous statement must be terminated with a semicolon. Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '1023'. |
Neil
2008-08-19 |
re: How to transfer SQL logins between SQL Server 2005 instances In finally got the create user portions working but SQL does not like: CONVERT(nvarchar(512), COALESCE(@password, 'NULL')) I keep getting an error "Error Near NULL" when the create user statement is run. |
Jeroen
2008-08-27 |
re: How to transfer SQL logins between SQL Server 2005 instances We just started with SQL 2005 mirroring and when searching for a nice way of copying users across the sql servers we found this solution. It works great, except that we just found out that the SID's are different between the 2 server when we tried testing the failover. By looking at the script it seems as if you used the original SID's from the principal server when creating them on the mirror, would there be any explanation why we end up with different SID's between the two servers after using the script? |
Jeroen
2008-08-27 |
re: How to transfer SQL logins between SQL Server 2005 instances Found a solution that seems to work for us Changed 2 lines in the script Old: SET @sql = @sql + ' HASHED, CHECK_POLICY = OFF' New: SET @sql = @sql + ' HASHED, CHECK_POLICY = OFF' + ',SID = ' + master.sys.fn_varbintohexstr(@SID) This way it uses the original SID for creating the user, however it made me wondering why you didn't include the SID in the create statement but did retrieve it with the select option? |
Tara
2008-08-27 |
re: How to transfer SQL logins between SQL Server 2005 instances Jeroen, Thank you for the follow-up. I'm sure I had a plan for the sid variable. ;-) I don't recall though as I wrote the code several months ago. |
Jeroen
2008-09-22 |
re: How to transfer SQL logins between SQL Server 2005 instances Walked into a new problem with the script. A hopefully usefull tip: We are using SQL mirroring and everytime you synchronize the users it will remove your endpoint mapping on the server and disconnects the login from the user in the master database. To solve it simply add your users used by mirroring for server A, B and witness to the script like the exclusion of SA and Guest |