Set Octopus release notes from TFS query

Octopus.Script exported 02/16/2023 by harrisonmeister belongs to 'Octopus' category.

Sets Octopus release notes from TFS query

Parameters

When steps based on the template are included in a project's deployment process, the parameters below can be set.

TFS Instance

tfsInstance

Collection

tfsCollection

Project

tfsProject

Personal access token

tfsPat

Personal access token to retrieve from TFS

Octopus API key

octopusAPIKey

Script body

Steps based on this template will execute the following PowerShell script.
Show script
#TFS
$instance = $OctopusParameters["tfsInstance"]
$collection = $OctopusParameters["tfsCollection"]
$project = $OctopusParameters["tfsProject"]
$PAT = $OctopusParameters["tfsPat"]
$pathquery = $OctopusParameters["tfsPathQuery"]

#Octopus
$octopusAPIKey = $OctopusParameters['octopusAPIKey']
$baseUri = $OctopusParameters['#{if Octopus.Web.ServerUri}Octopus.Web.ServerUri#{else}Octopus.Web.BaseUrl#{/if}']
$octopusProjectId = $OctopusParameters['Octopus.Project.Id']
$thisReleaseNumber = $OctopusParameters['Octopus.Release.Number']

write-host "Instance: $($instance)"
write-host "collection: $($collection)"
write-host "project: $($project)"
write-host "baseUri: $($baseUri)"
write-host "projectId: $($projectId)"
write-host "thisReleaseNumber: $($thisReleaseNumber)"
write-host "TFS path: $($pathquery)"

#Create HEADERS
$bytes = [System.Text.Encoding]::ASCII.GetBytes($PAT)
$base64 = [System.Convert]::ToBase64String($bytes)
$basicAuthValue = "Basic $base64"
$headers = @{ }
$headers.Add("Authorization", $basicAuthValue)
$headers.Add("Accept","application/json")
$headers.Add("Content-Type","application/json")

$reqheaders = @{"X-Octopus-ApiKey" = $octopusAPIKey }
$putReqHeaders = @{"X-HTTP-Method-Override" = "PUT"; "X-Octopus-ApiKey" = $octopusAPIKey }

function Test-SpacesApi {
	Write-Verbose "Checking API compatibility";
	$rootDocument = Invoke-WebRequest "$baseUri/api" -Headers $reqheaders -UseBasicParsing | ConvertFrom-Json;
    if($rootDocument.Links -ne $null -and $rootDocument.Links.Spaces -ne $null) {
    	Write-Verbose "Spaces API found"
    	return $true;
    }
    Write-Verbose "Pre-spaces API found"
    return $false;
}

if(Test-SpacesApi) {
	$spaceId = $OctopusParameters['Octopus.Space.Id'];
    if([string]::IsNullOrWhiteSpace($spaceId)) {
        throw "This step needs to be run in a context that provides a value for the 'Octopus.Space.Id' system variable. In this case, we received a blank value, which isn't expected - please reach out to our support team at https://help.octopus.com if you encounter this error.";
    }
	$baseApiUrl = "/api/$spaceId" ;
} else {
	$baseApiUrl = "/api" ;
}

# Get the current release
$releaseUri = "$baseUri$baseApiUrl/projects/$octopusProjectId/releases/$thisReleaseNumber"
write-host "Release uri $($releaseUri)"
try {
    $currentRelease = Invoke-RestMethod $releaseUri -Headers $reqheaders -UseBasicParsing 
} catch {
    if ($_.Exception.Response.StatusCode.Value__ -ne 404) {
        $result = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.Io.StreamReader($result);
        $responseBody = $reader.ReadToEnd();
        throw "Error occurred: $responseBody"
    }
}

if(![string]::IsNullOrWhiteSpace($currentRelease.ReleaseNotes)){
	write-host "Release notes already filled in. $($currentRelease.ReleaseNotes)"
    Set-OctopusVariable -name "ReleaseNotes" -value $releaseNotes
	exit;
}


#Get projectid
$url = "http://$($instance)/tfs/$($collection)/$($projectId)/_apis/projects/$($project)?&includeCapabilities=false&includeHistory=false&api-version=2.2"
write-host "Invoking url= $($url)"
$projectresponse = Invoke-RestMethod $url -Method GET -Headers $headers

$projectid = $projectresponse.id
write-host "projectid $($projectid)"

#Get the ID of the query to execute
$queryResult = Invoke-RestMethod "http://$($instance)/tfs/$($collection)/$($projectId)/_apis/$($pathquery)?$depth=1&api-version=2.2" -Method GET -Headers $headers
write-host "queryResult $($queryResult)"

#https://{instance}/DefaultCollection/[{project}/]_apis/wit/wiql/{id}?api-version={version}
$queryResult = Invoke-RestMethod "http://$($instance)/tfs/$($collection)/$($projectId)/_apis/wit/wiql/$($queryResult.Id)?api-version=2.2" -Method GET -Headers $headers

Write-Host "Found $($queryResult.workItems.Count) number of workitems for query: ReleaseNotes$($releaseTag)"

$releaseNotes = "**Work Items:**"


if($queryResult.workItems.Count -eq 0)
{
	Write-Host "No work items for release"
	$releaseNotes = "`n no new work items"
}
else
{
	#Create a list of ids
	$ids = [string]::Join("%2C", ($queryResult.workItems.id))

	#Get all the work items
	$workItems = Invoke-RestMethod  "http://$($instance)/tfs/$($collection)/_apis/wit/workItems?ids=$($ids)&fields=System.Title" -Method GET -Headers $headers

	foreach($workItem in $workItems.value)
	{
		#Add line for each work item
		$releaseNotes = $releaseNotes + "`n* [$($workItem.id)] (http://$($instance)/tfs/$($collection)/9981e67f-b27c-4628-b5cf-fba1d327aa07/_workitems/edit/$($workItem.id)) : $($workItem.fields.'System.Title')"
	}

}



# Update the release notes for the current release
$currentRelease.ReleaseNotes = $releaseNotes 
write-host "Release notes $($currentRelease.ReleaseNotes)"
Write-Host "Updating release notes for $thisReleaseNumber`:`n`n"
try {
    $releaseUri = "$baseUri$baseApiUrl/releases/$($currentRelease.Id)"
    write-host "Release uri $($releaseUri)"
    $currentReleaseBody = $currentRelease | ConvertTo-Json
    write-host "Current release body $($currentReleaseBody)"
    $result = Invoke-RestMethod $releaseUri -Method Post -Headers $putReqHeaders -Body $currentReleaseBody -UseBasicParsing
	write-host "result $($result)"
} catch {
    $result = $_.Exception.Response.GetResponseStream()
    $reader = New-Object System.Io.StreamReader($result);
    $responseBody = $reader.ReadToEnd();
    Write-Host "error $($responseBody)"
    throw "Error occurred: $responseBody"
}

Set-OctopusVariable -name "ReleaseNotes" -value $releaseNotes

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

Show JSON
{
  "Id": "c6faf6be-296c-44ee-abf6-ce87331b2557",
  "Name": "Set Octopus release notes from TFS query",
  "Description": "Sets Octopus release notes from TFS query",
  "Version": 6,
  "ExportedAt": "2023-02-16T15:38:44.043Z",
  "ActionType": "Octopus.Script",
  "Author": "harrisonmeister",
  "Parameters": [
    {
      "Id": "08ce7e1a-7251-4b9f-bd07-f63cbfbc7f20",
      "Name": "tfsInstance",
      "Label": "TFS Instance",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "85eff544-5275-420f-81d8-cf01ea42d903",
      "Name": "tfsCollection",
      "Label": "Collection",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "a423bf6c-52be-4994-8bab-2d64f05f167f",
      "Name": "tfsProject",
      "Label": "Project",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "1db49bf8-d38a-4ae7-b858-693c584272d5",
      "Name": "tfsPat",
      "Label": "Personal access token",
      "HelpText": "Personal access token to retrieve from TFS",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "0427c73a-2925-4c55-a2ce-5584cd24b1dd",
      "Name": "octopusAPIKey",
      "Label": "Octopus API key",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "#TFS\n$instance = $OctopusParameters[\"tfsInstance\"]\n$collection = $OctopusParameters[\"tfsCollection\"]\n$project = $OctopusParameters[\"tfsProject\"]\n$PAT = $OctopusParameters[\"tfsPat\"]\n$pathquery = $OctopusParameters[\"tfsPathQuery\"]\n\n#Octopus\n$octopusAPIKey = $OctopusParameters['octopusAPIKey']\n$baseUri = $OctopusParameters['#{if Octopus.Web.ServerUri}Octopus.Web.ServerUri#{else}Octopus.Web.BaseUrl#{/if}']\n$octopusProjectId = $OctopusParameters['Octopus.Project.Id']\n$thisReleaseNumber = $OctopusParameters['Octopus.Release.Number']\n\nwrite-host \"Instance: $($instance)\"\nwrite-host \"collection: $($collection)\"\nwrite-host \"project: $($project)\"\nwrite-host \"baseUri: $($baseUri)\"\nwrite-host \"projectId: $($projectId)\"\nwrite-host \"thisReleaseNumber: $($thisReleaseNumber)\"\nwrite-host \"TFS path: $($pathquery)\"\n\n#Create HEADERS\n$bytes = [System.Text.Encoding]::ASCII.GetBytes($PAT)\n$base64 = [System.Convert]::ToBase64String($bytes)\n$basicAuthValue = \"Basic $base64\"\n$headers = @{ }\n$headers.Add(\"Authorization\", $basicAuthValue)\n$headers.Add(\"Accept\",\"application/json\")\n$headers.Add(\"Content-Type\",\"application/json\")\n\n$reqheaders = @{\"X-Octopus-ApiKey\" = $octopusAPIKey }\n$putReqHeaders = @{\"X-HTTP-Method-Override\" = \"PUT\"; \"X-Octopus-ApiKey\" = $octopusAPIKey }\n\nfunction Test-SpacesApi {\n\tWrite-Verbose \"Checking API compatibility\";\n\t$rootDocument = Invoke-WebRequest \"$baseUri/api\" -Headers $reqheaders -UseBasicParsing | ConvertFrom-Json;\n    if($rootDocument.Links -ne $null -and $rootDocument.Links.Spaces -ne $null) {\n    \tWrite-Verbose \"Spaces API found\"\n    \treturn $true;\n    }\n    Write-Verbose \"Pre-spaces API found\"\n    return $false;\n}\n\nif(Test-SpacesApi) {\n\t$spaceId = $OctopusParameters['Octopus.Space.Id'];\n    if([string]::IsNullOrWhiteSpace($spaceId)) {\n        throw \"This step needs to be run in a context that provides a value for the 'Octopus.Space.Id' system variable. In this case, we received a blank value, which isn't expected - please reach out to our support team at https://help.octopus.com if you encounter this error.\";\n    }\n\t$baseApiUrl = \"/api/$spaceId\" ;\n} else {\n\t$baseApiUrl = \"/api\" ;\n}\n\n# Get the current release\n$releaseUri = \"$baseUri$baseApiUrl/projects/$octopusProjectId/releases/$thisReleaseNumber\"\nwrite-host \"Release uri $($releaseUri)\"\ntry {\n    $currentRelease = Invoke-RestMethod $releaseUri -Headers $reqheaders -UseBasicParsing \n} catch {\n    if ($_.Exception.Response.StatusCode.Value__ -ne 404) {\n        $result = $_.Exception.Response.GetResponseStream()\n        $reader = New-Object System.Io.StreamReader($result);\n        $responseBody = $reader.ReadToEnd();\n        throw \"Error occurred: $responseBody\"\n    }\n}\n\nif(![string]::IsNullOrWhiteSpace($currentRelease.ReleaseNotes)){\n\twrite-host \"Release notes already filled in. $($currentRelease.ReleaseNotes)\"\n    Set-OctopusVariable -name \"ReleaseNotes\" -value $releaseNotes\n\texit;\n}\n\n\n#Get projectid\n$url = \"http://$($instance)/tfs/$($collection)/$($projectId)/_apis/projects/$($project)?&includeCapabilities=false&includeHistory=false&api-version=2.2\"\nwrite-host \"Invoking url= $($url)\"\n$projectresponse = Invoke-RestMethod $url -Method GET -Headers $headers\n\n$projectid = $projectresponse.id\nwrite-host \"projectid $($projectid)\"\n\n#Get the ID of the query to execute\n$queryResult = Invoke-RestMethod \"http://$($instance)/tfs/$($collection)/$($projectId)/_apis/$($pathquery)?$depth=1&api-version=2.2\" -Method GET -Headers $headers\nwrite-host \"queryResult $($queryResult)\"\n\n#https://{instance}/DefaultCollection/[{project}/]_apis/wit/wiql/{id}?api-version={version}\n$queryResult = Invoke-RestMethod \"http://$($instance)/tfs/$($collection)/$($projectId)/_apis/wit/wiql/$($queryResult.Id)?api-version=2.2\" -Method GET -Headers $headers\n\nWrite-Host \"Found $($queryResult.workItems.Count) number of workitems for query: ReleaseNotes$($releaseTag)\"\n\n$releaseNotes = \"**Work Items:**\"\n\n\nif($queryResult.workItems.Count -eq 0)\n{\n\tWrite-Host \"No work items for release\"\n\t$releaseNotes = \"`n no new work items\"\n}\nelse\n{\n\t#Create a list of ids\n\t$ids = [string]::Join(\"%2C\", ($queryResult.workItems.id))\n\n\t#Get all the work items\n\t$workItems = Invoke-RestMethod  \"http://$($instance)/tfs/$($collection)/_apis/wit/workItems?ids=$($ids)&fields=System.Title\" -Method GET -Headers $headers\n\n\tforeach($workItem in $workItems.value)\n\t{\n\t\t#Add line for each work item\n\t\t$releaseNotes = $releaseNotes + \"`n* [$($workItem.id)] (http://$($instance)/tfs/$($collection)/9981e67f-b27c-4628-b5cf-fba1d327aa07/_workitems/edit/$($workItem.id)) : $($workItem.fields.'System.Title')\"\n\t}\n\n}\n\n\n\n# Update the release notes for the current release\n$currentRelease.ReleaseNotes = $releaseNotes \nwrite-host \"Release notes $($currentRelease.ReleaseNotes)\"\nWrite-Host \"Updating release notes for $thisReleaseNumber`:`n`n\"\ntry {\n    $releaseUri = \"$baseUri$baseApiUrl/releases/$($currentRelease.Id)\"\n    write-host \"Release uri $($releaseUri)\"\n    $currentReleaseBody = $currentRelease | ConvertTo-Json\n    write-host \"Current release body $($currentReleaseBody)\"\n    $result = Invoke-RestMethod $releaseUri -Method Post -Headers $putReqHeaders -Body $currentReleaseBody -UseBasicParsing\n\twrite-host \"result $($result)\"\n} catch {\n    $result = $_.Exception.Response.GetResponseStream()\n    $reader = New-Object System.Io.StreamReader($result);\n    $responseBody = $reader.ReadToEnd();\n    Write-Host \"error $($responseBody)\"\n    throw \"Error occurred: $responseBody\"\n}\n\nSet-OctopusVariable -name \"ReleaseNotes\" -value $releaseNotes\n"
  },
  "Category": "Octopus",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates/octopus-set-Octopus-releaese-notes-from-TFS-query.json",
  "Website": "/step-templates/c6faf6be-296c-44ee-abf6-ce87331b2557",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFT6Tl////L5Pg8vj9Y67omsvwPJrisdfzfbzs5fL7y+T32Ov5isLucLXqvt31CJPHWwAABMJJREFUeNrs3deW4jAMAFDF3U75/89dlp0ZhiU4blJEjvQ8hYubLJsA00UCBCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIhD8kJm+t+QprfdKfB9HbYpx6CWfspj8HMi+gMgHL/AmQA8W3JTKH+ALFvzCeL0RbpyoCPE9IJeNOSQwh5Z3qd6yRGWQ2qi2cZQWxqj1WzQYSjeoJmJlAklOd4VlArOqPhQEkqBERToeMcfRJBkC0Uep8CfBpjz4JsHJ0zF3dkEWNje0kiB/sUC6eApndaIiCMyAa1PiwJ0AWhRGJHJJQHG2dC7h1rNbO1QOxSA7lNCkkKrQIpJCAB1GREILYIC1NAiwbpKFJgGWDNExcwGstfExcZBCHC6nOglshHtmhViLIig1RNBCN7qjtW8C0Z1UvJcC1Z9XmwMBzzvobmgAyEzgq91dtEEsBsQSQQAFZCSBAATEEEApHZbrVBIkkEIUPSVeB+KtALA0kXQUSrwKZBCIQBnk8Y4i5CsReBeKvkqLM+BCSDWJlrZFvGk9SRTHshkgjZCGAaArIxm3H3grhVzFlW2msfl1ca79UJ1bofYvsDHHlNdTZnlh5MghuPd5NdBDUNZHyCkfktIh03XzALGRPlBDPac7qgWjHZzWcmF5zmmkhidMQ6boKiDXcDTUEaylZqCGJ0Vjvu/fLJtHqhSANEvqb2OYqkOUqEHuVMbJcZdZCGiPhKhC4yjqiIjEE7XThMp8fAWII3mY3kUIQD+AMKQTzPiBhgQ63HlT/KSvgtoi0dq5mCPah1UIE0eh3sT0NhOByvKeAkFzi8PgQomumFhsyOxpIzZN4gLOj5plVwNpR0b2AuePWKBEHQu24pSsJA+LVCeHHQxZ1SiyDIdqok8IOhSSnTottHEQTdyt4ettAj4KkzA4dMikk2Dht2S5ptm1vswnPDxn0YyDZ5oDM3iToo2T5voWaYe+Q+vdjH80QyAzZhCgcDtLMI1Tmtz9w++XHgziHQHJJu/OZ3bs9Xn8gQ72NcP3dKqEfkp10F51xhoIi2I91R+LurXV/5q7pH+wx061CzO16oSQleMyr8fXvwMA0Pro8432DPD/ySx8XrHfSuDAM8n6UhnjQabaiXf5Bq/lREHvEeNtn1rJ08+C/uXkQZHeguxAPC3UvtcJYUogLzZX5hhZZvS6onG5lxXtzWGaygwb79vT/IXhdlNibwlKYOR6T8xjI7W8n+xV7T+GH4tMzWwR+lZhRkJYSsC0thpmCYqyngOz3rN2FLBZ2wZflBCggUHF0Vnp88JKienzIXLSEZCZqU7IKr/gQW9yx3pzV7Y9kvWZWTRRIqDmTtRUnU7b2lLcTYmoqHqnmiO1poER0SPkAeZMAZxaJx0Y3TCdAclsIqDz03ALcyxfTCZBsthoGXWmigGyVhWPLFJJfuuKQWycoEFdXbH4dJJoJxNR1eD/kshz6yn48cF8yW8sFoitflB1w6Q8n+/15Za7oA17/pYNmYgP5fmWm8L1NOHPWgK8kuFew1/JXtOA0yJCv7ah7X8ObUuT5kObU30+fDZm8+zqP+HTIpK0xQ796b5Kv2hSIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIpBf8UeAAQAEjtYmlDTcCgAAAABJRU5ErkJggg==",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History »