Monday, November 18, 2019

How to get the Active Directory user password policy values

Recently I have had to troubleshoot quite a bit of SQL login issues and often times the issue was with the users active directory user account.

I was aware the my organization has security policies that includes among other things, active directory password policy. Of course there is also SQL Server user security policy, which I know by heart.

To better support my users, I thought it would be a good idea to familiarize myself with the active directory password policy that is in effect. Now, there is a document that outlines the policy but, I want to get what is actually implemented.

Here, I am using powershell to get the password policy values.

You will need to have the ActiveDirectory PowerShell module installed for the following cmdlets to work.


Get-ADUserResultantPasswordPolicy -Identity aduser1

That returned nothing in my case. That most likely implies that the password policy is not assigned per user basis.
So now I am going to check what is the default password policy in the domain.

Get-ADDefaultDomainPasswordPolicy

Edited Results:

ComplexityEnabled : True
DistinguishedName : DC=internal,DC=external,DC=org
LockoutDuration : 00:30:00
LockoutObservationWindow : 00:30:00
LockoutThreshold : 6
MaxPasswordAge : 90.00:00:00
MinPasswordAge : 1.00:00:00
MinPasswordLength : 8
objectClass : {domainDNS}
objectGuid : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
PasswordHistoryCount : 8
ReversibleEncryptionEnabled : False



if you are curious what all these values mean, please see the below MS document:

Set-ADDefaultDomainPasswordPolicy



How do you use PowerShell to check if an active directory user locked out, disabled etc.?

If your organization uses a password policy (there are very good odds these days that it does) and, especially stricter password requirement for administrative users, your might have experienced instances where yours or your users Active Directory user might be locked out.

How do you check if that is the case? Well, for one thing the Windows will tell you so when you try to login and/or failed login attempts are logged in to sql log, event logs etc.  What if user does not logout or have more than one user account, one for regular use and one for administrative tasks? There maybe other scenarios where you have a need to check status of a user account in the Active Directory.

I don't have admin privileges and presumably you don't either.  However, I do have read permission on the AD so I could have used Active Directory Users and Groups snap-in.

But, here I am going to show the powershell way.

You will need to have the ActiveDirectory PowerShell module installed for the following cmdlets to work.


# Is account disabled?

get-aduser aduser1 -Properties enabled | ft Enabled

Enabled
-------
True


# Is account locked out?

get-aduser aduser1 -Properties LockedOut | ft LockedOut

LockedOut
---------
False


# When does the password expire?

Get-ADUser aduser1 -properties msDS-UserPasswordExpiryTimeComputed | select @{N="PasswordExpiryDate";E={[DateTime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}}


PasswordExpiryDate
------------------
2/13/2020 2:58:26 PM


# Finally, view all properties for a user account

get-aduser aduser1 -Properties *


Wednesday, November 13, 2019

Event Notifications Example in SQL Server


Event notifications are kinda like a trigger in the sense that they respond to specific event, specifically in response to DDL statements and SQL Trace events.
The major difference between the triggers and event notifications is that the triggers are fired in synchronous mode to execute a specific code within the same session and transaction. Whereas, the event notification do not execute any code, it only sends information in asynchronous mode which then can be logged and acted upon later on.


Here is a snippet from the Microsoft Documentation that explains the differences:

Event Notifications vs. Triggers

The following table compares and contrasts triggers and event notifications.
Triggers Event Notifications
DML triggers respond to data manipulation language (DML) events. DDL triggers respond to data definition language (DDL) events. Event notifications respond to DDL events and a subset of SQL trace events.
Triggers can run Transact-SQL or common language runtime (CLR) managed code. Event notifications do not run code. Instead, they send xml messages to a Service Broker service.
Triggers are processed synchronously, within the scope of the transactions that cause them to fire. Event notifications may be processed asynchronously and do not run in the scope of the transactions that cause them to fire.
The consumer of a trigger is tightly coupled with the event that causes it to fire. The consumer of an event notification is decoupled from the event that causes it to fire.
Triggers must be processed on the local server. Event notifications can be processed on a remote server.
Triggers can be rolled back. Event notifications cannot be rolled back.
DML trigger names are schema-scoped. DDL trigger names are database-scoped or server-scoped. Event notification names are scoped by the server or database. Event notifications on a QUEUE_ACTIVATION event are scoped to a specific queue.
DML triggers are owned by the same owner as the tables on which they are applied. The owner of an event notification on a queue may have a different owner than the object on which it is applied.
Triggers support the EXECUTE AS clause. Event notifications do not support the EXECUTE AS clause.
DDL trigger event information can be captured using the EVENTDATA function, which returns an xml data type. Event notifications send xml event information to a Service Broker service. The information is formatted to the same schema as that of the EVENTDATA function.
Metadata about triggers is found in the sys.triggers and sys.server_triggerscatalog views. Metadata about event notifications is found in the sys.event_notifications and sys.server_event_notifications catalog views.



In the following sample SQL script, I am creating an Extended Event to capture  ALTER TABLE events in a database.



-- Create a brand new database for the testing purpose
use master
go
if db_id('temp_event_notification_test_929368242990-321836') is not null
drop database [temp_event_notification_test_929368242990-321836]
go
CREATE DATABASE [temp_event_notification_test_929368242990-321836]
GO


-- Enable the service broker if its not already
if not exists (select * from sys.databases where name = '[temp_event_notification_test_929368242990-321836]' and is_broker_enabled = 1)
ALTER DATABASE [temp_event_notification_test_929368242990-321836] SET ENABLE_BROKER;
go

-- set the trustworth property ON
if not exists (select * from sys.databases where name = '[temp_event_notification_test_929368242990-321836]' and is_trustworthy_on = 1)
ALTER DATABASE [temp_event_notification_test_929368242990-321836] SET TRUSTWORTHY ON;
go

-- check if there is already a service broker end point running
if not exists (select * from sys.service_broker_endpoints where type_desc = 'SERVICE_BROKER' and state_desc = 'STARTED' )
BEGIN
-- check if there is a SB endpoint with same name
if not exists (select * from sys.service_broker_endpoints where NAME = 'en_service_broker_929368242990-321836')
BEGIN
-- check to make sure the tcp port is not already in use
if not exists (SELECT * FROM SYS.tcp_endpoints where port = 5122)
CREATE ENDPOINT [en_service_broker_929368242990-321836]
STATE = STARTED
AS TCP ( LISTENER_PORT = 5122)
FOR SERVICE_BROKER (AUTHENTICATION = WINDOWS );
else
raiserror('Error: An end point cannot be created. Please check if there is already one with same port.', 16,1)
END
else
raiserror('Error: An end point cannot be created. Please check if there is already one with same name.', 16,1)
END
GO
USE [temp_event_notification_test_929368242990-321836]

go
CREATE QUEUE [ent_929368242990-321836] ;
GO
CREATE SERVICE [ens_929368242990-321836]
ON QUEUE [ent_929368242990-321836]
(
[http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]
);
GO

CREATE ROUTE [enr_929368242990-321836]  
WITH SERVICE_NAME = 'ens_929368242990-321836',
ADDRESS = 'LOCAL';
GO

CREATE EVENT NOTIFICATION [enen_929368242990-321836]
ON DATABASE
FOR ALTER_TABLE
TO SERVICE 'ens_929368242990-321836', 'current database'

-- Test
-- Generate the events
if object_id('entt_929368242990-321836') is not null
drop table [entt_929368242990-321836]
go
create table [entt_929368242990-321836] (i int)
go
alter table [entt_929368242990-321836] add b int
go

-- verify/display that the event notification was captured
SELECT TOP (1000) *, casted_message_body = 
CASE message_type_name WHEN 'X' 
THEN CAST(message_body AS NVARCHAR(MAX))
ELSE message_body
END
FROM [temp_event_notification_test_929368242990-321836].[dbo].[ent_929368242990-321836] WITH(NOLOCK)

if @@ROWCOUNT = 0
RAISERROR('Error: Something is not right. Event notification was not captured.', 16,1)
else
SELECT 'Success!' Msg
go


-- clear the records from the queue
-- RECEIVE display the event as well as removes it from the queue
RECEIVE * FROM [ent_929368242990-321836] go
-- verify that the queue is now empty
SELECT TOP (1000) *, casted_message_body = CASE message_type_name WHEN 'X' THEN CAST(message_body AS NVARCHAR(MAX)) ELSE message_body END FROM [temp_event_notification_test_929368242990-321836].[dbo].[ent_929368242990-321836] WITH(NOLOCK)
-- Since I am only testing, I am using the following code to clean up afterwards
/* CLEAN UPUSE [temp_event_notification_test_929368242990-321836]goif exists (SELECT * FROM sys.event_notifications where name = '[enen_929368242990-321836]' and parent_class_desc = 'DATABASE')DROP EVENT NOTIFICATION [enen_929368242990-321836] ON DATABASE; go if exists (select * from sys.routes where name = '[enr_929368242990-321836]' and address = 'LOCAL')DROP ROUTE [enr_929368242990-321836] if exists (SELECT * FROM sys.services where name = 'ens_929368242990-321836')DROP SERVICE [ens_929368242990-321836]GOif exists (SELECT * FROM sys.service_queues where name = 'ent_929368242990-321836' and schema_id = 1)DROP QUEUE [dbo].[ent_929368242990-321836]GOuse mastergoif db_id('temp_event_notification_test_929368242990-321836') is not null drop database [temp_event_notification_test_929368242990-321836]go */

..............

Thursday, November 7, 2019

What SQL Server Agent Alerts Do I have setup?

I am in a situation where I have to incorporate SQL Server Agent alerts in my monitoring and alerting strategy.

I needed a query (DMV) to get details on what alerts are setup on each server. And the result is the following query that I will run as a multi-server query.



SELECT a.[id]                        [alert_id], 
a.[name] [alert_name],
a.[enabled] [is_alert_enabled],
o.[enabled] [is_operator_enabled],
o.[email_address] [email_address],
o.[pager_address] [pager_address],
o.[netsend_address] [netsend_address],
j.[name] [job_name],
a.[event_source] [alert_event_source],
a.[event_category_id] [alert_event_category_id],
sc.[name] [alert_category_name],
CASE sc.[category_class]
WHEN 1 THEN 'JOB'
WHEN 2 THEN 'ALERT'
WHEN 3 THEN 'OPERATOR'
ELSE '0'
END [alert_class_name],
sm.[description] [alert_message_description],
a.[event_id] [alert_event_id],
a.[message_id] [alert_message_id],
a.[severity] [alert_severity],
a.[enabled] [alert_enabled],
a.[delay_between_responses] [alert_delay_between_responses],
a.[last_occurrence_date] [alert_last_occurrence_date],
a.[last_occurrence_time] [alert_last_occurrence_time],
a.[last_response_date] [alert_last_response_date],
a.[last_response_time] [alert_last_response_time],
a.[notification_message] [alert_notification_message],
a.[include_event_description] [alert_include_event_description],
a.[database_name] [alert_database_name],
a.[event_description_keyword] [alert_event_description_keyword],
a.[occurrence_count] [alert_occurrence_count],
a.[count_reset_date] [alert_count_reset_date],
a.[count_reset_time] [alert_count_reset_time],
a.[job_id] [alert_job_id],
a.[has_notification] [alert_has_notification],
a.[flags] [alert_flags],
a.[performance_condition] [alert_performance_condition],
a.[category_id] [alert_category_id]
FROM msdb.dbo.sysalerts a
LEFT OUTER JOIN msdb.dbo.syscategories sc ON a.category_id = sc.category_id
LEFT OUTER JOIN msdb.dbo.sysnotifications sn ON ( a.id = sn.alert_id )
LEFT OUTER JOIN msdb.dbo.sysoperators o ON ( o.id = sn.operator_id )
LEFT OUTER JOIN msdb.dbo.sysjobs j ON j.job_id = a.job_id
LEFT OUTER JOIN msdb.dbo.sysmessages sm ON sm.error = a.message_id
and sm.msglangid = SERVERPROPERTY('LCID')
ORDER BY 1

And here is the sample result:




Finding out the most cached database in the buffer cache - The DMV Way


While there are many ways and criteria to find out what database is the most used, under optimized, trouble maker etc....., here is one more to find out the database/s using the most buffer cache.


SELECT 
CASE database_id
WHEN 32767 THEN 'ResourceDb'
ELSE db_name(database_id)
END AS database_name,
COUNT(*)AS cached_pages_count,
COUNT(*) / 128 / 1024 AS cache_size_gb

FROM sys.dm_os_buffer_descriptors
GROUP BY DB_NAME(database_id) ,database_id
ORDER BY cached_pages_count DESC;

Here is the results I got on one of the production SQL Servers. 



Lucky for me, it turned out to be an extreme case. Now I knew where I should focus optimization efforts or even if that database belongs with rest of the databases on that same server.

I have tested this query for SQL 2008 (SP3) and up.

Hope you find this useful.



Tuesday, July 23, 2019

A simple powershell script to look up server hardware and OS information


# Specify the server name here

$server         = "Server1"


# pull all the information
$hardware = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $server
$OS = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $server
$CPU = Get-CimInstance -ClassName Win32_Processor -ComputerName $server
$PhysicalMemory = Get-CimInstance -ClassName CIM_PhysicalMemory -ComputerName $server
$Bios = Get-CimInstance -ClassName Win32_BIOS -ComputerName $server

# build custom array to get some key properties in a single row
$server_summary = New-Object PSObject

Add-Member -inputObject $server_summary -memberType NoteProperty -Name Manufacturer -value $hardware.Manufacturer
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Model -value $hardware.Model
Add-Member -inputObject $server_summary -memberType NoteProperty -Name HypervisorPresent -value $hardware.HypervisorPresent
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Bios -value $Bios.Name
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OS -value $OS.Caption
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSArchitecture -value $OS.OSArchitecture
Add-Member -inputObject $server_summary -memberType NoteProperty -Name CPUs -value $CPU.count
Add-Member -inputObject $server_summary -memberType NoteProperty -Name PhySicalMemory_GB -value $total_memory_gb
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSVersionNumber -value $OS.Version
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMajorVersion -value $OS.ServicePackMajorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMinor -value $OS.ServicePackMinorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name LastBootUpTime -value $OS.LastBootUpTime

# Display the data on the screen

# But first, lets up the buffer size so we can see the complete output on the screen
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size (500, 3000)

"summary"
"======="

$server_summary | ft -AutoSize

""
"Detailed Properties"
"==================="

"Hardware:"
$hardware | ft -Property *

"Bios:"
$Bios | ft -Property *

"Operating System:"
$OS | ft -Property *

"CPUs:"
$CPU | ft -Property *

"Physical Memory:"
$PhysicalMemory | ft -property *




A simple powershell script to look up server hardware and OS information

This is a simple powershell script to query and display hardware and OS information from a remote computer.

It uses CIM (Common Information Model) that is available since Powershell version 3 and is the recommended direction.  Please see the following article on why "we" should use CIM instead of the WMI.

https://devblogs.microsoft.com/scripting/should-i-use-cim-or-wmi-with-windows-powershell/



# Specify the server name here

$server         = "server1"


# pull all the information
$hardware = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $server
$OS = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $server
$CPU = Get-CimInstance -ClassName Win32_Processor -ComputerName $server
$PhysicalMemory = Get-CimInstance -ClassName CIM_PhysicalMemory -ComputerName $server
$Bios = Get-CimInstance -ClassName Win32_BIOS -ComputerName $server

$total_memory = ($PhysicalMemory | measure-object -Property Capacity -sum).sum
$total_memory_gb = $total_memory / 1024 / 1024 / 1024

# build custom array to get some key properties in a single row
$server_summary = New-Object PSObject

Add-Member -inputObject $server_summary -memberType NoteProperty -Name Manufacturer -value $hardware.Manufacturer
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Model -value $hardware.Model
Add-Member -inputObject $server_summary -memberType NoteProperty -Name HypervisorPresent -value $hardware.HypervisorPresent
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Bios -value $Bios.Name
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OS -value $OS.Caption
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSArchitecture -value $OS.OSArchitecture
Add-Member -inputObject $server_summary -memberType NoteProperty -Name CPUs -value $CPU.count
Add-Member -inputObject $server_summary -memberType NoteProperty -Name PhySicalMemory_GB -value $total_memory_gb
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSVersionNumber -value $OS.Version
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMajorVersion -value $OS.ServicePackMajorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMinor -value $OS.ServicePackMinorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name LastBootUpTime -value $OS.LastBootUpTime

# Display the values

# First, lets up the buffer size first so we can see the complete output on the screen
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size (500, 3000)

"summary"
"======="

$server_summary | ft -AutoSize

""
"Detailed Properties"
"==================="

"Hardware:"
$hardware | ft -Property *

"Bios:"
$Bios | ft -Property *

"Operating System:"
$OS | ft -Property *

"CPUs:"
$CPU | ft -Property *

"Physical Memory:"
$PhysicalMemory | ft -property *




Caveat:

That worked on most servers but on some I ran into an error with the CIM.














So I tried the solution suggested by the error message, which is to run the winrm quickconfig on the remote computer.  That threw message that the "WinRM service is already running on this machine", so maybe there is a firewall that is blocking it.

So I decided to go back to the good old faithful WMI for those servers. The powershell methods are still interchangeable between CIM and WMI so all it took was to do a global search/ replace for  Get-CimInstance / Get-WmiObject.


# Specify the server name here

$server         = "server1"


# pull all the information
$hardware = Get-WmiObject -ClassName Win32_ComputerSystem -ComputerName $server
$OS = Get-WmiObject -ClassName Win32_OperatingSystem -ComputerName $server
$CPU = Get-WmiObject -ClassName Win32_Processor -ComputerName $server
$PhysicalMemory = Get-WmiObject -ClassName CIM_PhysicalMemory -ComputerName $server
$Bios = Get-WmiObject -ClassName Win32_BIOS -ComputerName $server

$total_memory = ($PhysicalMemory | measure-object -Property Capacity -sum).sum
$total_memory_gb = $total_memory / 1024 / 1024 / 1024

# build custom array to get some key properties in a single row
$server_summary = New-Object PSObject

Add-Member -inputObject $server_summary -memberType NoteProperty -Name Manufacturer -value $hardware.Manufacturer
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Model -value $hardware.Model
Add-Member -inputObject $server_summary -memberType NoteProperty -Name HypervisorPresent -value $hardware.HypervisorPresent
Add-Member -inputObject $server_summary -memberType NoteProperty -Name Bios -value $Bios.Name
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OS -value $OS.Caption
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSArchitecture -value $OS.OSArchitecture
Add-Member -inputObject $server_summary -memberType NoteProperty -Name CPUs -value $CPU.count
Add-Member -inputObject $server_summary -memberType NoteProperty -Name PhySicalMemory_GB -value $total_memory_gb
Add-Member -inputObject $server_summary -memberType NoteProperty -Name OSVersionNumber -value $OS.Version
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMajorVersion -value $OS.ServicePackMajorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name ServicePackMinor -value $OS.ServicePackMinorVersion
Add-Member -inputObject $server_summary -memberType NoteProperty -Name LastBootUpTime -value $OS.LastBootUpTime

# Display the values

# First, lets up the buffer size first so we can see the complete output on the screen
$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size (500, 3000)

"summary"
"======="

$server_summary | ft -AutoSize

""
"Detailed Properties"
"==================="

"Hardware:"
$hardware | ft -Property *

"Bios:"
$Bios | ft -Property *

"Operating System:"
$OS | ft -Property *

"CPUs:"
$CPU | ft -Property *

"Physical Memory:"
$PhysicalMemory | ft -property *

Simple enough?  Nah... but I am sticking with the title!



Tuesday, July 16, 2019

SQL Script to Create, Test and Verify Event Notifications

Event notifications are kinda like a trigger in the sense that they respond to specific event, specifically in response to DDL statements and SQL Trace events.
The major difference between the triggers and event notifications is that the triggers are fired in synchronous mode to execute a specific code within the same session and transaction. Whereas, the event notification do not execute any code, it only sends information in asynchronous mode which then can be logged and acted upon later on.


Here is a snippet from the Microsoft Documentation that explains the differences:


Event Notifications vs. Triggers

The following table compares and contrasts triggers and event notifications.
Triggers Event Notifications
DML triggers respond to data manipulation language (DML) events. DDL triggers respond to data definition language (DDL) events. Event notifications respond to DDL events and a subset of SQL trace events.
Triggers can run Transact-SQL or common language runtime (CLR) managed code. Event notifications do not run code. Instead, they send xml messages to a Service Broker service.
Triggers are processed synchronously, within the scope of the transactions that cause them to fire. Event notifications may be processed asynchronously and do not run in the scope of the transactions that cause them to fire.
The consumer of a trigger is tightly coupled with the event that causes it to fire. The consumer of an event notification is decoupled from the event that causes it to fire.
Triggers must be processed on the local server. Event notifications can be processed on a remote server.
Triggers can be rolled back. Event notifications cannot be rolled back.
DML trigger names are schema-scoped. DDL trigger names are database-scoped or server-scoped. Event notification names are scoped by the server or database. Event notifications on a QUEUE_ACTIVATION event are scoped to a specific queue.
DML triggers are owned by the same owner as the tables on which they are applied. The owner of an event notification on a queue may have a different owner than the object on which it is applied.
Triggers support the EXECUTE AS clause. Event notifications do not support the EXECUTE AS clause.
DDL trigger event information can be captured using the EVENTDATA function, which returns an xml data type. Event notifications send xml event information to a Service Broker service. The information is formatted to the same schema as that of the EVENTDATA function.
Metadata about triggers is found in the sys.triggers and sys.server_triggerscatalog views. Metadata about event notifications is found in the sys.event_notifications and sys.server_event_notifications catalog views.



In the following sample SQL script, I am creating an Extended Event to capture  ALTER TABLE events in a database.


-- Create a brand new database for the testing purpose
use master
go
if db_id('temp_event_notification_test_929368242990-321836') is not null
drop database [temp_event_notification_test_929368242990-321836]
go
CREATE DATABASE [temp_event_notification_test_929368242990-321836]
GO


-- Enable the service broker if its not already
if not exists (select * from sys.databases where name = '[temp_event_notification_test_929368242990-321836]' and is_broker_enabled = 1)
ALTER DATABASE [temp_event_notification_test_929368242990-321836] SET ENABLE_BROKER;
go

-- set the trustworth property ON
if not exists (select * from sys.databases where name = '[temp_event_notification_test_929368242990-321836]' and is_trustworthy_on = 1)
ALTER DATABASE [temp_event_notification_test_929368242990-321836] SET TRUSTWORTHY ON;
go

-- check if there is already a service broker end point running
if not exists (select * from sys.service_broker_endpoints where type_desc = 'SERVICE_BROKER' and state_desc = 'STARTED' )
BEGIN
-- check if there is a SB endpoint with same name
if not exists (select * from sys.service_broker_endpoints where NAME = 'en_service_broker_929368242990-321836')
BEGIN
-- check to make sure the tcp port is not already in use
if not exists (SELECT * FROM SYS.tcp_endpoints where port = 5122)
CREATE ENDPOINT [en_service_broker_929368242990-321836]
STATE = STARTED
AS TCP ( LISTENER_PORT = 5122)
FOR SERVICE_BROKER (AUTHENTICATION = WINDOWS );
else
raiserror('Error: An end point cannot be created. Please check if there is already one with same port.', 16,1)
END
else
raiserror('Error: An end point cannot be created. Please check if there is already one with same name.', 16,1)
END
GO
USE [temp_event_notification_test_929368242990-321836]

go
CREATE QUEUE [ent_929368242990-321836] ;
GO
CREATE SERVICE [ens_929368242990-321836]
ON QUEUE [ent_929368242990-321836]
(
[http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]
);
GO

CREATE ROUTE [enr_929368242990-321836]  
WITH SERVICE_NAME = 'ens_929368242990-321836',
ADDRESS = 'LOCAL';
GO

CREATE EVENT NOTIFICATION [enen_929368242990-321836]
ON DATABASE
FOR ALTER_TABLE
TO SERVICE 'ens_929368242990-321836', 'current database'

-- Test
-- Trigger the event notification
if object_id('entt_929368242990-321836') is not null
drop table [entt_929368242990-321836]
go
create table [entt_929368242990-321836] (i int)
go
alter table [entt_929368242990-321836] add b int
go

-- verify that the event notification was captured
SELECT TOP (1000) *, casted_message_body = 
CASE message_type_name WHEN 'X'
THEN CAST(message_body AS NVARCHAR(MAX))
ELSE message_body
END
FROM [temp_event_notification_test_929368242990-321836].[dbo].[ent_929368242990-321836] WITH(NOLOCK)

if @@ROWCOUNT = 0
RAISERROR('Error: Something is not right. Event notification was not captured.', 16,1)
else
SELECT 'Success!' Msg
go


-- clear the queue
RECEIVE * FROM [ent_929368242990-321836]
go

SELECT TOP (1000) *, casted_message_body =
CASE message_type_name WHEN 'X'
THEN CAST(message_body AS NVARCHAR(MAX))
ELSE message_body
END
FROM [temp_event_notification_test_929368242990-321836].[dbo].[ent_929368242990-321836] WITH(NOLOCK)


/* CLEAN UP
USE [temp_event_notification_test_929368242990-321836]
go
if exists (SELECT * FROM sys.event_notifications where name = '[enen_929368242990-321836]' and parent_class_desc = 'DATABASE')
DROP EVENT NOTIFICATION [enen_929368242990-321836] ON DATABASE;
go

if exists (select * from sys.routes where name = '[enr_929368242990-321836]' and address = 'LOCAL')
DROP ROUTE [enr_929368242990-321836]

if exists (SELECT * FROM sys.services where name = 'ens_929368242990-321836')
DROP SERVICE [ens_929368242990-321836]
GO
if exists (SELECT * FROM sys.service_queues where name = 'ent_929368242990-321836' and schema_id = 1)
DROP QUEUE [dbo].[ent_929368242990-321836]
GO
use master
go
if db_id('temp_event_notification_test_929368242990-321836') is not null
drop database [temp_event_notification_test_929368242990-321836]
go

*/

blah blah....

Thursday, July 11, 2019

Get email alert when number of queries waiting for CPU exceeds thresold

You may have situations where the CPU % usage is well below the alert threshold but still queries are running slow because they are waiting for CPU to be available.

This script creates an alert to send out email if the number of queries waiting for CPU exceeds the threshold.  Please update the value for the @operator variable to whatever is the email operator you have setup in the SQL Server Agent.

And since I am testing I am using the threshold value of 10. You may want to lower that after testing in your environment.

Lastly, Since I did not want to get bombarded with emails, I am using the 900 seconds (15 minutes) delay of between alert emails. Please feel free to adjust it to your needs.


USE [msdb]
GO
declare @operator varchar(500) -- email operator name
declare @threshold int -- number of queries waiting for the CPU
declare @delay_between_email_alerts int -- this value is in seconds
declare @drop_alert_if_exists bit -- drops and recreates the alert if already exists

-- Assign default values to variables
set @operator = 'DBA'
set @threshold = 10
set @delay_between_email_alerts = 900
set @drop_alert_if_exists = 0


declare @sql_add_alert nvarchar(4000)
declare @sql_add_notification nvarchar(4000)
declare @sql_drop_alert_if_exists nvarchar(4000)

if @drop_alert_if_exists = 1
begin
if exists (select * from msdb..sysalerts where name = 'Alert: Number of processes waiting for CPU exceeded thresold')
EXEC msdb.dbo.sp_delete_alert @name=N'Alert: Number of processes waiting for CPU exceeded thresold'
end

set @sql_add_alert =
'EXEC msdb.dbo.sp_add_alert @name=N''Alert: Number of processes waiting for CPU exceeded thresold'',
@message_id=0,
@severity=0,
@enabled=1,
@delay_between_responses=' + cast(@delay_between_email_alerts as nvarchar(10)) + ',
@include_event_description_in=1,
@category_name=N''[Uncategorized]'',
@performance_condition=N''Wait Statistics|Wait for the worker|Waits in progress|>|' + cast(@threshold as nvarchar(10)) + ''',
@job_id=N''00000000-0000-0000-0000-000000000000''
'
print @sql_add_alert
exec(@sql_add_alert)
set @sql_add_notification =
'EXEC msdb.dbo.sp_add_notification
@alert_name=N''Alert: Number of processes waiting for CPU exceeded thresold'',
@operator_name=N''' + @operator + ''',
@notification_method = 1
'
print @sql_add_notification
exec(@sql_add_notification)
go

Truncating very large transaction log in full recovery mode

While I always configure transaction log backups for every database in non-simple recovery model, and to compliment it I also have monitorin...