Category Archives: .Net

Move MSSQL Databases using Powershell with Detach\Attach

This is a script which can assist you in moving or copying a number of databases to a new server. We recently had to move move a lot of databases to new hardware and this came in very handy. The script uses the Detach and Attach method. You can move one database or you can pipeline a number of databases through the script. Database owners are maintained so you will need to migrate those logins prior to using the script. Also, the script does not reattach the databases on the source server. At some point I may update the script to accommodate reattaching the databases. For my current purpose, I did not need to do so. However, a switch is provided which informs the script to reattach the source database after it has been copied.

There is an assumption the script is run by a member of the ‘Domain Admins’ AD group. It shouldn’t be too difficult to change the script to use “shares” other than admin shares (i.e. Drives with a $ sign).

The code contains several references to functions which I have in an imported module. Over time I’ve developed a long list of my own helper functions and found it easier to have them in a module. The functions I reference are listed individually after the main source code.

Here is the mainline source for the script.

<#
.SYNOPSIS
	Copy SQL databases to another server.
.DESCRIPTION
	Uses the Detach and Attach method to copy or move a database or databases
	from one SQL Server to another. 
.PARAMETER Database
	Name of the database to be copied
.PARAMETER SourceServer
	Server and instance name of the source SQL Server
.PARAMETER DestinationServer 
	Server and Instance name of the destination SQL Server
.EXAMPLE
	$dblist | Migrate-Databases.ps1 -SourceServer 'SQLSERVER1' -DestinationServer 'SQLSERVER2\SQL2'
	Pipelines an array list of database names into the migration script with
	SQLSERVER1 as the source and SQLSERVER2\SQL2 as the destination.
.EXAMPLE 
	Migrate-Databases.ps1 -database 'MyDB' -SourceServer 'SQLSRV1' -DestinationServer 'SQLSRV2'
	Copies MyDB from SQLSRV1 to SQLSRV2
.NOTES
	Script uses administrative UNC shares names for all files to be copied and must be run by someone
	in the 'Domain Admins' group. Many of the functions called are part of an Imported 
	module containing many helper funtions. The 
	Author: 	John P. Wood
	Company:	ATOS, Inc\Lee County, FL BOCC
	Date:		June, 2013
	This code can be copied and used as long as proper attribution is given and
	these notes are kept intact.
#>
[CmdletBinding()]
Param(
	[Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
	[String]$Database,
	[Parameter(Mandatory=$true,Position=1)]
	[string]$SourceServer,
	[Parameter(Mandatory=$true,Position=2)]
	[string]$DestinationServer,
	[Parameter(Mandatory=$false,Position=3)]
	[switch]$ReattachSource
)

Begin
{
	Set-StrictMode -Version Latest
	Try
	{
		$smosrc = New-SMOconnection -server $SourceServer
		$smodest = New-SMOconnection -server $DestinationServer
		$filedest = Get-UncPath -server $DestinationServer -path $smodest.DefaultFile
		$logdest = Get-UncPath -server $DestinationServer -path $smodest.DefaultLog
	}
	Catch
	{
		Throw
	}
}
Process
{
	Try
	{
		$db = $smosrc.Databases[$database]

		# Break the mirror
		If($db.IsMirroringEnabled -eq $true) {
			Write-Verbose "Breaking mirror for $($db.Name)"
			$db.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
			$db.Alter()
			$db.Refresh()
		}

		# Get Data files
		$files = @(Get-DatabaseDataFiles -Database $db.Name -Server $SourceServer |
			ForEach-Object{$_.FileName})
		$files | Write-Verbose 

		# Get log files
		$logfiles = @($db.LogFiles | ForEach-Object{$_.Filename})
		$logfiles | Write-Verbose

		# get Owner
		$owner = $db.Owner
		$owner | Write-Verbose

		# Detach database
		$smosrc.KillAllProcesses($db.Name)
		$smosrc.DetachDatabase($db.Name,$false)

		# Copy files to destination
		$destfiles = @()
		$destlogs = @()

		$files | Get-UncPath -Server $SourceServer | Copy-Item -Destination $filedest
		$files | ForEach-Object {
			$destfiles += [System.IO.Path]::Combine($filedest,
				[System.IO.Path]::GetFileName($_))}

		$logfiles | Get-UncPath -Server $SourceServer | Copy-Item -Destination $logdest 
		$logfiles | ForEach-Object {
			$destlogs += [System.IO.Path]::Combine($logdest,
				[System.IO.Path]::GetFileName($_))
		}

		# Attach database
		$sc =  New-Object System.Collections.Specialized.StringCollection

		$destfiles | ForEach-Object{
			$sc.Add($(Get-LocalPath -uncpath $_ ))
		}
		$destlogs | ForEach-Object{
			$sc.Add($(Get-LocalPath -uncpath $_ ))
		}
		$smodest.AttachDatabase($db.Name, $sc, $owner, 
			[Microsoft.SqlServer.Management.Smo.AttachOptions]::None)

		# Reattach the source databases if switch is on
		If($ReattachSource.IsPresent)
		{
			$sc.Clear()
			$files | ForEach-Object{$sc.Add($_ )}
			$logfiles | ForEach-Object{$sc.Add($_ )}
			$smosrc.AttachDatabase($db.Name, $sc, $owner, 
				[Microsoft.SqlServer.Management.Smo.AttachOptions]::None)
		}
		Return $smodest.Databases[$database]
	}
	Catch 
	{
		$ex = $_.Exception
		Write-Error $ex.Message
		While($ex.InnerException) 
		{
			$ex = $ex.InnerException
			Write-Error $ex.Message
		}
	}	
}
End
{
	$smosrc.ConnectionContext.Disconnect()
	$smodest.ConnectionContext.Disconnect()

}

A small function to get a UNC path

Function Get-UncPath {
<#
.Synopsis
	Returns a windows UNC path when given a server and path on the server.
.Description
	Returns a windows UNC path when given a server and path on the server.
.Parameter ComputerName
	The name of a computer or full dns name. This will also take an SQL Instance
	and remove the Instance name leaving the server name.
.Parameter Path
	The local path name of a a path on the computer
.Example Get-UncPath -ComputerName "LCFSQL07" -Path "D:\Logs"
	Returns a UNC path of "\\LCFSQL07\D$\Logs"
.Example Get-UncPath -ComputerName "LCFSQL07\SQL01" -Path "D:\Logs"
	Returns a UNC path of "\\LCFSQL07\D$\Logs"
#>
	[CmdletBinding()]
	Param(
		[Parameter(Position=0,Mandatory=$true)]
		[Alias("ComputerName")]
		[string]$Server,
		[string]$path
	)
	Process{
	Join-Path "\\$($server.split('\')[0])" $path.replace(':','$')
	}
}

Function to get an SMO connection

Function New-SMOconnection
{
	[CmdletBinding()]
    Param (
		[Parameter(Mandatory=$true)]
		[string]$Server,
		[Parameter(Mandatory=$false)]
		[string]$ApplicationName = "Powershell SMO",
		[Parameter(Mandatory=$false)]
		[int]$StatementTimeout=0
	)
	$conn = New-Object Microsoft.SqlServer.Management.Common.ServerConnection($Server)
	$conn.ConnectTimeout = 5
	$conn.applicationName = $ApplicationName
	$conn.StatementTimeout = $StatementTimeout
	Try 
	{
		$conn.Connect()
		Return New-Object Microsoft.SqlServer.Management.Smo.Server($conn)
	}
	Catch 
	{
		Throw 
	}
}

A function to get physical files associated with a database

Function Get-DatabaseDataFiles
{	
	[CmdletBinding()]
	Param(
		[string]$Server,
		[string]$Database
	)
	Try
	{
		$smo = New-SMOconnection -server $Server
	}
	Catch
	{
		Throw $_.Exception
	}
	Try
	{
		$files = @()
		$db = $smo.Databases[$Database]
		If(!($db)) { Throw "$Database was not found" }
		Foreach($group in $db.FileGroups)
		{
			Foreach($datafile in $group.Files)
			{
				$hashtable = @{Group = $group.Name;
					Name = $datafile.Name;
					FileName = $datafile.Filename
				}
				$file = New-Object -TypeName PsObject -Property $hashtable
				$files += $file
			}
		}
	}
	Finally
	{
		$smo.ConnectionContext.Disconnect()
	}
		Return $files
}

Convert a UNC path back to a local path

Function Get-LocalPath {
	[CmdletBinding()]
	Param(
		[Parameter(Mandatory=$true)]
		[string]$uncpath
	)
	Process{
	$dirs = ($uncpath.Remove(0,2)).Split('\')
	$dir = ($dirs[1]).Replace('$',':')
	For([int]$ix=2; $ix -lt $dirs.Count; $ix++) {
		$dir += "\$($dirs[$ix])"
	}
	$dir
	}
}
Share

Restore All SQL Transaction Logs using Powershell

I came across another opportunity for using Powershell when setting up mirroring for a large database at our DR site. The database was over 73Gig. The problem arose when it took a very long time to copy the backup and transaction logs and run the database restore. By the time the copy had finished another several transaction logs had been produced. While applying the *.bak  restore, yet another several transaction logs had been produced. Continue reading

Share

Cycle Those SQL Server Logs

Don’t you just hate it when you open one of your server logs in Management Studio and then you have to wait forever to review it because it never stops reading? I’ve done it enough times and decided to put an end to it or at least greatly reduce the incidence.

Using Powershell and a simple script to execute Sp_Cycle_Errorlog, I now have a weekly, Windows scheduled task which executes the sproc to cycle the logs for all my SQL Servers.

foreach ($svr in get-content D:\Scripts\Servers.TXT ){
    $svr
    Invoke-Expression 'SQLCMD -E -S $svr -Q "Exec Sp_Cycle_Errorlog"' | Out-Null
	}
Share

PowerShell Where Clause – Compare to a list of values

As a databases admin with a few dozen servers and a few hundred SQL Server database, I need to effectively build automated jobs to perform various administrative tasks such as backing up databases. I’m still new to PowerShell but I am beginning to see how immensely helpful it is to me and my work.

I found a good PowerShell script for backing up databases at this blog location. However, I wanted to change it slightly to accommodate backing up ALL, System, User, or a list of databases. Processing ALL, System, or User is straightforward enough but I had to it was the first time I came up against have to compare a value against a list of values. In SQL you simply use an IN operator with a list of values but I was not sure of how to accomplish the same process in PowerShell.

Here was my problem: the user could supply a list of databases to backup and the backup script has a line with captures all the database objects for that SQL Server instance. I need to backup only those which are in the users list.

I found that the PowerShell WHERE clause can use a -contains operator which effectively filters the selection for an array. To test the operation I set up a small script to build two arrays and then I piped one array through a WHERE filter of which the results are piped to a ForEach. The ForEach sees only the results of the WHERE which mimics the SQL IN operator.

Run this code in PowerShell to see what I mean.

$a = @("db1","db2","db3","db4")
$d = @("db1","db2","db3","db4","db5","db6","db7","db8")
$d | where { $a -contains $_ } | foreach { Write-Host $_ }

$a represents the list of database supplied by the user.
$d represents the array of databases returned when accessing the Microsoft.SqlServer.Management.Smo.Server object.
I can now back up the user supplied databases within the foreach script block.

Share

Tips & Tricks Using Compare-Object – Copy New Files

This is a wonderful article which not only details the use of Compare-Object but also provides some very excellent examples on its use.

Tips & Tricks Using Compare-Object – Dreaming in PowerShell – PowerShell.com

I am very new to PowerShell but I am beginning to like using it considerably more each day. I recently had a need for a quick and easy way to script a process which would copy only new files from one location to another. after scouring the Internet for a good example, I stumbled upon the Compare-Object cmdlet and this excellent article.

The actual script I came up with is rather simple and serves my purpose exactly. As I  mentioned, I am new to PowerShell and there may very well be a better method. However, this one works for me.

The script code:

param(
    [string]$s = '\\serverx\c$\ProgramData\Polaris\3.5\SQLVS1\AuthorityUpdates',
    [string]$t = '\\servery\data03\shared\libraries\zmarc'
)

$target = Get-ChildItem $t
$source = get-childitem $s
Compare-Object $source $target -Property Name -PassThru |
    Where-Object { $_.SideIndicator -eq '<=' } |
    foreach-object -process{
        copy-item $_.FullName -destination $t
        }
Share