1

Good evening all. I am hoping that the community maybe able to help in my little C# programming issue. I will say here that I am very new to programming in C# and that it is a very steep learning curve from Borland Pascal!

I am currently trying to use the Virtual Disk API (as mentioned on here on the MSDN Website), translating to c# (hopefully keeping within the managed context).

I have managed to get OpenVirtualDisk() to accept the first parameter, but it fails on the second (file path of the said ISO that I would like to open and attach.) From a lot of research I am declaring the param "Path" as string, to allow the CLR to best fit with the function signature.

The Application is currently being run under elivated rights (via Visual Studio 2017)

The following is my dllImport:

        [DllImport("VirtDisk.dll", EntryPoint = "OpenVirtualDisk", CharSet = CharSet.Ansi, ThrowOnUnmappableChar = true ,SetLastError = true)]
        public extern static Int64 OpenVirtualDisk(
                                               [In] ref _VIRTUAL_STORAGE_TYPE PVIRTUAL_STORAGE_TYPE,
                                               string Path,
                                               _VIRTUAL_DISK_ACCESS_MASK LVIRTUAL_DISK_ACCESS_MASK,
                                               long OPEN_VIRTUAL_DISK_FLAG,
                                               [In, Optional] ref OPEN_VIRTUAL_DISK_PARAMTERS POPEN_VIRTUAL_DISK_PARAMTERS,
                                               ref IntPtr pHandle);

All other constants, enums and Structs are declared as provided by VirtDisk.h, can be provided if required.

Here is all the code I am using to call OpenVirtualDisk (as you can see, I am using File.Exists() to ensure that the file exists before starting any work):

        private void button1_Click(object sender, EventArgs e)
        {
          IntPtr pHandle = IntPtr.Zero;
          Int64 RetVal = 0;
          string VirtualDiskPath = @"F:\Images\Windows10.ISO";
          _VIRTUAL_DISK_ACCESS_MASK VIRTUAL_DISK_ACCESS;
          Int64 VIRTUAL_DISK_FLAG;
          _VIRTUAL_STORAGE_TYPE VIRTUAL_STORAGE_TYPE;

        if (File.Exists(VirtualDiskPath))
        {

            VIRTUAL_DISK_ACCESS = _VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_ATTACH_ALL;
            VIRTUAL_DISK_FLAG = OPEN_VIRTUAL_DISK_FLAG_NONE;

            VIRTUAL_STORAGE_TYPE.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_ISO;
            VIRTUAL_STORAGE_TYPE.VendorId = VENDORMICROSOFT;

            IntPtr VirtualStorPtr = IntPtr.Zero;
            VirtualStorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(VIRTUAL_STORAGE_TYPE));
            Marshal.StructureToPtr(VIRTUAL_STORAGE_TYPE, VirtualStorPtr, true);

            IntPtr VirtualDiskVer = IntPtr.Zero;
            OPEN_VIRTUAL_DISK_PARAMTERS OPEN_VIRTUAL_DISK_PARAM;
            OPEN_VIRTUAL_DISK_PARAM = new OPEN_VIRTUAL_DISK_PARAMTERS
            {
                version = OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_1,
                version1 = new OPEN_VIRTUAL_DISK_PARAMTERS1
                {
                    RWDepth = 0
                }
            };

            VirtualDiskVer = Marshal.AllocHGlobal(Marshal.SizeOf(OPEN_VIRTUAL_DISK_PARAM));
            Marshal.StructureToPtr(OPEN_VIRTUAL_DISK_PARAM, VirtualDiskVer, true);

            RetVal = OpenVirtualDisk(ref VIRTUAL_STORAGE_TYPE,
                                     VirtualDiskPath,
                                     VIRTUAL_DISK_ACCESS,
                                     VIRTUAL_DISK_FLAG,
                                     ref OPEN_VIRTUAL_DISK_PARAM,
                                     ref pHandle);
            if ((RetVal == 0))
            {
                MessageBox.Show("OpenVirtualDisk() has succeded in opening the file: \r\n" +
                                VirtualDiskPath + "\r\nWith the file Handle: " + pHandle.ToString());
            }
            else
            {
                MessageBox.Show("OpenVirtualDisk() failed to open the file: " +
                                VirtualDiskPath + ",\r\nWith Error Code: " + Marshal.GetLastWin32Error().ToString() +
                                "\r\nReturn Value: " + RetVal.ToString());
            }
            Marshal.FreeHGlobal(VirtualStorPtr);

        }
    }

Every attempt so far has either had a RetVal of 87 "Invalid Parameter" with GetLastWin32Error reporting 1008 "Invalid Token", or RetVal of 2 "File Not Found" with GetLastWin32Error reporting the same.

Can anyone spot where I am going wrong?

Thanks in advance.

Richie

N.B this is part of a recovery suite that I have been building in c#, based on the Windows Imaging API (Wimgapi.h). This is to allow installing Windows (any version as long as it is 64bit) if so desired. It will run on WinPE (Windows 10, .Net Version 5.4.2) I have even tried using FileIOPermssion to allow that process full access to the file.

The fault has been sorted, as below comments. The FILE_ACCESS_MASK needed to be changed to FILE_ACCESS_MASK_RO, but the DLLimport declaration also needed to be adapted. The DLLImport now reads:

[DllImport("virtdisk.dll" CharSet = CharSet.UNICODE, ThrowOnUnmappableChar = true ,SetLastError = true)] public extern static Int64 OpenVirtualDisk( [In] ref _VIRTUAL_STORAGE_TYPE PVIRTUAL_STORAGE_TYPE, string Path, [In] _VIRTUAL_DISK_ACCESS_MASK LVIRTUAL_DISK_ACCESS_MASK, [In] long OPEN_VIRTUAL_DISK_FLAG, [In, Optional] ref OPEN_VIRTUAL_DISK_PARAMTERS POPEN_VIRTUAL_DISK_PARAMTERS, [In] ref IntPtr pHandle);

1 Answers1

1

I have opened virtual ISOs before and I will post my code for you:

public static class NativeMethods
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Warning is bogus.")]
    [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
    public static extern Int32 OpenVirtualDisk(ref VIRTUAL_STORAGE_TYPE VirtualStorageType, 
        string Path, 
        VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, 
        OPEN_VIRTUAL_DISK_FLAG Flags, 
        ref OPEN_VIRTUAL_DISK_PARAMETERS Parameters, 
        ref VirtualDiskSafeHandle Handle);

    public static readonly Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B");
    public const int OPEN_VIRTUAL_DISK_RW_DEPTH_DEFAULT = 1;

    public const Int32 ERROR_SUCCESS = 0;
    public const Int32 ERROR_FILE_CORRUPT = 1392;
    public const Int32 ERROR_FILE_NOT_FOUND = 2;
    public const Int32 ERROR_PATH_NOT_FOUND = 3;
    public const Int32 ERROR_ACCESS_DENIED = 5;

    /// CD or DVD image file device type. (.iso file)
    /// </summary>
    public const int VIRTUAL_STORAGE_TYPE_DEVICE_ISO = 1;

    /// <summary>
    /// Device type is unknown or not valid.
    /// </summary>
    public const int VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN = 0;

    /// <summary>
    /// Virtual hard disk device type. (.vhd file)
    /// </summary>
    public const int VIRTUAL_STORAGE_TYPE_DEVICE_VHD = 2;

    /// <summary>
    /// VHDX format virtual hard disk device type. (.vhdx file)
    /// </summary>
    public const int VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 3;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct VIRTUAL_STORAGE_TYPE
    {
        /// <summary>
        /// Device type identifier.
        /// </summary>
        public Int32 DeviceId; //ULONG

        /// <summary>
        /// Vendor-unique identifier.
        /// </summary>
        public Guid VendorId; //GUID
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct OPEN_VIRTUAL_DISK_PARAMETERS
    {
        /// <summary>
        /// An OPEN_VIRTUAL_DISK_VERSION enumeration that specifies the version of the OPEN_VIRTUAL_DISK_PARAMETERS structure being passed to or from the VHD functions.
        /// </summary>
        public OPEN_VIRTUAL_DISK_VERSION Version; //OPEN_VIRTUAL_DISK_VERSION

        /// <summary>
        /// A structure.
        /// </summary>
        public OPEN_VIRTUAL_DISK_PARAMETERS_Version1 Version1;
    }

    public enum OPEN_VIRTUAL_DISK_VERSION : int
    {
        /// <summary>
        /// </summary>
        OPEN_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0,

        /// <summary>
        /// </summary>
        OPEN_VIRTUAL_DISK_VERSION_1 = 1
    }

    [Flags]
    public enum VirtualDiskAccessMask : int
    {
        /// <summary>
        /// Open the virtual disk for read-only attach access. The caller must have READ access to the virtual disk image file. If used in a request to open a virtual disk that is already open, the other handles must be limited to either VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail.
        /// </summary>
        AttachReadOnly = 0x00010000,
        /// <summary>
        /// Open the virtual disk for read-write attaching access. The caller must have (READ | WRITE) access to the virtual disk image file. If used in a request to open a virtual disk that is already open, the other handles must be limited to either VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail. If the virtual disk is part of a differencing chain, the disk for this request cannot be less than the RWDepth specified during the prior open request for that differencing chain.
        /// </summary>
        AttachReadWrite = 0x00020000,
        /// <summary>
        /// Open the virtual disk to allow detaching of an attached virtual disk. The caller must have (FILE_READ_ATTRIBUTES | FILE_READ_DATA) access to the virtual disk image file.
        /// </summary>
        Detach = 0x00040000,
        /// <summary>
        /// Information retrieval access to the VHD. The caller must have READ access to the virtual disk image file.
        /// </summary>
        GetInfo = 0x00080000,
        /// <summary>
        /// VHD creation access.
        /// </summary>
        Create = 0x00100000,
        /// <summary>
        /// Open the VHD to perform offline meta-operations. The caller must have (READ | WRITE) access to the virtual disk image file, up to RWDepth if working with a differencing chain. If the VHD is part of a differencing chain, the backing store (host volume) is opened in RW exclusive mode up to RWDepth.
        /// </summary>
        MetaOperations = 0x00200000,
        /// <summary>
        /// Allows unrestricted access to the VHD. The caller must have unrestricted access rights to the virtual disk image file.
        /// </summary>
        All = 0x003f0000,
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct OPEN_VIRTUAL_DISK_PARAMETERS_Version1
    {
        /// <summary>
        /// Indicates the number of stores, beginning with the child, of the backing store chain to open as read/write. The remaining stores in the differencing chain will be opened read-only. This is necessary for merge operations to succeed.
        /// </summary>
        public Int32 RWDepth; //ULONG
    }

    public enum OPEN_VIRTUAL_DISK_FLAG : int
    {
        /// <summary>
        /// No flag specified.
        /// </summary>
        OPEN_VIRTUAL_DISK_FLAG_NONE = 0x00000000,

        /// <summary>
        /// Open the backing store without opening any differencing-chain parents. Used to correct broken parent links.
        /// </summary>
        OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x00000001,

        /// <summary>
        /// Reserved.
        /// </summary>
        OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x00000002,

        /// <summary>
        /// Reserved.
        /// </summary>
        OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x00000004
    }

    public enum VIRTUAL_DISK_ACCESS_MASK : int
    {
        /// <summary>
        /// Open the virtual disk for read-only attach access. The caller must have READ access to the virtual disk image file. If used in a request to open a virtual disk that is already open, the other handles must be limited to either VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail.
        /// </summary>
        VIRTUAL_DISK_ACCESS_ATTACH_RO = 0x00010000,

        /// <summary>
        /// Open the virtual disk for read-write attaching access. The caller must have (READ | WRITE) access to the virtual disk image file. If used in a request to open a virtual disk that is already open, the other handles must be limited to either VIRTUAL_DISK_ACCESS_DETACH or VIRTUAL_DISK_ACCESS_GET_INFO access, otherwise the open request with this flag will fail. If the virtual disk is part of a differencing chain, the disk for this request cannot be less than the RWDepth specified during the prior open request for that differencing chain.
        /// </summary>
        VIRTUAL_DISK_ACCESS_ATTACH_RW = 0x00020000,

        /// <summary>
        /// Open the virtual disk to allow detaching of an attached virtual disk. The caller must have (FILE_READ_ATTRIBUTES | FILE_READ_DATA) access to the virtual disk image file.
        /// </summary>
        VIRTUAL_DISK_ACCESS_DETACH = 0x00040000,

        /// <summary>
        /// Information retrieval access to the VHD. The caller must have READ access to the virtual disk image file.
        /// </summary>
        VIRTUAL_DISK_ACCESS_GET_INFO = 0x00080000,

        /// <summary>
        /// VHD creation access.
        /// </summary>
        VIRTUAL_DISK_ACCESS_CREATE = 0x00100000,

        /// <summary>
        /// Open the VHD to perform offline meta-operations. The caller must have (READ | WRITE) access to the virtual disk image file, up to RWDepth if working with a differencing chain. If the VHD is part of a differencing chain, the backing store (host volume) is opened in RW exclusive mode up to RWDepth.
        /// </summary>
        VIRTUAL_DISK_ACCESS_METAOPS = 0x00200000,

        /// <summary>
        /// Reserved.
        /// </summary>
        VIRTUAL_DISK_ACCESS_READ = 0x000d0000,

        /// <summary>
        /// Allows unrestricted access to the VHD. The caller must have unrestricted access rights to the virtual disk image file.
        /// </summary>
        VIRTUAL_DISK_ACCESS_ALL = 0x003f0000,

        /// <summary>
        /// Reserved.
        /// </summary>
        VIRTUAL_DISK_ACCESS_WRITABLE = 0x00320000
    }

}

[SecurityPermission(SecurityAction.Demand)]
public class VirtualDiskSafeHandle : SafeHandle
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Warning is bogus.")]
    [DllImportAttribute("kernel32.dll", SetLastError = true)]
    [return: MarshalAsAttribute(UnmanagedType.Bool)]
    public static extern Boolean CloseHandle(IntPtr hObject);

    public VirtualDiskSafeHandle()
        : base(IntPtr.Zero, true)
    {
    }

    public override bool IsInvalid
    {
        get { return (this.IsClosed) || (base.handle == IntPtr.Zero); }
    }

    public override string ToString()
    {
        return this.handle.ToString();
    }

    protected override bool ReleaseHandle()
    {
        return CloseHandle(handle);
    }

    public IntPtr Handle
    {
        get { return handle; }
    }
}

To use it, create a method like this:

private VirtualDiskSafeHandle OpenIso(string fileName, NativeMethods.VirtualDiskAccessMask fileAccessMask)
{
    var parameters = new NativeMethods.OPEN_VIRTUAL_DISK_PARAMETERS();
    parameters.Version = NativeMethods.OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_1;
    parameters.Version1.RWDepth = NativeMethods.OPEN_VIRTUAL_DISK_RW_DEPTH_DEFAULT;

    var storageType = new NativeMethods.VIRTUAL_STORAGE_TYPE();
    storageType.DeviceId = NativeMethods.VIRTUAL_STORAGE_TYPE_DEVICE_ISO;
    storageType.VendorId = NativeMethods.VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;

    fileAccessMask = ((fileAccessMask & NativeMethods.VirtualDiskAccessMask.GetInfo) == NativeMethods.VirtualDiskAccessMask.GetInfo) ? NativeMethods.VirtualDiskAccessMask.GetInfo : 0;
    fileAccessMask |= NativeMethods.VirtualDiskAccessMask.AttachReadOnly;

    VirtualDiskSafeHandle handle = new VirtualDiskSafeHandle();

    int res = NativeMethods.OpenVirtualDisk(ref storageType, fileName,
        (NativeMethods.VIRTUAL_DISK_ACCESS_MASK) fileAccessMask,
        NativeMethods.OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NONE, ref parameters, ref handle);

    if (res == NativeMethods.ERROR_SUCCESS)
    {
        return handle;
    }
    else
    {
        handle.SetHandleAsInvalid();
        if ((res == NativeMethods.ERROR_FILE_NOT_FOUND) || (res == NativeMethods.ERROR_PATH_NOT_FOUND))
        {
            throw new FileNotFoundException("File not found.");
        }
        else if (res == NativeMethods.ERROR_ACCESS_DENIED)
        {
            throw new IOException("Access is denied.");
        }
        else if (res == NativeMethods.ERROR_FILE_CORRUPT)
        {
            throw new InvalidDataException("File type not recognized.");
        }
        else
        {
            throw new Win32Exception(res);
        }
    }
}

And then throughout your program, you simple can do this:

VirtualDiskSafeHandle handle = OpenIso(@"c:\IsoLocation\Isoname.iso", NativeMethods.VirtualDiskAccessMask.All);
MessageBox.Show($"Handle = {handle.Handle}");

Hope this helps!!

Icemanind
  • 47,519
  • 50
  • 171
  • 296
  • Thank you Icemanind, Implmented as a second button and works a treat in my testbed application.Where did I go astray - other than not encapsulating everything in it's own class? – R.McInnes-Piper May 16 '18 at 21:06
  • @R.McInnes-Piper I am thinking that because it's an ISO and ISOs are read-only, you might have had to set the `VIRTUAL_DISK_ACCESS_MASK` to read only or something. Otherwise, it will try to open it up for write access, which is invalid for ISOs – Icemanind May 17 '18 at 00:30
  • Found it! I changed the VIRTUAL_DISK_ACCESS_MASK to RO, and it failed still. The error seemed to be in my DLLImport declaration. I removed the DllImport(..., EntryPoint = "OpenVirtualDisk" and changed the CharSet to CharSet = CharSet.Unicode,. It worked! Seems to be a hangover from Borland Pascal days... – R.McInnes-Piper May 17 '18 at 06:32