Azure DevOps gives you the ability to retain your release information for analysis. It also allows you to see what changes you’ve made to your application over time. If you ever need to roll back to a previous version, you can do it using some handy extensions. That said, you won’t have any customization options. Thus, you’ll need to resort to a PowerShell script here.
A PowerShell script gives you the extra customizations that allow you to set a retention policy and select other stages. This script also calls the Azure DevOps API and sorts your releases by the parameters (stage, number of days since the last release) you set. In this tutorial, I’ll walk you through writing this PowerShell script. Let’s first start with the initial setup and how to set your global variables.
1. Initial Setup
In your initial setup, you’ll want to:
- Add a PowerShell task and the retain indefinitely task. You can do this on your classic pipeline, at the end of your task group as shown in the image below
- Configure the setting in the retain indefinitely task, as shown in the image below
- Set your custom conditions to the following script to get it to work with your PowerShell task:
- Create the “ShouldRetainForever” in your PowerShell script. Then, pass it from the PowerShell task to the retain forever task
- Start scripting once the initial setup is ready
Next, let’s see how you can set up your retention policy.
2. Powershell Script: Retention Policy
The retention policy will be the number of days between releases you’d like to save and the environments you would like to save it on. In this section, I’ll go through the script bit by bit to help you understand what the code is doing.
When you make an API call to Azure, it’s good to make some of your info into variables you’ll pass in the URI. That way, you can easily change it and not use hard-coded values. Organization and project will be specific to your company. I’ve also included two pipeline variables. Thus, others, like project managers, can easily change the release retention policy without hardcoding values into the script.
Note: Pipeline variables require $env: prefix to grab any variables being input from the pipeline.
Thus, the script here would be as follows:
You can set your Retention rate as often as you’d like to save a release. In this case, I’ve set it to 30 days. Say the application is released each week; the script will check to see when the most recent saved release was. The current release will be saved if it’s older than 30 days. In other words, if in 30 days you have 4 releases, only 1 of the 4 will be saved forever. After you’ve set the variables, let’s write a function that will call the API to do a search and then filter the data.
3. Find the Last Release Function
Let’s start by writing a function that will be called to make an API call. This is a large function, so I’ll break it up for clarity.
- Set out headers. Note that if you want to get environment info (dev, test, stage, production), we’ll need to add the $expand=environment to get deeper into the API
- Write the URI and drop in our variables
- Set a try/catch block and set our response equal to the Invoke-RestMethod @params. If the API call fails in the catch block, write a status code and error message to inform the user and for logging
Now, let’s filter the data.
We’ll want to filter the API response down to the data we want to work with. That’s because the initial call will return every release for every application in that project which might be thousands. We take our $response and filter down all the release data where the environment is equal to “test”, the release has succeeded, and retained forever was set to true. See the below code block on how to set this up.
Now, we’ve filtered all the releases to only the ones that match that criteria. That concludes the function find-lastRelease. Next, we’ll handle input from pipeline variables and do the math on the retention policy.
- Handling Pipeline Variables
In the variable section, we have two variables you can set from the pipeline. We also want to ensure that if an input exists, the inputted values override the hardcoded ones in the script. If no inputted values exist, the script will use the hardcoded values.
In the above code block, we first handle the Retention Rate or the number of days. Thus, we say if the pipeline variable isn’t equal to null, set the $RetentionRate to whatever is set in the pipeline and write in the log that an inputted value was present. We then do the same for $stageToRetain. Maybe someone wants to retain releases on stage or dev to keep track of the changes over time. Next, we set our retention policy.
4. Retention Policy
Our retention policy is a simple equation that you can set to whatever time or the number of days you desire to space out the retention of the releases. In the below code snippet, we set our $RetentionPolicy to a cmdlet (get-date) which gets the current date. Then, we add days to it and subtract our $RetentionRate that we’ve set to 30. So let’s say today is November 30. We get the date and then make it into only days (when you get the date, it gives you the day, month, year, and time). Next, subtract 30 to end up with the date October 31.
We handled our pipeline variables and created a retention policy. Now, let’s handle the conditions on when to do what.
- Conditional Statements
In this section, I’ll go through 3 main conditional statements. The first one is easy. If the release environment is production, no matter what, save it forever. This preserves the original functionality of our extension.
The next condition is a bit complex since it has the callback to the function we wrote at the start. Thus, if the $currentReleaseEnv is equal to $stageToRetain (test in this case), we’ll call back our function to call the API and look for release information and set it to $LastRetainedRelease.
Last, we take $LastRetainedRelease and say if there was never one retained before, retain this current one. Otherwise, we use the release retention policy to check the dates and either save it or not.
Now, only one step remains. We need to set our boolean variable and pass it to the retain task so that it’ll work. In addition, we need to add in our logging and messages.
- Final Logging and Boolean Creation
In the last code block, we tackle logging and printing out variable values for debugging. That said, the most important is creating the boolean that will activate the retain forever task. I wrote it at the start of this article. Still, I’ll reiterate it here:
Write-Host “##vso[task.setvariable variable=ShouldRetainForever]$RetainForever”
If $Retainforever is equal to true, it’ll pass ShouldRetainForever to the next task and activate it. If $RetainForever is equal to false, ShouldRetainForever won’t get passed, and the retain task will be skipped.
Now, let’s wrap up with a quick recap.
Sorting and saving releases on stages other than production is helpful to find issues or track progress on an application over time without having to parse every single release. This script takes an existing task and allows you to customize it further, which was lacking in the retain indefinitely script. The PowerShell script works pretty well, but I’ve encountered errors. Sometimes the default variables for $stageToRetain and $RetentionRate get messed up if no pipeline variables are present. I’m unsure why this happens since the defaults should run if no pipeline variables are present. That said, to fix this, just add the pipeline variables.
Want to learn more or have more questions? Check out the FAQ and Resources below.
Which is better: Inline scripts or pull script from a repo?
In Azure DevOps, you can drop the code in the task as inline code if you’re using the classic pipeline. This is ok if the script is a short few lines. That said, if it’s complex, like the tutorial above, it’s best to save it in a repo of DevOps scripts and pull it from there.
Why does PowerShell have syntax inconsistencies to task groups?
That’s a good question. PowerShell has its own syntax. That said, whenever you need to set a custom condition on a task, the syntax isn’t the same as PowerShell. This can lead to a lot of keyboard banging. Until it’s corrected, the best advice is to keep some examples tucked away on your notepad.
How are pipeline variables used in PowerShell?
If you want to grab any variables from the pipeline, you need to set a prefix $env:NAME_OF_VARIABLE. This is the correct syntax to access the values of pipeline variables.
How should I use response data variables?
When you have data coming back in a response, you want to use that data as we did in the tutorial. You need to add $_. to the data variable you want to use. For example, $_.environments. The underscore period is part of PowerShell’s syntax that allows the program to access data coming back in a JSON response.
What’s the best way to debug?
I recommend using Visual Studio Code to write your code and then using the debugger to run the script. It’s handy when calling an API since you can enter the response, parse through the data manually, and find what you’re looking for. That said, once you move into the pipeline, you’ll need to do more debugging. Thus, be prepared to spend most of your time debugging once you’ve got it working locally.