Wayne Sheffield

My blog about SQL Server

Browsing Posts published by Wayne Sheffield

If you can establish a connection to a SQL Server, but are having problems logging in to it, you will get an 18456 error. This error is deliberately obfuscated in SQL Server so that the user can’t tell why the error failed (and thus try to crack into the server). All the user will get back is that the login failed, but not why.

However, being the DBA for this server, you have to troubleshoot this. Which means that you need to know why the login failed. The trick is to examine the SQL Server log. You can examine the log directly from SSMS, from the sp_readerrorlog stored procedure, or the xp_readerrorlog extended stored procedure. What you are looking for is the error number (18456), and the state. The state is the code that you need for why the login can’t log in to the server.

In 2006, Microsoft published a partial list of the different error states on this msdn blog post: http://blogs.msdn.com/b/sql_protocols/archive/2006/02/21/536201.aspx. In the years since, several other error states have been talked about by Microsoft in the comments to this article. This blog post is an effort to consolidate some of this information to make it easier to find what your issue could be faster.

I’m going to start off with the table in the aforementioned blog post. I’ve added to it some of the other error states mentioned, and have elaborated on some of the descriptions.

ERROR STATE ERROR DESCRIPTION

1

Prior to 2005, this is all you would get. 2005+ – you will get this if you don’t have permissions to even get an error message

2 and 5

Invalid userid

6

Attempt to use a Windows login name with SQL Authentication

7

Login disabled and password mismatch

8

Password mismatch (2005+: Passwords are case sensitive)

9

Invalid password

11 and 12

Valid login but server access failure (Windows login is valid, but doesn’t have login access to the server)

13

SQL Server service paused

16

Incoming user does not have permissions to log into the target database, or target database is offline.

18

Change password required

23

Server in process of shutting down while user is trying to log in.

27

Cannot determine initial database for the session

38

(2008+) Specified database is unavailable (security, otherwise). Formerly state 16

40

(2008+) Users default database is unavailable. Formerly state 16.

58

Attempting SQL Authentication on server set for Windows Authentication only.

There are still a lot of missing states – leave a comment below if you have a reference to any others, and I’ll update this list. I prefer a Microsoft Reference.

Troubleshooting the login errors

In the 25 pages of comments on this Microsoft blog is buried some good information for researching / fixing the causes of some of these login errors. Here is a consolidation of the ones that I found useful from what the posters determined to be the cause for them:

State 8

  1. In 2005+, passwords are case sensitive. See http://support.microsoft.com/kb/907284

State 11/12

  1. Check msdn.microsoft.com/…/dd207004.aspx for allowing Windows Login to access SQL Server when the sysadmins are locked out.

State 11

  1. If the account is in the Local Admin group, drop and re-add the account to this group.
  2. Is the account in a group with DENY CONNECT?

State 16

  1. Ensure all schemas in the default database are owned by a valid login.
  2. Ensure database name is properly cased in the connection string.
  3. This error can also be caused by jobs.
  4. Could be caused by the database being in single user (with another connection using it) or restricted user modes.
  5. Ensure that there is a default database for the user.
  6. Using BCP? Don’t use brackets around the server name parameter (-S).
  7. Check for dropped publication / subscriber databases, but replication jobs for those databases still running.
  8. Using SharePoint? Setting it up to utilize Single-Sign-On (SSO)?
    1. While attempting to enable SSO for the first time, if you fail to configure the Microsoft Single Sign-on Service with the proper ‘Service Account’ you will get an error. SharePoint creates a job looking for the ‘SSO’ database, even though it failed to create the database.
    2. Steps to correct the issue:
      1. Open Windows Services
      2. Configure Microsoft Single Sign-on Service to use the proper account
      3. Open Central Administration >> Operations >> Manage settings for single sign-on
      4. Configure properties to use the same account used for Microsoft ‘Single Sign-on Service’
    3. The database (SSO) should be created successfully and the Error: 18456 should stop.

If you have any other causes for this error (any state), please add a comment below.

I hope that this consolidation is helpful to you!

We all have a bucket list – that list of things that we want to accomplish before one, well, kicks the bucket. Perhaps you want to ski the Swiss Alps. Learn a foreign language. Learn to play a musical instrument. Surf the Hawaiian swells. Visit far-away places. Get romanced by a Frenchman in Paris (which can be on your list… this isn’t for me!). You get the point… things that you want to do while you still can.

Do you remember that old Navy recruiting line “Join the Navy; See the world”. In what feels like a lifetime ago, when I was in the Navy, I did get to see many places over in the Mediterranean Sea region – places that are now on my bucket list to return to with my wife so that I can share that experience with her. I’ve climbed to the top of the Leaning Tower of Pisa (something that can’t be done anymore), walked around the Roman Coliseum, visited the Parthenon in Athens, and toured Israel. And there are other places that I’ve never been to, but want to go visit, such as St. Petersburg in Russia.

Last year, while on the Alaska SQL Cruise (it just so happens that going to Alaska was also on my bucket list), during one of the “office hours” sessions I shared with the other cruisers my Bucket List Item #1. So I am pleased (proud? gratified?) to let the world know that on Friday, I learned that after a lot of work, study, practice and, yes, head-banging, that I have passed the Microsoft Certified Master: SQL Server 2008 lab exam. Yes, this exclusive club has opened their doors to me, and I am indeed humbled to be a part of it. Which means that I am now a:

So what does being a MCM mean? Well, I think that Tom LaRock said it best here when he said: “I like to tell people that my MCM means I know a fair amount of detail for about 20% of the entire product line.” This is true to me – I know that there are areas of SQL where I’m not satisfied with the level of what I know. But this means that you shouldn’t be surprised to see me getting my learn on at events like SQL Saturdays, the PASS Summit, and other places. I still have more to learn, and with new versions of SQL Server still being developed, to keep abreast on. (Note that this is a 2008 certification, so next up will be the 2012 certification when that is available.)

There are many people that have influenced me in my decision to pursue this certification, and who have sustained me on this journey. Some are MCMs, some should be, and others don’t even touch a computer. Many of these on this list will be surprised to find themselves there, but fear not… I picked up something from you that motivated me. So I will start off with first thanking my wife and mother for their strong support and encouragement, as well as my sons, daughter-in-laws (it’s close enough Beth!) and grandchildren (yeah… I’m that old). And next, in only the order that they popped into my mind, I thank: Brent, Jeff, Steve, Tim, Mala, Robert, Illona, Grant, Gail, Andy and Allen. I also give heartfelt thanks to my company, Ntirety, who not only strongly encouraged this certification for me, but also paid for the exams. But I especially want to thank one individual who not only pushed and challenged me, but helped to make this a fun journey. So, to my co-worker Jason Brimhall: saying “Thanks for everything” is not enough – yet it says so much. (Incidentally, Jason has a pretty big announcement of his own.)

After all of this effort, there is one thing that I need to to before anything else… party!

It’s time for our monthly blogging party, commonly known as T-SQL Tuesday. This month, Bob Pusateri is providing the motivation by selecting the topic, and he would like to know how we came to love presenting. Being the inquisitive type, Bob would like to know things like what was the first time you gave a presentation in front of a group and really enjoyed it? Was it something that was required of you in school? Something you did in the workplace? Were you inspired by other SQL community members and thought “I think I can do that too”?

The Past

In the past, a few decades ago, I used to serve in the US Navy. In the division that I was in, we had to do training for the entire division. And this is when I first had to present. This was definitely something that I was required to do, and I never enjoyed it. Even though I knew the material extremely well, I was always very nervous and flailed about miserably. Talks that should have lasted an hour I sped through in around a half-hour. I think people there enjoyed watching me be so nervous.

The Present

A few years back, I started getting heavily involved in SQL Server Central, and writing articles for them. One article was born out of a curiosity of why table variables and temporary tables had massively different performance results. This article became extremely popular, and it was suggested to me that I create a presentation based upon this article. So, with a lot of prodding from SQL friends and mentors, I did. After getting it ready, I practiced it day after day, before finally presenting it to my local user group. With my past history, I was very nervous… maybe even terrified. And guess what… it went very smoothly. But what I enjoyed the most was when I saw the light go on in some of the attendees eyes. I knew that I had reached them, that they understood, and most importantly, that they had learned something. And I found out that I enjoy watching others learn. In the last couple of years, I’ve presented this topic twenty different times, including last year at PASS, and I have also created two other presentations that I present to user groups and SQL Saturdays.

The Future???

After discovering how much I’ve enjoyed presenting and helping / seeing others learn, I can see myself in a few years possibly going into a training mode. Will this happen? I’m not sure, but it is a possible path that I might take in the future. It sure would be a crazy irony to be doing something that previously I was so terrified of.

So that’s my presenting background… what’s yours?

Welcome to Day 28 of my “A Month of PowerShell” series – the last day. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

During this series, we’ve learned how to do a lot of things in SQL Server using PowerShell, primarily with the Database Engine and the Job Engine. While there are other areas that were not covered, times up and we have just today left.

So far through this series, when I’ve dealt with working with multiple instances of SQL Server, I’ve been working with instances that are all on one server. In my opinion, the real power of PowerShell is being able to loop through a list of servers to work on multiple servers all at once. So, today we’ll wrap up the Month of PowerShell series by learning how to use PowerShell to connect to a remote server.

What is Windows PowerShell Remoting?

What’s the big hubbub about with Windows PowerShell Remoting? After all, we already have COM / RPC (Report Procedure Calls) tools that can work with remote computers. The main issues with these tools that PowerShell resolves are consistency, security and scalability.

  • Consistency – the different tools have different ways for specifying parameters, or dealing with quoted strings. PowerShell uses one consistent method. Additionally, some tools behave differently when being run locally versus being run remotely. When you use Windows PowerShell Remoting, your session is connected to a session on the remote server, and the commands that you issue are sent to the remote server, executed locally, and then the results are returned to your system.
  • Security – these various tools all have to punch holes in firewalls, deals with intrusion prevention systems, and the like. Yes, a hole still needs to be put in place in the firewall for PowerShell… but not one hole for every RDP port.
  • Scalability – When you execute a tool against multiple servers, you have a loop that runs the command on each server one by one. In contrast, PowerShell allows you to execute the same command against multiple servers concurrently.

Enabling Windows PowerShell Remoting

If you are running SQL Server 2012 on Windows Server 2012, Windows PowerShell Remoting is already enabled. If you are running Windows Server 2008 or Windows Server 2008R2, then you need to enable this from an elevated PowerShell window (elevated… meaning it’s running as an Administrator) by typing the following:

Enable-PSRemoting -Force

The –Force parameter isn’t necessary, but it does simplify things… if you omit it, you will be prompted numerous times for the various actions necessary to implement Windows PowerShell Remoting. As a side-note, The Scripting Guy (Ed Wilson) at Microsoft has many blog posts about Enabling Windows PowerShell Remoting.

Connecting to a remote server that is on the same domain with the same credentials.

Once you have Remoting set up on the server, you can access it across the domain relatively simply. First, you run a command to retrieve your current credentials, which are stored in a variable (running this first command will prompt you for the password). Secondly, you create a PowerShell session to the server utilizing these credentials. Again, thanks to The Scripting Guy for showing how this is performed.

$cred = Get-Credential nwtraders\administrator
Enter-PSSession -ComputerName sql1 -Credential $cred

Connecting to a remote server that is not on the same domain

When trying to run a PowerShell script on a server not on the same domain, you will likely get the following error:

      ERROR:  The WinRM client cannot process the request. If the
      authentication scheme is different from Kerberos, or if the client
      computer is not joined to a domain, then HTTPS transport must be used
      or the destination machine must be added to the TrustedHosts
      configuration setting.

Perhaps the easiest way to correct this is to add the remote server to the local servers TrustedHosts configuration setting. (The following example will add all computers to the TrustedHosts configuration. See help About_Remote_Troubleshooting for how to add specific computers to an existing list, or allow all computers from specific domains.) This command needs to be run from an elevated PowerShell window.

Set-Item WSMan:\localhost\Client\TrustedHosts -Value * -Force

Running PowerShell commands remotely.

Now you can run PowerShell commands from the remote computer. In the following example, I have two virtual machines (SQL2005-Peer1 and SQL2005-Peer2). I will establish a remote connection to one (from my VM host), and ping the other, in the IPv4 format:

$cred = Get-Credential SQL2005-Host1\Administrator
Invoke-Command `
    -ComputerName SQL2005-Peer1 `
    -ScriptBlock {ping SQL2005-Peer2 -4} `
    -Credential $cred

Wrap-Up

Today we have seen how to establish a remote PowerShell connection between different computers, and how to run a command on the remote server. It should be simple by now to read a file of servers (and sql instances) that connects to these servers to perform additional actions.

For more help with PowerShell Remoting, try the help cmdlet for the following topics:

  • Help About_Remote
  • Help About_Remote_Requirements
  • Help About_Remote_Troubleshooting

As referenced throughout todays post, The Scripting Guy has many blog posts dealing with PowerShell in general and PowerShell remoting specifically. One of The Scripting Guy guest bloggers, Jason Hofferle, has a five part series on an introduction to PowerShell remoting that I encourage you to read for more into PowerShell remoting.

Welcome to Day 27 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

One of the tasks that we are frequently called upon to perform is to script out objects from one server and to apply them to another. Fortunately, PowerShell makes this easy.

Virtually every object in the SMO library has a Script method. Actually, they have two, one without parameters and one that accepts a ScriptOptions collection for how to control the scripting. Want to script out an entire database?  Navigate to the database, and call the Script() method. Need a procedure? Navigate to the procedure, and call the Script() method. Views? Functions? Tables? Indexes? They all have a Script() method that you can use.

What’s really neat is that with every version of SQL Server, the SMO libraries are updated to handle the new features. For instance, SQL Server 2012 introduced a ColumnStore index. The Script() method for the index can script it out.

Have you ever written a T-SQL script to script out all of the indexes in a database? It can get quite long (the incomplete version that I have is over 300 lines of code)… and are you certain that it handles everything correctly? Here’s my PowerShell script that will script out any index. Over half of this script is parameters, comments and code to verify parameters and to get it ready to send the output to a file. The actual scripting of all of the indexes is less than 40 lines… let’s see you do that in T-SQL. As an added bonus,  the next version of SQL Server will probably include clustered ColumnStore indexes… this script will generate those without any modifications, since the logic to generate the script is in the SMO library, not in PowerShell (the only thing that I might have to change is the method of ensuring that the SMO libraries are loaded… that keeps changing from version to version). Here is my PowerShell script:

#
#      Syntax: & GetIndexes.ps1 "SQLServer\Instance" "Database" "OutputPathAndFileName"
#
#   In order for PowerShell to run scripts, run Set-ExecutionPolicy RemoteSigned
#
#      Notes:
#      1. If the specified output file already exists, it is deleted without warning.
#      2. If the specified output path does not exist, it is created.
#---------------------------------------------------------------------------------------------------
#                                       PARAMETERS (Required)
#---------------------------------------------------------------------------------------------------
# $SQLInstance [string] - the name of the sql instance, in server\instance format.
# $Database [string]    - the name of the database to script out indexes for.
# $FileName [string]    - the location of the output file.
#---------------------------------------------------------------------------------------------------
#                                       PARAMETERS (Optional)
#---------------------------------------------------------------------------------------------------
# $Schema [string]          - the name of the schema to script out indexes for. If not specified, uses all.
# $Table [string]           - the name of the table to script out indexes for. If not specified, uses all.
# $IncludeDrop [boolean]    - set to true (default) to have the script generate drop statements and
#                             existance check prior to dropping.
# $AppendToFile [boolean]   - set to true (default) to append to the file. False will overwrite the file.
# $IncludeHeaders [boolean] - set to true (default) to include scripting comments.
# $NoFileGroup [boolean]    - set to true (default) to suppress filegroup placement options in generated script.
# $ToFileOnly [boolean]     - set to true (default) to send generated script to the file only.
#                             Setting to false will also send the generated script to the screen.
#---------------------------------------------------------------------------------------------------
#                                         MODIFICATION LOG
#---------------------------------------------------------------------------------------------------
#2012-08-31 WGS Initial Creation.
#---------------------------------------------------------------------------------------------------
 
param(
       [String] $SQLInstance,                # the SQL instance to generate a script from, in Server\Instance format.
       [String] $Database,                   # the database to generate the index scripts from.
       [String] $Filename,                   # the output file for the generated scripts.
       #Optional parameters follow
       [String] $SchemaName,                 # if specified, restricts generated scripts to tables in this schema.
       [String] $TableName,                  # if specified, restricts generated scripts to tables with this name.
       [boolean] $IncludeDrop = $True,       # set to $True to include drop statements
       [boolean] $AppendToFile = $True,      # set to $False to overwrite file
       [boolean] $IncludeHeaders = $True,    # set to $False to not have the scripting headers
       [boolean] $NoFileGroup = $True,       # set to $False to include filegroup placement in script
       [boolean] $ToFileOnly = $True         # set to $False to send results to screen also
)
cls
 
if ((!$SQLInstance) -or (!$Database) -or (!$Filename))
{
       Write-Warning 'Syntax: & "GetIndexes.ps1" "SQLServer\Instance" "DatabaseName" "Output Path and Filename" '
       Write-Warning 'Syntax (Optional Parameters): '
       Write-Warning '  "SchemaName"'
       Write-Warning '  "TableName"'
       Write-Warning '  "Include Drop Statements" (boolean)'
       Write-Warning '  "Append each object''s script to file" (boolean)'
       Write-Warning '  "Include Script Headers" (boolean)'
       Write-Warning '  "Don''t include filegroup placement settings" (boolean)'
       Write-Warning '  "Send script output to file only" (boolean)'
       Write-Warning '    "Pass in boolean values as $True or $False"'
       Write-Host ""
}
else
{
       # if the file already exists, delete it
       if (Test-Path $Filename) {Remove-Item $Filename}
       # get the path that this file is in
       $Path = Split-Path $Filename -Parent
       # if the path doesn't exist, create it
       if (!(Test-Path -path $Path)) {New-Item $Path -Type Directory}
       Write-Host "Output file: $Filename"
 
       #Load SMO, connect to server and database
       [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO')  | out-null 
       $server =  New-Object ('Microsoft.SqlServer.Management.SMO.Server') "$SQLInstance"
       $db = $server.Databases[$Database]
 
       # this object is used to script the actual index
       $Scriptr = New-Object ('Microsoft.SqlServer.Management.Smo.Scripter') ($server)
       $Scriptr.Options.ScriptDrops = $False
       $Scriptr.Options.Indexes = $True
       $Scriptr.Options.IncludeHeaders = $IncludeHeaders
       $Scriptr.Options.ClusteredIndexes = $True
       $Scriptr.Options.NonClusteredIndexes = $True
       $Scriptr.Options.XmlIndexes = $True
       $Scriptr.Options.AppendToFile = $AppendToFile
       $Scriptr.Options.FileName = $Filename
       $Scriptr.Options.ToFileOnly = $ToFileOnly
       $Scriptr.Options.NoFileGroup = $NoFileGroup
 
       foreach ($table in $db.Tables)
       {
              if (((!$TableName) -or ($TableName -eq $table.name)) -and
                     ((!$SchemaName) -or ($SchemaName -eq $table.Schema)))
              {
                     foreach ($index in $table.Indexes)
                     {
                           Write-Host "Database: $db; Table: $table; Index: $index"
 
                           if ($IncludeDrop -eq $True)
                           {
                                  $Scriptr.Options.IncludeIfNotExists = $True
                                  $Scriptr.Options.ScriptDrops = $True
                                  $Scriptr.Script($index)
                           }
                           $Scriptr.Options.IncludeIfNotExists = $False
                           $Scriptr.Options.ScriptDrops = $False
                           $Scriptr.Script($index)
                     }
              }
       }
}

 

Welcome to Day 26 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

If you utilize the sa login to perform admin chores (instead of selected windows accounts with windows authentication only), then you should periodically change the sa passwords. And these passwords should be different on each server. So, how do you want to do this: log in to each server, navigate to the server logins, and change the sa password… or run a PowerShell script to connect to each server and change it according to a list?

That’s what I thought. Let’s make a PowerShell script.

We’ll start off by creating a delimited text file. At your PowerShell prompt, enter:

notepad $ENV:TEMP\saPwdList.csv

In the file that opens up, on the first line put ServerName and Pwd, separated by the delimiter of your choice (ie. “|”). On subsequent lines, enter each Server\Instance name, the chosen delimiter, and the sa password to use on that server.

Finally, run the following script to change all of those passwords:

$Items = Import-CSV $ENV:TEMP\saPwdList.csv -Delimiter ","| #Use the chosen delimiter
ForEach ($Item in $Items)
{
  $Instance = $Item.ServerName
  $Pwd      = $Item.Pwd
 
  $Server = New-Object ("Microsoft.SqlServer.Management.Smo.Server") $Instance
  $Server.Logins.Item('sa').ChangePassword($Pwd)
  $Server.Logins.Item('sa').Alter()
}

This script could easily be modified to handle any login on multiple servers:

param(
  [string]$LoginName
  )
 
$Items = Import-CSV $ENV:TEMP\ServerList.csv -Delimiter | #Use the chosen delimiter
ForEach ($Item in $Items)
{
    $Instance = $Item.ServerName
    $Pwd      = $Item.Pwd
 
    $Server = New-Object ("Microsoft.SqlServer.Management.Smo.Server") $Instance
    $Login = $Server.Logins[$LoginName]
    if (($Login)) #Check to see if the login exists
    {
        $Login.ChangePassword($Pwd)
        $Login.Alter()
    }
}

This script checks to see if the login exists on the server, and then changes the login’s password to the value specified. Both the servers and passwords are read in from the file.

Welcome to Day 25 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

We all know that we need to run integrity checks on our databases as frequently as possible to ensure that the databases are not corrupt, and to detect the corruption at the earliest possible time when you might have other resources (for example, backups) to assist in recovering the data. In PowerShell, you can run all of the DBCC CHECK consistency checks, except for DBCC CHECKDB. Since DBCC CHECKDB consists of running other DBCC CHECK consistency checks, the capability is still there, you just have to call the various checks individually. The following table shows a cross-reference of the DBCC CHECK commands to their corresponding PowerShell SMO methods and in which collections you can find those methods:

DBCC CHECK Command SMO Collection SMO Method
DBCC CHECKALLOC Databases CheckAllocations / CheckAllocationsDataOnly
DBCC CHECKCATALOG CheckCatalog
DBCC CHECKCONSTRAINTS
DBCC CHECKDB Not implemented Not implemented
DBCC CHECKFILEGROUP Databases / FileGroups CheckFileGroup / CheckFileGroupDataOnly
DBCC CHECKIDENT Databases / Tables CheckIdentityValues
DBCC CHECKTABLE Databases / Tables CheckTables / CheckTablesDataOnly

By now, it should be pretty easy to know how to run these methods in PowerShell:

 

#Assign variables
$Instance   = "localhost\SQL2012"
$DBName     = "PoSh"
 
#Assign the SMO class to a variable
$SMO        = "Microsoft.SqlServer.Management.Smo"
 
# get the server
$Server = New-Object ("$SMO.Server") "$Instance"
 
# assign the database name to a variable
$MyDB = $Server.Databases[$DBName]
 
#Run the consistency check
$MyDB.CheckCatalog([Microsoft.SqlServer.Management.Smo.RepairType]::None)
$MyDB.CheckAllocations([Microsoft.SqlServer.Management.Smo.RepairType]::None)
$MyDB.CheckTables([Microsoft.SqlServer.Management.Smo.RepairType]::None)

 

Welcome to Day 24 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

One of the best practices that you should observe is to run regular integrity checks on our databases. This is best performed by running as a job, so let’s make a job that runs every Saturday at 3am to run DBCC CHECKDB on all of our databases.

Creating Jobs, Job Steps and Job Schedules

#Assign variables
$Instance   = "localhost\SQL2012"
#Assign the SMO class to a variable
$SMO        = "Microsoft.SqlServer.Management.Smo"
 
# get the server
$Server = New-Object ("$SMO.Server") "$Instance"
 
$Job = $Server.JobServer.Jobs["Database Consistency Checks"]
IF (!($Job))
{
    # Create the job
    $Job = New-Object ("$SMO.Agent.Job") ($Server.JobServer, 'Database Consistency Checks')
    $Job.OwnerLoginName = 'sa'
    $Job.Create()
}
 
$JobStep = $Job.JobSteps["Run DBCC on all databases"]
IF (!($JobStep))
{
    # Create the job step to run DBCC CHECKDB for all databases
    $JobStep = New-Object ("$SMO.Agent.JobStep") ($Job, "Run DBCC on all databases")
    $JobStep.Command = "EXECUTE sp_msForEachDB 'DBCC CHECKDB([?])'"
    $JobStep.OnSuccessAction = [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]::QuitWithSuccess
    $JobStep.OnFailAction = [Microsoft.SqlServer.Management.Smo.Agent.StepCompletionAction]::QuitWithFailure
    $JobStep.Create()
}
 
$JobSchedule = $Job.JobSchedules["Saturday's at 3am"]
IF (!($JobSchedule))
{
    # Create the job schedule to run on Saturdays at 3am
    $JobSchedule = New-Object ("$SMO.Agent.JobSchedule") ($Job, "Saturday's at 3am")
    $JobSchedule.FrequencyTypes = [Microsoft.SqlServer.Management.Smo.Agent.FrequencyTypes]::Weekly
    $JobSchedule.FrequencyInterval = [Microsoft.SqlServer.Management.Smo.Agent.WeekDays]::Saturday
    $JobSchedule.FrequencyRecurrenceFactor = 1 # Run every 1 week
    $TimeSpanStart = New-Object -TypeName TimeSpan -ArgumentList 3,0,0
    $JobSchedule.ActiveStartTimeOfDay = $TimeSpanStart
    $JobSchedule.Create()
}

Yesterday we created operators for the alerts. If you want an operator for the job, you go about creating one in the same manner as we did yesterday, and in the job class set the appropriate operator properties prior to creating the job.

Welcome to Day 23 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

Today, we’re going to look into the JobServer collections. The JobServer collections manage all aspects of SQL Agent – jobs, job schedules, alerts, operators, etc. We can see all of the collections that the JobServer manages with:

Get-ChildItem SQLSERVER:SQL\localhost\SQL2012\JobServer

Checking for Alerts

One of the best practices to have in place is to have alerts on system errors 823, 824, 825 and 829. A quick PowerShell script checks see if these are in place:

Get-ChildItem SQLSERVER:SQL\localhost\SQL2012\JobServer\Alerts | `
    Select-Object Name, MessageID | Where-Object MessageID -in 823,824,825,829 | `
    Format-Table –Autosize

What!??? You don’t have these in place? Okay, let’s make them.

Creating Operators and Alerts by Error Number

We will create an operator, and set its NetSendAddress to localhost. Next we will create an alert for each of these conditions, and finally add the operator to the alert.

#Assign variables
$Instance   = "localhost\SQL2012"
 
#Assign the SMO class to a variable
$SMO        = "Microsoft.SqlServer.Management.Smo"
 
# get the server
$Server = New-Object ("$SMO.Server") "$Instance"
# Get the Job Server
$JobServer = $Server.JobServer
 
# First, ensure that a local operator exists.
$Operator = $JobServer.Operators["LocalHost"]
IF (!($Operator)) #Check if the operator exists
{
    # Operator does not exist, so create it
    $Operator = New-Object ("$SMO.Agent.Operator") ($JobServer, "LocalHost")
    $Operator.NetSendAddress = "LocalHost"
    $Operator.Create()
}
 
ForEach ($MessageID in (823,824,825,829))
{
    $AlertName = "Alert: Message Id: $MessageID"
    # Get the alert if it already exists
    # You could search for the alert by name, however you can only have one alert per messageid.
    # So, search by the message id.
    $Alert = $JobServer.Alerts | Where-Object Severity -EQ $Severity
 
    IF (!($Alert)) #Check to see if the alert already exists
    {
        # The alert does not exist, so add it
        $Alert = New-Object ("$SMO.Agent.Alert") ($JobServer, $AlertName)
        $Alert.MessageID = $MessageID
        $Alert.Create()
 
        # Add the operator to the alert
        $Alert.AddNotification("LocalHost", [Microsoft.SqlServer.Management.Smo.Agent.NotifyMethods]::NetSend)
    }
}

As you can see, as long as you are treating each event the same, you can include all of the MessageIds in the ForEach loop to process them all. One item to point out: you can only have one alert per MessageId, so this script is searching for an existing alert based upon the MessageId to be created. If one already exists, it just skips adding that alert.

Creating Operators and Alerts by Severity

As you might imagine, creating alerts by a severity is just as easy… just change the MessageId to Severity:

#Assign variables
$Instance   = "localhost\SQL2012"
 
#Assign the SMO class to a variable
$SMO        = "Microsoft.SqlServer.Management.Smo"
 
# get the server
$Server = New-Object ("$SMO.Server") "$Instance"
# Get the Job Server
$JobServer = $Server.JobServer
 
#First, ensure that a local operator exists.
$Operator = $JobServer.Operators["LocalHost"]
IF (!($Operator)) #Check if the operator exists
{
    #Operator does not exist, so create it
    $Operator = New-Object ("$SMO.Agent.Operator") ($JobServer, "LocalHost")
    $Operator.NetSendAddress = "LocalHost"
    $Operator.Create()
}
 
ForEach ($Severity in (22,23))
{
    $AlertName = "Alert: Severity: $Severity"
    # Get the alert if it already exists
    # You could search for the alert by name, however you can only have one alert per Severity.
    # So, search by the Severity.
    $Alert = $JobServer.Alerts | Where-Object Severity -EQ $Severity
 
    IF (!($Alert)) #Check to see if the alert already exists
    {
        # The alert does not exist, so add it
        $Alert = New-Object ("$SMO.Agent.Alert") ($JobServer, $AlertName)
        $Alert.Severity = $Severity
        $Alert.Create()
 
        #Add the operator to the alert
        $Alert.AddNotification("LocalHost", [Microsoft.SqlServer.Management.Smo.Agent.NotifyMethods]::NetSend)
    }
}

Again, you can only have one alert per Severity, so the code checks for existing alerts by the severity before adding a new one.

Welcome to Day 22 of my “A Month of PowerShell” series. This series will use the series landing page on this blog at http://blog.waynesheffield.com/wayne/a-month-of-powershell/. Please refer to this page to see all of the posts in this series, and to quickly go to them.

Yesterday we backed up all of our databases. However, what good is a backup if it can’t be restored? Now we’ll restore all those backups into a test database. This script will restore the most recent full backup for each database (into a database named “Verify”), moving all files to a separate directory to avoid trying to restore on top of the existing files. It will then restore the most recent differential backup, and then (if the database is not in the simple recovery model) all transaction log backups since the later of the full / differential backups. This script does assume that if the latest differential backup is more recent that the latest full, that it belongs to that full backup, and the script uses the paths where the databases were backed up to in yesterday’s backup script.

#clear variables
$Server     = $null
 
#Assign variables
$Instance   = "localhost\SQL2008"
$VerifyDB   = 'Verify'
 
#Assign the SMO class to a variable
$SMO        = "Microsoft.SqlServer.Management.Smo"
 
# get the server
$Server = New-Object ("$SMO.Server") "$Instance"
 
# Restoring to a new database changes the $Server.Databases collection, raising an error.
# So, create an empty array to hold the current list of databases and loop through that.
$Databases = @()
# Add each database to this array
ForEach ($db in $Server.Databases)
{
    IF ($db.Status -eq 'Normal' -and $db.IsMirroringEnabled -eq $false -and `
        $db.Name -ne 'tempdb' -and $db.Name -ne $VerifyDB)
    {
        $Databases = $Databases + $db
    }
}
 
ForEach ($db in $Databases)
{
    # Get the location to store the data files at for this verify database
    $Verify = $Server.Settings.DefaultFile
    IF (!($Verify.EndsWith('\'))) {$Verify = $Verify + '\'}
    $Verify = $Verify + "$($VerifyDB)_DBFiles\"
 
    # Ensure that the directory exists
    IF (!(Test-Path -Path $Verify)) {New-Item $Verify -Type Directory}
 
    $dir = $Server.Settings.BackupDirectory + "\" + $db.Name + "\Full\"
    # Get the most recent full backup
    $Full = $Null
    IF (Test-Path -Path $dir)
    {
        $Full = Get-ChildItem $dir |`
            Sort-Object LastWriteTime -Descending |`
            Select-Object -First 1 |`
            Select-Object Name, FullName, LastWriteTime
    }
 
    IF ($Full)
    {
        # Restore the full backup
        $BackupDevice = New-Object ("$SMO.BackupDeviceItem") ($Full.FullName, 'File')
        $Restore = New-Object ("$SMO.Restore")
        $Restore.Checksum = $True
        $Restore.Devices.Add($BackupDevice)
        $Restore.Database = "$VerifyDB"
        $Restore.NoRecovery = $True
        $Restore.ReplaceDatabase = $True
 
        #Relocate the files so that they don't stomp on the existing db files
        ForEach ($FileGroup in $DB.FileGroups)
        {
            ForEach ($File in $FileGroup.Files)
            {
            $FileName = Split-Path $File.FileName -Leaf #Get just the filename
            $FileName = $Verify + $FileName
            $NewFileLoc = New-Object ("$SMO.RelocateFile") ($File.Name, $FileName)
            $Restore.RelocateFiles.Add($NewFileLoc) | Out-Null
            }
        }
 
        ForEach ($File in $DB.LogFiles)
        {
            $FileName = Split-Path $File.FileName -Leaf #Get just the filename
            $FileName = $Verify + $FileName
            $NewFileLoc = New-Object ("$SMO.RelocateFile") ($File.Name, $FileName)
            $Restore.RelocateFiles.Add($NewFileLoc) | Out-Null
        }
        $RelocateFiles = $Restore.RelocateFiles
        Write-Host "Restoring Full backup file $($Full.FullName) to $VerifyDB database"
        $Restore.SqlRestore($Server)
 
        $LastWriteTime = $Full.LastWriteTime
 
        # Get the most recent differential backup
        $Diff = $null
        $TestPath = $Server.Settings.BackupDirectory + "\" + $db.Name + "\Diff\"
        IF (Test-Path -Path $TestPath)
        {
            $Diff = Get-ChildItem $TestPath |`
                Sort-Object LastWriteTime -Descending |`
                Select-Object -First 1 |`
                Select-Object Name, FullName, LastWriteTime
 
            # if the latest diff was before the latest full, don't do anything with it.
            IF ($Diff.LastWriteTime -LT $Full.LastWriteTime) {$Diff = $null}
            ELSE {$LastWriteTime = $Diff.LastWriteTime}
        }
 
        # If there is one,
        IF ($Diff)
        {
            $BackupDevice = New-Object ("$SMO.BackupDeviceItem") ($Diff.FullName, 'File')
            $Restore = New-Object ("$SMO.Restore")
            $Restore.Checksum = $True
            $Restore.Database = "$VerifyDB"
            $Restore.Devices.Add($BackupDevice)
            $Restore.NoRecovery = $True
            ForEach ($RelocateFile in $RelocateFiles) {$Restore.RelocateFiles.Add($RelocateFile) | Out-Null}
            Write-Host "Restoring Differential backup file $($Diff.FullName) to $VerifyDB database"
            $Restore.SqlRestore($Server)
        }
 
        IF ($DB.RecoveryModel -ne "Simple")
        {
            # Restore all transaction logs written since the last full/diff backup
            $TestPath = $Server.Settings.BackupDirectory + "\" + $db.Name + "\Log\"
            IF (Test-Path -Path $TestPath)
            {
                ForEach ($File in Get-ChildItem $TestPath |`
                            Where {$_.LastWriteTime -ge $LastWriteTime} |`
                            Sort-Object LastWriteTime |`
                            Select-Object Name, FullName )
                {
                    $BackupDevice = New-Object ("$SMO.BackupDeviceItem") ($File.FullName, 'File')
                    $Restore = New-Object ("$SMO.Restore")
                    $Restore.Checksum = $True
                    $Restore.Database = "$VerifyDB"
                    $Restore.Devices.Add($BackupDevice)
                    $Restore.NoRecovery = $True
                    ForEach ($RelocateFile in $RelocateFiles) {$Restore.RelocateFiles.Add($RelocateFile) | Out-Null}
                    Write-Host "Restoring Transaction Log backup file $($File.FullName) to $VerifyDB database"
                    $Restore.SqlRestore($Server)
                }
            }
        }
        # Bring the database online
        $Restore.NoRecovery = $False
        Write-Host "Bringing $VerifyDB database online"
        $Restore.SqlRestore($Server)
        Write-Host "Test Restore of the [$($db.name)] database passed"
 
        # This would be a good time to do a checkdb!
    }
}

If you are utilizing SMO from SQL Server 2012, a new method has been added: Restore-SqlDatabase. This cmdlet has parameters to control all aspects of the restore.