Provisioning WebDeploy in VSTS/TFS release via DSC script

It’s been a while since we got at our disposition some great tooling for the provisioning of our machines. Unfortunately, it is not as used as I would like it to be. For us working on Microsoft platform, there is a tool that can do the job, available out of the box, for free, on every modern windows machine. Obviously, I’m talking about the Windows PowerShell Desired State Configuration or for short, DSC. Although not truly a provisioning tool and more as Microsoft defines it, “a management platform in PowerShell that enables you to manage your IT and development infrastructure with the configuration as code”, it will get easily job done when it comes to provisioning tooling and features.

After this long introduction lets cut the chase. In this post I’m going to show you how to write a DSC script which will make sure that the desired IIS components are installed on a given machine, check for Microsoft WebDeploy and eventually install all of those if not present. Once the script is ready, I’ll show you how to execute it during the deployment of your project in a VSTS/TFS Release. I will not get in details of how does DSC work, how to write DSC configuration functions or create your custom DSC Configuration Resources. I’ll focus on a big picture, on how to combine all of the necessary to actually get the work done. When it comes to the details, it’s quite easy to find the necessary technical guidance by just googling the desired terms.

I wrote a script that, given a machine name, will make sure a DSC configuration is applied to it.

param(
    [parameter(Mandatory=$true)]
    [string]
    $ServerName
)

$ConfigurationData = @{
    AllNodes = @(
        @{
            NodeName=$ServerName
            PSDscAllowPlainTextPassword=$true
            RebootNodeIfNeeded = $true
         }
   )
}

Configuration DashboardProvisioning
{
 	Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
     
	Node $AllNodes.NodeName 
     {
        WindowsFeature IIS
        {
            Ensure = "Present"
            Name = "Web-Server"
        }

        WindowsFeature IISManagementTools
        {
            Ensure = "Present"
            Name = "Web-Mgmt-Tools"
            DependsOn='[WindowsFeature]IIS'
        }

        WindowsFeature IISAspNet45
        {
            Ensure = "Present"
            Name = "Web-Asp-Net45"
            DependsOn='[WindowsFeature]IIS'
        }

        WindowsFeature WebManagementService
        {
            Ensure = "Present"
            Name = "Web-Mgmt-Service"
            DependsOn='[WindowsFeature]IIS'
        }

        Package WebDeploy
        {
             Ensure = "Present"
             Path  = "\\MyShareServer\Software\WebDeploy_amd64_en-US.msi"
             Name = "Microsoft Web Deploy 3.6"
             LogPath = "$Env:SystemDrive\temp\logoutput.txt"
             ProductId = "6773A61D-755B-4F74-95CC-97920E45E696"
             Arguments = "LicenseAccepted='0' ADDLOCAL=ALL"
        }
    }
}

DashboardProvisioning -ConfigurationData $ConfigurationData

Start-DscConfiguration -Path .\DashboardProvisioning -Wait -Force -Verbose

The configuration part will make sure that three Windows features are installed and those are all IIS components, necessary for my website to run. The last part, package configuration entry, is making sure that WebDeploy is present, more precisely version 3.6 of WebDeploy. In case it is not installed on the given machine it will run the installer that is located in this particular case on a share at “\\MyShareServer\Software\WebDeploy_amd64_en-US.msi“. You will need to adjust this setting and adapt it to the path where you have placed the msi installer of WebDeploy 3.6. The agent that executes this configuration script will need to have the sufficient rights to access and read that path.

You can manually test this script from your local PC in order to make sure that is working as expected. Once ready we will execute this script in our deployment pipeline.

An example of the invocation is shown in the following screenshot:

As you can see, I’m using the simple PowerShell build task to run my script and as the argument, I’m passing in the machine name, FQDN of my web server in that particular environment. It is that simple! Now, before I do try to copy my files, create and deploy my website, I’m sure that all of the prerequisites are in place so that my deployment can succeed. This step takes a very short time to execute in case the configuration that I specified is already in place. A major benefit is that I can start with a clean machine and my deployment will take care that all of the necessary is in place before proceeding with the actual deployment. In a more complex environment, this will bring consistency in the configuration between different machines and environments and reduce the manual interventions regarding the configuration to a bare minimum.

Once you start testing, make sure that Windows Management Framework of at least version 4 is installed on both your build server and the destination machine and that WinRM is set up, again, for both of these machines.

Once successful I’ll encourage you to extend this script with all of your custom configuration settings, necessary for your application to run.

Cheers!

Detailing TFS configuration – IIS

One of the most annoying things when it comes to accessing the TFS portal is that you need to specify the context path /tfs. In other words, if you just type http://mytfs.com:8080 you will not get redirected to your application, which has the full path of http://mytfs.com:8080/tfs. Same if SSL is used, often and by default you are obliged to indicate the https or even worst in case SSL is made mandatory, you will get a nice 403.

Well this is a bit of shame. Many people standing behind and supporting TFS are often not keen to set this details up. This can be because of the leak of knowledge, “fear” of the unknown, negligence, character. It is such a simple operation that shows that you do care. So let’s check a couple of things you can do in order to make this happen.

The Beauty Is In the Details

There are several improvements that we can make on IIS that is running our TFS instance. I will try to make you a couple of suggestion, if some of them can’t apply on your case by any reason, it is not mandatory to set them. Following are just suggestions on how to tide up your default TFS installation.

The cleanup

Often I do see on the IIS of TFS server a Default Web Site. In 99,9% of the cases it is not used. If that is also your case (running only TFS on that machine) you are safe to remove it.

iis-initial

As you can see, aside of the Team Foundation Server site, there is the Default Web Site in my case. I will just right click it and choose Remove.

remove-default

After you removed the Default Web Site, you can do the same for all unused Application pools.

app-pool-remove

As from the image, get to the Application Pools and remove all of the pools which name doesn’t start on per Microsoft Team Foundation.

The redirect

It will be handy that in the browser you do not need to type over and over the context path of /tfs. In order to set this up we can leverage the Http Redirect feature of IIS. By default it is not installed thus we will need to add it. Open Server Manager and choose Manage -> Add Roles and Features.

server-manager-add-role

Now get to the Server Roles and under Web Server (IIS) – Web Server – Common HTTP Features select the HTTP Redirects.

add-roles-http-redirection

Conclude the installation procedure and restart the IIS Manager.
Now after selecting your Team Foundation Server site, you will see the HTTP Redirect feature.

http-redirect

Select this option by double clicking it and enable the Redirect Requests as set on the following image.

set-redirect

In the text box you will need to enter the full URL of your TFS comprehensive of the context path. Now, once the IIS recieves a request towards the root of your application it will redirect it towards your TFS application, called tfs. Make sure before Applying these settings that the Only redirect requests to content in this directory (not subdirectories) is selected, otherwise all of your calls will result in an recursive redirect. Apply these settings and try calling your server without the context path. If you are monitoring your web calls with tools like Fiddler, you will see that your first call is redirected by the server towards the URL we specified under the Redirect request option.

This technique is only working with your portal and browser. You will always need to specify the full URL in your Visual Studio or any other tooling that requires the TFS path. This is because they are not able to understand the redirect and act in the way your browser does. Keep this in mind.

Connecting people

By default TFS will set it’s default port to 8080. Again if it is the only application on your server it is a shame being in need to specify the port for the each call. What about letting it replay also to a port 80, which is the default http port and doesn’t need to be specified?
Welcome bindings. Select your Team Foundation Server and chose in between the available actions the one called Bindings. You will be presented with the following screen.

site-bindings

Make sure that aside the http binding to port 8080 there is the one that binds the requests to the port 80. If it is not there, first edit the current binding of port 8080 and change it to port 80. Then add a new one and make it replay to port 8080. Click close and try calling your TFS without specifying the port 8080. Your IIS should replay correctly.

The result of this change will work with all of the tooling accessing TFS, like Visual Studio. You are now not anymore obliged to specify the port 8080.

Talking under four eyes

It may be a good idea or a necessity to use transport layer security. Enabling HTTPS on you web site is fairly simple. How to create and import a certificate is out of the scope of this post. Given that you have correctly imported a certificate into the IIS certificate store, open the site binding and add a new one.

add-site-binding-ssl

As a type choose https and select the certificate that you intend to use for TFS.
From now on, you can point to https://yourTFS/tfs and you will be using a secure connection. In case you omit the context path, you will end up on a non protected connection. To sort that out, change your redirect and make it point to the secure version of link.

Also you may desire to make the HTTPS mandatory. If that is the case, you could simply enable Require SSL option under SSL Settings for your TFS Web Site.

require-ssl

If you do so, pointing to a non SSL version of your site will result in a 403 Forbidden response. Although it is self explanatory as a message it is not nicely handled. It would be nicer if you would be redirected to the https version of your request. For that, we can use a trick. Open Error pages pane and edit the 403 page.

custom-error-403

Set the Respond with a 302 redirect and set your desired https URL. Now, instead of showing a Forbidden message, your browser will be automatically redirected to a correct link.

Note that in case you set the SSL mandatory, you need to do it for both the WebSite and the Virtual folder usually called tfs. After you set both of them to be required, you will need to open the TFS Administration console and on Application Tier screen, choose the Change URL’s action:

change-urls2

Make sure that Notification and server URL are correctly set to point to the new link, otherwise you may experience some issues with TFS Administration console and some other local tooling.

Conclusion

I showed you a couple of tips on how to set you TFS IIS in order to be more friendly in responses and to remove the unnecessary things. There are other tips I may have on this argument, however they do fall in a maintenance domain, such as managing log files, etc. Soon I will publish a separate, more detailed blog post about this argument.

Cheers

Manage with CodedUI sites that are using Windows authentication

Introduction

It can happen that you have a need to automate tests via CodedUI on Web Sites that do use Windows authentication. This means that no login forms will be presented and your browser, by default, will pass the currently logged in user credentials to the IIS. This is not handy when it comes to automated tests, as for sure, you are planning to use a specific user to run your tests and not the one that is running the test agent services.
When it comes to Internet Explorer we do have an option to set, so that the browser will always request the credentials for the authentications process. You can find this setting inside the Internet Options setting panel Security Tab:

Internet Options

In most of the cases, as you probably will be running your tests inside your intranet, you are going to select the “Local Intranet” zone and edit the settings for it by clicking the “Custom Level” button. The following screen will be shown:

Security Settings - Local Internet Zone

Once you scroll to the bottom you will find the settings in question under User authentication -> Logon. By default, it is set to “Automatic logon only in intranet zone”. If we change it to “Prompt for user name and password” you will see that once we now reopen our site, a request to insert our credentials will be presented. In this way we can login wit a different user than the one we are logged in on this system.

Note that in some cases this settings can be disabled by a Group Policy. This is often the case in the larger organizations.

This is very handy and let’s see how to achieve this via our CodedUI test.

Setting the User Logon options from code

Instead of using the interface, we are going to try to set this option through the registry. This is quite handy as it can also bypass the group Policy restriction in case we do have sufficient rights. After some Googling I came across the following page Internet Explorer security zones registry entries for advanced users. As you can see from the title it enlists all of the settable options for Internet Explorer security zones via the registry entries. In between others there is the one we are interested to, precisely, “1A00 User Authentication: Logon”.
Our task is now clear, we need to open the key SoftwareMicrosoftWindowsCurrentVersionInternet SettingsZones1 under the Current User hive and change the value of 1A00 setting.
Let’s write some code that will help us with that.

First of all I am going to declare a couple of constants that will be used in our methods and a property that will expose the interested key:

private const string LocalIntranetZoneKeyPath =
	@"SoftwareMicrosoftWindowsCurrentVersionInternet SettingsZones1";
private const string LogonSettingValueName = "1A00";

private static RegistryKey _localIntranetZone;

/// 
/// Gets a key-level node in the registry regarding the ID Local Intranet Zone settings.
/// 
protected static RegistryKey LocalIntranetZone
{
	get
	{
		if (_localIntranetZone == null)
		{
			_localIntranetZone = Registry.CurrentUser.OpenSubKey(LocalIntranetZoneKeyPath, true);
		}

		return _localIntranetZone;
	}
}

To follow are the methods that will allow me to retrieve and set the correct logon settings. In order to have a cleaner and more precise overview over the possible options, I am going to create and use an enumeration in order to manipulate them. I will call my enumeration LogonSetting and it will list all of the accepted values.

public enum LogonSetting
{
    NotSet = -1,
    AutomaticallyLogonWithCurrentUsernameAndPassword = 0x00000,
    PromptForUserNameAndPassword = 0x10000,
    AutomaticLogonOnlyInTheIntranetZone = 0x20000,
    AnonymousLogon = 0x30000
}

All of the necessary information came from the MSDN document I previously mentioned.
Plus I added a NotSet value for the cases in which the value 1A00 doesn’t exists, which is plausible in certain cases.
At the end, the two methods that will set and retrieve the values in question:

/// 
/// Sets the IE Logon setting to the desired value.
/// 
/// The desired value to assign to the Logon Setting.
public static void SetLogonSettings(LogonSetting logonSetting)
{
	if (logonSetting == LogonSetting.NotSet)
	{
		LocalIntranetZone.DeleteValue(LogonSettingValueName);
	}
	else
	{
		LocalIntranetZone.SetValue(LogonSettingValueName, (int)logonSetting);
	}
}

/// 
/// Retrieves the current IE Logon setting.
/// 
public static LogonSetting GetLogonSettings()
{
	object logonSettingValue = LocalIntranetZone.GetValue(LogonSettingValueName);

	if (logonSettingValue == null)
	{
		return LogonSetting.NotSet;
	}

	return (LogonSetting)logonSettingValue;
}

This is all of the necessary code that we need to comfortably interact with this settings.
You can note that we are using an extra state in our enumerator that indicates the value not being set at all. If that is the case, once we are setting the value, we need to eventually remove it from the registry.

Using the code in CodedUI

Once we start laying down our CodedUI test code we need to choose what strategy we are going to adopt for preforming our task of changing the IE setting. There are couple of places in which we can do that, more precisely three events we can consider for this task. CoudedUI put’s on our disposition three attributes that we can use to trigger the execution of our code at certain, predetermined moment. This attributes are respectively TestInitialize, ClassInitialize and AssemblyInitialize. You can read more about this attributes at the following page Anatomy of a Unit Test. As most of you probably already came across this attributes I will not get in the details about them and I will pick the TestInitialize which runs given code before the run of each test. Based on your situation you may prefer to perform this only once per assembly or at the class level. The choice is yours and the implementation may vary based on your needs.

What we need to do before our test is executed is:

  1. Retrieve the current value of this setting, so that we can restore it once the test is done.
  2. Change the setting to the desire state.

This is how it translates to the code:

private const LogonSetting DesiredLogonSetting = LogonSetting.PromptForUserNameAndPassword;
private LogonSetting originalLogonSetting;

[TestInitialize()]
public void MyTestInitialize()
{
    originalLogonSetting = Page.GetLogonSettings();

    if (originalLogonSetting != DesiredLogonSetting)
    {
        Page.SetLogonSettings(DesiredLogonSetting);
    }
}

As you can see we are persisting the original value inside a variable on the class level by using our previously created method GetLogonSettings() then checking if perhaps it is already set to our desired value (so that we may be do not need to change it) and if not we are using our SetLogonSettings() method to set it to the desired value.

Now our browser will be set to always prompt for user name and password. The next thing is to restore the original condition. We are going to use the antagonistic attribute to TestInitialize which is called TestCleanup.

[TestCleanup()]
public void MyTestCleanup()
{
    if (originalLogonSetting != DesiredLogonSetting)
    {
        Page.SetLogonSettings(originalLogonSetting);
    }
}

Again, we check if the desired setting is not our original setting (and in that case we do not need to do nothing), otherwise we set our setting to the original value.

All done!

In the following paragraph we will see on how this works and how to authenticate via the Windows Security window.

Last missing piece

In order to intercept the Windows Security window, with whom we are going to interact and automatically provide the credentials, we need to declare it in the way that CodedUI can recognize it and map the elements we are going to interact with. To be clear which window we are speaking about, here is a picture of it.

Windows Security Window

This is the window that will be presented once the IE is asked to provide the credentials.
With the following code we will declare this window so that CodedUI is able to recognize it.

public class WindowsSecurityWindow : WinWindow
{
    private WinText uiUseanotheraccountText;
    private WinEdit uiUsernameEdit;
    private WinEdit uiPasswordEdit;
    private WinButton uiokButton;

    public WindowsSecurityWindow()
    {
        SearchProperties[PropertyNames.Name] = "Windows Security";
        SearchProperties[PropertyNames.ClassName] = "#32770";
        TechnologyName = "MSAA";
        WindowTitles.Add("Windows Security");
    }

    public WinText UseAnotherAccountText
    {
        get
        {
            if ((uiUseanotheraccountText == null))
            {
                uiUseanotheraccountText = new WinText(this);
                uiUseanotheraccountText.SearchProperties[WinText.PropertyNames.Name] = "Use another account";
            }

            return uiUseanotheraccountText;
        }
    }

    public WinEdit UsernameEdit
    {
        get
        {
            if ((uiUsernameEdit == null))
            {
                uiUsernameEdit = new WinEdit(this);
                uiUsernameEdit.SearchProperties[WinEdit.PropertyNames.Name] = "User name";
            }

            return uiUsernameEdit;
        }
    }

    public WinEdit PasswordEdit
    {
        get
        {
            if ((uiPasswordEdit == null))
            {
                uiPasswordEdit = new WinEdit(this);
                uiPasswordEdit.SearchProperties[WinEdit.PropertyNames.Name] = "Password";
            }

            return uiPasswordEdit;
        }
    }

    public WinButton OkButton
    {
        get
        {
            if ((uiokButton == null))
            {
                uiokButton = new WinButton(this);
                uiokButton.SearchProperties[WinButton.PropertyNames.Name] = "OK";
            }

            return uiokButton;
        }
    }

    public void Authenticate(string userName, string password)
    {
        if (UseAnotherAccountText.Exists)
        {
            Mouse.Click(UseAnotherAccountText);
        }

        UsernameEdit.Text = userName;
        PasswordEdit.Text = password;

        Mouse.Click(OkButton);
    }
}

Aside the standard code, you can see that we are searching for an element called “Use another account”. It can happen in certain cases that a variation of the window we saw in the previous image gets presented. The variation looks like following:

Windows Security Window Use Another Account

If this is the case we are still able to handle it correctly. We are going first to select the right tab:

Windows Security Window Use Another Account Selected

And insert the credentials as in the ordinary case (luckily element names are always called the same).
What remains is to recall it in the following way and pass in the desired credentials.

WindowsSecurityWindow windowsSecurityWindow = new WindowsSecurityWindow();
windowsSecurityWindow.Authenticate("userName", "password");

Putting all together

We saw all the pieces of the puzzle. Now let’s see how to prepare the IE, start it and authenticate. In the bottom of this post you will find a link where you can download my example project. There is an ASP.NET web site which uses Windows Authentication and relative CodedUI project which executes the test. In this way you can have the complete picture.

Our CodedUI test will state the following

[TestMethod]
public void CodedUITestMethod1()
{
    BrowserWindow.Launch(new Uri("http://localhost:59542/"));

    WindowsSecurityWindow windowsSecurityWindow = new WindowsSecurityWindow();
    windowsSecurityWindow.Authenticate(@"Home8test", "test");
}

As you can see, we are launching the browser window and pointing it to our application (make sure IIS Express is running before you do execute your test) and just specifying our WindowsSecurityWindow object. After that authenticate will kick in and all the actions will be performed as expected. Thanks to the code we wrote earlier, the browser will request the credentials to be provided and it will not try to login with our current user.

Note: In order this example to work, with IIS Express, you will need to enable windows authentication for IIS Express.

In order to enable windows authentication in IIS express open the file called applicationhost.config which is located in My DocumentsIISExpressconfig which again translated in my case is C:UsersMaiODocumentsIISExpressconfig. Once you open this file for edit and search the “windowsAuthentication enabled” string. You will land on the right spot. By default, this element is set to false. Just set it to true and you are ready to go.

This is an abstract of the final state of applicationhost.config.


...
  
...
    
      
    
...
  
...

One more thing, make sure that you create an user that you are going to use to test this scenario. Open the computer management and create a dummy user (in my case called test with a strong password equaling to test).

Computer Management

Last thing to do is to change the application web config with the right user and update the user name and credentials into your test.

You can now open your Test Explorer and hit run! You should see IE starting and pointing to your application page, authentication window popping out, credentials being passed and finally you are logged in!

That’s all folks

I hope you enjoyed reading this article and that you fancy the neat technique of managing the windows authentication. In this way you do not need to pre-prepare your clients on which you are going to execute your tests and more important you do not need to mess up wit the Credential Manager.
Stay tuned for more articles about CodedUI and testing automation.

Happy coding!

Download the complete example