Automatically Deploy ASP.NET MVC, ASP.NET Web API, WCF Services or Generall Web Application to IIS

This is a follow up to my previous blog post on ‘Automated Deployments and Automation, in General, is Essential‘.

NO ONE should EVER deploy web applications manually! If you are currently doing this, please use the following script below and start deploying your web application automatically.

This is a PowerShell script, I must say I’m new to PowerShell, but I found it quite easy to learn and work with.

Gist: https://gist.github.com/zulucoda/81cd751f67a8cbba3605#file-create-web-application-in-iis-ps1

<# .SYNOPSIS
create-web-application - Automatic website creation.
.DESCRIPTION
Allows you to create a website and its ApplicationPool.
.NOTES
File Name : create-web-application.ps1
Author : Muzikayise Flynn Buthelezi - muzi@mfbproject.co.za
Copyright : MFBproject mfbproject.co.za
.EXAMPLE
PS D:\>create-web-application.ps1 -SiteName TESTSITE -Port 8080 -Environment PREPROD -Runtime v4.0 -Pipeline Classic
Creates a website named 'TESTSITE-PREPROD', listening on the TCP8080 port, responding to 'http://*' (default value). The associated ApplicationPool 'TESTSITE' running with the identity 'NetworkService' (default value), v4.0 .NET Framework managed runtime and 'Classic' managed pipeline mode.
#>
Param(
[Parameter(Mandatory=$true, HelpMessage="You must provide a display name for the website.")]
$SiteName = "testsite",
$Port = "8080",
#[ValidatePattern("([\w-]+\.)+[\w-]+(/[\w- ;,./?%&=]*)?")]
$HostName = "",
[ValidateSet("PROD", "PREPROD", "INTEG", "QUAL", "DEV")]
$Environment = "PROD",
[ValidateSet("0", "1", "2", "3", "4")]
$Identity = "2",
[ValidateSet("v1.1", "v2.0", "v4.0")]
[string]$Runtime = "v4.0",
[ValidateSet("Classic", "Integrated")]
[string]$Pipeline = "Integrated"
)
 
switch ($Identity)
{
0 {$FullIdentity = "LocalSystem"}
1 {$FullIdentity = "LocalService"}
2 {$FullIdentity = "NetworkService"}
3 {$FullIdentity = "SpecificUser"}
4 {$FullIdentity = "ApplicationPoolIdentity"}
}
 
function main(){
 
Write-Host "deploy web application"
 
if (LoadIIS7Module -eq $true) {
Write-Verbose "Add a New IIS 7.0 Web Site..."
Add-IIS7Website $SiteName $Port $HostName $Environment $Identity $Runtime $Pipeline
} else {
Write-Host "IIS7 WebAdministration Snapin or Module not found."
Write-Host "Please consult the Microsoft documentation for installing the IIS7 PowerShell cmdlets"
}
}
 
function Check-IfWebsiteExists($SiteName, $Environment){
$SiteName += "-$Environment"
if ((Test-Path -path "IIS:\Sites\$SiteName") -ne $false)
{
return $false
}
return $true
}
 
function LoadIIS7Module () {
$ModuleName = "WebAdministration"
$ModuleLoaded = $false
$LoadAsSnapin = $false
if ((Get-Module -ListAvailable |
ForEach-Object {$_.Name}) -contains $ModuleName) {
Import-Module $ModuleName
if ((Get-Module | ForEach-Object {$_.Name}) -contains $ModuleName) {
$ModuleLoaded = $true
} else {
$LoadAsSnapin = $true
}
}
elseif ((Get-Module | ForEach-Object {$_.Name}) -contains $ModuleName) {
$ModuleLoaded = $true
} else {
$LoadAsSnapin = $true
}
if ($LoadAsSnapin) {
if ((Get-PSSnapin -Registered |
ForEach-Object {$_.Name}) -contains $ModuleName) {
Add-PSSnapin $ModuleName
if ((Get-PSSnapin | ForEach-Object {$_.Name}) -contains $ModuleName) {
$ModuleLoaded = $true
}
}
elseif ((Get-PSSnapin | ForEach-Object {$_.Name}) -contains $ModuleName) {
$ModuleLoaded = $true
}
else {
$ModuleLoaded = $false
}
}
return $ModuleLoaded
}
 
function Read-Choice {
Param(
[System.String]$Message,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.String[]]$Choices,
[System.Int32]$DefaultChoice = 1,
[System.String]$Title = [string]::Empty
)
[System.Management.Automation.Host.ChoiceDescription[]]$Poss = $Choices | ForEach-Object {
New-Object System.Management.Automation.Host.ChoiceDescription "&$($_)", "Sets $_ as an answer."
}
$Host.UI.PromptForChoice($Title, $Message, $Poss, $DefaultChoice)
}
 
function Select-IPAddress {
[cmdletbinding()]
Param(
[System.String]$ComputerName = 'localhost'
)
$IPs = Get-WmiObject -ComputerName $ComputerName -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled='True'" | ForEach-Object {
$_.IPAddress
} | Where-Object {
$_ -match "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
}
 
if($IPs -is [array]){
Write-Host "`nServer $ComputerName uses these IP addresses:"
$IPs | ForEach-Object {$Id = 0} {Write-Host "${Id}: $_" -ForegroundColor Yellow; $Id++}
$IPs[(Read-Choice -Message "`nChoose an IP Address" -Choices (0..($Id - 1)) -DefaultChoice 0)]
}
else{$IPs}
}
 
function Add-IIS7Website($SiteName, $Port, $HostName, $Environment, $Identity, $Runtime, $Pipeline)
{
Write-Host "`n**********************************************************" -ForegroundColor Yellow
Write-Host "*`t`tAutomatic Website Creation" -ForegroundColor Yellow
Write-Host "*" -ForegroundColor Yellow
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Parameters"
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Website Name (-SiteName):`t`t" -nonewline; Write-Host "$SiteName" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Website Port (-Port):`t`t`t" -nonewline; Write-Host "$Port" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Website Hostname (-Hostname):`t`t" -nonewline; Write-Host "$HostName" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Website Environment (-Environment):`t" -nonewline; Write-Host "$Environment" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " AppPool Identity (-Identity):`t`t" -nonewline; Write-Host "$FullIdentity ($Identity)" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Managed Runtime (-Runtime):`t`t" -nonewline; Write-Host "v$Runtime" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow -nonewline; Write-Host " Managed Pipeline Mode (-Pipeline):`t" -nonewline; Write-Host "$Pipeline" -ForegroundColor DarkGreen
Write-Host "*" -ForegroundColor Yellow
Write-Host "**********************************************************" -ForegroundColor Yellow
if ((Check-IfWebsiteExists $SiteName $Environment) -eq $false) {
Write-Host "Website $SiteName already created!" -ForegroundColor Yellow
return $false
}
 
if ($Identity -eq "3") {
$AppPoolUser = Read-Host "`nPlease provide username for the ApplicationPool identity"
$AppPoolPwd = Read-Host "Please provide the password for '$AppPoolUser' user" -AsSecureString
}
 
$ChosenIP = Select-IPAddress
Write-Host "`nThe selected IP address is: $ChosenIP`n" -ForegroundColor DarkGreen
 
$SiteName += "-$Environment"
# Create the website directory
Write-Host "Creating application directory" -ForegroundColor Yellow
$WWWPath = "C:\inetpub\wwwroot"
$SitePath = "$WWWPath" + "\" + "$SiteName"
if (!(Test-Path $SitePath)) {
New-Item -ItemType Directory -Path $SitePath
}
 
# Creates the website logfiles directory
Write-Host "Creating application logfiles directory" -ForegroundColor Yellow
$LogsPath = "C:\inetpub\logs\LogFiles"
$SiteLogsPath = "$LogsPath" + "\" + "$SiteName"
if (!(Test-Path $SiteLogsPath)) {
New-Item -ItemType Directory -Path $SiteLogsPath
}
 
Import-Module "WebAdministration" -ErrorAction Stop
if ($Pipeline -eq "Integrated") {$PipelineMode = "0"} else {$PipelineMode = "1"}
 
# Creates the ApplicationPool
Write-Host "Creating website application pool" -ForegroundColor Yellow
New-WebAppPool –Name $SiteName -Force
Set-ItemProperty ("IIS:\AppPools\" + $SiteName) -Name processModel.identityType -Value $Identity
if ($Identity -eq "3") {
Set-ItemProperty ("IIS:\AppPools\" + $SiteName) -Name processModel.username -Value $AppPoolUser
Set-ItemProperty ("IIS:\AppPools\" + $SiteName) -Name processModel.password -Value $AppPoolPwd
}
Set-ItemProperty ("IIS:\AppPools\" + $SiteName) -Name managedRuntimeVersion -Value $Runtime
Set-ItemProperty ("IIS:\AppPools\" + $SiteName) -Name managedPipelineMode -Value $PipelineMode
 
# Creates the website
Write-Host "Creating website" -ForegroundColor Yellow
New-Website –Name $SiteName -Port $Port –HostHeader $HostName -IPAddress $ChosenIP -PhysicalPath $SitePath -ApplicationPool $SiteName -Force
Set-ItemProperty ("IIS:\Sites\" + $SiteName) -Name logfile.directory -Value $SiteLogsPath
 
Start-WebAppPool -Name $SiteName
Start-WebSite $SiteName
 
Write-Host "Website $SiteName created!" -ForegroundColor DarkGreen
}
 
main

Automated Deployments and Automation in General is Essential

It’s 02:00 in the morning, and I’ve been up for more than 14 hours now. I’m still manually deploying the latest services and application. It’s my first time deploying these services and apps because usually, other team members do it. We finish the deployment at 05:30 in the morning. We are all tired we go home and rest. We come in at around 11:00. There are some issues with various apps and services. We immediately start investigating. Deep into our investigation, we realise that missing steps caused most of the issues during the deployment. It’s simple things like updating the config or restarting the service. Surely there must be an easier way???

There is an easier way, and we all know what the easy way is. It’s automation. As suggested by Pragmatic Programmer book, we must take advantage of automation as much as possible. Yet we are manually deploying code, WHY? In my last team, the excuse was that we don’t have time to create automation scripts. OR Our suite of services and applications are too complicated to be automated. Fair enough, this might be true in the beginning, maybe there is no time to put together automation scripts because we are trying to complete functionality ASAP. So we let it slip. On our first deployment, we deploy everything within an hour; therefore, we convince ourselves that we don’t need automation.

STOP, don’t fall into this trap, you will pay for it later, just like we did at my previous company.

My suggestion is to set time aside for automation, add this as a practice during development, similar to unit testing or CI (Continuous Integration). What I’ve started doing is automating whatever task I repeatedly do. For example, the images throughout my blog have a filter added to them and are the same size. Also, they have been uploaded automatically to my server. I created a script, which does this.