In-Depth
Scripting Basics for Virtual Server 2005 R2, Part 2
You know how to create them, now here's how to interact with these VMs that you created with your scripting magic.
In the
last installment of this article, we looked at building a basic development environment and took the first steps to learn about scripting for Virtual Server 2005 R2. We explored how to provision and control virtual machines, and how to manage them using Administration Web site in a secured setup.
Let's kick off this second part by looking at the importance of rolling out Virtual Machine Additions before moving on to describe how you can programmatically interact with virtual machines using the keyboard and mouse device. If you've created a bunch of virtual machines under Virtual PC, the section on recycling virtual machines should interest you. Understanding the fundamentals of working with virtual hard disks will equip you with key information to improve the administrative and operational aspects of virtualization. To round up, I'll revisit the topic of choosing a development platform and highlight useful tips to help you save time while simplifying script development.
Virtual Machine Additions
If you're through with the first part of the series, you already know how to provision a virtual machine. The next step is to install the latest version of Virtual Machine Additions in the guest operating system. Otherwise, you'll forego improved performance and key host-guest integration features. Furthermore, Virtual Machine Additions is essential to determine if the guest OS is alive, querying or making configuration changes as well as enabling host time synchronization.
The script to install Virtual Machine Additions in a running guest OS is fairly straightforward:
Set objGuestOS = objVM.GuestOS
'objVM = reference to a virtual machine object
objGuestOS.InstallAdditions()
In a Windows-based guest OS, the autorun feature is typically enabled. The objGuestOS.InstallAdditions() method attempts to locate the Virtual Machine Additions ISO image and execute the Additions installer automatically. By default, this is at %ProgramFiles%\Microsoft Virtual Server \Virtual Machine Additions\VMAdditions.iso. This method will fail if the virtual machine’s DVD-ROM drive is not empty, so it’s a good idea to execute the collDVDs(1).ReleaseImage() method first (collDVDs(1) is a reference to the first DVD-ROM drive in the collection).
Upon successful installation of the Virtual Machine Additions, the Parameters key is automatically created under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Virtual Machine\Guest. Four values of type REG_SZ (string) are populated whenever the Windows-based guest OS starts up, namely HostName, PhysicalHostName, PhysicalHostNameFullyQualified and VirtualMachineName. To work with these values inside a guest OS, you will have to resort to using Windows Management Instrumentation (WMI), as there is no native COM API exposed in Virtual Server 2005 R2 for direct manipulation.
Virtual Machine Interaction
The whole idea of scripting is to automate mundane tasks with minimal manual intervention as much as possible, so you can use a combination of the PressAndReleaseKey and TypeKeySequence methods to simulate keystrokes for dispatch to the guest OS. To illustrate, here's a code snippet to script the Virtual Machine Additions installation in Windows Vista:
objVMKB = objVM.Keyboard
objVMKB.PressAndReleaseKey "Key_Enter"
'allow autoplay to start (UAC)
objVMKB.TypeKeySequence "DOWN, Key_LeftAlt, DOWN, Key_C,
UP, Key_LeftAlt, UP, Key_C"
objVMKB.TypeKeySequence "DOWN, Key_LeftAlt, DOWN, Key_N,
UP, Key_LeftAlt, UP, Key_N"
objVMKB.TypeKeySequence "DOWN, Key_LeftAlt, DOWN, Key_F,
UP, Key_LeftAlt, UP, Key_F"
objVMKB.TypeKeySequence "DOWN, Key_LeftAlt, DOWN, Key_Y,
UP, Key_LeftAlt, UP, Key_Y"
The TypeAsciiText method simulates typing a string of ASCII characters. It's particularly useful if the application is expecting some text input, such as entering a product key, and the typing speed is designed to be “just-right” without overwhelming the guest OS . However, if there is a chance for more than one application process or users to access the same keyboard device at the same time, you can acquire exclusive access by setting objVM.keyboard.HasExclusiveAccess to vbTrue. Remember to set this back to vbFalse as soon as the keyboard is no longer needed.
Note that you should send the actual Ctrl+Alt+Del key sequence to the guest OS instead of using the Host+Del combination, like this:
strKBCtlAltDel = "DOWN, Key_LeftCtrl, DOWN, Key_LeftAlt, " & _
"DOWN, Key_Delete, UP, Key_LeftCtrl, UP, Key_LeftAlt, " & _
"UP, Key_Delete"
objVMKB = objVM.Keyboard
objVMKB.TypeKeySequence strKBCtlAltDel
Even in a scripted environment, most of us still prefer the convenience and intuitive nature of the mouse to navigate within the Windows GUI. You script it this way:
Set objMouse = objVM.Mouse
objMouse.UsingAbsoluteCoordinates = True
'change to this coordinate system
The position of the mouse cursor can be represented in either absolute or delta coordinates. Virtual Server defaults to using delta coordinates, but it is generally easier to work with the other coordinate system.
With VBScript, you can query or set the mouse cursor position by using the read/write property objMouse.HorizontalPosition and objMouse.VerticalPosition, representing x- and y- coordinates, respectively. If you have a mouse with a scroll wheel, you can set the objMouse.ScrollWheelPosition write-only property to move the mouse cursor along the z-coordinate, such as scrolling through a long Web page.
Once you've placed the mouse cursor on the desired spot, the next logical action is to execute objMouse.Click(). This method simulates the clicking action on the specified mouse button described by the enumerated constants vmMouseButton_Left (1), vmMouseButton_Right (2) or vmMouseButton_Center (3). Occasionally, you will need to hold a mouse button down and keep it depressed to select a number of objects or to drag a document across the Windows Desktop. You can control the state of the mouse button with the subroutine shown, or check out the objMouse.GetButton() method if you need to query the current mouse button state:
objMouse.SetButton vmMouseButton_Left, True
'vmMouseButton_Left = left mouse button
'True = set button state to down (False to up)
So that you can correctly position the mouse cursor within a virtual machine’s screen estate, it is imperative to first ascertain its video display properties:
Set objVMDisplay = objVM.Display
Wscript.Echo "Guest video mode=" & objVMDisplay.VideoMode
'vmVideoMode_TextMode (0), vmVideoMode_CGAMode (1)
'vmVideoMode_VGAMode (2), vmVideoMode_SVGAMode (3)
Wscript.Echo "Guest display width (pixels)=" & _
objVMDisplay.Width
Wscript.Echo "Guest display height (pixels)=" & _
objVMDisplay.Height
Equally important, you can adjust the video resolution, but not the video mode (determined by the guest OS’ virtualized graphics driver):
objVMDisplay.SetDimensions 800, 640 'width, height in pixels
This subroutine expects the two parameters to be evenly divisible by eight, and must fall within the range of 640 to 1600 and 480 to 1200 for the width and height parameters, respectively (minimum is 640x480; maximum is 1600x1200).
It is often necessary to pinpoint the correct application window or a specific dialog box to send keystrokes or mouse clicks to the guest OS. You may think of using the objGuestOS.ExecuteCommand() method to query this information. The bad news is that although it is officially documented, it is not implemented and its future is unclear. There is also no native Virtual Server 2005 R2 COM API function such as IVMFindWindow() or equivalent. One interesting approach around this limitation is to compare the state of the current guest OS display to that of a previously saved bitmap. This is one effective way to automate the setup process and is described in detail here.
For a list of valid key identifiers used to simulate keystrokes in a virtual machine, refer to the Virtual Server Programmer's Guide and search for Key Identifiers Reference.
Recycling Virtual Machines
You may already have a library of virtual machines previously created under Virtual PC 2004 SP1 or Virtual PC 2007. But do you know that these same virtual machines can be ported over to Virtual Server 2005 R2, thereby allowing you to gain the benefits of improved security, scalability and manageability?
To port over those libraries, the virtual machines' configuration (identified by their .vmc file name) must first be registered with a call to the objVS.RegisterVirtualMachine() method:
Set objVS = CreateObject("VirtualServer.Application")
objVS.RegisterVirtualMachine("vista.vmc", _
"C:\vmachines")
'vista = name of virtual machine to register
'c:\vmachines = the full path to the
'.vmc configuration file
If you attempt to label a virtual machine with a name that has been used before, objVS.RegisterVirtualMachine() will fail even though the name does not appear on the Administration Web site. You'll need to first stop the virtual machine and perform an unregistration step:
Set objVM = objVS.FindVirtualMachine("vista")
objVS.UnregisterVirtualMachine(objVM)
The underlying .vmc file will only be deleted with an explicit call to objVS.DeleteVirtualMachine() on a stopped virtual machine. This includes all associated virtual hard disk data such as VM saved state or undo files but the virtual hard disk .vhd will remain intact. For newly provisioned virtual machines, registration is automatically implied with objVS.CreateVirtualMachine(), so you don't need to explicitly call objVS.RegisterVirtualMachine().
Behind the scene, Virtual Server manages the creation and deletion of Windows shortcuts (*.lnk) that point to virtual machine configuration settings in the %ALLUSERSPROFILE%\Application Data\Microsoft\Virtual Server\Virtual Machines folder. Only local machine administrators or designated Virtual Server administrators with Modify, View, Remove and Control or Full permissions can perform actions related to virtual machine registration and unregistration via the COM API or the Administration Web site. This can be configured on the “Virtual Server Security Properties” page, or you can use this code:
'setVSSec.vbs
Dim ace
Set objVS = Wscript.CreateObject("VirtualServer.Application")
Set objSec = WScript.CreateObject("VirtualServer.VMSecurity")
Set objSec = objVS.Security
Set ace = objSec.AddEntry("domain\AdminsVirtualServer", _
vmAccessRights_Allowed)
ace.WriteAccess = True
ace.ReadAccess = True
ace.ExecuteAccess = True
ace.DeleteAccess = True
ace.ReadPermissions = False
ace.ChangePermissions = False
objVS.Security = objSec
Table 1.Permission mappings to Administration Website |
Access Rights |
Permissions Exposed on Administration Web site |
.WriteAccess |
Modify |
.ReadAccess |
View |
.ExecuteAccess |
Control |
.DeleteAccess |
Remove |
.ReadPermissions |
N/A |
.ChangePermissions |
Change permissions
|
N/A |
Special permissions |
All access rights |
Full |
|
|
The same permissions also apply to virtual machines stored in the default virtual machine configuration folder specified on the “Virtual Server Search Paths” page. By default, a virtual hard disk (.vhd) is stored in the same location as the .vmc file when a new virtual machine is provisioned. If you choose to store them elsewhere, set the discretionary access control list (DACL) to Modify or Full NTFS permissions on the target folder where the .vmc resides. Otherwise, you'll receive an error like this:
C:\vmachines>cscript regVMfromText.vbs
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.
C:\vmachines\regVMfromText.vbs(9, 2) VirtualServer.Application.1: The virtual machine configuration could not be added. You do not have the appropriate access rights.
To quickly register several virtual machines listed in a plain text file, take a look this code snippet:
'regVMfromText.vbs
On Error Resume Next
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objVMList = objFSO.OpenTextFile("c:\vmachines\vmlist.txt")
Set objVS = CreateObject("VirtualServer.Application")
Do Until objVMList.AtEndOfStream
strVM = objVMList.ReadLine 'read VM name line by line
objVM = objVS.RegisterVirtualMachine(strVM & _
".vmc"," c:\vmachines")
'take action if the name has been registered before
If objVM = VM_E_CONFIG_DUPLICATE_NAME Then
WScript.Echo "Registration of '" & strVM & _
".vmc' failed (duplicate VM name)"
Set objVM = objVS.FindVirtualMachine(strVM)
objVS.UnregisterVirtualMachine(objVM)
WScript.Echo "Unregistered: '" & strVM & "'"
'try to register again
objVM = objVS.RegisterVirtualMachine(strVM & _
".vmc"," c:\vmachines")
End If
WScript.Echo "Registration of '" & strVM & _
".vmc' successful"
Loop
objVMList.Close
Working with Virtual Hard Disk
In contrast to the real world, the physical characteristics of a virtual hard disk .vhd are generic and are not dependent on a specific hard disk controller type or technology to function. That is, you're free to attach a .vhd image to a SCSI or IDE adapter under Virtual Server simply by the passing the appropriate parameters to the objVM.AddHardDiskConnection() method. This fact is not well understood but is vital in maintaining .vhd portability between Virtual Server and Virtual PC.
What if the virtual hard disk is already connected to a SCSI bus controller? No problem; just execute this subroutine:
colHDs(i).SetBusLocation vmDriveBusType_IDE, 0, 1
' vmDriveBusType_IDE = type IDE (enumerated constant 0)
' vmDriveBusType_SCSI = type SCSI (enumerated constant 1)
' 0 = bus number (first IDE controller)
' 1 = device number (primary channel 1)
To find out the disk controller bus type a virtual hard disk is currently hooked up to, look into the colHDs(i).HardDisk.BusType property. The settings describing this are also recorded in the virtual machine's .vmc configuration file under <scsi_adapter> or <ide_adapter>.
You can query common properties of a virtual hard disk for a number of reasons like inventory control or capacity planning. You’ll start by retrieving a collection of hard disk connections attached to the virtual machine through the HardDiskConnections property:
Set colHDs = objVM.HardDiskConnections
Next, you can enumerate through the collection and read basic information about the HardDisk object. A few of the common properties of interest are File, HostFreeDiskSpace, SizeInGuest and SizeOnHost. To correctly access these properties, prefix them with colHDs(i).HardDisk, where i = 1 refers to the first hard disk image and so on, i.e. colHDs(1).HardDisk.File, colHDs(1).HardDisk.HostFreeDiskSpace, etc.
For example: You are tasked to find out the total hard disk space consumed by all registered virtual machines to plan for a virtualization migration project. The code fragment to do this looks a bit like this (assuming that all .vhd reside on same path):
'chkVHDSizes.vbs
On Error Resume Next
Dim iSizeInGuestTotal, iSizeOnHostTotal, iNoOfRegVM
Set objVS = CreateObject("VirtualServer.Application")
iSizeInGuestTotal = 0
iSizeOnHostTotal = 0
iNoOfRegVM = 0
For Each objVM in objVS.VirtualMachines
iNoOfRegVM = iNoOfRegVM + 1
Set colHDs = objVM.HardDiskConnections
For i = 1 To colHDs.Count
WScript.Echo "VM Name='" & objVM.Name & "'"
WScript.Echo "objHDs(" & i & ").HardDisk.File='" & _
colHDs(i).HardDisk.File & "'"
WScript.Echo "objHDs(" & i & _
").HardDisk.SizeInGuest=" & _
clng(cdbl(colHDs(i).HardDisk.SizeInGuest) _
/ 1048576) & " MB"
WScript.Echo "objHDs(" & i & _
").HardDisk.SizeOnHost=" & _
clng(cdbl(colHDs(i).HardDisk.SizeOnHost)_
/ 1048576) & " MB"
iSizeInGuestTotal = iSizeInGuestTotal + _
clng(cdbl(colHDs(i).HardDisk.SizeInGuest) _
/ 1048576)
iSizeOnHostTotal = iSizeOnHostTotal + _
clng(cdbl(colHDs(i).HardDisk.SizeOnHost) _
/ 1048576)
WScript.Echo vbLBCR
Next
Next
Wscript.Echo "Total SizeInGuest=" & _
iSizeInGuestTotal & " MB"
Wscript.Echo "Total SizeOnHost=" & _
iSizeOnHostTotal & " MB"
Wscript.Echo "Total no. of registered VMs=" & iNoOfRegVM
A virtual hard disk image can be created as one of four types: dynamic, fixed-size, differencing or linked to a physical host drive. This information is revealed with a call to the colHDs(i).HardDisk.Type property, with return values describing the image type, namely vmDiskType_Dynamic (0), vmDiskType_FixedSize (1), vmDiskType_Differencing (2) or vmDiskType_HostDrive (4).
Of particular interest is the differencing hard disk image type. A hierarchy of virtual hard disks can be built where a .vhd can be defined as a parent to a child and the child can have offsprings. Here, common traits get inherited and each generation brings about its own unique attributes. In virtual machine terms, a common Windows Server 2003 RTM image with no service pack can be built and deployed as a base parent virtual hard disk. Subsequently, you can create variations of differencing child disks base on this parent .vhd, say one with service pack 1 installed and another with Internet Information Server to test a critical patch.
The child can be constructed as a fixed-sized or dynamic virtual hard disk type. The former is preferred if you have sufficient disk space to hold the full capacity that must be allocated upon creation. Since disk space is likely to be allocated in a contiguous fashion by the host operating system, this has the added benefit of keeping disk spindle head movements to a minimum and maintaining performance. Additionally, performance can be further optimized by deploying a SCSI type bus adapter and installing the latest version of Virtual Machine Additions in the guest OS.
Information about the relationship of a parent and child .vhd is only stored in the latter, containing a relative (“.\win2003base.vhd”) and absolute (“c:\vmachines\win2003base.vhd”) path to the parent .vhd coded in both ASCII and Unicode format. The parent .vhd has absolutely no information about any child .vhd dependencies at all. To maintain the integrity of a virtual hard disk, it is best practice to mark the .vhd designated as a parent read-only.
Now, to learn similar information about a parent .vhd, all you have to do is insert the noun Parent before the desired hard disk property, like this:
colHDs(i).HardDisk.Parent.File, colHDs(1).HardDisk.Parent.SizeInGuest,
If you have the full path of a .vhd image file handy, you can pass this as a parameter to the GetHardDisk() method of the Virtual Server instance:
Set objHD = objVS.GetHardDisk( _
"c:\vmachines\vista.vhd")
Since the returned object is a VMHardDisk, you can use the same construct described earlier such as objHD.SizeInGuest to query the raw virtual hard disk capacity. In case you didn’t notice, this property and the other methods give you similar results attainable using the “Virtual Disks” | “Inspect” option on the Administration Web site.
Sizing Up VHD Space |
A virtual hard disk has a maximum size of 2 TB, but the limit when connected to an IDE controller is 127 GB. Be aware that the size of a virtual hard disk cannot be adjusted once it's created, regardless of .vhd type.
In the guest OS, you can partition and allocate available disk space on the virtual hard disk in the usual manner. The resulting partitions/drive letters are visible only from within the guest OS and no method is exposed in the Virtual Server COM API to retrieve this information. |
|
|
It is useful to know that the undo disks feature is configured on a per virtual machine basis. Each attached virtual hard disk will generate a file with a .vud extension, which is essentially a virtual hard disk in disguise and gets created in the same folder as the original .vhd. This behavior can be turned on or off with the objVM.Undoable read/write property and is exposed as “Enable undo disks” on the Administration Web site (“virtual machine name” Configuration | “Hard disks”). Once enabled, changes will accumulate in the .vud undo file instead of the original .vhd. File size will continue to grow and can have a serious effect on performance. Therefore, if the size of the undo .vud file gets too large, consider merging (or committing) it back to the original .vhd via objVM.MergeUndoDisks(), or discard it by calling objVM.DiscardUndoDisks(). If the host drive runs out of working disk space, Virtual Server will automatically pause all running virtual machines until you address this critical condition.
Finally, you can configure the default action to be performed on all undo drives when the virtual machine is shut down with the objVM.UndoAction read/write property. Valid values are vmUndoAction_Discard (0), vmUndoAction_Keep (1) or vmUndoAction_Commit (2).
Microsoft has publicly made available the “VHD Image Format Specification Document” under the Open Specification Promise (OSP) program. This initiative is a boost to third-party adoption and development of interoperable solutions based on this format, and will find its way into the ecosystem around flagship products such as Windows Vista and “Longhorn” Server (more info here).
PrimalScript: Pure and Simple |
Are you still using a basic editor such as Notepad to develop all your Virtual Server scripts? Or have you recognized Notepad’s limitations and are hunting for a modern, feature-rich and robust editing environment without the unnecessary complexity of a full-scale developer’s workbench?
There are many excellent choices out there, such as UltraEdit, MultiEdit or TextPad -- even Visual Studio, just to name a few. I recommend that you look at SAPIEN Technologies’ PrimalScript. On top of an already impressive list of supported programming, scripting and other languages, it has sophisticated features such as team source control, Windows PowerShell support, live syntax checking (analogous to Microsoft Word’s spelling checker), powerful and integrated Windows script debugger, flexible customization options and third-party support, all designed to ease the life of an IT administrator.
You may wonder how an investment in an advanced development environment would support your daily scripting needs. One great timesaver for novices to overcome the initial learning curve and seasoned programmers alike is the ability to identify the correct methods, parameters or properties for a specific object. This is where PrimalSense, a technology made popular by Microsoft’s Intellisense, becomes very practical.
|
Figure 1. PrimalSense works like Intellisense, intelligently guessing what method, parameter or property you might want to call up in your scripts. |
An alternative is to make use of the extensive COM Type Library Browser to explore your host system’s repository of registered COM components in its native form. This allows you to gain access to important information such as interfaces, methods and properties that are often hard to find, inadequate or undocumented from the official sources.
How about another time-saving technique? Simply type ifelse then hit Ctrl+J and the relevant snippet of code is automatically filled in with the cursor correctly positioned. You can even build and save favorite code snippets and reuse them in a similar fashion. Code folding is another useful editing feature that assists in keeping your code tidy and manageable. This is not restricted to standard functions or subroutines, and you're free to mark a user-defined block of code for folding. Lastly, one of the biggest challenges working with the undocumented Virtual Server WMI interfaces can be alleviated by deploying the WMI Wizard when you are ready to venture into this largely uncharted territory.
All in all, a product such as PrimalScript offers a highly dependable platform that helps the busy IT administrator get up to speed quickly, resulting in the delivery of more robust code in an efficient and productive manner. PrimalScript is an indispensable item that belongs in every IT administrator’s scripting toolbox. |
|
|
Go Forth and Script Virtually
Scripting Virtual Server can be a challenging and rewarding proposition. Getting a good grasp on the basic working of virtual machines can empower you with much-needed knowledge to better administer and protect your investment in virtualization. Fortunately, your job today as an IT administrator is lightened with the right choice of tools and availability of extensive range of excellent resources to help hone your Virtual Server scripting skills.