I've created a small app to make backups of disks and to restore a disk from a backup. The logic is pretty simple:
CreateFile
on \\.\PhysicalDrive<index>
DeviceIoControl
with FSCTL_LOCK_VOLUME
on source + destination handlesDeviceIoControl
with FSCTL_DISMOUNT_VOLUME
on source + destination handlesReadFile
to read from source and write it to destination using WriteFile
until all bytes have been written.DeviceIoControl
with FSCTL_UNLOCK_VOLUME
on both source and destination handlesCloseHandle
on both source and destination handlesI always check that all operations are successful before continuing to the next call. This works really well, but I noticed something that is a bit puzzling:
If, while a copy is taking place, the OS tries to do something with the physical disk (which I know is locked) then the next WriteFile
fails.
Examples of things that cause this to fail:
Get-Disk
Those things all cause the very next WriteFile
call to fail, and I have no idea why. According to the documentation I read FSCTL_LOCK_VOLUME
should prevent any other process from acquiring a handle to the disk, so I can't understand why those things could cause issues on a locked volume. Can anyone elaborate and provide a way around it?
Here's my code: it's written in PowerShell but it could easily be converted to C# or something similar.
function Start-RawCopy
{
[CmdletBinding()]
param
(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FileAndFile')]
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='FileAndDisk')]
[System.IO.FileInfo]
$SourceFile,
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndFile')]
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndDisk')]
[UInt16]
$SourceDiskNumber,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='FileAndFile')]
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndFile')]
[System.IO.FileInfo]
$DestinationFile,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='FileAndDisk')]
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='DiskAndDisk')]
[UInt16]
$DestinationDiskNumber
)
Begin
{
$FSCTL_LOCK_VOLUME = 0x00090018
$FSCTL_UNLOCK_VOLUME = 0x0009001c
$FSCTL_DISMOUNT_VOLUME = 0x00090020
$DELETE = 0x00010000
$READ_CONTROL = 0x00020000
$WRITE_DAC = 0x00040000
$WRITE_OWNER = 0x00080000
$SYNCHRONIZE = 0x00100000
$STANDARD_RIGHTS_REQUIRED = 0x000F0000
$STANDARD_RIGHTS_READ = $READ_CONTROL
$STANDARD_RIGHTS_WRITE = $READ_CONTROL
$STANDARD_RIGHTS_EXECUTE = $READ_CONTROL
$FILE_READ_DATA = 0x0001 # file & pipe
$FILE_LIST_DIRECTORY = 0x0001 # directory
$FILE_WRITE_DATA = 0x0002 # file & pipe
$FILE_ADD_FILE = 0x0002 # directory
$FILE_APPEND_DATA = 0x0004 # file
$FILE_ADD_SUBDIRECTORY = 0x0004 # directory
$FILE_CREATE_PIPE_INSTANCE = 0x0004 # named pipe
$FILE_READ_EA = 0x0008 # file & directory
$FILE_WRITE_EA = 0x0010 # file & directory
$FILE_EXECUTE = 0x0020 # file
$FILE_TRAVERSE = 0x0020 # directory
$FILE_DELETE_CHILD = 0x0040 # directory
$FILE_READ_ATTRIBUTES = 0x0080 # all
$FILE_WRITE_ATTRIBUTES = 0x0100 # all
$FILE_ALL_ACCESS = $STANDARD_RIGHTS_REQUIRED -bor $SYNCHRONIZE -bor 0x1FF
$FILE_GENERIC_READ = $STANDARD_RIGHTS_READ -bor $FILE_READ_DATA -bor $FILE_READ_ATTRIBUTES -bor $FILE_READ_EA -bor $SYNCHRONIZE
$FILE_GENERIC_WRITE = $STANDARD_RIGHTS_WRITE -bor $FILE_WRITE_DATA -bor $FILE_WRITE_ATTRIBUTES -bor $FILE_WRITE_EA -bor $FILE_APPEND_DATA -bor $SYNCHRONIZE
$FILE_GENERIC_EXECUTE = $STANDARD_RIGHTS_EXECUTE -bor $FILE_READ_ATTRIBUTES -bor $FILE_EXECUTE -bor $SYNCHRONIZE
$FILE_SHARE_DELETE = 0x00000004
$FILE_SHARE_READ = 0x00000001
$FILE_SHARE_WRITE = 0x00000002
$CREATE_NEW = 1
$CREATE_ALWAYS = 2
$OPEN_EXISTING = 3
$OPEN_ALWAYS = 4
$TRUNCATE_EXISTING = 5
Add-Type -AssemblyName System.Core
}
Process
{
# Validate parameters
if ($PSBoundParameters.ContainsKey('SourceFile'))
{
if (!$SourceFile.Exists)
{
throw "Source file does not exist, cannot continue"
}
$source = $SourceFile.FullName
}
else
{
$source = "\\.\PhysicalDrive$SourceDiskNumber"
}
if ($PSBoundParameters.ContainsKey('DestinationFile'))
{
$destination = $DestinationFile.FullName
$creationDisposition = $CREATE_ALWAYS
}
else
{
$destination = "\\.\PhysicalDrive$DestinationDiskNumber"
$creationDisposition = $OPEN_EXISTING
}
try
{
# Start process
Write-Warning "Starting raw copy - $(Get-Date)"
# Open source object
$handleSource = [NativeMethods]::CreateFile($source, $FILE_GENERIC_READ, $FILE_SHARE_READ -bor $FILE_SHARE_WRITE, 0, $OPEN_EXISTING, 0, [IntPtr]::Zero)
if (!$handleSource)
{
throw "Failed to open source object to read"
}
# Clean destination volume
if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
{
Get-Disk -Number $DestinationDiskNumber | Get-Partition | Remove-Partition -Confirm:$false
# Without a sleep this will fail on the 5th WriteFile call all the time. Some caching issue?
Start-Sleep -Seconds 5
}
# Open destination object
$handleDestination = [NativeMethods]::CreateFile($destination, $FILE_ALL_ACCESS, $FILE_SHARE_READ -bor $FILE_SHARE_WRITE -bor $FILE_SHARE_DELETE, 0, $creationDisposition, 0, [IntPtr]::Zero)
if (!$handleDestination)
{
throw "Failed to open destination object to write"
}
$i = 0
# Lock and dismount source volume
if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
{
if (![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_LOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to lock source volume"
}
if (![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_DISMOUNT_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to dismount source volume"
}
}
# Lock and dismount destination volume
if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
{
if (![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_LOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to lock destination volume"
}
if (![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_DISMOUNT_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to dismount destination volume"
}
}
# Get source size
if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
{
$sourceSize = (Get-CimInstance -Namespace ROOT/Microsoft/Windows/Storage -Class MSFT_Disk -Filter "Number = $SourceDiskNumber").Size
}
else
{
$sourceSize = $SourceFile.Length
}
$buffer = [Byte[]]::new(32MB)
$bytesRead = [UInt32] 0
$bytesWritten = [UInt32] 0
[UInt64] $totalBytesRead = 0
# Copy data
do
{
if ($totalBytesRead + $buffer.Length -gt $sourceSize)
{
$buffer = [Byte[]]::new($sourceSize - $totalBytesRead)
if ($Validate)
{
$verifyBuffer = [Byte[]]::new($buffer.Length)
}
}
if (![NativeMethods]::ReadFile($handleSource, $buffer, $buffer.Length, [ref] $bytesRead, [IntPtr]::Zero))
{
throw "Failed to read from source"
}
if (![NativeMethods]::WriteFile($handleDestination, $buffer, $bytesRead, [ref] $bytesWritten, [IntPtr]::Zero))
{
throw "Failed to write to destination"
}
if ($bytesRead -ne $bytesWritten)
{
throw "Read $bytesRead bytes but only wrote $bytesWritten bytes"
}
$totalBytesRead += $bytesRead
} until ($totalBytesRead -eq $sourceSize)
}
finally
{
# Unlock source volume
if ($PSBoundParameters.ContainsKey('SourceDiskNumber'))
{
if ($handleSource -and ![NativeMethods]::DeviceIoControl($handleSource, $FSCTL_UNLOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to unlock source volume"
}
}
# Unlock destination volume
if ($PSBoundParameters.ContainsKey('DestinationDiskNumber'))
{
if ($handleDestination -and ![NativeMethods]::DeviceIoControl($handleDestination, $FSCTL_UNLOCK_VOLUME, $null, 0, $null, 0, [ref] $i, [IntPtr]::Zero))
{
throw "Failed to unlock destination volume"
}
}
# Close source handle
if ($handleSource -and ![NativeMethods]::CloseHandle($handleSource))
{
throw "Failed to close source object"
}
# Close destination handle
if ($handleDestination -and ![NativeMethods]::CloseHandle($handleDestination))
{
throw "Failed to close destination object"
}
Write-Warning "Ended raw copy - $(Get-Date)"
}
}
}
User contributions licensed under CC BY-SA 3.0