Я создал небольшое приложение для резервного копирования дисков и восстановления диска из резервной копии.Логика довольно проста:
- Вызов
CreateFile
на \\.\PhysicalDrive<index>
- Сделайте то же самое для физического диска назначения
- Вызов
DeviceIoControl
с FSCTL_LOCK_VOLUME
для источника + указатели назначения - вызов
DeviceIoControl
с FSCTL_DISMOUNT_VOLUME
для источника + указатели назначения - В цикле используйте
ReadFile
для чтения из источника и записи его в место назначения, используя WriteFile
пока не записаны все байты. - Вызов
DeviceIoControl
с FSCTL_UNLOCK_VOLUME
на дескрипторах источника и назначения - Вызов
CloseHandle
на дескрипторах источника и назначения
Я всегда проверяю, все ли операции выполнены успешно, прежде чем перейти к следующему вызову.Это работает очень хорошо, но я заметил кое-что немного озадачивающее:
Если во время копирования ОС пытается что-то сделать с физическим диском (который я знаю заблокирован), затем произойдет сбой следующего WriteFile
.
Примеры причин, которые приводят к сбою:
- Извлечение отдельного диска (например, извлечение USB-накопителя, не используемого в качествеисточник или место назначения)
- Открытие управления дисками
- В PowerShell выполнение
Get-Disk
Все это вызывает сбой следующего вызова WriteFile
,и я понятия не имею, почему.Согласно документации, которую я прочитал FSCTL_LOCK_VOLUME
, любой другой процесс не должен получать дескриптор диска, поэтому я не могу понять, почему эти вещи могут вызвать проблемы на заблокированном томе.Кто-нибудь может уточнить и предоставить способ обойти это?
Вот мой код: он написан на PowerShell, но его можно легко преобразовать в C # или что-то подобное.
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)"
}
}
}