HaveComputerWillCode.Com

Welcome!
Life is a Non-Deterministic Finite State Automata
Automation ? (*pGeekiness)++ : Code /eneration;

June 15, 2012

Automating the creation of standard environments using the VS2012 RC API (Update)

The full source code for this article can be downloaded here (V1.1) It is built against the 2012 RC version.

UPDATE 28-June-2012: The TF259637 error has been confirmed and sounds like it will be fixed with better UI guidance in the next release. The sample code is still at V1.1 and I will update my code at RTM.

A very welcome feature in the new edition of VS2012 Lab Center is the concept of a ‘Standard Environment’. Within Lab Centre, you can add any running machine to an environment and automatically push out the Test Agent ready for distributing tests to:

This means that VmWare, Virtual Box, Virtual PC and physical machines can easily be incorporated into your Lab Center environments. This post will show how to automate that feature using the (now public) API. No need to jump through hoops anymore like in VS2010!

However, before going on, you must at least be able to push these agents out manually using the Lab Center user interface so you know your infrastructure is set up for this: ensure that your target machine has file sharing set up; that you have fixed this rather obscure registry setting if necessary and ensure that IPSec and the Firewall aren’t getting in the way. And lots more.

We will now write some code to create a new Standard Environment and push the Agent out ready to run tests:

It looks like all of the API calls to the Lab Service and supporting infrastructure are now public in MSDN. There is some very kool stuff in there!

Getting going
The first thing is to add a few references to your project: Microsoft.TeamFoundation.DLL, Microsoft.TeamFoundation.Client.DLL and Microsoft.TeamFoundation.Lab.Client.DLL (search your machine for these):

Then it’s a simple case of connecting to a Team Project…

TeamProjectPicker picker = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, false);

DialogResult result = picker.ShowDialog();
if (result != System.Windows.Forms.DialogResult.OK) return;
if (picker.SelectedProjects.Length == 0) return;

… and making the following calls to create a new environment and register it with a test controller:

LabSystemDefinition single = new LabSystemDefinition("TheMachineNameYouWantToPushTheAgentsOutTo", "TheMachineNameYouWantToPushTheAgentsOutTo", "YourMachineRole");

LabEnvironmentDefinition definition = new LabEnvironmentDefinition("The Environment Name", "The Environment Description", new List() { single });
definition.TestControllerName = "TheTestController:6901";

LabEnvironment newEnvironment = service.CreateLabEnvironment(ProjectName, definition, null, null);

There is then a nicely exposed ‘InstallTestAgent’ method that does exactly what it says on the tin:

// Download the source code to see how the credentials are set up for this call (process == null if you want to run the Test Agent as a service)
themachine.InstallTestAgent(admin, process);

THATS IT!:

The name ‘InstallTestAgent’ is a little misleading – it installs the Agent if it does not already exist and then reconfigures it.

Configuring the Agent to run tests interactively is similar: all we need to do is to provide another set of credentials that we want to run the Test Agent as on each end-point and tell the Lab Environment which machine roles require an interactive agent so the deployed agents can be configured correctly. We do this prior to creating the environment otherwise we would have to call LabService.UpdateLabEnvironment afterwards:

definition.CodedUIRole = p.MachineRoles;
definition.CodedUIUserName =  String.Format("{0}\\{1}", p.InteractiveCredentials.Domain, p.InteractiveCredentials.UserName);

IS THIS A BUG?
I had issues getting my Lab Center to push out a test agent across *Workgroups* to run interactively even when I drive the operation manually from the Lab Center UI (not the API): this happened from a completely fresh install or otherwise. After pushing out the Agent, rebooting and automatically logging in, the Lab Center UI would keep hitting me with error “TF259637: The test machine configuration does not match the configuration on the environment. The account…”. The benefit of a public API is that it lets us investigate! The error appears to be the way Lab Center stores and/or validates its LabEnvironment.CodedUIUserName and LabSystem.Configuration.ConfiguredUserName parameters when distributing a Test Agent across Workgroups. The LabEnvironment.CodedUIUserName was set to ‘W732AGENTS\Graham’ (the value I entered in the Lab Center UI because that is what I want to run the Agent as on the end-point) whereas the LabSystem.Configuration.ConfiguredUserName property was set to .\Graham. Clearly a mismatch. To fix it, it seems all we need to do is sync the two. I need to be clear [especially given how the above code snippet obviously creates this problem!]: the issue occurs when driving the Lab Center UI manually, so it is not specific to the API or this sample. For the sample, I have chosen to mimic (what I think is) the behaviour of the Lab Center UI.

I have posted an issue with Connect with more information to seek clarification – please see ID: 749436.

If deploying Agents to Workgroups, you might get the TF2569637 error. I have left my source code to pass parameters to the API the same way that the Lab Center UI RC appears to (ie: without validation or guidance) so I attempt to deal with the error and automatically fix it post-deployment. It actually makes things more robust anyhow. I will update my code to reflect Lab Center UI changes post-RC:

var theEnvironment = service.QueryLabEnvironments(new LabEnvironmentQuerySpec() { Project = ProjectName }).First(f => f.Name == p.EnvironmentName);
var theMachine = theEnvironment.LabSystems.First(f => f.Name == p.MachineName);

string testAgentRunningAs = theMachine.Configuration.ConfiguredUserName;
string environmentThinksTestAgentRunningAsd = theEnvironment.CodedUIUserName;

if (String.Compare(testAgentRunningAs, environmentThinksTestAgentRunningAsd, true) != 0)
{
    // Synchronize the user names... 
    service.UpdateLabEnvironment(theEnvironment.Uri, new LabEnvironmentUpdatePack() { CodedUIUserName = testAgentRunningAs });
}

You can also use that snippet to fix a manually-deployed environment that is broken with error TF259637.

Putting it all together, you can can download this sample here. :

It looks like there is a slightly more flexible way of doing the installation of the test agents using a combination of the TestAgentDeploy class and the AMLCommandBase-derived classes. But perhaps more on that some other time!

Enjoy!

December 3, 2010

Launching LEViewer.EXE from within SCVMM

Filed under: ALM,Testing — Tags: , , , — admin @ 8:31 am

(and using SCVMM PowerShell to query running Lab Center Environments)

You can download the PowerShell script here (CORRECTION: 4th December: I fixed a bug in Filter-LabMachines so it returns $machine instead of $InputObject).

The PowerShell interface that drives SCVMM is awesome but it doesn’t provide anything out of the box that helps you write queries against virtual machines deployed by Lab Center. As far as SCVMM is concerned, any VM’s deployed by Lab Center are just VM’s. This post will (eventually) provide a few crude helper methods so you can use PowerShell to show all running virtual machines grouped by Project, Lab Center environment or whatever.

When using Lab Center and you right click on a running environment and select “Connect”, it launches the Lab Environment Viewer (LEViewer.EXE):

Ultimately, the virtual machine(s) you connect to using LEViewer.EXE are running on a Hyper-V host managed by SCVMM in a mysterious part of your Enterprise. For reasons of pure geekiness, I wanted to launch that viewer from within the SCVMM PowerShell console based on Virtual Machine queries. How?

By using Process Explorer, it’s easy to inspect the running LEViewer.EXE command line and you’ll see something like this:

Kool! So if we can get at the TFS Collection, the Environment and the Virtual Machine we want to connect to from within SCVMM, we can launch LEViewer in that context from PowerShell. We can do that!

When you deploy a virtual machine using Lab Center, it is given an aesthetically pleasing random-GUID name like so:

But it’s “Description” field within SCVMM yields all of the useful information we require (the Description field is set by Lab Center). Right click the machine and select Properties:

So all we have to do is to extract that information and construct the command line. To make it easier, I’ll do things ‘the properish way’ and write helper functions that can be used in the PowerShell pipeline:

function global:Filter-LabMachines 
{
param([Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        	[PSObject[]]$InputObject)

    BEGIN 
	{
   	}	
    PROCESS 
    {
        	foreach ($machine in $InputObject) 
		{
			try
			{
				# Turn the Description into an Object so we can walk it. [xml] has to be one of the most underrated PowerShell features *EVER!*
				$labDescription = [xml] $machine.Description;
				$crudeThrowawayChecker = $labDescription.LabManagement.LabEnvironment;
		
				# Add some dynamic member properties
				Add-Member -MemberType NoteProperty -Name LabTfs -InputObject $machine -Value $labDescription.LabManagement.TFS;
				Add-Member -MemberType NoteProperty -Name LabProject -InputObject $machine -Value $labDescription.LabManagement.Project;
				Add-Member -MemberType NoteProperty -Name LabProjectCollection -InputObject $machine -Value $labDescription.LabManagement.Collection;

				Add-Member -MemberType NoteProperty -Name LabEnvironmentId -InputObject $machine -Value $labDescription.LabManagement.LabEnvironment.Id;
				Add-Member -MemberType NoteProperty -Name LabEnvironmentName -InputObject $machine -Value $labDescription.LabManagement.LabEnvironment.InnerText;

				Add-Member -MemberType NoteProperty -Name LabSystemId -InputObject $machine -Value $labDescription.LabManagement.LabSystem.Id;
				Add-Member -MemberType NoteProperty -Name LabSystemName -InputObject $machine -Value $labDescription.LabManagement.LabSystem.InnerText;

				# If we get to here, it is probably a candidate for being a Lab-Deployed Virtual Machine
			        $machine;
			}
			catch
			{
	
			}
		}
        }
    	END 
	{
    	}
}

The idea is that you use something like this to identify all Virtual Machines managed by Lab Center:

Get-VM | Filter-LabMachines

Get-VM is part of SCVMM; when the virtual machines are piped through our Filter-LabMachines, we only pass lab machines down the pipeline. I have decided that a VM is a Lab Machine if it has a ‘Description’ field that looks like XML. The additional crude test for this is to use the [xml] caster in PowerShell and then walk the object to extract the properties we need. Note the use of ‘Add-Member’: I augment the $machine with the value of the Lab properties so I don’t have to recalculate them. The PowerShell type system *ROCKS!*

So with a list of machines that were (probably) deployed by Lab Center, we can now dump the SCVMM Name and the name of the Virtual Machine as seen in Lab Center (see LEViewer.EXE above):

Get-VM | Filter-LabMachines | Select-Object -Property LabProject,LabEnvironmentName,LabSystemName

So it looks like this:

Now we can use PowerShell to show all running Lab Center environments and the projects to which they belong. ie:

Get-VM | Filter-LabMachines | Where-Object { $_.Status -eq "Running" } | Select-Object -Property LabEnvironmentName,LabSystemName | Sort-Object -Property LabEnvironmentName | Format-Table -GroupBy LabEnvironmentName

And finally to launch LEViewer to connect to our environment from PowerShell in SCVMM, we just need to pick an environment with a running virtual machine and pipe it into Start-LEViewer:

Get-VM | Filter-LabMachines | Where-Object { $_.LabProject -eq "Grom" } | Where-Object { $_.Status -eq "Running" } | Sort -Property LabEnvironmentName -Unique | Start-LEViewer

The helper method ‘Start-LEViewer’ is here:

function global:Start-LEViewer 
{
param([Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        	[object[]]$InputObject)

    BEGIN 
	{
    }	
    PROCESS 
	{
        foreach ($machine in $InputObject) 
		{
			# Turn the Description into an Object so we can walk it. [xml] has to be one of the most underrated PowerShell features *EVER!*
			$labDescription = [xml] $machine.Description;
		
			# The Command Line of a spawned LEViewer looks something like this:
			#
			# "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\leviewer.exe" /tfsUri:http://win-tskurfclu7g:8080/tfs/defaultcollection /environmentUri:vstfs:///LabManagement/LabEnvironment/192 /systemUri:vstfs:///LabManagement/LabSystem/193
				
			$executable = "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\leviewer.exe";
			$p = "/tfsUri:$($labDescription.LabManagement.TFS)/$($labDescription.LabManagement.Collection) /environmentUri:vstfs:///LabManagement/LabEnvironment/$($labDescription.LabManagement.LabEnvironment.Id) /systemUri:vstfs:///LabManagement/LabSystem/$($labDescription.LabManagement.LabSystem.Id)";
 
			[System.Diagnostics.Process]::Start($executable, $p);
		}
	}
	END
	{
	}
}

Thanks to this link for information on writing PowerShell Advanced Functions to use the Pipeline.

Tchau!

October 1, 2010

Long Path Error in MTM / Lab Center / CodedUI

Filed under: ALM,Testing — Tags: , , , , , — admin @ 6:01 am

I was trying to get my CodedUI Tests to run automatically on a Virtual Environment using MTM when I got this error:

"TestOutcome 'Error'; Message 'The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters'."

What a fascinating, informative error message! What path? What filename? Tip guys: String.Format(“{x}”) is powerful conveyer of information! The same problem occured when I kicked off a build with an automated build-deploy-test cycle: all of my tests were marked ‘Not Executed’ and marked as ‘Inconclusive’ with no indication in the log or summary as to why.

A period of much head scratching and elevated blood pressure ensued.

After some investigation and turning on the logging of the Agents and Controllers on the Virtual Machine, and examining all the various .logs, it turns out that the Test Controller (QTController.EXE) on the Virtual Machine uses Application Data as a stem and then adds a Guid and the name of your Virtual Machine to the end of the path: that is the working / staging directory. Ultimately, I had cunningly given my Virtual Machines descriptive names within the Lab Center Environment so I knew what they were but this caused the path limit exception. Not wise!

Rather than recreate my Golden Environment again, I went into QTController.exe.config (search your Virtual Machine for this) and saw this:

   

Nice! So I modified the QTController.exe.config by adding a key under appSettings in that file:

   <appSettings>
      <add key="WorkingDirectory" value="C:\Woo" />
      <add key="LogSizeLimit" ... />
      ... etc ...
   </appSettings>

After rebooting the Virtual Machine, my tests started to work but that only gave me another 30 or so characters to play with.

Ultimately, when you create your Environments, you might need to give your computer a shorter name (taken to the extreme below!) because the controller uses it as part of its path:

Tchau!

September 22, 2010

Automating Microsoft Lab Center API/SDK: Part 4

Filed under: ALM — Tags: , , — admin @ 9:51 am

This is a multi-part post for programmatically automating Microsoft Lab Center 2010 / Visual Studio Lab Management 2010 / Lab Manager 2010 (not VS/TFS2012) via C#:

  • Part 1Source Code – Enumerate Environments
  • Part 2Source Code – Deploy Environment
  • Part 3Source Code – Deploy Environment, Monitor Operations, Start, Stop, Delete Environment
  • Part 4 – Source Code – Part 3 + Network Isolation, RDP/Connect, Thumbnail Control
  • The entry point to the API / SDK documentation is here via the GetService call (LabService is the main one, but also see LabAdminService and LabFrameworkService)

    Getting in…
    When an environment is running within Lab Center, how do you programmatically ‘get inside’ that Environment and find out the names of the running machines? What about the internal and external computer names? In the case of a Network Isolated environment that has been deployed, you need to know the world-facing DNS and IP Address name so that you can connect to it remotely or initiate custom deployments.

    How to do this?

    I’ve modified the UI so that each deployed environment lists it’s virtual machines as children. Each LabEnvironment object has a LabSystems property containing a list of all virtual machines contained within. I mean, really: could it be any easier?! Of interest to us are the ‘LabSystem.IsolationOptions’ through which we can find the Internal and External computer names:

    The code of interest to us is:

                    // ls==LabSystem
                    if (ls.ExtendedInfo != null)
                    {
                        string roles = String.Format("Roles: {0}", ls.Roles);
                        string hostOs = String.Format("Guess Operation System: {0}", ls.ExtendedInfo.GuestOperatingSystem);
                        string hasVmAdditions = String.Format("Has Vm Additions: {0}", ls.ExtendedInfo.HasVMAdditions);
                        
                        // This is the Hyper-V System hosting this virtual machine. 
                        string hostname = String.Format("Virutalization Host Name: {0}", ls.ExtendedInfo.HostName);
    
                        string remoteSessionInfo = "Remote Session Info: (Not Available)";
    
                        if (ls.ExtendedInfo.RemoteInfo != null)
                        {
                            // The DNS name we will use to connect to a machine in a Network Isolated environment. 
                            string computerName = String.Format("Computer Name: {0}", ls.ExtendedInfo.RemoteInfo.ComputerName);
    
                            // The internal name of the machine on the Isolated Network. 
                            string internalName = String.Format("Internal Name: {0}", ls.ExtendedInfo.RemoteInfo.InternalComputerName);
    
                            remoteSessionInfo = String.Format("Remote Session Info:\r\n{0}\r\n{1}", computerName, internalName);
                        }
    
                        string vmInformation = String.Format("Extended Info:\r\n{0}\r\n{1}\r\n{2}\r\n{3}\r\n{4}\r\n", new object[] { hostOs, hasVmAdditions, hostname, remoteSessionInfo, roles });
    

    From the LabSystem and LabSystem.ExtendedInfo objects you can get at the OS Profile, hardware profile, roles, machine ID’s so I’ve provided a subset of those properties at the bottom… in fact, you can get at *EVERYTHING* that you normally set up and enter when you create a new environment with Lab Center. It’s easy, and it’s very easy to relate those properties to what you see in the UI.

    What good is this? A lot! After deploying or connecting to an environment you can ‘discover’ the environment, identify custom properties associated with it at the time of deployment and direct interaction with it. With this information, it is trivial to use RDP to connect through to the machine in question (I have added a ‘Connect’ button to the bottom of the dialog). In this example, I am launching MSTSC but you might be better off using an embedded RDP6 Interop so you can host the RDP session directly within your application (when using Lab Center and “Connect” to a machine the large view on the right hand side is just a hosted RDP Active-X Control…):

    string RdpPath = Environment.ExpandEnvironmentVariables(@"%windir%\system32\mstsc.exe");
    
    ProcessStartInfo psi = new ProcessStartInfo();
    
    psi.Arguments = String.Format("/v:{0}", ls.ExtendedInfo.RemoteInfo.ComputerName);
    psi.FileName = RdpPath;
    
    Process.Start(psi);
    

    But something is missing! When using Lab Center, you get a really cool Thumbnail view of each host:

    I WANT THAT! Where does that come from?! Not from Lab Center: it comes from Hyper-V. There is a class called ‘SystemThumbnail’ in *.LEViewer.DLL but didn’t pursue it because it’s not part of the API Documentation I could find. Instead, I found a post that explains how you obtain an image of a running environment on Hyper-V. I won’t elaborate (all the code is there and *ALL* credit goes to that guy):

    I modified a few things so that it did not actually save a Thumbnail file – instead, I just extract the returned Bitmap and assign it as the BackgroundImage on the control:

    Thumbnail thumbnail = new Thumbnail(Width, this.Height);
    Bitmap result = thumbnail.GetVirtualSystemThumbnailImage(LabSystem.VMName, LabSystem.ExtendedInfo.HostName);
    
    this.BackgroundImage = result;
    

    It is set to refresh every 5 seconds at the moment and you need to have a virtual machine (not environment) selected for it to appear… be patient :-)

    Recall that when you deploy an Environment in Lab Center, you chose a Host Group to apply it to – the ultimate Hyper-V Host it was deployed to (based on how SCVMM calculated the resources and suitability for the environment you are deploying) can be obtained with a call to theLabSystem.ExtendedInfo.HostName.

    I’ve wrapped the whole thing up in ‘ThumbnailControl.cs’ (without any error checking) which works the same as ‘EnvironmentMonitorControl.cs’ – you Bind a Virtual Machine to it, and every few seconds it will refresh the display.

    Tchau!

    Older Posts »

    Powered by WordPress