4

So i'm essentially trying to retrieve the information in the screenshot but with PowerShell. Kind of getting stumped at the first hurdle.

enter image description here

Best i can find is https://superuser.com/questions/643536/how-to-find-and-open-previous-versions-of-a-folder-programmatically-using-power

But it's not really suited.

Been reading up on https://msdn.microsoft.com/en-us/library/aa393625(v=vs.85).aspx

But I'm not able to make much sense of it. Is that the right path to be going down?

Conan1989
  • 328
  • 1
  • 4
  • 8

5 Answers5

2

There are a few of steps in PowerShell to get to browsing shadow copies. First, below code will display a list of drives and their shadow copies

$shadowStorageList = @();
$volumeList = Get-WmiObject Win32_Volume -Property SystemName,DriveLetter,DeviceID,Capacity,FreeSpace -Filter "DriveType=3" | select @{n="DriveLetter";e={$_.DriveLetter.ToUpper()}},DeviceID,@{n="CapacityGB";e={([math]::Round([int64]($_.Capacity)/1GB,2))}},@{n="FreeSpaceGB";e={([math]::Round([int64]($_.FreeSpace)/1GB,2))}} | Sort DriveLetter;
$shadowStorages = gwmi Win32_ShadowStorage -Property AllocatedSpace,DiffVolume,MaxSpace,UsedSpace,Volume |
                Select @{n="Volume";e={$_.Volume.Replace("\\","\").Replace("Win32_Volume.DeviceID=","").Replace("`"","")}},
                @{n="DiffVolume";e={$_.DiffVolume.Replace("\\","\").Replace("Win32_Volume.DeviceID=","").Replace("`"","")}},
                @{n="AllocatedSpaceGB";e={([math]::Round([int64]($_.AllocatedSpace)/1GB,2))}},
                @{n="MaxSpaceGB";e={([math]::Round([int64]($_.MaxSpace)/1GB,2))}},
                @{n="UsedSpaceGB";e={([math]::Round([int64]($_.UsedSpace)/1GB,2))}}

# Create an array of Customer PSobject
foreach($shStorage in $shadowStorages) {
    $tmpDriveLetter = "";
    foreach($volume in $volumeList) {
        if($shStorage.DiffVolume -eq $volume.DeviceID) {
            $tmpDriveLetter = $volume.DriveLetter;
        }
    }
    $objVolume = New-Object PSObject -Property @{
        Volume = $shStorage.Volume
        AllocatedSpaceGB = $shStorage.AllocatedSpaceGB
        UsedSpaceGB = $shStorage.UsedSpaceGB
        MaxSpaceGB = $shStorage.MaxSpaceGB
        DriveLetter = $tmpDriveLetter
    }
    $shadowStorageList += $objVolume;
}


for($i = 0; $i -lt $shadowStorageList.Count; $i++){
    $objCopyList = Get-WmiObject Win32_ShadowCopy  | Where-Object {$_.VolumeName -eq $shadowStorageList[$i].Volume} | select DeviceObject, InstallDate
    $shadowStorageList[$i] | add-member Noteproperty shadowcopies $objCopyList
    $shadowStorageList[$i]
}

Sample output:

AllocatedSpaceGB : 9.17 DriveLetter : F: Volume : \?\Volume{6c974bfe-0525-11e7-80bf-0050568007f5}\ MaxSpaceGB : 15 UsedSpaceGB : 8.46 shadowcopies : {@{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy39; InstallDate=20170902070009.648986+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy40; InstallDate=20170903070009.902376+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy41; InstallDate=20170904070016.340573+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy42; InstallDate=20170904120031.644419+600}...}

AllocatedSpaceGB : 6.28 DriveLetter : C: Volume : \?\Volume{4c22f9da-2b50-11e6-80b3-806e6f6e6963}\ MaxSpaceGB : 6.96 UsedSpaceGB : 5.78 shadowcopies : {@{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy3; InstallDate=20170921070020.298687+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy4; InstallDate=20170921120026.126738+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5; InstallDate=20170922070025.309517+600}, @{DeviceObject=\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy6; InstallDate=20170922120004.852824+600}...}

To browse a shadow copy (example GLOBALROOT\Device\HarddiskVolumeShadowCopy6), you need to create a symbolic link to it (windows shortcut) which you can then browse in Windows explorer. Example code below:

# Load assembly to create symlink
try {
    $null = [mklink.symlink]
}
catch {
Add-Type @"
    using System;
    using System.Runtime.InteropServices;

  namespace mklink
    {
    public class symlink
    {
        [DllImport("kernel32.dll")]
        public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
    }
  }
"@
}
# create symlink
[mklink.symlink]::CreateSymbolicLink('symlink path (example C:\temp\link1)', '\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy4\', 1);
Iggy Zofrin
  • 515
  • 3
  • 10
1

Accessing Volume Shadow Copy (VSS) Snapshots from powershell has some further information.

There is also a utility called vssadmin which appears to be the native utility. This link uses vssadmin and select-string to get the information. https://p0w3rsh3ll.wordpress.com/2014/06/21/mount-and-dismount-volume-shadow-copies/

Community
  • 1
  • 1
Tim Haintz
  • 628
  • 1
  • 6
  • 9
1

Though it may not be an exact solution to the OP's question I'm adding this to several related posts in the hopes it will help someone else, who, like me, needed a way to list all snapshots off a remote path. Searching for this was agony and I was about to give up until I found the answer.

I searched and searched for the ability to list previous versions programmatically.. could not find a solution for viewing previous versions on an SMB / CIFS share. Volrest, vssadmin, alphaVss, etc.. round and round.. even win32_shadowCopy failed, because our target machines are netapps. Nothing worked.

Then I fount this posting says they can use SMB commands in Perl to view them. https://mark.clow.es/?blog/2018/02/listing-shadow-copies

If Perl can do it, surely some winAPI could also. FSCTL_SRV_ENUMERATE_SNAPSHOTS is the SMB command needed. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/bffc70f9-b16a-453b-939a-0b6d3c9263af

Finally found this genius ( God bless kenjiuno ) who created a dll for .NET: https://github.com/HiraokaHyperTools/LibEnumRemotePreviousVersion

After adding reference to kenjiuno's dll, I called it:

Dim s() as String = LibEnumRemotePreviousVersion.PreviousversionOnRemote("\\server\share")

it returned all previous versions as @GMT-blablabla. Then all you need to do is append the one you want to the end of your UNC path.

No mklink, no mapping drives, none of it.. everything works exactly and as simply as it should have if Microsoft would have pulled their head out and put this in .Net themselves.

halfer
  • 19,824
  • 17
  • 99
  • 186
mark
  • 11
  • 1
0

Shadow copies are not stored on a per-folder basis. It's a per-volume basis. You can exclude things from being shadow-copied on that volume by setting registry keys in HKLM\system\CurrentControlSet\Control\BackupRestore\FilesNotToSnapshot. You'll see examples there already. Be aware of the "/s" parameter which indicates to recurse sub-directories. Also note that you can't put wildcards in the middle of the path. They can only be at the end. Below is my script to enumerate the copies with their associated information.

$SnapshotState=@("VSS_SS_UNKNOWN","VSS_SS_PREPARING","VSS_SS_PROCESSING_PREPARE","VSS_SS_PREPARED","VSS_SS_PROCESSING_PRECOMMIT","VSS_SS_PRECOMMITTED","VSS_SS_PROCESSING_COMMIT","VSS_SS_COMMITTED","VSS_SS_PROCESSING_POSTCOMMIT","VSS_SS_PROCESSING_PREFINALCOMMIT","VSS_SS_PREFINALCOMMITTED","VSS_SS_PROCESSING_POSTFINALCOMMIT","VSS_SS_CREATED","VSS_SS_ABORTED","VSS_SS_DELETED","VSS_SS_POSTCOMMITTED","VSS_SS_COUNT")
$Volumes=Get-Volume
$ShadowProvider=Get-WmiObject -Namespace "root\cimv2" -Class "Win32_ShadowProvider" | Select-Object -Property ID,Name
$ShadowOn=Get-WmiObject -Namespace "root\cimv2" -Class "Win32_ShadowOn" | Select-Object -Property Dependent,Antecedent
$ShadowOn | ForEach-Object { $_.Dependent=$_.Dependent.Replace("Win32_ShadowCopy.ID=",""); $_.Antecedent=$_.Antecedent.Replace("Win32_Volume.DeviceID=",""); $_.Dependent=$_.Dependent.Replace('"',""); $_.Antecedent=$_.Antecedent.Replace('"',""); $_.Antecedent=$_.Antecedent.Replace("\\","\") }
$ShadowCopy=Get-WmiObject -Namespace "root\cimv2" -Class "Win32_ShadowCopy"
$ShadowCopy=@($ShadowCopy)
for ($i=0; $i -lt $ShadowCopy.Count; $i++) {
  $DiffVolume=$ShadowOn | Where-Object { $_.Dependent -eq $ShadowCopy[$i].ID } | Select-Object -ExpandProperty Antecedent
  $DiffVolume=$Volumes | Where-Object { $_.Path -eq $DiffVolume } | Select-Object -ExpandProperty DriveLetter
  $ShadowCopy[$i] | Add-Member -MemberType NoteProperty -Name "DiffVolume" -Value $DiffVolume
  $DriveLetter=$Volumes | Where-Object { $_.Path -eq $ShadowCopy[$i].VolumeName } | Select-Object -ExpandProperty DriveLetter
  $ShadowCopy[$i] | Add-Member -MemberType NoteProperty -Name "Volume" -Value $DriveLetter
  $ShadowCopy[$i] | Add-Member -MemberType NoteProperty -Name "strState" -Value $SnapshotState[$ShadowCopy[$i].State]
  $ShadowCopy[$i] | Add-Member -MemberType NoteProperty -Name "CreateDate" -Value ([System.Management.ManagementDateTimeConverter]::ToDateTime($ShadowCopy[$i].InstallDate))
  $ShadowCopy[$i].PSObject.Properties.Remove("VolumeName")
  $ShadowCopy[$i] | Add-Member -MemberType NoteProperty -Name "Provider" -Value ($ShadowProvider | Where-Object { $_.ID -eq $ShadowCopy[$i].ProviderID } | Select-Object -ExpandProperty Name)
  $ShadowCopy[$i].PSObject.Properties.Remove("ProviderID")
}
$ShadowCopy | Select-Object Count,CreateDate,Volume,DiffVolume,strState,MaxSpace,UsedSpace,AllocatedSpace,Persistent,Differential,ClientAccessible,NoAutoRelease,NoWriters,ExposedLocally,ExposedRemotely,NotSurfaced,Transportable,Provider | Sort-Object -Property Count
0

After many tries and Copy-Pasting code samples from all over the internet, I come up with this solution, maybe somebody will find it useful.

      function Get_Snapshots
            {
            param (
                [ValidateScript({
                        if (-Not ($_ | Test-Path -PathType Container))
                        {
                            throw "Not a Diretory"
                        }       
                        return $true
                    })]
                [System.IO.FileInfo]$Path
            )
            
            
                if (-not ("Snapshot.GetSnapshot" -as [type]))
                {
                    Add-Type -Language VisualBasic  @"
                    Imports System
                    Imports System.Collections.Generic
                    Imports System.IO
                    Imports System.Runtime.InteropServices
                    Namespace Snapshot
                        Public Class GetSnapshot
                            <StructLayout(LayoutKind.Sequential)> Structure IOSTATUSBLOCK
                                Public Status As Integer
                                Public Information As Integer
                            End Structure
                            Declare Function NtFsControlFile Lib "ntdll" (ByVal FileHandle As Microsoft.Win32.SafeHandles.SafeFileHandle, ByVal Evennt As IntPtr, ByVal ApcRoutine As IntPtr, ByVal ApcContext As IntPtr, ByRef IoStatusBlock As IOSTATUSBLOCK, ByVal FsControlCode As Integer,  ByVal InputBuffer() As Byte,  ByVal InputBufferLength As Integer,  ByVal OutputBuffer() As Byte,  ByVal OutputBufferLength As Integer) As IntPtr
                            Declare Auto Function CreateFile Lib "kernel32.dll" (ByVal lpFileName As String,ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, ByVal lpSecurityAttributes As IntPtr, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As IntPtr)As Microsoft.Win32.SafeHandles.SafeFileHandle
                        End Class
                    End Namespace   
            
"@
            }
                try{
                    $NtStatus = [System.Byte[]]::CreateInstance([System.Byte], 64000)
                    $NtStatusClean = [System.Byte[]]::CreateInstance([System.Byte], 64000)
                
                    $Filehandle = [Snapshot.GetSnapshot]::CreateFile([ref]$Path, 129, 1, [System.IntPtr]::Zero, 3, 33554432, [System.IntPtr]::Zero)     
                    
                    $objIOStatusBlock = [Activator]::CreateInstance([Snapshot.GetSnapshot+IOSTATUSBLOCK])
                                                                                
                    $intServerResponse = [Snapshot.GetSnapshot]::NtFsControlFile($Filehandle, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [ref]$objIOStatusBlock, 1327204, $null, 0, $NtStatus, $NtStatus.Length);
                        
                    
                    $Filehandle.Dispose()
                        
                    if ($intServerResponse -ne 0)
                    {
                        return ("Error request was not successfull: " + $intServerResponse)
                    }
                    
                    [System.Array]::Copy($NtStatus, 12, $NtStatusClean, 0, ($NtStatusClean.Length - 12))    
                            
                    $Snapshots = ([System.Text.Encoding]::Unicode.GetString($NtStatusClean) -split "`0" | Where-Object { $_ -ne "" })
                    $Snapshots
                }
                catch 
                {
                    $Error[0]  
                }
             }

Example: Get_Snapshots h:\P12

@GMT-2021.08.05-08.30.01
@GMT-2021.08.04-14.00.08

After that I can do a "get-childitem h:\P12\@GMT-20xxx "

Gyula Kokas
  • 141
  • 6