--- Sjoerd Hooft's InFormation Technology ---

User Tools

Site Tools



Recently Changed Pages:

View All Pages

View All Tags

WIKI Disclaimer: As with most other things on the Internet, the content on this wiki is not supported. It was contributed by me and is published “as is”. It has worked for me, and might work for you.
Also note that any view or statement expressed anywhere on this site are strictly mine and not the opinions or views of my employer.

Pages with comments

2019/11/18 13:52 1 Comment

View All Comments


TFS Maintenance Build

Note that this is done on TFS Server 2018

When using TFS for deploying an isolated test environment you might have the need to maintain that environment or get some monitoring working. We have the specific case that the environment is cloned from another environment so that means that names and ip addresses are also the same. So this article shows you how to create the infrastructure and the build definition to monitor such environment.


First you need a server to run a build agent on. Note that this server needs to be able to talk to the TFS server by hostname. So you might have to open up some firewall ports, provide NATting and add hostnames to DNS or the hostfile, it all depends on your specific setup. Once you can access the build server from the server, you need to install the build agent. I already explained on how to do that in this article.

Build Definition

There are several ways to do this, but I opted to create a PowerShell script that will run on the Build Agent and create some html files that can be published as an artifact so you can deploy them to a webserver.

Create the Build Definition

New Repository

First create a new repository to store the scripts we'll make later on. Go to Code → The current repository dropdown on the left and click “+ New Repository” . Use GIT and give it a name, her I'll use DevOps-Maintenance.

New Build Definition

Go to Build and Release → Builds and click “+ New”.

Make sure that when you create the Build Definition to select the Agent queue in which you installed the Build Agent in the step above. Also, make note of the name as you'll need it later on. The name I use in this article is: DevOps-DEV-Maintenance

Add PowerShell Script

Before you can run a powershell script you first need to create one. In the repository we created above create a new folder called powershell and then create a new file within the new folder. You can name it how you like but here I'm using basicmonitoring.ps1. Note that the ps1 extension is required.

This is the script I'm using to monitor state, diskusage and uptime:

# Define D-T-A environment depending on builddefinition name
if ($env:BUILD_DEFINITIONNAME -eq "DevOps-DEV-Maintenance"){$dtaenvironment = "dev"}
if ($env:BUILD_DEFINITIONNAME -eq "DevOps-TST-Maintenance"){$dtaenvironment = "tst"}
if ($env:BUILD_DEFINITIONNAME -eq "DevOps-ACC-Maintenance"){$dtaenvironment = "acc"}
Write-Host "Working in dta environment $dtaenvironment"
# adminpass is now a string so must be converted
$adminpasssec = ConvertTo-SecureString -String $adminpass -AsPlainText -Force
# Checking credentials
Write-Host "Credential variables user is $admin and password is $adminpasssec"
# Create the Credentials object
$admincreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $admin,$adminpasssec
# Script Variables 
# Disks to check for freespace - if changed you also need to change the Arrays to hold the information
$diskLetters = "C","D"
# Script Functions
# Function to display the uptime in a Human Readable Format
Function ToHumanReadable(){
  If ($timespan.TotalHours -lt 1) {
    return $timespan.Minutes + "minutes"
  $sb = New-Object System.Text.StringBuilder
  If ($timespan.Days -gt 0) {
    [void]$sb.Append(" days")
    [void]$sb.Append(", ")    
  If ($timespan.Hours -gt 0) {
    [void]$sb.Append(" hours")
  If ($timespan.Minutes -gt 0) {
    [void]$sb.Append(" and ")
    [void]$sb.Append(" minutes")
  return $sb.ToString()
# Function to check if a server responds to ping, and if so, responds to wmi
Function CheckAvailability(){
    param ($computername)
    if (Test-Connection $computername -quiet -count 1){
		# Server responds to ping
		# Powershell 6 does not support wmi like this anymore:
		if ($PSVersionTable.PSVersion.Major -eq 6){
			Return "OK"}
		elseif (get-wmiobject -ErrorAction SilentlyContinue -computername $computername "win32_process" -Credential $admincreds){
			# Server responds to wmi
			Return "OK"}
		else{Return "WMIError"}}
	else{Return "PingError"}
# Function to check hard disk free space and percentage
Function CheckHardDiskUsage() { 
	param ($hostname, $deviceID)
    	$HardDisk = $null
		$HardDisk = Get-WmiObject Win32_LogicalDisk -ComputerName $hostname -Filter "DeviceID='$deviceID' and Drivetype='3'" -ErrorAction Stop -Credential $admincreds| Select-Object Size,FreeSpace
		#if ($HardDisk -ne $null){
        if (!(([string]::IsNullOrEmpty($HardDisk)))){
            $DiskTotalSize = $HardDisk.Size 
            $DiskFreeSpace = $HardDisk.FreeSpace 
            $PercentageDS = (($DiskFreeSpace / $DiskTotalSize ) * 100); $PercentageDS = [math]::round($PercentageDS, 2)
            Add-Member -InputObject $HardDisk -MemberType NoteProperty -Name PercentageDS -Value $PercentageDS
            Add-Member -InputObject $HardDisk -MemberType NoteProperty -Name frSpace -Value $frSpace
		return $HardDisk
        write-host "Error returned while checking the Hard Disk usage. Perfmon Counters may be fault"
# Create a raw list of AD servers (Note, won't work for Windows 2000)
$computers = Get-ADComputer -Filter {(operatingSystem -like "*windows*Server*")}
# Creating arrays to hold server information
$allserverstatus = @()
$allserverstatusnotok = @()
# Start checking all servers in this environment
ForEach ($computer in $computers){
    $computername = [string]$computer.Name
    $serverstate = CheckAvailability $computername
	if ($serverstate -eq "OK"){
        write-host "$computername = $serverstate"
        $serverinfo = "" | Select-Object name,status,cdisk,cdiskperc,ddisk,ddiskperc,uptime
        $ = $computername
        $serverinfo.status = $serverstate
        # Checking diskspace
        foreach ($disk in $diskLetters)
            # Check Disk Usage 
            $HardDisk = CheckHardDiskUsage -hostname $computername -deviceID "$($disk):"
            #if ($HardDisk -ne $null) {	
                if (!(([string]::IsNullOrEmpty($HardDisk)))){
                $PercentageDS = $HardDisk.PercentageDS
                $frSpace = $HardDisk.frSpace
                # Add the variables to output 
                write-host "Disk is $disk, Free disk space is $frSpace GB, Percentage is $PercentageDS %"
                if ($disk -eq "C"){
                    $serverinfo.cdisk = "$frSpace GB"
                    $serverinfo.cdiskperc = "$PercentageDS %"
                if ($disk -eq "D"){
                    $serverinfo.ddisk = "$frSpace GB"
                    $serverinfo.ddiskperc = "$PercentageDS %"
                # Resetting values
                $PercentageDS = 0
                $frSpace = 0
                $HardDisk = $null
        # Check uptime
        try { $wmi=Get-WmiObject -class Win32_OperatingSystem -computer $computername -Credential $admincreds}
        catch { $wmi = $null }
        # Perform WMI related checks
        #if ($wmi -ne $null) {
        if (!(([string]::IsNullOrEmpty($wmi)))){
            Write-Host "last boot time = $LBTime"
            [TimeSpan]$uptime=New-TimeSpan $LBTime $(get-date)
            $hruptime = ToHumanReadable($uptime)
            $serverinfo.uptime = $hruptime
        else { 
            write-host "WMI connection failed - check WMI for corruption"
    # Update Array
    $allserverstatus += $serverinfo
    elseif ($serverstate -eq "WMIError"){
        write-host "$computername = $serverstate"
        $serverinfo = "" | Select-Object name,status
        $ = $computername
        $serverinfo.status = $serverstate
        $allserverstatusnotok += $serverinfo
    elseif ($serverstate -eq "PingError"){
        write-host "$computername = $serverstate"
        $serverinfo = "" | Select-Object name,status
        $ = $computername
        $serverinfo.status = $serverstate
        $allserverstatusnotok += $serverinfo
# need to export the output to html
# Not all servers have E-drive
if (Test-path -Path "E:\DevOps\Output"){
    $allserverstatus | ConvertTo-Html | Out-File E:\DevOps\Output\$($dtaenvironment)serverstatus.html
    $allserverstatusnotok | ConvertTo-Html | Out-File E:\DevOps\Output\$($dtaenvironment)serverstatusnotok.html
}elseif (Test-path -Path "D:\DevOps\Output"){
    $allserverstatus | ConvertTo-Html | Out-File D:\DevOps\Output\$($dtaenvironment)serverstatus.html
    $allserverstatusnotok | ConvertTo-Html | Out-File D:\DevOps\Output\$($dtaenvironment)serverstatusnotok.html


  • The scripts expects the user and the user password as input. You need to add these to the variables.
  • The script is ready for multiple environments and different types of build agent servers. Modify the script to your need accordingly.

Task: Run PowerShell Script

Follow these steps to add the script to the build definition:

  • Add a task of the type PowerShell, version 1
  • Modify these settings:
    • Type: File Path
    • Script Path: powershell/basicmonitoring.ps1
    • Arguments: -admin $(devadmin) -adminpass $(devadminpass)
    • Control Option: Continue on Error should be enabled

Add Variables

Go to the variables tab and add the variables devadmin and devadminpass as required by input. Mark the devadminpass as a secret.

Task: Copy Files

Follow these steps to copy the output files to the build artifact directory:

  • Add a taks of the type Copy Files, version 2
  • Modify these settings:
    • Source Folder: E:\DevOps\Output (but depends on the exact output. Check the script above)
    • Contents: **
    • Target Folder: $(build.artifactstagingdirectory)

Task: Publish Build Artifacts

Follow these steps to publish the artifact so it can be released:

  • Add a task of the type Publish Build Artifacts
  • Modify these settings:
    • Path to Publish: $(build.artifactstagingdirectory)
    • Artifact Name: Maintenance
    • Artifact Type: Server


As shown before in create_release you can now create a new release pipeline or add the artifact to a current release. As Creating a Build and Release Pipeline in TFS 2018 already shows you how to create a release pipeline follow these steps to add this artifact to a existing one:

  • Go to Releases and edit the Release Pipeline you want to edit
  • In the Artifact section you can click +Add to select the Build Definition as a source. Note thtat you must have run the build in order to be able to select it. Click done to save, and you'll see that the artifact will have been added to the artifact section.
  • Now in the environment section click the link to the phases and tasks
  • As I am releasing to a website click the plus sign to add a task: Deploy IIS website
  • Modify these settings:
    • Package or Folder: $(System.DefaultWorkingDirectory)/DevOps-DEV-Maintenance/maintenance

That's all, you can now configure CI/CD if needed.


Enter your comment. Wiki syntax is allowed:
tfsmaintenancebuild.txt · Last modified: 2020/02/25 12:57 by sjoerd