Running Hyper-V Data Exchange Service in WinPE 4.0 (ADK)

This is an update to the excellent post by my friend Johan Arwidmark, where he described how to install the Data Exchange Service in WinPE 3.0. I had an case where I wanted to have this service available in WinPE 4.0 (ADK), and the instructions for WinPE 3.0 weren’t working. I wanted to share my findings before I forgot how they worked :^).

Background

Hyper-V provides an API and service that allows you to send key/value pairs from a Hyper-V Host OS to a Client OS. On the host OS, you would use the Msvm_KvpExchangeDataItem WMI class to read/write values to the shared space, and on the Client OS, you would simply read out of the registry from: “HKLM\SOFTWARE\Microsoft\Virtual Machine\Auto” or “External” Keys.

The only caveat is that you must have a special service running on the client to read and write with the shared space and the Registry location. This service is automatically present in Windows 8 and Windows Server 2012, and is available with the optional integration components available on the client vmguest.iso image. There is some work necessary to get the integration services working on WinPE.

Getting the Integration Component drivers

First off, I can’t say how much I love 7-zip, it makes extracting files so much easier. If you are using other tools to mount *.iso, *.wim, or *.vhd files, stop, remove the 3rd party tools and install 7-zip instead. :^)

The integration components are available in the VMGuest.iso file that is installed with Hyper-V on both Windows Server 2012 or Windows 8.

  1. Open the VMGuest.iso file using 7-zip and get the support\amd64\Windows6.x-HyperVIntegrationServices-x64.cab file.  You may also find this file in the Windows\WinSxs\… folder in a Windows 8 or Windows Server 2012 install.wim file (you must find).
  2. Open the Windows6.x-HyperVIntegrationServices-x64.cab file and extract out the contents of the amd64_wvmic.inf_31bf3856ad364e35_6.2.9200.16433_none_5ae07770d609077b folder.  Inside should be the wvmic.inf, vmicres.dll, and about 5 other files.
  3. Import these drivers into MDT, ensure that you have “System” drivers checked in your WinPE driver settings, and have the drivers in a folder that is included in your WinPE “Selection Profile”. Don’t forget to perform a full update on WinPE once you have made changes to the Drivers, BootStrap.ini and UserExit.vbs script.

Enabling the Driver

Once we have imported the driver into WinPE, it does not mean that the system will automatically start the driver once the OS boots. WinPE will only install, load, and run a subset of drivers for devices it finds on the System. Even though the Integration Components are available on the system, WinPE does not load them by default, instead we must *force* them to be Installed. One option is to download and use DevCon.exe with an Install or Update parameter. However in my case, I didn’t want to redistribute the DevCon.exe utility, so I just wrote a C/C++ program to install the application:

//
// Program to force the installation of a driver for a specific device.
// Driver must be pre-installed in the driver store.
// This program is really only useful for WinPE scenarios where PNP is not ready for all scenarios.
// (KeithGa@KeithGa.com)   10/27/2012
// Copyright Keith Garner, All rights reserved.
//

#pragma comment(lib, "setupapi")
#pragma comment(lib, "Newdev")
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <SetupAPI.h>
#include <newdev.h>

#define PNP_TARGET_DEVICE TEXT("VMBUS\\{a9a0f4e7-5a45-4d96-b827-8a841e8c03e6}\\{242ff919-07db-4180-9c2e-b86cb68c8c55}")

extern "C" void __cdecl mainCRTStartup()
{
    HDEVINFO DeviceInfoSet;
    SP_DEVINFO_DATA DeviceInfoData;
    BOOL NeedsReboot = false;
    DWORD i;
    DWORD NewLength;
    TCHAR Buffer[1024];
    DWORD gle;

    // Get a Device Info set, but target for only one device PNP_TARGET_DEVICE
    DeviceInfoSet =  SetupDiGetClassDevs( NULL, PNP_TARGET_DEVICE, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT | DIGCF_ALLCLASSES );
    if ( DeviceInfoSet == INVALID_HANDLE_VALUE ) 
    {
        _tprintf(TEXT("SetupDiGetClassDevs() failure:  0x%08x\n"), GetLastError());
        ExitProcess(1);
        return ;
    }

    // Enumerate through all of the devices found in the DeviceInfoSet (there should only be one)
    ZeroMemory(&DeviceInfoData, sizeof(SP_DEVINFO_DATA));
    DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for ( i=0;SetupDiEnumDeviceInfo( DeviceInfoSet, i, &DeviceInfoData );i++)
    {
        // Get the DeviceInstanceID (should be the same as the PNP_TARGET_DEVICE)
        if (!SetupDiGetDeviceInstanceId( DeviceInfoSet, &DeviceInfoData,Buffer,sizeof(Buffer),&NewLength) )
        {
            _tprintf(TEXT("SetupDiGetDeviceInstanceId() failure:  0x%08x\n"), GetLastError());
            continue;
        }

        printf("Found Device: %s\n", Buffer);  // Display the InstanceID for debugging

        // Install the driver for the Device Found. Driver must be in the driver store.
        if ( ! DiInstallDevice(NULL,DeviceInfoSet,&DeviceInfoData,NULL,DIIDFLAG_NOFINISHINSTALLUI, & NeedsReboot) )
        {
            _tprintf(TEXT("DiInstallDevice() failure:  0x%08x\n"), GetLastError());
            continue;
        }

        _tprintf(TEXT("Install Success %d\n"),NeedsReboot);

    }

    // Cleanup and Exit
    gle = GetLastError();
    SetupDiDestroyDeviceInfoList ( DeviceInfoSet );

    if ( gle != ERROR_NO_MORE_ITEMS)
    {
        _tprintf(TEXT("SetupDiEnumDeviceInfo() failure:  0x%08x\n"), gle);
        ExitProcess(1);
    }
    else
    {
        _tprintf(TEXT("Success!\n"));
        ExitProcess(0);
    }

    return;
}

Then I created a UserExit.vbs script and loaded it from the bootstrap.ini file:

[Settings]
Priority=Default

[Default]
SkipBDDWelcome=YES
SubSection=ISVM-%ISVM%

[ISVM-True]
UserExit=UserExit.vbs

Within the UserExit.vbs Script, we then load the driver, Create a *Fake* Remote Desktop Service, and start the Key Value Pair exchange service. Now we can read and write to the registry. Any Value in the registry, will be picked up by the script and loaded into MDT Litetouch.

option explicit

function UserExit(sType,Swhen,Sdetail,bSkip)
	oLogging.CreateEntry "USEREXIT:ModelAliasExit.vbs started: " & sType & " " & sWhen & " " & sDetail, LogTypeInfo 

	If ucase(oEnvironment.Item("ISVM")) = "TRUE" and oEnvironment.Item("OSVERSION") = "WinPE" then

		oLogging.CreateEntry "Load the Integration Components", LogTypeInfo
		oUtility.RunWithConsoleLogging "ForceDriverInstall.exe"
		CreateFakeService "TermService", "Remote Desktop Service", "FakeService.exe"
		GetObject("winmgmts:root\cimv2").Get("Win32_Service.Name='vmickvpexchange'").StartService()
		oUtility.SafeSleep 10000
		LoadVariablesFromHyperV

	End if

	' Write back an unique number to the host to let them know that the variables have been loaded.
	oUtility.RegWrite "HKLM\SOFTWARE\Microsoft\Virtual Machine\Auto\HydrationGuestStatus","eb471002-58aa-473a-850f-7b626613055f"

	Userexit=success

end function

' ---------------------------------------------------------

Function CreateFakeService ( sName, sDisplayName, sPathName )

	Dim objService
	Dim objInParam

	' Obtain the definition of the Win32_Service class.
	Set objService = GetObject("winmgmts:root\cimv2").Get("Win32_Service")

	' Obtain an InParameters object specific to the Win32_Service.Create method.
	Set objInParam = objService.Methods_("Create").inParameters.SpawnInstance_()

	' Add the input parameters.
	objInParam.Properties_.item("Name") = sName
	objInParam.Properties_.item("DisplayName") = sDisplayName
	objInParam.Properties_.item("PathName") = sPathName
	objInParam.Properties_.item("ServiceType") = 16
	objInParam.Properties_.item("ErrorControl") = 0
	objInParam.Properties_.item("StartMode") = "Manual"
	objInParam.Properties_.item("DesktopInteract") = False

	' Execute the method and obtain the return status.
	CreateFakeService = objService.ExecMethod_("Create", objInParam).ReturnValue

End function

Function LoadVariablesFromHyperV

	Dim objReg
	Dim aSubValues
	Dim aValues
	Dim SubVal

	oLogging.CreateEntry "Has MDT Environment Variables in Integration Components.", LogTypeInfo
	Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
	objReg.EnumValues &H80000002, "SOFTWARE\Microsoft\Virtual Machine\External", aSubValues, aValues

	If isArray(aSubValues) then
		For Each SubVal in aSubValues
			oLogging.CreateEntry "Found Key: [" & SubVal & "]", LogTypeInfo
			oEnvironment.Item(SubVal) = oUtility.RegRead("HKLM\SOFTWARE\Microsoft\Virtual Machine\External\" & SubVal)
		Next
	End if

End Function

Some notes

  • I haven’t had much need for this system after I created it last year. Instead I have been developing powershell scripts to auto-mount the new *.vhd file, and inject variables directly into the client Virtual Machine from the Host, less messy. The specific scenario I had developed was to directly copy over MDT *.iso Media files and mount without cracking open the *.ISO images. Haven’t had much of a use for *.iso images in a while.
  • One of the things I was using this for was to send administrator passwords to the MDT system, however this is a security breach, since the variables will remain around the client. So I programed a check into the host script to automatically remove the variables from shared space once the client reports back done.

-k

Advertisements

One thought on “Running Hyper-V Data Exchange Service in WinPE 4.0 (ADK)

  1. Pingback: PowerShell is King – Building a Reference Image Factory « The Deployment Bunny

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s