Parse SID as a parameter - sql-server

I have 2 contained databases in Microsoft SQL 2019. In DB01, a user with password has been created:
USE DB01
CREATE USER [username] WITH PASSWORD = '1234'
In DB02, a user without login/password needs to be created with the same SID.
If I run the command and fill out the SID manually, it works without any issues:
USE DB02
CREATE USER [username] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [schema_1], SID = 0x010500000000000903000000AD91E331ECFC284B938E65DAF644BAF6
However, if I try to parse the SID as a parameter, it fails with the error "Incorrect syntax near '#usersid'"
USE DB01
DECLARE #usersid varbinary(MAX) = (SELECT sid FROM sys.sysusers WHERE name = 'username')
USE DB02
CREATE USER [username] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [schema_1], SID = #usersid
Any idea why?
EDIT
Following the recommendations of #Stephan (many thanks!!!), I came with the following solution. I had to modify the query a little as the DB names actually come from variables as well. I don't know if it is the best way, but it works :)
DECLARE #DB01 nvarchar(30) = 'DB01'
DECLARE #DB02 nvarchar(30) = 'DB02'
DECLARE #usersid varbinary(85)
DECLARE #get_sid nvarchar(max) = 'SELECT #usersid = sid FROM ' + #DB01 + '.sys.database_principals WHERE [name] = ''username'''
EXEC sp_executesql #get_sid, N'#usersid varbinary(85) OUTPUT', #usersid OUTPUT
DECLARE #create_user nvarchar(max) = CONCAT(N'USE ',#DB02,N' CREATE USER [username] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [dbo],SID = ',CONVERT(NVARCHAR(MAX),#usersid,1))
EXEC(#create_user)

As others mentioned, you must use dynamic SQL as CREATE USER is not design to accept input parameters, only literal strings.
Dynamic SQL to Create User with Specific SID
DECLARE #UserSID varbinary(85) = (SELECT sid FROM sys.database_principals WHERE [name] = N'UserName');
DECLARE #SQL NVARCHAR(MAX) = CONCAT(N'CREATE USER [username] WITHOUT LOGIN WITH DEFAULT_SCHEMA = dbo,SID = ',CONVERT(NVARCHAR(MAX),#UserSID,1))
EXEC(#SQL)

Related

What is best way to auto-fix all orphaned user accounts in all SQL Server databases

A DBA consultant provided us with the following SQL script several years ago. It still works but SQL prompt reports that [master].[dbo].[syslogins] is depreciated. My question is, what replaces [syslogins] in SQL Server 2019 and/or is there a better process for fixing orphaned users in all databases? Here's the script we use:
DECLARE #UserCount INT
DECLARE #UserCurr INT
DECLARE #userName VARCHAR(100)
DECLARE #vsql NVARCHAR(4000)
DECLARE #Users TABLE
(
id INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
userName VARCHAR(100) NOT NULL
)
INSERT INTO #Users(UserName)
SELECT
[name]
FROM
[master].[dbo].[syslogins] --depreciated?
SELECT
#UserCount = MAX([id])
FROM
#Users
SET #UserCurr = 1
WHILE(#UserCurr <= #UserCount)
BEGIN
SELECT
#userName = [userName]
FROM
#Users
WHERE [id] = #UserCurr
SET #vsql = '[dbo].[sp_change_users_login] ''AUTO_FIX'',''' + #userName + ''''
EXEC (#vsql)
SET #UserCurr = #UserCurr + 1
END
I've searched for other solutions but none seem better than what we're already using. If our script is referencing a depreciated table though, I'd like to update the code. Thanks in advance.

How to calculate the SID for new logins?

I want to create a number of database users on two sql server instances. To be able to easily copy databases from one instance to another, I want the SIDs to be consistent.
My idea was to use predictable SIDs, to minimize maintenance pain. I a dreaming about something like this:
CREATE LOGIN newuser1 WITH PASSWORD = '...', SID = CAST('newuser1' AS BINARY(16))
-- or alternatively:
CREATE LOGIN newuser1 WITH PASSWORD = '...', SID = HASHBYTES('newuser1')
Calculating values for the SID, however, seems not to be supported by the CREATE LOGIN command:
Meldung 102, Ebene 15, Status 1, Zeile 1
Falsche Syntax in der Nähe von "SID". -- Wrong syntax near "SID"
How can I use a calculated value for SID?
This can be accomplished by assembling the sql statement in a variable and the executing it with sp_executesql:
DECLARE #SQL nvarchar(4000)
SET #SQL = 'CREATE LOGIN newuser1 WITH PASSWORD = ''...'', SID = 0x'+CONVERT(VARCHAR(1000), CAST('newuser1' AS binary(16)), 2)
PRINT #SQL
EXEC sp_executesql #SQL
Alternatively, if you want to reuse this approach, you can use create a stored procedure:
CREATE PROCEDURE create_user_with_predictable_sid
#UserName varchar(256),
#Password varchar(256)
AS
DECLARE #SQL nvarchar(4000) = 'CREATE LOGIN '+#UserName+' WITH PASSWORD = '''+#Password+''', SID = 0x'+CONVERT(VARCHAR(1000), CAST(#UserName AS binary(16)), 2)
PRINT #SQL
EXEC sp_executesql #SQL
GO
GO
create_user_with_predictable_sid 'newuser1', '...'
GO
DROP PROCEDURE create_user_with_predictable_sid

how to compare the fied password in master.dbo.syslogins in ms sql 2008

i have a form login and want to check if a login name and password match with name and password in system table master.dbo.syslogins but a column password was encrypted. a procedure like this :
create proc checklogin(#name nchar(10), #password nvarchar, #tb nchar(10) out)
as
if Exists (select * from master.dbo.syslogins where name = #name and password=#password)
begin
set #tb='success';
else set #tb='fail';
i cant compare password because it was encrypted in database.I want to know if there is another way to check or how to decrypt password.any help would be great.
Use the PWDCOMPARE() function.
select * from master.dbo.syslogins
where name = #name and PWDCOMPARE(#password,password) = 1

Create database user conditionally syntax error

I'm using the following to create a user if it doesn't already exist in the database:
use DBExample
GO
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = 'user1')
BEGIN
CREATE USER [user1] WITH PASSWORD = 'abc')
END
EXEC sp_addrolemember 'role1', 'user1'
GO
DBExample already has a user1, so when I try to run the script, SQL Server Management Studio complains about an 'Incorrect syntax near 'user1'. (in the create user line)
What am I missing to make this work?
I think you're confusing Logins with Users - in SQL Server 2008R2, at least, you can't have one without the other. I'd recommend having a quick look at Books Online for these concepts.
You're probably looking for something like:
IF NOT EXISTS (SELECT * from sys.server_principals WHERE name = 'user1')
BEGIN
CREATE LOGIN [user1] WITH PASSWORD = 'abc';
END
GO
USE DBExample
GO
IF NOT EXISTS
(
SELECT * from sys.database_principals dp
INNER JOIN sys.server_principals sp on dp.sid = sp.sid
WHERE dp.name = 'user1' or sp.name = 'user1'
)
BEGIN
CREATE USER [user1] FOR LOGIN [user1]
END
GO
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = 'user1')
BEGIN
EXEC sp_addrolemember 'role1', 'user1'
END
GO
This creates a Login if it doesn't exist, goes to the database then creates a User if it doesn't exist, then associates a User with a Role.
I used the answer found here: https://stackoverflow.com/a/6159882 to make use of variables to substitute the user name to get around the 'There is already login/user named xxx in the database' error SSMS was complaining about. The code looks like this at the end:
USE DBExample
GO
DECLARE #userName varchar(100)
SET #userName = 'user1'
IF NOT EXISTS (SELECT * from sys.server_principals WHERE name = #userName)
BEGIN
DECLARE #LoginSQL varchar(200);
SET #LoginSQL = 'CREATE LOGIN ' + #userName + ' WITH PASSWORD = abc';
EXEC (#LoginSQL);
END
IF NOT EXISTS (SELECT * from sys.database_principals WHERE name = #userName)
BEGIN
DECLARE #UserSQL varchar(200);
SET #UserSQL = 'CREATE USER ' + #userName + ' FOR LOGIN ' + #userName;
EXEC (#UserSQL);
END
IF NOT EXISTS (SELECT * from sys.database_role_members WHERE USER_NAME(member_principal_id) = #userName)
BEGIN
EXEC sp_addrolemember 'role1', #userName
END
GO

How to find out whether sql server login has any user mapped to using SMO

I need to write a function to delete the login in the database if it does not have any users to map to using SQL Server Management Objects (SMO). How can I achieve this ?
Just like to add that when using the login.EnumDatabaseMappings(),when there are no users mappped to the login , will return null.So you can not use something like login.EnumDatabaseMappings().Length rather you should use
mylogin = server.Logins(loginName)
If Not mylogin Is Nothing Then
If Not mylogin.EnumDatabaseMappings() Is Nothing Then
mylogin.Drop()
End If
End If
How about this:
Server server = new Server("your server name");
foreach (Login login in server.Logins)
{
DatabaseMapping[] mappings = login.EnumDatabaseMappings();
}
Should work and give you what you're looking for.
Give this a go,
If you comment out the code about the cursor and look at the result of the select statement you can see what logins it wants to drop.
USE MASTER;
GO
DECLARE #loginName varchar(max)
DECLARE #SQL varchar(max)
CREATE TABLE #dbusers (
sid VARBINARY(85))
EXEC sp_MSforeachdb
'insert #dbusers select sid from [?].sys.database_principals where type != ''R'''
DECLARE loginCursor CURSOR FOR
SELECT name
FROM sys.server_principals
WHERE sid IN (SELECT sid
FROM sys.server_principals
WHERE TYPE != 'R'
AND name NOT LIKE ('##%##')
EXCEPT
SELECT DISTINCT sid
FROM #dbusers)
AND type_desc = 'SQL_LOGIN'
OPEN loginCursor
FETCH NEXT FROM loginCursor into #loginName
WHILE ##FETCH_STATUS=0
BEGIN
SET #SQL = 'DROP LOGIN '+#loginName
EXEC sp_executesql #SQL
END
CLOSE loginCursor
DEALLOCATE loginCursor
GO
DROP TABLE #dbusers

Resources