Wednesday, April 24, 2013

SQL Power Doc Update - Version 1.0.1.0

A quick note to let you know that SQL Power Doc version 1.0.1.0 is now live. This release fixes issues reported since version 1.0, adds 11 new assessment checks, and improves error handling in multiple places. The nitty-gritty details of what's changed are available in the changelog.

You can grab the latest version from the CodePlex project page. If you find it useful please do consider giving it a rating and\or review. And of course if you run into any problems post a description on the discussions page and I'll be happy to help troubleshoot.

Thursday, April 11, 2013

Discover, Diagnose, and Document ALL Your SQL Servers On Your Coffee Break

We all know that we should document our SQL Servers, right? RIGHT?

So let's say you had some time on your hands and decided that today's the day you're going to get it done. You might open SSMS, connect to a server in Object explorer, start clicking around, and copying properties into an Excel spreadsheet. That gets old quick, so you write TSQL to grab information...until you realize that you're going deep down the rabbit hole accounting for all the nuances and new features between versions of SQL Server. If you're adventurous you might crack open your IDE of choice and dig into SMO, but that's another rabbit hole of a different kind, too. Frustrated, you give in and download a trial version of a commercial documentation tool. It works pretty well, except it's taking forever to manually go through every database on every server. A couple hours later you've got a handful of databases documented...but what about server configurations or  details about the Windows OS they're installed on? How do you compare server configurations against each other? Or spot potential configuration problems? And what about those servers you just inherited - or don't even know are out there yet?

Wow, you think to yourself as you head home, this could be someone's full time job just keeping track of all this stuff!

There Is a Better Way: Introducing SQL Power Doc

Starting today, there is a better way. I'm proud to officially announce the release of SQL Power Doc.

Imagine combining the functionality of what Quest's Discovery Wizard for SQL Server, your favorite commercial SQL documentation tool, msinfo32.exe, and Brent Ozar Unlimited's sp_Blitz™ script does into a single standalone tool that you can run against all of your SQL servers at once. That's SQL Power Doc.

Here's a look at what SQL Power Doc does:

Discover

Find SQL Server Services on your network by:

  • Active Directory DNS
  • Subnet Scan
  • Computer Name
Document

Collect comprehensive details about all your SQL Server instances and their underlying Windows OS, including:

  • Service Details For All Installed SQL Sever Services
  • Database Engine
    • Configuration
    • Security
    • Server Objects
    • Databases
      • Configuration
      • Database Objects
      • Service Broker
      • Storage
      • Security
  • SQL Agent
    • Configuration
    • Jobs
    • Alerts
    • Operators
  • Windows OS
    • Machine Information
    • OS Information
    • Software 
Diagnose

SQL Power Doc performs over 100 checks to find hidden problems and performance bottlenecks on your SQL Servers before they turn into major headaches.

SQL Power Doc does all of this using Windows PowerShell and Microsoft Excel. Never touched PowerShell before? Don't worry, I've written a How To Guide for PowerShell Beginners to walk you through every step of the way.

While you're on your coffee break you can put SQL Power Doc to work for you to generate comprehensive, top to bottom documentation of your entire SQL Server environment. This documentation is useful for:

  • Baselines - know what your SQL Server environment looked like last week, last month, etc.
  • Security Audits
  • Licensing Audits
  • Provide a complete look at how your servers are configured without having to grant access
  • Troubleshooting
  • Comparing servers and databases
  • Creating a runbook that you can give to your operations team
  • Planning upgrades - see what hidden features are in use on an instance

One More Thing...

SQL Power Doc isn't limited to just SQL Server - you can also use it to collect an inventory of all the Windows machines on your network. If your IT department is looking for a way to document their Windows machines do them a favor and tell them about SQL Power Doc.

Ready To Get Started?

Now that you know there's a tool that can do the work for you there's no excuse not to document your servers. Head on over to the SQL Power Doc CodePlex site, check out the Requirements and How To Guide, then grab the code to discover, document, and diagnose your SQL Servers today!

Tuesday, April 9, 2013

T-SQL Tuesday #41 - Presenting Opens Doors

This blog post is part of T-SQL Tuesday, a monthly SQL blog party with a rotating host and common topic. This month marks #41, hosted by Bob Pusateri (@SQLBob), and the theme is how I came to love presenting.

Presenting Changed My Life

My story starts in early 2006; I had moved to Orlando to work for a small E-commerce company as a Sr. Production DBA, but I was the DBA's equivalent of the ghost who codes. While I had a nice resume there really wasn't much associated with my name and SQL server that I could show off to people. When I had a problem I searched for the answer on Google. I didn't follow anyone's blog or know any of the big names in the SQL Server world. I had no idea was PASS was all about or that the concept of a user group even existed. As I write this I laugh because I'm reminded that I even searched for logins to SQLServerCentral on BugMeNot because I wanted to remain anonymous.

A coworker of mine at the time told me that a friend of his was looking for people to present to a local group of database people. It sounded like an interesting way to meet other people around town who worked with SQL Server so I accepted and agreed to talk about the new XML features in SQL 2005. A month later I made the one hour trek across town to present to 15 people and as much as I'd like to remember that it was an overwhelming success it was anything but. I rambled. I had 3 slides and spent the rest of the time showing code in SSMS. I went at least 20 minutes over time. I know I lost a few people along the way and probably gave a few wrong answers to questions. By all accounts it was a terrible presentation.

But I survived.

The next day I received an email from the user group leader - one Andy Warren (@sqlandy) - with constructive suggestions on how to improve and an invitation to give the presentation again at this newfangled concept he called SQLSaturday. I accepted, took the suggestions to heart, and felt like a million bucks after I gave a much improved presentation to a full room at SQLSaturday #1. It was a great feeling, so I did it again...and again...and started to find new things to present about. I began attending OPASS and reading blogs (and started this blog, too). I kept at it, and in 2009 I was selected to present a session at the PASS Summit, an honor I had every year until 2012 when I attended the Summit in a different capacity - as a PASS Board member and Director of the SQLSaturday portfolio.

It's been quite a journey, to say the least! Along the way I've learned a metric ton (that's the technical term) about SQL Server and I've met incredible people, many of whom I have become good friends with. What a difference between the 2006 me and who I've become now - in large part because of presenting.

You Never Know Who You Will Influence

It wasn't obvious to me at first but I've come to the realize that one of the most rewarding things about presenting is that you have the opportunity to open doors for people, if ever so slightly. I doubt Kevin Kline had any idea who I was when I sat in a session he gave in Tampa many years ago, or who that guy named Jonathan Kehayias (@SQLPoolBoy) a few seats over from me was. I certainly had no idea who Jonathan was when I gave my XML presentation at SQLSaturday, and it's fun to think now that I was the one who first introduced him to XML-DML in SQL Server.

My point is that you never know if the next great blogger, user group leader, or eventual PASS President is someone you encouraged to give their presentation or someone who attended your session and walked away motivated to do more because of it. It doesn't matter if you're just getting started or you're a veteran speaker - by presenting you're helping yourself and others at the same time. I'll call that a win any day!

Your Turn

If you've ever considered giving a presentation but something's holding you back, consider this a challenge to get over it and submit. There are plenty of PASS chapters who will be happy to put you on their schedule and there's no shortage of SQLSaturdays these days either. Both are great places to start and there are plenty of people in the SQL community who can help you prepare in advance.

I promise, it won't be too painful - but be careful, because you might just walk away wanting to do it again!

Tuesday, February 12, 2013

T-SQL Tuesday #39 - Use PowerShell To Copy A Database To Another Instance

This blog post is part of T-SQL Tuesday, a monthly SQL blog party with a rotating host and common topic. This month marks #39, hosted by Wayne Sheffield (@DBAWayne), and the theme is "Can You Shell What The PoSh Is Cooking?"

For many years I made learning PowerShell a goal and for many years the best I could do was use the PowerShell console as a replacement for the command prompt. Like most DBAs I figured out how get by with T-SQL and multi-server queries...and then I started a side project using PowerShell that didn't involve SQL Server (yet) and it was time to sink or swim. I'm happy to say a year and a half later I swam, and now PowerShell has become such an essential core skill for me that I'm kicking myself for not having learned it sooner!

On that note, Wayne has started an excellent month-long series on PowerShell that even seasoned script writers should check out. Open a console, start at the series landing page, and get scripting!

For today's post I'm going to share a script I wrote which uses Server Management Objects (SMO) to copy a database from one instance to another via backup and restore. This works against all versions and editions of SQL Server starting with SQL 2000.

If you've got Management Studio installed then you've already got SMO. If you want to run this script on a machine that doesn't have SMO you can download it as part of the SQL 2012 Feature Pack.

The script is below. Copy and paste the contents into your favorite editor and save the file as Copy-SqlServerDatabase.ps1. If copy and paste isn't your thing you can download the script - but make sure to unblock the file in Windows Explorer before extracting or you'll run into problems when you try to run it.

Finally, make sure your Execution Policy is set to RemoteSigned before trying to run this script (and I'm hoping Wayne blogs about that in his series!).

 

<#
.
SYNOPSIS
Copies a SQL Server DB from a source instance to a target instance via backup & restore, optionally renaming the DB at the target

.
DESCRIPTION
This script uses SQL Server Management Objects (SMO) to copy a SQL Server database from one instance to another, optionally renaming the database at the target instance.

SQL Server Native backup & restore operations are used to write a backup to a file and restore it to the target instance.

Once restored to the target instance, the database owner is changed to SA and the compatibility level adjusted to match the target instance's model database.

NOTE: This script does NOT delete the backup file that it creates. If you no longer need the file you must delete it manually.

.
PARAMETER SourceInstance
The source instance containing the database you want to copy

.
PARAMETER SourceDatabase
The name of the database that you want to copy

.
PARAMETER TargetInstance
The target instance that you want to copy the database to

.
PARAMETER TargetDatabase
The new name for the database at the target SQL Server instance. If omitted, the database will retain the same name as on the source instance

.
PARAMETER BackupDirectoryPath
The intermediate location where the backup file will be written. This should be accessible by both the source and target instances

.
PARAMETER Username
Username to use when connecting to the source and target instances with a SQL login. If omitted, Windows Authentication is used.

.
PARAMETER Password
Password to use when connecting to the source and target instances with a SQL login. If omitted, Windows Authentication is used.

.
PARAMETER Force
Force overwriting the database at the target server if a database with the same name already exists

.
EXAMPLE
.\
Copy-SqlServerDatabase.ps1 -SourceInstance Server1 -SourceDatabase AdventureWorks -TargetInstance Server2 -BackupDirectoryPath \\Server3\Backups

.
EXAMPLE
.\
Copy-SqlServerDatabase.ps1 -SourceInstance Server1 -SourceDatabase AdventureWorks -TargetInstance Server2 -TargetDatabase AdventureWerkz -BackupDirectoryPath \\Server3\Backups -Force

.
EXAMPLE
.\
Copy-SqlServerDatabase.ps1 -SourceInstance Server1 -SourceDatabase AdventureWorks -TargetInstance Server2 -TargetDatabase AdventureWerkz -BackupDirectoryPath \\Server3\Backups -Username sa -Password N0tBl@nk!

.
EXAMPLE
.\
Copy-SqlServerDatabase.ps1 -SourceInstance Server1,3143 -SourceDatabase AdventureWorks -TargetInstance Server2,4133 -TargetDatabase AdventureWerkz -BackupDirectoryPath \\Server3\Backups
#>

[
cmdletBinding(DefaultParametersetName='WindowsAuthentication')]
param(
[
Parameter(Mandatory=$true, ParameterSetName = 'WindowsAuthentication')]
[
Parameter(Mandatory=$true, ParameterSetName = 'SQLAuthentication')]
[
ValidateNotNullOrEmpty()]
[
alias('Source', 'Src')]
[
System.String]
$SourceInstance
,
[
Parameter(Mandatory=$true)]
[
ValidateNotNullOrEmpty()]
[
alias('SourceDb', 'SrcDb', 'Database')]
[
System.String]
$SourceDatabase
,
[
Parameter(Mandatory=$true)]
[
ValidateNotNullOrEmpty()]
[
alias('Target', 'Tgt')]
[
System.String]
$TargetInstance
,
[
Parameter(Mandatory=$false)]
[
ValidateNotNullOrEmpty()]
[
alias('TargetDb', 'TgtDb', 'RenameAs')]
[
System.String]
$TargetDatabase = $SourceDatabase
,
[
Parameter(Mandatory=$true)]
[
ValidateNotNullOrEmpty()]
[
alias('Path')]
[
System.String]
$BackupDirectoryPath
,
[
Parameter(Mandatory=$true, ParameterSetName = 'SQLAuthentication')]
[
ValidateNotNull()]
[
System.String]
$Username
,
[
Parameter(Mandatory=$true, ParameterSetName = 'SQLAuthentication')]
[
ValidateNotNull()]
[
System.String]
$Password
,
[
Parameter(Mandatory=$false)]
[
Switch]
$Force = $false
)

# Load SMO assembly, and if we're running SQL 2008 DLLs or higher load the SMOExtended and SQLWMIManagement libraries
# SMO Major Versions
# 9 : SQL 2005
# 10: SQL 2008 & 2008 R2
# 11: SQL 2012
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | ForEach-Object {
$SmoMajorVersion = $_.GetName().Version.Major
if ($SmoMajorVersion -ge 10) {
[
System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended') | Out-Null
[
System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SQLWMIManagement') | Out-Null
}
}


######################
# CONSTANTS
######################

# SQL Versions
# See
http://social.technet.microsoft.com/wiki/contents/articles/783.sql-server-versions.aspx for version timeline
# Also see
http://support.microsoft.com/kb/321185
# Also see
http://sqlserverbuilds.blogspot.com/

New-Object -TypeName System.Version -ArgumentList '8.0.0.0' | New-Variable -Name SQLServer2000 -Scope Script -Option Constant
New-Object -TypeName System.Version -ArgumentList '9.0.0.0' | New-Variable -Name SQLServer2005 -Scope Script -Option Constant
New-Object -TypeName System.Version -ArgumentList '10.0.0.0' | New-Variable -Name SQLServer2008 -Scope Script -Option Constant
New-Object -TypeName System.Version -ArgumentList '10.50.0.0' | New-Variable -Name SQLServer2008R2 -Scope Script -Option Constant
New-Object -TypeName System.Version -ArgumentList '11.0.0.0' | New-Variable -Name SQLServer2012 -Scope Script -Option Constant


######################
# FUNCTIONS
######################

function Get-SqlConnection {
[
CmdletBinding()]
[
OutputType([System.Data.SqlClient.SqlConnection])]
param(
[
Parameter(Mandatory=$false)]
[
ValidateNotNullOrEmpty()]
[
System.String]
$Instance = '(local)'
,
[
Parameter(Mandatory=$false)]
[
ValidateNotNullOrEmpty()]
[
System.String]
$Database = 'master'
,
[
Parameter(Mandatory=$true, ParameterSetName = 'SQLAuthentication')]
[
ValidateNotNull()]
[
System.String]
$Username
,
[
Parameter(Mandatory=$true, ParameterSetName = 'SQLAuthentication')]
[
ValidateNotNull()]
[
System.String]
$Password
,
[
Parameter(Mandatory=$true, ParameterSetName = 'WindowsAuthentication')]
[
ValidateNotNull()]
[
alias('WindowsAuth','IntegratedAuth')]
[
switch]
$WindowsAuthentication
,
[
Parameter(Mandatory=$false)]
[
System.String]
$FailoverPartner = $null
,
[
Parameter(Mandatory=$false)]
[
System.String]
$ApplicationName = 'Windows PowerShell' # $MyInvocation.ScriptName
)
try {

# ConnectionStringBuilder docs: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder.aspx
# http://www.connectionstrings.com/Articles/Show/all-sql-server-connection-string-keywords

$SQLConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection
$SQLConnectionBuilder = New-Object -TypeName system.Data.SqlClient.SqlConnectionStringBuilder

$SQLConnectionBuilder.psBase.DataSource = $Instance
$SQLConnectionBuilder.psBase.InitialCatalog = $Database

if ($PSCmdlet.ParameterSetName -eq 'SQLAuthentication') {
$SQLConnectionBuilder.psBase.IntegratedSecurity = $false
$SQLConnectionBuilder.psBase.UserID = $Username
$SQLConnectionBuilder.psBase.Password = $Password
}
else {
$SQLConnectionBuilder.psBase.IntegratedSecurity = $true
}

$SQLConnectionBuilder.psBase.FailoverPartner = $FailoverPartner
$SQLConnectionBuilder.psBase.ApplicationName = $ApplicationName

$SQLConnection.ConnectionString = $SQLConnectionBuilder.ConnectionString

Write-Output $SQLConnection

}
catch {
Throw
}
}

######################
# VARIABLES
######################
$SourceConnection = $null
$SourceServer = $null
$TargetConnection = $null
$TargetServer = $null
$TargetDbExists = $false
$SaLogin = 'sa'

$FileDeviceType = [Microsoft.SqlServer.Management.Smo.DeviceType]::File

$TargetDataPath = [String]::Empty
$TargetLogPath = [String]::Empty
$PhysicalName = [String]::Empty

$Backup = $null
$Restore = $null
$RelocateFile = $null


######################
# BEGIN SCRIPT
######################

try {

# Open a connection to the target server and check if the target database already exists
if ($PSCmdlet.ParameterSetName -eq 'SQLAuthentication') {
$TargetConnection = Get-SqlConnection -Instance $TargetInstance -Username $Username -Password $Password
}
else {
$TargetConnection = Get-SqlConnection -Instance $TargetInstance -WindowsAuthentication
}
$TargetServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $TargetConnection
$TargetServer.ConnectionContext.Connect()

$TargetServer.Databases | Where-Object { $_.Name -ieq $TargetDatabase } | ForEach-Object {
$TargetDbExists = $true
}

# If the target database already exists and -Force was not specified throw an error
if ($TargetDbExists -and -not $Force) {
throw "Database '$TargetDatabase' exists on target instance $TargetInstance; Rerun script and specify -Force to overwrite the existing database"
}


# Open a connection to the source server and kick off a full, copy-only backup to the backup path
if ($PSCmdlet.ParameterSetName -eq 'SQLAuthentication') {
$SourceConnection = Get-SqlConnection -Instance $SourceInstance -Username $Username -Password $Password
}
else {
$SourceConnection = Get-SqlConnection -Instance $SourceInstance -WindowsAuthentication
}
$SourceServer = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $SourceConnection
$SourceServer.ConnectionContext.Connect()

# Setup the backup
$Backup = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Backup
$Backup.Action = 'Database'
$Backup.BackupSetName = "$SourceDatabase FULL Backup"
$Backup.BackupSetDescription = 'FULL backup of $SourceDatabase for RemitPlus upgrade'
$Backup.Database = $SourceDatabase
$Backup.Incremental = $false

# COPYONLY supported by SQL 2005+ and SMO 2008
if ((($SourceServer.Information.Version).CompareTo($SQLServer2005) -ge 0) -and ($SmoMajorVersion -ge 10)) {
$Backup.CopyOnly = $true
}

# Compression supported by:
# - SQL 2008 enterprise edition
# - SQL 2008 R2 standard edition and higher
if (
$( $SourceServer.Information.Version).CompareTo($SQLServer2008R2) -ge 0 -or
(
$( $SourceServer.Information.Version).CompareTo($SQLServer2008) -ge 0 -and
$SourceServer.Information.Edition -ilike '*enterprise*'
)
)
{
$Backup.CompressionOption = 'on'
}

# Build backup filename
# Note: I'm not accounting for invalid characters here. I'm assuming this won't be an issue for this iteration!
$BackupFileName = [String]::Join('_', @($SourceServer.Name.Replace('\','_'), $SourceDatabase, 'FULL', [System.DateTime]::Now.ToString('yyyy_MM_dd_HH_mm')))
$BackupFileName = [System.IO.Path]::ChangeExtension($BackupFileName, 'bak')
$BackupPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($BackupDirectoryPath, $BackupFileName))

$BackupDevice = New-Object -TypeName Microsoft.SqlServer.Management.Smo.BackupDeviceItem -ArgumentList $BackupPath, $FileDeviceType
$Backup.Devices.Add($BackupDevice)

# Do the backup
$Backup.SqlBackup($SourceServer)

# Cleanup
$Backup.Devices.Remove($BackupDevice) | Out-Null
$Backup = $null
$SourceServer.ConnectionContext.Disconnect()



# Get the path to the data and log file directories on the target server

# Get the default data and log file path on the target server
$TargetDataPath = if (($TargetServer.Settings.DefaultFile).Length -gt 0) { $TargetServer.Settings.DefaultFile } else { $TargetServer.Information.MasterDBPath }
$TargetLogPath = if (($TargetServer.Settings.DefaultLog).Length -gt 0) { $TargetServer.Settings.DefaultLog } else { $TargetServer.Information.MasterDBPath }


# Setup the restore
$Restore = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Restore
$Restore.Action = 'Database'
$Restore.Database = $TargetDatabase
$Restore.NoRecovery = $false
$Restore.Devices.Add($BackupDevice)

# Iterate through files in backup and set up a new physical path for each
$Restore.ReadFileList($TargetServer).Rows | ForEach-Object {
$RelocateFile = New-Object -TypeName Microsoft.SqlServer.Management.Smo.RelocateFile
$RelocateFile.LogicalFileName = $_.LogicalName

$PhysicalName = [System.IO.Path]::GetFileName($_.PhysicalName)
#$PhysicalName = [System.IO.Path]::GetFileNameWithoutExtension($_.PhysicalName) + '_TEST' + [System.IO.Path]::GetExtension($_.PhysicalName)

# Set new physical path depending on file type
if ($_.Type -ieq 'L') {
$RelocateFile.PhysicalFileName = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($TargetLogPath, $PhysicalName))
}
else {
$RelocateFile.PhysicalFileName = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($TargetDataPath, $PhysicalName))
}

$Restore.RelocateFiles.Add($RelocateFile) | Out-Null
}

# Do the restore
$Restore.SqlRestore($TargetServer)

# Cleanup
$Restore.Devices.Remove($BackupDevice) | Out-Null
$Restore = $null


# Get the SA login for the target instance (fallback is 'sa')
$TargetServer.Logins | Where-Object { [System.BitConverter]::ToString($_.Sid) -eq [System.BitConverter]::ToString(0x01) } | ForEach-Object { $SaLogin = $_.Name }

# Have SMO update the list of databases
$TargetServer.Databases.Refresh()

# Change DB Owner to SA and compatibility level to match target server's model DB compatibility level
$TargetServer.Databases.Item($TargetDatabase) | ForEach-Object {
$_.SetOwner($SaLogin)
$_.CompatibilityLevel = $TargetServer.Databases['model'].CompatibilityLevel
$_.Alter()
}

$TargetServer.ConnectionContext.Disconnect()
}
catch {
# Get the lowest level error and throw it
$ThisException = $_.Exception
while ($ThisException.InnerException) {
$ThisException = $ThisException.InnerException
}
throw $ThisException
}
finally {
# Close any open connections
If ($TargetServer.ConnectionContext.IsOpen) { $TargetServer.ConnectionContext.Disconnect() }
If ($SourceServer.ConnectionContext.IsOpen) { $SourceServer.ConnectionContext.Disconnect() }
}

Remove-Variable -Name SourceConnection, SourceServer, TargetConnection, TargetServer, FileDeviceType, BackupFileName, `
BackupPath, TargetDataPath, TargetLogPath, Backup, Restore, RelocateFile, PhysicalName, SmoMajorVersion, TargetDbExists, SaLogin