Queueing TFS builds via PowerShell - powershell

TFS2012 with a one 2010 build controller with one 2010 build agent. Also have one 2012 build controller with multiple 2012 build agents.
We have multiple builds for multiple versions of our software. The builds are named according to a convention e.g. Foo_version_1_0 and Foo_version_2_0.
When I run this code on my local machine, all the builds are queued. When I run this code manually on a 2012 build agent, the builds are queued. When I run this code manually on a 2010 build agent, no builds are queued. When the code is executed as part of a triggered build in TFS (either on the 2010 or 2012 controller/agent), it doesn't queue any builds and errors out with my custom exception saying no definitions returned from TFS.
My questions:
Is the $buildServer.QueryBuildDefinitions() function an administrator function only? I.e. if a non-admin user account (like TFSService) runs it, it won't be able to get the data from the TFS api?
Is the $buildServer.QueryBuildDefinitions() a new function that is only available in 2012?
Is there another way of doing this that will work? Previously, we had all our build names hard coded - this is not a viable way forward for us.
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Build.Client")
$serverName="http://tfs:8080/tfs"
$tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)
$buildserver = $tfs.GetService([Microsoft.TeamFoundation.Build.Client.IBuildServer])
$buildServer.QueryBuildDefinitions("FooProject") | foreach {
if ($_.EndsWith("version_1_0"))
{
echo "Queueing build: $_.Name"
$buildServer.QueueBuild($buildServer.GetBuildDefinition("FooProject",$_.Name))
}
}
}
Edit: removed $buildDefinitions = $buildServer.QueryBuildDefinitions("FooProject").Name, replaced it with $buildServer.QueryBuildDefinitions("FooProject") | foreach...
Builds are now queued programmatically.

The API hasn't changed, and I suppose that both agents are using the same account.
The line
$buildDefinitions = $buildServer.QueryBuildDefinitions("FooProject").Name
seems wrong: the Name property get will raise an exception for an empty result.

You can also utilize one of the built in APIs, to prevent downloading dll binaries.
The following will work for
TFS 2017:
https://github.com/sameer-kumar/adhoc-posh/blob/master/QueueTfsBuild.ps1
$rootTfsUri = "http://myTFS:8080/tfs"
$collectionName = "Default"
$projectName = "Project1"
$tfsUri = $rootTfsUri + "/" + $collectionName + "/" + $projectName
$buildDefinition = "DevCI-vnext"
$buildDefinitionUri = "$tfsUri/_apis/build/definitions?api-version=3.1&name=$buildDefinition"
# first get build definition id
$buildResponse = Invoke-WebRequest -Uri $buildDefinitionUri -UseDefaultCredentials -Method Get -Verbose -UseBasicParsing -ContentType "application/json"
$buildResponseAsJson = $buildResponse.Content | convertfrom-json
$buildDefinitionId = $buildResponseAsJson.value.id
# Now queue this build definition
$requestContentString = #"
{
"definition": {
"id" : "$buildDefinitionId"
}
}
"#
$buildUri = "$tfsUri/_apis/build/builds?api-version=3.1"
$buildResponse = Invoke-WebRequest -Uri $buildUri -UseDefaultCredentials -Method Post -Verbose -UseBasicParsing -ContentType "application/json" -Body $requestContentString
$buildNumber = ($buildResponse.Content | ConvertFrom-Json).buildNumber
TFS 2015
Utilizes a slightly different structure where uri definition replace with this,
$buildDefinitionUri = "$tfsUri/_apis/Build/builds?api-version=2.0&name=$buildDefinition"

Related

Update Azure DevOps artifact properties using API call

Does anyone know here how to update an Artifact's properties? We've had a project assigned to us to move all of our artifact drops from one location to another - This is because our file server is about to die (We use On-Prem Azure DevOps Services)
I've wrote a PowerShell script already that updates what's needed for builds/releases and that works great.
So I wrote another PowerShell script to update what's needed within the Artifacts properties and then uses an "PUT" call to replace the info but there's an issue....
Invoke-RestMethod : {"count":1,"value":{"Message":"The requested resource does not support http method 'PUT'."}}
Does anyone know if this is possible/knows how to do it?
Here's my script;
Example of an endpoint;
https://dev.azure.com/{organization}/{project}/_apis/build/builds/{buildId}/artifacts?api-version=5.1
$artefacts = Get-Content ".\artefacts.txt"
$oldDataURL = "\\rdtstore01.main.rdt.co.uk\Horizon\Drops"
$newDataURL = "\\main.rdt.co.uk\shares\Horizon\Drops"
foreach ($line in $artefacts)
{
$var = Invoke-RestMethod -Uri $line -UseDefaultCredentials -Method Get
### Updte Data line ###
$data = $var.value.resource.data
$dataNew = $data -replace [RegEx]::Escape($oldDataURL), $newDataURL
$var.value.resource.data = #{}
$var.value.resource.data += #{"value" = $dataNew}
$result = Invoke-RestMethod -Uri $line -Method Put -ContentType "application/json" -UseDefaultCredentials -Body (ConvertTo-Json $var -Depth 10)
}
Use Rest API to update Artifacts properties is not support right now. We only provide below methods for working with artifacts produced by builds.
Sorry, there is not any workaround, you may have to use build task to publish artifacts again.

How can one automate promotion of an artifact to a feed view in Azure DevOps?

Our build artifact is an Octopus nuget package. When the build is released it lands into the QA stage where the artifact is deployed through Octopus. This octopus consumes it directly from the Azure Artifacts nuget feed.
If the deployment and the subsequent tests are successful we want to promote the artifact to the Release view of the Azure Artifacts nuget feed, because we think it gives us a different nuget URL that can be used by another Octopus serving the next stage (for historic reasons we have dedicated Octopus per stage - working to change that, but it takes time).
We can promote manually, but we want to do it automatically. How can this be done?
We are testing it on on-premises TFS 2019 RC2.
EDIT 1
The suggested plugin does not seem to install on on-premises TFS 2019 RC2:
Using PowerShell...
$organisationName = '' # Name of organisation
$projectName = '' # Name of project
$feedName = '' # Name of Azure Artifacts feed
$viewName = 'Release' # I believe this can also be Prerelease, but I've not tried it
# List of names of packages within Azure Artifacts feed to be promoted
$packagesToPromote = #('')
# Need a personal access token for this script to work
# PAT token should be assigned to Packaging (Read, Write and Manage) scopes
$azureArtifactsPAT = ''
$AzureArtifactsPAT_Base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($azureArtifactsPAT)"))
$restAPICallHeader = #{ Authorization = "Basic $AzureArtifactsPAT_Base64" }
$feedBaseURL = "https://feeds.dev.azure.com/$organisationName/$projectName/_apis/packaging/feeds"
$packageBaseURL = "https://pkgs.dev.azure.com/$organisationName/$projectName/_apis/packaging/feeds"
$feedIdURL = "$feedBaseURL/$feedName/?api-version=5.1-preview.1"
$feedIdResponse = (Invoke-RestMethod -Method Get -Uri $feedIdUrl -Headers $restAPICallHeader -ContentType 'application/json')
$feedId = $feedIdResponse.id
$viewIdURL = "$feedBaseURL/$feedId/views/$viewName/?api-version=5.1-preview.1"
$viewIdResponse = (Invoke-RestMethod -Method Get -Uri $viewIdUrl -Headers $restAPICallHeader -ContentType 'application/json')
$viewId = $viewIdResponse.id
$restAPICallBodyJson = #{
views = #{
op = 'add'
path = '/views/-'
value = "$viewId"
}
}
$restAPICallBody = (ConvertTo-Json $restAPICallBodyJson)
foreach ($packageName in $packagesToPromote) {
$packageQueryUrl = "$feedBaseURL/$feedId/packages?api-version=5.1-preview.1&packageNameQuery=$packageName"
$packagesResponse = (Invoke-RestMethod -Method Get -Uri $packageQueryUrl -Headers $restAPICallHeader -ContentType 'application/json')
$latestPackageVersion = ($packagesResponse.value.versions | ? { $_.isLatest -eq $True } | Select -ExpandProperty version)
$encodedPackageVersion = [System.Web.HttpUtility]::UrlEncode($latestPackageVersion)
Write-Host "Package Name: $packageName"
Write-Host "Package Version: $latestPackageVersion"
$releaseViewURL = $packageBaseURL `
+ "/$($feedId)" `
+ "/nuget/packages/$packageName" `
+ "/versions/$encodedPackageVersion" `
+ "?api-version=5.1-preview.1"
$response = Invoke-RestMethod -Method Patch -Uri $releaseViewURL -Headers $restAPICallHeader -ContentType 'application/json' -Body $restAPICallBody
Write-Host $response
}
For reference, the script above uses the following API calls:
Feed Management - Get Feed
https://learn.microsoft.com/en-us/rest/api/azure/devops/artifacts/feed%20%20management/get%20feed?view=azure-devops-rest-5.1
Feed Management - Get Feed View
https://learn.microsoft.com/en-us/rest/api/azure/devops/artifacts/feed%20%20management/get%20feed%20view?view=azure-devops-rest-5.1
Artifact Details - Get Packages
https://learn.microsoft.com/en-us/rest/api/azure/devops/artifacts/artifact%20%20details/get%20packages?view=azure-devops-rest-5.1
NuGet - Update Package Version
https://learn.microsoft.com/en-us/rest/api/azure/devops/artifactspackagetypes/nuget/update%20package%20version?view=azure-devops-rest-5.1
As per Azure DevOps documentation the marketplace task Promote package to Release View is the recommended way to accomplish this from a CI/CD pipeline.
The repository can be found on Github.
Edit:
Since you are on-prem with a version that this task doesn't support. I would say that the comments about using the REST api would be the route you need to go in something like a powershell script.
Having never used the REST Api for this task I'm not exactly sure how the body is supposed to look for the request. However, it seems to be documented here.
My understanding of the JSON Patch object is limited, but I would think you might use the replace operation.
{ "op": "replace", "path": "/view", "value": "#Release" }
This article may also be helpful, but I still don't see anything that would relate to the from identifier on the JsonPatchObject definition in the REST Api documentation.
I also recently struggled with trying to implement version using TFS. I've produced some PowerShell scripts (adapting other scripts out on the web) to do package versioning.
https://gist.github.com/oceanexplorer/6a91930419b35c1923974af265777a5f
https://gist.github.com/oceanexplorer/35e0f26962018dc8578c745060365c15
The first step is my build pipeline I use the "Update AssemblyInfo" task to set the build version which then gets embedded into the DLL's.
https://marketplace.visualstudio.com/items?itemName=sebastianlux.UpdateAssemblyInfo
Initially I embedded the above scripts with my project to get things going but eventually in my release pipeline I then have a task that deploys these build scripts via a "NuGet Install" task which effectively pulls them from a feed and unzips them.
In the release pipeline I then have a task "Version Package" which is a custom PowerShell script that calls functions defined in the two gists above, what these do is to unzip the NuGet packages that have been created from the build pipeline and placed in the artifact directory, applies the correct versioning to the package and zips it back up. I have used the following build number format in my build pipeline:
$(version.major).$(version.minor).$(version.patch).$(Date:yyyyMMdd)$(Rev:r)-CI
1.0.0.201902051-CI
This will produce a semantic build number format of:
1.0.0-alpha.201902051
I call the scripts using an inline PowerShell task
##-------------------------------------------
## Import Build Scripts
##-------------------------------------------
gci -Recurse "$(System.DefaultWorkingDirectory)\scripts\*.psm1" | ForEach-Object { Import-Module $_.FullName }
##-------------------------------------------
## Version Files
##-------------------------------------------
Expand-NugetPackages -packagesDirectory "$(artifact.directory)" -Verbose
Add-VersionToAssemblies -suffix "$(Release.EnvironmentName)" -semVer "2.0" -artifactsToApplyTo "nuspec" -isRelease $(isRelease) -Verbose
Compress-NugetPackages -packagesDirectory "$(artifact.directory)" -Verbose
Then a NuGet push task to push the package
Them another inline PowerShell script which sets the release view for the package feed:
##-------------------------------------------
## Import Build Scripts
##-------------------------------------------
gci -Recurse "$(System.DefaultWorkingDirectory)\scripts\*.psm1" | ForEach-Object { Import-Module $_.FullName }
##-------------------------------------------
## Set Package Quality
##-------------------------------------------
Set-PackageQuality -feedName "Libraries" -packageId $(nuget.packageId) -packageVersion $env:semanticVersion -packageQuality $(Release.EnvironmentName)
Something changed recently in the ADO RestApi.
What worked for me was sending a PATCH request like:
curl --location --request PATCH 'https://pkgs.dev.azure.com/YOUR_ORGANIZATION/YOUR_PROJECT/_apis/packaging/feeds/YOUR_FEEDNAME/upack/packages/YOUR_PACKAGENAME/versions/YOUR_PACKAGEVERSION?api-version=5.1-preview.1' \
--header 'Authorization: Basic YOUR_TOKEN' \
--header 'Content-Type: application/json' \
--data-raw '{
"views": {
"op": "add",
"path": "/views/-",
"value": "Release"
}
}'

Azure DevOps Build - Invoke-RestMethod - An item with the same key has already been added error

We have a powershell script as a step in our DevOps build that finds changeset details since the last good build.
$TfsUrl = 'https://' + $RestAPIAccount + '.visualstudio.com/defaultcollection/' + $ProjectName
$BaseUrl = "https://" + $RestAPIAccount + ".visualstudio.com/defaultcollection/_apis/tfvc/changesets"
$ChangeSetHistoryUrl = $TfsUrl + "/_apis/build/builds/$BuildId/changes?api-version=$RestAPIVersion"
$changeSetHistoryDef = (Invoke-RestMethod -Uri $ChangeSetHistoryUrl -Headers $Headers -Method Get).Value
The last statement has starting failing with the following error:
"Invoke-RestMethod : {"$id":"1","innerException":null,"message":"An item with the same key has already been added.","typeName":"System.ArgumentException, mscorlib, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","typeKey":"ArgumentException","errorCode":0,"eventId":0}"
We have been running the same script without any issues for the last year or more.
Any ideas on what could be causing this or how to troubleshoot further.
That's because the build you requested has no changes since the last good build.
Based on my test, if you have 2 build definitions A and B, and they have set the same sources... Edit and modify some files/code then check in the changes.
Trigger build definition A and generate a build#1, this time
it works by calling below REST API:
_apis/build/builds/build#1/changes?api-version=$RestAPIVersion
Then trigger build definition B and generate build#2, even they used the same Source version, but this
time the REST API does not work... and you will get the error message... Because build#2have no changes compare with build#1...
_apis/build/builds/build#2/changes?api-version=$RestAPIVersion (build#2/changes will not work here as no changes....)
Tested with below script: (Allow Scripts to Access OAuth Token)
$url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$env:Build_BuildId/changes"
Write-Host $url
$pipeline = Invoke-RestMethod -Uri $url -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
}
Write-Host "Pipeline:" $pipeline.value | ConvertTo-Json -Depth 100
Our build steps are working this morning, without any changes being made to our process.
Test cases I had used to troubleshoot this issue earlier in the week using particular build id's that were failing are now working also.
Something must have changed by Microsoft which has subsequently been fixed??

TFS: Best way to trigger build on server restart, or on Windows Updates installation

In short, the requirement is to verify that our latest released software can be built and then installed after the latest Windows updates and/or other patches were applied. So the build server VM(s) will be configured just for this purpose and the build only needs to run after an update.
Since such updates usually are followed with a restart, I am thinking of a server restart event triggering a build and deployment. Does such option exist in TFS 2017?
If there is no way to do it through TFS then, I guess, a PowerShell script that runs on startup should work?
No such a build-in function to achieve that. However create a PowerShell script that runs on startup should work. Just as Jessehouwing said, you can create the script with the REST API to trigger builds.
Create a script to trigger the specific build definition. (Reference below sample)
Run the script on startup:
How to run a batch file each time the computer boots
How to schedule a Batch File to run automatically in Windows
10/8/7
Param(
[string]$collectionurl = "http://server:8080/tfs/DefaultCollection",
[string]$projectName = "ProjectName",
[string]$keepForever = "true",
[string]$BuildDefinitionId = "34",
[string]$user = "username",
[string]$token = "password"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
function CreateJsonBody
{
$value = #"
{
"definition": {
"id": $BuildDefinitionId
},
"parameters": "{\"system.debug\":\"true\",\"BuildConfiguration\":\"debug\",\"BuildPlatform\":\"x64\"}"
}
"#
return $value
}
$json = CreateJsonBody
$uri = "$($collectionurl)/$($projectName)/_apis/build/builds?api-version=2.0"
$result = Invoke-RestMethod -Uri $uri -Method Post -Body $json -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
There is no existing trigger that handles this, but there is a simple REST API to query and trigger builds.
It would be easy to create an on startup job in the task scheduler, use the REST API to query a list of Build Definitions based on a certain name or tag and then queue it.
List build definitions
Queue a build

Start vNext build from Powershell and get artifacts

In order to automate our deployments, I would like to rebuild an application, based on a given ChangeSetId. Once this build has completed, I want to get the artifacts of the build (the .exe), so we can deploy them. For the sake of the question I am focussing on the 'Get artifacts from build' part.
For DevOps purposes, I'd like to use PowerShell, since it should be able to access the TFS API libraries and because MS recommends using it.
Environment
I've set up Builds in our On Premise TFS 2015 server (which are working neatly) - and added a VSO task 'Publish artifacts' after this build. So far so good.
The published artifacts is are to be stored on the Server, which basically means I have to download the artifacts connected to build - every existing build will have its artifacts linked - which is better then an UNC drop in my book.
No comes my challenge; how do I programmaticaly access these artifacts, step 3?
Get Sources for ChangeSetId
MSBuild application with given configuration
Get build Artifacts using PowerShell
Deploy to environment using Release Management (Powershell as well)
TFS 2015 comes with the new REST API, and it includes the method to get the artifacts of the specific build. I would approach your challenge in the following way:
Add a "PowerShell script" build step after your "Publish artifacts" step
In that PowerShell script:
Get the ID of the current build. TFS exposes a number of predefined variables, and build ID is there. All those variables end up as environment variable, and this post can help you read the appropriate one from your PowerShell script
Next, make a web request to get build artifacts. As you can see from the API description, you'll have to provide just the build ID
Then, parse the JSON response - the downloadUrl property contains the link to download all the artifacts of the build zipped as a single archive
Finally, extract the archive and pick up those artifacts you need. Perhaps, you'd like to deploy it to your testing environment in this step as well
Hope this helps.
Okay so, like Yan Sklyarenko said, TFS 2015 (and 2013, after some update), has an excellent REST API.
I've created a very, very rough basic PowerShell script that does what i want. I cannot emphasize enough how much this code needs refactoring - i really just needed this to work as a proof of concept, and we will develop multiple scripts for different needs, but for the people that came here for a code example, you'll find it here.
Connect to TFS' Build system
List Build Definition items (for myself, Poc)
Search for some string and get Build ID
Kick off a build, using a hard coded ID 7 (because I knew this was going to work, and therefor my work was done)
Get the Artifacts (in which I incorporated the VSO build task 'Publish Artifacts Server')
Extract said received Artifacts, because TFS zips them.
From there on out i will incorporate these scripts and outputs into MS Release Management services - and be ready to migrate to VSO Release vNext when it ships for on-premise TFS 2015!
$projectId ='{ProjectIdGuid}'
$buildNr = '3945'
$username = 'username'
$password = 'password'
$zipDestination = 'C:\temp\unzip\temp.zip'
$workingFolder = ('C:\temp\unzip\' + [System.DateTime]::Now.ToString("yyyyMMddhhmmss")) #temp because of file already exist warnings... after completion we should delete the working directory content
$tfsURL = 'http://myTFS:8080/tfs/MyCollection/'+ $projectId
$cred = New-Object System.Management.Automation.PSCredential($username, (ConvertTo-SecureString -String $password -AsPlainText -Force))
#write list of build definitions (to be used later)
$allbuildDefs = (Invoke-RestMethod -Uri ($tfsURL + '/_apis/build/definitions?api-version=2.0') -Method GET -Credential $cred).value | Where-Object {$_.name -like '*buildName*'} | Out-Default | select name
Write-Host($allbuildDefs)
$buildDefs = ConvertFrom-Json($allbuildDefs)
$buildId = ($buildDefs.value).id;
#Get build Definition for what you want to build
$buildDefinitionURI = $tfsURL + '/_apis/build/requests?api-version=1.0'
#kick off build
$body = '{ "definition": { "id": '+ 7 + '}, reason: "Manual", priority: "Normal"}'
$BuildReqBodyJson = $body | ConvertTo-Json
$buildOutput = Invoke-RestMethod -Method Post -Uri $buildDefinitionURI -Credential $cred -ContentType 'application/json' -Body $body
#get buildNr
#build URI for buildNr
$BuildURI = $tfsURL + '/_apis/build/builds/' + $buildNr + '/artifacts'
#get artifact downloadPath
$downloadURL = (Invoke-RestMethod -Uri $BuildURI -Credential $cred).Value.Resource.downloadUrl
#download ZIP
Invoke-WebRequest -uri $downloadURL -Credential $cred -OutFile $zipDestination
#unzip
Add-Type -assembly 'system.io.compression.filesystem'
[io.compression.zipfile]::ExtractToDirectory($zipDestination, $workingFolder)