This article is part of the series 'Consultant tips'.

As a consultant when a job ends you need to transition from the previous to the next. How do you efficiently clean up all open ends? The open branches that you leave behind are sort of dead code. Nobody is going to take care of it. Make sure you don’t generate more work for ex-colleagues following these tips.

Using the scripts below we can create a small to-do list, which you should do before leaving a company.

💬 “The first impression is just as important as the last impression. Make sure you leave a good impression.” - Mart de Graaf

1. No open work items

Even if you would work with Trello, it would be nice to hand over open items to coworkers. When working in Azure DevOps, you can use the following Powershell script to get all open work items assigned to you.

In the PowerShell script below we can easily get all open work items. This is not only handy when leaving companies but also when you want to get an overview of all open work items. You can use this script to get knowledge of all open work items or to hand over the work to a colleague. You could also do this in Azure DevOps, but when you already have a PAT-token, you can check this as well with this easy script.

OpenWorkItems.ps1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Set variables
$organizationUrl = "https://dev.azure.com/MART" # Replace Mart with organization name
$projectName = "ProjectName" # Replace ProjectName with project name

# Get the access token from current az login session
# see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli
$accessToken = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query "accessToken" --output tsv
if ($null -eq $accessToken) {
    exit 1
}
# Set headers
$headers = @{
    "Authorization" = ("Bearer {0}" -f $accessToken)
    "Accept"        = "application/json"
}

# Get a list of workitems for given username
$workItemsUrl = "$organizationUrl/$projectName/_apis/wit/wiql?api-version=6.0"

$wiql = @"
SELECT [System.Id], [System.Title], [System.State], [System.AssignedTo], [System.Tags], [System.WorkItemType]
FROM workitems
WHERE [System.TeamProject] = @project
AND [System.WorkItemType] = 'Task'
AND [System.State] <> 'Closed'
AND [System.State] <> 'Removed'
AND [System.State] <>  'Done'
AND [System.AssignedTo] = @me
ORDER BY [System.ChangedDate] desc
"@

$body = @{ query = $wiql } | ConvertTo-Json

$workItemsResponse = Invoke-RestMethod -Uri $workItemsUrl -Headers $headers -Method Post -Body $body -ContentType "application/json"

# use workitemsbatch api to get all SELECT values

$workItemsUrl = "$organizationUrl/$projectName/_apis/wit/workitemsbatch?api-version=6.0"

# get the ids from $workItemsResponse.workItems in a list max 200
$ids = $workItemsResponse.workItems.id | Select-Object -First 200

# body is the list of ids in the workitemsresponse workitems.id, and the fields to select in a fields array
$body = @{ ids = $ids; fields = "System.Id", "System.Title", "System.State", "System.AssignedTo", "System.Tags", "System.WorkItemType" } | ConvertTo-Json

$workItemsResponse2 = Invoke-RestMethod -Uri $workItemsUrl -Headers $headers -Method Post -Body $body -ContentType "application/json"

Write-Host "Workitems found '$($workItemsResponse.workItems.count)'"

# write the response to a JSON file
$workItemsResponse2 | ConvertTo-Json -Depth 100 | Out-File -FilePath "workitems.json" -Force

Output

It will be output in a JSON file, for now. I think that will be easy to read if you are leaving. It also limits to 200 work items. If you have more than 200 work items, you probably have a problem.

2. No open branches 🥦 or pull requests

When working with Git, you can use the following Powershell script to get all open branches. You can use this script to get knowledge of all open branches or to create a pull request for each branch. The pull request can be used to hand over the work to a colleague.

OpenBranches.ps1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# Set variables
$organizationUrl = "https://dev.azure.com/MART" # Replace Mart with organization name
$projectName = "ProjectName" # Replace ProjectName with project name

$dayTolerance = 14

# Get the access token from current az login session
# see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli
$accessToken = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query "accessToken" --output tsv
if ($null -eq $accessToken) {
    exit 1
}
# Set headers
$headers = @{
    "Authorization" = ("Bearer {0}" -f $accessToken)
    "Accept"        = "application/json"
}

# Get a list of repositories in the project
$reposUrl = "$organizationUrl/$projectName/_apis/git/repositories?api-version=6.0"
$reposResponse = Invoke-RestMethod -Uri $reposUrl -Headers $headers -Method Get

Write-Host "Repos found '$reposResponse.count'"

#create dictionary openBranchesPerUser
$openBranchesPerUser = @{}

foreach ($repo in $reposResponse.value) {
    $repoName = $repo.name
    
    if ($repo.isDisabled) {
        Write-Host "Skipping disabled repo: '$repoName'" -foregroundcolor Gray
        continue;
    }
    
    Write-Host "Checking '$repoName':" -foregroundcolor yellow
	
    $branchesUrl = "$organizationUrl/$projectName/_apis/git/repositories/$repoName/refs?filter=heads&api-version=6.0"
    $branchesResponse = Invoke-RestMethod -Uri $branchesUrl -Headers $headers -Method Get
    foreach ($branch in $branchesResponse.value) {
        $branchName = $branch.name
        $BranchNameTrimmed = $branchName.replace('refs/heads/', '')
        if ($BranchNameTrimmed -eq 'master') {
            continue;
        }
        if ($BranchNameTrimmed -eq 'main') {
            continue;
        }
        $encodedBranchName = [System.Uri]::EscapeDataString($branchName)
        $pushesUrl = "$organizationUrl/$projectName/_apis/git/repositories/$repoName/pushes?searchCriteria.includeRefUpdates&searchCriteria.refName=$encodedBranchName&api-version=6.0"
        ## Write-Host "$branchName - $pushesUrl"
        $pushesResponse = Invoke-RestMethod -Uri $pushesUrl -Headers $headers -Method Get
        # get first push in the list
        $push = $pushesResponse.value[0];
        $firstPush = $pushesResponse.value[-1];

        #Convert $lastPush.date to DateTime object
        $lastPushDate = [DateTime]::Parse($firstPush.date);
        # if the last push date is older than today minus the dayTolerance, skip the branch
        if ($lastPushDate -gt (Get-Date).AddDays(-$dayTolerance)) {
            Write-Host "Skipping '$repoName' - '$branchName' - last push date '$($lastPush.date)'  compare date '$((Get-Date).AddDays(-$dayTolerance))' '$($push.pushedBy.uniqueName)'"  -foregroundcolor Red
            continue;
        }
        else {
            Write-Host "Checking '$repoName' - '$branchName' - last push date '$($lastPush.date)' compare date '$((Get-Date).AddDays(-$dayTolerance))' '$($push.pushedBy.uniqueName)' "
        }

        $pushedBy = $firstPush.pushedBy.uniqueName
        # Add to openBranchesPerUser dictionary with the user name as key and a object as value including branch name, repositoy and respository url
        if ($openBranchesPerUser.ContainsKey($pushedBy)) {
            $openBranchesPerUser[$pushedBy] += [PSCustomObject]@{
                Repository    = $repoName
                RepositoryUrl = $repo.webUrl + "/branches?_a=all"
                Branch        = $branchName
                firstPushDate = $firstPush.date
                lastPushDate  = $push.date
                lastPusher    = $push.pushedBy.uniqueName
            }
        }
        else {
            $openBranchesPerUser.Add($pushedBy, @([PSCustomObject]@{
                        Repository    = $repoName
                        RepositoryUrl = $repo.webUrl + "/branches?_a=all"
                        Branch        = $branchName
                        firstPushDate = $firstPush.date
                        lastPushDate  = $push.date
                    }))
        }
    }
}

# Write openBranchesPer user as a table, exclude RepositoryUrl
$openBranchesPerUser.GetEnumerator() | ForEach-Object {
    Write-Host "User: $($_.Key)" -ForegroundColor Green
    $_.Value | Format-Table -Property Repository, Branch, firstPushDate, lastPushDate, lastPusher -AutoSize
}

# Write openBranchesPerUser to a JSON file
$openBranchesPerUser | ConvertTo-Json | Out-File -FilePath "openBranchesPerUser.json" -Encoding ascii

Output

The output will be visible in the console and a JSON file.

3. Get feedback

Ask for feedback from your colleagues and manager. This feedback can be used to improve yourself in the future. It can also be used to improve the company you worked for. If you don’t ask for feedback, you will never know what you could have done better.

When asking for feedback keep in mind it’s to improve yourself, not to get a compliment. You can ask for feedback in the following way:

💬 “What could I have done better while working together?”

4. Say goodbye

Take the time to say goodbye to your colleagues and express your gratitude for the time you spent working together. You never know when you might cross paths with them again in the future.

Make sure you connect on social media with people you want to connect on the long term.

Checklist

  • ☑ Hand over open work items, or unassign them
  • ☑ Delete open branches
  • ☑ Say Goodbye to your team and colleagues
  • ☑ Check for your ip whitelists in Azure DevOps

Conclusion

When leaving a company, you want to leave no technical debt behind and clean up after yourself. You can use the scripts in this article to help you with that.

Further reading

This article is part of the series 'Consultant tips'.