So you need to authenticate yourself against the TFS?

Normally when you use the TFS REST API against a TFS located in your domain and you’re already authenticated against the domain you could use the switch -UseDefaultCredentials when you try to execute Invoke-WebRequest or Invoke-RestMethod. However, if you have a computer outside of the domain and you’re not authenticated against the DC, how could you connect in such case? Well if you add following function to your script:

function New-PSCredential {
  param (
    [string] $Username,
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Password
  )
  Set-StrictMode -Version 'Latest'
  if( $Password -is [string] ) {
    $Password = ConvertTo-SecureString -AsPlainText -Force -String $Password
  }
  elseif( $Password -isnot [securestring] ) {
    Write-Error ('Value for Password parameter must be a [String] or [System.Security.SecureString]. You passed a [{0}].' -f $Password.GetType())
    return
  }
  return New-Object 'Management.Automation.PsCredential' $UserName, $Password
}

 

Then you could call it to get a credential that works for the purpose.

[Management.Automation.PsCredential] $cred = New-PSCredential -Username 'domain\username' -Password 'p@55w0rd'

 

And then you could simply call the Rest or Web-method like this:

$resp = Invoke-WebRequest -Uri $uri -ContentType application/json -Method Get -Credential $cred

Advertisements

Reading the result of all releases and deploys in a TFS project

If you need a quick look at the results for all releases and deploys in a TFS project you could make use of the TFS REST API. Following snippet will pull out all the releases for the project and return them in object format.

$TfsUri = http://yourtfs:8080/tfs/yourcollection/'
$TfsProjects = 'yourproject'
$Uri = '{0}{1}/_apis/release/releases' -f $TfsUri, $TfsProject
$Releases = Invoke-RestMethod -Uri $Uri -ContentType application/json -Method Get -UseDefaultCredentials

 

If you would like to save the result to a file with json-format:

$Releases | ConvertTo-Json –Compress –Depth 10 | Out-File "releases.json"

 

Or, if you would like to dive deep into all the relesases and verify all the environments and their results you could start by creating an ArrayList for all the Environments:

$ReleaseList = New-Object System.Collections.ArrayList

 

And then simply iterate all the releases that you read before and add some info from each environment into the ArrayList:

foreach($Release in ($Releases.value)){
  $ReleaseData = Invoke-RestMethod -Uri $Release.url -ContentType application/json -Method Get -UseDefaultCredentials
  $ReleaseData.environments | ForEach-Object {
    $ReleaseList.Add([PSObject]@{ Id=$ReleaseData.id; Name=$ReleaseData.name; Environment=$_.name; Status=$_.status; CreatedOn=$_.createdOn }) | Out-Null
  }
}

 

If you finally would like to output some readable list you could do something like this:

$TitleRow = "{0,-10} {1,-25} {2,-25} {3,-25} {4,-20}" -f "Id", "Name", "Environment", "CreatedOn", "Status"
Write-Host $TitleRow
$ReleaseList | ForEach-Object {
  $Date = ""
  if(![string]::IsNullOrEmpty($_.CreatedOn)) {
    $Date=[DateTime]::Parse($_.CreatedOn)
  }
  $DataRow = "{0,-10} {1,-25} {2,-25} {3,-25} {4,-20}" -f $_.Id, $_.Name, $_.Environment, $Date, $_.Status
  if(($_.Status -eq "rejected")-or($_.Status -eq "canceled")) {
    Write-Host $DataRow -ForegroundColor Red
  }
  elseif($_.Status -eq "succeeded") {
    Write-Host $DataRow -ForegroundColor Green
  }
  elseif($_.Status -eq "notStarted") {
    Write-Host $DataRow -ForegroundColor Yellow
  }
  else{
    Write-Host $DataRow -ForegroundColor White
  }
}

 

Quite simple, this sample is not supposed to be perfect, just a brief description of how to play around with the TFS REST API.

If you want to know more about what you could do with the API, please refer to https://www.visualstudio.com/en-us/docs/integrate/api/overview

Pre and post build events in VS C# projects

When you’re doing build automation in TFS you could end up with a non working build because the developers added some non-standard / non-compatible pre or postbuild events to the solution.

So if you want to know if there’s some Visual Studio type PreBuildEvent or PostBuildEvent defined in your solution, then here’s a short PowerShell snippet that you could run:

 

gci $folder\*.csproj -Recurse | %{
  [xml]$rows = gc $_.FullName
  if($rows.Project.PropertyGroup.PreBuildEvent -ne $null)
  {
    Write-Warning ("Contains PreBuild event: {0}" -f $_.FullName)
    $rows.Project.PropertyGroup.PreBuildEvent
  }
  if($rows.Project.PropertyGroup.PostBuildEvent -ne $null)
  {
    Write-Warning ("Contains PostBuild event: {0}" -f $_.FullName)
    $rows.Project.PropertyGroup.PostBuildEvent
  }
}

If on the other hand they are defined as MSBuild type BeforeBuild and AfterBuild directly in the csproj (with support for parameters etc), the script should look like this instead:

gci $folder\*.csproj -Recurse | %{
  [xml]$rows = gc $_.FullName
  if(($rows.Project.Target | ? {$_.Name -match "BeforeBuild"}) -ne $null)
  {
    Write-Warning ("Contains BeforeBuild event: {0}" -f $_.FullName)
    ($rows.Project.Target | ? {$_.Name -match "BeforeBuild"}).OuterXml
  }
  if(($rows.Project.Target | ? {$_.Name -match "AfterBuild"}) -ne $null)
  {
    Write-Warning ("Contains AfterBuild event: {0}" -f $_.FullName)
    ($rows.Project.Target | ? {$_.Name -match "AfterBuild"}).OuterXml
  }
}

Both snippets will start in the directory identified by $folder and recurse through all folders searching for *.csproj. For each folder found they will read the content to find the identifiers for these kind of events.

Working with branches and security inheritance in TFS

I just ran into some issues at a customer site. First of all it showed out that they had an incredible amount of branches (almost 2.500) and secondly they had a mess in these branches because security inheritance was turned off in all of them. Ok you think, does it matter? Well, if you’re using build automation it does, consider following.

Build agents are defined at collection level, each agent is running as a service with a service account and all these service accounts needs access to the version control to be able to get the source code it’s going to build. For that reason there’s a collection level group called “Project Collection Build Service Account”, this is where you add all the service accounts that should be able to build (get) the source code in all the projects hosted inside the collection. This is what you could consider as a basic default, add the service account to that group and by inheritance that group should have access to the whole version control, just like the groups “Project Collection Administrators” and the “Project Collection Build Administrators” have their access to all the projects.

If, on the other hand, you have some odd projects where they need a more tightened security, then they become a target of change. In that case you break the inheritance and modify it on a project level. And just like that, if you have a branch that needs a specific security level you break the inheritance on that single branch. That’s the way it should be done, using inheritance in most cases until it isn’t feasible, at that time you can break it and define something different.

Ok, back to the problem I ran into, how to solve the issue of almost 2.500 branches having no security inheritance and trying to apply build automation with the need of that inherited security. That’s where PowerShell and the TFS Rest API will be your absolutely best friend!

$cred = New-PSCredential -Username “domain\username” -Password “pa55w0rd”
$uri = “http://YourTfs:8080/tfs/DefaultCollection/_apis/tfvc/branches?includeChildren=true”
$rootBranches = Invoke-RestMethod $uri -Method Get -ContentType application/json -Credential $cred

 

That’s it, by running those three lines of code you’ll get a PSObject in return containing all the root branches including their child branches. Just to iterate them over and for each branch simply call the old TF.exe with correct parameters for applying inheritance. Following commandline will accomplish this.

tf.exe vc permission /inherit:yes /collection:YourCollectionUri TheBranchPath

 

Okay, so let’s put it all together into a script:

function New-PSCredential {
  param (
    [string] $Username,
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Password
  )
  Set-StrictMode -Version ‘Latest’
  if($Password -is [string]) {
    $Password = ConvertTo-SecureString -AsPlainText -Force -String $Password
  }
  elseif($Password -isnot [securestring]) {
    Write-Error (‘Value for Password parameter must be a [String] or [System.Security.SecureString]. You passed a [{0}].’ -f $Password.GetType())
    return
  }
  return New-Object ‘Management.Automation.PsCredential’ $UserName, $Password
}
 
function Recurse {
  param(
    $branch,
    $tf,
    $collection
  )
  & “$tf” vc permission /inherit:yes /collection:$collection $($branch.path)
  $branch.children | ForEach-Object {
    Recurse $_ $tf $collection
  }
}
 
#Find latest version of TF.exe
$tf = (Get-ChildItem “C:\Program Files (x86)\Microsoft Visual Studio 1?.0\Common7\IDE\tf.exe” | Sort-Object CreationTime -Descending | Select-Object -First 1).FullName
if([string]::IsNullOrEmpty($tf)) {
  Write-Error “Can’t find TF.exe”
  exit
}
 
#Set some useful variables
$cred = New-PSCredential -Username “domain\user” -Password “pa55w0rd”
$collectionUri = “http://YourServer:8080/tfs/DefaultCollection”
$branchUri = “$collectionUri/_apis/tfvc/branches?includeChildren=true”
 
#Get all the root branches
$rootBranches = Invoke-RestMethod $branchUri -Method Get -ContentType application/json -Credential $cred
 
#Recursively run TF.exe on each one of them
$rootBranches.value | ForEach-Object {
  Recurse $_ $tf $collectionUri
}

New fresh start

Started my own company a while ago so I thought I will use this place to share some solutions and ideas around the stuff I’m working with, Application Lifecycle Management, Software Configuration Management, Continuous Delivery/Deploy, DevOps and Test Automation.