Skip to content

Instantly share code, notes, and snippets.

@jborean93
Created March 20, 2019 01:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jborean93/64d021dc363f820015db1fe326d101f6 to your computer and use it in GitHub Desktop.
Save jborean93/64d021dc363f820015db1fe326d101f6 to your computer and use it in GitHub Desktop.
Use DeviceIoControl to enumerate shadow copies
<#
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