I am trying out VirtualDisk APIs, So far I am able to Open a VHDX file, get some of the properties by using GetVirtualDiskInformation. But I am not able to get RCT information and ChangedAreas.
The first call to GetVirtualDiskInformation is successful. The second call to GetVirtualDiskInformation fails with insufficient buffer ERROR_INSUFFICIENT_BUFFER(122). Call to QueryChangesVirtualDisk fails with ERROR_INVALID_HANDLE(6). The RCT ID hardcoded in the code is proper, I am able to get the ChangedAreas using WMI Explorer. Attached screenshot of the same.
If it is invalid handle, then GetVirtualDiskInformation should also throw the same error?
#include "pch.h"
#include <iostream>
#include <string>
#include <cstdlib>
#define WINVER _WIN32_WINNT_WIN10
#include <windows.h>
#include <winioctl.h>
#include <virtdisk.h>
#include <initguid.h>
#pragma comment(lib, "virtdisk.lib")
DEFINE_GUID(VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT, 0xec984aec, 0xa0f9, 0x47e9, 0x90, 0x1f, 0x71, 0x41, 0x5a, 0x66, 0x34, 0x5b);
DEFINE_GUID(VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
int main()
{
HANDLE vhdHandle;
_VIRTUAL_STORAGE_TYPE storageType;
storageType.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN;
storageType.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN;
wchar_t path[] = L"C:\\Hyper-V\\Virtual Hard Disks\\Lacazette\\Windows2016.vhdx";
VIRTUAL_DISK_ACCESS_MASK mask = VIRTUAL_DISK_ACCESS_GET_INFO;
PGET_VIRTUAL_DISK_INFO diskInfo;
ULONG diskInfoSize = sizeof(GET_VIRTUAL_DISK_INFO);
std::wcout << "size of diskinfo structure " << diskInfoSize << std::endl;
diskInfo = (PGET_VIRTUAL_DISK_INFO)malloc(diskInfoSize);
if (diskInfo == NULL)
{
std::cout << "Failed to malloc disk info, ret=" << std::endl;
return 0;
}
std::wcout << "Opening Virtual disk " << path << std::endl;
DWORD res = OpenVirtualDisk(&storageType, path,
VIRTUAL_DISK_ACCESS_GET_INFO,
OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS,
NULL,
&vhdHandle);
if (res != ERROR_SUCCESS)
{
std::cout << "Failed to open disk, ret=" << res << std::endl;
return 0;
}
diskInfo->Version = GET_VIRTUAL_DISK_INFO_SIZE;
res = GetVirtualDiskInformation(vhdHandle, &diskInfoSize, diskInfo, NULL);
if (res != ERROR_SUCCESS)
{
std::cout << "Failed to GET_VIRTUAL_DISK_INFO_SIZE, ret=" << res << std::endl;
}
long physicalSize = diskInfo->Size.PhysicalSize;
long virtualSize = diskInfo->Size.VirtualSize;
long sectorSize = diskInfo->Size.SectorSize;
long blockSize = diskInfo->Size.BlockSize;
std::wcout << "physicalSize :" << physicalSize << std::endl;
std::wcout << "virtualSize :" << virtualSize << std::endl;
std::wcout << "sectorSize :" << sectorSize << std::endl;
std::wcout << "blockSize :" << blockSize << std::endl;
diskInfo->Version = GET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE;
res = GetVirtualDiskInformation(vhdHandle, &diskInfoSize, diskInfo, NULL);
if (res != ERROR_SUCCESS)
{
std::cout << "Failed to GET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE, ret=" << res << std::endl;
}
std::cout << "\nrct id:" << diskInfo->ChangeTrackingState.MostRecentId << std::endl;
std::cout << "\nQuerying for changed disk areas...\n" << std::endl;
wchar_t rctId[] = L"rctX:c2eb01d9:ccb1:405d:acb6:f0e76d055906:00000001";
ULONG64 byteOffset = 0L;
ULONG64 byteLength = 19327352832;
QUERY_CHANGES_VIRTUAL_DISK_RANGE* changedAreas = NULL;
ULONG rangeCount = 0L;
ULONG64 processedLength = 0L;
res = QueryChangesVirtualDisk(&vhdHandle, rctId, byteOffset, byteLength,
QUERY_CHANGES_VIRTUAL_DISK_FLAG_NONE,
changedAreas, &rangeCount, &processedLength);
if (res != ERROR_SUCCESS)
{
std::cout << "Failed to get chanegd areas, ret=" << res << std::endl;
if (vhdHandle != NULL)
{
CloseHandle(vhdHandle);
std::cout << "closing handle!" <<std::endl;
}
return 0;
}
std::cout << "Total changed areas:" << rangeCount << std::endl;
std::cout << "Total processed length:" << processedLength << std::endl;
if (vhdHandle != NULL)
{
CloseHandle(vhdHandle);
}
return 0;
}
Output of the program:
Screenshot of wmi explorer output.


Here's something that more or less works.. the issue was with the format of the rctid which I was passing to the QueryChangesVirtualDisk API. Should be as in the below sample. I was enclosing it inside the braces {} which was not correct. The below code returns RCT changed blocks successfully
Another observation is that it seems the ChangeTrackingState.MostRecentId which we get from GetVirtualDiskInformation for a offline vhdx is one more than what we initially created while the VM was up.. For example, I created a reference point with rctid: rctX:4abdf273:7e45:4748:85af:78ec4af82ebf:00000000 Now when the vhdx is offline and if I query the ChangeTrackingState.MostRecentId it is giving me this : rctX:4abdf273:7e45:4748:85af:78ec4af82ebf:00000001 which fails the QueryChangesVirtualDisk with invalid rctid error. The below function expects the user to pass the correct rctid externally. Apparently, the Get-VmReferencePoints still lists only the earlier 00000 reference point.. This one (00001) seems to be returned only by the GetVirtualDiskInformation API
Finally, for the other issue of getting access denied with a live VM, it seems we have to set the VIRTUAL_DISK_ACCESS_MASK in OpenVirtualDisk to 0 along with version 3 of OPEN_VIRTUAL_DISK_PARAMETERS as shown below. With this we we are able to query RCT CBT from a live checkpointed VM's base disk. Got clarified from https://github.com/cloudbase/rct-service/issues/1