Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CreateVirtualDisk gives error 87 (The parameter is incorrect.)

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.

Bonus Reading

  • CreateVirtualDisk function
  • CREATE_VIRTUAL_DISK_PARAMETERS structure
  • What are the correct parameters for CREATE_VIRTUAL_DISK_VERSION_2?
  • Windows SDK Sample: CreateVirtualDisk
  • VirtDisk.h (Windows 7 era; i don't need anything newer)
  • Someone else's translation of VirtDisk.pas
like image 458
Ian Boyd Avatar asked Dec 31 '19 16:12

Ian Boyd


Video Answer


1 Answers

@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.

like image 178
Sertac Akyuz Avatar answered Oct 05 '22 15:10

Sertac Akyuz