Setting up an integrated build and deploy pipeline for LightSwitch applications (part 5)

Introduction

When I write articles on a particular subject in multiple parts (a series), I never start with the end of the series in mind. So, refactoring can happen, “en cours de route”.

That is what happening also with this series on deploying LightSwitch apps via a build server.  I’m very choosy when it comes to making things simple and that’s what I’m trying to do here as well.

My goal is :

  • getting rid of any batch files and use powershell instead
  • simply the deployment of site and app pool.

Using powershell for setting up website and app pool.

You might remember how I created in my previous approach the website and the app pool: I used a kind of template webdeploy package for both site and app pool and deployed this in a batch file via webdeploy.

This approach has the clear advantage that I can also add specific content to the site that I want to deploy but there are also drawbacks:

  • the approach is destructive. When you deploy via webdeploy a site, all underlying material (the application) is destroyed. This makes that even a very small change in an application requires that the full application is redeployed.
  • the approach starts from template packages. The construction of this template packages is tedious.

Therefor, something new: simply creating with powershell the site and application pool with following powershell script:

param($strUser,$strPass,$webSiteName, $appPoolName, $httpPort, $sslPort )

Write-Host ("userId :" +  $strUser)
Write-Host ("strPass :" +  $strPass)
Write-Host ("webSiteName :" +  $webSiteName)
Write-Host ("appPoolName :" +  $appPoolName)
Write-Host ("httpPort :" +  $httpPort)
Write-Host ("sslPort :" +  $sslPort)

Import-Module webadministration
#Create App Pool

$strRunTime="v4.0"
$physicalPath= Join-Path -Path "d:\InetPub" -ChildPath webSiteName

if(Test-Path IIS:\AppPools\$AppPoolName)
{
    #not clear what happens with sites using the app pool ?
    Remove-Item IIS:\AppPools\$AppPoolName -Recurse
}

New-WebAppPool -Name $appPoolName -Force
Set-ItemProperty ("IIS:\AppPools\" + $appPoolName) -name processModel.identityType -value 3
Set-ItemProperty ("IIS:\AppPools\" + $appPoolName) -name processModel.userName -value $strUser
Set-ItemProperty ("IIS:\AppPools\" + $appPoolName) -name processModel.password -value $strPass
Set-ItemProperty ("IIS:\AppPools\" + $appPoolName) -name managedRuntimeVersion -value $strRunTime

if (!(test-path $physicalPath) )
{
    New-item -path $physicalPath -type directory
}

$cert=Get-ChildItem cert:\LocalMachine\MY | Where-Object {$_.Subject -match "CN=$env:COMPUTERNAME"}

if(Test-Path "IIS:\SslBindings\0.0.0.0!${sslPort}")
{
    Remove-Item "IIS:\SslBindings\0.0.0.0!${sslPort}"
}

New-Item -Path "IIS:\SslBindings\0.0.0.0!${sslPort}" -Value $cert -Force -ErrorAction Continue
New-Item IIS:\Sites\$WebsiteName -bindings (@{protocol="http";bindingInformation="*:${httpPort}:"}, @{protocol="https";bindingInformation="*:${sslPort}:"}) -physicalPath $physicalpath  -ApplicationPool $appPoolName -Force

Start-Website $webSiteName

 

Note that we no longer need the 2 zip packages for creating the web site (TemplateWebSitePackage.zip) and  app pool (TemplateAppPoolPackage.zip) on our build server  !

Note also that the refactoring I’m presenting in this article has no impact at all on the build process template.

Where will this powershell script be stored and executed?

When it comes to executing this script we have several options. Powershell allows remote connections. That would be option 1. I didn’t chose this option. We are anyhow using webdeploy and webdeploy is great in doing thins remotely. It can not only do remote installations of sites and applications, but it can also run remotely a script (e.g. a powershell script).

As a result, we will adapt our initial powershell script that is ran on the build server to trigger the above powershell on the IIS Server. One drawback, the script needs to be installed on the IIS server. So, put in somewhere in a folder like “D:\DeploymentInfra\deploy.ps1.

 

What’s the refactor of the WebDeployHandling.ps1 script?

Well, we need to call the above script remotely on the IIS Server from the base script that is running on our IIS Server.

Let’s first focus on how we will arrange the remote invocation of deploy.ps1 (the above script):

[string[]]$msdeployArgs = @(
  "-verb:sync",
  "-dest:auto,ComputerName=$computerName",
  "-allowUntrusted",
   "-verbose",
  "-source:runcommand=`"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ,
  "d:\deploymentinfra\deploy.ps1",
  "$appPoolUser",
  "$appPoolPassword",
  "$siteName",
  "$appPoolName",
  "$httpPort",
  "$httpsPort"
)

&$msdeploy $msdeployArgs

write-Host "finished RunCommand"
Write-Host("last exit code " + $LASTEXITCODE)
if($LASTEXITCODE -ne 0)
{
    throw "installation of site and/or app pool did not succeed"
}

So, we use a powershell script (webdeployhandling.ps1)  in which we invoke an other powershell script (deploy.ps1) which is stored on the IIS server and the remote invocation is handled by webdeploy.

The full webdeployhandling.ps1 script that is called from the build process template.

param($BinariesDirectory,$SourcesDirectory)

function GetValueFromXmlParameterFile([string] $attributeName, $xmlFilePath)
{
$xmldata = [xml](Get-Content ($xmlFilePath))
$result =  (($xmldata.parameters.setParameter | Where-Object{ $_.name -eq $attributeName} | Select-Object value).value)
return $result
}

Write-Host ("Binaries Directory ******" + $BinariesDirectory)
Write-Host ("Sources Directory ******" + $SourcesDirectory)

$webdeployRootFolder = Join-Path -Path $BinariesDirectory -ChildPath "WebDeploy"

#copy xml parameter files to src folder 
Copy-Item -Path (Join-Path -Path $SourcesDirectory -ChildPath "WebDeploy\*.xml") -Destination $webdeployRootFolder

$buildServerInfraFolder = "D:\WS\buildserverinfra"

#copy lightswitch app zip to source folder
Copy-Item -Path (Join-Path -Path $BinariesDirectory -ChildPath "Publish\*.zip") -Destination $webdeployRootFolder

$xmlFilePath = Join-Path -Path $webdeployRootFolder -ChildPath "SetParameters.xml"

[string]$siteAndApp = GetValueFromXmlParameterFile "IisWebApplication" $xmlFilePath
$siteName = $siteAndApp.Split('/')[0]
$appName = $siteAndApp.Split('/')[1]
$appPoolName = GetValueFromXmlParameterFile "Application Pool" $xmlFilePath
$appPoolUser = GetValueFromXmlParameterFile "username" $xmlFilePath
$appPoolPassword = GetValueFromXmlParameterFile "password" $xmlFilePath
$httpPort = GetValueFromXmlParameterFile "Site-Http" $xmlFilePath
$httpsPort = GetValueFromXmlParameterFile "Site-Https" $xmlFilePath

$msdeploy = "MsDeploy"
$computerName="https://myserver.cloudapp.net:8172/msdeploy.axd,UserName='administrator',Password='secretpassword',AuthType='Basic',IncludeAcls='False'"
$WebAppSourcePackageName = $appName + ".zip"
$webAppTargetPackageName = "DEPLOY." + $appName + ".zip"
$webAppParametersFile = "SetParameters.xml"

[string[]]$msdeployArgs = @(
  "-verb:sync",
  "-dest:auto,ComputerName=$computerName",
  "-allowUntrusted",
   "-verbose",
  "-source:runcommand=`"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" ,
  "d:\deploymentinfra\deploy.ps1",
  "$appPoolUser",
  "$appPoolPassword",
  "$siteName",
  "$appPoolName",
  "$httpPort",
  "$httpsPort"
)

&$msdeploy $msdeployArgs

write-Host "finished RunCommand"
Write-Host("last exit code " + $LASTEXITCODE)

if($LASTEXITCODE -ne 0)
{
    throw "installation of site and/or app pool did not succeed"
}

[string[]]$msdeployArgs = @(
  "-verb:sync",
  "-source:package=$WebAppSourcePackageName",
  "-dest:package=$webAppTargetPackageName",
  "-declareParamFile=WebAppDeclareParameters.xml"
)
&$msdeploy $msdeployArgs

[string[]]$msdeployArgs = @(
  "-verb:sync",
  "-source:package=$webAppTargetPackageName",
  "-dest:auto,ComputerName=$computerName",
  "-setParamFile=$webAppParametersFile",
  "-skip:ObjectName=dbFullSql",
  "-allowUntrusted"
)

&$msdeploy $msdeployArgs

 

As you can see, apart from the change in website and app pool deployment, we are no longer making use of a command file to trigger msdeploy. The calls to msDeploy are incorporated now directly in the powershell script. That makes the approach really a lot more concise and thus maintainable !

Conclusion

So far my contribution to a better LightSwitch deployment experience :) This is the end of the series.