Thursday 3 April 2014

Automatically Deploying IIS Express with your Web-based Software

Why IIS Express?
While a lot of businesses use Microsoft’s IIS to host their websites, a Microsoft Server operating system with a full IIS suite is not always necessary, and sometimes too complicated for the average user to configure.

Microsoft provides a free alternative – IIS Express – which provides the user with basic web hosting capabilities. However, automating the installation and configuration of IIS Express is not as easy as it could be. The process is explained in detail here.

Context
At Digiata Technologies, we needed to simplify the installation and setup process for our software package, Stadium 5.

Stadium 5 requires a web server for users to deploy applications they create. Previous versions of Stadium assumed the user had IIS installed and configured. We needed to remove the dependency, and therefore install IIS Express if the user didn’t have IIS installed. Not all users would be installing on Microsoft Server 2012 or similar operating systems. Similarly, not all users would have IIS installed on their operating systems – so we needed to automatically install IIS Express, should the user not have IIS.

The ideal result would be to completely protect users from any IIS or IIS Express installation or configuration steps, with our software acting as a layer between IIS Express and the user-created web applications.

Benefits of IIS Express
IIS Express 8.0 is the relatively new, lightweight and free brother to IIS. You would consider using it if:
·         You don’t want to pay for a Server package with IIS
·         Your software needs to deploy in an environment that doesn’t necessarily have IIS installed, and you don’t want the users to have to install a web server themselves
·         You don’t need all the features of IIS
·         You need Microsoft support for your product

Overall IIS structure and functionality
IIS Express is not entirely intuitive. It is quite different to IIS, and does not come with a Graphical User Interface (GUI) like IIS, but rather relies purely on .config file manipulation.

·         The installation path is defaulted to your Program Files folder
·         As each user runs the iisexpress.exe process, located in the installation path, IIS Express creates a home directory for that user in their Documents folder. Configuration files for that instance of IIS Express are stored in that folder
·         The iisexpress.exe process runs as the NT AUTHORITY\SYSTEM user. Should you access databases from your web application, it will use that user to access the database.
·         Each user’s applicationHost.config file in his home directory stores that user’s web application information in XML format. As a result, each user will have his own web application list

In some cases, such as ours, it is not desirable to have different instances of IIS Express for different users. Unfortunately, it’s not very simple to run one instance of IIS Express between multiple users:

Automatically installing IIS Express
We used the WIX Toolset to build our installer file. The installer downloads and installs IIS Express if no IIS is installed. This functionality could easily be replicated with other install kits.






Running IIS Express between multiple users
When you run iisexpress.exe from the command line, you can specify the flag /userhome:[home], where [home] can point to any directory. Using this means you can have a central IIS Express configuration file in a location such as your C:\Users\Public\Documents folder.  Beware of choosing a folder that some users don’t have access to.

The first time this command is run, the configuration files will be created in the userhome directory if they do not yet exist.

However, when setting a common userhome, we encountered a problem with using the /userhome flag:

IIS Express could no longer locate the aspnet.config file, because %IIS_USER_HOME% variable that IIS Express uses became invalid.  This caused errors when trying to run a web service from IIS Express.

No solution could be found to the problem apart from programmatically hard coding the applicationHost.config file in the userhome directory to point to the aspnet.config file. This involved simply searching for instances of %IIS_USER_HOME% and replacing them with the actual directory location. This was achieved by passing through the XML file to this C# method:







Programmatically adding an application to IIS Express
IIS Express applications are all defined in the relevant applicationHost.config file (in the userhome directory – either the default one or the one you manually specified.)

Adding an application is as simple as adding a site node to the sites node in the config file. The physicalPath attribute points to the path containing the web application’s files.


Programmatically launching IIS Express applications
Whether using C# or any other language to launch IIS Express, the principles remain the same.

Simply run iisexpress.exe from its installation directory with two flags:
-          /userhome:[userhome], filling in the userhome directory you’ve chosen
-          /site:[site], filling in the site name you chose for this web application

This does the following:
-          Launches IIS Express, if it is not already running
-          Creates the userhome directory with the required configuration files, if it has not yet been created
-          Attempts to open the specified site. This will output an error to the console if the site does not exist.

Conclusion
With a few minor hiccups, we managed to create a system to automatically deploy and use IIS Express to host our web applications.

Through IIS Express’s XML configuration file, programmatic manipulation of IIS Express is simple and enables you to take control of IIS Express for the user, simplifying deployment of any applications you have that need to be hosted on the user’s computer.

Although IIS Express relatively new and free to use, it is most definitely suitable for basic business functionality. If your clients don’t need to get their hands dirty with their web server software, it might be a viable option to go with IIS Express and programmatically deal with the configuration yourself.

Wednesday 19 June 2013

Automated UI WatIN Test - Setting date and format for Jquery Datepicker

I came across  situation where I have to write test case in which user will select date from calendar and that selected date and format has to asset in test case. Below DatePickerField class is useful to set date and format in JQuery Datepicker

using System;
using WatiN.Core;

namespace Twenty57.Stadium.TestHelpers.WatiNElements
{
       public class DatePickerField : Control<TextField>
       {
              public override WatiN.Core.Constraints.Constraint ElementConstraint
              {
                     get { return Find.ByClass("hasDatepicker"); }
              }

              public DateTime? Date
              {
                     get
                     {
                           if (Element.Text == null) return null;

                           var date = Eval("{0}('getDate').toUTCString()", ElementReference);

                            return DateTime.Parse(date);
                     }
                     set
                     {
                           var javascriptDate = "null";
                           if (value.HasValue)
                           {
                                  var date = value.Value;
                                  javascriptDate = string.Format("new Date({0}, {1}, {2})", date.Year, date.Month - 1, date.Day);
                           }

                           CallDatepicker("'setDate', {0}", javascriptDate);
                     }
              }

              public string DateFormat
              {
                     get { return GetOption("dateFormat"); }
              }

              public bool Enabled
              {
                     get
                     {
                           var isDisabled = CallDatepicker("'isDisabled'");
                           return !bool.Parse(isDisabled);
                     }
              }

              public string GetOption(string option)
              {
                     return CallDatepicker("'option', '{0}'", option);
              }

              private string ElementReference
              {
                     get
                     {
                           return string.Format("window.jQuery({0}).datepicker", Element.GetJavascriptElementReference());
                     }
              }

              private string CallDatepicker(string script, params object[] args)
              {
                     var theScript = string.Format(script, args);
                     return Eval("{0}({1})", ElementReference, theScript);
              }

              private string Eval(string script, params object[] args)
              {
                     return Element.DomContainer.Eval(string.Format(script, args));
              }
       }
}

[Test]
              public void TestSetDate()
              {
                     this.browser.GoTo(this.appURL + "/Page1");
                     this.browser.WaitForComplete();

                     DateTime dateToValidate = new DateTime(2013, 06, 11);               
                     string dateText = dateToValidate.ToString(Config.DateFormat);

                     string controlId = "Page1_DatePicker1";
                     DatePickerField datePicker = this.browser.Control<DatePickerField>(Find.ById(controlId));
                     Assert.IsNotNull(datePicker, string.Format("DatePicker with Id [{0}] not found.", controlId));
                     Assert.IsTrue(datePicker.Enabled, "DatePicker should be enabled.");
                     datePicker.Date = dateToValidate;
                     Assert.AreEqual(dateText, datePickerTextField.Text);
                     Assert.AreEqual(JQueryUIDatePickerHelper.GetDateFormat(), datePicker.DateFormat, "Date format is incorrect.");
}

JQueryUIDatePickerHelper is class which maps C# date format with JQuery Datepicker format.

public static class JQueryUIDatePickerHelper
       {
              public static string GetDateFormat()
              {
                     string currentFormat = Config.DateFormat;

                     currentFormat = currentFormat.Replace("dddd", "DD");
                     currentFormat = currentFormat.Replace("ddd", "D");

                     if (currentFormat.Contains("MMMM"))
                     {
                           currentFormat = currentFormat.Replace("MMMM", "MM");
                     }
                     else if (currentFormat.Contains("MMM"))
                     {
                           currentFormat = currentFormat.Replace("MMM", "M");
                     }
                     else if (currentFormat.Contains("MM"))
                     {
                           currentFormat = currentFormat.Replace("MM", "mm");
                     }
                     else
                     {
                           currentFormat = currentFormat.Replace("M", "m");
                     }

                     currentFormat = currentFormat.Contains("yyyy") ? currentFormat.Replace("yyyy", "yy") : currentFormat.Replace("yy", "y");

                     return currentFormat;
              }

       }

Automated UI WatiN Test - JavaScript Keyup event not firing

While writing UI test case for textbox validator I came across a problem that JavaScript key up event is not firing. Below extension method  “ForceKeyUpEvent”  helps to fire JavaScript event.
using System;
using WatiN.Core;

namespace Twenty57.Stadium.TestHelpers.WatiNElements
{
       public static class WatiNExtensions
       {
              public static void ForceKeyUpEvent(this Element e)
              {
                     e.DomContainer.Eval("$('#" + e.Id + "').keyup();");
              }
       }
}

[Test]
              public void TestAmountValidator()
              {
                     string validatorControlID = "Page1_AmountTextBox";
                     string validatorMessage = "Please enter a valid amount";

                     this.browser.GoTo(this.appURL + "/Page1");
                     this.browser.WaitForComplete();

                     TextField textbox = this.browser.TextField(Find.ById(validatorControlID));
                     Assert.IsNotNull(textbox, string.Format("Validator textbox with Id [{0}] not found.", validatorControlID));
                     textbox.TypeText("Amount Validator");
                     textbox.ForceKeyUpEvent();
                     this.browser.WaitForComplete();

                     string errorspanId = validatorControlID + "_Error";
                     Span errorspan = this.browser.Span(Find.ById(errorspanId));
                     Assert.IsNotNull(errorspan, string.Format("Validator error span with Id [{0}] not found.", errorspanId));
                     if (validatorMessage == null)
                     {
                           Assert.AreEqual("none", errorspan.Style.Display, "Validation message display is not correct.");
                     }
                     else
                     {
                           Assert.AreEqual("inline", errorspan.Style.Display, "Validation message display is not correct.");
                           Assert.AreEqual(validatorMessage, errorspan.Text, "Validator message is not correct.");
                     }

              }

Monday 10 June 2013

Enabling Windows AutoAdminLogon with a batch file (Server 2008 R2)

When using Windows Azure, I came across the problem of needing to enable Windows' AutoAdminLogon automatically, upon the first startup of a VM.

The following script enables AutoAdminLogon once, upon startup, without a user being logged in. It then restarts the computer to apply the settings, and logs the configured user in automatically. A "Restarted" registry entry makes sure restart only happens once.



Store this script in a .bat file, and run the script on startup using Server 2008 R2's task scheduler. http://www.sevenforums.com/tutorials/67503-task-create-run-program-startup-log.html has a handy tutorial on how to do this.

Launching VMs from images and capturing images from VMs with the Azure REST API (C#)

Windows Azure's REST API is a web service which you can use to interact with your Azure subscription.

To use it, you first need to create and a certificate to access your subscription with the API. Microsoft describe the process quite well here: http://msdn.microsoft.com/en-us/library/windowsazure/gg551722.aspx

Once you have created your certificate and uploaded it on the Azure portal, you can use the code provided by Microsoft to use your new certificate in your C# project: http://msdn.microsoft.com/en-us/library/windowsazure/ee460782.aspx

A general method to call REST API XML

All requests can be generated through this code. You pass through the XML, the request URL, and request type. This request object stores a request ID which can be used to check the request status.



Creating a cloud service

Before launching a VM from an image, you need to create a cloud service for it. You need to have a cloud service for each image created.

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices
Request type: POST
Return the XML required with the following method:



Launching a VM from an image

Once you have manually created an image on the Azure portal, you can launch it from the REST API as follows:

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments
You need to use the service name of the service you just created.
Request type: POST
Return the XML with the following method:



Capturing an image from a VM

When you have finished working on the VM, it is important that the VM runs Sysprep.exe and shuts itself down. This can be done in a batch file with the following command:



When the VM is shut down, you can call the following code to capture an image: (Note that you can't overwrite images - you need to delete the old one first if you want the image to have the same name)

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments/[deployment-name]/roleinstances/[vm-name]/operations

You need to use the service name, deployment name and machine name configured earlier.

Request type: POST
Return the XML with the following method:



Deleting and discarding your VM

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments/[vm-name]
You need to use the names of the service and virtual machines created earlier.
Request type: DELETE
No request XML required.

Deleting your cloud service

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]
You need to use the service name of the service you just created.
Request type: DELETE
No request XML required.

Checking the result of a REST API call

The REST API provides a way to check the result of your call. The first block of code described in this post includes a method MakeStatusRequest, which returns a 0 if a "successful" result is returned by Azure. Provide it with the URL https://management.core.windows.net/[subscription-id]/operations/[request-id]  (where request-id is the requestID of the request you passed earlier)

It is useful to put this in a loop which checks for a successful result, and returns failure upon timeout. I call it as such:





Using Windows Azure as a QA environment

I've recently been tasked with investigating whether or not Microsoft's Windows Azure cloud service is suitable for a Quality Assurance (QA) environment for our company software.

TL;DR: Azure is currently unsuitable for our QA environment, because of Azure's requirement of running Sysprep.exe before creating an image. Sysprep, and the problems caused by it, make our Upgrade test workflow practically unachievable.

What we require from a QA environment:

  • A Windows Server 2012 build server to kick off software builds and to store them. The build server needs to be up all the time.  
  • Windows Server 2008 R2 images of Virtual Machines (VMs). Each software package we own needs two of these - an Install VM to test a fresh install, and an Upgrade VM to test an upgrade from the previous version.

Our QA process:

  • At a scheduled time, the build server kicks off NAnt scripts which grabs our source code from our repository on the cloud, compiles it, and uses the WiX toolset to build an installer file for our software.
  • The build server launches a VM without the software installed. This "Install" VM uses automatically launched NAnt scripts to grab the install file from the build server, install the software, and run NUnit tests. The Install VM writes back a result to the build server. The install VM is then reverted back to its previous state.
  • If the Install VM's tests passed, the build server launches an "Upgrade" VM which has our software installed on it. The Upgrade VM uses the same install file to upgrade it's software, and tests are run. 
  • If the Upgrade VM's tests passed, the build server saves the state of the Upgrade VM, and publishes the install file.

Windows Azure

Azure seems to have been developed as a one-model-fits-all cloud service. The bits of interest to our QA process are:
  • Virtual machine instances: A virtual machine which can be running or stopped (shut down). You can only capture an image from an instance if the virtual machine is stopped. 
  • Images: Captured images of virtual machine instances. Virtual machines can be launched from these images.
  • Disks: Disks used by current or previous virtual machines. Disks have to be manually deleted after use.
  • Cloud services: In the QA process, a cloud service is essentially just a DNS with a web address to access each Virtual Machine instance you launch.
  • Storage: A storage account with a specific location. For QA purposes, we used only one storage account in one location. You pay for any data transfer between locations.
  • Virtual networks: Used to put all of your QA VMs on the same network, so they can communicate.

Windows Azure's REST API

I only used Azure's REST API, which provides enough functionality to create cloud services, launch VMs from images, capture images from VMs and delete disks.

In a future post, I'll explain how to use the API to launch VMs and play around with them.

Problems with Azure

Azure's model was intuitive enough, but after fiddling around with their services for a few days, I realised the following problems.
  • Azure provides no decent form of snapshotting. If you want to save the state of a VM, you need to run sysprep.exe, shut down the VM, capture an image, and launch it from scratch later on.
  • Sysprep.exe is compulsory before creating an image from a VM. takes a very long time (15-20 minutes) to run. What's more, Sysprep.exe managed to irreversibly destroy an image. Some developers claim that sysprep has a "3 generation limit," - after you run it three times, you can't run it any more without error. This may have been what destroyed my image.
  • Your main administrator account is reset when you launch a VM from an image. This means you lose your user's files, your settings, and so on. You need to create a secondary account with administrator privileges to get around this.
  • In the sysprep.exe and image launching process, some registry items like the crucial autoadminlogon settings are lost. I attempted to get around this by writing a batch file which sets the autoadminlogon registry entries on startup, and then restarting the machine to apply them. It was during testing of this script that Sysprep destroyed my image, so I never got to see the result of my batch file.
  • The REST API doesn't provide functionality to check the status of a VM. For instance, you can't check whether a VM is stopped or running, which you need to know to capture an image from a VM.  To get around this, your scripts to capture an image from a VM need to loop a preprogrammed amount of times, expect error codes, and wait until Azure returns a positive result code.
  • There is currently no way of giving your VMs static IP addresses. You also can't use the REST API to check a VMs IP address on the virtual network, which would be a very useful feature, Microsoft. Cough.

Conclusion

With all of these problems, it is difficult, but possible, to host a QA environment on Azure, provided that the QA VMs don't need to save state after each test. If you'll be launching the same non-updated image every test, then Azure might work for you.

However, our upgrade test workflow was not achievable with Azure. With enough effort, it might have been, but Sysprep.exe and other Azure hurdles just made it too much effort.

In the next few posts I will get into some of the technical aspects of using Azure for QA environment.