Thursday, August 20, 2009

Finding Event ID's

A while back, I had the need to look for a particular EventID across all our SQL servers. The script I came up to do the work is here.

This was one of my earliest efforts so its a bit primitive, but works just fine. The main working portion of the script is a straighforward query of the Win32_NTLogEvent wmi class.

The fun part was converting the wmi time into something more usable/readable. I did this in a function that breaks the wmi time into discrete chunks & then strings them all back together in my preferred format. Additionally it modify's the new date/time to account for daylight savings.
Function Convert-WMITime
{
    param([string]$WMITime)

    #Break Date & Time into discreet elements
    $ds = [string]$WMITime.substring(21,4)
    $yr = [string]$WMITime.Substring(0,4)
    $mo = [string]$WMITime.substring(4,2)
    $dy = [string]$WMITime.substring(6,2)
    $tm = [string]$WMITime.substring(8,6)
    $hr = [string]$tm.Substring(0,2)
    $min = [string]$tm.substring(2,2)
    $sec = [string]$tm.substring(4,2)
    
    #Create string in desired format
    $s = "$mo/$dy/$yr " + $hr + ":" + $min + ":" + $sec
    
    #cast result as DateTime type
    $result = [DateTime]$s
    
    #Account for DST
    if ($cp = $ds.Contains("-240"))
    {
        $result = $result.AddHours(-1)
    }
    
    return $result
}

Friday, August 14, 2009

Get Files, Sizes & Percent Free by DB

This week I've been working on our space needs for the upcoming budget year. As part of that effort I spent a few minutes writing a script to return infrmation on database files; including file group, logical name, physical name, allocated size, used size & percent free space for each file. The code is here.

In the next few weeks I'm planning to do a write up on the entire process I used to generate my space requests, it involves several pieces, inluding a process to populate database size (this is not mine).

Monday, August 3, 2009

Generating log file shrinks

We had a situation where we needed to shrink all log files for all databases on a couple of drives preparatory to moving all the files to a new LUN which would then be mounted as the same drive letter. This was being done to correct improperly aligned partitions on the drives causing performance issues.

When disk partitions are not properly aligned there is a significant performance degradation (30-40%) on the disk, see Chad Miller's blog post on this for more detail. Additionally, correcting this will reduce throughput to the SAN array which can benefit overall performance of the array.

I don't normally recommend (and would typically strongly oppose) shrinking files, but in this case we had a limited time frame to make this move & shrinking these files meant 150GBs less data to move which was a make or break for the timeline we were allowed to complete the work in.

Since these were all log files I choose to shrink them all to 1MB for maximum benefit. For the sake of paranoia (I like to double/triple/quadruple check scripts prior to running in Production) I had it generate the statements to run & then ran them manually. To customize for your drives you would just change the T-SQL defined in the @sql variable. The script is here.

Monday, July 27, 2009

Simple-Talk article

The folks at Simple-Talk were kind enough to give me an opportunity to write an article for them about Powershell, Reg-Gate SQLCompare & Snapshots.
The result is here, please let me know what you think by voting. Thanks!

Friday, July 17, 2009

Granting use of Graphical Execution Plan in SQL2005

I recently had the need to allow non-sysadmin, non-db_owner users to use the graphcal execution plan in SQL2005 on a query using tempdb (temp table). Assuming this worked just like "SET SHOWPLAN_* ON" I granted the user Showplan on the DB in question.

To test, I created a user with access to only the DB in question & tested "SET SHOWPLAN_TEXT ON", "SET SHOWPLAN_XML ON" & "SET SHOWPLAN_ALL ON" with no issues. However, when I tried to use the graphical execution plan ("Include Actual Execution Plan" option on the query) it failed with the below error...
Msg 262, Level 14, State 4, Line 29
SHOWPLAN permission denied in database 'tempdb'.

So, for a "normal" user who uses the graphical execution plan it seems my only option is to explicitly add them to tempdb & grant Showplan permissions (this would entail adding them to model as well so their perms persist on startup when tempdb is recreated). The other option would be to grant Showplan perms to guest on model/tempdb which given the official & observed (for example) vulnerabilities associated with SHOWPLAN seems a little crazy.

I don't normally knock MS around too much, but I really dislike both these options. Basically I'm being forced to choose between a gaping security hole (the guest scenario), a smaller security hole + a (to me) bad practice of granting a user access to a system DB that they really shouldn't need OR denying the perms to a user who's actually trying to do right.

Wednesday, July 15, 2009

Using Powershell to gather NIC info

In the course of troubleshooting a performance issue (in part caused by replication latency), we noticed that the NIC on our SQL server had been changed from Full Duplex to Half Duplex. Needless to say as soon as we switched back to Full Duplex our latency dropped back to what we'd come to expect. This was renforced by the behavior we observed when observing replication latency where it would be delivering transactions in bursts which lines up with the Half Duplex arcitecture of one-way communication.

As as outgrowth of this I thought it would be prudent to check the rest of our environment to make sure we didn't have any more mis-configured NICs. The majority of our Prod SQL Instances are clustered & have 2 NICs, one for the Heartbeat network & one for the Public Network. Per MS best practices for Clustered SQL the Heartbeat NIC is set to "100 Mb Full" & the Public NIC is set to "1000 Mb Full" so differentiating bewteen the NICs is important.

My first thought was to use the Win32_NetworkAdapter class which has properties for speed & duplex, unfortunantly looking at msdn it appears the properties haven't been implemented yet (this was verified in my testing). After viewing an excellent post on the topic by Hugo Peeters, I decided his method would work with a few small changes. My main gripe about this is the fact that MS forces us to (yet again) go to the registry for information that should be in WMI. Since the registry path changes by vendor (and in some cases model number) you'll need to modify your version of the script to account for the NIC vendors/models in your environment.

The script is here, I was not able to get it to show in a usable format on the page so we'll try this instead.

Tuesday, June 16, 2009

Fun with Powershell & Red-Gate SQL Compare

Last week I was asked to help out on a project where a group of users that didn't have access to Production needed to be able to perform schema compares of Dev <-> Prod DBs. We currently use Red-Gate SQL Compare which is a great product, BTW so a solution was designed around it. The solution decided on was to use SQL Compare to generate Snapshots of Prod nightly that could then be compared against without the need for access to Prod (Note: a SQL Compare Snapshot is a flat file which contains all information needed to perform a complete schema compare, but no actual data).

We wanted the process that generated the Snapshots to automatically run nightly against all online, non system DBs on all Prod SQL Instances, also excluding any DBs reserved for DBA usage only. The process should also be able to be manually run for an individual DB, one Instance or a group of SQL Instances. My final solution involved 2 powershell scripts & a scheduled job. One script actually creates the Snapshots & one script runs 4 iterations of the create script in parallel (more on this later). There are a few considerations to take into considerations when using this method, SQL Compare 8.0 is required to create Snapshots from the command line & to compare against the Snapshots. There is a free utility from Red-Gate that allows for command line creation of Snapshots (they are also usable by SQL Compare 7.0 and higher) that can be found here "http://www.red-gate.com/downloads/labs/RedGate.SQLSnapper.zip" in our case we needed some of the features offered by Professional Edition so we went that route.

The automated steps looks like this...

  1. Scheduled job "Create RG Snaps" runs...
  2. PWS Script "Run-RGSnaps.ps1" which make 4 parallel calls to...
  3. PWS Script "Create-RGSnaps.ps1" which save the snaps to... \\server\FOLDER\RedGate_Snaps\ in...
  4. SERVER$INSTANCE.DB naming format

There are 3 methods by which the PWS Script "Create-RGSnaps.ps1" can be run manually.

  • One Database
  • Open Powershell
  • Navigate to scripts folder, issue a "sl C:\\Script" (no quotes) command
  • To run against yourdb on SERVER1\INSTANCE1, for example, issue a "./Create-RGSnaps.ps1 SERVER1\INSTANCE1 yourdb" (no quotes) command. Output will look like the below...

SQL Compare Command Line V8.0.0.965==============================================================================

Copyright c Red Gate Software Ltd 1999-2009 Serial Number: 012-345-678901-2345 Creating database snapshot file '\\SERVER\FOLDER\RedGate_Snaps\SERVER1$INSTANCE1.yourdb .snp' from database 'yourdb ' on 'server1\instance1'...

OK

  • One Instance
  • Open Powershell
  • Navigate to scripts folder, issue a "sl C:\\Script" (no quotes) command
  • To run against SERVER1\INSTANCE1, for example, issue a "./Create-RGSnaps.ps1 SERVER1\INSTANCE1" (no quotes) command. Output will look the same as above...

  • Multiple Instances
  • Open powershell
  • Navigate to scripts folder, issue a "sl C:\\Script" (no quotes) command
  • To run against SERVER1\INSTANCE1 & SERVER2\INSTANCE2, for example, create a file named Instances.txt (or a name of your choice) in "C:\\Script" that contains entries for SERVER1\INSTANCE1 & SERVER2\INSTANCE2 (one line per instance)
  • Issue a "Get-Content Instances.txt ForEach {./Create-RGSnaps $_}" (no quotes) command. Output will look the same as above...

The script that does the work "Create-RGSnaps.ps1" takes a required parameter of Instance & an optional parameter of Database. If a value for Database is passed the script calls the command line interface for SQL Compare with no further processing. If no value for Database is passed the script parses all DBs on the Instance that are active/accessable & calls SQL Compare for each DB. The code is below...

  1. param($Instance = $(throw 'Instance is required.'), $Database)   
  2.   
  3. $ErrorActionPreference = "SilentlyContinue"  
  4.   
  5. $Path = "\\SERVER\FOLDER\RedGate_Snaps\"  
  6. $Query = "SELECT name FROM dbo.sysdatabases (NOLOCK) WHERE dbid > 4 AND name NOT IN ('distribution','DBADatabase') AND has_dbaccess(name) = 1"  
  7.  
  8. #########################  
  9. function Get-SQLData  
  10. {  
  11.     param($Instance = $(throw 'Instance is required.'), $DBName = $(throw 'Database is required.'), $Query = $(throw 'Query is required.'))  
  12.       
  13.     Write-Verbose "Get-SqlData Instance:$Instance Database:$DBName Query:$Query"  
  14.       
  15.     $ConnString = "Server=$Instance;Database=$DBName;Integrated Security=SSPI;"  
  16.     $DataAdapter = New-Object "System.Data.SqlClient.SqlDataAdapter" ($Query,$ConnString)  
  17.     $DataTable = New-Object "System.Data.DataTable"  
  18.     [void]$DataAdapter.fill($DataTable)  
  19.     $DataTable  
  20. } #Get-SQLData  
  21.  
  22. #########################  
  23. function Get-Databases  
  24. {  
  25.     param($Instance = $(throw 'Instance is required.'))  
  26.  
  27.     Get-SqlData $Instance 'master' $Query  
  28.  
  29. } #Get-Databases  
  30.  
  31. #########################  
  32. function Invoke-SqlCompare  
  33. {  
  34.     param($Instance = $(throw 'Instance is required.'), $Database = $(throw 'Database is required.'), $Path = $(throw 'Path for Snap files required.'))  
  35.  
  36.     $InstanceStrng = ($Instance -replace('\\','`$')).ToUpper()  
  37.     $Command = '&"C:\Program Files (x86)\Red Gate\SQL Compare 8\sqlcompare.exe" /Server1:' + $Instance + ' /database1:"' + $Database + '" /makesnapshot:"' + $Path + $InstanceStrng + '.' + $Database + '.snp" /force'   
  38.   
  39.     Invoke-Expression $Command  
  40.   
  41. #Invoke-SqlCompare   
  42.   
  43. #########################   
  44. #Main   
  45.   
  46. If ($Database)   
  47.     {Invoke-SQLCompare $Instance $Database $Path}   
  48. Else   
  49.     {Get-Databases $Instance | ForEach {Invoke-SQLCompare $Instance $_.name $Path}}   

The script that runs the Create script in parallel "Run-RGSnaps.ps1" was written by my boss, his blog is here http://chadwickmiller.spaces.live.com/default.aspx & was only modified by me to meet my needs. Since in Powershell 1.0 there's no way to spawn background processes the Run script launches new PWS threads unitil it reaches the number defined as it's max & then checks every 15 seconds for less than the max threads (excluding itself) & if less than the defined max, spawns a new thread. I take no credit for his excellent work, just want to include for completeness. The script takes no parameters, it has a built-in function to get a list of Prod Instances we have defined in a DB & pulls back the ones marked active & passes them to the create script to run in parallel.

I tested running serially and with 4 & 8 iterations in parallel with nothing else running on the host server. The best balance between performance & resource utilization was at 4 parallel iterations (your results will probably vary). Serially resource usage was minimal we saw 8% CPU, 447 DBs across 26 Instances completed in 56 minutes average; at 4 parallel iteration we saw 33% CPU & completion of the same DBs/Instances took 15 minutes average; at 4 parallel iteration we saw 85% CPU & completion of the same DBs/Instances took 12 minutes average. the server we're running this on has 4 single core procs, my suspicion is that you can run as many threads as you have processor cores. Code is below...

  1. $ScriptRoot = "C:\<yourdir>\Script\"  
  2. $SQLPSXServer = "SERVER3\INSTANCE3"  
  3. $SQLPSXDatabase = "DB3"  
  4. $Query = "SELECT <column> FROM dbo.<table> (NOLOCK) WHERE IsEnabled = 'true'"  
  5. $MaxThread = 4  
  6. $ServerList = New-Object System.Collections.ArrayList  
  7.  
  8. #########################  
  9. function Get-SQLData  
  10. {  
  11.     param($Instance = $(throw 'Instance is required.'), $DBName = $(throw 'Database is required.'), $Query = $(throw 'Query is required.'))  
  12.       
  13.     Write-Verbose "Get-SqlData Instance:$Instance Database:$DBName Query:$Query"  
  14.       
  15.     $ConnString = "Server=$Instance;Database=$DBName;Integrated Security=SSPI;"  
  16.     $DataAdapter = New-Object "System.Data.SqlClient.SqlDataAdapter" ($Query,$ConnString)  
  17.     $DataTable = New-Object "System.Data.DataTable"  
  18.     [void]$DataAdapter.fill($DataTable)  
  19.     $DataTable  
  20. } #Get-SQLData  
  21.  
  22. ########################  
  23. function Get-SqlList  
  24. {     
  25.     param ($SQLPSXServer = $(throw 'SQLPSX Instance is required.'), $SQLPSXDatabase = $(throw 'SQLPSX Database is required.'), $Query = $(throw 'Query is required.'))  
  26.  
  27.     Get-SqlData $SQLPSXServer $SQLPSXDatabase $Query | ForEach {$ServerList.Add($_.Server) > $null}  
  28. }# Get-SqlList  
  29.  
  30. ########################  
  31. function LaunchThread  
  32. {  
  33.     param($SQLServer)  
  34.  
  35.     $StartInfo = new-object System.Diagnostics.ProcessStartInfo  
  36.     $StartInfo.FileName = "$pshome\powershell.exe"  
  37.     $StartCommand = $ScriptRoot + "Create-RGSnaps.ps1 " + $SQLServer  
  38.     $StartInfo.Arguments = " -NoProfile -Command " + $StartCommand  
  39.     $StartInfo.WorkingDirectory = $ScriptRoot  
  40.     $StartInfo.LoadUserProfile = $true  
  41.     $StartInfo.UseShellExecute = $true  
  42.     [System.Diagnostics.Process]::Start($StartInfo) > $null  
  43. }# LaunchThread  
  44.  
  45. ########################  
  46. #Main  
  47.  
  48. Get-SqlList $SQLPSXServer $SQLPSXDatabase $Query  
  49.  
  50. While ($ServerList.Count -gt 0)  
  51. {  
  52.     If ($(Get-Process | where {$_.ProcessName -eq 'Powershell' -and $_.Id -ne $PID} | Measure-Object).count -lt $MaxThread)  
  53.     {     
  54.         $Server = $ServerList[0]  
  55.         LaunchThread $Server  
  56.         $ServerList.Remove("$server")   
  57.     }   
  58.     Else   
  59.     {      
  60.         [System.Threading.Thread]::Sleep(15000)   
  61.     }   
  62. }