Copying a file with bad blocks

Jan 2, 2024  

I needed to copy some huge files, used by VMware Workstation, to a new machine. I used the Windows Explorer and simply dragged the folder to the new destination. However, after a few minutes the copy aborted with following error:

File copy error

I launched the Computer Management console and opened Windows Logs/System under the Event Viewer node. Looking at the event log confirmed that my disk has some bad blocks:

The device, \Device\Harddisk0\DR0, has a bad block

I confirmed this by running chkdsk /scan /r C:, however, after rebooting and attempting a repair, the error was still preventing me from copying the huge file.

Force Copy to the rescue

Looking for a solution, I came across an old post from Davor Josipovic (June 2013). Davor had written a PowerShell script which tries to copy a file, skipping the bad blocks and replacing them with zeroes if multiple read attempts don’t succeed.

I downloaded the Force-Copy.ps1 file and copied it to my C:\Tools folder. As explained by Davor, trying to execute it directly would fail with following message:

C:\Tools\Force-Copy.ps1 : File C:\Tools\Force-Copy.ps1 cannot be loaded.
The file C:\Tools\Force-Copy.ps1 is not digitally signed. You cannot
run this script on the current system. For more information about running
scripts and setting execution policy, see about_Execution_Policies at

However, rather than changing the execution policy globally, I prefer to unblock just that file:

Unblock-File -Path C:\Tools\Force-Copy.ps1

Copying the damaged file

Now that I unblocked the file without changing the execution policy of my system, I can run the script to copy the damaged file:

C:\Tools\Force-Copy.ps1 \
  -SourceFilePath "Build (S)-000003.vmdk" \
  -DestinationFilePath "D:\VMs\Build (S)-000003.vmdk"

I tried using the -BufferSize 33554432 -BufferGranularSize 4096 arguments to speed up the whole operation by a factor of 5, however the copy did not work as advertised and produced a hole of 32MB in the file, rather than discarding just 4096 bytes.

The -BufferSize 33554432 argument tells the script to attempt to read chunks of 32MB each, and -BufferGranularSize 4096 ensures that the reads will be done with the minimal cluster size once when an error is encountered. This makes the whole process much faster.