├── extensions ├── generic │ ├── tasks.json │ ├── icon-large.png │ ├── icon-default.png │ ├── vss-extension.public.json │ ├── vss-extension.json │ └── overview.md ├── apple-xcode │ ├── fix.ps1 │ ├── icon-default.png │ ├── icon-large.png │ ├── tasks.json │ ├── vss-extension.json │ ├── vss-extension.public.json │ └── overview.md ├── dotnetcore │ ├── tasks.json │ ├── icon-large.png │ ├── icon-default.png │ ├── vss-extension.json │ ├── vss-extension.public.json │ └── overview.md ├── appcenter │ ├── icon-large.png │ ├── icon-default.png │ ├── tasks.json │ ├── vss-extension.json │ ├── vss-extension.public.json │ ├── fix.ps1 │ └── overview.md ├── visualstudio │ ├── icon-large.png │ ├── icon-default.png │ ├── tasks.json │ ├── vss-extension.public.json │ ├── fix.ps1 │ ├── vss-extension.json │ └── overview.md ├── pre-post-tasks │ ├── icon-large.png │ ├── tasks.json │ ├── icon-default.png │ ├── vss-extension.public.json │ ├── vss-extension.json │ └── overview.md ├── nuget-deprecated │ ├── icon-default.png │ ├── icon-large.png │ ├── vss-extension.public.json │ ├── vss-extension.json │ ├── tasks.json │ ├── overview.md │ └── fix.ps1 ├── vss-extension.debug.json └── vss-extension.json ├── .github ├── CODEOWNERS ├── renovate.json ├── dependabot.yml ├── FUNDING.yml ├── workflows │ ├── publish-tasks.yml │ ├── powershell.yml │ └── codeql.yml └── copilot-instructions.md ├── .gitignore ├── LICENSE ├── upload-releases.ps1 ├── PRIVACY.md ├── releasenote.template.md ├── publish-extensions.ps1 ├── scripts └── install-task.ps1 ├── generate-sxs.ps1 ├── download.ps1 ├── UUIDv5.ps1 ├── generate-pre-post.ps1 ├── generate-deprecated.ps1 ├── README.md └── prepare-extensions.ps1 /extensions/generic/tasks.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jessehouwing 2 | -------------------------------------------------------------------------------- /extensions/apple-xcode/fix.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Recurse -Filter "* *" -Path "./_tasks/" | Rename-Item -NewName { $_.Name -replace " ", "_" } -------------------------------------------------------------------------------- /extensions/dotnetcore/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "postfixes": ["sxs"], 3 | "prefixes": [], 4 | "tasks": ["DotNetCoreCLI", "UseDotNet"] 5 | } -------------------------------------------------------------------------------- /extensions/generic/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/generic/icon-large.png -------------------------------------------------------------------------------- /extensions/appcenter/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/appcenter/icon-large.png -------------------------------------------------------------------------------- /extensions/dotnetcore/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/dotnetcore/icon-large.png -------------------------------------------------------------------------------- /extensions/generic/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/generic/icon-default.png -------------------------------------------------------------------------------- /extensions/appcenter/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/appcenter/icon-default.png -------------------------------------------------------------------------------- /extensions/appcenter/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "postfixes": ["sxs"], 3 | "prefixes": [], 4 | "tasks": ["AppCenterDistribute", "AppCenterTest"] 5 | } -------------------------------------------------------------------------------- /extensions/apple-xcode/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/apple-xcode/icon-default.png -------------------------------------------------------------------------------- /extensions/apple-xcode/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/apple-xcode/icon-large.png -------------------------------------------------------------------------------- /extensions/dotnetcore/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/dotnetcore/icon-default.png -------------------------------------------------------------------------------- /extensions/visualstudio/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/visualstudio/icon-large.png -------------------------------------------------------------------------------- /extensions/pre-post-tasks/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/pre-post-tasks/icon-large.png -------------------------------------------------------------------------------- /extensions/pre-post-tasks/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes": ["Pre", "Post"], 3 | "postfixes": [], 4 | "tasks": ["PowerShell", "Bash", "CmdLine"] 5 | } -------------------------------------------------------------------------------- /extensions/visualstudio/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/visualstudio/icon-default.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _*/ 2 | .vs/ 3 | extensions/*/LICENSE 4 | extensions/*/PRIVACY.md 5 | extensions/*/vss-extension.debug.json 6 | extensions/*/vss-extension.tasks.json -------------------------------------------------------------------------------- /extensions/nuget-deprecated/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/nuget-deprecated/icon-default.png -------------------------------------------------------------------------------- /extensions/nuget-deprecated/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/nuget-deprecated/icon-large.png -------------------------------------------------------------------------------- /extensions/pre-post-tasks/icon-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-tasks-zips/HEAD/extensions/pre-post-tasks/icon-default.png -------------------------------------------------------------------------------- /extensions/vss-extension.debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.VisualStudio.Services" 5 | } 6 | ], 7 | "public": false 8 | } -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>jessehouwing/.github:renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /extensions/apple-xcode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "postfixes": ["sxs"], 3 | "prefixes": [], 4 | "tasks": ["InstallAppleCertificate", "InstallAppleProvisioningProfile", "Xcode"] 5 | } -------------------------------------------------------------------------------- /extensions/pre-post-tasks/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.VisualStudio.Services" 5 | } 6 | ], 7 | "public": true 8 | } 9 | -------------------------------------------------------------------------------- /extensions/visualstudio/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "postfixes": ["sxs"], 3 | "prefixes": [], 4 | "tasks": ["VSBuild", "VSTest", "VisualStudioTestPlatformInstaller", "MSBuild"] 5 | } -------------------------------------------------------------------------------- /extensions/pre-post-tasks/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pre-post-tasks", 3 | "name": "Pre and post script tasks", 4 | "description": "Run scripts before a job starts or after it ends." 5 | } -------------------------------------------------------------------------------- /extensions/nuget-deprecated/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.VisualStudio.Services" 5 | } 6 | ], 7 | "public": true 8 | 9 | } 10 | -------------------------------------------------------------------------------- /extensions/apple-xcode/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compatibility Pack: xcode", 3 | "description": "Adds current version of Apple Xcode tasks for Azure DevOps Server.", 4 | "id": "apple-xcode" 5 | } -------------------------------------------------------------------------------- /extensions/dotnetcore/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compatibility Pack: DotNetCoreCLI", 3 | "description": "Adds current version of DotNetCore for Azure DevOps Server.", 4 | "id": "dotnetcore" 5 | } -------------------------------------------------------------------------------- /extensions/generic/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.TeamFoundation.Server", 5 | "version": "[16.0, 19.0)" 6 | } 7 | ], 8 | "public": true 9 | } -------------------------------------------------------------------------------- /extensions/apple-xcode/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.TeamFoundation.Server", 5 | "version": "[16.0, 20.0)" 6 | } 7 | ], 8 | "public": true 9 | } -------------------------------------------------------------------------------- /extensions/generic/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "${{ task }} for Azure DevOps Server", 3 | "description": "Adds side-by-side version of ${{ task }} for Azure DevOps Server.", 4 | "id": "${{ task }}}" 5 | } -------------------------------------------------------------------------------- /extensions/nuget-deprecated/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nuget-deprecated", 3 | "name": "Compatibility Pack: NuGet", 4 | "description": "Adds current version of Nuget tasks that were removed from Azure DevOps" 5 | } -------------------------------------------------------------------------------- /extensions/dotnetcore/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.TeamFoundation.Server", 5 | "version": "[18.0, 19.0)" 6 | } 7 | ], 8 | "public": true 9 | } 10 | -------------------------------------------------------------------------------- /extensions/nuget-deprecated/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes": [], 3 | "postfixes": ["deprecated"], 4 | "tasks": ["NuGetRestore", "NuGetInstaller", "NuGetAuthenticate", "NuGetPackager", "NuGet", "NuGetPublisher"] 5 | } 6 | -------------------------------------------------------------------------------- /extensions/visualstudio/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.TeamFoundation.Server", 5 | "version": "[16.0, 20.0)" 6 | } 7 | ], 8 | "public": true 9 | } 10 | -------------------------------------------------------------------------------- /extensions/appcenter/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compatibility Pack: appcenterdistribute, appcentertest", 3 | "description": "Adds current version of AppCenter tasks for Azure DevOps Server.", 4 | "id": "appcenter" 5 | } 6 | -------------------------------------------------------------------------------- /extensions/visualstudio/fix.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -path "_tasks/VSTest-sxs/v*/node_modules/tunnel/test/keys/*.pem" -recurse | Remove-Item 2 | Get-ChildItem -path "_tasks/VSTest-sxs/v*/node_modules/http-signature/http_signing.md" -recurse | Remove-Item -------------------------------------------------------------------------------- /extensions/visualstudio/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Compatibility Pack: vsbuild, msbuild, vstest", 3 | "description": "Adds side-by-side version of Visual Studio 2022 and 2026 for Azure DevOps Server.", 4 | "id": "visualstudio" 5 | } 6 | -------------------------------------------------------------------------------- /extensions/appcenter/vss-extension.public.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "id": "Microsoft.TeamFoundation.Server", 5 | "version": "[16.0, 20.0)" 6 | } 7 | ], 8 | "public": true, 9 | "galleryFlags": [ 10 | "Preview" 11 | ] 12 | } -------------------------------------------------------------------------------- /extensions/appcenter/fix.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Recurse -Filter "#" -Path "./_tasks/" | %{ 2 | $_ | Rename-Item -NewName { $_.Name -replace "#", "_hash_" } 3 | 4 | $indexjs = $_.Parent.FullName + "/index.js" 5 | if (Test-Path -PathType Leaf -Path $indexjs) 6 | { 7 | (gc -raw $indexjs) -replace "\./#","./_hash_" | set-content $indexjs 8 | } 9 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jessehouwing 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.me/jessehouwing/5 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jesse Houwing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /extensions/generic/overview.md: -------------------------------------------------------------------------------- 1 | # ${{ task }} for Azure DevOps Server 2 | 3 | This extension will install the ${{ task }} into your Azure DevOps Server. 4 | 5 | These tasks are installed side-by-side the original tasks 6 | 7 | * In UI based builds you can recognize them by the `(Side-by-side)` postfix in the name of the task. 8 | * In YAML based builds you can recognize them by the `-sxs` postfix in the task identifier. 9 | 10 | 11 | ## Required agent version 12 | 13 | You will need to [install a recent agent (2.195.0 or newer) from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases). 14 | 15 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before launching the agent: 16 | 17 | ``` 18 | AZP_AGENT_DOWNGRADE_DISABLED=true 19 | ``` 20 | 21 | ## Replacing the built-in tasks 22 | 23 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 24 | 25 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 26 | -------------------------------------------------------------------------------- /extensions/appcenter/overview.md: -------------------------------------------------------------------------------- 1 | # AppCenter tasks for Azure DevOps Server 2 | 3 | This extension will install the latest AppCenterDistribute and AppCenterTest tasks into your Azure DevOps Server. 4 | 5 | These tasks are installed side-by-side the original tasks 6 | 7 | * In UI based builds you can recognize them by the `(Side-by-side)` postfix in the name of the task. 8 | * In YAML based builds you can recognize them by the `-sxs` postfix in the task identifier. 9 | 10 | 11 | ## Required agent version 12 | 13 | You will likely need to [install a more recent agent version from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases). 14 | 15 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before upgrading the agent: 16 | 17 | ``` 18 | AZP_AGENT_DOWNGRADE_DISABLED=true 19 | ``` 20 | 21 | ## Replacing the built-in tasks 22 | 23 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 24 | 25 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 26 | -------------------------------------------------------------------------------- /extensions/apple-xcode/overview.md: -------------------------------------------------------------------------------- 1 | # Apple Xcode tasks for Azure DevOps Server 2 | 3 | This extension will install the latest Xcode, InstallAppleCertificate and InstallAppleProvisioningProfile tasks into your Azure DevOps Server. 4 | 5 | These tasks are installed side-by-side the original tasks 6 | 7 | * In UI based builds you can recognize them by the `(Side-by-side)` postfix in the name of the task. 8 | * In YAML based builds you can recognize them by the `-sxs` postfix in the task identifier. 9 | 10 | 11 | ## Required agent version 12 | 13 | You will likely need to [install a more recent agent version from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases). 14 | 15 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before upgrading the agent: 16 | 17 | ``` 18 | AZP_AGENT_DOWNGRADE_DISABLED=true 19 | ``` 20 | 21 | ## Replacing the built-in tasks 22 | 23 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 24 | 25 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 26 | -------------------------------------------------------------------------------- /upload-releases.ps1: -------------------------------------------------------------------------------- 1 | [string[]] $existingReleases = & gh release list --repo jessehouwing/azure-pipelines-tasks-zips --limit 500 | Select-String "m\d+-tasks" | %{ $_.Matches.Value } 2 | $knownAssets = @{} 3 | 4 | foreach ($release in $existingReleases) 5 | { 6 | $releaseDetails = & gh release view --repo jessehouwing/azure-pipelines-tasks-zips $release --json name,tagName,assets | ConvertFrom-Json 7 | 8 | $knownAssets["$release"] = $releaseDetails.assets 9 | } 10 | 11 | foreach ($taskzip in (@(Get-ChildItem _gen/*.zip) + @(Get-ChildItem .\_download\*.zip))) 12 | { 13 | $taskzip.Name -match "-(?\d+\.\d+\.\d+)\.zip" | Out-Null 14 | $version = [version]$Matches.version 15 | 16 | if ($version.Minor -lt 100) 17 | { 18 | continue 19 | } 20 | 21 | if ($knownAssets."m$($version.Minor)-tasks") 22 | { 23 | if ($knownAssets."m$($version.Minor)-tasks" | Where-Object { $_.name -eq $taskzip.Name }) 24 | { 25 | continue 26 | } 27 | & gh release upload --repo jessehouwing/azure-pipelines-tasks-zips "m$($version.Minor)-tasks" $taskzip.FullName 28 | } 29 | else { 30 | & gh release create --repo jessehouwing/azure-pipelines-tasks-zips --title "m$($version.Minor) - Tasks" --notes-file .\releasenote.template.md "m$($version.Minor)-tasks" $taskzip.FullName 31 | $knownAssets."m$($version.Minor)-tasks" = @( 32 | @{ 33 | name = $taskzip.Name 34 | } 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /extensions/dotnetcore/overview.md: -------------------------------------------------------------------------------- 1 | # DotNetCore 6 and 7 tasks for Azure DevOps Server 2 | 3 | This extension will install the DotNetCore tasks with support for DotNetCore 6 and 7 into your Azure DevOps Server. 4 | 5 | These tasks are installed side-by-side the original tasks 6 | 7 | * In UI based builds you can recognize them by the `(Side-by-side)` postfix in the name of the task. 8 | * In YAML based builds you can recognize them by the `-sxs` postfix in the task identifier. 9 | 10 | 11 | ## Required agent version 12 | 13 | You will need to [install a recent agent (2.195.0 or newer) from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases) for it to auto-detect Visual Studio 2022, or alternatively add the capabilities to the agent manually. 14 | 15 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before launching the agent: 16 | 17 | ``` 18 | AZP_AGENT_DOWNGRADE_DISABLED=true 19 | ``` 20 | 21 | ## Replacing the built-in tasks 22 | 23 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 24 | 25 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 26 | -------------------------------------------------------------------------------- /extensions/visualstudio/overview.md: -------------------------------------------------------------------------------- 1 | # Visual Studio 2022 & 2026 tasks for Azure DevOps Server 2 | 3 | This extension will install the `vsbuild`, `msbuild`, `vstest` and `vstestplatforminstaller` with support for Visual Studio 2022 into your Azure DevOps Server. 4 | 5 | These tasks are installed side-by-side the original tasks 6 | 7 | * In UI based builds you can recognize them by the `(Side-by-side)` postfix in the name of the task. 8 | * In YAML based builds you can recognize them by the `-sxs` postfix in the task identifier. 9 | 10 | ## Required agent version 11 | 12 | You will need to [install a recent agent (2.195.0 or newer) from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases) for it to auto-detect Visual Studio 2022, or alternatively add the capabilities to the agent manually. 13 | 14 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before launching the agent: 15 | 16 | ``` 17 | AZP_AGENT_DOWNGRADE_DISABLED=true 18 | ``` 19 | 20 | ## Replacing the built-in tasks 21 | 22 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 23 | 24 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 25 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | We're working hard to protect your privacy, while delivering software that brings you the performance, power and convenience you desire. This privacy statement explains many of the data collection and use practices for this extension. This isn't intended to be an exhaustive list and does only apply to this extension. 3 | ## Security of Your Information 4 | We are committed to protecting the security of your information. No data is stored by the extension. 5 | ## Data for Quality and Reliability Improvments 6 | We do not collect any information to improve the quality and reliability of the extension, nor do we collect telemetry about usage, performance and errors. 7 | ## Collection and Use of Your Personal Information 8 | We do not collect any information that identifies you as an individual, except the information Microsoft provides to us for commercial purposes 9 | ## Disclosure to Third Parties 10 | Except as described in this statement, information you provide will not be transferred to third parties, Microsoft excluded, without your consent. We may access or disclose information about you, including the content of your communications, in order to: (a) comply with the law or respond to lawful requests or legal process; (b) protect the rights or property of Microsoft or our customers, including the enforcement of our agreements or policies governing your use of the services; or (c) act on a good faith belief that such access or disclosure is necessary to protect the personal safety of our employees, customers, or the public. We may also disclose personal information as part of a corporate transaction such as a merger or sale of assets. -------------------------------------------------------------------------------- /extensions/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "version": "1.200.0", 4 | "publisher": "jessehouwing", 5 | "categories": [ 6 | "Azure Pipelines" 7 | ], 8 | "tags": [ 9 | "Utility", 10 | "xebia" 11 | ], 12 | "icons": { 13 | "default": "icon-default.png", 14 | "large": "icon-large.png" 15 | }, 16 | "screenshots": [], 17 | "content": { 18 | "details": { 19 | "path": "overview.md" 20 | }, 21 | "license": { 22 | "path": "LICENSE" 23 | }, 24 | "privacy": { 25 | "path": "PRIVACY.md" 26 | } 27 | }, 28 | "links": { 29 | "getstarted": { 30 | "uri": "https://github.com/jessehouwing/azure-pipelines-tasks-zips/wiki" 31 | }, 32 | "support": { 33 | "uri": "https://github.com/jessehouwing/azure-pipelines-tasks-zips/issues" 34 | }, 35 | "license": { 36 | "uri": "https://github.com/jessehouwing/azure-pipelines-tasks-zips/LICENSE" 37 | }, 38 | "privacypolicy": { 39 | "uri": "https://github.com/jessehouwing/azure-pipelines-tasks-zips/blob/main/PRIVACY.md" 40 | } 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "uri": "https://github.com/jessehouwing/azure-pipelines-tasks-zips" 45 | }, 46 | "badges": [ 47 | { 48 | "href": "https://github.com/sponsors/jessehouwing", 49 | "uri": "https://img.shields.io/github/sponsors/jessehouwing", 50 | "description": "GitHub Sponsors" 51 | } 52 | ], 53 | "branding": { 54 | "color": "rgb(36, 43, 50)", 55 | "theme": "dark" 56 | }, 57 | "files": [ 58 | { 59 | "path": "_tasks" 60 | } 61 | ], 62 | "contributions": [ 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/publish-tasks.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Download tasks 4 | 5 | # Controls when the workflow will run 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "22 2 * * *" 10 | 11 | # Reject all permissions by default 12 | permissions: {} 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: windows-latest 20 | 21 | # Permissions required for this job 22 | permissions: 23 | contents: write # Read repository contents and create/update releases and their assets 24 | actions: read # Read actions for GitHub CLI operations 25 | 26 | # Steps represent a sequence of tasks that will be executed as part of the job 27 | steps: 28 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 29 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 30 | with: 31 | lfs: true 32 | 33 | - name: Setup Node.js environment 34 | uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 35 | with: 36 | node-version: 24 37 | check-latest: true 38 | 39 | - run: | 40 | .\build.ps1 41 | shell: pwsh 42 | env: 43 | AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }} 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | name: Download tasks and build side-by-side versions. 46 | 47 | - run: | 48 | .\publish-extensions.ps1 49 | shell: pwsh 50 | name: Publish extensions 51 | env: 52 | AZURE_MARKETPLACE_PAT: ${{ secrets.AZURE_MARKETPLACE_PAT }} 53 | -------------------------------------------------------------------------------- /extensions/nuget-deprecated/overview.md: -------------------------------------------------------------------------------- 1 | # Nuget Tasks that were deprecated from Azure DevOps 2 | 3 | > ⚠️ It's highly recommended to migrate your existing pipelines to the newer `NuGetCommand@2` tasks. This extension is provided purely for backwards compatibility. 4 | 5 | This extension contains the last versions of the `NuGetInstaller` and `NuGetRestore` tasks which will be removed on 27th of November 2023. 6 | 7 | These tasks are installed side-by-side the original tasks 8 | 9 | * In UI based builds you can recognize them by the `(Deprecated)` postfix in the name of the task. 10 | * In YAML based builds you can recognize them by the `-deprecated` postfix in the task identifier. 11 | 12 | You can use these tasks in case you need more time to transition or if you need these older tasks for older reproducible builds. 13 | 14 | ## Background: 15 | 16 | > [Sprint 229 update - Deprecation announcement for NuGet Restore v1 and NuGet Installer v0 pipeline tasks](https://learn.microsoft.com/en-us/azure/devops/release-notes/2023/sprint-229-update) 17 | > 18 | > With this update, we are announcing the upcoming deprecation of NuGet Restore v1 and NuGet Installer v0 pipeline tasks. Promptly transition to the NuGetCommand@2 pipeline task to avoid build failure starting on November 27, 2023. 19 | > 20 | > If you're using the NuGet Restore v1 and NuGet Installer v0 pipeline tasks, promptly transition to the NuGetCommand@2 pipeline task. You'll begin receiving alerts in your pipelines soon if the transition hasn't been made. If no action is taken, starting November 27, 2023, your builds will result in failure. 21 | 22 | ## Replacing the built-in tasks 23 | 24 | An extension is unable to replace the built-in tasks. This is a security feature of the marketplace. But it's possible to replace a task by uploading it directly to your Azure DevOps server. 25 | 26 | You can find the [latest version of the task and the scripts to overwrite the built-in tasks in this project's repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips#installation). 27 | -------------------------------------------------------------------------------- /.github/workflows/powershell.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # 6 | # https://github.com/microsoft/action-psscriptanalyzer 7 | # For more information on PSScriptAnalyzer in general, see 8 | # https://github.com/PowerShell/PSScriptAnalyzer 9 | 10 | name: PSScriptAnalyzer 11 | 12 | on: 13 | push: 14 | branches: [ "main" ] 15 | pull_request: 16 | branches: [ "main" ] 17 | schedule: 18 | - cron: '35 2 * * 1' 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | build: 25 | permissions: 26 | contents: read # for actions/checkout to fetch code 27 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 28 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 29 | name: PSScriptAnalyzer 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 33 | 34 | - name: Run PSScriptAnalyzer 35 | uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f # v1.1 36 | with: 37 | # Check https://github.com/microsoft/action-psscriptanalyzer for more info about the options. 38 | # The below set up runs PSScriptAnalyzer to your entire repository and runs some basic security rules. 39 | path: .\ 40 | recurse: true 41 | # Include your own basic security rules. Removing this option will run all the rules 42 | includeRule: '"PSAvoidGlobalAliases", "PSAvoidUsingConvertToSecureStringWithPlainText"' 43 | output: results.sarif 44 | 45 | # Upload the SARIF file generated in the previous step 46 | - name: Upload SARIF results file 47 | uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 48 | with: 49 | sarif_file: results.sarif 50 | -------------------------------------------------------------------------------- /releasenote.template.md: -------------------------------------------------------------------------------- 1 | # TaskName.guid-1.200.71.zip 2 | 3 | This zip contains a verbatim copy of the task. They are downloaded directly from my Azure DevOps organisation and are published unchanged. 4 | 5 | # TaskName-sxs.guid.1.200.71.zip 6 | 7 | This zip contains a patched copy of the task. These tasks can be installed side-by-side with the built-in tasks. All tasks have a new unique id and the task's name has been post-pended with `-sxs`. 8 | 9 | # Installation 10 | 11 | To install these tasks into your Team Foundation Server / Azure DevOps Server use `tfx`: 12 | 13 | ``` 14 | npm install -g tfx-cli 15 | tfx build tasks upload --task-zip-path Task.guid-version.zip --service-url https://yourtfs.com/tfs/DefaultCollection 16 | ``` 17 | 18 | Or [this PowerShell script](https://github.com/jessehouwing/azure-pipelines-tasks-zips/blob/main/scripts/install-task.ps1): 19 | 20 | ``` 21 | . ./script/install-task.ps1 -CollectionUrl https://yourtfs.com/tfs/DefaultCollection -TaskZip Task.guid-version.zip 22 | ``` 23 | 24 | # Extension 25 | 26 | A few tasks seem to be getting the most demand. I've added a pre-built extension for those and also published these to the marketplace: 27 | 28 | * 🛍️ [AppCenter for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.appcenter) 29 | * 🛍️ [Apple Xcode for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.Apple-Xcode) 30 | * 🛍️ [DotNetCore 6 and 7 for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.dotnetcore) 31 | * 🛍️ [Nuget (Deprecated)](https://marketplace.visualstudio.com/items?itemName=jessehouwing.nuget-deprecated) 32 | * 🛍️ [Pre and post script tasks](https://marketplace.visualstudio.com/items?itemName=jessehouwing.pre-post-tasks) 33 | * 🛍️ [Visual Studio 2022 for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.visualstudio) 34 | 35 | These extensions install the side-by-side version into your Azure DevOps Server. 36 | 37 | -------------------------------------------------------------------------------- /publish-extensions.ps1: -------------------------------------------------------------------------------- 1 | if (test-path -PathType Leaf ./_vsix/*.vsix) { 2 | & npm install tfx-cli@^0.22 --location=global --no-fund 3 | $anyFailures = $false 4 | foreach ($vsix in dir ./_vsix/*.vsix) { 5 | Write-Output "Publishing: $($vsix.FullName)" 6 | Write-Output "::group::Checking extension version for $($vsix.Name)" 7 | $json = (& tfx extension show --token $env:AZURE_MARKETPLACE_PAT --vsix $vsix.FullName --json) | ConvertFrom-Json 8 | Write-Output "::endgroup::" 9 | 10 | if (-not ($json.versions -and $json.versions.count -gt 0 -and $vsix.FullName.EndsWith("$($json.versions[0].version).vsix"))) { 11 | Write-Output "::group::Publishing extension $($vsix.Name)" 12 | & tfx extension publish --vsix $vsix.FullName --token $env:AZURE_MARKETPLACE_PAT --no-wait-validation 13 | if ($LASTEXITCODE -ne 0) { 14 | Write-Output "::error::Failed to publish extension $($vsix.Name)" 15 | $anyFailures = $true 16 | } 17 | else { 18 | Write-Output "::notice::Successfully published extension $($vsix.Name)" 19 | } 20 | Write-Output "::endgroup::" 21 | } 22 | else { 23 | Write-Output "::notice::Extension $($vsix.Name) is already up to date, skipping publish" 24 | } 25 | } 26 | 27 | foreach ($vsix in dir ./_vsix/*.vsix) { 28 | $sleep = 0 29 | Write-Output "::group::Validating extension $($vsix.Name)" 30 | do { 31 | $status = & tfx extension isvalid --vsix $vsix.FullName --service-url https://marketplace.visualstudio.com/ --token $env:AZURE_MARKETPLACE_PAT --json | ConvertFrom-Json 32 | Start-Sleep -Seconds $sleep 33 | $sleep = $sleep + 15 34 | } while ($status.status -eq "pending") 35 | 36 | if ($status.status -ne "success") { 37 | Write-Output "::error::Extension validation failed for extension $($vsix.Name)" 38 | write-output $status.message.message 39 | $anyFailures = $true 40 | } 41 | Write-Output "::endgroup::" 42 | } 43 | 44 | if ($anyFailures) { 45 | Write-Output "::error::One or more extensions failed to publish" 46 | exit 1 47 | } 48 | } -------------------------------------------------------------------------------- /scripts/install-task.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [Parameter(Mandatory = $true)] 4 | [string]$CollectionUrl, 5 | [Parameter(Mandatory = $true)] 6 | [string]$TaskZip) 7 | 8 | $ErrorActionPreference = 'Stop' 9 | 10 | # Adapted from: https://github.com/microsoft/azure-pipelines-tasks/tree/master/docs/pinToTaskVersion 11 | 12 | function Install-Task { 13 | [CmdletBinding()] 14 | param( 15 | [Parameter(Mandatory = $true)] 16 | [string]$CollectionUrl, 17 | 18 | [Parameter(Mandatory = $true)] 19 | $Task) 20 | 21 | "Installing task '$($Task.Name)' version '$($Task.Version)' id '$($Task.Id)'." 22 | $url = "$($CollectionUrl.TrimEnd('/'))/_apis/distributedtask/tasks/$($Task.Id)/?overwrite=false&api-version=2.0" 23 | 24 | # Format the content. 25 | [byte[]]$bytes = [System.Convert]::FromBase64String($Task.Base64Zip) 26 | 27 | # Send the HTTP request. 28 | try { 29 | Invoke-RestMethod -Uri $url -Method Put -Body $bytes -UseDefaultCredentials -ContentType 'application/octet-stream' -Headers @{ 30 | #'Authorization' = "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$Pat")))" 31 | 'X-TFS-FedAuthRedirect' = 'Suppress' 32 | 'Content-Range' = "bytes 0-$($bytes.Length - 1)/$($bytes.Length)" 33 | } 34 | } catch { 35 | $details = $null 36 | try { $details = ConvertFrom-Json $_.ErrorDetails.Message } 37 | catch { } 38 | 39 | if ($details.TypeKey -eq 'TaskDefinitionExistsException') { 40 | Write-Warning $details.Message 41 | } else { 42 | throw 43 | } 44 | } 45 | } 46 | 47 | # Validate the directory exists. 48 | if (!(Test-Path $TaskZip -PathType Leaf)) 49 | { 50 | throw "File does not exist: '$TaskZip'." 51 | } 52 | 53 | # Resolve the directory info. 54 | $TaskZip = Get-Item $TaskZip 55 | 56 | 57 | $base64Zip = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes((Get-Item -LiteralPath $TaskZip).FullName)) 58 | $TaskZip.Name 59 | 60 | if ($TaskZip.Name -match "(?m)^(?.*)\.(?[0-9a-f]{8}[-](?:[0-9a-f]{4}[-]){3}[0-9a-f]{12})-(?\d+\.\d+\.\d+)\.zip$") 61 | { 62 | $manifest = $Matches 63 | 64 | # Embed the task into the script. 65 | $id = "$($manifest.Id)" 66 | $name = "$($manifest.Name)" 67 | $version = "$($manifest.Version)" 68 | $task = @{ 69 | Id = $id.Replace("'", "''") 70 | Name = $name.Replace("'", "''") 71 | Version = $version.Replace("'", "''") 72 | Base64Zip = $base64Zip 73 | } 74 | 75 | Install-Task -CollectionUrl $CollectionUrl -Task $task 76 | } 77 | else 78 | { 79 | throw "File does not match required pattern 'name-id.version.zip': '$TaskZip'." 80 | } -------------------------------------------------------------------------------- /extensions/pre-post-tasks/overview.md: -------------------------------------------------------------------------------- 1 | # Pre and Post job script tasks 2 | 3 | This extension contains patched versions of the "BashV3", "CmdLineV2" and "PowerShellV2" tasks. They are built from the latets release that was installed into my Azure DevOps Account. 4 | 5 | Each task is extended with a Pre-Job version and a Post-Job version. 6 | 7 | ## Uses 8 | 9 | You can use these tasks to inject an (inline) script that runs prior to checkout (Pre-job), or as part of the cleanup steps of a job (Post-Job). 10 | 11 | ### Pre-job 12 | 13 | The Pre-job tasks can be used to inject a script very early in pipeline. Under normal circumstances, you'd be required to use a custom task or a decorator to do this. 14 | 15 | * Change variables that influence behavior of the Checkout task. (e.g. `Build.SyncSources`) 16 | * Install certificates into the git trusted certificates store 17 | * Replace `git` or `tf` with a different version / configuration 18 | * Install an extension to `git` required by your repository 19 | * Validate certain conditions and fail the build even befor it starts checking out souces 20 | 21 | Some of these steps are more useful on the Azure Pipelines Hosted Agents, since you can't change their configuration prior to the job starting. 22 | 23 | > Note: since these tasks run prior to checkout, you can't rely on any script files from your repositories. If you want to run a script file, you'll need to first add a tasks that downloads your script using the inline option, then run the script with a second task that runs the script downloaded by the first task. 24 | 25 | ### Post-Job 26 | 27 | The post-job tasks can be used to inject a script that will run after the job has completed. 28 | 29 | * Perform a clean-up task (delete temporary files, trusted certificates etc) 30 | * Remove gpg sign key from the agent. 31 | 32 | ## My own uses 33 | 34 | I've used these tasks to test scripts I've later included in custom tasks and decorators. That way I did not have to build and publish the extension(s) containing these tasks every time I needed to test something. Examples of my own usage: 35 | 36 | * [Fix parallel pipeline execution of TFVC builds on the hosted pool](https://jessehouwing.net/azure-pipelines-fixing-massive-parallel-builds-with-tfvc/). 37 | * [Skip Checkout / Don't sync sources task for TFVC](https://github.com/jessehouwing/azure-pipelines-tfvc-tasks/tree/main/tf-vc-dontsync/v2) 38 | * [Install trusted GPG keys prior to checkout](https://github.com/jessehouwing/azure-pipelines-verify-signed-decorator/blob/main/verify-signed-decorator.yml) 39 | 40 | And you can find many other people who included [pre-](https://github.com/search?q=prejobexecution+filename%3Atask.json&type=Code&ref=advsearch&l=&l=) and [post-job](https://github.com/search?q=postjobexecution+filename%3Atask.json&type=Code&ref=advsearch&l=&l=) tasks in their extensions. I'm hoping these tasks will make the job of developing these extensions a little easier. And possibly will remove the need for these kinds of extensions for one-of scripts that need to run outside of the job context. 41 | -------------------------------------------------------------------------------- /generate-sxs.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference="Stop" 2 | 3 | $outputDir = mkdir "_gen" -force 4 | $tasksToPatch = get-childitem "_download/*.zip" 5 | 6 | foreach ($task in $tasksToPatch) 7 | { 8 | if (Test-Path "_tmp") 9 | { 10 | Remove-Item "_tmp" -force -Recurse 11 | } 12 | 13 | $taskDir = "_tmp" 14 | 15 | if (Test-Path -path "_gen\$($task.Name -replace '^([^.]+).*-','$1-sxs*')" -PathType Leaf) 16 | { 17 | continue 18 | } 19 | 20 | # Expand-Archive -Path $task -DestinationPath _tmp 21 | & "C:\Program Files\7-Zip\7z.exe" x $task -o_tmp task*.json *.resjson -r -bd 22 | if ($LASTEXITCODE -ne 0) 23 | { 24 | Remove-item $task 25 | Write-Error "Failed to extract $task" 26 | continue 27 | } 28 | 29 | $taskManifestFiles = @("task.loc.json", "task.json") 30 | $manifest = @{} 31 | 32 | foreach ($taskManifestFile in $taskManifestFiles) 33 | { 34 | $manifestPath = "$taskDir/$taskManifestFile" 35 | if (Test-Path -Path $manifestPath -PathType Leaf) 36 | { 37 | $manifest = (Get-Content $manifestPath -raw) | ConvertFrom-Json -AsHashtable 38 | $manifest.name = "$($manifest.name)-sxs" 39 | if ($taskManifestFile -eq "task.json") 40 | { 41 | $manifest.friendlyName = "$($manifest.friendlyName) (Side-by-side)" 42 | if (Test-Path -Path "$taskDir\Strings" -PathType Container) 43 | { 44 | $resourceFiles = Get-ChildItem "$taskDir\Strings\resources.resjson\resources.resjson" -recurse -ErrorAction "Continue" 45 | foreach ($resourceFile in $resourceFiles) 46 | { 47 | try { 48 | $resources = (Get-Content $resourceFile -raw) | ConvertFrom-Json -AsHashtable 49 | } 50 | catch { 51 | # allow for manual intervention. 52 | $resources = (Get-Content $resourceFile -raw) | ConvertFrom-Json -AsHashtable 53 | } 54 | if ($resources["loc.friendlyName"]) 55 | { 56 | $resources["loc.friendlyName"] = $manifest.friendlyName 57 | } 58 | $resources | ConvertTo-Json -depth 100 | Out-File $resourceFile -Encoding utf8NoBOM 59 | 60 | } 61 | } 62 | } 63 | $manifest.id = Get-UUIDv5 $manifest.id $manifest.name 64 | $manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM 65 | } 66 | } 67 | 68 | $taskName = $manifest.name 69 | $taskid = $manifest.id 70 | $taskversion = "$($manifest.version.Major).$($manifest.version.Minor).$($manifest.version.Patch)" 71 | $taskZip = "$taskName.$taskid-$taskversion.zip" 72 | 73 | Copy-Item $task "_gen\$taskzip" 74 | Push-Location _tmp 75 | 76 | & "C:\Program Files\7-Zip\7z.exe" u "$outputDir\$taskzip" "*" -r -bd 77 | if ($LASTEXITCODE -ne 0) 78 | { 79 | Remove-Item "$outputDir\$taskzip" 80 | Write-Error "Failed to compress $task" 81 | continue 82 | } 83 | 84 | write-output "Created: $taskzip" 85 | Pop-Location 86 | } 87 | -------------------------------------------------------------------------------- /download.ps1: -------------------------------------------------------------------------------- 1 | $outputDir = md _download -force 2 | 3 | $org = "jessehouwing-brazil" 4 | $pat = $env:AZURE_DEVOPS_PAT 5 | 6 | $url = "https://dev.azure.com/$org" 7 | $header = @{authorization = "Basic $([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(".:$pat")))"} 8 | 9 | write-output "::notice::Fetching all tasks from $org" 10 | $tasks = Invoke-RestMethod -Uri "$url/_apis/distributedtask/tasks?allversions=true" -Method Get -ContentType "application/json" -Headers $header | ConvertFrom-Json -AsHashtable 11 | 12 | $taskMetadatas = $tasks.value 13 | 14 | write-output "::notice::Fetching all releases from GitHub" 15 | [string[]] $existingReleases = & gh release list --repo jessehouwing/azure-pipelines-tasks-zips --limit 500 | Select-String "m\d+-tasks" | %{ $_.Matches.Value } 16 | $allAssets = @() 17 | foreach ($release in $existingReleases) 18 | { 19 | $releaseDetails = & gh release view --repo jessehouwing/azure-pipelines-tasks-zips $release --json name,tagName,assets | ConvertFrom-Json 20 | $allAssets = $allAssets + $releaseDetails.assets 21 | } 22 | 23 | $taskMetadatas | ForEach-Object -Parallel { 24 | $url = $using:url 25 | $outputDir = $using:outputDir 26 | $header = $using:header 27 | 28 | $taskMetadata = $_ 29 | if ($taskMetadata.serverOwned) 30 | { 31 | $taskName = $taskMetadata.name 32 | $taskid = $taskMetadata.id 33 | $taskversion = "$($taskMetadata.version.major).$($taskMetadata.version.minor).$($taskMetadata.version.patch)" 34 | $taskZip = "$taskName.$taskid-$taskversion.zip" 35 | 36 | if ((-not ( 37 | (Test-Path -PathType Leaf -Path "$outputDir/$taskZip") -or 38 | (($using:allAssets | Where-Object { $_.name -eq $taskZip }).Count -gt 0) -or 39 | ($taskMetadata.version.minor -lt 100) 40 | ) ) 41 | ) 42 | { 43 | $success = $false 44 | $retry = 5 45 | while ((-not $success) -and ($retry -gt 0)) 46 | { 47 | try 48 | { 49 | write-output "::notice::Downloading: $taskZip" 50 | Invoke-WebRequest -Uri "$url/_apis/distributedtask/tasks/$taskid/$taskversion" -OutFile "$outputDir/$taskZip" -Headers $header 51 | 52 | write-output "::notice::Verifying: $taskZip" 53 | & "C:\Program Files\7-Zip\7z.exe" t "$outputDir/$taskZip" 54 | if ($LASTEXITCODE -ne 0) 55 | { 56 | throw "Failed test failed for: $taskZip" 57 | } 58 | 59 | $success = $true 60 | } 61 | catch 62 | { 63 | $retry-- 64 | remove-item -path "$outputDir/$taskZip" -force -erroraction silentlycontinue 65 | if ($retry -eq 0) 66 | { 67 | write-output "::error::Failed to download $taskZip" 68 | } 69 | else 70 | { 71 | write-output "::warning::Failed to download $taskZip, retrying" 72 | } 73 | } 74 | } 75 | write-output "::notice::Downloaded: $taskZip" 76 | } 77 | else 78 | { 79 | write-output "::debug::Already have: $taskZip" 80 | } 81 | } 82 | } -ThrottleLimit 8 83 | -------------------------------------------------------------------------------- /UUIDv5.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference="Stop" 2 | 3 | $Source = @" 4 | using System; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | public static class UUIDv5 9 | { 10 | public static Guid Create(Guid namespaceId, string name) 11 | { 12 | if (name == null) 13 | throw new ArgumentNullException("name"); 14 | 15 | // convert the name to a sequence of octets (as defined by the standard or conventions of its namespace) (step 3) 16 | // ASSUME: UTF-8 encoding is always appropriate 17 | byte[] nameBytes = Encoding.UTF8.GetBytes(name); 18 | 19 | // convert the namespace UUID to network order (step 3) 20 | byte[] namespaceBytes = namespaceId.ToByteArray(); 21 | SwapByteOrder(namespaceBytes); 22 | 23 | // comput the hash of the name space ID concatenated with the name (step 4) 24 | byte[] hash; 25 | using (HashAlgorithm algorithm = SHA1.Create()) 26 | { 27 | algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); 28 | algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); 29 | hash = algorithm.Hash; 30 | } 31 | 32 | // most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12) 33 | byte[] newGuid = new byte[16]; 34 | Array.Copy(hash, 0, newGuid, 0, 16); 35 | 36 | // set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8) 37 | newGuid[6] = (byte)((newGuid[6] & 0x0F) | (5 << 4)); 38 | 39 | // set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10) 40 | newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); 41 | 42 | // convert the resulting UUID to local byte order (step 13) 43 | SwapByteOrder(newGuid); 44 | return new Guid(newGuid); 45 | } 46 | 47 | /// 48 | /// The namespace for fully-qualified domain names (from RFC 4122, Appendix C). 49 | /// 50 | public static readonly Guid DnsNamespace = new Guid("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); 51 | 52 | /// 53 | /// The namespace for URLs (from RFC 4122, Appendix C). 54 | /// 55 | public static readonly Guid UrlNamespace = new Guid("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); 56 | 57 | /// 58 | /// The namespace for ISO OIDs (from RFC 4122, Appendix C). 59 | /// 60 | public static readonly Guid IsoOidNamespace = new Guid("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); 61 | 62 | // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). 63 | internal static void SwapByteOrder(byte[] guid) 64 | { 65 | SwapBytes(guid, 0, 3); 66 | SwapBytes(guid, 1, 2); 67 | SwapBytes(guid, 4, 5); 68 | SwapBytes(guid, 6, 7); 69 | } 70 | 71 | private static void SwapBytes(byte[] guid, int left, int right) 72 | { 73 | byte temp = guid[left]; 74 | guid[left] = guid[right]; 75 | guid[right] = temp; 76 | } 77 | } 78 | "@ 79 | 80 | Add-Type -TypeDefinition $Source -Language CSharp 81 | 82 | function Get-UUIDv5 { 83 | param( 84 | [guid] $namespace, 85 | [string] $name 86 | ) 87 | 88 | return [UUIDv5]::Create([guid]$manifest.id, [string]$manifest.name).ToString() 89 | } -------------------------------------------------------------------------------- /generate-pre-post.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference="Stop" 2 | 3 | $outputDir = mkdir "_gen" -force 4 | 5 | $tasksToPatch = @("Bash", "CmdLine", "PowerShell") 6 | $taskKinds = @("Pre", "Post") 7 | 8 | $filesToPatch = @() 9 | foreach ($task in $tasksToPatch) 10 | { 11 | $filesToPatch += Get-ChildItem "_download/$task.*.zip" 12 | } 13 | 14 | foreach ($task in $filesToPatch) 15 | { 16 | foreach ($kind in $taskKinds) 17 | { 18 | if (Test-Path "_tmp") 19 | { 20 | Remove-Item "_tmp" -force -Recurse 21 | } 22 | 23 | $taskDir = "_tmp" 24 | 25 | if (Test-Path -path "_gen\$($task.Name -replace '^([^.]+).*-',"$kind-`$1.*")" -PathType Leaf) 26 | { 27 | continue 28 | } 29 | 30 | # Expand-Archive -Path $task -DestinationPath _tmp 31 | & "C:\Program Files\7-Zip\7z.exe" x $task -o_tmp task*.json *.resjson -r -bd 32 | if ($LASTEXITCODE -ne 0) 33 | { 34 | Remove-item $task 35 | Write-Error "Failed to extract $task" 36 | continue 37 | } 38 | 39 | $taskManifestFiles = @("task.loc.json", "task.json") 40 | $manifest = @{} 41 | 42 | foreach ($taskManifestFile in $taskManifestFiles) 43 | { 44 | $manifestPath = "$taskDir/$taskManifestFile" 45 | if (Test-Path -Path $manifestPath -PathType Leaf) 46 | { 47 | $manifest = (Get-Content $manifestPath -raw) | ConvertFrom-Json 48 | $manifest.name = "$kind-$($manifest.name)" 49 | if ($taskManifestFile -eq "task.json") 50 | { 51 | $manifest.friendlyName = "$($manifest.friendlyName) ($kind-Job)" 52 | if (Test-Path -Path "$taskDir\Strings" -PathType Container) 53 | { 54 | $resourceFiles = Get-ChildItem "$taskDir\Strings\resources.resjson\resources.resjson" -recurse -ErrorAction "Continue" 55 | foreach ($resourceFile in $resourceFiles) 56 | { 57 | $resources = (Get-Content $resourceFile -raw) | ConvertFrom-Json -AsHashtable 58 | if ($resources["loc.friendlyName"]) 59 | { 60 | $resources["loc.friendlyName"] = $manifest.friendlyName 61 | } 62 | $resources | ConvertTo-Json -depth 100 | Out-File $resourceFile -Encoding utf8NoBOM 63 | } 64 | } 65 | } 66 | $manifest.id = Get-UUIDv5 $manifest.id $manifest.name 67 | $manifest.author = "Jesse Houwing" 68 | $manifest | Add-Member -MemberType NoteProperty -Name "$($kind.ToLower())jobexecution" -Value $manifest.execution 69 | $manifest.PSObject.Properties.Remove('execution') 70 | 71 | $manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM 72 | } 73 | } 74 | 75 | $taskName = $manifest.name 76 | $taskid = $manifest.id 77 | $taskversion = "$($manifest.version.Major).$($manifest.version.Minor).$($manifest.version.Patch)" 78 | $taskZip = "$taskName.$taskid-$taskversion.zip" 79 | 80 | Copy-Item $task "_gen\$taskzip" 81 | Push-Location _tmp 82 | 83 | & "C:\Program Files\7-Zip\7z.exe" u "$outputDir\$taskzip" "*" -r -bd 84 | if ($LASTEXITCODE -ne 0) 85 | { 86 | Remove-Item "$outputDir\$taskzip" 87 | Write-Error "Failed to compress $task" 88 | continue 89 | } 90 | 91 | write-output "Created: $taskzip" 92 | Pop-Location 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /generate-deprecated.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference="Stop" 2 | 3 | $outputDir = mkdir "_gen" -force 4 | 5 | $tasksToPatch = @("NuGetRestore", "NuGetInstaller", "NuGetAuthenticate", "NuGetPackager", "NuGet", "NuGetPublisher") 6 | $taskKinds = @("deprecated") 7 | 8 | $filesToPatch = @() 9 | foreach ($task in $tasksToPatch) 10 | { 11 | $filesToPatch += Get-ChildItem "_download/$task.*.zip" 12 | } 13 | 14 | foreach ($task in $filesToPatch) 15 | { 16 | foreach ($kind in $taskKinds) 17 | { 18 | if (Test-Path "_tmp") 19 | { 20 | Remove-Item "_tmp" -force -Recurse 21 | } 22 | 23 | $taskDir = "_tmp" 24 | 25 | if (Test-Path -path "_gen\$($task.Name -replace '([^.]+).*-',"`$1-$kind.*")" -PathType Leaf) 26 | { 27 | continue 28 | } 29 | 30 | # Expand-Archive -Path $task -DestinationPath _tmp 31 | & "C:\Program Files\7-Zip\7z.exe" x $task -o_tmp task*.json *.resjson -r -bd 32 | if ($LASTEXITCODE -ne 0) 33 | { 34 | Remove-item $task 35 | Write-Error "Failed to extract $task" 36 | continue 37 | } 38 | 39 | $taskManifestFiles = @("task.loc.json", "task.json") 40 | $manifest = @{} 41 | 42 | foreach ($taskManifestFile in $taskManifestFiles) 43 | { 44 | $manifestPath = "$taskDir/$taskManifestFile" 45 | if (Test-Path -Path $manifestPath -PathType Leaf) 46 | { 47 | $manifest = (Get-Content $manifestPath -raw) | ConvertFrom-Json 48 | $manifest.name = "$($manifest.name)-deprecated" 49 | if ($taskManifestFile -eq "task.json") 50 | { 51 | $manifest.friendlyName = "$($manifest.friendlyName) (Deprecated)" 52 | if (Test-Path -Path "$taskDir\Strings" -PathType Container) 53 | { 54 | $resourceFiles = Get-ChildItem "$taskDir\Strings\resources.resjson\resources.resjson" -recurse -ErrorAction "Continue" 55 | foreach ($resourceFile in $resourceFiles) 56 | { 57 | $resources = (Get-Content $resourceFile -raw) | ConvertFrom-Json -AsHashtable 58 | if ($resources["loc.friendlyName"]) 59 | { 60 | $resources["loc.friendlyName"] = $manifest.friendlyName 61 | } 62 | $resources | ConvertTo-Json -depth 100 | Out-File $resourceFile -Encoding utf8NoBOM 63 | } 64 | } 65 | } 66 | $manifest.id = Get-UUIDv5 $manifest.id $manifest.name 67 | $manifest.author = "Jesse Houwing" 68 | $manifest.PSObject.Properties.Remove('removalDate') 69 | if (-not $manifest.deprecated) 70 | { 71 | $manifest | Add-Member -MemberType NoteProperty -Name "deprecated" -Value $true 72 | } 73 | 74 | $manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM 75 | } 76 | } 77 | 78 | $taskName = $manifest.name 79 | $taskid = $manifest.id 80 | $taskversion = "$($manifest.version.Major).$($manifest.version.Minor).$($manifest.version.Patch)" 81 | $taskZip = "$taskName.$taskid-$taskversion.zip" 82 | 83 | Copy-Item $task "_gen\$taskzip" 84 | Push-Location _tmp 85 | 86 | & "C:\Program Files\7-Zip\7z.exe" u "$outputDir\$taskzip" "*" -r -bd 87 | if ($LASTEXITCODE -ne 0) 88 | { 89 | Remove-Item "$outputDir\$taskzip" 90 | Write-Error "Failed to compress $task" 91 | continue 92 | } 93 | 94 | write-output "Created: $taskzip" 95 | Pop-Location 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /extensions/nuget-deprecated/fix.ps1: -------------------------------------------------------------------------------- 1 | Get-ChildItem -Recurse -Filter "* *" -Path "./_tasks/" | Rename-Item -NewName { $_.Name -replace " ", "_" } 2 | 3 | # Reorder inputs in task.json files so that visibleRule references only point to inputs declared above them 4 | # This is required for publishing tasks to the Visual Studio Marketplace 5 | # Inputs are moved only as far as needed - just above the first input that references them 6 | 7 | function Reorder-TaskJsonInputs { 8 | param ( 9 | [string]$TaskJsonPath 10 | ) 11 | 12 | $content = Get-Content -Path $TaskJsonPath -Raw 13 | $task = $content | ConvertFrom-Json 14 | 15 | if (-not $task.inputs -or $task.inputs.Count -eq 0) { 16 | return 17 | } 18 | 19 | $inputs = [System.Collections.ArrayList]::new(@($task.inputs)) 20 | $inputNames = @($inputs | ForEach-Object { $_.name }) 21 | 22 | # Iteratively fix ordering issues until none remain 23 | $maxIterations = $inputs.Count * $inputs.Count # Prevent infinite loops 24 | $iteration = 0 25 | $changed = $true 26 | 27 | while ($changed -and $iteration -lt $maxIterations) { 28 | $changed = $false 29 | $iteration++ 30 | 31 | for ($i = 0; $i -lt $inputs.Count; $i++) { 32 | $currentInput = $inputs[$i] 33 | 34 | # Find dependencies from visibleRule 35 | $deps = @() 36 | if ($currentInput.visibleRule) { 37 | $ruleText = $currentInput.visibleRule 38 | foreach ($inputName in $inputNames) { 39 | # Check if this input name appears in the visibleRule as a referenced input 40 | if ($ruleText -match "(?:^|[&|]\s*)$([regex]::Escape($inputName))\s*[!=]") { 41 | $deps += $inputName 42 | } 43 | } 44 | } 45 | 46 | foreach ($depName in $deps) { 47 | # Find the position of the dependency 48 | $depIndex = -1 49 | for ($j = 0; $j -lt $inputs.Count; $j++) { 50 | if ($inputs[$j].name -eq $depName) { 51 | $depIndex = $j 52 | break 53 | } 54 | } 55 | 56 | # If dependency is after this input, move it just before this input 57 | if ($depIndex -gt $i) { 58 | $depInput = $inputs[$depIndex] 59 | $inputs.RemoveAt($depIndex) 60 | $inputs.Insert($i, $depInput) 61 | $changed = $true 62 | Write-Host " Moving '$depName' before '$($currentInput.name)'" 63 | break # Restart the check from the beginning 64 | } 65 | } 66 | 67 | if ($changed) { break } 68 | } 69 | } 70 | 71 | if ($iteration -ge $maxIterations) { 72 | Write-Warning "Max iterations reached for $TaskJsonPath - possible circular dependency" 73 | return 74 | } 75 | 76 | # Check if order actually changed from original 77 | $originalInputs = @($task.inputs) 78 | $orderChanged = $false 79 | for ($i = 0; $i -lt $originalInputs.Count; $i++) { 80 | if ($originalInputs[$i].name -ne $inputs[$i].name) { 81 | $orderChanged = $true 82 | break 83 | } 84 | } 85 | 86 | if ($orderChanged) { 87 | Write-Host "Reordering inputs in: $TaskJsonPath" 88 | $task.inputs = $inputs.ToArray() 89 | $task | ConvertTo-Json -Depth 100 | Set-Content -Path $TaskJsonPath -Encoding UTF8 90 | } 91 | } 92 | 93 | # Process all task.json files in _tasks directory 94 | Get-ChildItem -Recurse -Filter "task.json" -Path "./_tasks/" | ForEach-Object { 95 | Reorder-TaskJsonInputs -TaskJsonPath $_.FullName 96 | } 97 | 98 | # Also process task.loc.json files if they exist 99 | Get-ChildItem -Recurse -Filter "task.loc.json" -Path "./_tasks/" | ForEach-Object { 100 | Reorder-TaskJsonInputs -TaskJsonPath $_.FullName 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Pipelines Tasks Zips & Extensions 🗜️📁 2 | 3 | This repository contains a pre-built version of the built-in tasks of Azure DevOps. In case you need to install an updated version into Team Foundation Server or Azure DevOps Server, you can use these zips. 4 | 5 | You can download the tasks from the Releases in this repository. [You'll find two kinds of task zips in the releases](https://github.com/jessehouwing/azure-pipelines-tasks-zips/releases/latest). 6 | 7 | The releases are named after the [current release milestone of the azure-pipelines-tasks repo](https://github.com/microsoft/azure-pipelines-tasks/branches/all?query=releases%2Fm). 8 | 9 | ## TaskName.guid-1.200.71.zip 10 | 11 | This zip contains a verbatim copy of the task. They are downloaded directly from my Azure DevOps organisation and are published unchanged. 12 | 13 | ## TaskName-sxs.guid.1.200.71.zip 14 | 15 | This zip contains a patched copy of the task. These tasks can be installed side-by-side with the built-in tasks. All tasks have a new unique id and the task's name has been post-pended with `-sxs`. 16 | 17 | # Installation 18 | 19 | [Download the task from the latest release from this repository](https://github.com/jessehouwing/azure-pipelines-tasks-zips/releases). To install these tasks into your Team Foundation Server / Azure DevOps Server use `tfx`: 20 | 21 | ``` 22 | npm install -g tfx-cli 23 | tfx build tasks upload --task-zip-path Task.guid-version.zip --service-url https://yourtfs.com/tfs/DefaultCollection 24 | ``` 25 | 26 | Or [this PowerShell script](./scripts/install-task.ps1): 27 | 28 | ``` 29 | . ./script/install-task.ps1 -CollectionUrl https://yourtfs.com/tfs/DefaultCollection -TaskZip Task.guid-version.zip 30 | ``` 31 | # Extension 32 | 33 | A few tasks seem to be getting the most demand. I've added a pre-built extension for those and also published these to the marketplace: 34 | 35 | * 🛍️ [AppCenter for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.appcenter) 36 | * 🛍️ [Apple Xcode for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.Apple-Xcode) 37 | * 🛍️ [DotNetCore 6 and 7 for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.dotnetcore) 38 | * 🛍️ [Nuget (Deprecated)](https://marketplace.visualstudio.com/items?itemName=jessehouwing.nuget-deprecated) 39 | * 🛍️ [Pre and post script tasks](https://marketplace.visualstudio.com/items?itemName=jessehouwing.pre-post-tasks) 40 | * 🛍️ [Visual Studio 2022 for Azure DevOps Server](https://marketplace.visualstudio.com/items?itemName=jessehouwing.visualstudio) 41 | 42 | These extensions install the side-by-side version into your Azure DevOps Server. 43 | 44 | # Required agent version 45 | 46 | You will need to [install a recent agent (2.195.0 or newer) from the azure-pipelines-agent repository](https://github.com/microsoft/azure-pipelines-agent/releases) for it to auto-detect Visual Studio 2022, or alternatively add the capabilities to the agent manually. 47 | 48 | You may need to force Azure DevOps Server to not downgrade back to its preferred agent version. You can do so by setting the following environment variable at the system level on your server before launching the agent: 49 | 50 | ``` 51 | AZP_AGENT_DOWNGRADE_DISABLED=true 52 | ``` 53 | 54 | # Maximum extension size 55 | 56 | For on-premise installations there is a maximum extension size configured for the internal marketplace. Some of the extensions might not meet the configured maximum. If this is the case you'll receive an error message similar to the following: 57 | 58 | ``` 59 | Upload Error 60 | 61 | The extension package size '38060652 bytes' exceeds the maximum package size '26214400 bytes' 62 | ``` 63 | 64 | To work around this issue, you can [use the installation method that pushes the task-zips directly](#installation) or increase the configured maximum by running the following SQL statement against your Azure DevOps Server Configuration database using a size that is bigger than the one reported in the error message: 65 | 66 | ``` 67 | DECLARE @keyvalues dbo.typ_keyvaluepairstringtablenullable; 68 | 69 | INSERT @keyvalues 70 | VALUES ('#\Configuration\Service\Gallery\LargeExtensionUpload\MaxPackageSizeMB\', '100') 71 | 72 | exec prc_UpdateRegistry 1, @keyvalues 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '20 19 * * 1' 21 | 22 | # Reject all permissions by default 23 | permissions: {} 24 | 25 | jobs: 26 | analyze: 27 | name: Analyze (${{ matrix.language }}) 28 | # Runner size impacts CodeQL analysis time. To learn more, please see: 29 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 30 | # - https://gh.io/supported-runners-and-hardware-resources 31 | # - https://gh.io/using-larger-runners (GitHub.com only) 32 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 33 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 34 | permissions: 35 | # required for all workflows 36 | security-events: write 37 | 38 | # required to fetch internal or private CodeQL packs 39 | packages: read 40 | 41 | # only required for workflows in private repositories 42 | actions: read 43 | contents: read 44 | 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | include: 49 | - language: actions 50 | build-mode: none 51 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' 52 | # Use `c-cpp` to analyze code written in C, C++ or both 53 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 54 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 55 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 56 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 57 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 58 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 59 | steps: 60 | - name: Checkout repository 61 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 62 | 63 | # Add any setup steps before running the `github/codeql-action/init` action. 64 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 65 | # or others). This is typically only required for manual builds. 66 | # - name: Setup runtime (example) 67 | # uses: actions/setup-example@v1 68 | 69 | # Initializes the CodeQL tools for scanning. 70 | - name: Initialize CodeQL 71 | uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 72 | with: 73 | languages: ${{ matrix.language }} 74 | build-mode: ${{ matrix.build-mode }} 75 | # If you wish to specify custom queries, you can do so here or in a config file. 76 | # By default, queries listed here will override any specified in a config file. 77 | # Prefix the list here with "+" to use these queries and those in the config file. 78 | 79 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 80 | # queries: security-extended,security-and-quality 81 | 82 | # If the analyze step fails for one of the languages you are analyzing with 83 | # "We were unable to automatically build your code", modify the matrix above 84 | # to set the build mode to "manual" for that language. Then modify this step 85 | # to build your code. 86 | # ℹ️ Command-line programs to run using the OS shell. 87 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 88 | - if: matrix.build-mode == 'manual' 89 | shell: bash 90 | run: | 91 | echo 'If you are using a "manual" build mode for one or more of the' \ 92 | 'languages you are analyzing, replace this with the commands to build' \ 93 | 'your code, for example:' 94 | echo ' make bootstrap' 95 | echo ' make release' 96 | exit 1 97 | 98 | - name: Perform CodeQL Analysis 99 | uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 100 | with: 101 | category: "/language:${{matrix.language}}" 102 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Azure Pipelines Tasks Zips Repository 2 | 3 | Azure Pipelines Tasks Zips is a PowerShell-based build system that downloads Azure DevOps pipeline tasks and packages them into direct task zips, side-by-side (sxs) versions with new GUIDs, and Visual Studio marketplace extensions (.vsix files). 4 | 5 | Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here. 6 | 7 | ## Working Effectively 8 | 9 | ### Prerequisites and Dependencies 10 | Install these tools in order before attempting to build: 11 | - `pwsh` (PowerShell Core 7.4+) - REQUIRED for all build scripts 12 | - `7z` (7-zip) - REQUIRED for task zip manipulation: `apt-get install p7zip-full` 13 | - `npm` and `node` (Node 20+) - REQUIRED for tfx-cli: Install via NodeSource or package manager 14 | - `tfx-cli` - REQUIRED for extension creation: `npm install -g tfx-cli@0.21.3` 15 | - `gh` (GitHub CLI) - REQUIRED for release operations: Install via package manager 16 | 17 | ### Environment Variables 18 | Set these environment variables before running build scripts: 19 | - `AZURE_DEVOPS_PAT` - Personal Access Token for downloading tasks from Azure DevOps (required for download.ps1) 20 | - `GITHUB_TOKEN` - GitHub token for release operations (required for upload-releases.ps1) 21 | - WITHOUT these tokens, download.ps1 and upload-releases.ps1 will fail 22 | 23 | ### Build Process Overview 24 | The main build process (build.ps1) orchestrates these scripts in sequence: 25 | 1. **download.ps1** - Downloads tasks from Azure DevOps (requires AZURE_DEVOPS_PAT) - takes 5-15 minutes. NEVER CANCEL. Set timeout to 30+ minutes. 26 | 2. **generate-sxs.ps1** - Creates side-by-side versions with modified GUIDs/names - takes ~16 seconds per task 27 | 3. **generate-deprecated.ps1** - Generates deprecated NuGet task versions - takes ~16 seconds per applicable task 28 | 4. **generate-pre-post.ps1** - Generates pre/post job versions of Bash/CmdLine/PowerShell tasks - takes ~22 seconds per 2 tasks 29 | 5. **prepare-extensions.ps1** - Packages tasks into VSIX extensions - takes 30-60 minutes. NEVER CANCEL. Set timeout to 90+ minutes. 30 | 6. **upload-releases.ps1** - Uploads to GitHub releases (requires GITHUB_TOKEN) 31 | 32 | ### Platform Compatibility 33 | **Windows**: All scripts work as-is using paths like `"C:\Program Files\7-Zip\7z.exe"` 34 | 35 | **Linux**: Scripts require these modifications for cross-platform compatibility: 36 | - Replace `mkdir "_gen" -force` with `New-Item "_gen" -ItemType Directory -Force` 37 | - Replace `"C:\Program Files\7-Zip\7z.exe"` with `"7z"` 38 | - Replace backslash paths with forward slashes: `"$outputDir\$file"` → `"$($outputDir.FullName)/$file"` 39 | 40 | ### Essential Build Commands 41 | Run these commands in order for a complete build: 42 | 43 | ```powershell 44 | # CRITICAL: Set required environment variables first 45 | $env:AZURE_DEVOPS_PAT = "your-azure-devops-pat" 46 | $env:GITHUB_TOKEN = "your-github-token" 47 | 48 | # Full build process (Windows-specific paths): 49 | pwsh -File build.ps1 # Takes 45-90 minutes total. NEVER CANCEL. Set timeout to 120+ minutes. 50 | 51 | # Individual component testing (Linux-compatible): 52 | pwsh -Command '. ./UUIDv5.ps1; . ./generate-sxs.ps1' # ~16 seconds per task 53 | pwsh -Command '. ./UUIDv5.ps1; . ./generate-deprecated.ps1' # ~16 seconds per task 54 | pwsh -Command '. ./UUIDv5.ps1; . ./generate-pre-post.ps1' # ~22 seconds per 2 tasks 55 | 56 | # Extension creation: 57 | pwsh -File prepare-extensions.ps1 # 30-60 minutes. NEVER CANCEL. Set timeout to 90+ minutes. 58 | ``` 59 | 60 | ## Validation 61 | 62 | ### Always Test After Changes 63 | - **NEVER CANCEL long-running builds** - Download takes 5-15 minutes, extension preparation takes 30-60 minutes, full build takes 45-90 minutes 64 | - **Build verification**: Run `pwsh -File build.ps1` and wait for complete success before committing changes 65 | - **Task transformation verification**: Check that generated files in `_gen/` have correctly modified task names, IDs, and friendly names 66 | - **Extension verification**: Verify `.vsix` files are created in `_vsix/` directory and can be listed with `7z l filename.vsix` 67 | 68 | ### Manual Validation Scenarios 69 | After making changes, always validate these scenarios: 70 | 1. **Task transformation**: Extract a generated task zip and verify the `task.json` has new GUID, modified name with "-sxs" suffix, and updated friendlyName 71 | 2. **Extension creation**: Create a test extension with `tfx extension create` and verify it packages successfully (~0.4 seconds) 72 | 3. **UUID generation**: Run UUIDv5.ps1 and verify it loads without errors (~4 seconds) 73 | 74 | ### Testing Commands 75 | ```powershell 76 | # Verify PowerShell and dependencies 77 | pwsh --version # Should be 7.4+ 78 | 7z # Should show 7-zip help 79 | npm --version && node --version # Should show versions 80 | tfx --version # Should show TFS CLI v0.21.3+ 81 | gh --version # Should show GitHub CLI version 82 | 83 | # Test UUID generation 84 | pwsh -File UUIDv5.ps1 # ~0.6 seconds, should load without errors 85 | 86 | # Test task transformation with sample task 87 | # (Create test task zip first, then run transformation scripts) 88 | 89 | # Test extension creation 90 | cd extensions/appcenter 91 | tfx extension create --manifests vss-extension.json vss-extension.public.json # ~0.4 seconds 92 | ``` 93 | 94 | ## Common Tasks 95 | 96 | ### Repository Structure 97 | Key files and directories: 98 | - **build.ps1** - Main orchestration script 99 | - **download.ps1** - Downloads tasks from Azure DevOps 100 | - **generate-*.ps1** - Transform tasks (sxs, deprecated, pre-post) 101 | - **UUIDv5.ps1** - UUID generation utility (must be sourced before other scripts) 102 | - **prepare-extensions.ps1** - Creates marketplace extensions 103 | - **extensions/** - Extension definitions (appcenter, apple-xcode, dotnetcore, etc.) 104 | - **scripts/install-task.ps1** - PowerShell script for installing individual tasks 105 | - **_download/** - Downloaded task zips (created during build) 106 | - **_gen/** - Generated transformed task zips (created during build) 107 | - **_vsix/** - Generated extension files (created during build) 108 | 109 | ### Workflow Integration 110 | The repository uses GitHub Actions (`.github/workflows/publish-tasks.yml`) that: 111 | - Runs on Windows (`windows-latest`) 112 | - Executes the full build process 113 | - Publishes extensions to Visual Studio Marketplace 114 | - Uses secrets: `AZURE_DEVOPS_PAT`, `GITHUB_TOKEN`, `AZURE_MARKETPLACE_PAT` 115 | 116 | ### Extension Configuration 117 | Extensions are defined in `extensions/*/` directories with: 118 | - **tasks.json** - Lists task names and prefixes/postfixes 119 | - **vss-extension.json** - Base extension manifest 120 | - **vss-extension.public.json** - Public marketplace settings 121 | - **fix.ps1** - Optional post-processing script 122 | - **overview.md** - Extension description 123 | - Icon files (icon-default.png, icon-large.png) 124 | 125 | Available extensions: appcenter, apple-xcode, dotnetcore, generic, nuget-deprecated, pre-post-tasks, visualstudio 126 | 127 | ### Build Artifacts and Validation 128 | The build process creates these key artifacts: 129 | - **_download/TaskName.guid-version.zip** - Original tasks from Azure DevOps 130 | - **_gen/TaskName-sxs.newguid-version.zip** - Side-by-side versions with new GUIDs 131 | - **_gen/TaskName-deprecated.newguid-version.zip** - Deprecated versions (NuGet tasks only) 132 | - **_gen/Pre-TaskName.newguid-version.zip** - Pre-job versions (Bash/CmdLine/PowerShell) 133 | - **_gen/Post-TaskName.newguid-version.zip** - Post-job versions (Bash/CmdLine/PowerShell) 134 | - **_vsix/jessehouwing.extensionname-version.vsix** - Marketplace extensions 135 | 136 | ### Critical Validation Steps 137 | After any build, verify these artifacts: 138 | 1. **Task transformation verification**: Extract any `-sxs` task and confirm task.json has new GUID and modified name 139 | 2. **Extension packaging verification**: List .vsix contents with `7z l filename.vsix` to ensure _tasks directory is included 140 | 3. **Build completeness verification**: Check all expected files exist in `_gen/` and `_vsix/` directories 141 | 142 | ### Task Installation 143 | Install individual tasks using: 144 | ```powershell 145 | # Using PowerShell script: 146 | ./scripts/install-task.ps1 -CollectionUrl https://yourtfs.com/tfs/DefaultCollection -TaskZip Task.guid-version.zip 147 | 148 | # Using tfx-cli: 149 | npm install -g tfx-cli 150 | tfx build tasks upload --task-zip-path Task.guid-version.zip --service-url https://yourtfs.com/tfs/DefaultCollection 151 | ``` 152 | 153 | ## Error Handling and Troubleshooting 154 | 155 | ### Common Build Failures 156 | - **"mkdir: invalid option"** on Linux: Use `New-Item -ItemType Directory -Force` instead of `mkdir -force` 157 | - **"7z command not found"**: Install with `apt-get install p7zip-full` (Linux) or install 7-Zip (Windows) 158 | - **"tfx command not found"**: Install with `npm install -g tfx-cli@0.21.3` 159 | - **HTTP 401 errors during download**: Check `AZURE_DEVOPS_PAT` environment variable 160 | - **GitHub API errors**: Check `GITHUB_TOKEN` environment variable 161 | - **Extension creation failures**: Verify task.json has required fields like `instanceNameFormat` 162 | 163 | ### Build Time Expectations 164 | - **UUIDv5.ps1 loading**: ~0.6 seconds 165 | - **Single task transformation**: ~16 seconds 166 | - **Pre/Post generation (2 tasks)**: ~22 seconds 167 | - **Extension creation**: ~0.4 seconds per extension 168 | - **Full download process**: 5-15 minutes - NEVER CANCEL 169 | - **Extension preparation**: 30-60 minutes - NEVER CANCEL 170 | - **Complete build**: 45-90 minutes - NEVER CANCEL 171 | 172 | ### Performance Notes 173 | - Build scripts use PowerShell parallel processing (`ForEach-Object -Parallel`) 174 | - 7-zip operations are throttled to prevent resource exhaustion 175 | - Extension creation processes multiple task versions simultaneously 176 | - Always allow sufficient time for completion - premature cancellation corrupts the build state 177 | 178 | ### Recovery from Failed Builds 179 | ```powershell 180 | # Clean build directories 181 | Remove-Item _download, _gen, _tmp, _vsix -Recurse -Force -ErrorAction SilentlyContinue 182 | 183 | # Re-run specific build phases 184 | pwsh -File download.ps1 # Re-download tasks 185 | pwsh -File generate-sxs.ps1 # Re-generate side-by-side versions 186 | pwsh -File prepare-extensions.ps1 # Re-create extensions 187 | ``` 188 | 189 | Always verify environment variables are set before retrying failed builds. -------------------------------------------------------------------------------- /prepare-extensions.ps1: -------------------------------------------------------------------------------- 1 | [string[]] $existingReleases = & gh release list --repo jessehouwing/azure-pipelines-tasks-zips --limit 500 | Select-String "m\d+-tasks" | % { $_.Matches.Value } 2 | $allAssets = @() 3 | foreach ($release in $existingReleases) { 4 | $releaseDetails = & gh release view --repo jessehouwing/azure-pipelines-tasks-zips $release --json name,tagName,assets | ConvertFrom-Json 5 | $allAssets = $allAssets + $releaseDetails.assets 6 | } 7 | 8 | $org = "jessehouwing-brazil" 9 | $pat = $env:AZURE_DEVOPS_PAT 10 | 11 | 12 | $url = "https://dev.azure.com/$org" 13 | $header = @{authorization = "Basic $([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(".:$pat")))" } 14 | 15 | $tasks = Invoke-RestMethod -Uri "$url/_apis/distributedtask/tasks?allversions=true" -Method Get -ContentType "application/json" -Headers $header | ConvertFrom-Json -AsHashtable 16 | 17 | $taskMetadata = $tasks.value 18 | 19 | $forceUpdate = $false 20 | if ($env:FORCE_UPDATE -and $env:FORCE_UPDATE -eq "true") { 21 | $forceUpdate = $true 22 | } 23 | 24 | & npm install tfx-cli@^0.22 -g --silent --no-progress 25 | 26 | if (Test-Path "_vsix") { 27 | Remove-Item "_vsix" -force -Recurse | Out-Null 28 | } 29 | New-Item -ItemType Directory -Path "_vsix" -Force | Out-Null 30 | 31 | function get-extensions { 32 | return Get-ChildItem -Path .\extensions -Directory 33 | } 34 | 35 | function get-extensiontasks { 36 | param( 37 | [string] $extensionId 38 | ) 39 | 40 | return (get-content -raw "./extensions/$extensionId/tasks.json" | ConvertFrom-Json).tasks 41 | } 42 | 43 | function expand-taskprepostfixes { 44 | param( 45 | [string] $extensionId, 46 | [string] $taskname 47 | ) 48 | 49 | $tasks = get-content -raw "./extensions/$extensionId/tasks.json" | ConvertFrom-Json 50 | 51 | $result = @() 52 | 53 | foreach ($prefix in $tasks.prefixes) { 54 | $result += "$prefix-$taskname" 55 | } 56 | 57 | foreach ($postfix in $tasks.postfixes) { 58 | $result += "$taskname-$postfix" 59 | } 60 | 61 | return $result 62 | } 63 | 64 | function get-versionsfortask { 65 | param( 66 | [string] $taskName 67 | ) 68 | 69 | # find all tasks with the given name 70 | $tasks = $taskMetadata | Where-Object { $_.name -eq $taskName } 71 | 72 | # find all major versions for that task 73 | $majorversions = $tasks | foreach-object { $_.version.major } | Select-Object -Unique 74 | 75 | # find the latest version for each major version 76 | $result = $majorversions | ForEach-Object { 77 | $majorversion = $_ 78 | 79 | $tasks | 80 | where-object { $_.version.major -eq $majorversion } | 81 | where-object { ([int]$_.version.minor) -ge 100 } | 82 | sort-object { [version]"$($_.version.major).$($_.version.minor).$($_.version.patch)" } | 83 | select-object -last 1 84 | } 85 | 86 | return $result 87 | } 88 | 89 | function calculate-version { 90 | param( 91 | [object[]] $versions 92 | ) 93 | 94 | $maxminorversion = ($versions | measure-object -maximum { ([version]$_).Minor }).Maximum 95 | $maxbuildversion = ($versions | where-object { ([version]$_).Minor -eq $maxminorversion } | measure-object -maximum { ([version]$_).Build }).Maximum 96 | $count = ($versions | where-object { 97 | (([version]$_).Minor -eq $maxminorversion) -and 98 | (([version]$_).Build -eq $maxbuildversion) 99 | }).Count 100 | return "$maxminorversion.$maxbuildversion.$count" 101 | } 102 | 103 | function get-marketplace-version { 104 | param( 105 | [string] $extensionId, 106 | [string] $publisher = "jessehouwing" 107 | ) 108 | 109 | try { 110 | $json = & tfx extension show --publisher $publisher --extension-id $extensionId --json --token $env:AZURE_DEVOPS_PAT | ConvertFrom-Json 111 | if ($json.versions -and $json.versions.Count -gt 0) { 112 | return $json.versions.version | sort-object { [version]"$_" } | select-object -last 1 113 | } 114 | } 115 | catch { 116 | Write-Host "Could not retrieve marketplace version for $publisher.$extensionId (may not exist or network issue)" 117 | } 118 | 119 | return $null 120 | } 121 | 122 | function get-forced-extension-version { 123 | param( 124 | [string] $extensionId, 125 | [string] $extensionVersion 126 | ) 127 | if ($forceUpdate) { 128 | $marketplaceVersion = get-marketplace-version -extensionId $extensionId 129 | if ($marketplaceVersion -and ([version]$extensionVersion -le [version]$marketplaceVersion)) { 130 | [version]$newVersion = [version]$marketplaceVersion 131 | if ($newVersion.Revision -le 0) { 132 | $revision = 1 133 | } 134 | else { 135 | $revision = $newVersion.Revision + 1 136 | } 137 | $newVersion = [version]"$($newVersion.Major).$($newVersion.Minor).$($newVersion.Build).$revision" 138 | $extensionVersion = $newVersion.ToString() 139 | Write-Host "Forcing update, new version is $extensionVersion" 140 | } 141 | } 142 | return $extensionVersion 143 | } 144 | 145 | function should-skip-extension-creation { 146 | param( 147 | [string] $extensionId, 148 | [string] $extensionVersion, 149 | [string] $publisher = "jessehouwing" 150 | ) 151 | 152 | $marketplaceVersion = get-marketplace-version -extensionId $extensionId -publisher $publisher 153 | 154 | if ($marketplaceVersion -and ([version]$marketplaceVersion -ge [version]$extensionVersion)) { 155 | Write-Host "Extension $publisher.$extensionId version $extensionVersion already exists in marketplace, skipping creation" 156 | return $true 157 | } 158 | 159 | if ($marketplaceVersion) { 160 | Write-Host "Extension $publisher.$extensionId marketplace version: $marketplaceVersion, building version: $extensionVersion" 161 | } 162 | else { 163 | Write-Host "Extension $publisher.$extensionId not found in marketplace or network issue, proceeding with creation" 164 | } 165 | 166 | return $false 167 | } 168 | 169 | $extensions = get-extensions 170 | foreach ($extension in $extensions) { 171 | Remove-Item -Recurse "extensions/$($extension.Name)/_tasks" -Force -ErrorAction SilentlyContinue 172 | $tasks = get-extensiontasks -extensionId $extension.Name 173 | if ($tasks.Count -eq 0) { 174 | continue 175 | } 176 | 177 | $extensionManifest = ConvertFrom-Json -InputObject (get-content -raw "./extensions/vss-extension.json") 178 | $extensionManifest.contributions = @() 179 | 180 | $taskversions = @() 181 | foreach ($task in $tasks) { 182 | $versions = get-versionsfortask -taskName $task 183 | 184 | foreach ($version in $versions) { 185 | $taskVersion = "$($version.version.major).$($version.version.minor).$($version.version.patch)" 186 | $taskversions += $taskVersion 187 | } 188 | } 189 | $extensionVersion = calculate-version -versions $taskversions 190 | $extensionVersion = get-forced-extension-version -extensionId $extension.Name -extensionVersion $extensionVersion 191 | 192 | 193 | $skipMainExtension = should-skip-extension-creation -extensionId "$($extension.Name)" -extensionVersion $extensionVersion 194 | $skipDebugExtension = should-skip-extension-creation -extensionId "$($extension.Name)-debug" -extensionVersion $extensionVersion 195 | 196 | if ($skipMainExtension -and $skipDebugExtension) { 197 | Write-Output "Both main and debug extensions for $($extension.Name) are up to date, skipping creation" 198 | continue 199 | } 200 | 201 | foreach ($task in $tasks) { 202 | foreach ($taskName in expand-taskprepostfixes -extensionId $($extension.Name) -taskName $task) { 203 | $versions = get-versionsfortask -taskName $task 204 | 205 | foreach ($version in $versions) { 206 | $taskVersion = "$($version.version.major).$($version.version.minor).$($version.version.patch)" 207 | $taskzip = $allAssets | Where-Object { $_.name -ilike "$taskName.*-$taskVersion.zip" } | Select-Object -First 1 208 | if (-not $taskzip) { 209 | $taskzip = dir "./_gen/$taskName.*-$taskVersion.zip" | Select-Object -First 1 210 | $filePath = "./_gen/$($taskzip.Name)" 211 | } 212 | else { 213 | $filePath = "./_gen/$($taskzip.name)" 214 | if (-not (Test-Path -PathType leaf -Path $filePath)) { 215 | & gh release download --repo jessehouwing/azure-pipelines-tasks-zips "m$($version.version.minor)-tasks" --pattern "$taskName.*-$taskVersion.zip" --dir ./_gen 216 | } 217 | } 218 | 219 | Expand-Archive -Path $filePath -DestinationPath "extensions/$($extension.Name)/_tasks/$taskName/v$taskVersion" 220 | write-output "Added: $taskName/v$taskVersion" 221 | } 222 | 223 | # Hack to fixup contributionIds that can't be changed. 224 | $contributionId = $taskName 225 | $contributionId = $contributionId -replace "^(Pre|Post)-(CmdLine|PowerShell)$","`$0V2" 226 | $contributionId = $contributionId -replace "^(Pre|Post)-(Bash)$","`$0V3" 227 | 228 | $extensionManifest.contributions += [ordered] @{ 229 | "id" = "$contributionId" 230 | "type" = "ms.vss-distributed-task.task" 231 | "targets" = @("ms.vss-distributed-task.tasks") 232 | "properties" = @{ 233 | "name" = "_tasks/$taskName" 234 | } 235 | } 236 | } 237 | } 238 | 239 | # fix-up paths and files 240 | if (test-path -PathType Leaf -path "extensions/$($extension.Name)/fix.ps1") { 241 | Push-Location "extensions/$($extension.Name)" 242 | & .\fix.ps1 243 | Pop-Location 244 | } 245 | 246 | $extensionManifest.version = $extensionVersion 247 | 248 | $extensionManifest | ConvertTo-Json -depth 100 | Out-File "extensions/$($extension.Name)/vss-extension.tasks.json" -Encoding utf8NoBOM 249 | Copy-Item .\extensions\vss-extension.debug.json "extensions/$($extension.Name)" 250 | Copy-Item .\LICENSE "extensions/$($extension.Name)" 251 | Copy-Item .\PRIVACY.md "extensions/$($extension.Name)" 252 | 253 | Push-Location "extensions/$($extension.Name)" 254 | 255 | if (-not $skipMainExtension) { 256 | Write-Output "Creating main extension: jessehouwing.$($extension.Name)-$extensionVersion.vsix" 257 | & tfx extension create --extension-id "$($extension.Name)" --manifests "vss-extension.json" "vss-extension.public.json" "vss-extension.tasks.json" --output-path "..\..\_vsix\_jessehouwing.$($extension.Name)-$extensionVersion.vsix" 258 | } 259 | 260 | if (-not $skipDebugExtension) { 261 | Write-Output "Creating debug extension: jessehouwing.$($extension.Name)-debug-$extensionVersion.vsix" 262 | & tfx extension create --extension-id "$($extension.Name)-debug" --manifests "vss-extension.json" "vss-extension.debug.json" "vss-extension.tasks.json" --output-path "..\..\_vsix\_jessehouwing.$($extension.Name)-debug-$extensionVersion.vsix" 263 | } 264 | 265 | Pop-Location 266 | } 267 | --------------------------------------------------------------------------------