Need help with the Query Report.

I need to run a report to return all the users have access rights but having a problem to return those users in Server roles.

Ex: if JSmith in ServerAdmin and SetupAdmin Roles, he should return 2 roles but I it return only 1 role serveradmin role.

I know why because of the UPDATE statement below but can't think of the way to fix it.
Any help would greatly appreciate.  Please see the desire output below.
I am using SQL 2012 and SQL 2008.

-- stop Issues.
-- This section update if a user is in Server Role.
   UPDATE a
      SET server_role = b.ServerRole
     FROM @FinalResult AS a
     JOIN dbo.#ServerRole AS b
       ON ( RTRIM(a.iuser_name) = RTRIM(b.UserName) );


-- dbo.#ServerRole
ServerRole          UserName        TypeDesc
------------------ ---------------- --------------
serveradmin         INT\JSmith      WINDOWS_LOGIN
setupadmin          INT\JSmith      WINDOWS_LOGIN

-- Desire output.
Servername       DatabaseName     UserName        TypeDesc          DatabaseRole       ServerRole
---------------- ---------------- --------------- ----------------- -----------------  -----------
CABISETestABC    Prod1            INT\JSmith      WINDOWS_USER      db_datareader      serveradmin
CABISETestABC    Prod1            INT\JSmith      WINDOWS_USER      db_datawriter      setupadmin


-----------------------------------------------------------------------------------------------------
USE [msdb];
GO

IF OBJECT_ID('dbo.usp_AdminReturnUserAccessReport', 'p') IS NOT NULL
  DROP PROCedure dbo.usp_AdminReturnUserAccessReport
GO

CREATE PROCedure dbo.usp_AdminReturnUserAccessReport
(
  @DBName     VARCHAR(60) =  NULL
 ,@UserName   VARCHAR(25) =  NULL
)
AS
/*****************************************************************************************
**      Description:  This sp report all users belong to certain role(s).  See output
**                    below.  This work on SQL Server 2012 too.  Tested 01/14/2014.
**
**      Written date:  01/26/2009
**
**
**     Modifications:
**     ------------+---------------+-------------------------------------------------------
**     Date:       |  Author:      |   Reasons:
**     xx/xx/xxxx  |  xxxxxxxxxx   |   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
**
**     ----------  --------------  --------------------------------------------------------
******************************************************************************************/
SET NOCOUNT ON;

/*
PRINT '/********************************************'
PRINT 'Server Name: ' + @@SERVERNAME
PRINT 'Today is: ' + CONVERT(VARCHAR(10), CURRENT_TIMESTAMP, 126) + RIGHT(CONVERT(VARCHAR(35), GETDATE(), 109), 14)
PRINT '*********************************************/'
PRINT ' '
PRINT ' '
--------------------------------------------------------
*/

DECLARE @FinalResult TABLE
(
  seq_num              INT          NOT NULL
 ,database_name        VARCHAR(40)      NULL
 ,iuser_name           VARCHAR(40)      NULL
 ,principal_type_desc  VARCHAR(26)      NULL
 ,role_name            VARCHAR(30)      NULL
 ,server_role          VARCHAR(30)      NULL
);

IF OBJECT_ID('Tempdb.dbo.#ServerRole', 'u') IS NOT NULL
  DROP TABLE dbo.#ServerRole

CREATE TABLE dbo.#ServerRole
(
  ServerRole     VARCHAR(50)  NULL
 ,UserName       VARCHAR(70)  NULL
 ,TypeDesc       VARCHAR(40)  NULL
)
CREATE INDEX EI1UserName ON dbo.#ServerRole (UserName);

    INSERT dbo.#ServerRole (ServerRole, UserName, TypeDesc)
    SELECT CAST(suser_name(rm.role_principal_id) AS VARCHAR(25)) AS [ServerRole]
          ,CAST(suser_name(rm.member_principal_id) AS VARCHAR(65)) AS [UserName]
    ,CAST(u.type_desc AS VARCHAR(20)) AS [TypeDesc]
      FROM sys.server_role_members rm
      JOIN sys.server_principals AS p
        ON rm.role_principal_id = p.principal_id
      JOIN sys.server_principals AS u
        ON rm.member_principal_id = u.principal_id
     WHERE (RTRIM(u.name) NOT IN ('NT SERVICE\SQLWriter', 'NT SERVICE\Winmgmt', 'NT Service\MSSQLSERVER',
                                  'NT SERVICE\SQLSERVERAGENT', 'Home\SQLService', 'sa') )
   ORDER BY 'UserName' ASC, 'ServerRole' ASC, 'TypeDesc' ASC;

PRINT '-- dbo.#ServerRole'
SELECT * FROM dbo.#ServerRole ORDER BY UserName ASC;

IF OBJECT_ID('Tempdb..#MemberRole', 'u') IS NOT NULL
  DROP TABLE #MemberRole

CREATE TABLE #MemberRole
(
  database_name        VARCHAR(50)  NULL,
  principal_name       VARCHAR(40)  NULL,
  principal_type_desc  VARCHAR(40)  NULL,
  role_name            VARCHAR(40)  NULL
)

EXECute sp_MSforeachdb
'USE ?;
 WITH perms_cte AS
         ( SELECT CAST(USER_NAME(p.grantee_principal_id) AS VARCHAR(40)) AS principal_name,
                  dp.principal_id,
                  dp.type_desc AS principal_type_desc,
                  p.class_desc,
                  OBJECT_NAME(p.major_id) AS object_name,
                  p.permission_name,
                  p.state_desc AS permission_state_desc
             FROM sys.database_permissions AS p
             JOIN sys.database_principals AS dp
               ON p.grantee_principal_id = dp.principal_id
          )
          --users
          INSERT #MemberRole (database_name, principal_name, principal_type_desc, role_name)
          SELECT ''?'',
                 CAST(p.principal_name AS VARCHAR(40)),
                 CAST(p.principal_type_desc AS VARCHAR(40)),
                 CAST(CAST(NULL AS SYSNAME) AS VARCHAR(40))
            FROM perms_cte AS p
           WHERE (principal_type_desc <> ''DATABASE_ROLE'')
          UNION
          --role members
          SELECT ''?'',
                 CAST(rm.member_principal_name AS VARCHAR(40)),
                 CAST(rm.principal_type_desc AS VARCHAR(40)),
                 CAST(rm.role_name AS VARCHAR(40))
            FROM perms_cte AS p
            RIGHT OUTER JOIN (SELECT role_principal_id,
                                     dp.type_desc AS principal_type_desc,
                                     member_principal_id,
                                     user_name(member_principal_id) as member_principal_name,
                                     user_name(role_principal_id) as role_name
                                FROM sys.database_role_members AS rm
                                JOIN sys.database_principals AS dp
                                  ON rm.member_principal_id = dp.principal_id
                               ) AS rm
            ON rm.role_principal_id = p.principal_id
      ORDER BY p.principal_name ASC'

          DELETE dbo.#MemberRole
           WHERE (database_name IN ('model', 'master', 'tempdb', 'pubs', 'Northwind', 'msdb',
                                    'ReportServer', 'ReportServerTempDB', 'AdventureWorksDW2012',
                                    'AdventureWorks2012', 'DBAMaintenance'));
          DELETE dbo.#MemberRole
           WHERE (role_name IS NULL)

          DELETE dbo.#MemberRole
           WHERE (principal_name = 'dbo')

          -- Below just for Report purpose.

          IF OBJECT_ID('Tempdb.dbo.#FinalResult', 'u') IS NOT NULL
            DROP TABLE dbo.#FinalResult

          INSERT @FinalResult (seq_num, database_name, iuser_name, principal_type_desc, role_name)
          SELECT ROW_NUMBER() OVER(PARTITION BY database_name ORDER BY database_name ASC) AS 'seq_num'
                ,database_name
                ,principal_name
                ,principal_type_desc
                ,role_name
            FROM dbo.#MemberRole
           WHERE (database_name = ISNULL(@DBName, database_name))
        ORDER BY database_name ASC, seq_num ASC, principal_name ASC


-- stop Issues.
-- This section update if a user is in Server Role.
   UPDATE a
      SET server_role = b.ServerRole
     FROM @FinalResult AS a
     JOIN dbo.#ServerRole AS b
       ON ( RTRIM(a.iuser_name) = RTRIM(b.UserName) );


        IF (@UserName IS NOT NULL)
          BEGIN
            --PRINT '--Go to 1.'
              SELECT --seq_num,
                    CAST(@@SERVERNAME AS VARCHAR(35)) AS 'servername'
                   ,database_name AS [DatabaseName]
                   ,iuser_name AS [UserName]
                   ,principal_type_desc AS 'TypeDesc'
                   ,role_name AS 'DatabaseRole'
                   ,ISNULL(server_role, ' ') AS 'ServerRole'
               FROM @FinalResult
              WHERE (iuser_name = ISNULL(@UserName, iuser_name) )
          END
       ELSE
         IF (@UserName IS NULL)
         AND (@DBName IS NOT NULL)
          BEGIN
             --PRINT 'Go to 2.'
             SELECT --seq_num,
                     CAST(@@SERVERNAME AS VARCHAR(35)) AS 'ServerName'
                    ,database_name AS [DatabaseName]
                    ,iuser_name AS [UserName]
                    ,principal_type_desc AS 'TypeDesc'
                    ,role_name AS 'DatabaseRole'
                    ,ISNULL(server_role, ' ') AS 'ServerRole'
                FROM @FinalResult
               WHERE (database_name = ISNULL(@DBName, database_name) )
           END
        ELSE
          IF (@UserName IS NULL)
         AND (@DBName IS NULL)
          BEGIN
             --PRINT '--Go to 3.'
             UPDATE @FinalResult
                SET database_name = ' '
              WHERE (seq_num > 1);


             -- Output results.
             SELECT --seq_num,
                    CAST(@@SERVERNAME AS VARCHAR(35)) AS 'ServerName'
                   ,database_name AS [DatabaseName]
                   ,iuser_name AS [UserName]
                   ,principal_type_desc AS 'TypeDesc'
                   ,role_name AS 'DatabaseRole'
                   ,ISNULL(server_role, ' ') AS 'ServerRole'
                FROM @FinalResult;
           END
   GO

---------------------------------------------------------------------------------------------------------

EXECute msdb.dbo.usp_AdminReturnUserAccessReport @UserName = 'ABC\LaNguyen'
                                                ,@dbname = 'prod1'
GO

July 20th, 2015 2:26pm

I would suggest that you restructure the report format and  all the server role membership in a separate secton with NULL for the database name, or the text "SERVER ROLES" in that column.

The desired output where you have

   db_datareader   serveradmin
   db_datawriter   setupadmin

is achieveable, but not desirable. And messy. Say that Joe Cool is member of eight server roles, and then member of db_datareader (but nothing more) in 15 databases. Should you really output 120 rows where you repeat these eight server roles again and again?   

If you want to pursue the current approach, you need to add a row_number per database in @FinalResult, and likewise a row_number in #ServerRole. Then you need to use MERGE - update if row number matches, and insert if not there.

I would also recommend that you identify keys in your work tables, and the correct nullability. This usually pays off and helps you get things right.

Free Windows Admin Tool Kit Click here and download it now
July 20th, 2015 6:07pm

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics