Dual Boot from VHD

When Windows 7 Beta came out, a colleague and I decided that the being able to dual boot from a VHD file would be useful for demos and to test Windows7 and Windows Server 2008 R2, due to the ability to replace and service VHD files pretty much at will (VHD files can be mounted as volumes in Vista https://blogs.msdn.com/cschotte/archive/2008/03/26/how-to-mount-a-vhd-quickly-under-vista-using-your-mouse.aspx and it is even easier in Windows 7 using the task Attach VHD in Disk Management). So I pulled together some information from various sources and wrote a script (BootFromVHD.vbs) to come up with a process that quickly enables booting from a VHD file.

This method does not provide any way of single booting from a VHD and assumes that either Windows 7\Windows Server 2008 R2 are already installed and running.

Create a VHD

To start with a VHD is required and this will only work with a Windows 7 or Windows Server 2008 R2 VHD. I personally found it easier to create a vanilla Windows Server 2008 R2 (in my case; but it works equally well with Windows 7) virtual guest in Windows Server 2008 Hyper-V with a fixed disk size. Then while the guest virtual computer is still running on the Hyper-V server I ran the following command from an elevated (administrator) command prompt within the guest “c:\windows\system32\sysprep\sysprep /generalize /oobe /shutdown” . This basically, re-bases the image and removes anything specific to the hardware it was built on (in this case a virtual environment).

Alternatively, you could use this method to create a VHD from a WIM image.

1. Download Win7 WAIK - https://www.microsoft.com/downloads/details.aspx?FamilyID=696dd665-9f76-4177-a811-39c26d3b3b34&displaylang=en

2. Download WIM2VHD - https://code.msdn.microsoft.com/wim2vhd

3. Windows 7 or Windows 2008 R2 installation media required.

4. Install WAIK on a Windows 7 client

5. Run WIM2VHD using this command line (this can be modified for Win7 or other version of 2008 R2; see the examples on the WIM2VHD page):

cscript wim2vhd.wsf /wim:path /sku:serverstandard /vhd:path /size:15360 /disktype:fixed

Once you have a VHD file it needs to be copied to the hardware from which it will boot.

Very Important Notes :

  • The VHD can reside in a folder on the volume on which you copy it, but the VHD needs to reside on a volume that does not have Bitlocker enabled.
  • Also, the VHD needs to be less than the size of the partition it is stored on minus the Pagefile size (VHD size + PageFile Size <= Storage partition Size).
  • Booting from VHD does not work with VHD’s created in Virtual PC.
  • If the VHD is dynamically expanding, when it first opens it will convert to a full fixed size VHD and this may take a very long time. This will fail if the volume the VHD sits on is not sized correctly see note above.
  • This is a vanilla install and will result in the “out of the box” installation running to configure for your hardware. You may need to provide drivers to get it all working.
  • Each tool provides its own instructions on how it works and what changes will be made.

Add an entry in the BCD to boot from the VHD

This can be done manually by using an elevated command prompt in the original operating system on the computer using the Windows 7 version of BCDEdit.exe, or by script (VBScript or PowerShell). I did tried to write a VBScript that uses just WMI to carry out this task, but unfortunately I couldn’t get the BCDEdit WMI provider to recognise that I had created a new entry for the rest of the script to edit. So, I went with the method of calling BCDEdit.exe from within the VBScript.

I won’t explain the method of doing this manually, but will provide the script below. Please be aware that I or my employer holds no responsibility for your using this script and the outcomes thereof and I provide the code without guarantees or warranties (basically, test it prior to using it and ensure you are happy with what it is doing; myself and many of my colleagues have used this and have only seen one failure; which was not terminal). The script does create a backup of your existing BCD and will roll back any changes if any errors are encountered. If for some reason you want to roll back the BCD changes all you need to do is run “bcdedit /import c:\bcdbackup” from an elevated command prompt.

Notes

  • The script needs to be run using cscript in an elevated command prompt.
  • Carefully read the instructions of each popup dialog box to ensure you input the correct information. Firstly you must provide a name for the BCD entry and secondly the location of the VHD does not require the drive specifier (the bootloader will find what volume the file is stored on at boot time).

'*************************************************************************************************************

'* bcdbootfromvhd.vbs

'*

'* Purpose: Creates a new BCD Boot Loader Entry by copying an

'* existing entry and making changes

'*

'* Parms: None.

'*

'* Requires: To be run on Vista\Windows 7 or Windows Server 2008 (R2) to access BcdStore using

'* BCDEDIT and WMI.

'*

'* References: 1) Vista Software Development Kit.

'* 2) MSDN Library, "Boot Configuration Data (BCD)" at

'* https://msdn2.microsoft.com/en-us/library/aa362692.aspx

'* 3) "Boot Configuration Data in Windows Vista" at

'* https://download.microsoft.com/download/a/f/7/af7777e5-7dcd-4800-8a0a-b18336565f5b/BCD.doc

'*

'* Note : This function should be invoked as a cscript.

'* Issue the command "cscript //h:cscript" to set the

'* default scripting as cscript before issuing this

'* command.

'*

'* Author : Carl Harrison

'*************************************************************************************************************

Option Explicit

On Error Resume Next

Dim strComputer

Dim strNewLoaderGUID

Dim strVHDPath, strBootEntryName

Dim objStoreClass, objStore, objDefault, objElement

Dim objShell, objBCDEditCopyCmd

Dim strCommandOutputReadLine

Dim varDefaultLoader

Const BcdLibraryString_Description = &h12000004

Const Current = "{fa926493-6f1c-4193-a414-58f0b2456d1e}"

Const WindowsImages = &h10200003

strComputer = "."

'Connect to the BCD store with WMI

Set objStoreClass = GetObject("winmgmts:{(Backup,Restore)}\\" & strComputer & "\root\wmi:BcdStore")

if not objStoreClass.OpenStore("", objStore) then

     WScript.Echo "Couldn't open the system store!"

     WScript.Quit

end if

 

'Get some info about the current booted OS from the BCD

'We are going to use it later

objStoreClass.OpenStore "", objStore

objStore.OpenObject Current, objDefault

objDefault.GetElement BcdLibraryString_Description, objElement

Set objShell = CreateObject("Wscript.Shell")

 

'Get a backup of the existing BCD

Set objBCDEditCopyCmd = objShell.Exec("bcdedit /export c:\bcdbackup")

If Instr(objBCDEditCopyCmd.StdOut.ReadAll,"The operation completed successfully.") <> 0 then

     WScript.Echo "BCD Backup was created successfully"

Else

     WScript.Echo "Failed to create BCD Backup"

     WScript.Echo "Script is Exiting"

     WScript.Quit

End If

 

'Popup dialog to ask for the BCD entry human friendly name

'We don’t accept blanks

strBootEntryName = InputBox ("Name of Boot loader entry at Start Up e.g. Windows 7")

If strBootEntryName = "" then

     WScript.Echo "Blank Loader Entry Name is not accepted."

     WScript.Echo "ROLLING BACK CHANGES"

     Set objBCDEditCopyCmd = objShell.Exec("bcdedit /import C:\bcdbackup")

     WScript.Quit

End If

'Now copy the existing current BCD entry and determine the new GUID of the new BCD entry

Set objBCDEditCopyCmd = objShell.Exec("bcdedit /copy " & objElement.ObjectID & " /d " & """" & strBootEntryName & """")

Do While Not objBCDEditCopyCmd.StdOut.AtEndOfStream

     strCommandOutputReadLine = objBCDEditCopyCmd.StdOut.ReadLine()

     If Instr(strCommandOutputReadLine, "The entry was successfully copied to") <> 0 Then

          strNewLoaderGUID = Left(Right(strCommandOutputReadLine,39),38)

End If

Loop

WScript.Echo "New Loader GUID = " & strNewLoaderGUID

WScript.Echo

If strNewLoaderGUID = "" Then

     WScript.Echo strComputer & vbTAB & "Could not Copy OS Loader."

     WScript.Quit

End If

'Popup dialog to get the location and name of the vhd

'The BCD does not need a drive specification

'We do not accept blank entries

strVHDPath = InputBox ("Please provide the path, excluding drive letter and colon and file name of the VHD to boot from (use quotes if there are spaces). So if the path to the vhd is d:\win7.vhd just type \win7.vhd")

If strVHDPath = "" then

     WScript.Echo "Blank path is not accepted for " & strNewLoaderGUID

     WScript.Echo "ROLLING BACK CHANGES"

     Set objBCDEditCopyCmd = objShell.Exec("bcdedit /import C:\bcdbackup")

     WScript.Quit

End If

'Ok now call the sub setBCDEdit and pass it the necessary parameters to make changes to the newly copied BCD entry

Call setBCDEdit ("/set " & strNewLoaderGUID & " device vhd=[locate]" & strVHDPath, "device partition", strVHDPath)

Call setBCDEdit ("/set " & strNewLoaderGUID & " osdevice vhd=[locate]" & strVHDPath, "osdevice partition", strVHDPath)

Call setBCDEdit ("/set " & strNewLoaderGUID & " resumeobject " & strNewLoaderGUID, "resumeObject", strNewLoaderGUID)

Call setBCDEdit ("/set " & strNewLoaderGUID & " detecthal on", "detecthal value", "on")

Call setBCDEdit ("/set " & strNewLoaderGUID & " nx OptIn", "nx value", "optin")

varDefaultLoader = MsgBox ("Do you want this to be the default booting OS?", vbYesNo)

If varDefaultLoader = vbYes Then

     Call setBCDEdit ("/default " & strNewLoaderGUID, "boot loader", "Default")

End If

Wscript.Echo

'This Sub accepts parameters from the calling line and inserts them into the subsequent bcdedit command line before running them

Sub setBCDEdit (strCmdswitch, strSwitchDescription, strSwitchSetting)

Set objBCDEditCopyCmd = objShell.Exec("bcdedit " & strCmdSwitch)

If Instr(objBCDEditCopyCmd.StdOut.ReadAll,"The operation completed successfully.") <> 0 then

     WScript.Echo "The " & strSwitchDescription & " for the new Boot Loader " & strNewLoaderGUID & " was successfully set to " & strSwitchSetting

     WScript.Echo

Else

     WScript.Echo "Failed to set the " & strSwitchDescription & " for the new Boot Loader to " & strSwitchSetting

     WScript.Echo "ROLLING BACK CHANGES"

     Set objBCDEditCopyCmd = objShell.Exec("bcdedit /import C:\bcdbackup")

     WScript.Quit

End If

End Sub