Send email when application user login to SSMS

  • Hello,

    I need to create some trigger or some other mechanism so that I can get email when developers login to SQL Server Management studio using application user account.

  • I tried this code but seems like something is wrong and it generated more than 2500 emails just for 1 event that I tested.

    USE [msdb]

    GO

    -- Create a queue

    CREATE QUEUE Q_Logon_Triggers_Queue;

    GO

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

    USE [msdb]

    GO

    -- Create a service

    CREATE SERVICE S_Logon_Triggers_Service

    ON QUEUE Q_Logon_Triggers_Queue([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);

    GO

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

    USE [msdb]

    GO

    -- Create a route

    CREATE ROUTE R_Logon_Triggers_Route

    WITH SERVICE_NAME = N'S_Logon_Triggers_Service'

    , ADDRESS = N'LOCAL';

    GO

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

    USE [msdb]

    GO

    /* current user get ownership of EVENT NOTIFICATION, so switch to 'sa' */

    EXEC AS LOGIN = 'sa';

    go

    -- Create the event notification at the server level for the AUDIT_LOGIN event

    CREATE EVENT NOTIFICATION N_Login_Notification

    ON SERVER FOR AUDIT_LOGIN

    TO SERVICE 'S_Logon_Triggers_Service', 'current database';

    go

    /* Switch back to original user */

    REVERT;

    GO

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

    USE [msdb]

    GO

    ALTER PROCEDURE [dbo].[p_dba_appuser_login_ssms_events]

    WITH EXECUTE AS OWNER

    AS

    BEGIN

    SET NOCOUNT ON;

    WHILE (1 = 1)

    BEGIN

    DECLARE @messageBody VARBINARY(MAX);

    DECLARE @messageTypeName NVARCHAR(256);

    WAITFOR (

    RECEIVE TOP(1)

    @messageTypeName = message_type_name,

    @messageBody = message_body

    FROM Q_Logon_Triggers_Queue

    ), TIMEOUT 500

    -- If there is no message, exit

    IF @@ROWCOUNT = 0

    BEGIN

    BREAK ;

    END ;

    -- If the message type is EventNotification do the actual work

    IF (@messageTypeName = 'http://schemas.microsoft.com/SQL/Notifications/EventNotification')

    BEGIN

    DECLARE @XML XML,

    @host_name varchar(128) ,

    @program_name varchar(128) ,

    @nt_domain varchar(128) ,

    @nt_user_name varchar(128) ,

    @login_name varchar(128) ,

    @original_login_name varchar(128) ,

    --@client_net_address varchar(48) ,

    @Database_Name varchar(128) ,

    @ts_logon datetime,

    @Subject_line Varchar(200),

    @body_line Varchar(4000),

    @SPID VARCHAR(5);

    SELECT @XML=CONVERT(XML,@messageBody)

    ,@host_name = ''

    ,@program_name = ''

    ,@nt_domain = ''

    ,@nt_user_name = ''

    ,@login_name = ''

    ,@original_login_name = ''

    --,@client_net_address =''

    ,@SPID ='';

    -- Get the SPID and the Login name using the value method

    SELECT @SPID = @XML.value('(/EVENT_INSTANCE/SPID)[1]', 'VARCHAR(5)')

    , @ts_logon = @XML.value('(/EVENT_INSTANCE/StartTime)[1]', 'NVARCHAR(128)')

    , @host_name = @XML.value('(/EVENT_INSTANCE/HostName)[1]', 'NVARCHAR(128)')

    , @program_name = @XML.value('(/EVENT_INSTANCE/ApplicationName)[1]', 'NVARCHAR(128)')

    , @nt_domain = @XML.value('(/EVENT_INSTANCE/NTDomainName)[1]', 'NVARCHAR(128)')

    , @nt_user_name = @XML.value('(/EVENT_INSTANCE/NTUserName)[1]', 'NVARCHAR(128)')

    , @original_login_name = @XML.value('(/EVENT_INSTANCE/SessionLoginName)[1]', 'NVARCHAR(128)')

    , @login_name = @XML.value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(128)')

    , @Database_Name = @XML.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(128)')

    ;

    SET @Subject_line = 'Application user tried to login to ssms on '+@host_name+' Detected'

    SET @body_line = 'Application user ' + @login_name+ ' tried to login to SSMS from ' + @host_name+ ' from '+ @program_name --+'.' -- ' at ' + @login_time +'.'

    EXEC msdb.dbo.sp_send_dbmail

    @profile_name = 'dev',

    @recipients = 'abc@abc.com',

    @subject = @subject_line,

    @body = @body_line;

    END

    END

    END

    --END

    GO

    ALTER QUEUE Q_Logon_Triggers_Queue

    WITH ACTIVATION

    (

    STATUS = ON,

    PROCEDURE_NAME = [dbo].[p_dba_appuser_login_ssms_events],

    MAX_QUEUE_READERS = 1,

    EXECUTE AS OWNER

    );

    GO

  • Why not just use a logon trigger? You could even prevent that logon from occurring.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Looking at this I wonder why not just use dbmail instead of the event notification?

    In addition, a logon trigger would suit nicely here.

    I am also missing how this results in false.

    WHILE (1 = 1)

    Looks to me like an infinite loop if the rowcount is greater than 0. But maybe I missed something in the code.

    In short, this is overkill for what is needed. The code at http://www.sqlservercentral.com/Forums/FindPost1742443.aspx would work just find. Just throw a sp_send_dbmail in lieu of the raiserror.

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

  • Thnaks but in the same thread if you go to page-3, U will see my code which says, and i still have issues.

    --SELECT * FROM sys.dm_exec_sessions order by login_time desc

    USE [master]

    GO

    /****** Object: DdlTrigger [TR_LOGON_APP] Script Date: 12/3/2015 11:56:37 AM ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    ALTER TRIGGER [TR_LOGON_APPUSER_SSMS]

    ON ALL SERVER

    FOR LOGON

    AS

    BEGIN

    DECLARE @program_name nvarchar(128)

    DECLARE @host_name nvarchar(128)

    DECLARE @login_name nvarchar(128)

    DECLARE @login_time DATETIME

    DECLARE @Subject_line nvarchar(128)

    DECLARE @body_line nvarchar(MAX)

    SELECT @program_name = program_name,

    @host_name = host_name,

    @login_name = login_name,

    @login_time = login_time

    FROM sys.dm_exec_sessions AS c

    WHERE c.session_id = @@spid

    IF ORIGINAL_LOGIN() LIKE '%user' OR ORIGINAL_LOGIN() LIKE 'ssis%' OR ORIGINAL_LOGIN() LIKE 'bo%'

    AND @program_name LIKE '%Management%Studio%'

    BEGIN

    --RAISERROR('This login is for application use only.',16,1)

    SET @Subject_line = 'Application user tried to login to ssms on '+@host_name+' Detected'

    SET @body_line = 'Application user ' + @login_name+ ' tried to login to SSMS from ' + @host_name+ ' from '+ @program_name+ ' at ' + @login_time +'.'

    EXEC msdb.dbo.sp_send_dbmail

    @profile_name = 'dev',

    @recipients = 'abc@abc.com',

    @subject = @Subject_line,

    @body = @body_line

    --ROLLBACK;

    END

    END;

    GO

  • I had to do something quite similar this year and I can share my experience with it.

    Personally, I would avoid both logon triggers and service broker for this particular problem.

    Logon triggers can slow down the application significantlty, even when the trigger code does nothing at all.

    Service broker can sometimes go nuts and start throwing poison messages. You don't want that to happen for an event that fires hundreds of times per second: it will quickly fill your sysxmitqueue table in msdb (true story).

    What I ended up doing is this:

    1) create an extended events session for the logon events

    2) watch the session with the XE streaming API

    3) send an email (or whatever makes sense for you) from the app that reads the stream

    I have an example of streaming XEvents from powershell here: http://spaghettidba.com/2015/04/20/tracking-table-usage-and-identifying-unused-objects/

    -- Gianluca Sartori

  • Here is a quick and dirty example.

    First, you need an event session:

    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

    Then you need a script to capture the events and process them.

    [CmdletBinding()]

    Param(

    [Parameter(Mandatory=$True,Position=1)]

    [string]$servername

    )

    sl $Env:Temp

    Add-Type -Path 'C:\Program Files\Microsoft SQL Server\120\Shared\Microsoft.SqlServer.XE.Core.dll'

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

    $SessionName = "Audit_Logon"

    # 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

    #send email

    if((($original_login_name -like "*user") -or ($original_login_name -like "ssis*") -or ($original_login_name -like "bo*")) -and $client_app_name -like "*Management*Studio*")

    {

    $subject = "Application user tried to login to ssms on " + $client_host_name + " detected "

    $body = "Application user " + $original_login_name + " tried to login to SSMS from " + $client_host_name + " from " + $client_app_name + " at " + Get-Date

    Send-MailMessage -To "dba@mycompany.com" -Subject $subject -Body $body

    }

    }

    The easiest way to run the script is from a SQL Agent job:

    USE [msdb]

    GO

    BEGIN TRANSACTION

    DECLARE @ReturnCode INT

    SELECT @ReturnCode = 0

    IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)

    BEGIN

    EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    END

    DECLARE @jobId BINARY(16)

    EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBA_COLLECT_AUDIT_LOGIN',

    @enabled=1,

    @notify_level_eventlog=0,

    @notify_level_email=0,

    @notify_level_netsend=0,

    @notify_level_page=0,

    @delete_level=0,

    @description=N'No description available.',

    @category_name=N'[Uncategorized (Local)]',

    @owner_login_name=N'sa', @job_id = @jobId OUTPUT

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    /****** Object: Step [COLLECT] Script Date: 04/12/15 10:06:56 ******/

    EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'COLLECT',

    @step_id=1,

    @cmdexec_success_code=0,

    @on_success_action=1,

    @on_success_step_id=0,

    @on_fail_action=2,

    @on_fail_step_id=0,

    @retry_attempts=0,

    @retry_interval=0,

    @os_run_priority=0, @subsystem=N'CmdExec',

    @command=N'powershell -File X:\scripts\collect_audit_login.ps1 -servername $(ESCAPE_DQUOTE(SRVR))',

    @flags=0,

    @proxy_name=N'SomeProxyAccountThatCanSendEmail'

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'EVERY MINUTE',

    @enabled=1,

    @freq_type=4,

    @freq_interval=1,

    @freq_subday_type=4,

    @freq_subday_interval=1,

    @freq_relative_interval=0,

    @freq_recurrence_factor=0,

    @active_start_date=20150227,

    @active_end_date=99991231,

    @active_start_time=0,

    @active_end_time=235959,

    @schedule_uid=N'0db89756-e08f-4e7f-914a-4137518608fe'

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    COMMIT TRANSACTION

    GOTO EndSave

    QuitWithRollback:

    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

    EndSave:

    GO

    The job will alway be running, but I'm ok with that.

    A good improvement could be filtering the events directly in the session definition instead of filtering them in powershell. I'll leave that excercise for you.

    BTW, in the previous thread you mentioned that the target instance was SQL 2014, but you posted in the SQL 2008 forum. Obviously, this code won't work in 2008.

    Hope this helps.

    Gianluca

    -- Gianluca Sartori

  • Thanks but unfortunately we r still on sql 2005 server and this feature is not there.

  • Let's see if I understand correctly:

    * you said you're on 2014

    * you posted in the 2008 forums

    * you are on 2005 instead

    OK, makes sense now 🙂

    -- Gianluca Sartori

  • Where exactly I said its 2014? I don't remember but if I said I m so sorry.

    And I put it in 2008 becuase 2005 and 2008 are still very similar and no one reads 2005 forum so to get faster answer I put it in 2008.

  • dallas13 (12/4/2015)


    Where exactly I said its 2014? I don't remember but if I said I m so sorry.

    And I put it in 2008 becuase 2005 and 2008 are still very similar and no one reads 2005 forum so to get faster answer I put it in 2008.

    No worries, I'm just joking 🙂

    BTW, I think it was here: http://www.sqlservercentral.com/Forums/FindPost1702825.aspx

    However, I'm sorry that the solution I offered was not useful for you.

    In 2005 I would use logon triggers. Service broker is too dangerous in my opinion, especially if you're not monitoring the queues attentively.

    -- Gianluca Sartori

  • dallas13 (12/3/2015)


    Thnaks but in the same thread if you go to page-3, U will see my code which says, and i still have issues.

    --SELECT * FROM sys.dm_exec_sessions order by login_time desc

    USE [master]

    GO

    /****** Object: DdlTrigger [TR_LOGON_APP] Script Date: 12/3/2015 11:56:37 AM ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    ALTER TRIGGER [TR_LOGON_APPUSER_SSMS]

    ON ALL SERVER

    FOR LOGON

    AS

    BEGIN

    DECLARE @program_name nvarchar(128)

    DECLARE @host_name nvarchar(128)

    DECLARE @login_name nvarchar(128)

    DECLARE @login_time DATETIME

    DECLARE @Subject_line nvarchar(128)

    DECLARE @body_line nvarchar(MAX)

    SELECT @program_name = program_name,

    @host_name = host_name,

    @login_name = login_name,

    @login_time = login_time

    FROM sys.dm_exec_sessions AS c

    WHERE c.session_id = @@spid

    IF ORIGINAL_LOGIN() LIKE '%user' OR ORIGINAL_LOGIN() LIKE 'ssis%' OR ORIGINAL_LOGIN() LIKE 'bo%'

    AND @program_name LIKE '%Management%Studio%'

    BEGIN

    --RAISERROR('This login is for application use only.',16,1)

    SET @Subject_line = 'Application user tried to login to ssms on '+@host_name+' Detected'

    SET @body_line = 'Application user ' + @login_name+ ' tried to login to SSMS from ' + @host_name+ ' from '+ @program_name+ ' at ' + @login_time +'.'

    EXEC msdb.dbo.sp_send_dbmail

    @profile_name = 'dev',

    @recipients = 'abc@abc.com',

    @subject = @Subject_line,

    @body = @body_line

    --ROLLBACK;

    END

    END;

    GO

    What is the error that you are getting? Or what is the issue with this code that you are experiencing

    Jason...AKA CirqueDeSQLeil
    _______________________________________________
    I have given a name to my pain...MCM SQL Server, MVP
    SQL RNNR
    Posting Performance Based Questions - Gail Shaw[/url]
    Learn Extended Events

  • When I use this,

    IF ORIGINAL_LOGIN() LIKE '%user' OR ORIGINAL_LOGIN() LIKE 'ssis%' OR ORIGINAL_LOGIN() LIKE 'bo%'

    After then when application user logs in, it throws error which I don't want.

    So instead I used below code but then user logs in but I dont get emails. Also no errors in error log as well as sql db mail logs. which is starnge.

    IF @login_name LIKE '%user' OR @login_name LIKE 'ssis%' OR @login_name LIKE 'bo%'

  • My code is working but its sending thousands of emails for 1 event. Isn't that abnormal?

Viewing 14 posts - 1 through 13 (of 13 total)

You must be logged in to reply to this topic. Login to reply