IIS7 & Self-elevating PowerShell
Route to automated deployment with PowerShell and IIS7
Despite my love for all things DVCS, DAG, and Git it’s no secret that there is one area of TFS 2010 that I am quite fond of – Visual Studio Lab Management. Utilising Microsoft Hyper-V technology it allows us to manage and use virtual machines in building, testing, and deploying applications.
One post is not enough to cover this in detail (in fact I suspect a 3-day workshop might be more likely) but one element I was keen to share with you was our deployment script that enables our automated cradle-to-grave testing environment and which sits alongside our CI (Continuous Integration) and automated testing strategy.
Elevation & Execution
To achieve this we needed the workflow capability to deploy and configure an IIS 7 site on a completely fresh and clean deployed environment; importantly one with no prior manual configuration or deployments. As much of the IIS functionality would need elevated privileges this presented a couple of problems;
How to bypass PS ExecutionPolicy that by Windows installation would default to “Restricted”
How could we elevate the script once spawned without manual interaction
I quickly came across Ben Armstrong’s blog on self-elevating an executing PowerShell script, more so it easily dropped into the top of our deployment script. Next using some previous in-house experimentation I was able to find that using the following PowerShell.exe line would bypass the “Restricted” ExecutionPolicy.
Our workflow command is executing PowerShell.exe and passing a -Command of the full path and filename of the PS deployment script (in our case location of our last good known integration build) together with;
This tells the PowerShell prompt to load of system modules, I do this to ensure we have the WebAdministration module for IIS7 is loaded; however if the script does self-elevate as we expect in this scenario it’s likely redundant!
As we are passing the -Command argument I’m re-enforcing that STDIN shouldn’t apply any formatting; incidentally I believe “None” is an undocumented option.
This is what makes our baby fly, bypassing execution poliicies to ensure our scripts runs despite our newly deployed environment having an ExecutionPoliicy of “Restricted”. Interestingly I haven’t yet needed to re-apply this during elevation.
As a side note my -Command execution passes the following arguments to our executing deployment script;
I’m passing the build location where our deployment files are; in our scenario this is normally the DropDirectory of the CI build (e.g.
\MyBuildMachineCIBuildsProjectX.Trunk.CIProjectX.Trunk.CI_20120101.1). I could determine this in-script using
$MyInvocation.MyCommand.Pathbut I want engineers to be able to run this script manually.
Name of the website to create in IIS; it also becomes the name of the created IIS AppPool prefixed with
AppPool_and the physical directory name.
The web project name to deploy from under the build’s
_PublishedWebsitesdirectory; I’m looking to determine this intuitively in future.
Here is the top of our WebDeploy.ps1 script with elevation as guided by Ben but with some minor tweaks to pass the original arguments;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
Note: It is my suspicion that -ImportSystemModules should be added to our PowerShell.exe argument list here to ensure correct operation under all scenarios, doing
Import-Module WebAdministration mid-script is giving me mixed results (update soon!).
As part of our deployment script we need to do some key things;
Check if our deployment directories exist and if not create them
Configure IIS with our site and application pool
Copy our files across from the build directory
Set permission on our IIS directories (especially
An example function for configuring the site and application pool might be;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
It’s worth noting there are a lot of assumptions above, such as allowing CgiRestrictions against .NET 4.0 and changing attributes of the created application pool to be .NET v4.0 (Classic); I don’t mind as it’s consistent with our FubuMVC deployments but you may wish to make these configurable for your script. I should also mention functions such as “Write-Status” and capturing output into “$g_ScriptLog” global variable are our own.
As part of the copy I also remove the
\_bin\_deployableAssemblies directory off the web-root as it’s not needed and bad msbuild targets voodoo!
1 2 3 4 5 6 7
If you don’t get your IIS directory permissions right you’ll be screaming to high heaven, especially if you are unfortunate enough to have CompactCe database in
AppData (trust me!). Below is an example of using icacls to set permissions for both IIS and your create application pool identity;
1 2 3 4 5 6 7 8
When creating the web root directory (not shown above) I set inherited permissions for
IUSRS, so not needed here.
I haven’t posted the whole script here as it contains lots of our own PS wrangling for email notifications, log file creation, and such like; if the snippets above are missing something vital for you do shout :)