Retrieving virtual disk file name from disk number
Asked Answered
P

4

8

When I list virtual disks within diskpart:

DISKPART> list vdisk

  VDisk ###  Disk ###  State                 Type       File
  ---------  --------  --------------------  ---------  ----
  VDisk 0    Disk 2    Attached not open     Fixed       C:\Disk.vhd

Interesting part for me here is file name. I tried to find equivalent of function that would give me file name (under File column) if I know disk number.

Ideally, I would give "\\?\PhysicalDrive2" and I would get "C:\Disk.vhd" as result.

I already tried:

  1. Using diskpart and parsing output - since it is undocumented format, it can change at any time. This is not something I would rely on.
  2. General VHD API - no function takes disk number as parameter.
  3. Microsoft.Storage.Vds.dll - There are enumerations that go through each drive (e.g. Service.Providers) but there is no property/function that will give me name of source file. While I can now be sure that e.g. drive D: is virtual drive, I still cannot know which .vhd file was attached.

Any idea which function that might be?

Pean answered 2/5, 2010 at 23:36 Comment(1)
I came across this question whilst researching my own project. I know the question is old and has been answered to the asker's satisfaction, but I thought a VHD API solution would be useful.Delacroix
H
15

Here are two solutions to retrieve virtual disks on the local machine and to print their information. The two solutions demonstrate how to use VDS COM objects to access these data both in a native and managed way.

Managed Solution

I have create a partial COM Interop from the MSDN Documentation and from the Windows 7 SDK (mainly vds.h). Note that the COM wrappers are partial, which means that some methods are yet to be ported.

Below is a managed application that uses the .NET COM interop to:

  • Load the VDS service
  • Query for the Virtual Disk Providers
  • List all the Virtual Disk handled by each provider:
    • The virtual disk's properties gives its GUID, its full driver path, its volume size and its disk file (i.e. C:\Disk.vhd).
    • A virtual disk can be also be queried as a generic disk and gives its name (i.e. \\?\PhysicalDrive1), its friendly name and other properties.

using System;
using System.Runtime.InteropServices;

namespace VDiskDumper
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the service loader
            VdsServiceLoader loaderClass = new VdsServiceLoader();
            IVdsServiceLoader loader = (IVdsServiceLoader)loaderClass;
            Console.WriteLine("Got Loader");

            // Load the service
            IVdsService service;
            loader.LoadService(null, out service);
            Console.WriteLine("Got Service");

            // Wait for readyness
            service.WaitForServiceReady();
            Console.WriteLine("Service is ready");

            // Query for vdisk providers
            IEnumVdsObject providerEnum;
            service.QueryProviders(VDS_QUERY_PROVIDER_FLAG.VDS_QUERY_VIRTUALDISK_PROVIDERS, out providerEnum);
            Console.WriteLine("Got Providers");

            // Iterate
            while (true)
            {
                uint fetched;
                object unknown;
                providerEnum.Next(1, out unknown, out fetched);

                if (fetched == 0) break;

                // Cast to the required type
                IVdsVdProvider provider = (IVdsVdProvider)unknown;
                Console.WriteLine("Got VD Provider");

                Dump(provider);
            }

            Console.ReadKey();
        }

        private static void Dump(IVdsVdProvider provider)
        {
            // Query for the vdisks
            IEnumVdsObject diskEnum;
            provider.QueryVDisks(out diskEnum);
            Console.WriteLine("Got VDisks");

            // Iterate
            while (true)
            {
                uint fetched;
                object unknown;
                diskEnum.Next(1, out unknown, out fetched);

                if (fetched == 0) break;

                // Cast to the required type
                IVdsVDisk vDisk = (IVdsVDisk)unknown;

                // Get the vdisk properties
                VDS_VDISK_PROPERTIES vdiskProperties;
                vDisk.GetProperties(out vdiskProperties);

                Console.WriteLine("-> VDisk Id=" + vdiskProperties.Id);
                Console.WriteLine("-> VDisk Device Name=" + vdiskProperties.pDeviceName);
                Console.WriteLine("-> VDisk Path=" + vdiskProperties.pPath);

                // Get the associated disk
                IVdsDisk disk;
                provider.GetDiskFromVDisk(vDisk, out disk);

                // Get the disk properties
                VDS_DISK_PROP diskProperties;
                disk.GetProperties(out diskProperties);

                Console.WriteLine("-> Disk Name=" + diskProperties.pwszName);
                Console.WriteLine("-> Disk Friendly=" + diskProperties.pwszFriendlyName);
            }
        }
    }

    [ComImport, Guid("118610b7-8d94-4030-b5b8-500889788e4e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IEnumVdsObject
    {
        void Next(uint numberOfObjects, [MarshalAs(UnmanagedType.IUnknown)] out object objectUnk, out uint numberFetched);
        void Skip(uint NumberOfObjects);
        void Reset();
        void Clone(out IEnumVdsObject Enum);
    }

    [ComImport, Guid("07e5c822-f00c-47a1-8fce-b244da56fd06"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IVdsDisk
    {
        void GetProperties(out VDS_DISK_PROP diskProperties);
        void GetPack(); // Unported method
        void GetIdentificationData(IntPtr lunInfo);
        void QueryExtents(); // Unported method
        void slot4();
        void SetFlags(); // Unported method
        void ClearFlags(); // Unported method
    }

    [ComImport, Guid("0818a8ef-9ba9-40d8-a6f9-e22833cc771e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IVdsService
    {
        [PreserveSig]
        int IsServiceReady();
        [PreserveSig]
        int WaitForServiceReady();
        void GetProperties(); // Unported method
        void QueryProviders(VDS_QUERY_PROVIDER_FLAG mask, out IEnumVdsObject providers);
        void QueryMaskedDisks(out IEnumVdsObject disks);
        void QueryUnallocatedDisks(out IEnumVdsObject disks);
        void GetObject(); // Unported method
        void QueryDriveLetters(); // Unported method
        void QueryFileSystemTypes(out IntPtr fileSystemTypeProps, out uint numberOfFileSystems);
        void Reenumerate();
        void Refresh();
        void CleanupObsoleteMountPoints();
        void Advise(); // Unported method
        void Unadvise(); // Unported method
        void Reboot();
        void SetFlags(); // Unported method
        void ClearFlags(); // Unported method
    }

    [ComImport, Guid("e0393303-90d4-4a97-ab71-e9b671ee2729"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IVdsServiceLoader
    {
        void LoadService([In, MarshalAs(UnmanagedType.LPWStr)] string machineName, out IVdsService vdsService);
    }

    [ComImport, Guid("1e062b84-e5e6-4b4b-8a25-67b81e8f13e8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IVdsVDisk
    {
        void Open(); // Unported method
        void GetProperties(out VDS_VDISK_PROPERTIES pDiskProperties);
        void GetHostVolume(); // Unported method
        void GetDeviceName(); // Unported method
    }

    [ComImport, Guid("b481498c-8354-45f9-84a0-0bdd2832a91f"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IVdsVdProvider
    {
        void QueryVDisks(out IEnumVdsObject ppEnum);
        void CreateVDisk(); // Unported method
        void AddVDisk(); // Unported method
        void GetDiskFromVDisk(IVdsVDisk pVDisk, out IVdsDisk ppDisk);
        void GetVDiskFromDisk(IVdsDisk pDisk, out IVdsVDisk ppVDisk);
    }

    [ComImport, Guid("9c38ed61-d565-4728-aeee-c80952f0ecde")]
    public class VdsServiceLoader
    {
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct Signature
    {
        [FieldOffset(0)]
        public uint dwSignature;
        [FieldOffset(0)]
        public Guid DiskGuid;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct VDS_DISK_PROP
    {
        public Guid Id;
        public VDS_DISK_STATUS Status;
        public VDS_LUN_RESERVE_MODE ReserveMode;
        public VDS_HEALTH health;
        public uint dwDeviceType;
        public uint dwMediaType;
        public ulong ullSize;
        public uint ulBytesPerSector;
        public uint ulSectorsPerTrack;
        public uint ulTracksPerCylinder;
        public uint ulFlags;
        public VDS_STORAGE_BUS_TYPE BusType;
        public VDS_PARTITION_STYLE PartitionStyle;
        public Signature dwSignature;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pwszDiskAddress;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pwszName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pwszFriendlyName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pwszAdaptorName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pwszDevicePath;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct VIRTUAL_STORAGE_TYPE
    {
        public uint DeviceId;
        public Guid VendorId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct VDS_VDISK_PROPERTIES
    {
        public Guid Id;
        public VDS_VDISK_STATE State;
        public VIRTUAL_STORAGE_TYPE VirtualDeviceType;
        public ulong VirtualSize;
        public ulong PhysicalSize;
        [MarshalAs(UnmanagedType.LPWStr)]
        public String pPath;
        [MarshalAs(UnmanagedType.LPWStr)]
        public String pDeviceName;
        public DEPENDENT_DISK_FLAG DiskFlag;
        public bool bIsChild;
        [MarshalAs(UnmanagedType.LPWStr)]
        public String pParentPath;
    }

    public enum DEPENDENT_DISK_FLAG
    {
        DEPENDENT_DISK_FLAG_NONE = 0x00000000,
        DEPENDENT_DISK_FLAG_MULT_BACKING_FILES = 0x00000001,
        DEPENDENT_DISK_FLAG_FULLY_ALLOCATED = 0x00000002,
        DEPENDENT_DISK_FLAG_READ_ONLY = 0x00000004,
        DEPENDENT_DISK_FLAG_REMOTE = 0x00000008,
        DEPENDENT_DISK_FLAG_SYSTEM_VOLUME = 0x00000010,
        DEPENDENT_DISK_FLAG_SYSTEM_VOLUME_PARENT = 0x00000020,
        DEPENDENT_DISK_FLAG_REMOVABLE = 0x00000040,
        DEPENDENT_DISK_FLAG_NO_DRIVE_LETTER = 0x00000080,
        DEPENDENT_DISK_FLAG_PARENT = 0x00000100,
        DEPENDENT_DISK_FLAG_NO_HOST_DISK = 0x00000200,
        DEPENDENT_DISK_FLAG_PERMANENT_LIFETIME = 0x00000400,
    }

    public enum VDS_DISK_STATUS
    {
        VDS_DS_UNKNOWN = 0,
        VDS_DS_ONLINE = 1,
        VDS_DS_NOT_READY = 2,
        VDS_DS_NO_MEDIA = 3,
        VDS_DS_FAILED = 5,
        VDS_DS_MISSING = 6,
        VDS_DS_OFFLINE = 4
    }

    public enum VDS_HEALTH
    {
        VDS_H_UNKNOWN = 0,
        VDS_H_HEALTHY = 1,
        VDS_H_REBUILDING = 2,
        VDS_H_STALE = 3,
        VDS_H_FAILING = 4,
        VDS_H_FAILING_REDUNDANCY = 5,
        VDS_H_FAILED_REDUNDANCY = 6,
        VDS_H_FAILED_REDUNDANCY_FAILING = 7,
        VDS_H_FAILED = 8,
        VDS_H_REPLACED = 9,
        VDS_H_PENDING_FAILURE = 10,
        VDS_H_DEGRADED = 11
    }

    public enum VDS_LUN_RESERVE_MODE
    {
        VDS_LRM_NONE = 0,
        VDS_LRM_EXCLUSIVE_RW = 1,
        VDS_LRM_EXCLUSIVE_RO = 2,
        VDS_LRM_SHARED_RO = 3,
        VDS_LRM_SHARED_RW = 4
    }

    public enum VDS_PARTITION_STYLE
    {
        VDS_PST_UNKNOWN = 0,
        VDS_PST_MBR = 1,
        VDS_PST_GPT = 2
    }

    public enum VDS_QUERY_PROVIDER_FLAG
    {
        VDS_QUERY_SOFTWARE_PROVIDERS = 0x1,
        VDS_QUERY_HARDWARE_PROVIDERS = 0x2,
        VDS_QUERY_VIRTUALDISK_PROVIDERS = 0x4
    }

    public enum VDS_STORAGE_BUS_TYPE
    {
        VDSBusTypeUnknown = 0,
        VDSBusTypeScsi = 0x1,
        VDSBusTypeAtapi = 0x2,
        VDSBusTypeAta = 0x3,
        VDSBusType1394 = 0x4,
        VDSBusTypeSsa = 0x5,
        VDSBusTypeFibre = 0x6,
        VDSBusTypeUsb = 0x7,
        VDSBusTypeRAID = 0x8,
        VDSBusTypeiScsi = 0x9,
        VDSBusTypeSas = 0xa,
        VDSBusTypeSata = 0xb,
        VDSBusTypeSd = 0xc,
        VDSBusTypeMmc = 0xd,
        VDSBusTypeMax = 0xe,
        VDSBusTypeFileBackedVirtual = 0xf,
        VDSBusTypeMaxReserved = 0x7f
    }

    public enum VDS_VDISK_STATE
    {
        VDS_VST_UNKNOWN = 0,
        VDS_VST_ADDED,
        VDS_VST_OPEN,
        VDS_VST_ATTACH_PENDING,
        VDS_VST_ATTACHED_NOT_OPEN,
        VDS_VST_ATTACHED,
        VDS_VST_DETACH_PENDING,
        VDS_VST_COMPACTING,
        VDS_VST_MERGING,
        VDS_VST_EXPANDING,
        VDS_VST_DELETED,
        VDS_VST_MAX
    }
}

Native Solution

Below is a native application that uses the VDS COM interfaces to:

  • Load the VDS service
  • Query for the Virtual Disk Providers
  • List all the Virtual Disk handled by each provider:
    • The virtual disk's properties gives its GUID, its full driver path, its volume size and its disk file (i.e. C:\Disk.vhd).
    • A virtual disk can be also be queried as a generic disk and gives its name (i.e. \\?\PhysicalDrive1), its friendly name and other properties.

#include "initguid.h"
#include "vds.h"
#include <stdio.h>

#pragma comment( lib, "ole32.lib" )
#pragma comment( lib, "rpcrt4.lib" )

// Simple macro to release non-null interfaces.
#define _SafeRelease(x) {if (NULL != x) { x->Release(); x = NULL; } }

void exploreVDiskProvider(IVdsVdProvider *pVdProvider);

int __cdecl main(void) 
{
    HRESULT hResult;
    ULONG ulFetched = 0;
    BOOL bDone = FALSE;

    IVdsServiceLoader *pLoader = NULL;
    IVdsService *pService = NULL;
    IEnumVdsObject *pProviderEnum = NULL;
    IUnknown *pUnknown = NULL;
    IVdsVdProvider *pVdProvider = NULL;

    // Initialize COM
    hResult = CoInitialize(NULL);
    if (FAILED(hResult)) goto bail;

    // For this, get a pointer to the VDS Loader
    hResult = CoCreateInstance(CLSID_VdsLoader,
        NULL,
        CLSCTX_LOCAL_SERVER,
        IID_IVdsServiceLoader,
        (void **) &pLoader);
    if (FAILED(hResult)) goto bail;

    printf("Loading VDS Service...\n");

    // Launch the VDS service. 
    hResult = pLoader->LoadService(NULL, &pService);

    // We're done with the Loader interface at this point.
    _SafeRelease(pLoader); 

    if (FAILED(hResult)) goto bail;

    // Wait for service to be ready
    hResult = pService->WaitForServiceReady();
    if (FAILED(hResult)) goto bail;

    printf("VDS Service Loaded\n");

    // Query for virtual disk providers
    hResult = pService->QueryProviders(VDS_QUERY_VIRTUALDISK_PROVIDERS, &pProviderEnum);
    if (FAILED(hResult)) goto bail;

    printf("Querying providers...\n");

    // Iterate over virtual disk providers
    while(1) 
    {
        ulFetched = 0;
        hResult = pProviderEnum->Next(1, &pUnknown, &ulFetched);
        if (FAILED(hResult)) {
            break;
        }
        if (hResult == S_FALSE) {
            break;
        }

        // Cast the current value to a virtual disk provider
        hResult = pUnknown->QueryInterface(IID_IVdsVdProvider, (void **) &pVdProvider); 
        if (FAILED(hResult)) goto bail;

        printf("VDS Virtual Disk Provider Found\n");

        exploreVDiskProvider(pVdProvider);

        _SafeRelease(pVdProvider);

        _SafeRelease(pUnknown);
    }

    getchar();
    return 0;

bail:
    printf("Failed hr=%x\n", hResult);
    return 1;
}

void exploreVDiskProvider(IVdsVdProvider *pVdProvider) {
    HRESULT hResult;
    ULONG ulFetched = 0;

    IEnumVdsObject *pVDiskEnum = NULL;
    IVdsVDisk *pVDisk = NULL;
    IUnknown *pUnknown = NULL;
    IVdsVolume *pVolume = NULL;
    VDS_VDISK_PROPERTIES vdiskProperties = { 0 };
    TCHAR *uuid = NULL;
    IVdsDisk *pDisk = NULL;
    VDS_DISK_PROP diskProperties = { 0 };

    // Query the disks handled by the provider
    hResult = pVdProvider->QueryVDisks(&pVDiskEnum);
    if (FAILED(hResult)) goto bail;

    printf("Querying virtual disks...\n");

    // Iterate over virtual disks
    while(1) 
    {
        ulFetched = 0;
        hResult = pVDiskEnum->Next(1, &pUnknown, &ulFetched);
        if (hResult == S_FALSE) {
            break;
        }

        if (FAILED(hResult)) goto bail;

        // Cast the current value to a disk
        hResult = pUnknown->QueryInterface(IID_IVdsVDisk, (void **) &pVDisk);
        if (FAILED(hResult)) goto bail;

        printf("Virtual disk Found\n");

        // Get the disk's properties and display some of them
        hResult = pVDisk->GetProperties(&vdiskProperties);
        if (FAILED(hResult)) goto bail;

        // Convert the GUID to a string
        UuidToString(&vdiskProperties.Id, (RPC_WSTR *) &uuid);

        // Dump some properties
        printf("-> Disk Id=%ws\n", uuid);
        printf("-> Disk Device Name=%ws\n", vdiskProperties.pDeviceName);
        printf("-> Disk Path=%ws\n", vdiskProperties.pPath);

        // Get the disk instance from the virtual disk
        hResult = pVdProvider->GetDiskFromVDisk(pVDisk, &pDisk);
        if (FAILED(hResult)) goto bail;

        _SafeRelease(pVDisk);

        _SafeRelease(pUnknown);

        // Get the disk's properties and display some of them
        hResult = pDisk->GetProperties(&diskProperties);
        if (FAILED(hResult)) goto bail;

        printf("-> Disk Name=%ws\n", diskProperties.pwszName);
        printf("-> Disk Friendly Name=%ws\n", diskProperties.pwszFriendlyName);
    }

    return;

bail:
    printf("Failed hr=%x\n", hResult);
}
Hoashis answered 23/5, 2010 at 14:9 Comment(6)
I will check this. It will take me some time to rewrite it in C#.Pean
Microsoft.Storage.Vds.dll is missing IVdsVDisk. I assume that this was problem with my checks.Pean
@Josip: I add a managed solution based on a partial COM Interop.Hoashis
@LaurentEtiemble - Maybe I shouldn't comment on a 6+ year old answer but I guess I am anyways... You left out the call to CoUninitialize(); According to msdn.microsoft.com/en-us/library/windows/desktop/… - "To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize". Other than that, nice answer. +1.Pragmatics
Yep, you are right. If you own the stuff, you clean the stuff. Nice catch.Hoashis
@LaurentEtiemble Do you why I get no VDisk in the Dump method?Pee
D
3

P/Invoking the GetStorageDependencyInformation will provide a strictly VHD API solution. While this function does not take a drive number as an input parameter, a wrapper method will. The wrapper method converts a drive number to a string of the form "\\\\.\\PhysicalDriveN" which is passed to CreateFile and the resultant handle is passed to GetStorageDependencyInformation. A similar wrapper method will take an input of a single char drive letter.
The following code was translated to C# from an unmanaged example:

using DWORD = System.UInt32;
using ULONG = System.UInt32;

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;

namespace VhdTest
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] arr;
            arr = VirtualDisk.GetDependentVolumePaths('e');
            arr = VirtualDisk.GetDependentVolumePaths(1);

        }
    }


    class VirtualDisk
    {
        #region [ Native ]

        #region [ Constants ]
        const DWORD ERROR_INSUFFICIENT_BUFFER = 122;
        const DWORD ERROR_SUCCESS = 0;

        const DWORD GENERIC_READ = 0x80000000;

        const DWORD FILE_SHARE_READ = 1;
        const DWORD FILE_SHARE_WRITE = 2;
        const DWORD OPEN_EXISTING = 3;

        const DWORD FILE_ATTRIBUTE_NORMAL = 0x00000080;
        const DWORD FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
        #endregion 

        #region [ Enums ]
        [Flags]
        enum DEPENDENT_DISK_FLAG
        {
            DEPENDENT_DISK_FLAG_NONE = 0x00000000,

            //
            // Multiple files backing the virtual storage device
            //
            DEPENDENT_DISK_FLAG_MULT_BACKING_FILES = 0x00000001,
            DEPENDENT_DISK_FLAG_FULLY_ALLOCATED = 0x00000002,
            DEPENDENT_DISK_FLAG_READ_ONLY = 0x00000004,

            //
            //Backing file of the virtual storage device is not local to the machine
            //
            DEPENDENT_DISK_FLAG_REMOTE = 0x00000008,

            //
            // Volume is the system volume
            //
            DEPENDENT_DISK_FLAG_SYSTEM_VOLUME = 0x00000010,

            //
            // Volume backing the virtual storage device file is the system volume
            //
            DEPENDENT_DISK_FLAG_SYSTEM_VOLUME_PARENT = 0x00000020,
            DEPENDENT_DISK_FLAG_REMOVABLE = 0x00000040,

            //
            // Drive letters are not assigned to the volumes
            // on the virtual disk automatically.
            //
            DEPENDENT_DISK_FLAG_NO_DRIVE_LETTER = 0x00000080,
            DEPENDENT_DISK_FLAG_PARENT = 0x00000100,

            //
            // Virtual disk is not attached on the local host
            // (instead attached on a guest VM for instance)
            //
            DEPENDENT_DISK_FLAG_NO_HOST_DISK = 0x00000200,

            //
            // Indicates the lifetime of the disk is not tied
            // to any system handles
            //
            DEPENDENT_DISK_FLAG_PERMANENT_LIFETIME = 0x00000400
        }

        [Flags]
        enum GET_STORAGE_DEPENDENCY_FLAG
        {
            GET_STORAGE_DEPENDENCY_FLAG_NONE = 0x00000000,

            // Return information for volumes or disks hosting the volume specified
            // If not set, returns info about volumes or disks being hosted by
            // the volume or disk specified
            GET_STORAGE_DEPENDENCY_FLAG_HOST_VOLUMES = 0x00000001,
            GET_STORAGE_DEPENDENCY_FLAG_PARENTS = GET_STORAGE_DEPENDENCY_FLAG_HOST_VOLUMES,
            //  The handle provided is to a disk, not volume or file
            GET_STORAGE_DEPENDENCY_FLAG_DISK_HANDLE = 0x00000002,

        }

        enum STORAGE_DEPENDENCY_INFO_VERSION
        {
            STORAGE_DEPENDENCY_INFO_VERSION_UNSPECIFIED = 0,
            STORAGE_DEPENDENCY_INFO_VERSION_1 = 1,
            STORAGE_DEPENDENCY_INFO_VERSION_2 = 2,
        }
        #endregion

        #region [ Structures ]
        [StructLayout(LayoutKind.Sequential)]
        struct STORAGE_DEPENDENCY_INFO_TYPE_1
        {
            DEPENDENT_DISK_FLAG DependencyTypeFlags;
            ULONG ProviderSpecificFlags;
            VIRTUAL_STORAGE_TYPE VirtualStorageType;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct STORAGE_DEPENDENCY_INFO_TYPE_2
        {
            public DEPENDENT_DISK_FLAG DependencyTypeFlags;
            public ULONG ProviderSpecificFlags;
            public VIRTUAL_STORAGE_TYPE VirtualStorageType;
            public ULONG AncestorLevel;
            public IntPtr DependencyDeviceName;
            public IntPtr HostVolumeName;
            public IntPtr DependentVolumeName;
            public IntPtr DependentVolumeRelativePath;
        }

        [StructLayout(LayoutKind.Explicit)]
        struct STORAGE_DEPENDENCY_INFO_Union
        {
            [FieldOffset(0)]
            STORAGE_DEPENDENCY_INFO_TYPE_1 Version1Entries;
            [FieldOffset(0)]
            STORAGE_DEPENDENCY_INFO_TYPE_2 Version2Entries;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct STORAGE_DEPENDENCY_INFO
        {
            public STORAGE_DEPENDENCY_INFO_VERSION Version;
            public ULONG NumberEntries;
            public STORAGE_DEPENDENCY_INFO_Union Union;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct VIRTUAL_STORAGE_TYPE
        {
            public ULONG DeviceId;
            public Guid VendorId;
        }
        #endregion

        #region [ PInvokes ]
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern SafeFileHandle CreateFile(string lpFileName,
            DWORD dwDesiredAccess,
            DWORD dwShareMode,
            IntPtr lpSecurityAttributes,
            DWORD dwCreationDisposition,
            DWORD dwFlagsAndAttributes,
            IntPtr hTemplateFile);


        [DllImport("virtdisk.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern DWORD GetStorageDependencyInformation(SafeHandle ObjectHandle,
            GET_STORAGE_DEPENDENCY_FLAG Flags,
            ULONG StorageDependencyInfoSize,
            IntPtr StorageDependencyInfo,
            ref ULONG SizeUsed);
        #endregion

        #endregion

        #region [ Managed Methods ]
        public static String[] GetDependentVolumePaths(char driveLetter)
        {
            driveLetter = Char.ToUpper(driveLetter);
            if (driveLetter < 'A' || driveLetter > 'Z')
            {
                String paramName = "driveLetter";
                String message = "Drive letter must fall in range [a-zA-Z]";
                throw new ArgumentOutOfRangeException(paramName, message);
            }
            String fileName = String.Format(@"\\.\{0}:\", driveLetter);
            return getDependentVolumePaths(fileName);
        }

        public static String[] GetDependentVolumePaths(UInt32 driveNumber)
        {
            // TODO:  Per SO, isn't max drive 15? 
            // https://mcmap.net/q/223994/-how-to-list-physical-disks
            if (driveNumber > 9)
            {
                String paramName = "driveNumber";
                String message = "Drive number must be <= 9";
                throw new ArgumentOutOfRangeException(paramName, message);
            }
            String fileName = String.Format(@"\\.\PhysicalDrive{0}", driveNumber);
            return getDependentVolumePaths(fileName);
        }

        static unsafe String[] getDependentVolumePaths(String fileName)
        {
            DWORD dwDesiredAccess = GENERIC_READ;
            DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
            DWORD dwCreationDisposition = OPEN_EXISTING;
            DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS;

            SafeHandle driveHandle = null;
            STORAGE_DEPENDENCY_INFO info = new STORAGE_DEPENDENCY_INFO();
            info.Version = STORAGE_DEPENDENCY_INFO_VERSION.STORAGE_DEPENDENCY_INFO_VERSION_2;
            IntPtr ptr;

            try
            {
                driveHandle = CreateFile(fileName,
                    dwDesiredAccess, //GENERIC_READ,
                    dwShareMode,
                    IntPtr.Zero,
                    dwCreationDisposition,
                    dwFlagsAndAttributes,
                    IntPtr.Zero);
                if (driveHandle.IsInvalid)
                {
                    return null;
                }

                GET_STORAGE_DEPENDENCY_FLAG flags = GET_STORAGE_DEPENDENCY_FLAG.GET_STORAGE_DEPENDENCY_FLAG_NONE;
                flags |= GET_STORAGE_DEPENDENCY_FLAG.GET_STORAGE_DEPENDENCY_FLAG_PARENTS;
                if (fileName.ToUpper().Contains("PHYSICAL"))
                    flags |= GET_STORAGE_DEPENDENCY_FLAG.GET_STORAGE_DEPENDENCY_FLAG_DISK_HANDLE;

                DWORD infoSize = (DWORD)Marshal.SizeOf(info);

                byte[] infoByteArray;
                DWORD cbSize = 0;
                DWORD opStatus;

                #region [ Pull STORAGE_DEPENDENCY_INFO into byte array  ]
                infoByteArray = new byte[infoSize];
                fixed (byte* p1 = infoByteArray)
                {
                    ptr = (IntPtr)p1;
                    Marshal.StructureToPtr(info, ptr, true);
                    opStatus = GetStorageDependencyInformation(driveHandle, flags, infoSize, ptr, ref cbSize);

                    if (opStatus == ERROR_INSUFFICIENT_BUFFER)
                    {
                        infoSize = cbSize;
                        cbSize = 0;

                        infoByteArray = new byte[infoSize];
                        fixed (byte* p2 = infoByteArray)
                        {
                            ptr = (IntPtr)p2;
                            Marshal.StructureToPtr(info, ptr, true);
                            opStatus = GetStorageDependencyInformation(driveHandle, flags, infoSize, ptr, ref cbSize);
                        }
                    }

                }

                #endregion
                if (opStatus != ERROR_SUCCESS)
                {
                    //
                    // This is most likely due to the disk not being a mounted VHD.
                    //
                    return null;
                }
            }
            finally
            {
                if (driveHandle != null && !driveHandle.IsInvalid && !driveHandle.IsClosed)
                {
                    driveHandle.Close();
                }
            }

            List<String> pathList = new List<String>();
            info = (STORAGE_DEPENDENCY_INFO)Marshal.PtrToStructure(ptr, typeof(STORAGE_DEPENDENCY_INFO));
            //STORAGE_DEPENDENCY_INFO_TYPE_2 sdi2 = new STORAGE_DEPENDENCY_INFO_TYPE_2();
            STORAGE_DEPENDENCY_INFO_TYPE_2* p = (STORAGE_DEPENDENCY_INFO_TYPE_2*)&info.Union;
            for (DWORD i = 0; i < info.NumberEntries; i++, p++)
            {
                ptr = (IntPtr)p;
                STORAGE_DEPENDENCY_INFO_TYPE_2 sdi2 = (STORAGE_DEPENDENCY_INFO_TYPE_2)Marshal.PtrToStructure(ptr, typeof(STORAGE_DEPENDENCY_INFO_TYPE_2));
                String str1 = Marshal.PtrToStringUni(sdi2.DependencyDeviceName);
                String str2 = Marshal.PtrToStringUni(sdi2.HostVolumeName);
                String str3 = Marshal.PtrToStringUni(sdi2.DependentVolumeName);
                String relativePath = Marshal.PtrToStringUni(sdi2.DependentVolumeRelativePath);
                String fullPath = Path.GetFullPath(relativePath);
                pathList.Add(fullPath);
            }
            return pathList.ToArray();
        }
        #endregion
    }

}
Delacroix answered 5/1, 2013 at 20:5 Comment(1)
this did return what promsied, vhd location from given disk letter, great solution.Albertson
V
2

There is no official managed .NET wrapper for the virtual disk APIs. So you currently have three options:

  1. Run the dos command and scrape the console response, which you don't want to do as it is not a stable API.

  2. UseMicrosoft.Storage.Vds.dll that was added in Server 2008. You can use .NET reflector to examine the API. However, this is also unofficial, in that it is undocumented and thus could change without warning in service packs, etc.

  3. Use the official C API. This is what I would recommend until an official managed wrapper class is released and documented. As noted above, the full API documentation has everything you need. I would recommend writing a simplified C wrapper dll around this API that does what you need and no more. Then, PInvoke your wrapper library.

Vermiculate answered 21/5, 2010 at 4:19 Comment(4)
1. Already did it, I do not like it. 2. This looks very promising. I will try it out. 3. Unfortunately, this functions all take file name as input. For me input needs to be disk number.Pean
How are you getting disk # initially?Explosive
No success with 2. There are enumerations that go through each drive (e.g. Service.Providers) but there is no property/function that will give me name of source file. While I can now be sure that e.g. drive D: is virtual drive, I still cannot know which .vhd file was attached.Pean
@Explosive I currently retrieve disk number through WMI (all disks) and then I compare it with parsed diskpart output in order to determine which one is virtual disk and what is it's real location.Pean
W
1

Not able to test vdisk on my machine. But you can give a try to WMI queries.

eg. I can get the partition information using following code

using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
    public class MyWMIQuery
    {
        public static void Main()
        {
            try
            {
                ManagementObjectSearcher searcher = 
                    new ManagementObjectSearcher("root\\CIMV2", 
                    "SELECT * FROM Win32_DiskPartition"); 

                foreach (ManagementObject queryObj in searcher.Get())
                {
                    Console.WriteLine("-----------------------------------");
                    Console.WriteLine("Win32_DiskPartition instance");
                    Console.WriteLine("-----------------------------------");
                    Console.WriteLine("Access: {0}", queryObj["Access"]);
                    Console.WriteLine("Availability: {0}", queryObj["Availability"]);
                    Console.WriteLine("BlockSize: {0}", queryObj["BlockSize"]);
                    Console.WriteLine("Bootable: {0}", queryObj["Bootable"]);
                    Console.WriteLine("BootPartition: {0}", queryObj["BootPartition"]);
                    Console.WriteLine("Caption: {0}", queryObj["Caption"]);
                    Console.WriteLine("ConfigManagerErrorCode: {0}", queryObj["ConfigManagerErrorCode"]);
                    Console.WriteLine("ConfigManagerUserConfig: {0}", queryObj["ConfigManagerUserConfig"]);
                    Console.WriteLine("CreationClassName: {0}", queryObj["CreationClassName"]);
                    Console.WriteLine("Description: {0}", queryObj["Description"]);
                    Console.WriteLine("DeviceID: {0}", queryObj["DeviceID"]);
                    Console.WriteLine("DiskIndex: {0}", queryObj["DiskIndex"]);
                    Console.WriteLine("ErrorCleared: {0}", queryObj["ErrorCleared"]);
                    Console.WriteLine("ErrorDescription: {0}", queryObj["ErrorDescription"]);
                    Console.WriteLine("ErrorMethodology: {0}", queryObj["ErrorMethodology"]);
                    Console.WriteLine("HiddenSectors: {0}", queryObj["HiddenSectors"]);
                    Console.WriteLine("Index: {0}", queryObj["Index"]);
                    Console.WriteLine("InstallDate: {0}", queryObj["InstallDate"]);
                    Console.WriteLine("LastErrorCode: {0}", queryObj["LastErrorCode"]);
                    Console.WriteLine("Name: {0}", queryObj["Name"]);
                    Console.WriteLine("NumberOfBlocks: {0}", queryObj["NumberOfBlocks"]);
                    Console.WriteLine("PNPDeviceID: {0}", queryObj["PNPDeviceID"]);

                    if(queryObj["PowerManagementCapabilities"] == null)
                        Console.WriteLine("PowerManagementCapabilities: {0}", queryObj["PowerManagementCapabilities"]);
                    else
                    {
                        UInt16[] arrPowerManagementCapabilities = (UInt16[])(queryObj["PowerManagementCapabilities"]);
                        foreach (UInt16 arrValue in arrPowerManagementCapabilities)
                        {
                            Console.WriteLine("PowerManagementCapabilities: {0}", arrValue);
                        }
                    }
                    Console.WriteLine("PowerManagementSupported: {0}", queryObj["PowerManagementSupported"]);
                    Console.WriteLine("PrimaryPartition: {0}", queryObj["PrimaryPartition"]);
                    Console.WriteLine("Purpose: {0}", queryObj["Purpose"]);
                    Console.WriteLine("RewritePartition: {0}", queryObj["RewritePartition"]);
                    Console.WriteLine("Size: {0}", queryObj["Size"]);
                    Console.WriteLine("StartingOffset: {0}", queryObj["StartingOffset"]);
                    Console.WriteLine("Status: {0}", queryObj["Status"]);
                    Console.WriteLine("StatusInfo: {0}", queryObj["StatusInfo"]);
                    Console.WriteLine("SystemCreationClassName: {0}", queryObj["SystemCreationClassName"]);
                    Console.WriteLine("SystemName: {0}", queryObj["SystemName"]);
                    Console.WriteLine("Type: {0}", queryObj["Type"]);
                }
            }
            catch (ManagementException e)
            {
                MessageBox.Show("An error occurred while querying for WMI data: " + e.Message);
            }
        }
    }
}

It gives pretty everything related to your machine. You can try the tool to get namespace and classes available on your machine : http://www.microsoft.com/downloads/details.aspx?familyid=2cc30a64-ea15-4661-8da4-55bbc145c30e&displaylang=en

Woll answered 24/5, 2010 at 13:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.