Can we create a trigger (or any object) to catch each login name - triggers

Actually I need to find all the users logged into a SQL Server in last month, so that I can delete the users which are not using SQL Server from AD group.
I was not able to find that so now I am trying to create trigger so that after one month I can delete users.

Under the server properties select the Security tab and enable Login auditing for "Both failed and successful logins". The information is then recorded in the SQL Server Logs.
It is also possible to do it via trigger. Something like
CREATE TRIGGER Logon_Audit
ON ALL SERVER
FOR LOGON
AS
BEGIN
declare #acct as nvarchar(max)
set #acct = ORIGINAL_LOGIN()
-- insert #acct into some table
END

I used fully qualified name for table and deleted multiple records
Create Table LoginDetails (iD int identity(1,1),LoginName nvarchar(max) , LoginTime datetime)
Create TRIGGER Logon_Audit
ON ALL SERVER
FOR LOGON
AS
BEGIN
declare #acct as nvarchar(max)
declare #LoginTime as nvarchar(max)
set #acct = ORIGINAL_LOGIN()
set #LoginTime = getdate()
insert into master..LoginDetails
Select top 1 #acct,#LoginTime
delete
from master..LoginDetails
where id not in (
Select max(id)
from LoginDetails
group by LoginName,LoginTime
)
END

Related

How to get Database Name in a Logon Trigger using EVENTDATA()

below the Database name is always blank
I am trying to get Database Name in a Logon Trigger using EVENTDATA() but its not working and it appears like the EVENTDATA() is not working at all, I am trying to block users who are using excel to query the 'TestDB'. Can someone tell me what I am doing wrong with this code.
CREATE TRIGGER tr_block_excel_users
ON ALL SERVER FOR LOGON
AS
declare #data XML
declare #DatabaseName as varchar(128)
SET #data = EVENTDATA();
set #DataBaseName = #data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'nvarchar(128)')
BEGIN
IF (select #DatabaseName) = 'TestDB' and ORIGINAL_LOGIN() <> N'xx\xxxxxxxex' AND APP_NAME() LIKE '%Microsoft Office%' OR APP_NAME() LIKE '%EXCEL%'
ROLLBACK;
END
I expected the name of the database using EVENTDATA(), but getting a blank database name,
If you are trying to just get the database and load it into the variable you can try:
SET #DatabBaseName = SELECT DB_NAME()

Get all database names from multiple servers

We have multiple SQL Servers and most of them are standalone. I am in need of creating a stored procedure / view that would insert all database names into a table from all servers.
Is there a way to do this via a stored procedure or a view? I do not have any powershell or .Net experience.
Here's what I have so far. I just can't figure out how to 'jump' from server to server and add all my results into a real table.
CREATE TABLE ##temp
(
DATABASE_NAME VARCHAR(100),
DATABASE_SIZE INT,
REMARKS VARCHAR(500)
)
INSERT into ##temp
EXEC [sp_databases]
--doing this to also get ServerName along with the db name.
--When I insert into a real table, I'll seperate it into two columns plus remove "#_!_#"
update ##temp
set DATABASE_NAME = (select ##SERVERNAME ) + '#_!_# ' + DATABASE_NAME
where DATABASE_NAME not like '%#_!_#%'
select DATABASE_NAME from ##temp
SQL Server Management Studio allows you to execute a query against multiple servers using the Registered Servers feature. This was added in SQL Server 2008 as this tutorial shows so you shouldn't worry about compatibility.
Running multi-server queries is easy:
From the View menu, select `Registered Servers. This will open a new window similar to the Object Explorer that displays the objects of a single server.
Add connections for all your servers connection details in the Local Server Groups folder
Right-click on the Local Server Groups folder and select New Query. The query you enter here will run an all registered servers.
To find all databases run select * from sys.databases or just sp_databases
SSMS will collect the results from all servers and display them in a grid. If you want the results to go to a single server's table though, you'll have to add the target server as a linked server to all others and use a four-part name to target the target table, eg INSERT INTO myManagementServer.MyDb.dbo.ThatTable...
SQL Server has even more powerful features for managing multiple servers. You can administer multiple servers through a Central Management Server and apply settings to multiple servers through policies. That feature was also added in 2008.
In SQL Server 2008 R2 the SQL Server Utility was added which goes even farther and collects diagnostics, metrics, performance data from multiple servers and stores it in a management warehouse for reporting. Imagine being able to see eg storage and query performance for multiple servers, or free space trends for the last X months.
The drawbacks are that historical data needs space. Collecting it also requires adding some stored procedures to all monitored servers, although this is done automatically.
For this kind of thing it's good to have at least one server that has a linked connection to all the servers you need information for. If you do then you can use this little script I just wrote:
-- (1) Create global temp table used to store results
IF OBJECT_ID('tempdb..##databases') IS NOT NULL DROP TABLE ##databases;
CREATE TABLE ##databases
(
serverDBID int identity,
serverName varchar(100),
databaseName varchar(100),
databaseSize decimal(20,6)
);
-- (2) Create and populate table variable used to collect server names
DECLARE #servers TABLE(id int identity, serverName varchar(100));
INSERT #servers(serverName)
SELECT name FROM sys.servers;
-- (3) loop through each DB and collect database names into ##databases
DECLARE #i int = 1, #serverName varchar(100), #db varchar(100), #sql varchar(8000);
WHILE #i <= (SELECT COUNT(*) FROM #servers)
BEGIN
SELECT #serverName = serverName FROM #servers WHERE id = #i;
SET #sql = 'INSERT ##databases(serverName, databaseName) SELECT '''+#serverName+
''', name FROM master.sys.databases';
EXEC (#sql);
SET #i += 1;
END;
-- (4) Collect database sizes
SET #i = 1; -- reset/re-use #i;
WHILE #i <= (SELECT COUNT(*) FROM ##databases)
BEGIN
SELECT #serverName = serverName, #db = databaseName
FROM ##databases
WHERE serverDBID = #i;
SET #sql =
'UPDATE ##databases
SET databaseSize =
(SELECT sum(size)/128. FROM ['+#serverName+'].['+#db+'].sys.database_files)
WHERE serverDBID = '+CAST(#i AS varchar(4))+';'
BEGIN TRY
EXEC (#sql);
END TRY
BEGIN CATCH
PRINT 'There was an error getting dbsize info for '+#serverName+' > '+#db;
END CATCH;
SET #i += 1;
END;
-- Final Output
SELECT * FROM ##databases;

SQL Server 2012 - Permission for access to sys.dm_db_index_usage_stats in Contained Database

In a contained database, how to I grant users read access on this table?
I am trying to create a function to get the date a table was last updated, but when contained users run it, it fails with error 297 ‘The user does not have permission to perform this action.’.
CREATE FUNCTION [MGMT].[GetLastObjectUpdateTime] (#ObjectName NVARCHAR(100))
RETURNS DATETIME
WITH EXECUTE AS OWNER
AS
BEGIN
DECLARE #Result DATETIME
SELECT #result = MAX(last_user_update)
FROM sys.dm_db_index_usage_stats
WHERE OBJECT_ID = OBJECT_ID(#ObjectName)
RETURN #Result
END
(code from : http://blog.sqlauthority.com/2009/05/09/sql-server-find-last-date-time-updated-for-any-table/#comment-684267 )
I would try granting the owner the server permission VIEW SERVER STATE which is needed to use that view.
USE master;
GO
GRANT VIEW SERVER STATE TO [owner login]
GO

Converting a SQL Server trigger to PostgreSQL trigger problems with the trigger function

I am in the middle of converting an existing SQL Server 2005 DB into a PostgreSQL 9.0 DB.
Everything works fine until now. I want to translate a SQL trigger into PostgreSQL but I have a problem with the trigger function.
I don't know how to implement the temp table inserted in the PostgreSQL syntax. In SQL Server the inserted table exists but not in PostgreSQL. Any ideas?
My code (PostgreSQL):
CREATE OR REPLACE FUNCTION func_co_insert()
RETURNS trigger AS
$BODY$begin
declare
aa bigint;
begin
select aa = co_id from inserted;
update com03 set co_creationdate = CURRENT_TIMESTAMP,
co_creationby = USER where co_id = aa;
end;
end;
Here the code of the trigger body of the SQL Server 2005 code
begin
declare #aa bigint;
select #aa = se_id from inserted;
update server set se_creationdate = CURRENT_TIMESTAMP , se_creationby = USER where se_id = #aa;
end;
thanks
Chris
The default in PostgreSQL is a row level trigger (as opposed to SQL Server where it's a statement level trigger), so there is no need for an "inserted" table to select from.
The new and old values can be accessed using the keyword new and old (old does not exist for an insert trigger).
In your case the statement would simply be:
update com03
set co_creationdate = CURRENT_TIMESTAMP,
co_creationby = CURRENT_USER
where co_id = new.co_id;
No need to "select from inserted".
This assumes the trigger is not firing for the table com03. If your trigger fires for com03 (which you didn't tell us), then it' even easier:
new.co_creationdate := current_timestamp;
new.co_creationby := current_user;
For details please refer to the manual: http://www.postgresql.org/docs/current/static/plpgsql-trigger.html
That page also contains an example which does exactly what you are trying to achieve

How to create DDL trigger to all databases in SQL Server 2005 instance

I am going to create a DDL trigger to all databases in SQL Server instance. I'd like to do it in one run instead of many runs for each database.
Below are the two T-SQL statements I need to execute:
-- Create table
use <dbname>
GO
CREATE TABLE dbo.ChangeAttempt
(EventData xml NOT NULL,
AttemptDate datetime NOT NULL DEFAULT GETDATE(),
DBUser char(50) NOT NULL)
GO
-- Create DDL trigger
use <dbname>
GO
CREATE TRIGGER db_trg_ObjectChanges
ON DATABASE
FOR ALTER_PROCEDURE, DROP_PROCEDURE,
ALTER_INDEX, DROP_INDEX,
ALTER_TABLE, DROP_TABLE, ALTER_TRIGGER, DROP_TRIGGER,
ALTER_VIEW, DROP_VIEW, ALTER_SCHEMA, DROP_SCHEMA,
ALTER_ROLE, DROP_ROLE, ALTER_USER, DROP_USER
AS
SET NOCOUNT ON
INSERT dbo.ChangeAttempt
(EventData, DBUser)
VALUES (EVENTDATA(), USER)
GO
My question is: how can I programmaticaly create DDL trigger in one run?
why do you need one run? this is the only way to do it.
Msg 111, Level 15, State 1, Line 2
'CREATE TRIGGER' must be the first statement in a query batch.
run the output generated by this:
DECLARE #DatabaseName varchar(500)
DECLARE #Database_id int
DECLARE #Query varchar(8000)
DECLARE #CRLF char(2)
SET #CRLF=CHAR(13)+CHAR(10)
---MODIFY THIS TO INCLUDE THE DATABASES THAT YOU WANT TO WORk ON
---MODIFY THIS TO INCLUDE THE DATABASES THAT YOU WANT TO WORk ON
select #Database_id=MIN(database_id) from sys.databases where database_id IN (5,7,8,6)
WHILE #Database_id IS NOT NULL
BEGIN
SELECT #DatabaseName=name from sys.databases where database_id=#Database_id
SET #Query='-- Create table'+#CRLF+#CRLF
+'use '+#DatabaseName+#CRLF
+' GO'+#CRLF
+' CREATE TABLE dbo.ChangeAttempt'+#CRLF
+' (EventData xml NOT NULL,'+#CRLF
+' AttemptDate datetime NOT NULL DEFAULT GETDATE(),'+#CRLF
+' DBUser char(50) NOT NULL)'+#CRLF
+'GO'+#CRLF+#CRLF
+'-- Create DDL trigger '+#CRLF+#CRLF
+'use '+#DatabaseName+#CRLF
+'GO'+#CRLF
+'CREATE TRIGGER db_trg_ObjectChanges'+#CRLF
+'ON DATABASE'+#CRLF
+'FOR ALTER_PROCEDURE, DROP_PROCEDURE,'+#CRLF
+' ALTER_INDEX, DROP_INDEX,'+#CRLF
+' ALTER_TABLE, DROP_TABLE, ALTER_TRIGGER, DROP_TRIGGER,'+#CRLF
+' ALTER_VIEW, DROP_VIEW, ALTER_SCHEMA, DROP_SCHEMA,'+#CRLF
+' ALTER_ROLE, DROP_ROLE, ALTER_USER, DROP_USER'+#CRLF
+'AS'+#CRLF
+'SET NOCOUNT ON'+#CRLF
+'INSERT dbo.ChangeAttempt'+#CRLF
+'(EventData, DBUser)'+#CRLF
+'VALUES (EVENTDATA(), USER)'+#CRLF
+'GO'+#CRLF
PRINT #Query
---MODIFY THIS TO INCLUDE THE DATABASES THAT YOU WANT TO WORk ON
---MODIFY THIS TO INCLUDE THE DATABASES THAT YOU WANT TO WORk ON
select #Database_id=MIN(database_id) from sys.databases WHERE database_id IN (5,7,8,6) AND database_id>#Database_id
END
EDIT
to determine what databases to generate scripts for do the following:
run this query:
select database_id,name from sys.databases
find all of the databases you want to run the scripts for
change my above script in two places (before loop & at bottom in loop) so all of the database_id that you want are in the following code section:
WHERE database_id IN (AAA,BBB,CCC,DDD,....)
You could use sp_MSforeachdb.
Something like this
sp_MSforeachdb
'
CREATE TABLE ?.dbo.ChangeAttempt
etc. etc. etc.
'
sp_MSforeachdb
'
CREATE TRIGGER ?.dbo.db_trg_ObjectChanges
etc. etc. etc.
'
I haven't tested this, in theory I'd expect it to work though. You want to make sure you exclude the system databases though.