Monday, June 22, 2009

Attaching a VHD to a Windows 7/Server 2008 host from managed code

This past weekend I had "fun" working on a project to attach VHD files to a Windows 7/Server 2008 host machine. My aim was to have a C# app be able to do this and then read the files off the VHD...

It turns out the easiest part was opening and attaching the VHD file. The VHD APIs are pretty straight-forward, and a simple managed wrapper lets you call these from C#. (I chose a Managed C++ wrapper, but there is a C# wrapper here)

By default AttachVirtualDisk will mount all the volumes on the virtual disk, which is what I wanted. (You could also tell the VHD API to not mount them and do it yourself for the partitions you cared about, but that sounded like a lot of extra work to me...)

Now came the fun - which drive letters were assigned to the mounted partitions on the VHD? It turns out the VHD API doesn't provide this information - the most it will give you is the physical path for the drive device (see GetVirtualDiskPhysicalPath)

I looked around and found some examples of people using Powershell to mount VHDs and figure out the drive letters. Some folks were using the Virtual Disk Service (VDS), which has a managed wrapper in Server 2008 (Microsoft.Storage.Vds.dll) - Taylor Brown talks about this on his blog.

That wouldn't work on Windows 7 though, so I was left considering writing a managed wrapper for the VDS COM interface. My head was beginning to hurt... :)

Luckily I found some hints at using WMI to get the info - using WMI from .NET is not too complicated once you figure out which objects and queries to use. Here's the resulting code (Managed C++) which retrieves the logical drive for the first partition on the VHD:

// Return the logical disk for the VHD's first partition
property String^ LogicalDisk
{
String^ get()
{
String^ physicalPath = this->PhysicalPath; // Calls GetVirtualDiskPhysicalPath API wrapper
String^ logicalDisk = nullptr;

// Use WMI to get the logical drive for the first partition on the VHD disk
RelatedObjectQuery^ q = gcnew RelatedObjectQuery(String::Format("\\\\.\\root\\cimv2:Win32_DiskDrive.DeviceID='{0}'", physicalPath), "Win32_DiskPartition");
ManagementObjectSearcher^ searcher = gcnew ManagementObjectSearcher(q);
String^ firstPartition = nullptr;
for each (ManagementObject^ o in searcher->Get())
{
firstPartition = o->Path->ToString();
break;
}
if (firstPartition != nullptr)
{
// Now see which volumes are related to the partitions
q = gcnew RelatedObjectQuery(firstPartition, "Win32_LogicalDisk");
searcher->Query = q;
for each (ManagementObject^ o in searcher->Get())
{
logicalDisk = o->GetPropertyValue("Name")->ToString();
break;
}
}
return logicalDisk;
}
}

2 comments:

Anonymous said...

The link to the c# wrapper is no longer valid. Do you have another link or can you post it here?

Mark said...

Hi, looks like the blog I linked to for the C# wrapper has moved to jmedved.com.

I updated the link to go to the new site.