The over-reliance on a familiar tool is best described with the quote, “if all you have is hammer, everything looks like nail” and for database professionals this means using or sometimes misusing T-SQL. Whenever database administrators are presented with a scripting problem they instinctively reach for good-old-familiar T-SQL. And why not? In many cases T-SQL provides an ideal solution to SQL Server administration scripting problems, however there are certain scenarios where another tool, Powershell provides a more elegant solution.
One such problem is scripting of database objects, T-SQL simply does not handle the complexities of objects script creation very well. In an attempt to use a familiar tool many people have written T-SQL scripts that trudge through system tables to produce an object creation script. The problem with such scripts is that they tend to be ugly, lengthy pieces of code that easily break. Experienced DBAs know querying system table directly is poor practice, yet they do it anyways in order to use their hammer T-SQL tool. There’s a better way, SQL Server Management Objects (SMO) has taken care of many of these issues for us and all we need to do is write a little Powershell code.
As an example let’s look at an object script problem from Brent Ozar’s post, “How to REALLY Compress Your SQL Server Backups.” He describes a database size compression solution that does the following:
- Script out all of the non-clustered indexes in the database
- Save those definitions to a table (or a stored procedure)
- Create a stored proc that will loop through those index definitions and recreate them later
- Drop the indexes
Although Brent does not provide us with a script because in his words it’s a little duct-tape-y, he does link a to a couple of T-SQL based solutions:
Scripts to Drop and ReCreate Indexes in SQL Server 2005
SQL Server 2005: Script all Indexes
The authors of the T-SQL scripts use a classic SQL administration scripting technique of building code by interrogating various system tables. The scripts are not without issue. A quick glance at the comment section of the second script reveals a dozen corrections were made to the original post after various users ran into things not accounted for. The abundance of corrections further illustrates T-SQL does not provide a good method to script out database objects. Now, let’s look at a more complete Powershell and SMO based solution.
In order to store the index create and drop statements, we’ll need a table. I’m going to create the table in the same database:
CREATE TABLE [dbo].[IndexDdl](
[DdlType] [varchar](10) NOT NULL,
[IndexScript] [varchar](4000) NOT NULL
)
SQL Server 2008 provides a Powershell interface accessible from SQL Server Management Studio. Start Powershell from the Tables folder in SQL Server Management Studio:
Script out the non-clustered index drop statements and save them to the IndexDdl table:
$scriptingOptions = New-Object Microsoft.SqlServer.Management.Smo.ScriptingOptions $scriptingOptions.DdlHeaderOnly = $true $scriptingOptions.ScriptDrops = $true dir | foreach {$_.indexes} | where {$_.IsClustered -eq $false -and $_.IsXmlIndex -eq $false} | `
foreach {$_.Script($scriptingOptions)} | foreach {invoke-sqlcmd -Query "INSERT dbo.IndexDdl VALUES('Drop','$_')"}
Using the SQL Server Powershell provider, we are able to get all indexes that are non-clustered and not an XML index and then call the SMO script method. Generating drop statements require a little extra setup in that we first need to create a scripting options object and set the DdlHeaderOnly and ScriptDrops properties to true. The resulting script is then inserted into the IndexDdl table. Note: Because the current connection is used the call to invoke-sqlcmd does not specify a server or database name.
Script out the create index statements:
dir | foreach {$_.indexes} | where {$_.IsClustered -eq $false -and $_.IsXmlIndex -eq $false} | `
foreach {$_.Script()} | foreach {invoke-sqlcmd -Query "INSERT dbo.IndexDdl VALUES('Create','$_')"}
The create statement can use the default behavior of the script method, no extra setup required.
To execute the drop statements:
$drops = Invoke-sqlcmd -Query "SELECT IndexScript FROM dbo.IndexDdl WHERE DdlType = 'Drop'" $drops | foreach {Invoke-sqlcmd -Query "$_.IndexScript"}
And finally if needed, to execute the create statements:
$creates = Invoke-sqlcmd -Query "SELECT IndexScript FROM dbo.IndexDdl WHERE DdlType = 'Create'" $creates | foreach {Invoke-sqlcmd -Query "$($_.IndexScript)"}
Observations
Although the command are run interactively the Powershell scripts can easily be incorporated into a SQL Server Agent Powershell step job. The solution works on down level versions of SQL Server as long as SMO 10 (SQL 2008) and sqlps (SQL Server Powershell) are available. The Powershell and SMO based solution is much less code, more easily understandable and since a standard SMO Script method is used, less prone to breakage. Not every SQL Server administration problem is a nail. Put down your T-SQL hammer and pick up Powershell!