TeamCity PowerShell
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.