Thinking & Doing


Feed

I just finished work on a project with a lot of v4 technologies (Silverlight, .Net, Entity Framework, WCF) where we leveraged Visual Studio database projects and IIS WebDeploy to set up a fully automated TFS 2010 build/deploy process.  We had automated builds after every checkin, manually-triggered automated deployments to our integration server, and automated nightly deployments to the test server our client could access.  It was really quite nice – I could go more than a week without having to remote into our dev/test environments to fix something up manually (usually a database change that the VS refactoring stuff couldn’t deduce).

Fast-forward a few weeks, and I’m now working on a brand new SharePoint 2010 project.  After having had automated build/deploy on my previous project, I figure it should be just as easy to set up SharePoint deployments as it was to set up the VSDB+WebDeploy stuff, right?  Right?

Well, not really…  Building the WSPs to the drop location? That’s pretty easy – add the “/p:IsPackaging=true” flag to your MSBuild arguments.  But deploying them to a remote SharePoint server?  That didn’t work (at least what I could find).  I probably could have just installed the TFS build server stuff on our SharePoint server, but we’re trying to keep those boxes nice and slim with no non-essential services so we can allocate them smaller slices of memory and squeeze more VMs into our environment.  I was looking for an option that would let me build a bunch of projects on a central build server and push them out to the project servers.  I could give up a couple of gigs of memory for a dedicated build VM server, but didn’t want to give an extra 500MB of RAM to each Sharepoint VM to run a local build server.

The way out?  PowerShell Remoting.  I took a deployment script I’d already put together to deploy the WSPs to the server, and set up another script to run on the build server *after* the files were copied to the drop location that used PowerShell Remoting to connect to the SharePoint server and run the deployment script there (since Install-SPSolution and it’s ilk only work to a local farm).  Here’s how to make that all work.

Step 1: setup build server

First, we need to set up our build server.  I was ok with wasting a bit of hard drive space here (I’m memory-limited, not disk-space-limited), so I took the easy way out – I installed VS 2010, SP 2010 (pick multi-server during installation, but don’t run Products & Technologies wizard – this way you get the files, but don’t actually setup or join a farm), and set up a TFS 2010 build server.  Next, I needed to enable PowerShell Remoting and CredSSP to get around the double-hop problem (http://blogs.msdn.com/b/powershell/archive/2008/06/05/credssp-for-second-hop-remoting-part-i-domain-account.aspx).  Run these commands from an elevated PowerShell console on the build server:

  • Enable-PSRemoting –force
  • Enable-WSManCredSSP -Role client * –force
    (note: this will allow CredSSP to delegate credentials to any machine it can connect to, and may have security implications in your environment)

And with that, the build server itself is ready to go.  Now to set up the target server.

Step 2: setup target server

This is the Sharepoint 2010 server we’ll be targeting with the automated deployment.  Run these commands from an elevated PowerShell console on the target server:

  • Enable-PSRemoting –force
  • Enable-WSManCredSSP –Role server –force
  • Set-ExecutionPolicy Bypass
    (note: this disables powershell security checks for all scripts on your server.  You may want to investigate using a signed script so you don’t need to completely disable this security feature.  You *should* be able to make this work with “Set-ExecutionPolicy Restricted” and appropriate IE settings, but I couldn’t get it to work reliably)
  • set-item WSMan:\localhost\Shell\MaxMemoryPerShellMB -Value 1024
    (allows the PowerShell remote shell to use more memory.  Without this, you will get errors about “Not enough storage”.  See http://www.scriptinganswers.com/forum2/forum_posts.asp?TID=4183&PID=26202 )
  • net stop winrm; net start winrm
    (restarts the service that hosts powershell remoting so the previous setting change takes effect right away)

Step 3: Create scripts

We need two scripts in your project – one that will be copied to the drop location beside our WSPs and handle all the Sharepoint calls (deployall.ps1), and one that will run on our build server (remotedeploy.ps1) and use PowerShell Remoting to call deployall.ps1 on the target server.

Deployall.ps1 basically does a retract/delete/add/install of your solution(s) on the specified web applications, then cycles IIS and OWSTimer. It needs to be added to the root of one of your projects that will be built by TFS (if you have several, which one doesn’t matter), and the “copy to output directory” property set to “copy always.  At a minimum, you’ll have to list your WSP(s) in the appropriate arrays near the beginning of the script:

  • # ALL wsps should be listed here, even ones listed in another group below
    $WSPS=@("MyWebAppDeployedWSP.wsp", "MyGlobalWSP.wsp");
  • # WSPs that should be deployed globally (not to a particular webapp)
    $WSPSGLOBAL=@("MyGlobalWSP.wsp");
  • # WSPs that are allowed to deploy to the GAC
    $WSPSGAC=@("MyGlobalWSP.wsp");
  • # WSPs that are allowed to deploy CAS policies
    $WSPSCAS=@("MyWebAppDeployedWSP.wsp");

You modify this script to do other things your project always needs at deploy time as well – copying PDBs to the GAC, activating web application features, registering ULS services, etc.  The appropriate places are mentioned in the script.

RemoteDeploy.ps1 can be put anywhere in your project, and does *not* need to have any special properties set on it (though it’ll work fine if you do). This script *does* need to be modified – you need to embed your farm admin username/password into the script. Look for these lines at the beginning of the script and fill in your credentials:

$secpasswd = ConvertTo-SecureString "MyPassword" -AsPlainText -Force;
$cred = New-Object System.Management.Automation.PSCredential ("MyDomain\MyFarmAdminAccount", $secpasswd);

BuildGACCopyCommands.ps1 is a small script you can run to build the commands you need to add to Deployall.ps1 in order to copy PDBs to the GAC when deploying.  You do *not* have to use it, and if you do, it doesn’t need to be in your project (ie. it’s a script you run to help write the other scripts, it’s never run in the build environment).

Step 4: import build template

Since this post is already longer than I planned, I’m not going to go into heavy details on how to make this build template – it follows the pattern laid out by http://www.ewaldhofman.nl/post/2010/11/09/Part-14-Execute-a-PowerShell-script.aspx to modify DefaultTemplate.xaml to add two optional Powershell scripts to call *after* the code has been built and copied to the drop location.  Get the .XAML file from the zip file at the beginning of this post, and check it into your “BuildProcessTemplates” folder in your TFS project.  We’ll cover the new properties this adds and what to set them to in the next section.

Step 5: configure build

In Visual Studio Team Explorer, right-click ‘builds’, and pick ‘create new build definition’.  Go to the ‘process’ screen.  If you don’t have the “BuildAndRemoteDeploy.xaml” template as a choice, pick “new template”, then “from source control”, and browse to the BuildAndRemoteDeploy.xaml file you added in step 4.

Pick your solution file to build and configuration (as you would for any TFS build).  Expand the ‘advanced’ section, and add “/p:IsPackaging=true” to the “MS Build Arguments” property (this causes MSBuild to output WSPs as it’s building your Sharepoint projects).

Now, it’s time to fill the arguments that make the deployment happen.  in the “Post-Drop Script 1”, fill in the fully-qualified TFS path to your “remotedeploy.ps1” script.  It should be something like “$/MyProject/trunk/Project/remotedeploy.ps1”.  In the “Post-Drop Script 1 Arguments”, supply any arguments that need to be passed to the script (in addition to the DropLocation parameter which is automatic) – in our case, that should be: “-WebApp http://mywebappurl/ –Server myservername”.  This tells the remote deploy script to connect to myservername for the deployment, and to tell deployall.ps1 to deploy the WSPs to the web application http://mywebappurl/.

image

Step 6: run the build

Now that all the pieces are in place, right-click the build, pick “queue new build”, and click “queue”.  If everything was set up right, your build will complete with no errors and be cleanly deployed to your deployment server.

Step 7: fix problems

Unfortunately, with something this complicated, it might not work right the first time.  First, make sure you run it with the logging verbosity (on the build properties) set to at least “Detailed” – there’s a bunch of useful stuff in there, like the full Powershell commands it runs – very useful so you can run them yourself manually and get more info about what’s failing.  Here’s some of the problems I’ve run into using this on a couple of projects so far, and what you need to do to work around it:

PROBLEM: Deploy script wouldn’t start, complained about “digital signing”

SOLUTION: Ran command “Set-ExecutionPolicy Unrestricted” on target Sharepoint server.

PROBLEM: Deploy script wouldn’t execute remotely, wanted confirmation (“Run only scripts that you trust. While scripts from the Internet can be useful, this script can potentially harm your computer.”)

SOLUTION: Ran command “Set-ExecutionPolicy Bypass” on endpoint server

PROBLEM: Deploy script complained about “disk space”

SOLUTION: Run command “set-item WSMan:\localhost\Shell\MaxMemoryPerShellMB -Value 1024” on endpoint server.

 

If you’re having another issue, these links may be helpful – they were all useful in some way to me while putting together and testing this post:

 

I hope this proves helpful to others out there – I’m pretty happy with how it turned out.  Slick, automated builds to my local servers, and a nice script to package with the WSPs to deploy onto the real production servers (deployall.ps1) as well.

Files referenced in this post

SYNDICATION