VB.Net: How To Display Previous Shadow Copy Versions of File Allowing User to Choose One

Dec 11, 2011 at 10:51 PM
Edited Dec 12, 2011 at 1:40 PM

This is a cross post from Stack Overflow, but I haven't gotten a response there yet. 

I'm writing an Excel file recovery program with VB.Net that tries to be a convenient place to gather and access Microsoft's recommended methods. If your interested in my probably kludgy, error filled, and lacking enough cleanup code it's here: http://pastebin.com/v4GgDteY. The basic functionality seems to work although I haven't tested graph macro table recovery yet.

It occurred to me that Vista and Windows 7 users could benefit from being offered a list of previous versions of the file within my application if the Shadow Copy Service is on and there are previous copies. How do I do this?

I looked at a lot of web pages but found no easy to crib code. One possibility I guess would be to use vssadmin via the shell but that is pretty cumbersome. I just want to display a dialogue box like the Previous Versions property sheet and allow users to pick one of the previous versions. I guess I could just display the previous version property sheet via the shell by programmatically invoking the context menu and the "Restore previous versions choice", however I also want to be able to offer the list for Vista Home Basic and Premium Users who don't have access to that tab even though apparently the previous versions still exist. 

I looked at MSDN on the Shadow Copy Service and went through all the pages, I also looked at AlphaVSS and AlphaFS and all the comments.  I'm kind of guessing that I need to use AlphaVss and AlphFS and do the following?

  1. Find out the list of snapshots/restore points that exist on the computer.
  2. Mount those snapshots.
  3. Navigate in the mounted volumes to the Excel file the user wants to recover and make a list of those paths.
  4. With the list of paths handy, compare with some kind of diff program, the shadow copies of the files with the original.
  5. Pull out the youngest or oldest version (I don't think it matters) of those shadow copies that differ from the recovery target.
  6. List those versions of the files that are found to be different.

This seems cumbersome and slow, but maybe is the fastest way to do things. I just need some confirmation that is the way to go now.

Dec 14, 2011 at 7:22 PM
Edited Dec 15, 2011 at 2:03 AM

I finally decided to go ahead and start coding. Please make suggestions for speeding up the code or what do with files that are found to be different from the recovery file target. Is there a simpler way to do this with AlphaVSS and AlphaFS?

Private Sub Button1_Click_2(sender As System.Object, e As System.EventArgs) Handles Button1.Click

    'Find out the number of vss shadow snapshots (restore 
All shadows apparently have a linkable path
'where # is a simple one or two or three digit integer. Dim objProcess As New Process() objProcess.StartInfo.UseShellExecute = False objProcess.StartInfo.RedirectStandardOutput = True objProcess.StartInfo.CreateNoWindow = True objProcess.StartInfo.RedirectStandardError = True objProcess.StartInfo.FileName() = "vssadmin" objProcess.StartInfo.Arguments() = "List Shadows" objProcess.Start() Dim burp As String = objProcess.StandardOutput.ReadToEnd Dim strError As String = objProcess.StandardError.ReadToEnd() objProcess.WaitForExit() Dim xnum As Integer = 0 Dim counterVariable As Integer = 1 ' Call Regex.Matches method. Dim matches As MatchCollection = Regex.Matches(burp, _
"HarddiskVolumeShadowCopy") ' Loop over matches. For Each m As Match In matches xnum = xnum + 1 'When it has reached it's maximum value, xnum + 1 'is finally the number of existing shadow snapshots. Next objProcess.Close() Do 'Here I make symbolic links to all the shadows, one at a time
'and loop through until all shadows are exposed as folders in C:\.
Dim myProcess As New Process() myProcess.StartInfo.FileName = "cmd.exe" myProcess.StartInfo.UseShellExecute = False myProcess.StartInfo.RedirectStandardInput = True myProcess.StartInfo.RedirectStandardOutput = True myProcess.StartInfo.CreateNoWindow = True myProcess.Start() Dim myStreamWriter As StreamWriter = myProcess.StandardInput myStreamWriter.WriteLine("mklink /d C:\shadow" & _counterVariable & _
" \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy" & _
counterVariable & "\") myStreamWriter.Close() myProcess.WaitForExit() myProcess.Close() ' Here I compare our recovery target file against the shadow copies. Dim sFile As String = PathTb.Text Dim sFileShadowPath As String = "C:\shadow" & counterVariable & _
DelFromLeft("C:", sFile) Dim jingle As New Process() jingle.StartInfo.FileName = "cmd.exe" jingle.StartInfo.UseShellExecute = False jingle.StartInfo.RedirectStandardInput = True jingle.StartInfo.RedirectStandardOutput = True jingle.StartInfo.CreateNoWindow = True jingle.Start() Dim jingleWriter As StreamWriter = jingle.StandardInput jingleWriter.WriteLine("fc """ & sFile & """ """ & _
sFileShadowPath & """") jingleWriter.Close() jingle.WaitForExit() Dim jingleReader As StreamReader = jingle.StandardOutput Dim JingleCompOut As String = jingleReader.ReadToEnd jingleReader.Close() jingle.WaitForExit() jingle.Close() Dim jingleBoolean As Boolean = JingleCompOut.Contains( _
"no differences encountered").ToString If jingleBoolean = "True" Then MsgBox(jingleBoolean) Else 'I haven't decided what to do with the paths of
'files that are different from the recovery target.
MsgBox("No") End If counterVariable = counterVariable + 1 Loop Until counterVariable = xnum + 1 End Sub
Dec 21, 2011 at 10:08 PM

Using AlphaVSS you can list the volumes using Alphaleonis.Filesystem.Volume.GetVolumes and GetVolumePathNamesForVolume

To emulate "vssadmin list shadows", generally it's like this:

Using y = CreateVssBackupComponents()
  For Each z In y.QuerySnapshots()
    'match z.OriginalVolumeName against the proper
    '  volume name matched in the GetVolumes command
    'store the z.SnapshotDeviceObject string to use later
End Using

Using AlphaFS you can manipulate and open files on these volumes.  I would simply list updated files based on a newer date stamp, but if you need byte-accuracy, you can open a data stream and compare the data within the .NET framework.

For high-speed data compare operations, you can use the following access to the Win32 API:

Declare Function IsArrayDifferent Lib "msvcrt.dll" Alias "memcmp" (ByVal byteArray1() As Byte, ByVal byteArray2() As Byte, ByVal bytesToCompare As Integer) As Boolean

Of course, it would be best if it was wrapped in a "safe" function to automatically determine the bytes to compare, and immediately return false if either array Is Nothing, or if the length of the arrays are different.