Introduction
If you want to automatically make backup copies of locked files, such as Microsoft Outlook PST files, you'll find that your options seem limited to dedicated backup utilities such as Microsoft Backup. But what if you want to backup only your PST files, and you want the backup copy to be immediately accessible, not compressed in a BKF file?
Microsoft Volume Shadow Copy Service is the solution to this problem. It's how Microsoft Backup and other backup utilities are able to make copies of locked files.
There's a Volume Shadow Copy service SDK and documentation on MSDN but it is by no means clear how to implement VSC using .NET, or even if it's possible. With a little help from Craig Andera's blog, I have found that it is, and I show you how, here below.
Background
When I first started looking into Volume Shadow Copy, I wasn't really sure where to begin. I thought I might need to create and register a provider. But then one of the commenters to Craig Andera's HOBOCOPY blog posting referenced a VSSCoordinatorClass
which turned out to be exactly what I needed.
The VSSCoordinatorClass
doesn't seem to be documented. If I Google it, I get the original blog comment plus one or two other unofficial and not particularly helpful references. MSDN doesn't say anything about it either. This class does seem to expose the methods of the IVssBackupComponents
interface, which is what you need to create shadow copies.
In particular, to create a volume shadow copy, here are the steps you need to take:
- Create a snapshot set using
StartSnapshotSet
- Add a volume to the snapshot set using
AddToSnapshotSet
- Initialize the volume snapshot using the
DoSnapshotSet
- Wait until
QueryStatus
indicates that the snapshot has been initialized - Get the volume name of the snapshot using
GetSnapshotProperties
- Use the
FindFile
and CopyFile
APIs to access the shadow volume - When done, use the
DeleteSnapshots
method to indicate that the shadow volume is no longer needed
Using the code
The attached source code is a simple utility that scans a source directory and any subdirectories for PST files. Any PST files found are copied to a target directory.
To use the utility, extract the files from the Zip file. Run makeshadowcopy.exe with the following parameters:
- -F: indicates the "from" directory. I.e.: C:\Documents and Settings\ttyree\Local Settings\Application Data\Microsoft\Outlook
- -T: indicates the "to" directory. I.e.: C:\tedsoutlookbackup
Note that these two Windows services are, by default, set to "manual", and will need to be started for Volume Shadow Copy to work:
- Volume Shadow Copy service
- MS Software Shadow Copy Provider
Much of the code in the sample utility is standard, and I won't try to explain how the FindFirstFile
and CopyFile
APIs are used. I did encapsulate the Volume Shadow Copy as a class and am posting it here for your perusal:
The class is instantiated with a volume name (e.g.: "C:" or "E:"), and once instantiated, the DeviceName
property will return the name of the shadow volume. When done with the shadow volume, release it using the Delete
method.
Public Class Snapshot
Private Const VSS_E_BAD_STATE = &H80042301
Private Const VSS_E_PROVIDER_ALREADY_REGISTERED = &H80042303
Private Const VSS_E_PROVIDER_NOT_REGISTERED = &H80042304
Private Const VSS_E_PROVIDER_VETO = &H80042306
Private Const VSS_E_PROVIDER_IN_USE = &H80042307
Private Const VSS_E_OBJECT_NOT_FOUND = &H80042308
Private Const VSS_S_ASYNC_PENDING = &H42309
Private Const VSS_S_ASYNC_FINISHED = &H4230A
Private Const VSS_S_ASYNC_CANCELLED = &H4230B
Private Const VSS_E_VOLUME_NOT_SUPPORTED = &H8004230C
Private Const VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER = &H8004230E
Private Const VSS_E_OBJECT_ALREADY_EXISTS = &H8004230D
Private Const VSS_E_UNEXPECTED_PROVIDER_ERROR = &H8004230F
Private Const VSS_E_CORRUPT_XML_DOCUMENT = &H80042310
Private Const VSS_E_INVALID_XML_DOCUMENT = &H80042311
Private Const VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED = &H80042312
Private Const VSS_E_FLUSH_WRITES_TIMEOUT = &H80042313
Private Const VSS_E_HOLD_WRITES_TIMEOUT = &H80042314
Private Const VSS_E_UNEXPECTED_WRITER_ERROR = &H80042315
Private Const VSS_E_SNAPSHOT_SET_IN_PROGRESS = &H80042316
Private Const VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED = &H80042317
Private Const VSS_E_WRITER_INFRASTRUCTURE = &H80042318
Private Const VSS_E_WRITER_NOT_RESPONDING = &H80042319
Private Const VSS_E_WRITER_ALREADY_SUBSCRIBED = &H8004231A
Private Const VSS_E_UNSUPPORTED_CONTEXT = &H8004231B
Private Const VSS_E_VOLUME_IN_USE = &H8004231D
Private Const VSS_E_MAXIMUM_DIFFAREA_ASSOCIATIONS_REACHED = &H8004231E
Private Const VSS_E_INSUFFICIENT_STORAGE = &H8004231F
Private Const VSS_E_NO_SNAPSHOTS_IMPORTED = &H80042320
Private Const VSS_S_SOME_SNAPSHOTS_NOT_IMPORTED = &H42320
Private moSnapshotSetID As New Guid
Private moSnapshotID As New Guid
Private msDeviceName As String
Public Sub New(ByVal sVolume As String)
Dim vss As New VSS.VSSCoordinatorClass
Abort(vss)
MakeNewSnapshot(vss, sVolume, moSnapshotSetID, moSnapshotID)
msDeviceName = GetSnapshotDeviceName(vss, moSnapshotID)
End Sub
Public ReadOnly Property DeviceName() As String
Get
Return msDeviceName
End Get
End Property
Private Sub MakeNewSnapshot( _
ByRef vss As VSS.VSSCoordinatorClass, _
ByVal sVolume As String, _
ByRef output_SnapShotSetID As Guid, _
ByRef output_SnapShotID As Guid)
vss.StartSnapshotSet(output_SnapShotSetID)
vss.AddToSnapshotSet(sVolume, Guid.Empty, output_SnapShotID)
Dim ovssAsync As VSS.IVssAsync = Nothing
Dim oCallBack As Object = Nothing
vss.DoSnapshotSet(oCallBack, ovssAsync)
Do While (True)
Dim hr As Integer = 0
Dim x As Integer = 0
ovssAsync.QueryStatus(hr, x)
Console.Write(".")
If hr = VSS_S_ASYNC_FINISHED Then
Exit Do
End If
Thread.Sleep(1000)
Loop
Console.WriteLine()
End Sub
Private Function GetSnapshotDeviceName( _
ByRef vss As VSS.VSSCoordinatorClass, _
ByVal oSnapShotID As Guid) As String
Dim sDeviceName As String = ""
Dim osnapshotProps As New VSS._VSS_SNAPSHOT_PROP
vss.GetSnapshotProperties(oSnapShotID, osnapshotProps)
sDeviceName = osnapshotProps.m_pwszSnapshotDeviceObject
Return sDeviceName
End Function
Private Sub Abort( _
ByRef vss As VSS.VSSCoordinatorClass)
vss.AbortAllSnapshotsInProgress()
End Sub
Public Sub Delete()
Dim iCountOfDeletedSnapshots As Integer = 0
Dim oNonDeletedSnapshotID As New Guid
Dim vss As New VSS.VSSCoordinatorClass
vss.DeleteSnapshots( _
moSnapshotSetID, _
_VSS_OBJECT_TYPE.VSS_OBJECT_SNAPSHOT_SET, _
True, _
iCountOfDeletedSnapshots, _
oNonDeletedSnapshotID)
End Sub
End Class
Possible improvements
There's a lot that could be done here, and top of the list is better error handling. Only one shadow volume can be open at a time, and if any sort of error is encountered while the app is attempting to initialize the shadow volume, it will crash with an ugly error message. Handling this and other likely errors would be a great step forward.
Other improvements relate more to the scope of the PST backup utility. Since I included this utility mostly as a demo of what Volume Shadow Copies are good for, such improvements are beyond the scope of this project.