SQL 未使用的登录名

jas*_*son 5 sql-server maintenance sql-server-2012

有没有办法找出过去 90 天内未登录 SQL Server 或访问数据库的登录名?

spa*_*dba 6

您可以使用扩展事件会话捕获此信息:

CREATE EVENT SESSION [Audit_Logon] ON SERVER 
ADD EVENT sqlserver.LOGIN (
    SET  collect_database_name = (1)    
        ,collect_options_text = (0) 
    ACTION(
         sqlserver.client_app_name
        ,sqlserver.client_hostname
        ,sqlserver.server_principal_name
    )
)
WITH (
     MAX_MEMORY = 4096 KB
    ,EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS
    ,MAX_DISPATCH_LATENCY = 30 SECONDS
    ,MAX_EVENT_SIZE = 0 KB
    ,MEMORY_PARTITION_MODE = NONE
    ,TRACK_CAUSALITY = OFF
    ,STARTUP_STATE = ON
)
GO
Run Code Online (Sandbox Code Playgroud)

然后,您可以流式传输会话中捕获的事件并使用 powershell 脚本处理它们。

首先,您需要几个表来存储结果。在这种情况下,您可以使用临时表来临时存储事件,并使用目标表以汇总形式存储事件。您对单个事件不感兴趣,您只需要记录上次成功登录的时间。如果这对您有意义,您可以通过一些有意义的属性对事件进行分组:

  • 登录名
  • 主机名
  • 应用名称
  • 数据库名称

master以下位置创建这些表:

CREATE TABLE [dbo].[AuditLogin](
    [LoggingID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    [LoginName] [sysname] NOT NULL,
    [HostName] [varchar](100) NULL,
    [NTUserName] [varchar](100) NULL,
    [NTDomainName] [varchar](100) NULL,
    [ApplicationName] [varchar](340) NULL,
    [DatabaseName] [nvarchar](4000) NULL,
    [FirstSeen] [datetime] NULL,
    [LastSeen] [datetime] NULL,
    [LogonCount] [bigint] NULL,
) 
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_AuditLogon] ON [dbo].[AuditLogin]
(
    [LoginName] ASC,
    [HostName] ASC,
    [ApplicationName] ASC,
    [DatabaseName] ASC
)
GO

CREATE TABLE [dbo].[AuditLogin_Staging](
    [event_date] [datetime] NULL,
    [original_login] [nvarchar](128) NULL,
    [host_name] [nvarchar](128) NULL,
    [program_name] [nvarchar](255) NULL,
    [database_name] [nvarchar](128) NULL
)
GO
Run Code Online (Sandbox Code Playgroud)

然后您将需要一个存储过程来将数据从暂存表合并到目标表:

USE master
GO

CREATE PROCEDURE [dbo].[spConsolidateAuditLogin]
AS
BEGIN

    SET NOCOUNT ON;

    IF OBJECT_ID('tempdb..#AuditLogin_Staging') IS NOT NULL
        DROP TABLE #AuditLogin_Staging;

    CREATE TABLE #AuditLogin_Staging(
        [event_date] [datetime] NULL,
        [original_login] [nvarchar](128) NULL,
        [host_name] [nvarchar](128) NULL,
        [program_name] [nvarchar](255) NULL,
        [database_name] [nvarchar](128) NULL
    );


    DELETE 
    FROM dbo.AuditLogin_Staging
    OUTPUT DELETED.* INTO #AuditLogin_Staging;




    MERGE INTO [AuditLogin] AS AuditLogin
    USING (
        SELECT MAX(event_date), original_login, host_name, program_name, database_name
            ,NtDomainName = CASE SSP.type WHEN 'U' THEN LEFT(SSP.name,CHARINDEX('\',SSP.name,1)-1) ELSE '' END
            ,NtUserName = CASE SSP.type WHEN 'U' THEN RIGHT(SSP.name,LEN(ssp.name) - CHARINDEX('\',SSP.name,1)) ELSE '' END
            ,COUNT(*)
        FROM #AuditLogin_Staging AS ALA
        INNER JOIN sys.server_principals AS SSP
            ON ALA.original_login = SSP.name
        GROUP BY original_login, host_name, program_name, database_name
            ,CASE SSP.type WHEN 'U' THEN LEFT(SSP.name,CHARINDEX('\',SSP.name,1)-1) ELSE '' END
            ,CASE SSP.type WHEN 'U' THEN RIGHT(SSP.name,LEN(ssp.name) - CHARINDEX('\',SSP.name,1)) ELSE '' END
    ) AS src (PostTime,LoginName,HostName,ApplicationName,DatabaseName,NtDomainName,NtUserName,LogonCount)
        ON AuditLogin.ApplicationName = src.ApplicationName
        AND AuditLogin.LoginName = src.LoginName
        AND AuditLogin.HostName = src.HostName
        AND AuditLogin.DatabaseName = src.DatabaseName
    WHEN MATCHED THEN 
        UPDATE SET
             LastSeen   = GETDATE()
            ,LogonCount += src.LogonCount
    WHEN NOT MATCHED THEN
        INSERT (
             LoginName
            ,HostName
            ,NTUserName
            ,NTDomainName
            ,ApplicationName
            ,DatabaseName
            ,FirstSeen
            ,LastSeen
            ,LogonCount
        )
        VALUES (
             src.LoginName
            ,src.HostName
            ,src.NTDomainName
            ,src.NTUserName
            ,src.ApplicationName
            ,src.DatabaseName
            ,src.PostTime
            ,src.PostTime
            ,src.LogonCount
        );
END
Run Code Online (Sandbox Code Playgroud)

有趣的部分是从会话流中读取事件的 powershell 脚本。将脚本另存为c:\capture_logon_events.ps1

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$True,Position=1)]
    [string]$servername
)

sl $Env:Temp

Add-Type -Path 'C:\Program Files\Microsoft SQL Server\110\Shared\Microsoft.SqlServer.XEvent.Linq.dll'


$connectionString = 'Data Source=' + $servername + '; Initial Catalog = master; Integrated Security = SSPI'

$SessionName = "Audit_Logon"


# create a DataTable to hold login information in memory
$queue = New-Object -TypeName System.Data.DataTable
$queue.TableName = $SessionName

[Void]$queue.Columns.Add("event_date",[DateTime])
[Void]$queue.Columns.Add("original_login",[String])
[Void]$queue.Columns.Add("host_name",[String])
[Void]$queue.Columns.Add("program_name",[String])
[Void]$queue.Columns.Add("database_name",[String])



$last_dump = [DateTime]::Now

# connect to the Extended Events session
[Microsoft.SqlServer.XEvent.Linq.QueryableXEventData] $events = New-Object -TypeName Microsoft.SqlServer.XEvent.Linq.QueryableXEventData `
    -ArgumentList @($connectionString, $SessionName, [Microsoft.SqlServer.XEvent.Linq.EventStreamSourceOptions]::EventStream, [Microsoft.SqlServer.XEvent.Linq.EventStreamCacheOptions]::DoNotCache)

$events | % {
    $currentEvent = $_

    $database_name = $currentEvent.Fields["database_name"].Value
    if($client_app_name -eq $null) { $client_app_name = [string]::Empty }
    $original_login_name = $currentEvent.Actions["server_principal_name"].Value
    $client_app_name = $currentEvent.Actions["client_app_name"].Value
    $client_host_name = $currentEvent.Actions["client_hostname"].Value

    $current_row = $queue.Rows.Add()
    $current_row["database_name"] = $database_name
    $current_row["program_name"] = $client_app_name
    $current_row["host_name"] = $client_host_name
    $current_row["original_login"] = $original_login_name
    $current_row["event_date"] = [DateTime]::Now


    $ts = New-TimeSpan -Start $last_dump -End (get-date)

    # Dump to database every 1 minutes
    if($ts.TotalMinutes -gt 1) {
        $last_dump = [DateTime]::Now

        # BCP data to the staging table master.dbo.master.dbo.AuditLogin_Staging
        $bcp = New-Object -TypeName System.Data.SqlClient.SqlBulkCopy -ArgumentList @($connectionString)
        $bcp.DestinationTableName = "master.dbo.AuditLogin_Staging"
        $bcp.Batchsize = 1000
        $bcp.BulkCopyTimeout = 0

        $bcp.WriteToServer($queue)

        $queue.Rows.Clear()

    }

}
Run Code Online (Sandbox Code Playgroud)

该脚本可以由作业中的 SQLAgent powershell 步骤运行。创建一个作业,将其设置为在 SQLAgent 启动时运行,使用以下代码添加一个 powershell 步骤:

powershell -File C:\capture_logon_events.ps1 -servername $(ESCAPE_DQUOTE(SRVR))
Run Code Online (Sandbox Code Playgroud)

然后,您需要另一个作业来使用存储过程合并事件:将其设置为每 5 分钟运行一次,并添加一个 T-SQL 步骤并调用合并存储过程。

EXEC [dbo].[spConsolidateAuditLogin]
Run Code Online (Sandbox Code Playgroud)

你已经完成了。看起来很复杂,其实不然。


Geo*_*wdy 1

/sf/ask/133365921/的可能重复

此查询应显示上次登录日期/时间和登录名。通过查询 sys.dm_exec_sessions 可以收集更多信息。

select max (login_time)as last_login_time, login_name from sys.dm_exec_sessions
group by login_name;
Run Code Online (Sandbox Code Playgroud)