Wednesday 10 December 2014

Monitoring Shadow Copies with PowerShell




Shadow Copies are a wonderful thing.  Yup, they're not a replacement for backups but they are not meant to be.  What they are is a very quick, very simple way to restore data.

For peace of mind, I like to have a quick glance over my servers to make sure Shadow Copies are turned on for the drives I need and are creating copies on schedule.  That's the sort of regular, repetitive task that PowerShell is great for.

I'm a fan of using PowerShell with Get-WMIObject to get data back from WMI and there's a WMI class for managing Shadow Copies called, well, Win32_ShadowCopy.  Get-WMIObject Win32_ShadowCopy $servername will get you a list of all current shadow copies on that server for all drives.  If you run that against a server with no shadow copies, all you're going to get back is an "InvalidOperation" error.

There are two properties of Win32_ShadowCopy that I'm really interested in, InstallDate and VolumeName.  InstallDate holds the date the shadow copy was created and VolumeName holds the ID of the device (drive) that the shadow copy was created on.  Both these fields need a little manipulating to be useful though.

A typical InstallDate is going to look something like this.

20141201094535.549577+000.  

I'm only interested in the part before the period which is the date and time in the format yyyymmddhhmmss as a long string so we need to break this up a bit.  Something like this.

$date = [datetime]::ParseExact($copy.InstallDate.Split(".")[0], "yyyyMMddHHmmss", $null)

That looks a little complicated but it's not really.  What we're doing is creating a new variable, using [datetime] to force the type and ParseExact to turn the string into something that's usable as a datetime.  The bit after ParseExact takes the string, splits it at the period - which gives a two element array, one before and one after the period - uses [0] to select the first element of that new array and uses the string yyyyMMddHHmmss to define which parts of that string to use as each element of the datetime.  Case is important here, for example, hh signifies hours in a 12-hour clock but HH signifies them in a 24-hour clock.  If you use the wrong case, you're going to get the wrong time.  There's great info on that here.  What we're left with is a variable called $date which holds the date and time the snapshot was created.  Perfect.  How do we find the drive though?  We use the VolumeName attribute, which looks like this.

\\?\Volume{aja08123-6c44-12e2-7693-006021310173}\

That actually looks worse than what we started with for the InstallDate!  This is an identifier for the drive but it doesn't really turn us anything.  I'd much rather see a drive letter here.  Fortunately, we can turn this ID into a drive letter using another wmi class, Win32_Volume.  Get-WMIObject Win32_Volume -ComputerName $server returns a list of all volumes present on $sever.  There's a lot of info in this class but only two properties I'm interested in here, DeviceID and Name.  DeviceID holds the same value that we get back from the VolumeName attribute in Win32_ShadowCopy and Name holds the relevant drive letter.  Perfect.  Actually, there are two other attributes you can use to get the driveletter, both DriveLetter and Caption.  Any of these three will work although the actual DriveLetter parameter does not include the trailing backslash.  Take your pick.  What we can do though, is create a hashtable then go through each result returned from Win32_Volume and add both the DeviceID and Name to the hashtable.

$volumes = @{}
$allvolumes = Get-WmiObject win32_volume -ComputerName $server -Property DeviceID, Name
foreach ($v in $allvolumes)
    {
        $volumes.add($v.DeviceID, $v.Name)
    }
$volumes

This is cool.  Now to return a drive letter, we only need to use the following.

$driveletters.Item($VolumeName)

How easy is that?  We give it the VolumeName we get back from Win32_ShadowCopy and it gives us the drive letter.  So now we need to wrap all this in a loop to iterate through all the shadow copies and find the date/time they were created and which drive they were created on.  That'll look something like this.

There are *lots* of improvements to be made here.  Error checking is non-existent, the output format could be tidied up and this really should be scheduled and emailed but it's a good place to start.

No comments: