Skip to main content

Script to move SQL Server System Database Files

One issue that can occur when using VM snapshots to back up a server with large database volumes is that the server gets paused when the snapshots are consolidated. We like to refer to this a "stunning" because it sounds worse, and it can cause really bad things if you are using clusters and/or availability groups. If a cluster node gets stunned for more than a few seconds, the other node will think that it needs to fail over. Additionally, when the stun is over, the node that was stunned comes back to life right where it left off, and if it was the owner when it was stunned, all this has to be sorted out, and honestly, after working with Microsoft Cluster for twenty years, you just don't want to put it in this situation unless there's no way to avoid it.

So, your backup strategy changes, and you set your large volumes as independent disks so that they are ignored by the VM snapshots. This lets you backup the OS and any other small volumes without a stun. Your databases, of course, are being backed up by other means.

So a problem arises if your system databases are on one of those large volumes. Sure, you're backing them up, but have you thought of what happens if you have to restore the entire server from backup? You restore the volumes that were backed up by the VM snapshot, and then you just start restoring databases, right?

Well, hopefully, yes. But if the volume that held your master, model, and msdb databases wasn't part of the VM snapshot, SQL Server isn't going to start if your data volumes are gone. You'll have to go through the SQL Server install to get a new master database, and then you'll have to restore the backup of master and model from command line in order to get SQL Server started again. So this is going to add probably an hour to your recovery time.

After you've thought through it, you realize that moving master, model, and msdb files to the C: drive (or another volume that is getting the VM snapshot backups) is a good solution. If the are on C: and you have to restore the system from backup, SQL Server will be able to start up and you can start restoring databases as soon as the VM snapshot backup is restored.

No big deal--if you've only got three servers. Big deal, if you have a lot of servers. This would be quite time-consuming to go through the process of moving the files on every system. It's really nothing difficult--just running some queries, a couple of registry edits, moving the files, setting permissions.

So following is a PowerShell script, that when paired with some version-specific registry files to import, will move the system database and log files to a new location in just a few seconds. This is not set up to run remotely, so you'll need to run this on the server. I highly recommend that you review Move System Databases so that you understand what's happening. Each section of the script has comments so that you can easily see what's being done.

##############################################################################
###                                                                        ###
###   Set the new path for the system database files to be moved to        ###
###   This path needs to be paired with the path in the registry file(s)   ###
###                                                                        ###
            $MoveToPath = "C:\SystemDatabases\DATA"
###                                                                        ###
##############################################################################

function Pause()
{
  Write-Host "     Press any key to continue ..."
  $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  write-host
}

# Launch this script as administrator if not running as administrator
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){   
    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
    Start-Process powershell -Verb runAs -ArgumentList $arguments
    Break
}

cls
write-host
write-host
write-host !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
write-host
write-host "     This script will stop SQL Server and move the system"
write-host "     databases to $MoveToPath."
write-host
write-host !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
write-host
Pause

#Set up the script enviroment
$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location "$myDir"

$SqlServer = $ENV:COMPUTERNAME

#Create the new directory
New-Item -ItemType Directory -Path $MoveToPath

#Get the current locations of the system database files
$MasterMdfPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'master') AND name = 'master';")[0]
$MasterLogPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'master') AND name = 'mastlog';")[0]

$ModelMdfPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'model') AND name = 'modeldev';")[0]
$ModelLogPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'model') AND name = 'modellog';")[0]

$MsdbMdfPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'msdb') AND name = 'MSDBData';")[0]
$MsdbLogPath = (Invoke-SqlCmd -ServerInstance $SqlServer -Query "SELECT physical_name AS CurrentLocation FROM sys.master_files WHERE database_id = DB_ID(N'msdb') AND name = 'MSDBLog';")[0]

#Change model database file paths
Invoke-SqlCmd -ServerInstance $SqlServer -Query "ALTER DATABASE model MODIFY FILE ( NAME = modeldev , FILENAME = '$MoveToPath\model.mdf' )"
Invoke-SqlCmd -ServerInstance $SqlServer -Query "ALTER DATABASE model MODIFY FILE ( NAME = modellog , FILENAME = '$MoveToPath\modellog.ldf' )"

#Change msdb database file paths
Invoke-SqlCmd -ServerInstance $SqlServer -Query "ALTER DATABASE msdb MODIFY FILE ( NAME = MSDBData , FILENAME = '$MoveToPath\MSDBData.mdf' )"
Invoke-SqlCmd -ServerInstance $SqlServer -Query "ALTER DATABASE msdb MODIFY FILE ( NAME = MSDBLog , FILENAME = '$MoveToPath\MSDBLog.ldf' )"

#Get the major SQL Server version so that the correct registry file can be found
$SqlVersion = (invoke-sqlcmd -ServerInstance $SqlServer -query "select SERVERPROPERTY('ProductMajorVersion')").Column1
write-host "SQL  Major Version: $SqlVersion"

#Set the registry file according to the version.  This sets the path to the master database and log files in the registry
#An improvement would be to make these registry changes with PowerShell so that additional files aren't needed
switch ($SqlVersion) {
   "10" {$RegFile = "2008_MoveMaster.reg"; break}
   "11" {$RegFile = "2012_MoveMaster.reg"; break}
   "12" {$RegFile = "2014_MoveMaster.reg"; break}
   "13" {$RegFile = "2016_MoveMaster.reg"; break}
   "14" {$RegFile = "2017_MoveMaster.reg"; break}
   default {"Unknown version of SQL Server!"; pause; exit}
}

#Import the registry file that changes the startup parameters for the master database files and data root
regedit.exe /S $RegFile

#Stop SQL Server services
stop-service sqlserveragent
stop-service mssqlserver

#Set permissions on the new directory so that the SQL Server service account has full control
$SqlServiceName = ((get-wmiobject win32_service | where {$_.Name -eq 'mssqlserver'}).StartName).replace(".\", "")
$Acl = get-acl $MoveToPath
$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule("$SqlServiceName", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
$Acl.SetAccessRule($Ar)
Set-Acl $MoveToPath $Acl

#Move the system database files
Move-item -Path $MasterMdfPath -Destination "$MoveToPath\"
Move-item -Path $MasterLogPath -Destination "$MoveToPath\"

Move-item -Path $ModelMdfPath -Destination "$MoveToPath\"
Move-item -Path $ModelLogPath -Destination "$MoveToPath\"

Move-item -Path $MsdbMdfPath -Destination "$MoveToPath\"
Move-item -Path $MsdbLogPath -Destination "$MoveToPath\"

#Start the SQL Server services
start-service mssqlserver
start-service sqlserveragent

The registry files need to look something like this:

2017_MoveMaster.reg
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\Parameters]
"SQLArg0"="-dC:\\SystemDatabases\\DATA\\master.mdf"
"SQLArg2"="-lC:\\SystemDatabases\\DATA\\mastlog.ldf"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\Setup]
"SQLDataRoot"="C:\\SystemDatabases"
2016_MoveMaster.reg
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\Parameters]
"SQLArg0"="-dC:\\SystemDatabases\\DATA\\master.mdf"
"SQLArg2"="-lC:\\SystemDatabases\\DATA\\mastlog.ldf"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\Setup]
"SQLDataRoot"="C:\\SystemDatabases"

...and so on. You need to create a file for each major version you'll use it with and change the registry paths accordingly. If you don't understand this, you probably ought not to use this script to move your system databases.

Comments

Apoorva said…
Thanks for sharing informative post.
Restore Master Database in SQL Server

Popular posts from this blog

SQL Diagnostics Manager - Script to Disable Maintenance Mode on All Servers

We use Idera's SQL Diagnostics Manager and there is a defect where monitored servers are sometimes not taken out of maintenance mode as specified by the schedule. As a workaround, I wrote the following script that runs each morning to log which servers are in maintenance mode, and disables maintenance mode on all servers. The use of the -MMRecurring switch on Set-SQLdmMonitoredInstance disables maintenance mode and keeps the maintenance mode schedule enabled. add-pssnapin sqldmsnapin $LogFile = "D:\Jobs\DisableMaintenanceModeAll\MMEnabledLog.txt" # Log any servers that are currently in maintenance mode $MMEnabled = invoke-sqlcmd -serverinstance -Query "USE SQLdmRepository; SELECT InstanceName FROM MonitoredSQLServers WHERE MaintenanceModeEnabled = 1" if( $MMEnabled.ItemArray.Count -gt 0 ) { add-content -Value " " -path $LogFile add-content -Value "================================================" -path $LogFile add-content -Value &qu

Maine Event - Days Eight, Nine, and Ten

Sunday morning brought more sunshine, so we puttered around the house a bit and then made a beeline for Popham Beach. The day went without much excitement other than Leigh finding a sand dollar about the size of a fifty cent piece. Emily once again had a fantastic time and even worked on her swimsuit modelling skills. We also flew my parafoil kite, which requires a good bit of wind to fly, but is extremely stable once it's up. Since we knew the secret of the beach, there was no mystery to the phenomena of the growing beach, but it was still super-duper. We knew this time to take our shoes to the island so we could explore a bit, so we tramped around it for quite a while. That was pretty much it for Day Eight. Day Nine didn't see much action of note until we headed to Portland to pick up Mamaw and Papaw Hinkle. After we loaded them up we stopped at L.L. Bean for a bit. They don't allow you to leave the state until you've made a purchase there. We drove down to the restau