Skip to content
Howard van Rooijen By Howard van Rooijen Co-Founder
TeamCity PowerShell

teamcity powershell specs image

Last week I was formally invited to become a member of the JetBrains Development Academy Board – to celebrate, I decided to give something back to the community that has a JetBrains flavour. As I mentioned in my last post – we've been doing a lot of ALM / DevOps work in the last year and some of those projects have involved implementing TeamCity and other have involved using a lot of PowerShell – so I thought it would be a good idea to combine to two.

Rather than implement a PowerShell API from scratch I decided to "work smarter" and stand on the shoulders of giants – in this case fellow JetBrains Academy Member Paul Stack, who created a very nice C# TeamCity Wrapper called TeamCitySharp.

PowerShell is hosted on the CLR – so calling .NET types is a breeze – it was very straightforward to wrap the C# API. Firstly I used dotPeek to list all the method names and I pasted these into a new PowerShell script and converted each of them into the vanilla Pester BDD format and outlined some behaviours:

Describe "Get-AllAgents" {
    It "should return multiple agents" {
    }
}

The next step was to create the parameters to pass into the cmdlet. To start with I mimicked the C# API and had expressive function signatures – but after writing the first couple of cmdlets did a small refactoring and switched to using splatting (one of the most unknown / underused features of PowerShell), which allowed me to create a pseudo ConnectionDetails object that is in fact a hashtable – which is a much nicer data structure for describing all the different connection options. The final step is to test the results of your cmdlet.

$ConnectionDetails = @{
   ServerUrl = "teamcity.codebetter.com"
   Credential = New-Object System.Management.Automation.PSCredential("teamcitysharpuser", (ConvertTo-SecureString "qwerty" -asplaintext -force))
}

Describe "Get-AllAgents" {
    $parameters = @{ ConnectionDetails = $ConnectionDetails }
    $result = Get-AllAgents @parameters
    It "should return multiple agents" {
       $result.Count.should.have_count_greater_than(1)
    }
}

This test will obviously fail as we haven't implemented the Get-AllAgents cmdlet – but that's simple enough to fix:

Function Get-AllAgents
{
   param
   (
      [Hashtable]
      $ConnectionDetails
   )

   $client = New-TeamCityConnection @PSBoundParameters
   return $client.AllAgents()
}

Note the use of @PSBoundParameters – this allows you to splat the parameters passed into the current cmdlet into a nested cmdlet. Very cool indeed and saves a lot of typing.

I repeated the process for the rest of the TeamCitySharp API and created the following cmdlets:

  • Get-AllAgents
  • Get-AllBuildConfigs
  • Get-ArtifactsByBuildId
  • Get-Artifact
  • Get-ArtifactsAsArchive *
  • Get-BuildConfigByConfigurationName
  • Get-AllBuildsOfStatusSinceDate
  • Get-AllBuildsSinceDate
  • Get-AllChanges
  • Get-AllGroupsByUserName
  • Get-AllProjects
  • Get-AllRolesByUserName *
  • Get-AllServerPlugins *
  • Get-AllUserGroups
  • Get-AllUserRolesByUserGroup
  • Get-AllUsers *
  • Get-AllUsersByUserGroup
  • Get-AllVcsRoots
  • Get-BuildConfigByConfigurationId
  • Get-BuildConfigByConfigurationName
  • Get-BuildConfigByProjectIdAndConfigurationId
  • Get-BuildConfigByProjectIdAndConfigurationName
  • Get-BuildConfigByProjectNameAndConfigurationId
  • Get-BuildConfigByProjectNameAndConfigurationName
  • Get-BuildConfigsByBuildConfigId
  • Get-BuildConfigsByConfigIdAndTag
  • Get-BuildConfigsByConfigIdAndTags
  • Get-BuildConfigsByProjectId
  • Get-BuildConfigsByProjectName
  • Get-BuildsByBuildLocator *
  • Get-BuildsByUserName
  • Get-ChangeDetailsByBuildConfigId
  • Get-ChangeDetailsByChangeId
  • Get-ErrorBuildsByBuildConfigId *
  • Get-FailedBuildsByBuildConfigId *
  • Get-LastBuildByAgent
  • Get-LastBuildByBuildConfigId
  • Get-LastChangeDetailByBuildConfigId
  • Get-LastErrorBuildByBuildConfigId *
  • Get-LastFailedBuildByBuildConfigId
  • Get-LastSuccessfulBuildByBuildConfigId
  • Get-LatestArtifact
  • Get-NonSuccessfulBuildsForUser
  • Get-ProjectById
  • Get-ProjectByName
  • Get-ServerInfo
  • Get-SuccessfulBuildsByBuildConfigId
  • Get-VcsRootById

* denotes a cmdlet that has been implemented but doesn't have a passing test (mainly due to a lack of rights on the http://teamcity.codebetter.com server).

Although the tests are integration tests and a little slow to run – there is nothing more reassuring than having a full suite of specifications:

A simple example of using the TeamCityPowerShell API is as follows:

$parameters = @{
     ConnectionDetails = @{
         ServerUrl = "teamcity.codebetter.com"
         Credential = New-Object System.Management.Automation.PSCredential("teamcitysharpuser", (ConvertTo-SecureString "qwerty" -asplaintext -force))
     }
     BuildConfigId = "bt437"
}

$builds = Get-BuildConfigsByBuildConfigId @parameters

foreach($build in $builds)
{
    Write-Host $build.Number
}

Very straight forward.

If you don't want to store your TeamCity credentials in plain text you can either enter them interactively using the following code:

$parameters = @{
   ConnectionDetails = @{
      ServerUrl = "teamcity.codebetter.com"
      Credential = Get-Credential
   }
   BuildConfigId = "bt437"
}

Alternatively you can retrieve them disk using this PowerShell Cookbook recipe: Importing and Exporting Credentials in PowerShell

One item to note - TeamCityPowerShell depends on TeamCitySharp which is a .NET 4.0 application. By default PowerShell only supports .NET 2.0 - to enable .NET 4.0 support copy TeamCityPowerShell\SetUp\PowerShell.exe.config to the PowerShell install directory - this allows PowerShell to host the .NET 4.0 runtime.

You can find TeamCityPowershell on GitHub. If you have any feedback – please get in contact.

@HowardvRooijen

Howard van Rooijen

Co-Founder

Howard van Rooijen

Howard spent 10 years as a technology consultant helping some of the UK's best known organisations work smarter, before founding endjin in 2010. He's a Microsoft ScaleUp Mentor, and a Microsoft Azure MVP, and helps small teams achieve big things using data, AI and Microsoft Azure.