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

Introduction

In this post we will focus on the powershell script that will be triggered by build process template and which will eventually trigger a deployment to a remote IIS server.

Show me the script

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

#take webdeploy templates from build server infrastructure folder
$buildServerInfraFolder = "D:\WS\LightSwitchCompetenceCenter"
Copy-Item -Path (Join-Path -Path $buildServerInfraFolder -ChildPath "*.zip") -Destination $webdeployRootFolder

#take RemoteDeployFromBuildServer.cmd  from infrastructure folder
Copy-Item -Path (Join-Path -Path $buildServerInfraFolder -ChildPath "*.cmd") -Destination $webdeployRootFolder

#copy lightswitch app zip
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

#do parameter injection in RunOnIISServer.cmd 

$commandParameters = @'
SET "SiteName=$siteName"
SET "AppName=$appName"
SET "AppPoolName=$appPoolName"
'@
$commandParameters=$ExecutionContext.InvokeCommand.ExpandString($commandParameters)

$textFilePath = Get-Item (Join-Path -Path $webdeployRootFolder -ChildPath "RunOnIISServer.cmd")
Set-ItemProperty $textFilePath -name IsReadOnly -value $false
$textFileContent = (Get-Content $textFilePath)
$textFileContent = $textFileContent -replace "xxxparameterinjectionxxx", $commandParameters
Set-Content $textFilePath $textFileContent

$commandFilePath = Get-Item (Join-Path -Path $webdeployRootFolder -ChildPath "RemoteDeployFromBuildServer.cmd")

# start the batch file for the deployment
& $commandFilePath $siteName $appName $appPoolName

Which infrastructure files do we need on the build server?

In a dedicated folder we store following material (the content is already covered in previous posts regarding deployment):

infraFiles

 

  • WebDeployHandling.ps1 is the current powershell file. That’s the one which is copied already by the build process template to the current build folder. It’s the heart of the deployment machinery.
  • TemplateAppPoolPackage and TemplateWebSitePackage are the msdeploy (.zip) packages for website and application pool deployment.
  • RemoteDeployFromBuildServer.cmd is a batch file which contains the calls to MsDeploy.
  • RunOnIISServer.cmd is a batch file which is called inside RemoteDeployFromBuildServer.cmd and will clean the current website and application.

 

How does it work?

Get all necessary files

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

#take webdeploy templates from build server infrastructure folder
$buildServerInfraFolder = "D:\WS\LightSwitchCompetenceCenter"
Copy-Item -Path (Join-Path -Path $buildServerInfraFolder -ChildPath "*.zip") -Destination $webdeployRootFolder

#take RemoteDeployFromBuildServer.cmd  from infrastructure folder
Copy-Item -Path (Join-Path -Path $buildServerInfraFolder -ChildPath "*.cmd") -Destination $webdeployRootFolder

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

This first block will assure we have everything from the infrastructure folder and also the webdeploy zip of our LightSwitch application (which is in the publish folder of the BinariesDirectory.

Inject parameters in RunOnIISServer

$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

#do parameter injection in RunOnIISServer.cmd 

$commandParameters = @'
SET "SiteName=$siteName"
SET "AppName=$appName"
SET "AppPoolName=$appPoolName"
'@
$commandParameters=$ExecutionContext.InvokeCommand.ExpandString($commandParameters)

$textFilePath = Get-Item (Join-Path -Path $webdeployRootFolder -ChildPath "RunOnIISServer.cmd")
Set-ItemProperty $textFilePath -name IsReadOnly -value $false
$textFileContent = (Get-Content $textFilePath)
$textFileContent = $textFileContent -replace "xxxparameterinjectionxxx", $commandParameters
Set-Content $textFilePath $textFileContent

The above block is fairly complicated for doing really something very trivial: inject some “text” in the RunOnIISServer.cmd file. The content of the injected text is coming from our xml parameter file (which is check-in together with the LightSwitch solution).

Note that I changed slightly the name of the parameter file (SetParameters.xml). In part 2 of this series this file was called SetWebAppAndAppPoolParameters.xml.

Trigger RemoteDeployFromBuildServer.cmd

That’s easy:

& $commandFilePath $siteName $appName $appPoolName

The command file takes in the site name, the app name and the app pool name.

show me RemoteDeployFromBuildServer.cmd

I did a minor change in this file since part 2 of this series, therefore I simply repeat the content:

rem ******************************
rem deployment specific parameters
rem ******************************

SET SiteName=%1
SET AppName=%2
SET AppPoolName=%3

rem *************************************
rem  server specific params
rem *************************************
SET _httpsSiteCertificate="de0e36943ab3fab29322ee58edebb2e304a48370"
SET _dummyPassword="secret"  
SET "_physicalRootPath=D:\Inetpub\"
SET "_computerName=https://YourServer.cloudapp.net:8172/msdeploy.axd,UserName='administrator',Password='yourpassword',AuthType='Basic',IncludeAcls='False' -allowUntrusted"

rem ********************
rem invariant parameters
rem ********************
SET "WebAppSourcePackageName=%AppName%.zip"

SET "_appPoolParametersFile=SetParameters.xml"
SET "_webAppParametersFile=SetParameters.xml"
SET "_webSiteParametersFile=SetParameters.xml"
SET "_webAppTargetpackageName=DEPLOY.%WebAppSourcePackageName%
SET "_destinationWebSitePhysicalPath=%_physicalRootPath%%SiteName%"

rem net start msdepsvc

rem ***********************
rem prereqs RUNCOMMAND deletion of web app and website
msdeploy -verb:sync -source:runcommand=RunOnIISServer.cmd -dest:auto,ComputerName=%_computerName%
rem ***********************

rem **********************************************
rem ***************create app pool****************
rem **********************************************

msdeploy.exe -verb:sync -source:package="TemplateAppPoolPackage.zip",encryptpassword=%_dummyPassword% -dest:appPoolConfig=%AppPoolName%,computerName=%_computerName% -setParamFile=%_appPoolParametersFile%

rem **********************create web site*************
rem **************************************************
rem **************************************************

msdeploy  -verb:sync -source:package="TemplateWebSitePackage.zip",encryptpassword=%_dummyPassword% -dest:appHostConfig=%SiteName%,ComputerName=%_computerName% -replace:objectName=virtualDirectory,targetAttributeName=physicalPath,match="^D:\\inetpub\\PreTemplateWebSite",replace=%_destinationWebSitePhysicalPath%   -replace:objectName=httpCert,targetAttributeName=hash,replace=%_httpsSiteCertificate%  -setParamFile=%_webSiteParametersFile%

rem *********************************************************
rem **********************create web application*************
rem *********************************************************

msdeploy.exe -verb:sync -source:package=%WebAppSourcePackageName% -dest:package=%_webAppTargetPackageName% -declareParamFile="WebAppDeclareParameters.xml"

msdeploy.exe -verb:sync -source:package=%_webAppTargetPackageName%  -dest:auto,ComputerName=%_computerName% -setParamFile=%_webAppParametersFile% -skip:ObjectName=dbFullSql

 Why both a PowerShell script and a command file (.cmd)?

You might wonder why I’m still using the RemoteDeployFromBuildServer.cmd and why I did not integrate this directly into the powershell script. Well, no doubt, that would make things more easy.  I need this because sometimes I can not do a remote deployment and need to transfer material to the target server and execute it locally over there.  Maybe in a later post I’ll integrate the command file into the powershell script.

Which files do I need to check in from my LightSwitch Solution?

That became now really simple:

solugion

 

The deployment specific material is now only the SetParameter.xml file and in case you want to inject more params in your web.config, you need also a WebAppDeclareParameters.xml file.

Conclusion

This series covered fully automated deployment. Deployment as a black box. There was a lot of thinking involved in order to get into a situation where you don’t need to thing any longer… at least not about deployment.

Enjoy.