On Windows 10, trying to use the CreateVirtualDisk API to create a virtual disk, fails and returns error code 87.
Complete minimal reproducible example.
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Winapi.Windows;
type
// Identifiers for virtual storage types and providers
VIRTUAL_STORAGE_TYPE = record
DeviceId: ULONG; // VIRTUAL_STORAGE_TYPE_DEVICE_xxx
VendorId: TGUID; // VIRTUAL_STORAGE_TYPE_VENDOR_xxx
end;
PVIRTUAL_STORAGE_TYPE = ^VIRTUAL_STORAGE_TYPE;
const
VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT: TGUID = '{EC984AEC-A0F9-47e9-901F-71415A66345B}';
VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN: TGUID = '{00000000-0000-0000-0000-000000000000}';
type
// Version definitions
CREATE_VIRTUAL_DISK_VERSION = (
CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0,
CREATE_VIRTUAL_DISK_VERSION_1 = 1
);
// Versioned CreateVirtualDisk parameter structure
CREATE_VIRTUAL_DISK_PARAMETERS_V1 = record
Version: CREATE_VIRTUAL_DISK_VERSION;
UniqueId: TGUID;
MaximumSize: ULONGLONG;
BlockSizeInBytes: ULONG;
SectorSizeInBytes: ULONG;
ParentPath: LPCWSTR;
SourcePath: LPCWSTR;
end;
PCREATE_VIRTUAL_DISK_PARAMETERS = Pointer;
const
VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN = 0; //Device type is unknown or not valid.
VIRTUAL_STORAGE_TYPE_DEVICE_ISO = 1; //CD or DVD image file device type. (.iso file) Windows 7 and Windows Server 2008 R2: This value is not supported before Windows 8 and Windows Server 2012.
VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2; //Virtual hard disk device type. (.vhd file)
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3; //VHDX format virtual hard disk device type. (.vhdx file) Windows 7 and Windows Server 2008 R2: This value is not supported before Windows 8 and Windows Server 2012.
type
VIRTUAL_DISK_ACCESS_MASK = (
VIRTUAL_DISK_ACCESS_NONE = $00000000,
VIRTUAL_DISK_ACCESS_ATTACH_RO = $00010000,
VIRTUAL_DISK_ACCESS_ATTACH_RW = $00020000,
VIRTUAL_DISK_ACCESS_DETACH = $00040000,
VIRTUAL_DISK_ACCESS_GET_INFO = $00080000,
VIRTUAL_DISK_ACCESS_CREATE = $00100000,
VIRTUAL_DISK_ACCESS_METAOPS = $00200000,
VIRTUAL_DISK_ACCESS_READ = $000d0000,
VIRTUAL_DISK_ACCESS_ALL = $003f0000,
VIRTUAL_DISK_ACCESS_WRITABLE = $00320000
);
// Flags for CreateVirtualDisk
CREATE_VIRTUAL_DISK_FLAG = (
CREATE_VIRTUAL_DISK_FLAG_NONE = $00000000, // i.e. dynamically expanding disk
CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = $00000001 // Pre-allocate all physical space necessary for the virtual size of the disk (e.g. a fixed VHD).
);
function CreateVirtualDisk(
{in} VirtualStorageType: PVIRTUAL_STORAGE_TYPE;
{in} Path: PWideChar;
{in} VirtualDiskAccessMask: VIRTUAL_DISK_ACCESS_MASK;
{in_opt} SecurityDescriptor: PSECURITY_DESCRIPTOR;
{in} Flags: CREATE_VIRTUAL_DISK_FLAG;
{in} ProviderSpecificFlags: ULONG;
{in} Parameters: PCREATE_VIRTUAL_DISK_PARAMETERS;
{in_opt} Overlapped: POverlapped;
out Handle: THandle
): DWORD; stdcall; external 'VirtDisk.dll';
procedure CreateVhd(Path: UnicodeString; FileSizeBytes: Int64);
var
storageType: VIRTUAL_STORAGE_TYPE;
parameters: CREATE_VIRTUAL_DISK_PARAMETERS_V1;
vhdHandle: THandle;
res: DWORD;
begin
// Specify UNKNOWN for both device and vendor so the system will use the file extension to determine the correct VHD format.
storageType.DeviceId := VIRTUAL_STORAGE_TYPE_DEVICE_VHD; //VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN;
storageType.VendorId := VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT; //VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN;
parameters := Default(CREATE_VIRTUAL_DISK_PARAMETERS_V1);
parameters.Version := CREATE_VIRTUAL_DISK_VERSION_1;
parameters.UniqueId := TGuid.NewGuid;
parameters.MaximumSize := FileSizeBytes;
parameters.BlockSizeInBytes := 0; //CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_BLOCK_SIZE;
parameters.SectorSizeInBytes := 512; //CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_SECTOR_SIZE;
parameters.ParentPath := nil;
parameters.SourcePath := nil;
res := CreateVirtualDisk(
@storageType,
PWideChar(Path),
VIRTUAL_DISK_ACCESS_NONE,
nil, // default security descriptor
CREATE_VIRTUAL_DISK_FLAG_NONE, // dynamically expanding disk
0,
@parameters,
nil, //not overlapped
{out}vhdHandle);
if res <> ERROR_SUCCESS then
begin
RaiseLastOSError(res);
Exit;
end;
CloseHandle(vhdHandle);
end;
begin
try
CreateVhd('C:\test.vhd', 15*1024*1024); //15 MB
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
WriteLn('Press enter to close...');
ReadLn;
end.
Obviously running as administrator makes no difference.
@RbMm's first comment to the question points where to look for and how to solve the problem. He states that the c++ translation does not reproduce the problem. Then the problem must be with the translation of the header (virtdisk.h). The comment even states that the translation from Delphi might not be accurate.
Quickly browsing the code for common translation errors we come across enums. With explicitly assigned values (largest one being 3 bytes) the first one (VIRTUAL_DISK_ACCESS_MASK) is good, the compiler will use 4 bytes here.
The next one is problematic:
CREATE_VIRTUAL_DISK_FLAG = (
CREATE_VIRTUAL_DISK_FLAG_NONE = $00000000, // i.e. dynamically expanding disk
CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION = $00000001 // Pre-allocate all physical space necessary for the virtual size of the disk (e.g. a fixed VHD).
Being conservative about enumeration sizes, the compiler will use 1 byte for this type. That will cause a binary mismatch with the exported function (CreateVirtualDisk
), hence 87 (ERROR_INVALID_PARAMETER).
You can use {$Z4}
before the declaration for this part.
Testing shows you also need to account for the other advice in the same comment, namely using VIRTUAL_DISK_ACCESS_NONE
. This causes a 5 in my test, which is ERROR_ACCESS_DENIED. I can create the disk with VIRTUAL_DISK_ACCESS_ALL
, like the comment advises.
More testing shows using the root of the root drive for the virtual disk might not be a very good idea, which is mentioned in this comment. My test with 'C:\test.vhd' succeeded but I can't find this file. Using another writable directory, I have no problem locating the file.