Exporting SQL Server Security Audit to Excel Using PowerShell

 DBAs often need a quick way to extract SQL Server security information logins, roles, and permissions into a readable format for audits and reviews.


USE [master]

GO


/****** Object:  StoredProcedure [dbo].[SP_Audit_DbPermissions]    Script Date: 2/15/2026 11:45:56 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE   PROCEDURE [dbo].[SP_Audit_DbPermissions]

AS

SET NOCOUNT ON;



-- =============================================


-- Description: List all DBs permission

--

-- =============================================

set nocount on


declare @permission table (

Database_Name sysname,

User_Role_Name sysname,

Account_Type nvarchar(60),

Action_Type nvarchar(128),

Permission nvarchar(60),

ObjectName sysname null,

Object_Type nvarchar(60)

)

declare @dbs table (dbname sysname)

declare @Next sysname

insert into @dbs

select name from sys.databases where state=0

order by name

select top 1 @Next = dbname from @dbs

while (@@rowcount<>0)

begin

insert into @permission

exec('use [' + @Next + ']

declare @objects table (obj_id int, obj_type char(2))

insert into @objects

select id, xtype from master.sys.sysobjects

insert into @objects

select object_id, type from sys.objects


SELECT ''' + @Next + ''', a.name as ''User or Role Name'', a.type_desc as ''Account Type'',

d.permission_name as ''Type of Permission'', d.state_desc as ''State of Permission'',

OBJECT_SCHEMA_NAME(d.major_id) + ''.'' + object_name(d.major_id) as ''Object Name'',

case e.obj_type

when ''AF'' then ''Aggregate function (CLR)''

when ''C'' then ''CHECK constraint''

when ''D'' then ''DEFAULT (constraint or stand-alone)''

when ''F'' then ''FOREIGN KEY constraint''

when ''PK'' then ''PRIMARY KEY constraint''

when ''P'' then ''SQL stored procedure''

when ''PC'' then ''Assembly (CLR) stored procedure''

when ''FN'' then ''SQL scalar function''

when ''FS'' then ''Assembly (CLR) scalar function''

when ''FT'' then ''Assembly (CLR) table-valued function''

when ''R'' then ''Rule (old-style, stand-alone)''

when ''RF'' then ''Replication-filter-procedure''

when ''S'' then ''System base table''

when ''SN'' then ''Synonym''

when ''SQ'' then ''Service queue''

when ''TA'' then ''Assembly (CLR) DML trigger''

when ''TR'' then ''SQL DML trigger''

when ''IF'' then ''SQL inline table-valued function''

when ''TF'' then ''SQL table-valued-function''

when ''U'' then ''Table (user-defined)''

when ''UQ'' then ''UNIQUE constraint''

when ''V'' then ''View''

when ''X'' then ''Extended stored procedure''

when ''IT'' then ''Internal table''

end as ''Object Type''

FROM [' + @Next + '].sys.database_principals a

left join [' + @Next + '].sys.database_permissions d on a.principal_id = d.grantee_principal_id

left join @objects e on d.major_id = e.obj_id

order by a.name, d.class_desc')

delete @dbs where dbname = @Next

select top 1 @Next = dbname from @dbs

end

set nocount off

select * from @permission

--where Action_Type not in ('select','CONNECT REPLICATION','connect','execute','REFERENCES','IMPERSONATE','VIEW DEFINITION','VIEW ANY COLUMN ENCRYPTION KEY DEFINITION','VIEW ANY COLUMN MASTER KEY DEFINITION',

--'VIEW CHANGE TRACKING','VIEW DATABASE STATE')

GO


/****** Object:  StoredProcedure [dbo].[SP_Audit_DbRoles]    Script Date: 2/15/2026 11:45:56 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE   PROCEDURE [dbo].[SP_Audit_DbRoles]

AS

SET NOCOUNT ON;



CREATE TABLE #UserPermission

(

ServerName SYSNAME,

DbName SYSNAME,

UserName SYSNAME,

TypeOfLogIn VARCHAR(50),

PermissionLevel VARCHAR(50),

TypeOfRole VARCHAR(50)

)

INSERT #UserPermission

EXEC sp_MSforeachdb '

use [?]

IF ''?'' <> ''master'' AND ''?'' <> ''model'' AND ''?'' <> ''msdb'' AND ''?'' <> ''tempdb'' and DATABASEPROPERTYEX(N''?'',''STATUS'') = ''online''


BEGIN

SELECT ServerName=@@servername, dbname=db_name(db_id()),p.name as UserName, p.type_desc as TypeOfLogin,

pp.name as PermissionLevel, pp.type_desc as TypeOfRole

FROM sys.database_role_members roles

JOIN sys.database_principals p ON roles.member_principal_id = p.principal_id

JOIN sys.database_principals pp ON roles.role_principal_id = pp.principal_id

--select p.name as DBUser, q.name as SrvUser,p.sid as DBSID, q.sid as SrvSID

--from sys.database_principals p

left join sys.server_principals q

on p.name COLLATE DATABASE_DEFAULT = q.name COLLATE DATABASE_DEFAULT

where p.type != "R"

and q.name is not null

END '


--select * from sys.database_principals


SELECT * FROM #UserPermission

DROP TABLE #UserPermission

GO


/****** Object:  StoredProcedure [dbo].[SP_Audit_Logins]    Script Date: 2/15/2026 11:45:56 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE   PROCEDURE [dbo].[SP_Audit_Logins]

AS

SET NOCOUNT ON;


--Script to generate Prevleged and Active SQL Accounts –


select ''

as PlatformName,

@@SERVERNAME as Server,

'SQL' as PlatformType,

sl.name as Account,

case when sl.isntgroup = 1 then 'GROUP'

when sl.isntgroup = 0 then 'USER'

end as 'Group',

'' as 'Owner',

case when isnull(sl.sysadmin,0) = 1 then 'Yes'

else 'No' end as 'Privileged?',

case when isnull(l.is_disabled,0) = 0 then 'Yes'

else 'No' end as 'Active Account?',

case when isnull(l.is_policy_checked,0) = 1 or sl.isntname = 1 then 'Yes'

else 'No' end as 'Password Required?',

case when isnull(l.is_expiration_checked, 0 ) = 1 or sl.isntname = 1 then ''

else 'Never' end as 'Password Expiration'


FROM syslogins sl

LEFT OUTER JOIN Sys.sql_logins l on sl.sid = l.sid

WHERE sl.name not like '##%' -- Exluding system generated accounts :

-- ##MS_AgentSigningCertificate##

-- ##MS_PolicyEventProcessingLogin##

-- ##MS_PolicySigningCertificate##

-- ##MS_PolicyTsqlExecutionLogin##

-- ##MS_SmoExtendedSigningCertificate##

-- ##MS_SQLAuthenticatorCertificate##

-- ##MS_SQLReplicationSigningCertificate##

-- ##MS_SQLResourceSigningCertificate##

AND sl.name not in ('NT SERVICE\SQLWriter', -- Excluding build in accounts

'NT SERVICE\Winmgmt',

'NT SERVICE\MSSQLSERVER',

'NT AUTHORITY\SYSTEM',

'NT SERVICE\SQLSERVERAGENT')

ORDER BY sl.name

GO


/****** Object:  StoredProcedure [dbo].[SP_Audit_ServerPermissions]    Script Date: 2/15/2026 11:45:56 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE   PROCEDURE [dbo].[SP_Audit_ServerPermissions]

AS

SET NOCOUNT ON;



---Script to generate Server level Roles & membership for all Logins -

SELECT --sys.server_permissions.class

--, sys.server_permissions.class_desc,

granteeserverprincipal.name AS Login_principal_name

--, sys.server_permissions.type

, sys.server_permissions.permission_name

--, sys.server_permissions.state

, sys.server_permissions.state_desc

--, sys.server_permissions.major_id

-- , sys.server_permissions.minor_id

-- , sys.server_permissions.grantee_principal_id

-- , sys.server_permissions.grantor_principal_id

, granteeserverprincipal.type_desc AS grantee_principal_type_desc

--, grantorserverprinicipal.name AS grantor_name

-- , CASE

-- WHEN sys.server_permissions.state = N'W'

-- THEN N'GRANT'

-- ELSE sys.server_permissions.state_desc

-- END + N' ' + sys.server_permissions.permission_name COLLATE SQL_Latin1_General_CP1_CI_AS + N' TO ' + QUOTENAME(granteeserverprincipal.name) AS permissionstatement

FROM sys.server_principals AS granteeserverprincipal

INNER JOIN sys.server_permissions

ON sys.server_permissions.grantee_principal_id = granteeserverprincipal.principal_id

INNER JOIN sys.server_principals AS grantorserverprinicipal

ON grantorserverprinicipal.principal_id = sys.server_permissions.grantor_principal_id


where permission_name not in ('Connect SQL','Connect')

GO


/****** Object:  StoredProcedure [dbo].[SP_Audit_ServerRoles]    Script Date: 2/15/2026 11:45:56 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE   PROCEDURE [dbo].[SP_Audit_ServerRoles]

AS

SET NOCOUNT ON;


SELECT

spU.name

,MAX(CASE WHEN srm.role_principal_id = 3 THEN 1 END) AS sysadmin

,MAX(CASE WHEN srm.role_principal_id = 4 THEN 1 END) AS securityadmin

,MAX(CASE WHEN srm.role_principal_id = 5 THEN 1 END) AS serveradmin

,MAX(CASE WHEN srm.role_principal_id = 6 THEN 1 END) AS setupadmin

,MAX(CASE WHEN srm.role_principal_id = 7 THEN 1 END) AS processadmin

,MAX(CASE WHEN srm.role_principal_id = 8 THEN 1 END) AS diskadmin

,MAX(CASE WHEN srm.role_principal_id = 9 THEN 1 END) AS dbcreator

,MAX(CASE WHEN srm.role_principal_id = 10 THEN 1 END) AS bulkadmin

FROM

sys.server_principals AS spR

JOIN

sys.server_role_members AS srm

ON

spR.principal_id = srm.role_principal_id

JOIN

sys.server_principals AS spU

ON

srm.member_principal_id = spU.principal_id

WHERE

spR.[type] = 'R'

GROUP BY

spU.name

GO

Long Running SQL Agent Jobs - How I Monitor Them Automatically

In day-to-day DBA work, one common issue we face is SQL Agent jobs that keep running for hours. They don’t fail, but they block other jobs, eat resources, and usually get noticed only after someone escalates.

To avoid that, I built a simple monitoring script that keeps an eye on long-running SQL Agent jobs and sends an email alert when something crosses the expected time.

How to Remotely Log Off a Stuck RDP Session

 Sometimes when working on a remote server, you may see "Signing out" stuck forever. In such cases, you can forcefully log off the session without restarting the server.




Using Command Prompt:-

qwinsta /server:ServerName

logoff <SessionID> /server:ServerName

Example logoff 2 /server:SP-SQL-BI


Using PowerShell:-

quser /server:ServerName

logoff <SessionID> /server:ServerName


Fast PowerShell Script to Copy Latest SQL Backups Over Network

If you're managing SQL Server backups and need to automatically copy the latest .bak files from one server to another within the same domain and on a high-bandwidth connection, this PowerShell + robocopy script is optimized for speed and reliability.

function Copy-LatestBackupFast {
    param (
        [string]$SourceDir,
        [string]$DestDir
    )

    # Ensure destination exists
    if (!(Test-Path $DestDir)) {
        New-Item -ItemType Directory -Path $DestDir -Force | Out-Null
    }

    # Get the latest .bak file
    $Latest = Get-ChildItem -Path $SourceDir -Filter *.bak | Sort-Object LastWriteTime -Descending | Select-Object -First 1

    if ($Latest) {
        $SourcePath = $Latest.FullName
        $FileName   = $Latest.Name

        # robocopy works on directories, so we pass the directory and file
        $SourceFolder = Split-Path $SourcePath

        $cmd = @(
            "robocopy",
            "`"$SourceFolder`"",
            "`"$DestDir`"",
            "`"$FileName`"",
            "/COPY:D",              # Copy data only
            "/NFL", "/NDL",         # No file/dir listing
            "/NP",                  # No progress
            "/NJH", "/NJS",         # No job header/summary
            "/R:0", "/W:0",         # No retries/wait
            "/MT:32"                # Multi-threaded copy
        ) -join ' '

        Write-Host "Copying: $FileName from $SourceFolder to $DestDir ..."
        Invoke-Expression $cmd
    } else {
        Write-Warning "No .bak files found in $SourceDir"
    }
}

# === Copy Each Backup Set ===

Copy-LatestBackupFast "\\node1\adads"             "\\node\Backup\addad"
Copy-LatestBackupFast "\\node2\aff"                "\\node\Backup\addd"
Copy-LatestBackupFast "\\node3\addd"            "\\node\Backup\asdref"
Copy-LatestBackupFast "\\node3\addd"   "\\node\Backup\adeedd"
Copy-LatestBackupFast "\\node3\eeww"        "\\node\Backup\addd"

Automate Emailing of Latest Excel Reports from Shared Folders Using PowerShell

Automatically send the latest Excel file from a shared folder via email, along with the file's creation date, using a simple PowerShell script. No more manual checks

# Configuration
$sharedFolder = "\\YourSharedServer\SharedFolderPath"
$smtpServer = "smtp.yourdomain.com"
$from = "sender@yourdomain.com"
$to = "recipient@yourdomain.com"
$subject = "Latest Excel File from Shared Location"

# Get the latest Excel file
$latestFile = Get-ChildItem -Path $sharedFolder -Filter *.xls* |
    Sort-Object LastWriteTime -Descending |
    Select-Object -First 1

if ($latestFile) {
    $filePath = $latestFile.FullName
    $fileName = $latestFile.Name
    $fileDate = $latestFile.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")

    $body = @"
Hello,

Please find the latest Excel file attached.

**File Name:** $fileName  
**Generated On:** $fileDate

Regards,  
Your DBA Automation
"@

    # Send email with attachment
    Send-MailMessage -From $from -To $to -Subject $subject -Body $body `
        -SmtpServer $smtpServer -BodyAsHtml -Attachments $filePath
} else {
    Write-Host "No Excel file found in the shared folder."
}