Created
March 20, 2019 01:44
-
-
Save jborean93/64d021dc363f820015db1fe326d101f6 to your computer and use it in GitHub Desktop.
Use DeviceIoControl to enumerate shadow copies
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
<# | |
This does not work due to the unsupported CTL_CODE used in DeviceIoControl | |
DeviceIoControl() get buffer size failed - Incorrect function (Win32 ErrorCode 1 - 0x00000001) | |
At C:\temp\enumerate_snapshots.ps1:145 char:1 | |
+ Get-ShadowCopy -Path "\\localhost\c$" | |
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException | |
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-ShadowCopy | |
#> | |
Add-Type -TypeDefinition @' | |
using Microsoft.Win32.SafeHandles; | |
using System; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
using System.Security.AccessControl; | |
namespace Win32 | |
{ | |
public class NativeHelpers | |
{ | |
// Usually define the structs for the output value. DeviceIoControl lpOutBuffer would typically | |
// align to one of these structures but I couldn't find the docs for FSCTL_SRV_ENUMERATE_SNAPSHOTS | |
[StructLayout(LayoutKind.Sequential)] | |
public struct SNAPSHOT_STRUCT | |
{ | |
} | |
} | |
public class NativeMethods | |
{ | |
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
public static extern SafeFileHandle CreateFileW( | |
string lpFileName, | |
FileSystemRights dwDesiredAccess, | |
FileShare dwShareMode, | |
IntPtr lpSecurityAttributes, | |
FileMode dwCreationDisposition, | |
UInt32 dwFlagsAndAttributes, | |
IntPtr hTemplateFile); | |
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
public static extern bool DeviceIoControl( | |
SafeFileHandle hDevice, | |
UInt32 dwIoControlCode, | |
IntPtr lpInBuffer, | |
UInt32 nInBufferSize, | |
IntPtr lpOutBuffer, | |
UInt32 nOutBufferSize, | |
out UInt32 lpBytesReturned, | |
IntPtr lpOverlapped); | |
} | |
} | |
'@ | |
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 { | |
param ( | |
[Parameter(Mandatory = $true)] | |
[System.String] | |
$Path | |
) | |
# Not needed but I like to have human readable names instead of hex values when using them | |
$FSCTL_SRV_ENUMERATE_SNAPSHOTS = 0x00144064 | |
$FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 | |
# Create a SafeFileHandle of the path specified and make sure it is valid | |
$Handle = [Win32.NativeMethods]::CreateFileW( | |
$Path, | |
[System.Security.AccessControl.FileSystemRights]"ListDirectory, ReadAttributes, Synchronize", | |
[System.IO.FileShare]::ReadWrite, | |
[System.IntPtr]::Zero, | |
[System.IO.FileMode]::Open, | |
$FILE_FLAG_BACKUP_SEMANTICS, | |
[System.IntPtr]::Zero | |
) | |
if ($Handle.IsInvalid) { | |
$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "CreateFileW($Path) failed - $Msg" | |
return | |
} | |
try { | |
# Create an empty output buffer and get the size required | |
$OutBuffer = [System.IntPtr]::Zero | |
$OutBufferSize = 0 | |
$Result = [Win32.NativeMethods]::DeviceIoControl( | |
$Handle, | |
$FSCTL_SRV_ENUMERATE_SNAPSHOTS, | |
[System.IntPtr]::Zero, | |
0, | |
$OutBuffer, | |
$OutBufferSize, | |
[Ref]$OutBufferSize, | |
[System.IntPtr]::Zero | |
) | |
# If we didn't receive ERROR_INSUFFICIENT_BUFFER then fail, something else went wrong | |
$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if (-not $Result -and $LastError -ne 0x0000007A) { # ERROR_INSUFFICIENT_BUFFER | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "DeviceIoControl() get buffer size failed - $Msg" | |
return | |
} | |
### ANYTHING BELOW HERE IS UNTESTED BECAUSE OF THE INVALID FUNCTION ERROR ### | |
# Allocate the output buffer pointer with the size required | |
$OutBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($OutBufferSize) | |
try { | |
# Call DeviceIoControl once again but with out allocated output buffer | |
$Result = [Win32.NativeMethods]::DeviceIoControl( | |
$Handle, | |
$FSCTL_SRV_ENUMERATE_SNAPSHOTS, | |
[System.IntPtr]::Zero, | |
0, | |
$OutBuffer, | |
$OutBufferSize, | |
[Ref]$OutBufferSize, | |
[System.IntPtr]::Zero | |
) | |
if (-not $Result) { | |
$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
$Msg = Get-LastWin32ExceptionMessage -ErrorCode $LastError | |
Write-Error -Message "DeviceIoControl() failed - $Msg" | |
return | |
} | |
# Convert the pointer to the defined PInvoke structure. This could vary depending on the output buffer | |
# defined by the CTL CODE | |
$Snapshots = [System.Runtime.InteropServices.Marshal]::PtrToStructure($OutBuffer, | |
[Type][Win32.NativeHelpers.SNAPSHOT_STRUCT]) | |
# Now do what must be done with the output structure | |
} finally { | |
# Ensure we free the unmanaged memory | |
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($OutBuffer) | |
} | |
} finally { | |
# Technically not needed as a SafeFileHandle will auto dispose once the GC is called but it's good to be | |
# explicit about these things | |
$Handle.Dispose() | |
} | |
} | |
Get-ShadowCopy -Path "\\localhost\c$" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment