Mirroring Hard drives Using PowerShell and SyncToy
18 Nov 2011
My main protection against hard drive failure for a long time has been RAID 1. I would get two identical hard drives and RAID them so that everything I do on the first drive would be replicated on the second. One time, this actually saved me from a hard drive failure with almost zero down time.
I can say that this has worked out very well for a long time until one day I made a stupid mistake and accidently chose the wrong drive when installing Windows. I wiped out the data drive and sure enough RAID has mirrored everything I did. It was 4 days of crying, recovering data with DiskDigger, banging my head on the desk, recovering data, more crying, and so on. Since then, I dropped the RAID 1 on all my hard drives and switched to using SyncToy to mirror identical disks once a day.
SyncToy is very simple to use and you can use it with Windows Task Scheduler to sync the drives at night. However, I ran into two main problems: Copying SQL Server database files, and Copying mounted VHD files. These files won’t get copied if they’re in use. To solve this problem, I need to stop the SQL Server service, and un-mount the VHD file before mirroring the drives. To do that and after learning all about it, I’ll be using PowerShell.
Running PowerShell Scripts
The first thing to try out is running PowerShell scripts in Windows. If you’re running Windows 7 like I’m, you could run into the issue of restricted execution policy. If you try to run a PowerShell script, any script, you’ll find this error message:
File C:\test.ps1 cannot be loaded because the execution of scripts is disabled on this system.
Please see “get-help about_signing” for more details.
This is caused by Windows limiting the ability to run PowerShell scripts by default. You can get the status of the execution policy using the PowerShell cmdlet Get-ExecutionPolicy
To change the status of the execution policy use the Set-ExecutionPolicy cmdlet. Here, I’m switching to “Unrestricted”:
Changing the execution policy is a one-time thing, you won’t have to worry about it again. Now, your Windows 7 is ready to run PowerShell scripts. You can read all about execution policy and running PowerShell scripts on Windows in this TechNet article.
Running PowerShell Scripts using Task Scheduler
The Task Scheduler will be responsible for running the script. It can’t run it directly, we’ll have to run PowerShell.exe and pass the script as an argument. It’s like running a PowerShell script from the command line. To do that we enter the PowerShell.exe path followed by “& then the path to the script surrounded by single or double quotes (Source). This helps with running scripts that have space in their path. Here’s an example:
Copying Database Files
Before we start writing the script, we’ll have to test copying the database file. I stopped the SQL service using the Services management console and tried to copy the file but it failed. Database files by default are not owned by your Windows user account. You have to take ownership of the files (the MDF file and the Log file) and grant your Windows account sufficient permission to be able to copy the files. You can use the Properties dialog to take ownership of the files. I do that and now I can copy the files.
I can write all the commands in the PowerShell script directly, but I would rather break down the script into reusable functions. To prepare to copy the database files, I have 3 functions; check the status of the SQL service, stop the service and start it. I would like to maintain the status of my SQL service as it is after the mirroring is complete. I want to stop the service and start it again only if it was actually running.
Checking the service status, stopping it, and starting it is the same for all services and is not related to SQL Server. You can read all about it here. We can simply use the commands Get-Service, Stop-Service and Start-Service.
I’m writing the functions in a bit of a generic way so that I can use them later in other scripts without having to change their content (only their name have to change). For example, I’m passing the name of the service. Here’s the Get-SqlServiceStatus function as an example:
All I need now is the name of the SQL service on my computer. To get the name of the service, navigate to the service properties dialog and pick the name of the service (not the display name). Make sure to use PowerShell escape characters to enter the name correctly. For example: “MSSQL$SQLEXPRESS” have a dollar sign ($), so I have to add a backtick before the dollar sign to make PowerShell parse the name correctly “MSSQL`$SQLEXPRESS”.
Mounting VHDs
I think that the common way to mount a VHD in PowerShell is using WMI (Windows Management Instrumentation). A quick search online will confirm this. However, WMI is not available for Windows 7, it’s for Windows Server. We can’t mount the VHD file using WMI because the namespace “Root\Virtualization” is not available on Windows 7. This namespace include the class that could be used to mount the VHD file. If you’re running Windows Server and can use WMI, here’s a description of how to mount virtual hard drives with HyperV and WMI.
Aside from using WMI, normally we’d mount a VHD using the DiskPart command. To do that, create a file and name it AttachMyVHD.s for example and add this to it:
Then create a Windows command (.cmd) file that call the diskpart command and pass this file to it.
In the S file, we’re selecting the VHD by specifying its location, then attaching it. In the Windows command file, we’re passing the S file to the DiskPart command.
In PowerShell, we’ll just execute the Windows command file. This Mount-Codebox function declares a variable with the location of the Windows command file. Codebox is the name of the VHD file I’m using.
To unmount a VHD, you can do the same thing, except in the S file, use detach vdisk instead of attach vdisk.
Before we unmount the VHD and re-mount it after the mirroring, we need to check if it’s already mounted. We don’t want our script to change an existing status, we want it to maintain it. If the VHD is not already mounted, don’t try to unmount, then re-mount it at the end. Just skip this step altogether.
To check if the VHD is already mounted, we’ll just check if the drive it creates exists or not. To check if a drive exists, we’ll use the Exists-Drive function.
Run SyncToy
To run SyncToy from PowerShell, we’ll use the same way we called the Windows command file above; using the & symbol to execute the SyncToy EXE file. We’ll call the SyncToyCmd.exe file which is the command line equivalent of the application so we can pass the name of the backup plan as an argument. If you use the command line to call SyncToyCmd, it’ll look like this:
We’ll create the Run-SyncToy function with the backup plan name as an input so we can call it multiple times for different plans.
Write PowerShell Output to File
Logically, I’m running this script overnight and I would like to understand what happened while I’m away, so we’ll write the output of the script to a log file named with the date of when the script ran.
To write the output of any PowerShell command or function to file, use the Out-File cmdlet. This TechNet page explains how to use it. Example:
This line write the output of the Get-ChildItem cmdlet to the text file named MyOutputFile.txt under the D drive. Notice the use of the two arguments –Append (which appends to the file if it already exists) and –Force (which creates the file if it doesn’t exist).
We’ll need to stamp the file name with time of when the script ran, to do that we’ll use the Get-Date cmdlet and the argument –Format. Example:
Putting It All Together
You can notice that all the work above just creates functions to be used in the main body of the script. Here’s where we put it all together.
Try It Out Yourself
You probably have a different backup strategy, but you can still take advantage of PowerShell and SyncToy in a whole host of other scenarios. Download the script here. Tweak it till it fit your requirements and please leave a comment below if you have a question or found a bug somewhere in the script. Even if not for mirroring hard drives, this still was a nice exercise for using PowerShell.