Tag: IIS

Creating web sites on IIS using Powershell

Basic operation

The simplest way to create an IIS website via Powershell is to use Carbon module:

Import-Module 'Carbon'

Install-IisWebsite -Name 'test site' -PhysicalPath 'c:\website'

If a website with a given name exists, the script will update it.

Host names

Suppose you need to run several versions of your software side by side, using neat host names such as “v1.localhost” or “v2.localhost”. For that, you need to manipulate the Windows hosts file, like this (again using Carbon):

Set-HostsEntry -IPAddress 127.0.0.1 -HostName v2.localhost

If such host record already exists, it will not insert a duplicate.

You can bind a website to the host in the following way:

Install-IisWebsite -Name 'test site - version 2' -PhysicalPath 'c:\v2\website' -Bindings 'http/*:80:v2.localhost'

Certificates

If you use HTTPS (as you should), you will probably need to make your website use some SSL certificate. The simplest way is to use a self-signed certificate created by IIS. You can automate it like this:

$certificate = New-SelfSignedCertificate -dnsname '*.localhost'

After you have a certificate, you need to tell the website to use it:

Install-IisWebsite -Name 'test' -PhysicalPath 'c:\website' -Bindings 'http/*:80:test.localhost', 'https/*:443:test.localhost'
$applicationId = [System.Guid]::NewGuid().ToString()
Set-IisWebsiteSslCertificate -SiteName test -Thumbprint $certificate.Thumbprint -ApplicationID $applicationId

This will create an IIS website with proper HTTPS bindings.

Note that Carbon’s function Set-IisWebsiteSslCertificate requires a parameter called ApplicationId. You can supply a meaningful value here, but I just use a new guid.

Detecting if a certificate has been created

Sometimes you need a script which takes advantage of the existing data, proceeding without errors if some items already exist and just adding missing bits to them. The certificate creation described above is not suitable for this purpose, as it creates certificate unconditionally. Let’s try to get the existing certificate and check if it exists

$certificate = Get-ChildItem -Path CERT:LocalMachine/My |
    Where-Object -Property DnsNameList -EQ '*.localhost' |
    Select-Object -first 1

if (!$certificate) { Write-Output 'Need to create certificate' }
else { Write-Output 'Certificate is already there' }

 

Putting everything together

Let’s combine the previous material in a couple of reusable functions to create a self-signed certificate if it does not exist, and create an HTTPS-enabled website with a corresponding host entry:

function Get-Or-Create-Certificate($certificateDns)
{
    $certificate = Get-ChildItem -Path CERT:LocalMachine/My |
        Where-Object -Property DnsNameList -EQ $certificateDns |
        Select-Object -first 1

    if (!$certificate)
    {
        Write-Output "Creating self-signed certificate for $certificateDns"

        $certificate = New-SelfSignedCertificate -dnsname $certificateDns
    }
    else
    {
        Write-Output "Self-signed certificate for $certificateDns already exists"
    }

    return $certificate
}

function Create-WebSite($hostName, $path, $siteName, $certificate)
{
    Write-Output "Adding host $hostName"
    Set-HostsEntry -IPAddress 127.0.0.1 -HostName $hostName

    Write-Output "Creating website $siteName on IIS"
    Install-IisWebsite -Name $siteName -PhysicalPath $path -Bindings "http/*:80:$hostName", "https/*:443:$hostName"

    Write-Output "Attaching certificate to the website $siteName"
    $applicationId = [System.Guid]::NewGuid().ToString()
    Set-IisWebsiteSslCertificate -SiteName $siteName -Thumbprint $certificate.Thumbprint -ApplicationID $applicationId
}

The functions above are made to be idempotent, so you can call them several times without errors. Use them as follows:

$certificate = Get-Or-Create-Certificate -certificateDns $certificateDns
Create-Dev-WebSite -hostName 'v1.localhost' -path 'c:\v1\website' -siteName v1 -certificate $certificate
Create-Dev-WebSite -hostName 'v2.localhost' -path 'c:\v2\website' -siteName v2 -certificate $certificate

Feel free to tailor those scripts to your needs.

.NET environment automation with Powershell

In the upcoming series of short posts, I would like to describe how some common developer tasks in .NET ecosystem can be automated via Powershell.

The list follows:
Create a website on IIS, including creation and usage of self-signed certificate
– Setting Sql Server authentication mode and creating database logins
– Creating a database based on Visual Studio Database Project
– Executing Sql queries
– Calling REST APIs

ASP.NET performance measurements on desktop Windows

Proper benchmarking has always been hard – it is trivial to get some numbers, but not so if you are trying to receive relevant ones, that is, results describing the behaviour of the system under test rather than some of auxiliary setup.

One example is testing ASP.NET web sites performance on a developer machine running Windows. It is very easy to achieve pretty low results compared to other systems like Apache, and the outcome is tempting to make far-reaching conclusions about platform’s performance. For a less obvious example, blog post I’ve stumbled upon recently compared the performance of Web API project using synchronous and asynchronous code, having threaded solution blown out of water by the magnitude of ten.

However, such results do not signify a weakness of Windows/IIS/ASP.NET platform in general due to the simple fact that IIS running on non-server Windows is limited to pretty small number (3 or 10) of concurrent web requests, which naturally limits throughput of a web application quite heavily if the request processing time is not too small. It might not be a bottleneck if your requests are done in 50 ms or so, but if the idea is to simulate a long call to an external web service, then the results stop to be realistic very soon.

How to get the real data then? Of course one way is to use Windows Server machine for performance testing. However, it might present less than optimal feedback cycle when you want to work in a measure – adjust – measure loop. Then another solution might be to use self-hosting capabilities present for a long time in Web API and recently included in ASP.NET MVC as well. In my experience, such hosts are quite quick and do not have any artificial limitations even on desktop Windows.

Multiple web servers simulation using nginx

Recently, I had to prepare a web application to being run on multiple web servers (also known as a web farm). The goal of that is to ensure the failover (so another web server replaces the primary one if the latter is down), or to increase the performance using a load-balancing between several webservers. I wanted to perform the most tests using a single machine, with several web sites using the same code, to have a benefit of a quick feedback cycle – without the need to touch another machine or roll up a VM.

As we use Microsoft technologies, the solution seemed to be pretty obvious: use IIS Application Request Routing (ARR) to set up a web farm. However, on a local machine, it’s proved to be a difficult task. After a day and a half of trying and getting only 503, I decided to search for another solution.

This another solution turned out to be nginx, widely used static/proxy web server used mostly on Unix machines. I was able to configure it in 40 minutes, and while Windows version of the server is not that well performant, it turned out to be good enough for our purposes.

This is an example of a minimal config you might use to simulate a web farm on localhost:

worker_processes  1;

error_log  logs/error.log info;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    client_max_body_size 100m;

    keepalive_timeout  65;

    upstream farm {
        server localhost:8003;
        server localhost:8004;
    }
    
    server {

        listen       8007 ssl;
        server_name  lb.localhost;
        
        ssl_certificate      cert.pem;
        ssl_certificate_key  cert.key;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
            proxy_pass https://farm;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Port 8007;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

In a nutshell, listen directive specifies on which port nginx will listen to incoming requests, and proxy_pass defines how nginx will forward them to the server set specified in the upstream directive. Normally nginx uses a round robin procedure to route the request, that is, first request lands on the first server listed in the upstream, next request lands on the subsequent one and so forth, starting from the beginning if the server list is exhausted.

Note that for HTTPS, you will need to acquire SSL certificates and put them into the nginx configuration directory (cert.pem is a certificate, and cert.key is a private key). The easiest (and free) way to do so is to use http://www.cert-depot.com/.

You can run nginx via typing start nginx at command line.

Note that ssl_session_cache directive present in the default nginx config is omitted here because it doesn’t work on Windows (if not removed, it will cause nginx to fail miserably). Also, client_max_body_size is increased compared to the default value for the case when your site supports uploads of large files (otherwise, you will get unexpected errors from nginx). Proxy_set_header is need in case your web site wants to know the outward domain name of the website.