If you are using TFS or now called Azure DevOps and still maintain a TFVC project (source code) you may not have many options for automating certain tasks.
In this article I will touch on the use of TF.exe which does some unique commands that can only be done with that command line utility none-interactively (in a script or command shell for the purpose of automation). I will focus more on the bigger problem that you will encounter, which is how can you authenticate the TF.exe utility none-interactively in a script being run in Azure DevOps for example.
Background
Building workflows (CICD) for your projects require executing certain tasks to get the job done. Now that DevOps is so popular you will find lots of good tools and resources that covers common scenarios for CICD and test automation. Most of which revolve around using GIT repos. For those older projects that still use a centralized code repository system like TFVC, you may not find tons of documentation.
In modern DevOps tools and code storage systems you can either use the GIT protocol or REST APIs to accomplish most of the build or deployment tasks through an automated task scheduler. That allows you to run specific tasks through scripts or web requests. TFS or Azure DevOps supports GIT as well as REST APIs. However, if you are using TFVC (Team foundation version control) you have a smaller set of options beside the precanned and marketplace CICD tasks.
One option would be to use REST APIs: TFVC REST API the problem with this endpoint is that it mostly offers APIs for reading data. You cannot do things like branch merges, check-ins …etc.
TFVC REST API covers read operations for the following:
- Branches
- Changesets
- Items
- Labels
- Shelvesets
The alternative is to use the Team foundation command line TF.exe. This tool comes in handy, if you want to get things done with your TFVC source code that require changes or updates. Things like delete, rename, shelve, merges and check-in\check-out.
For more information about TF.EXE check out Team Foundation commands
The problem
Using TF.exe is fun and can get things done easily with the available rich set of commands. It also works with the most recent version of Azure DevOps (cloud & server).
For example, if you want to create a new workspace you run the following command:
>tf.exe workspace /new /collection:https://afaragtfvc.visualstudio.com/
As you see above, because we are accessing a private project on Azure DevOps we are required to login. This will show an interactive screen that will let you login with your credentials. After successful login I can continue the execution of the script and get the workspace created.
What if this will run on an agent in build task unattended? well, you will need to make sure you don’t get prompt for credentials but rather enter them part of the command line.
The good news is, there is a switch for that. You can use the ‘/noprompt’ switch to prevent interactive prompts. However, if your current user running the command line doesn’t have an active auth session with Azure DevOps you will be access denied:
We also have a switch to login without a prompt using the command line:
/login:username,password: If you want to run the command as another user, you must specify the /login option verbatim, replace username with the name of the user, and if necessary, you can supply the password.
The problem with this switch is that Azure DevOps no longer supports basic login with a user name and password.
Azure DevOps no longer supports Alternate Credentials authentication since the beginning of March 2, 2020. If you’re still using Alternate Credentials, then they won’t work anymore. You have to switch to a more secure authentication method, to mitigate this breaking change impacting your DevOps workflows.
Tell me more …
The solution
There must be a way to authenticate that command line with the modern OAuth (JWT) tokens. With some close inspection to the built-in Azure DevOps tasks for TFVC pipelines I can see clearly that Azure DevOps uses TF.exe to get the latest code …etc.
You will notice that there is an undocumented switch being used called ‘/loginType:OAuth’ !! That’s exactly what we need.
Now, the last hurdle, how do we get an OAuth token? And make sure it hasn’t expired before we use it? How do we renew it?
Luckily that is something handled very well in Azure DevOps. ADO itself uses your credentials\PAT to issue an OAuth token when needed and stores it as an environment variable that you can consume in your script.
The variable is called $(System.AccessToken). To be able to use it you have to do an extra step to enable it in your pipeline. Edit your pipeline and click on the agent job node where the command line task will be running under then select ”Allow scripts to access the OAuth token”
Now you are ready to use that env variable in your script as follows:
>tf.exe workspaces /format:xml /collection:https://afaragtfvc.visualstudio.com/ /loginType:OAuth /login:.,$(System.AccessToken) /noprompt
That should work flawlessly in Azure DevOps
You can also get the raw JWT and insert it in the command if you like to test locally on your machine, but I wouldn’t recommend using that static token on Azure DevOps pipeline as it will expire and break your pipeline at some point.
tf.exe workspaces /format:xml /noprompt /collection:https://collectionName.visualstudio.com/ /loginType:OAuth /login:.,eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im9PdmN6NU1fN3AtSGpJS2xGWHo5M3VfVjBabyJ9.eyJuYW1laWQiOiI1NmE5ZDJhYi1iMjk1LTQwNjgtYjA2Yy1jNGRiMGIxNmE1YjQiLCJzY3AiOiJhcHBfdG9rZW4iLCJhdWkiOiIyZjgyYTNiZS04MmE4LTQxMzgtYTA5eS1iNDU2MDBiZjY2ZGMiLCJzaWQiOiI2MDtmNTRhNS1hNjlmLTRhYjUtYWE2Zi1mZTZiNjJhMGJiZDYiLCJCdWlsZElkIjoiMTllNrdkMDctZDIwYS00ZWJiLTk5NzYtMjMyYhIwMTkxNTc1OzciLCJvcmNoaWQiOiJmMjA3MTQxNC04MDQwLTRlZTItOGMxMy01MTg4Yjc4MmZiZTMuam9iXzEuX19kZWZhdWx0IiwicmVwb0lkcyI6IiIsImlzcyI6ImFwcC52c3Rva2VuLnZpc3VhbHN0dWRpby5jb20iLCJhdWQiOiJhcHAudnN0b2tlbi52aXN1YWxzdHVkaW8uY29tfHZzbzphZTEyNDFiYi0xMzQ3LTRiNzUtODg1YS05ZmNiMzczMwYxMjYiLCJuYmYiOjE1ODk1MDYyMzcsImV4cCI6MTU4OTUxMTAzN30.GcoNwj1Kco69N8r78EN2CMBnzhrZRX5Ye2ihTAw0mVIQtyRl20y1vxgb-kzgkEeVGXaYO3Waz7meBxJftlKLtE8hZX6KyNlbgrEh0y8iJmbg1Ry2xlLV-LSSu9Ij_LW3vnJtWbDKeGeca_Vx_78kR2t0VWABiq-ldaDfauOWp2hGEmJ7wVxkh6IxDp0GbxRMNdExeqeLlFrU5Mr1aK1dJWCSWulLba8zDCUr0_gZlXVpcWmeO-KkWWUUHGWtd2gI2RFIe7Nl47aWf6pv9M3mUyP_aAloCaA7t0DV34KjqlGxPsxn6uGxrDtOXFE7XQZQp9Y7vkbgC-uTcgv99I7H1Q