Detach/Attach

This category page is part of a series of Professional PowerShell SMO Recipes. You can find an index of all recipes on the main SMO Recipes page, and if you want to learn more about each recipe and see some sample screenshots, click on its category page.

These scripts were created and tested on Windows 8.1 with PowerShell v4 and SQL Server 2014, though most recipes should work with earlier versions of SQL Server. PowerShell v3 and above is required for many of the recipes. If you have a recipe request, leave a comment and I'll see what I can do. This cookbook will be continuously built, as I work more with SMO.

Recipe Categories

Detach/Attach

To see some these scripts used within a formally written PowerShell script that includes parameterized functions, check out my SQL migration script, Start-SQLMigration.ps1, on Microsoft’s ScriptCenter.

Detach Database

Warning! This code removes will break a mirror if one exists. It will also remove a database from an Availability Group if it is part of one. Be sure to note the database’s attributes such as owner chaining, trustworthy, broker enabled, read-only status, and dbowner. These settings are lost during attach/detaches.

$servername = "sqlserver"
$dbname = "mydb"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") 
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $servername
$database = $server.databases[$dbname]
if ($database -eq $null) { throw "Database does not exist." }

if ($database.IsMirroringEnabled) {
	try {
		Write-Warning "Breaking mirror for $dbname"
		$database.ChangeMirroringState([Microsoft.SqlServer.Management.Smo.MirroringOption]::Off)
		$database.Alter()
		$database.Refresh()		
	} catch { throw "Could not break mirror for $dbname." }
}

if ($database.AvailabilityGroupName.Length -gt 0 ) {
	$agname = $database.AvailabilityGroupName
	Write-Host "Attempting remove from Availability Group $agname" -ForegroundColor Yellow 
	try {
		$server.AvailabilityGroups[$database.AvailabilityGroupName].AvailabilityDatabases[$dbname].Drop()
		Write-Host "Successfully removed $dbname from  detach from $agname on $($server.name)" -ForegroundColor Green 
	} catch { throw "Could not remove $dbname from $agname on $servername" }
}
	
Write-Warning "Attempting detach from $dbname from $source"

# Kill connections & detach
$sql = "ALTER DATABASE [$dbname] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"
try { 
	$null = $server.databases['master'].ExecuteNonQuery($sql)
	$result = $server.DetachDatabase($dbname, $false, $false)
	Write-Host "Successfully detached $dbname from $source" -ForegroundColor Green 
} 
catch { $exception = $_.Exception.InnerException; Write-Host $exception -ForegroundColor Red -BackgroundColor Black }

Attach database to server from files contained within server default data and log locations.

Note that there are some database attributes (owner chaining, trustworthy, broker enabled, read-only, dbowner) that are lost when performing a detach/attach. Refer to my Start-SQLMigration.ps1 script to see how you can retain these settings when performing live migrations.

$servername = "sqlserver"
$mdfname = "myfile.mdf"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$filestructure = New-Object System.Collections.Specialized.StringCollection
$options = [Microsoft.SqlServer.Management.Smo.AttachOptions]::None
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $servername
$primaryfile = "$($server.DefaultFile)$mdfname"

# Here you can automatically determine the database name, or set it manually
$dbname = ($server.DetachedDatabaseInfo($primaryfile) | Where { $_.Property -eq "Database name" }).Value

$defaultdata = $server.DefaultFile
$defaultlog = $server.DefaultLog

if ($defaultdata.Length -eq 0) {
    $defaultdata = $server.Information.MasterDBPath
}
if ($defaultlog.Length -eq 0) {
    $defaultlog = $server.Information.MasterDBLogPath
}

foreach    ($file in $server.EnumDetachedDatabaseFiles($primaryfile)) {
	$newfilename = Split-Path $($file) -leaf    
	$newpath = "$defaultdata$newfilename"
	$null = $filestructure.add($newpath)
}

foreach ($file in $server.EnumDetachedLogFiles($primaryfile)) {
	$newfilename = Split-Path $($file) -leaf    
	$newpath = "$defaultlog$newfilename"
	$null = $filestructure.add($newpath)
}
  
try { 
    $server.AttachDatabase($dbname, $filestructure, "sa", $options)
    Write-Host "Successfully attached $dbname from $source" -ForegroundColor Green 
} 
catch { 
    $exception = $_.Exception.InnerException; 
    Write-Host $exception -ForegroundColor Red -BackgroundColor Black }
	

Attach Database, reusing previous file structure.

Note that there are some database attributes (owner chaining, trustworthy, broker enabled, read-only, dbowner) that are lost when performing a detach/attach.

$servername = "sqlserver"
$primaryfile = "M:\MSSQL12.MSSQLSERVER\MSSQL\DATA\mydb.mdf"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$filestructure = New-Object System.Collections.Specialized.StringCollection
$options = [Microsoft.SqlServer.Management.Smo.AttachOptions]::None
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $servername

# Here you can automatically determine the database name, or set it manually
$dbname = ($server.DetachedDatabaseInfo($primaryfile) | Where { $_.Property -eq "Database name" }).Value

# If you want to specify your files manually, use:
# $filestructure.add("M:\whatever\logs\mydb.ldf")
# $filestructure.add("M:\whatever\data\mydb.ndf"), etc

foreach    ($file in $server.EnumDetachedDatabaseFiles($primaryfile)) {
	$null = $filestructure.add($file)
}

foreach ($file in $server.EnumDetachedLogFiles($primaryfile)) {
	$null = $filestructure.add($file)
}

try { 
	$server.AttachDatabase($dbname, $filestructure, "sa", $options)
	Write-Host "Successfully attached $dbname from $source" -ForegroundColor Green 
} 
catch { 
	$exception = $_.Exception.InnerException; 
	Write-Host $exception -ForegroundColor Red -BackgroundColor Black }
}

Enumerate file structure within detached MDF database file

While researching how connect to a detached MDF and read the internal file structure of the internal files, including LDF files, I kept seeing suggestions for using the undocumented DBCC command DBCC checkprimaryfile. Initially, my code looked like this:

# SQL Server is required to read the file contents
$servername = "sqlserver\instance"
$mdf = "S:\DATA\mydb.mdf"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $servername

# Use the undocumented DBCC checkpriamryfile function to automatically determine
# where the files are located. If you moved the files, please see below.
$sql = "DBCC checkprimaryfile(N'$primaryfile',3)"
$dataset = $server.databases['master'].ExecuteWithResults($sql)
$dataset.Tables.Filename.Trim()

After some exploration, I discovered that this task can actually be accomplished purely in SMO using EnumDetachedDatabaseFiles and EnumDetachedLogFiles.

$servername = "sqlserver\instance"
$mdf = "S:\DATA\mydb.mdf"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$filestructure = New-Object System.Collections.Specialized.StringCollection
$server = New-Object Microsoft.SqlServer.Management.Smo.Server $servername

# Here you can automatically determine the database name, or set it manually
$dbname = ($server.DetachedDatabaseInfo($mdf) | Where { $_.Property -eq "Database name" }).Value
 
foreach    ($file in $server.EnumDetachedDatabaseFiles($mdf)) {
	$null = $filestructure.add($file)
}

foreach ($file in $server.EnumDetachedLogFiles($mdf)) {
	$null = $filestructure.add($file)
}

Get detached SQL Server MDF file information

Put it all together. You can find the formalized script of this snippet on ScriptCenter (Get-DetachedDBinfo.ps1).

get-detacheddbinfo

$servername = "sqlserver"
$mdf = "M:\archive\mydb.mdf"

[void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")

$server = New-Object Microsoft.SqlServer.Management.Smo.Server $server
try { $server.ConnectionContext.Connect() } catch { throw "Can't connect to SQL Server." }

$datafiles = New-Object System.Collections.Specialized.StringCollection
$logfiles = New-Object System.Collections.Specialized.StringCollection

try {
	$detachedDatabaseInfo = $server.DetachedDatabaseInfo($mdf)
	$dbname = ( $detachedDatabaseInfo | Where { $_.Property -eq "Database name" }).Value
	$dbversion = ($detachedDatabaseInfo | Where { $_.Property -eq "Database version" }).Value
	$collationid = ($detachedDatabaseInfo | Where { $_.Property -eq "Collation" }).Value
} catch { throw "$($server.name) cannot read the file $($MDF). Does service account $($server.ServiceAccount) have accesss to that path?" }

switch ($dbversion) {
	782 {$dbversion = "SQL Server 2014"}
	706 {$dbversion = "SQL Server 2012"}
	684 {$dbversion = "SQL Server 2012 CTP1"}
	661 {$dbversion = "SQL Server 2008 R2"}
	660 {$dbversion = "SQL Server 2008 R2"}
	655 {$dbversion = "SQL Server 2008 SP2+"}
	612 {$dbversion = "SQL Server 2005"}
	611 {$dbversion = "SQL Server 2005"}
	539 {$dbversion = "SQL Server 2000"}
	515 {$dbversion = "SQL Server 7.0"}
	408 {$dbversion = "SQL Server 6.5"}
}

$collationsql = "SELECT name FROM fn_helpcollations() where collationproperty(name, N'COLLATIONID')  = $collationid"
try {
	$dataset = $server.databases['master'].ExecuteWithResults($collationsql)
	$collation = "$($dataset.Tables[0].Rows[0].Item(0))"
} catch { $collation = $collationid }

if ($collation.length -eq 0) { $collation = $collationid }

try {
	foreach    ($file in $server.EnumDetachedDatabaseFiles($mdf)) {
		$datafiles +=$file
	}
	 
	foreach ($file in $server.EnumDetachedLogFiles($mdf)) {
		$logfiles +=$file
	}
} catch { throw "$($server.name) enumerate database or log structure information for $($MDF)" }
	
$mdfinfo = New-Object PSObject -Property @{
	"Database Name" = $dbname
	"Database Version" = $dbversion
	"Database Collation" = $collation
	"Data files" = $datafiles
	"Log files" = $logfiles
}

Write-Host "The following information was gathered about the detatched database:" -ForegroundColor Green
return $mdfinfo

$server.ConnectionContext.Disconnect()
Want to see more? You can find an index of all recipes on SMO Recipes Index Page page or click on any specific category at the top of this page.

Chrissy is a Cloud and Datacenter Management & Data Platform MVP who has worked in IT for over 20 years. She is the creator of the popular SQL PowerShell module dbatools, holds a master's degree in Systems Engineering and is coauthor of Learn dbatools in a Month of Lunches. Chrissy is certified in SQL Server, Linux, SharePoint and network security. You can follow her on Twitter at @cl.

One comment on “Detach/Attach
  1. Juan Starck says:

    Hello,
    I want to say thank you! Your code is great and help me a lot.
    I have attached arround 1800 DB’s to new server, thaks to you :)
    Best Regards!

Leave a Reply to Juan Starck Cancel reply