Archive for March, 2013

Monday, March 11th, 2013

One of the benefits of a startup is there is a very rapid code, test, deploy to production cycle. For a while at CollectedIt we had a manual (but documented!) process to deploy code to production. This worked well for a while, but it started to get tedious. Plus as anybody who has done any number of production installs knows the more steps a human does, the more chances for an error.

It was time for an automated way to deploy to our code. First we looked into using something like TFS or Jenkins. These tools however required installation somewhere. CollectedIt is very lean so we prefer not installing excess services in production, spinning up a new server in the cloud, or investing in a physical server just for an automated build tool (to be clear we would spend the resources if we deemed it necessary). Next our thoughts turned to writing something homegrown.

CollectedIt runs on Windows servers in the cloud. I had been exploring PowerShell on and off for a little while and seemed like the perfect solution for a quick and easy homegrown deployment script.

PowerShell comes with a very powerful feature called Remoting which is a technology that lets you use PowerShell to remotely control one (or many) remote computers. There were however 2 major obstacles that we needed to be overcome.

  1. Remoting has no out of the box way to copy files from server to server
  2. Remoting over the Internet is not the most straight forward of configurations

Not having a way out of the box to copy bits to a server with PowerShell is annoying. There are ways to copy bits over the Remoting (such passing over a byte[] parameter when doing a remote call that has the contents of the file). However there was no way that was robust enough, or performed well enough for our tastes. We went ahead and configured an FTP server as a file server. Since PowerShell is built on top of .NET we can use FTP with Microsoft.NET. The code samples in the using FTP with Microsoft.NET blog entry are all in C#, but they translate to PowerShell fairly easily. Here is an example of using FTP over explicit SSL to upload a file.


$ftp = [System.Net.WebRequest]::Create($ftpuri)
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = New-Object System.Net.NetworkCredential($username, $password)
$ftp.EnableSsl = $true
$ftp.ContentLength = $filebytes.Length
$s = $ftp.GetRequestStream()
$s.Write($filebytes, 0, $filebytes.Length)
$s.Close()

Now that we have a way to copy bits we turned to the Remoting part. In order to use Remoting over the Internet first we had to enable Remoting on the server.

PS> Enable-PSRemoting -Force

If we were on a secured domain we would have no more steps. Getting Remoting to work securely over the Internet however, we are just getting started.

  1. Create a certificate for SSL. We used the makecert command that comes with the windows SDK.

    PS> makecert.exe -r -pe -n "CN=collectedit.com" `
    >> -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localmachine -sky exchange`
    >> -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
    >>

    This places the certificate inside the “Local Computer\Personal” certificate store.
  2. Get the thumbprint for the certificate.

    PS> ls Cert:\LocalMachine\My

    Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\My

    Thumbprint Subject
    ---------- -------
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX CN=collectedit.com

  3. Now we needed to create an HTTPS endpoint. We can use the winrm command to help with that. One note of warning: winrm was made for use at regular old cmd.exe . Using it with PowerShell we end up with a lot of backticks. If you run into frustration using winrm with PowerShell just switch to cmd.exe (it’s okay I won’t tell).

    PS> winrm create `
    winrm/config/Listener?Address=*+Transport=HTTPS`@`{Hostname=`"`collectedit.com`"`
    ;CertificateThumbprint=`"`XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`"`}
  4. At this point the server is able to accept Remoting connections over the internet over SSL. To disable the HTTP remoting listener it’s as easy as finding the listener then removing it.

    PS> ls WSMan:\localhost\Listener

    WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener

    Type Keys Name
    ---- ---- ----
    Container {Address=*, Transport=HTTP} Listener_809701527
    Container {Address=*, Transport=HTTPS} Listener_1353925758

    PS> Remove-Item WSMan:\localhost\Listener\Listener_809701527

To use PowerShell Remoting over SSL there are additional parameters we needed to set when creating a remote session. The first is to tell PowerShell to use SSL and the second is to ignore the certificate authority since our certificate is self signed. This is as easy as

PS> $so = New-PSSessionOption -SkipCACheck # skip certificate authority check
PS> Enter-PSSession localhost -UseSSL -SessionOption $so # note the "UseSSL"

If there are any issues connecting first check firewall settings to allow port 5986 then check out this awesome blog post on Remote PSSession Over SSL finally if you still have issues use the about_Remote_Troubleshooting help page

With the two major hurdles solved we were confident that we could use PowerShell for our uses. Now we just needed to piece together code for

  1. Building of the project
  2. Zipping up the project
  3. Installing the project

We could have leveraged something like PSake to do our dirty work however coming from a background of .NET/bash/batch it was actually easier to build up our script ourselves, this may change in the future.

To build the project we just used MSBuild.

$msbuild = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe"
if (![System.IO.File]::Exists($msbuild)) {
# fall back to 32 bit version if we didn't find the 64 bit version
$msbuild = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
}
$buildcmd = "$msbuild $SolutionFile /t:Rebuild /p:Configuration=$Config"
Invoke-Expression $buildcmd

Zipping up the project we chose to use the zip method that (finally!) comes with .NET 4.5 (note: this required us to use PowerShell v3), System.IO.Compression.ZipFile.CreateFromDirectory in PowerShell it looks like this


[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem")
[System.IO.Compression.ZipFile]::CreateFromDirectory($dir, $zip)

Installation of CollectedIt code was straight forward. From the beginning we created a setup.exe (that uses the excellent Insight Schema Installer from the Insight Micro-ORM project) to do an install of the SQL database that we could use on the command line (hence we could use it with PowerShell. The website only required the output from the build be copied to the production location. Again this was straight forward using PowerShell. We only had to Invoke a few commands remotely on the server to get this going. It looks something like this


Invoke-Command -SessionOption $so -ComputerName $Servers -Credential $remotecreds -ArgumentList ($Config) -ScriptBlock {
Param(
$Config
)

[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem")
[System.IO.Compression.ZipFile]::ExtractToDirectory("$Config.zip", "c:\$Config")
Invoke-Expression "C:\$Config\Setup\setup.exe"
Copy-Item -Verbose -Recurse -Force "C:\$Config\Web" "D:\webroot"
}

That’s all the piece we had to put together for our ‘one click’ install. I should mention that our $remotecreds variable is populated with the Get-Credential cmdlet. For more of a streamlined process we are investigating securely storing the creds. Something along the lines of what is covered in this blog post on Importing and Exporting Credentials in PowerShell.

Hope this helps you build a streamlined process for deploying your own code with PowerShell. Drop me a line with any questions or comments with your own PowerShell deployment scripts.