Monday, July 27, 2009
Simple-Talk article
Wednesday, July 15, 2009
Using Powershell to gather NIC info
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
The automated steps looks like this...
- Scheduled job "Create RG Snaps" runs...
- PWS Script "Run-RGSnaps.ps1" which make 4 parallel calls to...
- PWS Script "Create-RGSnaps.ps1" which save the snaps to... \\server\FOLDER\RedGate_Snaps\ in...
- 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...
- param($Instance = $(throw 'Instance is required.'), $Database)
- $ErrorActionPreference = "SilentlyContinue"
- $Path = "\\SERVER\FOLDER\RedGate_Snaps\"
- $Query = "SELECT name FROM dbo.sysdatabases (NOLOCK) WHERE dbid > 4 AND name NOT IN ('distribution','DBADatabase') AND has_dbaccess(name) = 1"
- #########################
- function Get-SQLData
- {
- param($Instance = $(throw 'Instance is required.'), $DBName = $(throw 'Database is required.'), $Query = $(throw 'Query is required.'))
- Write-Verbose "Get-SqlData Instance:$Instance Database:$DBName Query:$Query"
- $ConnString = "Server=$Instance;Database=$DBName;Integrated Security=SSPI;"
- $DataAdapter = New-Object "System.Data.SqlClient.SqlDataAdapter" ($Query,$ConnString)
- $DataTable = New-Object "System.Data.DataTable"
- [void]$DataAdapter.fill($DataTable)
- $DataTable
- } #Get-SQLData
- #########################
- function Get-Databases
- {
- param($Instance = $(throw 'Instance is required.'))
- Get-SqlData $Instance 'master' $Query
- } #Get-Databases
- #########################
- function Invoke-SqlCompare
- {
- param($Instance = $(throw 'Instance is required.'), $Database = $(throw 'Database is required.'), $Path = $(throw 'Path for Snap files required.'))
- $InstanceStrng = ($Instance -replace('\\','`$')).ToUpper()
- $Command = '&"C:\Program Files (x86)\Red Gate\SQL Compare 8\sqlcompare.exe" /Server1:' + $Instance + ' /database1:"' + $Database + '" /makesnapshot:"' + $Path + $InstanceStrng + '.' + $Database + '.snp" /force'
- Invoke-Expression $Command
- } #Invoke-SqlCompare
- #########################
- #Main
- If ($Database)
- {Invoke-SQLCompare $Instance $Database $Path}
- Else
- {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...
- $ScriptRoot = "C:\<yourdir>\Script\"
- $SQLPSXServer = "SERVER3\INSTANCE3"
- $SQLPSXDatabase = "DB3"
- $Query = "SELECT <column> FROM dbo.<table> (NOLOCK) WHERE IsEnabled = 'true'"
- $MaxThread = 4
- $ServerList = New-Object System.Collections.ArrayList
- #########################
- function Get-SQLData
- {
- param($Instance = $(throw 'Instance is required.'), $DBName = $(throw 'Database is required.'), $Query = $(throw 'Query is required.'))
- Write-Verbose "Get-SqlData Instance:$Instance Database:$DBName Query:$Query"
- $ConnString = "Server=$Instance;Database=$DBName;Integrated Security=SSPI;"
- $DataAdapter = New-Object "System.Data.SqlClient.SqlDataAdapter" ($Query,$ConnString)
- $DataTable = New-Object "System.Data.DataTable"
- [void]$DataAdapter.fill($DataTable)
- $DataTable
- } #Get-SQLData
- ########################
- function Get-SqlList
- {
- param ($SQLPSXServer = $(throw 'SQLPSX Instance is required.'), $SQLPSXDatabase = $(throw 'SQLPSX Database is required.'), $Query = $(throw 'Query is required.'))
- Get-SqlData $SQLPSXServer $SQLPSXDatabase $Query | ForEach {$ServerList.Add($_.Server) > $null}
- }# Get-SqlList
- ########################
- function LaunchThread
- {
- param($SQLServer)
- $StartInfo = new-object System.Diagnostics.ProcessStartInfo
- $StartInfo.FileName = "$pshome\powershell.exe"
- $StartCommand = $ScriptRoot + "Create-RGSnaps.ps1 " + $SQLServer
- $StartInfo.Arguments = " -NoProfile -Command " + $StartCommand
- $StartInfo.WorkingDirectory = $ScriptRoot
- $StartInfo.LoadUserProfile = $true
- $StartInfo.UseShellExecute = $true
- [System.Diagnostics.Process]::Start($StartInfo) > $null
- }# LaunchThread
- ########################
- #Main
- Get-SqlList $SQLPSXServer $SQLPSXDatabase $Query
- While ($ServerList.Count -gt 0)
- {
- If ($(Get-Process | where {$_.ProcessName -eq 'Powershell' -and $_.Id -ne $PID} | Measure-Object).count -lt $MaxThread)
- {
- $Server = $ServerList[0]
- LaunchThread $Server
- $ServerList.Remove("$server")
- }
- Else
- {
- [System.Threading.Thread]::Sleep(15000)
- }
- }
Friday, May 22, 2009
Checking for a Service in Powershell
Here's what I came up with, just pass the service name & machine(s) you want (either singly on in a *.txt file) & it will spit out a list of all with the machine, friendly service name (if installed) or whatever you choose for those where it's not installed (I chose blank).
The cmd would look like...
./Check-ServiceInstalled SERVICENAME "C:\Lists\Servers.txt"
OR
./Check-ServiceInstalled SERVICENAME MACHINENAME
The Code (using Registry)...
$SvcName = $Args[0]
$ServerName = $Args[1]
$ErrorActionPreference = "SilentlyContinue"
$RegPath = "SYSTEM\\CurrentControlSet\\Services\\" + $SvcName
if ($ServerName -like "*.txt*")
{$Servers = Get-Content $ServerName}
else
{$Servers = $ServerName}
ForEach ($Computer in $Servers)
{
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$Computer)
$RegKey = $Reg.OpenSubKey($RegPath)
if ($RegKey -eq $null)
{new-object psobject |
add-member -pass NoteProperty Computer $Computer.ToUpper() |
add-member -pass NoteProperty Installed ""
}
else
{new-object psobject |
add-member -pass NoteProperty Computer $Computer.ToUpper() |
add-member -pass NoteProperty Installed $RegKey.GetValue('DisplayName')
}
}
The Code (using gwmi & service)...
$SvcName = $Args[0]
$ServerName = $Args[1]
if ($ServerName -like "*.txt*")
{$Servers = Get-Content $ServerName}
else
{$Servers = $ServerName}
ForEach ($Computer in $Servers)
{
$Svc = Get-WmiObject win32_service -ComputerName $Computer | where {$_.name -eq $SvcName} -ErrorAction SilentlyContinue
if ($Svc -eq $null)
{new-object psobject |
add-member -pass NoteProperty Computer $Computer.ToUpper() |
add-member -pass NoteProperty Installed ""
}
else
{new-object psobject |
add-member -pass NoteProperty Computer $Computer.ToUpper() |
add-member -pass NoteProperty Installed $Svc.DisplayName
}
}
Tuesday, March 10, 2009
Proactive File Deletion
- USE [msdb]
- GO
- EXEC msdb.dbo.sp_add_operator @name=N'DBA',
- @enabled=1,
- @weekday_pager_start_time=90000,
- @weekday_pager_end_time=180000,
- @saturday_pager_start_time=90000,
- @saturday_pager_end_time=180000,
- @sunday_pager_start_time=90000,
- @sunday_pager_end_time=180000,
- @pager_days=0,
- @email_address=N'dba@yourcomp.com',
- @category_name=N'[Uncategorized]'
- GO
- BEGIN TRANSACTION
- DECLARE @ReturnCode INT
- SELECT @ReturnCode = 0
- DECLARE @jobId BINARY(16)
- EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'00 Old File Cleanup',
- @enabled=1,
- @notify_level_eventlog=0,
- @notify_level_email=2,
- @notify_level_netsend=0,
- @notify_level_page=0,
- @delete_level=0,
- @description=N'The job will delete all *.bak & *.sqb files older than 3 days that do not have “goldcopy” or “donotdelete” in the name & do not have “do_not_delete” in the path. The job will also delete all RG log files that are older than 7 days in the “C:\Documents and Settings\All Users\Application Data\Red Gate\SQL Backup\Log\” folder.',
- @category_name=N'[Uncategorized (Local)]',
- @owner_login_name=N'sa',
- @notify_email_operator_name=N'DBA', @job_id = @jobId OUTPUT
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- /****** Object: Step [Backup Files] Script Date: 02/25/2009 15:38:25 ******/
- EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Backup Files',
- @step_id=1,
- @cmdexec_success_code=0,
- @on_success_action=3,
- @on_success_step_id=0,
- @on_fail_action=2,
- @on_fail_step_id=0,
- @retry_attempts=0,
- @retry_interval=0,
- @os_run_priority=0, @subsystem=N'CmdExec',
- @command=N'powershell "& C:\WINDOWS\Script\del-oldbackups-single.ps1 3"',
- @flags=0
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- /****** Object: Step [RG Logs] Script Date: 02/25/2009 15:38:25 ******/
- EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'RG Logs',
- @step_id=2,
- @cmdexec_success_code=0,
- @on_success_action=1,
- @on_success_step_id=0,
- @on_fail_action=2,
- @on_fail_step_id=0,
- @retry_attempts=0,
- @retry_interval=0,
- @os_run_priority=0, @subsystem=N'CmdExec',
- @command=N'powershell "& C:\WINDOWS\Script\del-rglogs-single.ps1 7"',
- @flags=0
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'Sat-Weekly',
- @enabled=1,
- @freq_type=8,
- @freq_interval=64,
- @freq_subday_type=1,
- @freq_subday_interval=0,
- @freq_relative_interval=0,
- @freq_recurrence_factor=1,
- @active_start_date=20090225,
- @active_end_date=99991231,
- @active_start_time=30000,
- @active_end_time=235959
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
- IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
- COMMIT TRANSACTION
- GOTO EndSave
- QuitWithRollback:
- IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
- EndSave:
- GO
- $factor = $Args[0]
- $disks = Get-WmiObject Win32_Volume -filter "DriveType = 3"
- $cutoff = (Get-Date).AddDays(-$factor)
- ForEach ($drv in $disks)
- {
- $path = $drv.Name + "sqlbu\"
- if ((Test-Path $path) -eq "True")
- {
- $count = (Get-ChildItem $path -include *.sqb,*.bak -Exclude *goldcopy*,*donotdelete* -recurse |
- ?{$_.LastWriteTime -lt $cutoff -and !$_.PSIsContainer -and $_.PSPath -notlike "*do_not_delete*"}).Count
- if ($count -eq $null) {$count = 0}
- Get-ChildItem $path -include *.sqb,*.bak -Exclude *goldcopy*,*donotdelete* -recurse |
- ?{$_.LastWriteTime -lt $cutoff -and !$_.PSIsContainer -and $_.PSPath -notlike "*do_not_delete*"} |
- Remove-Item
- Write-Host "There were" $count "backup files deleted from" $path
- }
- }
- $factor = $Args[0]
- $cutoff = (Get-Date).AddDays(-$factor)
- $path = "C:\Documents and Settings\All Users\Application Data\Red Gate\SQL Backup\Log\"
- if ((Test-Path $path) -eq "True")
- {
- $count = (Get-ChildItem $path -include *.log -recurse |
- ?{$_.LastWriteTime -lt $cutoff -and !$_.PSIsContainer}).Count
- if ($count -eq $null) {$count = 0}
- Get-ChildItem $path -include *.log -recurse |
- ?{$_.LastWriteTime -lt $cutoff -and !$_.PSIsContainer} |
- Remove-Item
- Write-Host "There were" $count "files deleted"
- }
- else {Write-Host "There are no files to be deleted"}
