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

An Omega Geek's Guide to Learning PowerShell

An Omega Geek's Guide to Learning PowerShell

Howard van Rooijen

PowerShell is fantastic language for DevOps, automation and general scripting. Every developer should be familiar with it; here's a guide to some useful resources to help you learn it.
How to consume a NuGet package in PowerShell

How to consume a NuGet package in PowerShell

Ed Freeman

Sometimes the logic for the bit of work you're doing in PowerShell won't be packaged into a handy PowerShell module with cmdlets to perform the operations you need to perform. But what if there is a .NET package listed on NuGet that does contain the assemblies you need for your work? How can we import that into PowerShell and make use of its classes and methods? This blog explains how to do just that, avoiding current pitfalls in PowerShell's implementation of the NuGet package provider.
How to update credentials for an on-prem Power BI data source using PowerShell

How to update credentials for an on-prem Power BI data source using PowerShell

Ed Freeman

Automating the updating of Power BI data source credentials is a common requirement in the application lifecycle management of a Power BI solution. However, on-premises data sources throw an extra spanner in the works: you must encrypt your credentials using the RSA-OAEP encryption algorithm before sending off the request to update the data source. There is currently no PowerShell module that encapsulates this logic into a set of handy cmdlets, so you must either implement the algorithm's logic yourself, or you can use the helper classes in the existing .NET SDK to do all the heavy lifting for you. This blog shows how to do the latter.

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.