Created
March 20, 2019 01:22
-
-
Save jborean93/05b7605a0269ac37c8d829ce65247875 to your computer and use it in GitHub Desktop.
Enumerate Shadow Copies and mount them
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @' | |
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
[return: MarshalAs(UnmanagedType.I1)] | |
public static extern bool CreateSymbolicLinkW( | |
string lpSymlinkFileName, | |
string lpTargetFileName, | |
UInt32 dwFlags); | |
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
public static extern bool RemoveDirectoryW( | |
string lpPathName); | |
'@ | |
Function Get-LastWin32ExceptionMessage { | |
param([int]$ErrorCode) | |
$Exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode | |
$ExpMsg = "{0} (Win32 ErrorCode {1} - 0x{1:X8})" -f $Exp.Message, $ErrorCode | |
return $ExpMsg | |
} | |
Function Get-ShadowCopy { | |
[OutputType('ShadowCopyInfo')] | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[System.String] | |
$Path | |
) | |
$CommonParameters = @{} | |
$PathUri = [System.Uri]$Path | |
if ($PathUri.IsUnc) { | |
$CommonParameters.ComputerName = $PathUri.DnsSafeHost | |
$ShareName = $Path.Split('\', 3, [System.StringSplitOptions]::RemoveEmptyEntries)[1] | |
$ShareParameters = @{ | |
ClassName = "Win32_Share" | |
Filter = "Name='$($ShareName)'" | |
Property = @("Path") | |
} | |
$ShareParameters += $CommonParameters | |
$Share = Get-CimInstance @ShareParameters | |
if (-not $Share) { | |
Write-Error -Message "Failed to find share '$($ShareName)' for path '$Path'" | |
return | |
} | |
$PathRoot = [System.IO.Path]::GetPathRoot($Share.Path) | |
} else { | |
$PathRoot = [System.IO.Path]::GetPathRoot($Path) | |
} | |
$VolumeParameters = @{ | |
ClassName = "Win32_Volume" | |
Filter = "DriveLetter='$($PathRoot.Substring(0, $PathRoot.Length - 1))'" | |
Property = @("DeviceID", "Name") | |
} | |
$Volume = Get-CimInstance @VolumeParameters | |
if (-not $Volume) { | |
Write-Error -Message "Failed to find volume with the drive '$PathRoot' for path '$Path'" | |
return | |
} | |
$ShadowParameters = @{ | |
ClassName = "Win32_ShadowCopy" | |
Filter = "VolumeName='$($Volume.DeviceID.Replace('\', '\\'))'" | |
Property = @("DeviceObject", "InstallDate", "OriginatingMachine") | |
} | |
$ShadowParameters += $CommonParameters | |
$ShadowCopies = Get-CimInstance @ShadowParameters | |
if (-not $ShadowCopies) { | |
Write-Error -Message "Failed to find shadow copies for volume '$($Volume.DeviceID)' for path '$Path'" | |
return | |
} | |
foreach ($ShadowCopy in $ShadowCopies) { | |
[PSCustomObject]@{ | |
PSTypeName = "ShadowCopyInfo" | |
Path = $Path | |
DeviceObject = $ShadowCopy.DeviceObject | |
DateModified = $ShadowCopy.InstallDate | |
VolumeName = $Volume.Name | |
ComputerName = $ShadowCopy.OriginatingMachine | |
} | |
} | |
} | |
Function Mount-ShadowCopy { | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[PSTypeName('ShadowCopyInfo')] | |
$ShadowCopy, | |
[System.String] | |
$MountPath, | |
[Switch] | |
$Force | |
) | |
if (-not $MountPath) { | |
$LeafName = "VSS Mount" | |
$MountPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $LeafName | |
} | |
$MountPath = "{0} ({1})" -f $MountPath, $ShadowCopy.DateModified.ToString("O").Replace(":", "_") | |
if (Test-Path -LiteralPath $MountPath) { | |
if ($Force) { | |
# Remove-Item fails if the symlink is broken, use PInvoke to bypass this | |
$Result = [Win32.NativeMethods]::RemoveDirectoryW($MountPath) | |
if (-not $Result) { | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "RemoveDirectoryW('$MountPath') failed - $Msg" | |
return | |
} | |
} else { | |
Write-Error -Message "Failed to mount shadow copy for $($ShadowCopy.Path) to $MountPath as the path already exists" | |
return | |
} | |
} | |
# The link target must end with "\" or else we cannot access it | |
$Result = [Win32.NativeMethods]::CreateSymbolicLinkW($MountPath, $ShadowCopy.DeviceObject + "\", 1) | |
if (-not $Result) { | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "CreateSymbolicLinkW('$MountPath', '$($ShadowCopy.DeviceObject)') failed - $Msg" | |
} | |
return $MountPath | |
} | |
Function Using-ShadowCopy { | |
<# | |
.SYNOPSIS | |
Automatically mounts and dismounts a shadow copy object for use in the PowerShell script. | |
.PARAMETER ShadowCopy | |
The ShadowCopyInfo object to mount, this can be retrieved with the Get-ShadowCopy cmdlet. This will only work for | |
shadow copies local to the system. | |
.PARAMETER Script | |
A script to execute in the mounted context. The path can be accessed with $MountedPath | |
.EXAMPLE | |
$ShadowCopies = Get-ShadowCopy -Path "C:\" | |
foreach ($ShadowCopy in $ShadowCopies) { | |
Using-ShadowCopy $ShadowCopy { | |
Get-ChildItem -LiteralPath $MountedPath | |
} | |
} | |
#> | |
Param ( | |
[Parameter(Mandatory = $true, Position = 0)] | |
[PSTypeName('ShadowCopyInfo')] | |
$ShadowCopy, | |
[Parameter(Mandatory = $true, Position = 1)] | |
[ScriptBlock] | |
$Script | |
) | |
$MountPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.IO.Path]::GetRandomFileName()) | |
try { | |
$MountedPath = Mount-ShadowCopy -ShadowCopy $ShadowCopy -MountPath $MountPath | |
&$Script | |
} finally { | |
$Result = [Win32.NativeMethods]::RemoveDirectoryW($MountedPath) | |
if (-not $Result) { | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "RemoveDirectoryW('$MountedPath') failed - $Msg" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment