├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── renovate.json └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE.md ├── PRIVACY.md ├── README.md ├── azure-pipelines.yml ├── eslint.config.mjs ├── extension ├── extension-overview.md └── images │ ├── Screenshots │ ├── In-workflow.png │ ├── expandvariables.png │ └── setvariable.png │ ├── donate.png │ ├── extension-icon.png │ ├── icons-small.psd │ └── icons.psd ├── package-lock.json ├── package.json ├── vss-extension.json ├── vsts-variable-expand └── v1 │ ├── icon.png │ ├── ps_modules │ └── VstsTaskSdk │ │ ├── FindFunctions.ps1 │ │ ├── InputFunctions.ps1 │ │ ├── LocalizationFunctions.ps1 │ │ ├── LoggingCommandFunctions.ps1 │ │ ├── LongPathFunctions.ps1 │ │ ├── OutFunctions.ps1 │ │ ├── ServerOMFunctions.ps1 │ │ ├── Strings │ │ └── resources.resjson │ │ │ ├── de-de │ │ │ └── resources.resjson │ │ │ ├── en-US │ │ │ └── resources.resjson │ │ │ ├── es-es │ │ │ └── resources.resjson │ │ │ ├── fr-fr │ │ │ └── resources.resjson │ │ │ ├── it-IT │ │ │ └── resources.resjson │ │ │ ├── ja-jp │ │ │ └── resources.resjson │ │ │ ├── ko-KR │ │ │ └── resources.resjson │ │ │ ├── ru-RU │ │ │ └── resources.resjson │ │ │ ├── zh-CN │ │ │ └── resources.resjson │ │ │ └── zh-TW │ │ │ └── resources.resjson │ │ ├── ToolFunctions.ps1 │ │ ├── TraceFunctions.ps1 │ │ ├── VstsTaskSdk.psd1 │ │ └── lib.json │ ├── task.json │ ├── vsts-variable-expand.v1.ps1 │ └── vsts-variable-expand.v3.ps1 ├── vsts-variable-set ├── v1 │ ├── icon.png │ ├── package-lock.json │ ├── package.json │ ├── ps_modules │ │ └── VstsTaskSdk │ │ │ ├── FindFunctions.ps1 │ │ │ ├── InputFunctions.ps1 │ │ │ ├── LegacyFindFunctions.ps1 │ │ │ ├── LocalizationFunctions.ps1 │ │ │ ├── LoggingCommandFunctions.ps1 │ │ │ ├── LongPathFunctions.ps1 │ │ │ ├── Minimatch.dll │ │ │ ├── OutFunctions.ps1 │ │ │ ├── PSGetModuleInfo.xml │ │ │ ├── ServerOMFunctions.ps1 │ │ │ ├── Strings │ │ │ └── resources.resjson │ │ │ │ ├── de-de │ │ │ │ └── resources.resjson │ │ │ │ ├── en-US │ │ │ │ └── resources.resjson │ │ │ │ ├── es-es │ │ │ │ └── resources.resjson │ │ │ │ ├── fr-fr │ │ │ │ └── resources.resjson │ │ │ │ ├── it-IT │ │ │ │ └── resources.resjson │ │ │ │ ├── ja-jp │ │ │ │ └── resources.resjson │ │ │ │ ├── ko-KR │ │ │ │ └── resources.resjson │ │ │ │ ├── ru-RU │ │ │ │ └── resources.resjson │ │ │ │ ├── zh-CN │ │ │ │ └── resources.resjson │ │ │ │ └── zh-TW │ │ │ │ └── resources.resjson │ │ │ ├── ToolFunctions.ps1 │ │ │ ├── TraceFunctions.ps1 │ │ │ ├── VstsTaskSdk.dll │ │ │ ├── VstsTaskSdk.psd1 │ │ │ └── lib.json │ ├── task.json │ ├── tsconfig.json │ ├── vsts-variable-set.ts │ ├── vsts-variable-set.v1.ps1 │ └── vsts-variable-set.v3.ps1 ├── v2 │ ├── icon.png │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tsconfig.json │ └── vsts-variable-set.ts └── v3 │ ├── icon.png │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tsconfig.json │ └── vsts-variable-set.ts └── vsts-variable-transform ├── v1 ├── icon.png ├── package-lock.json ├── package.json ├── task.json ├── tsconfig.json └── vsts-variable-transform.ts ├── v2 ├── icon.png ├── package-lock.json ├── package.json ├── task.json ├── tsconfig.json └── vsts-variable-transform.ts └── v3 ├── .taskkey ├── icon.png ├── package-lock.json ├── package.json ├── task.json ├── tsconfig.json └── vsts-variable-transform.ts /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jessehouwing 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | groups: 12 | ESLint: 13 | patterns: 14 | - esbuild 15 | - eslint 16 | - "@typescript-eslint/*" 17 | - "@eslint/*" 18 | - typescript-eslint 19 | - package-ecosystem: "npm" 20 | directory: "/vsts-variable-set/v1" 21 | schedule: 22 | interval: "weekly" 23 | ignore: 24 | - dependency-name: "azure-pipelines-task-lib" 25 | update-types: ["version-update:semver-major"] 26 | - dependency-name: "@types/node" 27 | update-types: ["version-update:semver-major"] 28 | - package-ecosystem: "npm" 29 | directory: "/vsts-variable-set/v2" 30 | schedule: 31 | interval: "weekly" 32 | ignore: 33 | - dependency-name: "azure-pipelines-task-lib" 34 | update-types: ["version-update:semver-major"] 35 | - dependency-name: "@types/node" 36 | update-types: ["version-update:semver-major"] 37 | - package-ecosystem: "npm" 38 | directory: "/vsts-variable-set/v3" 39 | schedule: 40 | interval: "weekly" 41 | ignore: 42 | - dependency-name: "@types/node" 43 | update-types: ["version-update:semver-major"] 44 | - package-ecosystem: "npm" 45 | directory: "/vsts-variable-transform/v1" 46 | schedule: 47 | interval: "weekly" 48 | ignore: 49 | - dependency-name: "azure-pipelines-task-lib" 50 | update-types: ["version-update:semver-major"] 51 | - dependency-name: "@types/node" 52 | update-types: ["version-update:semver-major"] 53 | - package-ecosystem: "npm" 54 | directory: "/vsts-variable-transform/v2" 55 | schedule: 56 | interval: "weekly" 57 | ignore: 58 | - dependency-name: "azure-pipelines-task-lib" 59 | update-types: ["version-update:semver-major"] 60 | - dependency-name: "@types/node" 61 | update-types: ["version-update:semver-major"] 62 | - package-ecosystem: "npm" 63 | directory: "/vsts-variable-transform/v3" 64 | schedule: 65 | interval: "weekly" 66 | ignore: 67 | - dependency-name: "azure-pipelines-task-lib" 68 | update-types: ["version-update:semver-major"] 69 | - dependency-name: "@types/node" 70 | update-types: ["version-update:semver-major"] 71 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>jessehouwing/.github:renovate-config" 5 | ], 6 | "azure-pipelines": { 7 | "enabled": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "rangeStrategy": "bump", 12 | "matchDatasources": ["npm"] 13 | }, 14 | { 15 | "packageNames": [ 16 | "azure-pipelines-task-lib", 17 | "@types/node" 18 | ], 19 | "updateTypes": ["major"], 20 | "enabled": false 21 | }, 22 | { 23 | "matchDatasources": ["azure-pipelines-tasks"], 24 | "extractVersion": "^(?\\d+)" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.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" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '25 3 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | *.psm1 4 | !/vsts-*-shared/*.psm1 5 | .last* 6 | task.json.last* 7 | *.vsix 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | # Build Results of an ATL Project 36 | [Dd]ebugPS/ 37 | [Rr]eleasePS/ 38 | dlldata.c 39 | # DNX 40 | project.lock.json 41 | artifacts/ 42 | *_i.c 43 | *_p.c 44 | *_i.h 45 | *.ilk 46 | *.meta 47 | *.obj 48 | *.pch 49 | *.pdb 50 | *.pgc 51 | *.pgd 52 | *.rsp 53 | *.sbr 54 | *.tlb 55 | *.tli 56 | *.tlh 57 | *.tmp 58 | *.tmp_proj 59 | *.log 60 | *.vspscc 61 | *.vssscc 62 | .builds 63 | *.pidb 64 | *.svclog 65 | *.scc 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opendb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | *.vspx 80 | *.sap 81 | # TFS 2012 Local Workspace 82 | $tf/ 83 | # Guidance Automation Toolkit 84 | *.gpState 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | # JustCode is a .NET coding add-in 90 | .JustCode 91 | # TeamCity is a build add-in 92 | _TeamCity* 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | # NCrunch 96 | _NCrunch_* 97 | .*crunch*.local.xml 98 | nCrunchTemp_* 99 | # MightyMoose 100 | *.mm.* 101 | AutoTest.Net/ 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | # Installshield output folder 105 | [Ee]xpress/ 106 | # DocProject is a documentation generator add-in 107 | DocProject/buildhelp/ 108 | DocProject/Help/*.HxT 109 | DocProject/Help/*.HxC 110 | DocProject/Help/*.hhc 111 | DocProject/Help/*.hhk 112 | DocProject/Help/*.hhp 113 | DocProject/Help/Html2 114 | DocProject/Help/html 115 | # Click-Once directory 116 | publish/ 117 | # Publish Web Output 118 | *.[Pp]ublish.xml 119 | *.azurePubxml 120 | # TODO: Comment the next line if you want to checkin your web deploy settings 121 | # but database connection strings (with potential passwords) will be unencrypted 122 | *.pubxml 123 | *.publishproj 124 | # NuGet Packages 125 | *.nupkg 126 | # The packages folder can be ignored because of Package Restore 127 | **/packages/* 128 | # except build/, which is used as an MSBuild target. 129 | !**/packages/build/ 130 | # Uncomment if necessary however generally it will be regenerated when needed 131 | #!**/packages/repositories.config 132 | # NuGet v3's project.json files produces more ignoreable files 133 | *.nuget.props 134 | *.nuget.targets 135 | # Microsoft Azure Build Output 136 | csx/ 137 | *.build.csdef 138 | # Microsoft Azure Emulator 139 | ecf/ 140 | rcf/ 141 | # Microsoft Azure ApplicationInsights config file 142 | ApplicationInsights.config 143 | # Windows Store app package directory 144 | AppPackages/ 145 | BundleArtifacts/ 146 | # Visual Studio cache files 147 | # files ending in .cache can be ignored 148 | *.[Cc]ache 149 | # but keep track of directories ending in .cache 150 | !*.[Cc]ache/ 151 | # Others 152 | ClientBin/ 153 | ~$* 154 | *~ 155 | *.dbmdl 156 | *.dbproj.schemaview 157 | *.pfx 158 | *.publishsettings 159 | node_modules/ 160 | orleans.codegen.cs 161 | # RIA/Silverlight projects 162 | Generated_Code/ 163 | # Backup & report files from converting an old project file 164 | # to a newer Visual Studio version. Backup files are not needed, 165 | # because we have git ;-) 166 | _UpgradeReport_Files/ 167 | Backup*/ 168 | UpgradeLog*.XML 169 | UpgradeLog*.htm 170 | # SQL Server files 171 | *.mdf 172 | *.ldf 173 | # Business Intelligence projects 174 | *.rdl.data 175 | *.bim.layout 176 | *.bim_*.settings 177 | # Microsoft Fakes 178 | FakesAssemblies/ 179 | # GhostDoc plugin setting file 180 | *.GhostDoc.xml 181 | # Node.js Tools for Visual Studio 182 | .ntvs_analysis.dat 183 | # Visual Studio 6 build log 184 | *.plg 185 | # Visual Studio 6 workspace options file 186 | *.opt 187 | # Visual Studio LightSwitch build output 188 | **/*.HTMLClient/GeneratedArtifacts 189 | **/*.DesktopClient/GeneratedArtifacts 190 | **/*.DesktopClient/ModelManifest.xml 191 | **/*.Server/GeneratedArtifacts 192 | **/*.Server/ModelManifest.xml 193 | _Pvt_Extensions 194 | # Paket dependency manager 195 | .paket/paket.exe 196 | # FAKE - F# Make 197 | .fake/ 198 | 199 | /typings 200 | /.cr 201 | /*/v[0-9]/*.js 202 | /*/v[0-9]/*.js.map 203 | tsconfig.tsbuildinfo 204 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | ## Copyright (c) 2016-2019 Jesse Houwing 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | Third Party software 23 | 24 | This Azure Pipelines extension depends on: 25 | 26 | * azure-pipelines-task-lib copyright Microsoft licensed under MIT 27 | * adaptions based on stripslashes copyright Locutus licensed under MIT 28 | * adaptions based on addslashes copyright Locutus licensed under MIT -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | > **19-09-2023** 4 | > - Added the ability to lift an enviroment variable from the agent to a workflow variable. 5 | 6 | > **05-12-2022** 7 | > - Changed: Upped version to 3.* 8 | > - Updated to `Azure-pipelines-task-lib@4` 9 | > - Updated to Node 16 (available in `@3`) 10 | > - Removed Node 6 support from v3 (available for backwards-compat through `@2`) 11 | > - Dropped dependency on core-js for v3. 12 | 13 | > **19-10-2021** 14 | > - Added: Ability to update ReleaseName with the new command string required in Azure DevOps 2020. 15 | > - Added: Backwards compat option for Azure DevOps 2019 and below 16 | > - Changed: Upped version number to 2.* 17 | 18 | > **15-04-2020** 19 | > - Fixed: [#48 Update to latest task-lib contains a breaking change](https://github.com/jessehouwing/azure-pipelines-variable-tasks/issues/48) 20 | > - Added: "Use Tasklib" option under Advanced section to control escape behavior introduced in [recent tasklib](https://github.com/microsoft/azure-pipelines-task-lib/commits/master/node/taskcommand.ts) 21 | > - Updated: moved to latest Task SDK and dependencies 22 | 23 | > **23-01-2018** 24 | > - Changed: Re-implemented Variable-Set in v3 PowerShell handler for better performance. 25 | > - Fixed: Pad-left and Pad-right now uses core-js library. 26 | > - Updated: moved to latest Task SDK and dependencies. 27 | 28 | > **17-11-2017** 29 | > - Fixed: Set Build.BuldNumber doesn't work on most recent Windows Build agents. Updated to VSTS-Task-Lib 2.1.0 to fix. 30 | 31 | > **1-9-2016** 32 | > - Fixed: Expand Variable Task throws an error on Agent 2.x, you can now remove this task, it is no longer needed. 33 | The new agent will automatically expand all variables. 34 | 35 | > **20-5-2016** 36 | > - Removed: Preview flag 37 | 38 | > **19-5-2016** 39 | > - Fixed: Replace with "" would result in "nullOriginalValue" 40 | 41 | 42 | # Description 43 | 44 | This Extension contains a slowly growing collection of tasks that help you manipulate and (soon) validate the values of build variables. 45 | 46 | # Set Variable 47 | Have you ever wanted to change the value of a variable between multiple build steps? Simply add the **Set Variable** task to your workflow and tell it which value you want to assign to which variable. 48 | 49 | You can use the value of other build variables to setup the value. 50 | 51 | > **Set: 'Build.DropLocation' to '\\\\share\drops\$(Build.DefinitionName)\$(Build.BuildNumber)'** 52 | > 53 | > * *Variablename*: `Build.DropLocation` 54 | > * *Value*: `\\share\drops\$(Build.DefinitionName)\$(Build.BuildNumber)` 55 | 56 | By assigning to the `Build.BuildNumber` variable, the build number of the Build will be updated/overwritten. 57 | 58 | # Transform value and assign to Variable 59 | If you need to do more advanced transformations of your values, use the transform task. You can use it to encode/decode the value and apply a number of simpe string manipulations, including Search & Replace, Change Case, Trim, Pad etc. 60 | 61 | You can use the value of other build variables to setup the value. 62 | 63 | > **Transform: 'your value here' and assign to Variable: VariableName'** 64 | > 65 | > * *Input Value*: `\\share\drops\$(Build.DefinitionName)\$(Build.BuildNumber)` 66 | > * *Variablename*: `Build.DropLocation` 67 | 68 | By assigning to the `Build.BuildNumber` variable, the build number of the Build will be updated/overwritten. 69 | 70 | You can apply the following manipulations (they'll be exectuted in the specified order): 71 | 72 | > **Manipulation** 73 | > 74 | > * [x] *Search & Replace* 75 | > * Use: `Basic` 76 | > * Search: `$Build.DefinitionName` 77 | > * Replacement: `%%Placeholder%%` 78 | > * [x] Trim 79 | > * [ ] Slice 80 | > * [ ] Substring 81 | > * [ ] Change Case 82 | > * [ ] Pad 83 | 84 | And finally you can transform (encode/decode) the string using: 85 | 86 | > * Base64 87 | > * Uri 88 | > * UriComponent 89 | > * AddSlashes / StripSlashes 90 | 91 | # Expand Variables [DEPRECATED] 92 | Have you ever wanted to use the value from one variable in another variable? Unfortunately, that's not possible with the standard Variables screen. 93 | 94 | | Variable | Value | 95 | | -------------------- | ------------------------------------------------------------ | 96 | | Build.DropLocation | \\\\share\drops\$(Build.DefinitionName)\$(Build.BuildNumber) | 97 | 98 | Will simply send the literal text to the tasks in your workflow. 99 | 100 | Add the Expand Variable(s) task to the top of your build steps and it will take care of the expansion for you. It even supportes multiple levels of nested variables! 101 | 102 | > **Expand variable: 'Build.DropLocation'** 103 | > 104 | > * *Variablename(s)*: `Build.DropLocation` 105 | 106 | Will expand your drop location variable to: 107 | 108 | | Variable | Value | 109 | | -------------------- | ------------------------------------------------------------ | 110 | | Build.DropLocation | \\\\share\drops\My Definition\My Definition_1.2.123 | 111 | 112 | And to make your life easier it now supports simply expanding all your variables! 113 | 114 | > **Expand variable: '*'** 115 | > 116 | > * *Variablename(s)*: `*` 117 | 118 | 119 | # Documentation 120 | 121 | Please check the [Wiki](https://github.com/jessehouwing/vsts-variable-tasks/wiki). 122 | 123 | If you like this extension, please leave a review and feedback. If you'd have suggestions or an issue, please [file an issue to give me a chance to fix it](https://github.com/jessehouwing/vsts-variable-tasks/issues). 124 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: '3.0$(rev:.r)' 2 | 3 | trigger: 4 | - main 5 | - features/* 6 | 7 | pr: 8 | - main 9 | 10 | pool: 11 | vmImage: 'windows-latest' 12 | 13 | variables: 14 | - name: 'extensionId' 15 | value: 'jessehouwing-vsts-variable-tasks' 16 | - name: 'tfxVersion' 17 | value: 'builtin' 18 | - name: 'nodeVersion' 19 | value: '18.x' 20 | 21 | stages: 22 | - stage: 'Build' 23 | displayName: 'Build' 24 | jobs: 25 | - job: 26 | displayName: 'Build' 27 | steps: 28 | - task: NodeTool@0 29 | inputs: 30 | versionSpec: $(nodeVersion) 31 | displayName: 'Install Node.js' 32 | 33 | - task: replacetokens@6 34 | inputs: 35 | sources: '**/task.json' 36 | encoding: 'utf-8' 37 | tokenPattern: 'default' 38 | addBOM: false 39 | 40 | - script: | 41 | npm install 42 | 43 | - script: | 44 | npm run initdev 45 | 46 | - script: | 47 | npm run build 48 | 49 | - task: TfxInstaller@5 50 | displayName: 'Use Node CLI for Azure DevOps' 51 | inputs: 52 | version: $(tfxVersion) 53 | checkLatest: true 54 | 55 | - task: PackageAzureDevOpsExtension@5 56 | displayName: 'Package Extension: $(Build.SourcesDirectory)' 57 | name: 'packageStep' 58 | inputs: 59 | rootFolder: '$(Build.SourcesDirectory)' 60 | outputPath: '$(Build.ArtifactStagingDirectory)\$(extensionId).vsix' 61 | publisherId: 'jessehouwing' 62 | extensionId: $(extensionId) 63 | extensionTag: '-build' 64 | extensionName: 'Variable Toolbox' 65 | extensionVersion: '$(Build.BuildNumber)' 66 | updateTasksVersion: true 67 | updateTasksVersionType: patch 68 | extensionVisibility: private 69 | 70 | - task: PowerShell@2 71 | inputs: 72 | targetType: 'inline' 73 | script: | 74 | md _tmp 75 | md _output 76 | 77 | Expand-Archive -Path $env:vsix -DestinationPath .\_tmp 78 | $manifest = gc .\_tmp\extension.vsomanifest | ConvertFrom-Json 79 | 80 | foreach ($contribution in $manifest.contributions | ?{ $_.type -eq "ms.vss-distributed-task.task"}) 81 | { 82 | $path = join-path -path ".\_tmp\" -childpath $contribution.properties.name 83 | $versions = Get-ChildItem -Attributes Directory -Path $path 84 | foreach ($version in $versions | %{$_.name}) 85 | { 86 | $taskpath = join-path -path $path -childpath $version 87 | $taskmanifestPath = (join-path -path $taskpath -childpath "task.json") 88 | $taskManifest = gc $taskmanifestPath | ConvertFrom-Json -AsHashTable 89 | $taskName = $taskManifest.name 90 | $output = join-path -path ".\_output" -childpath "$taskName$($version.ToUpper()).zip" 91 | 92 | Compress-Archive -Path $taskpath\* -DestinationPath $output 93 | } 94 | } 95 | pwsh: true 96 | workingDirectory: '$(Build.SourcesDirectory)' 97 | env: 98 | vsix: '$(Build.ArtifactStagingDirectory)\$(extensionId).vsix' 99 | 100 | - task: PublishPipelineArtifact@1 101 | displayName: 'Publish vsix' 102 | inputs: 103 | publishLocation: pipeline 104 | targetPath: '$(packageStep.Extension.OutputPath)' 105 | artifact: 'vsix' 106 | condition: succeededOrFailed() 107 | 108 | - task: PublishPipelineArtifact@1 109 | displayName: 'Publish tasks' 110 | inputs: 111 | publishLocation: pipeline 112 | targetPath: '$(Build.SourcesDirectory)\_output' 113 | artifact: 'tasks' 114 | condition: succeededOrFailed() 115 | 116 | - stage: PublishDev 117 | displayName: 'Publish privately' 118 | condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) 119 | dependsOn: 'Build' 120 | jobs: 121 | - deployment: 122 | environment: Publisher JesseHouwing (privately) 123 | strategy: 124 | runOnce: 125 | deploy: 126 | steps: 127 | - task: NodeTool@0 128 | inputs: 129 | versionSpec: $(nodeVersion) 130 | displayName: 'Install Node.js' 131 | 132 | - task: TfxInstaller@5 133 | displayName: 'Use Node CLI for Azure DevOps' 134 | inputs: 135 | version: $(tfxVersion) 136 | checkLatest: true 137 | 138 | - task: PublishAzureDevOpsExtension@5 139 | name: 'publishDev' 140 | inputs: 141 | connectTo: 'AzureRM' 142 | connectedServiceNameAzureRM: 'azure-devops-marketplace' 143 | fileType: 'vsix' 144 | vsixFile: '$(Pipeline.Workspace)/vsix/$(extensionId).vsix' 145 | publisherId: 'jessehouwing' 146 | extensionId: '$(extensionId)' 147 | extensionTag: '-dev' 148 | updateTasksVersion: false 149 | extensionVisibility: 'privatepreview' 150 | shareWith: 'jessehouwing-dev' 151 | noWaitValidation: true 152 | 153 | - task: IsAzureDevOpsExtensionValid@5 154 | inputs: 155 | connectTo: 'AzureRM' 156 | connectedServiceNameAzureRM: 'azure-devops-marketplace' 157 | method: 'vsix' 158 | vsixFile: '$(publishDev.Extension.OutputPath)' 159 | 160 | 161 | - task: PublishPipelineArtifact@1 162 | displayName: 'Publish vsix' 163 | inputs: 164 | publishLocation: pipeline 165 | targetPath: '$(publishDev.Extension.OutputPath)' 166 | artifact: 'dev' 167 | condition: succeededOrFailed() 168 | 169 | - stage: PublishProd 170 | displayName: 'Publish publicly' 171 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) 172 | dependsOn: 'PublishDev' 173 | jobs: 174 | - deployment: 175 | environment: Publisher JesseHouwing (publicly) 176 | strategy: 177 | runOnce: 178 | deploy: 179 | steps: 180 | 181 | - task: NodeTool@0 182 | inputs: 183 | versionSpec: $(nodeVersion) 184 | displayName: 'Install Node.js' 185 | 186 | - task: TfxInstaller@5 187 | displayName: 'Use Node CLI for Azure DevOps' 188 | inputs: 189 | version: $(tfxVersion) 190 | checkLatest: true 191 | 192 | - task: PublishAzureDevOpsExtension@5 193 | name: 'publishProd' 194 | inputs: 195 | connectTo: 'AzureRM' 196 | connectedServiceNameAzureRM: 'azure-devops-marketplace' 197 | fileType: 'vsix' 198 | vsixFile: '$(Pipeline.Workspace)/vsix/$(extensionId).vsix' 199 | publisherId: 'jessehouwing' 200 | extensionId: $(extensionId) 201 | updateTasksVersion: false 202 | extensionVisibility: 'public' 203 | noWaitValidation: true 204 | 205 | - task: IsAzureDevOpsExtensionValid@5 206 | inputs: 207 | connectTo: 'AzureRM' 208 | connectedServiceNameAzureRM: 'azure-devops-marketplace' 209 | method: 'vsix' 210 | vsixFile: '$(publishProd.Extension.OutputPath)' 211 | 212 | - task: PublishPipelineArtifact@1 213 | displayName: 'Publish vsix' 214 | inputs: 215 | publishLocation: pipeline 216 | targetPath: '$(publishProd.Extension.OutputPath)' 217 | artifact: 'prod' 218 | condition: succeededOrFailed() 219 | 220 | - task: GitHubRelease@1 221 | inputs: 222 | gitHubConnection: 'GitHub - jessehouwing' 223 | repositoryName: '$(Build.Repository.Name)' 224 | action: 'create' 225 | target: '$(Build.SourceVersion)' 226 | tagSource: 'userSpecifiedTag' 227 | tag: 'v$(Build.BuildNumber)' 228 | title: 'v$(Build.BuildNumber)' 229 | releaseNotesSource: 'inline' 230 | assets: | 231 | $(publishProd.Extension.OutputPath)* 232 | $(Pipeline.Workspace)/tasks/* 233 | changeLogCompareToRelease: 'lastFullRelease' 234 | changeLogType: 'issueBased' 235 | changeLogLabels: '[{ "state" : "closed" }]' 236 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import globals from "globals"; 3 | import tsParser from "@typescript-eslint/parser"; 4 | import path from "node:path"; 5 | import { fileURLToPath } from "node:url"; 6 | import js from "@eslint/js"; 7 | import { FlatCompat } from "@eslint/eslintrc"; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all 15 | }); 16 | 17 | export default [ 18 | ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), 19 | { 20 | plugins: { 21 | "@typescript-eslint": typescriptEslint, 22 | }, 23 | 24 | languageOptions: { 25 | globals: { 26 | ...globals.browser, 27 | ...globals.commonjs, 28 | }, 29 | 30 | parser: tsParser, 31 | parserOptions: { 32 | tsconfigRootDir: __dirname, 33 | sourceType: "module", 34 | }, 35 | ecmaVersion: 11, 36 | sourceType: "script", 37 | }, 38 | 39 | rules: { 40 | ...typescriptEslint.configs.recommended.rules, 41 | ...typescriptEslint.configs["recommended-requiring-type-checking"].rules, 42 | "@typescript-eslint/await-thenable": "error", 43 | "@typescript-eslint/no-floating-promises": "error", 44 | "@typescript-eslint/explicit-module-boundary-types": "off", 45 | "@typescript-eslint/no-explicit-any": "off", 46 | "no-control-regex": "off", 47 | }, 48 | }, 49 | ]; -------------------------------------------------------------------------------- /extension/extension-overview.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/extension-overview.md -------------------------------------------------------------------------------- /extension/images/Screenshots/In-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/Screenshots/In-workflow.png -------------------------------------------------------------------------------- /extension/images/Screenshots/expandvariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/Screenshots/expandvariables.png -------------------------------------------------------------------------------- /extension/images/Screenshots/setvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/Screenshots/setvariable.png -------------------------------------------------------------------------------- /extension/images/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/donate.png -------------------------------------------------------------------------------- /extension/images/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/extension-icon.png -------------------------------------------------------------------------------- /extension/images/icons-small.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/icons-small.psd -------------------------------------------------------------------------------- /extension/images/icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/extension/images/icons.psd -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jessehouwing-vsts-variable-tasks", 3 | "description": "Azure Pipelines Variable Toolbox", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/jessehouwing/azure-pipelines-variable-tasks" 7 | }, 8 | "keywords": [ 9 | "vsts", 10 | "tfs", 11 | "azure-devops", 12 | "azure-pipelines" 13 | ], 14 | "scripts": { 15 | "initdev": "npm run initdev:npm", 16 | "initdev:npm": "npm run initdev:npm:base & npm run initdev:npm:tasks", 17 | "initdev:npm:base": "npm install --no-progress --no-update-notifier", 18 | "initdev:npm:tasks": "glob-exec --parallel --foreach \"vsts-variable-*/*/tsconfig.json\" -- \"cd {{file.dir}} && npm install --no-update-notifier --no-progress\"", 19 | "compile:tasks": "glob-exec \"vsts-variable-*/*/tsconfig.json\" -- \"tsc -b {{files.join(' ')}}\"", 20 | "postcompile:tasks": "npm run lint:tasks", 21 | "cleanup:tasks": "glob-exec --parallel --foreach \"vsts-variable-*/*/tsconfig.json\" -- \"cd {{file.dir}} && npm dedupe && npm prune --production\"", 22 | "lint:tasks": "glob-exec --parallel --foreach \"vsts-variable-*/*/tsconfig.json\" -- \"eslint {{file.dir}}\\**\\*.ts --parser-options \"{'project':['{{file}}']}\"", 23 | "package:tasks": "tfx extension create --root . --output-path dist --manifest-globs vss-extension.json", 24 | "build": "npm run build:tasks", 25 | "build:clean": "npm run clean && npm run initdev && npm run build", 26 | "build:tasks": "npm run compile:tasks && npm run cleanup:tasks", 27 | "package": "npm run build:clean && npm run package:tasks", 28 | "clean": "git clean -fdX" 29 | }, 30 | "author": "Jesse Houwing", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@eslint/eslintrc": "^3.3.1", 34 | "@eslint/js": "^9.37.0", 35 | "@types/core-js": "^2.5.8", 36 | "@typescript-eslint/eslint-plugin": "^8.45.0", 37 | "@typescript-eslint/parser": "^8.45.0", 38 | "eslint": "^9.37.0", 39 | "glob-exec": "^0.1.1", 40 | "globals": "^16.4.0", 41 | "tfx-cli": "^0.22.1", 42 | "typescript": "^5.9.3", 43 | "typescript-eslint": "^8.45.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "id": "jessehouwing-vsts-variable-tasks", 4 | "name": "Variable Toolbox", 5 | "version": "3.0.0", 6 | "publisher": "jessehouwing", 7 | "targets": [ 8 | { 9 | "id": "Microsoft.VisualStudio.Services" 10 | } 11 | ], 12 | "scope": [ 13 | "vso.build" 14 | ], 15 | "description": "Azure Pipelines tasks for setting and expanding variables.", 16 | "categories": [ 17 | "Azure Pipelines" 18 | ], 19 | "tags": [ 20 | "Extension", 21 | "Release", 22 | "Build", 23 | "Variable", 24 | "Expand", 25 | "Override", 26 | "xebia" 27 | ], 28 | "screenshots": [ 29 | { 30 | "path": "extension/Images/Screenshots/in-workflow.png" 31 | }, 32 | { 33 | "path": "extension/Images/Screenshots/setvariable.png" 34 | }, 35 | { 36 | "path": "extension/Images/Screenshots/expandvariables.png" 37 | 38 | } 39 | ], 40 | "content": { 41 | "details": { 42 | "path": "readme.md" 43 | }, 44 | "license": { 45 | "path": "LICENSE.md" 46 | }, 47 | "privacy": { 48 | "path": "PRIVACY.md" 49 | } 50 | }, 51 | "links": { 52 | "getstarted": { 53 | "uri": "https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki" 54 | }, 55 | "support": { 56 | "uri": "https://github.com/jessehouwing/azure-pipelines-variable-tasks/issues" 57 | }, 58 | "license": { 59 | "uri": "https://github.com/jessehouwing/azure-pipelines-variable-tasks/LICENSE.md" 60 | }, 61 | "privacypolicy": { 62 | "uri": "https://github.com/jessehouwing/azure-pipelines-variable-tasks/blob/main/PRIVACY.md" 63 | } 64 | }, 65 | "badges": [ 66 | { 67 | "href": "https://github.com/sponsors/jessehouwing", 68 | "uri": "https://img.shields.io/github/sponsors/jessehouwing", 69 | "description": "GitHub Sponsors" 70 | }, 71 | { 72 | "href": "//gitter.im/jessehouwing/vsts-variable-tasks?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge", 73 | "uri": "//badges.gitter.im/jessehouwing/vsts-variable-tasks.svg", 74 | "description": "Chat" 75 | }, 76 | { 77 | "href": "https://jessehouwing.visualstudio.com/vsts-extensions/vsts-extensions%20Team/_build/index?definitionId=47&_a=completed", 78 | "uri": "https://jessehouwing.visualstudio.com/_apis/public/build/definitions/a88536a2-a889-45a3-a955-ddf1af8aeba1/47/badge", 79 | "description": "Build" 80 | } 81 | ], 82 | "repository": { 83 | "type": "git", 84 | "uri": "https://github.com/jessehouwing/azure-pipelines-variable-tasks" 85 | }, 86 | "branding": { 87 | "color": "rgb(36, 43, 50)", 88 | "theme": "dark" 89 | }, 90 | "icons": { 91 | "default": "extension/images/extension-icon.png" 92 | }, 93 | "files": [ 94 | { 95 | "path": "vsts-variable-set/" 96 | }, 97 | { 98 | "path": "vsts-variable-expand/" 99 | }, 100 | { 101 | "path": "vsts-variable-transform/" 102 | } 103 | ], 104 | "contributions": [ 105 | { 106 | "id": "vsts-variable-transform", 107 | "type": "ms.vss-distributed-task.task", 108 | "targets": [ 109 | "ms.vss-distributed-task.tasks" 110 | ], 111 | "properties": { 112 | "name": "vsts-variable-transform" 113 | } 114 | }, 115 | { 116 | "id": "vsts-variable-expand", 117 | "type": "ms.vss-distributed-task.task", 118 | "targets": [ 119 | "ms.vss-distributed-task.tasks" 120 | ], 121 | "properties": { 122 | "name": "vsts-variable-expand" 123 | } 124 | }, 125 | { 126 | "id": "vsts-variable-set", 127 | "type": "ms.vss-distributed-task.task", 128 | "targets": [ 129 | "ms.vss-distributed-task.tasks" 130 | ], 131 | "properties": { 132 | "name": "vsts-variable-set" 133 | } 134 | } 135 | ] 136 | } 137 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-expand/v1/icon.png -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/FindFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Finds files or directories. 4 | 5 | .DESCRIPTION 6 | Finds files or directories using advanced pattern matching. 7 | 8 | .PARAMETER LiteralDirectory 9 | Directory to search. 10 | 11 | .PARAMETER LegacyPattern 12 | Proprietary pattern format. The LiteralDirectory parameter is used to root any unrooted patterns. 13 | 14 | Separate multiple patterns using ";". Escape actual ";" in the path by using ";;". 15 | "?" indicates a wildcard that represents any single character within a path segment. 16 | "*" indicates a wildcard that represents zero or more characters within a path segment. 17 | "**" as the entire path segment indicates a recursive search. 18 | "**" within a path segment indicates a recursive intersegment wildcard. 19 | "+:" (can be omitted) indicates an include pattern. 20 | "-:" indicates an exclude pattern. 21 | 22 | The result is from the command is a union of all the matches from the include patterns, minus the matches from the exclude patterns. 23 | 24 | .PARAMETER IncludeFiles 25 | Indicates whether to include files in the results. 26 | 27 | If neither IncludeFiles or IncludeDirectories is set, then IncludeFiles is assumed. 28 | 29 | .PARAMETER IncludeDirectories 30 | Indicates whether to include directories in the results. 31 | 32 | If neither IncludeFiles or IncludeDirectories is set, then IncludeFiles is assumed. 33 | 34 | .PARAMETER Force 35 | Indicates whether to include hidden items. 36 | 37 | .EXAMPLE 38 | Find-VstsFiles -LegacyPattern "C:\Directory\Is?Match.txt" 39 | 40 | Given: 41 | C:\Directory\Is1Match.txt 42 | C:\Directory\Is2Match.txt 43 | C:\Directory\IsNotMatch.txt 44 | 45 | Returns: 46 | C:\Directory\Is1Match.txt 47 | C:\Directory\Is2Match.txt 48 | 49 | .EXAMPLE 50 | Find-VstsFiles -LegacyPattern "C:\Directory\Is*Match.txt" 51 | 52 | Given: 53 | C:\Directory\IsOneMatch.txt 54 | C:\Directory\IsTwoMatch.txt 55 | C:\Directory\NonMatch.txt 56 | 57 | Returns: 58 | C:\Directory\IsOneMatch.txt 59 | C:\Directory\IsTwoMatch.txt 60 | 61 | .EXAMPLE 62 | Find-VstsFiles -LegacyPattern "C:\Directory\**\Match.txt" 63 | 64 | Given: 65 | C:\Directory\Match.txt 66 | C:\Directory\NotAMatch.txt 67 | C:\Directory\SubDir\Match.txt 68 | C:\Directory\SubDir\SubSubDir\Match.txt 69 | 70 | Returns: 71 | C:\Directory\Match.txt 72 | C:\Directory\SubDir\Match.txt 73 | C:\Directory\SubDir\SubSubDir\Match.txt 74 | 75 | .EXAMPLE 76 | Find-VstsFiles -LegacyPattern "C:\Directory\**" 77 | 78 | Given: 79 | C:\Directory\One.txt 80 | C:\Directory\SubDir\Two.txt 81 | C:\Directory\SubDir\SubSubDir\Three.txt 82 | 83 | Returns: 84 | C:\Directory\One.txt 85 | C:\Directory\SubDir\Two.txt 86 | C:\Directory\SubDir\SubSubDir\Three.txt 87 | 88 | .EXAMPLE 89 | Find-VstsFiles -LegacyPattern "C:\Directory\Sub**Match.txt" 90 | 91 | Given: 92 | C:\Directory\IsNotAMatch.txt 93 | C:\Directory\SubDir\IsAMatch.txt 94 | C:\Directory\SubDir\IsNot.txt 95 | C:\Directory\SubDir\SubSubDir\IsAMatch.txt 96 | C:\Directory\SubDir\SubSubDir\IsNot.txt 97 | 98 | Returns: 99 | C:\Directory\SubDir\IsAMatch.txt 100 | C:\Directory\SubDir\SubSubDir\IsAMatch.txt 101 | #> 102 | function Find-Files { 103 | [CmdletBinding()] 104 | param( 105 | [ValidateNotNullOrEmpty()] 106 | [Parameter()] 107 | [string]$LiteralDirectory, 108 | [Parameter(Mandatory = $true)] 109 | [string]$LegacyPattern, 110 | [switch]$IncludeFiles, 111 | [switch]$IncludeDirectories, 112 | [switch]$Force) 113 | 114 | Trace-EnteringInvocation $MyInvocation 115 | if (!$IncludeFiles -and !$IncludeDirectories) { 116 | $IncludeFiles = $true 117 | } 118 | 119 | $includePatterns = New-Object System.Collections.Generic.List[string] 120 | $excludePatterns = New-Object System.Collections.Generic.List[System.Text.RegularExpressions.Regex] 121 | $LegacyPattern = $LegacyPattern.Replace(';;', "`0") 122 | foreach ($pattern in $LegacyPattern.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)) { 123 | $pattern = $pattern.Replace("`0", ';') 124 | $isIncludePattern = Test-IsIncludePattern -Pattern ([ref]$pattern) 125 | if ($LiteralDirectory -and !([System.IO.Path]::IsPathRooted($pattern))) { 126 | # Use the root directory provided to make the pattern a rooted path. 127 | $pattern = [System.IO.Path]::Combine($LiteralDirectory, $pattern) 128 | } 129 | 130 | # Validate pattern does not end with a \. 131 | if ($pattern[$pattern.Length - 1] -eq [System.IO.Path]::DirectorySeparatorChar) { 132 | throw (Get-LocString -Key PSLIB_InvalidPattern0 -ArgumentList $pattern) 133 | } 134 | 135 | if ($isIncludePattern) { 136 | $includePatterns.Add($pattern) 137 | } else { 138 | $excludePatterns.Add((Convert-PatternToRegex -Pattern $pattern)) 139 | } 140 | } 141 | 142 | $count = 0 143 | foreach ($path in (Get-MatchingItems -IncludePatterns $includePatterns -ExcludePatterns $excludePatterns -IncludeFiles:$IncludeFiles -IncludeDirectories:$IncludeDirectories -Force:$Force)) { 144 | $count++ 145 | $path 146 | } 147 | 148 | Write-Verbose "Total found: $count" 149 | Trace-LeavingInvocation $MyInvocation 150 | } 151 | 152 | ######################################## 153 | # Private functions. 154 | ######################################## 155 | function Convert-PatternToRegex { 156 | [CmdletBinding()] 157 | param([string]$Pattern) 158 | 159 | $Pattern = [regex]::Escape($Pattern.Replace('\', '/')). # Normalize separators and regex escape. 160 | Replace('/\*\*/', '((/.+/)|(/))'). # Replace directory globstar. 161 | Replace('\*\*', '.*'). # Replace remaining globstars with a wildcard that can span directory separators. 162 | Replace('\*', '[^/]*'). # Replace asterisks with a wildcard that cannot span directory separators. 163 | Replace('\?', '.') # Replace single character wildcards. 164 | New-Object regex -ArgumentList "^$Pattern`$", ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 165 | } 166 | 167 | function Get-FileNameFilter { 168 | [CmdletBinding()] 169 | param([string]$Pattern) 170 | 171 | $index = $Pattern.LastIndexOf('\') 172 | if ($index -eq -1 -or # Pattern does not contain a backslash. 173 | !($Pattern = $Pattern.Substring($index + 1)) -or # Pattern ends in a backslash. 174 | $Pattern.Contains('**')) # Last segment contains an inter-segment wildcard. 175 | { 176 | return '*' 177 | } 178 | 179 | return $Pattern 180 | } 181 | 182 | function Get-MatchingItems { 183 | [CmdletBinding()] 184 | param( 185 | [System.Collections.Generic.List[string]]$IncludePatterns, 186 | [System.Collections.Generic.List[regex]]$ExcludePatterns, 187 | [switch]$IncludeFiles, 188 | [switch]$IncludeDirectories, 189 | [switch]$Force) 190 | 191 | Trace-EnteringInvocation $MyInvocation 192 | $allFiles = New-Object System.Collections.Generic.HashSet[string] 193 | foreach ($pattern in $IncludePatterns) { 194 | $pathPrefix = Get-PathPrefix -Pattern $pattern 195 | $fileNameFilter = Get-FileNameFilter -Pattern $pattern 196 | $patternRegex = Convert-PatternToRegex -Pattern $pattern 197 | # Iterate over the directories and files under the pathPrefix. 198 | Get-PathIterator -Path $pathPrefix -Filter $fileNameFilter -IncludeFiles:$IncludeFiles -IncludeDirectories:$IncludeDirectories -Force:$Force | 199 | ForEach-Object { 200 | # Normalize separators. 201 | $normalizedPath = $_.Replace('\', '/') 202 | # **/times/** will not match C:/fun/times because there isn't a trailing slash. 203 | # So try both if including directories. 204 | $alternatePath = "$normalizedPath/" 205 | 206 | $isMatch = $false 207 | if ($patternRegex.IsMatch($normalizedPath) -or ($IncludeDirectories -and $patternRegex.IsMatch($alternatePath))) { 208 | $isMatch = $true 209 | 210 | # Test whether the path should be excluded. 211 | foreach ($regex in $ExcludePatterns) { 212 | if ($regex.IsMatch($normalizedPath) -or ($IncludeDirectories -and $regex.IsMatch($alternatePath))) { 213 | $isMatch = $false 214 | break 215 | } 216 | } 217 | } 218 | 219 | if ($isMatch) { 220 | $null = $allFiles.Add($_) 221 | } 222 | } 223 | } 224 | 225 | Trace-Path -Path $allFiles -PassThru 226 | Trace-LeavingInvocation $MyInvocation 227 | } 228 | 229 | function Get-PathIterator { 230 | [CmdletBinding()] 231 | param( 232 | [string]$Path, 233 | [string]$Filter, 234 | [switch]$IncludeFiles, 235 | [switch]$IncludeDirectories, 236 | [switch]$Force) 237 | 238 | if (!$Path) { 239 | return 240 | } 241 | 242 | if ($IncludeDirectories) { 243 | $Path 244 | } 245 | 246 | Get-DirectoryChildItem -Path $Path -Filter $Filter -Force:$Force -Recurse | 247 | ForEach-Object { 248 | if ($_.Attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) { 249 | if ($IncludeDirectories) { 250 | $_.FullName 251 | } 252 | } elseif ($IncludeFiles) { 253 | $_.FullName 254 | } 255 | } 256 | } 257 | 258 | function Get-PathPrefix { 259 | [CmdletBinding()] 260 | param([string]$Pattern) 261 | 262 | $index = $Pattern.IndexOfAny([char[]]@('*'[0], '?'[0])) 263 | if ($index -eq -1) { 264 | # If no wildcards are found, return the directory name portion of the path. 265 | # If there is no directory name (file name only in pattern), this will return empty string. 266 | return [System.IO.Path]::GetDirectoryName($Pattern) 267 | } 268 | 269 | [System.IO.Path]::GetDirectoryName($Pattern.Substring(0, $index)) 270 | } 271 | 272 | function Test-IsIncludePattern { 273 | [CmdletBinding()] 274 | param( 275 | [Parameter(Mandatory = $true)] 276 | [ref]$Pattern) 277 | 278 | # Include patterns start with +: or anything except -: 279 | # Exclude patterns start with -: 280 | if ($Pattern.value.StartsWith("+:")) { 281 | # Remove the prefix. 282 | $Pattern.value = $Pattern.value.Substring(2) 283 | $true 284 | } elseif ($Pattern.value.StartsWith("-:")) { 285 | # Remove the prefix. 286 | $Pattern.value = $Pattern.value.Substring(2) 287 | $false 288 | } else { 289 | # No prefix, so leave the string alone. 290 | $true; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/LocalizationFunctions.ps1: -------------------------------------------------------------------------------- 1 | $script:resourceStrings = @{ } 2 | 3 | <# 4 | .SYNOPSIS 5 | Gets a localized resource string. 6 | 7 | .DESCRIPTION 8 | Gets a localized resource string and optionally formats the string with arguments. 9 | 10 | If the format fails (due to a bad format string or incorrect expected arguments in the format string), then the format string is returned followed by each of the arguments (delimited by a space). 11 | 12 | If the lookup key is not found, then the lookup key is returned followed by each of the arguments (delimited by a space). 13 | 14 | .PARAMETER Require 15 | Writes an error to the error pipeline if the endpoint is not found. 16 | #> 17 | function Get-LocString { 18 | [CmdletBinding()] 19 | param( 20 | [Parameter(Mandatory = $true, Position = 1)] 21 | [string]$Key, 22 | [Parameter(Position = 2)] 23 | [object[]]$ArgumentList = @( )) 24 | 25 | # Due to the dynamically typed nature of PowerShell, a single null argument passed 26 | # to an array parameter is interpreted as a null array. 27 | if ([object]::ReferenceEquals($null, $ArgumentList)) { 28 | $ArgumentList = @( $null ) 29 | } 30 | 31 | # Lookup the format string. 32 | $format = '' 33 | if (!($format = $script:resourceStrings[$Key])) { 34 | # Warn the key was not found. Prevent recursion if the lookup key is the 35 | # "string resource key not found" lookup key. 36 | $resourceNotFoundKey = 'PSLIB_StringResourceKeyNotFound0' 37 | if ($key -ne $resourceNotFoundKey) { 38 | Write-Warning (Get-LocString -Key $resourceNotFoundKey -ArgumentList $Key) 39 | } 40 | 41 | # Fallback to just the key itself if there aren't any arguments to format. 42 | if (!$ArgumentList.Count) { return $key } 43 | 44 | # Otherwise fallback to the key followed by the arguments. 45 | $OFS = " " 46 | return "$key $ArgumentList" 47 | } 48 | 49 | # Return the string if there aren't any arguments to format. 50 | if (!$ArgumentList.Count) { return $format } 51 | 52 | try { 53 | [string]::Format($format, $ArgumentList) 54 | } catch { 55 | Write-Warning (Get-LocString -Key 'PSLIB_StringFormatFailed') 56 | $OFS = " " 57 | "$format $ArgumentList" 58 | } 59 | } 60 | 61 | <# 62 | .SYNOPSIS 63 | Imports resource strings for use with Get-VstsLocString. 64 | 65 | .DESCRIPTION 66 | Imports resource strings for use with Get-VstsLocString. The imported strings are stored in an internal resource string dictionary. Optionally, if a separate resource file for the current culture exists, then the localized strings from that file then imported (overlaid) into the same internal resource string dictionary. 67 | 68 | Resource strings from the SDK are prefixed with "PSLIB_". This prefix should be avoided for custom resource strings. 69 | 70 | .Parameter LiteralPath 71 | JSON file containing resource strings. 72 | 73 | .EXAMPLE 74 | Import-VstsLocStrings -LiteralPath $PSScriptRoot\Task.json 75 | 76 | Imports strings from messages section in the JSON file. If a messages section is not defined, then no strings are imported. Example messages section: 77 | { 78 | "messages": { 79 | "Hello": "Hello you!", 80 | "Hello0": "Hello {0}!" 81 | } 82 | } 83 | 84 | .EXAMPLE 85 | Import-VstsLocStrings -LiteralPath $PSScriptRoot\Task.json 86 | 87 | Overlays strings from an optional separate resource file for the current culture. 88 | 89 | Given the task variable System.Culture is set to 'de-DE'. This variable is set by the agent based on the current culture for the job. 90 | Given the file Task.json contains: 91 | { 92 | "messages": { 93 | "GoodDay": "Good day!", 94 | } 95 | } 96 | Given the file resources.resjson\de-DE\resources.resjson: 97 | { 98 | "loc.messages.GoodDay": "Guten Tag!" 99 | } 100 | 101 | The net result from the import command would be one new key-value pair added to the internal dictionary: Key = 'GoodDay', Value = 'Guten Tag!' 102 | #> 103 | function Import-LocStrings { 104 | [CmdletBinding()] 105 | param( 106 | [Parameter(Mandatory = $true)] 107 | [string]$LiteralPath) 108 | 109 | # Validate the file exists. 110 | if (!(Test-Path -LiteralPath $LiteralPath -PathType Leaf)) { 111 | Write-Warning (Get-LocString -Key PSLIB_FileNotFound0 -ArgumentList $LiteralPath) 112 | return 113 | } 114 | 115 | # Load the json. 116 | Write-Verbose "Loading resource strings from: $LiteralPath" 117 | $count = 0 118 | if ($messages = (Get-Content -LiteralPath $LiteralPath -Encoding UTF8 | Out-String | ConvertFrom-Json).messages) { 119 | # Add each resource string to the hashtable. 120 | foreach ($member in (Get-Member -InputObject $messages -MemberType NoteProperty)) { 121 | [string]$key = $member.Name 122 | $script:resourceStrings[$key] = $messages."$key" 123 | $count++ 124 | } 125 | } 126 | 127 | Write-Verbose "Loaded $count strings." 128 | 129 | # Get the culture. 130 | $culture = Get-TaskVariable -Name "System.Culture" -Default "en-US" 131 | 132 | # Load the resjson. 133 | $resjsonPath = "$([System.IO.Path]::GetDirectoryName($LiteralPath))\Strings\resources.resjson\$culture\resources.resjson" 134 | if (Test-Path -LiteralPath $resjsonPath) { 135 | Write-Verbose "Loading resource strings from: $resjsonPath" 136 | $count = 0 137 | $resjson = Get-Content -LiteralPath $resjsonPath -Encoding UTF8 | Out-String | ConvertFrom-Json 138 | foreach ($member in (Get-Member -Name loc.messages.* -InputObject $resjson -MemberType NoteProperty)) { 139 | if (!($value = $resjson."$($member.Name)")) { 140 | continue 141 | } 142 | 143 | [string]$key = $member.Name.Substring('loc.messages.'.Length) 144 | $script:resourceStrings[$key] = $value 145 | $count++ 146 | } 147 | 148 | Write-Verbose "Loaded $count strings." 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/OutFunctions.ps1: -------------------------------------------------------------------------------- 1 | # TODO: It would be better if the Out-Default function resolved the underlying Out-Default 2 | # command in the begin block. This would allow for supporting other modules that override 3 | # Out-Default. 4 | $script:outDefaultCmdlet = $ExecutionContext.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\Out-Default") 5 | 6 | ######################################## 7 | # Public functions. 8 | ######################################## 9 | function Out-Default { 10 | [CmdletBinding(ConfirmImpact = "Medium")] 11 | param( 12 | [Parameter(ValueFromPipeline = $true)] 13 | [System.Management.Automation.PSObject]$InputObject) 14 | 15 | begin { 16 | #Write-Host '[Entering Begin Out-Default]' 17 | $__sp = { & $script:outDefaultCmdlet @PSBoundParameters }.GetSteppablePipeline() 18 | $__sp.Begin($pscmdlet) 19 | #Write-Host '[Leaving Begin Out-Default]' 20 | } 21 | 22 | process { 23 | #Write-Host '[Entering Process Out-Default]' 24 | if ($_ -is [System.Management.Automation.ErrorRecord]) { 25 | Write-Verbose -Message 'Error record:' 4>&1 | Out-Default 26 | Write-Verbose -Message (Remove-TrailingNewLine (Out-String -InputObject $_ -Width 2147483647)) 4>&1 | Out-Default 27 | Write-Verbose -Message 'Script stack trace:' 4>&1 | Out-Default 28 | Write-Verbose -Message "$($_.ScriptStackTrace)" 4>&1 | Out-Default 29 | Write-Verbose -Message 'Exception:' 4>&1 | Out-Default 30 | Write-Verbose -Message $_.Exception.ToString() 4>&1 | Out-Default 31 | Write-TaskError -Message $_.Exception.Message 32 | } elseif ($_ -is [System.Management.Automation.WarningRecord]) { 33 | Write-TaskWarning -Message (Remove-TrailingNewLine (Out-String -InputObject $_ -Width 2147483647)) 34 | } elseif ($_ -is [System.Management.Automation.VerboseRecord]) { 35 | foreach ($private:str in (Format-DebugMessage -Object $_)) { 36 | Write-TaskVerbose -Message $private:str 37 | } 38 | } elseif ($_ -is [System.Management.Automation.DebugRecord]) { 39 | foreach ($private:str in (Format-DebugMessage -Object $_)) { 40 | Write-TaskDebug -Message $private:str 41 | } 42 | } else { 43 | # TODO: Consider using out-string here to control the width. As a security precaution it would actually be best to set it to max so wrapping doesn't interfere with secret masking. 44 | $__sp.Process($_) 45 | } 46 | 47 | #Write-Host '[Leaving Process Out-Default]' 48 | } 49 | 50 | end { 51 | #Write-Host '[Entering End Out-Default]' 52 | $__sp.End() 53 | #Write-Host '[Leaving End Out-Default]' 54 | } 55 | } 56 | 57 | ######################################## 58 | # Private functions. 59 | ######################################## 60 | function Format-DebugMessage { 61 | [CmdletBinding()] 62 | param([psobject]$Object) 63 | 64 | $private:str = Out-String -InputObject $Object -Width 2147483647 65 | $private:str = Remove-TrailingNewLine $private:str 66 | "$private:str".Replace("`r`n", "`n").Replace("`r", "`n").Split("`n"[0]) 67 | } 68 | 69 | function Remove-TrailingNewLine { 70 | [CmdletBinding()] 71 | param($Str) 72 | if ([object]::ReferenceEquals($Str, $null)) { 73 | return $Str 74 | } elseif ($Str.EndsWith("`r`n")) { 75 | return $Str.Substring(0, $Str.Length - 2) 76 | } else { 77 | return $Str 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/de-de/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Der Containerpfad wurde nicht gefunden: \"{0}\".", 3 | "loc.messages.PSLIB_EndpointAuth0": "\"{0}\"-Dienstendpunkt-Anmeldeinformationen", 4 | "loc.messages.PSLIB_EndpointUrl0": "\"{0}\"-Dienstendpunkt-URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Fehler beim Aufzählen von Unterverzeichnissen für den folgenden Pfad: \"{0}\"", 6 | "loc.messages.PSLIB_FileNotFound0": "Die Datei wurde nicht gefunden: \"{0}\".", 7 | "loc.messages.PSLIB_Input0": "\"{0}\"-Eingabe", 8 | "loc.messages.PSLIB_InvalidPattern0": "Ungültiges Muster: \"{0}\"", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Der Blattpfad wurde nicht gefunden: \"{0}\".", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Fehler bei der Normalisierung bzw. Erweiterung des Pfads. Die Pfadlänge wurde vom Kernel32-Subsystem nicht zurückgegeben für: \"{0}\"", 11 | "loc.messages.PSLIB_PathNotFound0": "Der Pfad wurde nicht gefunden: \"{0}\".", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Der Prozess \"{0}\" wurde mit dem Code \"{1}\" beendet.", 13 | "loc.messages.PSLIB_Required0": "Erforderlich: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Fehler beim Zeichenfolgenformat.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Der Zeichenfolgen-Ressourcenschlüssel wurde nicht gefunden: \"{0}\".", 16 | "loc.messages.PSLIB_TaskVariable0": "\"{0}\"-Taskvariable" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/en-US/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Container path not found: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' service endpoint credentials", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' service endpoint URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Enumerating subdirectories failed for path: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "File not found: '{0}'", 7 | "loc.messages.PSLIB_Input0": "'{0}' input", 8 | "loc.messages.PSLIB_InvalidPattern0": "Invalid pattern: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Leaf path not found: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for: '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "Path not found: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Process '{0}' exited with code '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Required: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "String format failed.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "String resource key not found: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' task variable" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/es-es/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "No se encuentra la ruta de acceso del contenedor: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Credenciales del punto de conexión de servicio '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL del punto de conexión de servicio '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "No se pudieron enumerar los subdirectorios de la ruta de acceso: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "Archivo no encontrado: '{0}'", 7 | "loc.messages.PSLIB_Input0": "Entrada '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Patrón no válido: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "No se encuentra la ruta de acceso de la hoja: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "No se pudo normalizar o expandir la ruta de acceso. El subsistema Kernel32 no devolvió la longitud de la ruta de acceso para: '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "No se encuentra la ruta de acceso: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "El proceso '{0}' finalizó con el código '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Se requiere: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Error de formato de cadena.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "No se encuentra la clave de recurso de la cadena: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variable de tarea '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/fr-fr/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Le chemin du conteneur est introuvable : '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Informations d'identification du point de terminaison de service '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL du point de terminaison de service '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Échec de l'énumération des sous-répertoires pour le chemin : '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "Fichier introuvable : {0}.", 7 | "loc.messages.PSLIB_Input0": "Entrée '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Modèle non valide : '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Le chemin feuille est introuvable : '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Échec de la normalisation/l'expansion du chemin. La longueur du chemin n'a pas été retournée par le sous-système Kernel32 pour : '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "Chemin introuvable : '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Le processus '{0}' s'est arrêté avec le code '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Obligatoire : {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Échec du format de la chaîne.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Clé de la ressource de type chaîne introuvable : '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variable de tâche '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Percorso del contenitore non trovato: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Credenziali dell'endpoint servizio '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL dell'endpoint servizio '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "L'enumerazione delle sottodirectory per il percorso '{0}' non è riuscita", 6 | "loc.messages.PSLIB_FileNotFound0": "File non trovato: '{0}'", 7 | "loc.messages.PSLIB_Input0": "Input di '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Criterio non valido: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Percorso foglia non trovato: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "La normalizzazione o l'espansione del percorso non è riuscita. Il sottosistema Kernel32 non ha restituito la lunghezza del percorso per '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "Percorso non trovato: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Il processo '{0}' è stato terminato ed è stato restituito il codice '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Obbligatorio: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Errore nel formato della stringa.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "La chiave della risorsa stringa non è stata trovata: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variabile dell'attività '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ja-jp/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "コンテナーのパスが見つかりません: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' サービス エンドポイントの資格情報", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' サービス エンドポイントの URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "パス '{0}' のサブディレクトリを列挙できませんでした", 6 | "loc.messages.PSLIB_FileNotFound0": "ファイルが見つかりません: '{0}'", 7 | "loc.messages.PSLIB_Input0": "'{0}' 入力", 8 | "loc.messages.PSLIB_InvalidPattern0": "使用できないパターンです: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "リーフ パスが見つかりません: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "パスの正規化/展開に失敗しました。Kernel32 サブシステムからパス '{0}' の長さが返されませんでした", 11 | "loc.messages.PSLIB_PathNotFound0": "パスが見つかりません: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "プロセス '{0}' がコード '{1}' で終了しました。", 13 | "loc.messages.PSLIB_Required0": "必要: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "文字列のフォーマットに失敗しました。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "文字列のリソース キーが見つかりません: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' タスク変数" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "컨테이너 경로를 찾을 수 없음: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' 서비스 끝점 자격 증명", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' 서비스 끝점 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "경로에 대해 하위 디렉터리를 열거하지 못함: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "{0} 파일을 찾을 수 없습니다.", 7 | "loc.messages.PSLIB_Input0": "'{0}' 입력", 8 | "loc.messages.PSLIB_InvalidPattern0": "잘못된 패턴: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Leaf 경로를 찾을 수 없음: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "경로 정규화/확장에 실패했습니다. 다음에 대해 Kernel32 subsystem에서 경로 길이를 반환하지 않음: '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "경로를 찾을 수 없음: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "'{1}' 코드로 '{0}' 프로세스가 종료되었습니다.", 13 | "loc.messages.PSLIB_Required0": "필수: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "문자열을 포맷하지 못했습니다.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "문자열 리소스 키를 찾을 수 없음: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' 작업 변수" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Путь к контейнеру не найден: \"{0}\".", 3 | "loc.messages.PSLIB_EndpointAuth0": "Учетные данные конечной точки службы \"{0}\"", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL-адрес конечной точки службы \"{0}\"", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Сбой перечисления подкаталогов для пути: \"{0}\".", 6 | "loc.messages.PSLIB_FileNotFound0": "Файл не найден: \"{0}\".", 7 | "loc.messages.PSLIB_Input0": "Входные данные \"{0}\".", 8 | "loc.messages.PSLIB_InvalidPattern0": "Недопустимый шаблон: \"{0}\".", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Путь к конечному объекту не найден: \"{0}\".", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Сбой нормализации и расширения пути. Длина пути не была возвращена подсистемой Kernel32 для: \"{0}\".", 11 | "loc.messages.PSLIB_PathNotFound0": "Путь не найден: \"{0}\".", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Процесс \"{0}\" завершил работу с кодом \"{1}\".", 13 | "loc.messages.PSLIB_Required0": "Требуется: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Сбой формата строки.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Ключ ресурса строки не найден: \"{0}\".", 16 | "loc.messages.PSLIB_TaskVariable0": "Переменная задачи \"{0}\"" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路径:“{0}”", 3 | "loc.messages.PSLIB_EndpointAuth0": "“{0}”服务终结点凭据", 4 | "loc.messages.PSLIB_EndpointUrl0": "“{0}”服务终结点 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "枚举路径的子目录失败:“{0}”", 6 | "loc.messages.PSLIB_FileNotFound0": "找不到文件: {0}。", 7 | "loc.messages.PSLIB_Input0": "“{0}”输入", 8 | "loc.messages.PSLIB_InvalidPattern0": "无效的模式:“{0}”", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "找不到叶路径:“{0}”", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路径规范化/扩展失败。路径长度不是由“{0}”的 Kernel32 子系统返回的", 11 | "loc.messages.PSLIB_PathNotFound0": "找不到路径:“{0}”", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "过程“{0}”已退出,代码为“{1}”。", 13 | "loc.messages.PSLIB_Required0": "必需: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "字符串格式无效。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字符串资源关键字:“{0}”", 16 | "loc.messages.PSLIB_TaskVariable0": "“{0}”任务变量" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路徑: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' 服務端點認證", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' 服務端點 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "為路徑列舉子目錄失敗: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "找不到檔案: '{0}'", 7 | "loc.messages.PSLIB_Input0": "'{0}' 輸入", 8 | "loc.messages.PSLIB_InvalidPattern0": "模式無效: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "找不到分葉路徑: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路徑正規化/展開失敗。Kernel32 子系統未傳回 '{0}' 的路徑長度", 11 | "loc.messages.PSLIB_PathNotFound0": "找不到路徑: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "處理序 '{0}' 以返回碼 '{1}' 結束。", 13 | "loc.messages.PSLIB_Required0": "必要項: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "字串格式失敗。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字串資源索引鍵: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' 工作變數" 17 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/ToolFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Asserts that a path exists. Throws if the path does not exist. 4 | 5 | .PARAMETER PassThru 6 | True to return the path. 7 | #> 8 | function Assert-Path { 9 | [CmdletBinding()] 10 | param( 11 | [Parameter(Mandatory = $true)] 12 | [string]$LiteralPath, 13 | [Microsoft.PowerShell.Commands.TestPathType]$PathType = [Microsoft.PowerShell.Commands.TestPathType]::Any, 14 | [switch]$PassThru) 15 | 16 | if ($PathType -eq [Microsoft.PowerShell.Commands.TestPathType]::Any) { 17 | Write-Verbose "Asserting path exists: '$LiteralPath'" 18 | } else { 19 | Write-Verbose "Asserting $("$PathType".ToLowerInvariant()) path exists: '$LiteralPath'" 20 | } 21 | 22 | if (Test-Path -LiteralPath $LiteralPath -PathType $PathType) { 23 | if ($PassThru) { 24 | return $LiteralPath 25 | } 26 | 27 | return 28 | } 29 | 30 | $resourceKey = switch ($PathType) { 31 | ([Microsoft.PowerShell.Commands.TestPathType]::Container) { "PSLIB_ContainerPathNotFound0" ; break } 32 | ([Microsoft.PowerShell.Commands.TestPathType]::Leaf) { "PSLIB_LeafPathNotFound0" ; break } 33 | default { "PSLIB_PathNotFound0" } 34 | } 35 | 36 | throw (Get-LocString -Key $resourceKey -ArgumentList $LiteralPath) 37 | } 38 | 39 | <# 40 | .SYNOPSIS 41 | Executes an external program. 42 | 43 | .DESCRIPTION 44 | Executes an external program and waits for the process to exit. 45 | 46 | After calling this command, the exit code of the process can be retrieved from the variable $LASTEXITCODE. 47 | 48 | .PARAMETER Encoding 49 | This parameter not required for most scenarios. Indicates how to interpret the encoding from the external program. An example use case would be if an external program outputs UTF-16 XML and the output needs to be parsed. 50 | 51 | .PARAMETER RequireExitCodeZero 52 | Indicates whether to write an error to the error pipeline if the exit code is not zero. 53 | #> 54 | function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS? 55 | [CmdletBinding()] 56 | param( 57 | [ValidatePattern('^[^\r\n]*$')] 58 | [Parameter(Mandatory = $true)] 59 | [string]$FileName, 60 | [ValidatePattern('^[^\r\n]*$')] 61 | [Parameter()] 62 | [string]$Arguments, 63 | [string]$WorkingDirectory, 64 | [System.Text.Encoding]$Encoding, 65 | [switch]$RequireExitCodeZero) 66 | 67 | Trace-EnteringInvocation $MyInvocation 68 | $isPushed = $false 69 | $originalEncoding = $null 70 | try { 71 | if ($Encoding) { 72 | $originalEncoding = [System.Console]::OutputEncoding 73 | [System.Console]::OutputEncoding = $Encoding 74 | } 75 | 76 | if ($WorkingDirectory) { 77 | Push-Location -LiteralPath $WorkingDirectory -ErrorAction Stop 78 | $isPushed = $true 79 | } 80 | 81 | $FileName = $FileName.Replace('"', '').Replace("'", "''") 82 | Write-Host "##[command]""$FileName"" $Arguments" 83 | Invoke-Expression "& '$FileName' --% $Arguments" 84 | Write-Verbose "Exit code: $LASTEXITCODE" 85 | if ($RequireExitCodeZero -and $LASTEXITCODE -ne 0) { 86 | Write-Error (Get-LocString -Key PSLIB_Process0ExitedWithCode1 -ArgumentList ([System.IO.Path]::GetFileName($FileName)), $LASTEXITCODE) 87 | } 88 | } finally { 89 | if ($originalEncoding) { 90 | [System.Console]::OutputEncoding = $originalEncoding 91 | } 92 | 93 | if ($isPushed) { 94 | Pop-Location 95 | } 96 | 97 | Trace-LeavingInvocation $MyInvocation 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/TraceFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Writes verbose information about the invocation being entered. 4 | 5 | .DESCRIPTION 6 | Used to trace verbose information when entering a function/script. Writes an entering message followed by a short description of the invocation. Additionally each bound parameter and unbound argument is also traced. 7 | 8 | .PARAMETER Parameter 9 | Wildcard pattern to control which bound parameters are traced. 10 | #> 11 | function Trace-EnteringInvocation { 12 | [CmdletBinding()] 13 | param( 14 | [Parameter(Mandatory = $true)] 15 | [System.Management.Automation.InvocationInfo]$InvocationInfo, 16 | [string[]]$Parameter = '*') 17 | 18 | Write-Verbose "Entering $(Get-InvocationDescription $InvocationInfo)." 19 | $OFS = ", " 20 | if ($InvocationInfo.BoundParameters.Count -and $Parameter.Count) { 21 | if ($Parameter.Count -eq 1 -and $Parameter[0] -eq '*') { 22 | # Trace all parameters. 23 | foreach ($key in $InvocationInfo.BoundParameters.Keys) { 24 | Write-Verbose " $($key): '$($InvocationInfo.BoundParameters[$key])'" 25 | } 26 | } else { 27 | # Trace matching parameters. 28 | foreach ($key in $InvocationInfo.BoundParameters.Keys) { 29 | foreach ($p in $Parameter) { 30 | if ($key -like $p) { 31 | Write-Verbose " $($key): '$($InvocationInfo.BoundParameters[$key])'" 32 | break 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | # Trace all unbound arguments. 40 | if (@($InvocationInfo.UnboundArguments).Count) { 41 | for ($i = 0 ; $i -lt $InvocationInfo.UnboundArguments.Count ; $i++) { 42 | Write-Verbose " args[$i]: '$($InvocationInfo.UnboundArguments[$i])'" 43 | } 44 | } 45 | } 46 | 47 | <# 48 | .SYNOPSIS 49 | Writes verbose information about the invocation being left. 50 | 51 | .DESCRIPTION 52 | Used to trace verbose information when leaving a function/script. Writes a leaving message followed by a short description of the invocation. 53 | #> 54 | function Trace-LeavingInvocation { 55 | [CmdletBinding()] 56 | param( 57 | [Parameter(Mandatory = $true)] 58 | [System.Management.Automation.InvocationInfo]$InvocationInfo) 59 | 60 | Write-Verbose "Leaving $(Get-InvocationDescription $InvocationInfo)." 61 | } 62 | 63 | <# 64 | .SYNOPSIS 65 | Writes verbose information about paths. 66 | 67 | .DESCRIPTION 68 | Writes verbose information about the paths. The paths are sorted and a the common root is written only once, followed by each relative path. 69 | 70 | .PARAMETER PassThru 71 | Indicates whether to return the sorted paths. 72 | #> 73 | function Trace-Path { 74 | [CmdletBinding()] 75 | param( 76 | [string[]]$Path, 77 | [switch]$PassThru) 78 | 79 | if ($Path.Count -eq 0) { 80 | Write-Verbose "No paths." 81 | if ($PassThru) { 82 | $Path 83 | } 84 | } elseif ($Path.Count -eq 1) { 85 | Write-Verbose "Path: $($Path[0])" 86 | if ($PassThru) { 87 | $Path 88 | } 89 | } else { 90 | # Find the greatest common root. 91 | $sorted = $Path | Sort-Object 92 | $firstPath = $sorted[0].ToCharArray() 93 | $lastPath = $sorted[-1].ToCharArray() 94 | $commonEndIndex = 0 95 | $j = if ($firstPath.Length -lt $lastPath.Length) { $firstPath.Length } else { $lastPath.Length } 96 | for ($i = 0 ; $i -lt $j ; $i++) { 97 | if ($firstPath[$i] -eq $lastPath[$i]) { 98 | if ($firstPath[$i] -eq '\') { 99 | $commonEndIndex = $i 100 | } 101 | } else { 102 | break 103 | } 104 | } 105 | 106 | if ($commonEndIndex -eq 0) { 107 | # No common root. 108 | Write-Verbose "Paths:" 109 | foreach ($p in $sorted) { 110 | Write-Verbose " $p" 111 | } 112 | } else { 113 | Write-Verbose "Paths: $($Path[0].Substring(0, $commonEndIndex + 1))" 114 | foreach ($p in $sorted) { 115 | Write-Verbose " $($p.Substring($commonEndIndex + 1))" 116 | } 117 | } 118 | 119 | if ($PassThru) { 120 | $sorted 121 | } 122 | } 123 | } 124 | 125 | ######################################## 126 | # Private functions. 127 | ######################################## 128 | function Get-InvocationDescription { 129 | [CmdletBinding()] 130 | param([System.Management.Automation.InvocationInfo]$InvocationInfo) 131 | 132 | if ($InvocationInfo.MyCommand.Path) { 133 | $InvocationInfo.MyCommand.Path 134 | } elseif ($InvocationInfo.MyCommand.Name) { 135 | $InvocationInfo.MyCommand.Name 136 | } else { 137 | $InvocationInfo.MyCommand.CommandType 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-expand/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.psd1 -------------------------------------------------------------------------------- /vsts-variable-expand/v1/ps_modules/VstsTaskSdk/lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": { 3 | "PSLIB_ContainerPathNotFound0": "Container path not found: '{0}'", 4 | "PSLIB_EndpointAuth0": "'{0}' service endpoint credentials", 5 | "PSLIB_EndpointUrl0": "'{0}' service endpoint URL", 6 | "PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Enumerating subdirectories failed for path: '{0}'", 7 | "PSLIB_FileNotFound0": "File not found: '{0}'", 8 | "PSLIB_Input0": "'{0}' input", 9 | "PSLIB_InvalidPattern0": "Invalid pattern: '{0}'", 10 | "PSLIB_LeafPathNotFound0": "Leaf path not found: '{0}'", 11 | "PSLIB_PathLengthNotReturnedFor0": "Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for: '{0}'", 12 | "PSLIB_PathNotFound0": "Path not found: '{0}'", 13 | "PSLIB_Process0ExitedWithCode1": "Process '{0}' exited with code '{1}'.", 14 | "PSLIB_Required0": "Required: {0}", 15 | "PSLIB_StringFormatFailed": "String format failed.", 16 | "PSLIB_StringResourceKeyNotFound0": "String resource key not found: '{0}'", 17 | "PSLIB_TaskVariable0": "'{0}' task variable" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vsts-variable-expand/v1/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5f8f1adf-42bd-442d-8a0f-ae009a66b99e", 3 | "name": "VariableExpandTask", 4 | "friendlyName": "Expand Variable [DEPRECATED]", 5 | "deprecated": true, 6 | "description": "Expands a variable on agent version 1.x.", 7 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 8 | "category": "Utility", 9 | "author": "Jesse Houwing", 10 | "version": { 11 | "Major": 1, 12 | "Minor": 5, 13 | "Patch": 0 14 | }, 15 | "visibility": [ 16 | "Build", 17 | "Release" 18 | ], 19 | "minimumAgentVersion": "1.83.0", 20 | "groups": [ 21 | 22 | ], 23 | "inputs": [ 24 | { 25 | "defaultValue": "*", 26 | "helpMarkdown": "List of variables to expand separated by `,`, `;` or newline. Only supply the variable name and do not include `$(...)`. To expand all variables, enter `*`.", 27 | "label": "Variables", 28 | "name": "VariableNames", 29 | "required": true, 30 | "type": "multiLine" 31 | } 32 | ], 33 | "instanceNameFormat": "Expand variables: '$(VariableNames)' on agent version 1.x", 34 | "execution": { 35 | "PowerShell": { 36 | "target": "$(currentDirectory)\\vsts-variable-expand.v1.ps1", 37 | "argumentFormat": "", 38 | "workingDirectory": "$(currentDirectory)", 39 | "platforms": [ 40 | "windows" 41 | ] 42 | }, 43 | "PowerShell3": { 44 | "target": "$(currentDirectory)\\vsts-variable-expand.v3.ps1", 45 | "argumentFormat": "" 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /vsts-variable-expand/v1/vsts-variable-expand.v1.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param( 3 | [Parameter(Mandatory=$true)] 4 | [ValidateNotNullOrEmpty()] 5 | [string] $VariableNames, 6 | [ValidateSet("true", "false", "1", "0")] 7 | [string] $MaxDepth = 5 8 | ) 9 | 10 | Write-Verbose "Entering script $($MyInvocation.MyCommand.Name)" 11 | Write-Verbose "Parameter Values" 12 | $PSBoundParameters.Keys | %{ Write-Verbose "$_ = $($PSBoundParameters[$_])" } 13 | 14 | Write-Verbose "Importing modules" 15 | import-module "Microsoft.TeamFoundation.DistributedTask.Task.Internal" 16 | import-module "Microsoft.TeamFoundation.DistributedTask.Task.Common" 17 | 18 | function Expand-Variable{ 19 | param 20 | ( 21 | [Parameter(Mandatory=$true)] 22 | [ValidateNotNullOrEmpty()] 23 | [string] $Variable 24 | ) 25 | begin 26 | { 27 | $value = Get-TaskVariable $distributedTaskContext $Variable 28 | } 29 | process 30 | { 31 | do 32 | { 33 | $newValue = $value 34 | $value = [Microsoft.TeamFoundation.DistributedTask.Agent.Common.ContextExtensions]::ExpandVariables($distributedTaskContext, $newValue) 35 | } 36 | while ($value -ne $newValue) 37 | } 38 | end 39 | { 40 | return $value 41 | } 42 | } 43 | 44 | function Get-VariableNames{ 45 | param 46 | ( 47 | [switch] $safe = $false 48 | ) 49 | begin 50 | { 51 | Write-Debug "Entering: Get-Variables" 52 | 53 | $type = [Microsoft.TeamFoundation.DistributedTask.Agent.Interfaces.IServiceManager] 54 | $variableService = $type.GetMethod("GetService").MakeGenericMethod([Microsoft.TeamFoundation.DistributedTask.Agent.Interfaces.IVariableService]).Invoke($distributedTaskContext, @()) 55 | $dictionary = New-Object "System.Collections.Generic.Dictionary[string,string]" ([System.StringComparer]::OrdinalIgnoreCase) 56 | } 57 | process 58 | { 59 | if ($safe.IsPresent) 60 | { 61 | $variableService.MergeSafeVariables($dictionary) 62 | } 63 | else 64 | { 65 | $variableService.MergeVariables($dictionary) 66 | } 67 | } 68 | end 69 | { 70 | Write-Debug "Leaving: Get-Variables" 71 | return @($dictionary.Keys) 72 | } 73 | } 74 | 75 | function Expand-Variables 76 | { 77 | param 78 | ( 79 | [array] $variables = @() 80 | ) 81 | begin 82 | { 83 | Write-Debug "Entering: Expand-Variables" 84 | } 85 | process 86 | { 87 | $Variables = $Variables | %{ $_.Trim() } 88 | 89 | if ($Variables -contains "*") 90 | { 91 | Expand-Variables -variables (Get-VariableNames -safe) 92 | return; 93 | } 94 | else 95 | { 96 | for ($i = 0; $i -lt $MaxDepth; $i++) 97 | { 98 | foreach ($Variable in $Variables) 99 | { 100 | $Variable = $Variable.Trim() 101 | 102 | if ($Variable -ne "") 103 | { 104 | Invoke-ExpandVariable $Variable 105 | } 106 | } 107 | } 108 | } 109 | } 110 | end 111 | { 112 | Write-Debug "Leaving: Expand-Variables" 113 | } 114 | } 115 | 116 | function Invoke-ExpandVariable 117 | { 118 | param 119 | ( 120 | [string] $name 121 | ) 122 | 123 | begin 124 | { 125 | Write-Debug "Entering: Expand variable" 126 | } 127 | process 128 | { 129 | for ($i = 0; $i -lt $MaxDepth; $i++) 130 | { 131 | $currentValue = Get-TaskVariable $distributedTaskContext $Variable 132 | $newValue = Expand-Variable $Variable 133 | 134 | if ($currentValue -cne $newValue) 135 | { 136 | Write-Output "Setting '$Variable' to '$newValue'." 137 | Write-Host "##vso[task.setvariable variable=$($Variable);]$newValue" 138 | break 139 | } 140 | } 141 | } 142 | end 143 | { 144 | Write-Debug "Leaving: Expand variable" 145 | } 146 | } 147 | $Variables = ($VariableNames -split "`r?`n|;|,") 148 | 149 | Expand-Variables $Variables 150 | 151 | Write-Host "##vso[task.complete result=Succeeded;]DONE" -------------------------------------------------------------------------------- /vsts-variable-expand/v1/vsts-variable-expand.v3.ps1: -------------------------------------------------------------------------------- 1 | import-module .\ps_modules\vststasksdk\vststasksdk.psd1 2 | 3 | Write-VstsTaskWarning -Message "Expand Variable task is no longer required. The 2.0 agent will automatically expand variables." 4 | Write-VstsSetResult -Result "SucceededWithIssues" -------------------------------------------------------------------------------- /vsts-variable-set/v1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v1/icon.png -------------------------------------------------------------------------------- /vsts-variable-set/v1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-set", 3 | "version": "0.0.0", 4 | "description": "Set Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^3.4.0" 11 | }, 12 | "scripts": { 13 | "initdev:npm": "npm install", 14 | "initdev": "npm run initdev:npm", 15 | "build": "tsc && npm dedupe && npm prune --production" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^16.18.126" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/LegacyFindFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Finds files or directories. 4 | 5 | .DESCRIPTION 6 | Finds files or directories using advanced pattern matching. 7 | 8 | .PARAMETER LiteralDirectory 9 | Directory to search. 10 | 11 | .PARAMETER LegacyPattern 12 | Proprietary pattern format. The LiteralDirectory parameter is used to root any unrooted patterns. 13 | 14 | Separate multiple patterns using ";". Escape actual ";" in the path by using ";;". 15 | "?" indicates a wildcard that represents any single character within a path segment. 16 | "*" indicates a wildcard that represents zero or more characters within a path segment. 17 | "**" as the entire path segment indicates a recursive search. 18 | "**" within a path segment indicates a recursive intersegment wildcard. 19 | "+:" (can be omitted) indicates an include pattern. 20 | "-:" indicates an exclude pattern. 21 | 22 | The result is from the command is a union of all the matches from the include patterns, minus the matches from the exclude patterns. 23 | 24 | .PARAMETER IncludeFiles 25 | Indicates whether to include files in the results. 26 | 27 | If neither IncludeFiles or IncludeDirectories is set, then IncludeFiles is assumed. 28 | 29 | .PARAMETER IncludeDirectories 30 | Indicates whether to include directories in the results. 31 | 32 | If neither IncludeFiles or IncludeDirectories is set, then IncludeFiles is assumed. 33 | 34 | .PARAMETER Force 35 | Indicates whether to include hidden items. 36 | 37 | .EXAMPLE 38 | Find-VstsFiles -LegacyPattern "C:\Directory\Is?Match.txt" 39 | 40 | Given: 41 | C:\Directory\Is1Match.txt 42 | C:\Directory\Is2Match.txt 43 | C:\Directory\IsNotMatch.txt 44 | 45 | Returns: 46 | C:\Directory\Is1Match.txt 47 | C:\Directory\Is2Match.txt 48 | 49 | .EXAMPLE 50 | Find-VstsFiles -LegacyPattern "C:\Directory\Is*Match.txt" 51 | 52 | Given: 53 | C:\Directory\IsOneMatch.txt 54 | C:\Directory\IsTwoMatch.txt 55 | C:\Directory\NonMatch.txt 56 | 57 | Returns: 58 | C:\Directory\IsOneMatch.txt 59 | C:\Directory\IsTwoMatch.txt 60 | 61 | .EXAMPLE 62 | Find-VstsFiles -LegacyPattern "C:\Directory\**\Match.txt" 63 | 64 | Given: 65 | C:\Directory\Match.txt 66 | C:\Directory\NotAMatch.txt 67 | C:\Directory\SubDir\Match.txt 68 | C:\Directory\SubDir\SubSubDir\Match.txt 69 | 70 | Returns: 71 | C:\Directory\Match.txt 72 | C:\Directory\SubDir\Match.txt 73 | C:\Directory\SubDir\SubSubDir\Match.txt 74 | 75 | .EXAMPLE 76 | Find-VstsFiles -LegacyPattern "C:\Directory\**" 77 | 78 | Given: 79 | C:\Directory\One.txt 80 | C:\Directory\SubDir\Two.txt 81 | C:\Directory\SubDir\SubSubDir\Three.txt 82 | 83 | Returns: 84 | C:\Directory\One.txt 85 | C:\Directory\SubDir\Two.txt 86 | C:\Directory\SubDir\SubSubDir\Three.txt 87 | 88 | .EXAMPLE 89 | Find-VstsFiles -LegacyPattern "C:\Directory\Sub**Match.txt" 90 | 91 | Given: 92 | C:\Directory\IsNotAMatch.txt 93 | C:\Directory\SubDir\IsAMatch.txt 94 | C:\Directory\SubDir\IsNot.txt 95 | C:\Directory\SubDir\SubSubDir\IsAMatch.txt 96 | C:\Directory\SubDir\SubSubDir\IsNot.txt 97 | 98 | Returns: 99 | C:\Directory\SubDir\IsAMatch.txt 100 | C:\Directory\SubDir\SubSubDir\IsAMatch.txt 101 | #> 102 | function Find-Files { 103 | [CmdletBinding()] 104 | param( 105 | [ValidateNotNullOrEmpty()] 106 | [Parameter()] 107 | [string]$LiteralDirectory, 108 | [Parameter(Mandatory = $true)] 109 | [string]$LegacyPattern, 110 | [switch]$IncludeFiles, 111 | [switch]$IncludeDirectories, 112 | [switch]$Force) 113 | 114 | # Note, due to subtle implementation details of Get-PathPrefix/Get-PathIterator, 115 | # this function does not appear to be able to search the root of a drive and other 116 | # cases where Path.GetDirectoryName() returns empty. More details in Get-PathPrefix. 117 | 118 | Trace-EnteringInvocation $MyInvocation 119 | if (!$IncludeFiles -and !$IncludeDirectories) { 120 | $IncludeFiles = $true 121 | } 122 | 123 | $includePatterns = New-Object System.Collections.Generic.List[string] 124 | $excludePatterns = New-Object System.Collections.Generic.List[System.Text.RegularExpressions.Regex] 125 | $LegacyPattern = $LegacyPattern.Replace(';;', "`0") 126 | foreach ($pattern in $LegacyPattern.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)) { 127 | $pattern = $pattern.Replace("`0", ';') 128 | $isIncludePattern = Test-IsIncludePattern -Pattern ([ref]$pattern) 129 | if ($LiteralDirectory -and !([System.IO.Path]::IsPathRooted($pattern))) { 130 | # Use the root directory provided to make the pattern a rooted path. 131 | $pattern = [System.IO.Path]::Combine($LiteralDirectory, $pattern) 132 | } 133 | 134 | # Validate pattern does not end with a \. 135 | if ($pattern[$pattern.Length - 1] -eq [System.IO.Path]::DirectorySeparatorChar) { 136 | throw (Get-LocString -Key PSLIB_InvalidPattern0 -ArgumentList $pattern) 137 | } 138 | 139 | if ($isIncludePattern) { 140 | $includePatterns.Add($pattern) 141 | } else { 142 | $excludePatterns.Add((Convert-PatternToRegex -Pattern $pattern)) 143 | } 144 | } 145 | 146 | $count = 0 147 | foreach ($path in (Get-MatchingItems -IncludePatterns $includePatterns -ExcludePatterns $excludePatterns -IncludeFiles:$IncludeFiles -IncludeDirectories:$IncludeDirectories -Force:$Force)) { 148 | $count++ 149 | $path 150 | } 151 | 152 | Write-Verbose "Total found: $count" 153 | Trace-LeavingInvocation $MyInvocation 154 | } 155 | 156 | ######################################## 157 | # Private functions. 158 | ######################################## 159 | function Convert-PatternToRegex { 160 | [CmdletBinding()] 161 | param([string]$Pattern) 162 | 163 | $Pattern = [regex]::Escape($Pattern.Replace('\', '/')). # Normalize separators and regex escape. 164 | Replace('/\*\*/', '((/.+/)|(/))'). # Replace directory globstar. 165 | Replace('\*\*', '.*'). # Replace remaining globstars with a wildcard that can span directory separators. 166 | Replace('\*', '[^/]*'). # Replace asterisks with a wildcard that cannot span directory separators. 167 | # bug: should be '[^/]' instead of '.' 168 | Replace('\?', '.') # Replace single character wildcards. 169 | New-Object regex -ArgumentList "^$Pattern`$", ([System.Text.RegularExpressions.RegexOptions]::IgnoreCase) 170 | } 171 | 172 | function Get-FileNameFilter { 173 | [CmdletBinding()] 174 | param([string]$Pattern) 175 | 176 | $index = $Pattern.LastIndexOf('\') 177 | if ($index -eq -1 -or # Pattern does not contain a backslash. 178 | !($Pattern = $Pattern.Substring($index + 1)) -or # Pattern ends in a backslash. 179 | $Pattern.Contains('**')) # Last segment contains an inter-segment wildcard. 180 | { 181 | return '*' 182 | } 183 | 184 | # bug? is this supposed to do substring? 185 | return $Pattern 186 | } 187 | 188 | function Get-MatchingItems { 189 | [CmdletBinding()] 190 | param( 191 | [System.Collections.Generic.List[string]]$IncludePatterns, 192 | [System.Collections.Generic.List[regex]]$ExcludePatterns, 193 | [switch]$IncludeFiles, 194 | [switch]$IncludeDirectories, 195 | [switch]$Force) 196 | 197 | Trace-EnteringInvocation $MyInvocation 198 | $allFiles = New-Object System.Collections.Generic.HashSet[string] 199 | foreach ($pattern in $IncludePatterns) { 200 | $pathPrefix = Get-PathPrefix -Pattern $pattern 201 | $fileNameFilter = Get-FileNameFilter -Pattern $pattern 202 | $patternRegex = Convert-PatternToRegex -Pattern $pattern 203 | # Iterate over the directories and files under the pathPrefix. 204 | Get-PathIterator -Path $pathPrefix -Filter $fileNameFilter -IncludeFiles:$IncludeFiles -IncludeDirectories:$IncludeDirectories -Force:$Force | 205 | ForEach-Object { 206 | # Normalize separators. 207 | $normalizedPath = $_.Replace('\', '/') 208 | # **/times/** will not match C:/fun/times because there isn't a trailing slash. 209 | # So try both if including directories. 210 | $alternatePath = "$normalizedPath/" # potential bug: it looks like this will result in a false 211 | # positive if the item is a regular file and not a directory 212 | 213 | $isMatch = $false 214 | if ($patternRegex.IsMatch($normalizedPath) -or ($IncludeDirectories -and $patternRegex.IsMatch($alternatePath))) { 215 | $isMatch = $true 216 | 217 | # Test whether the path should be excluded. 218 | foreach ($regex in $ExcludePatterns) { 219 | if ($regex.IsMatch($normalizedPath) -or ($IncludeDirectories -and $regex.IsMatch($alternatePath))) { 220 | $isMatch = $false 221 | break 222 | } 223 | } 224 | } 225 | 226 | if ($isMatch) { 227 | $null = $allFiles.Add($_) 228 | } 229 | } 230 | } 231 | 232 | Trace-Path -Path $allFiles -PassThru 233 | Trace-LeavingInvocation $MyInvocation 234 | } 235 | 236 | function Get-PathIterator { 237 | [CmdletBinding()] 238 | param( 239 | [string]$Path, 240 | [string]$Filter, 241 | [switch]$IncludeFiles, 242 | [switch]$IncludeDirectories, 243 | [switch]$Force) 244 | 245 | if (!$Path) { 246 | return 247 | } 248 | 249 | # bug: this returns the dir without verifying whether exists 250 | if ($IncludeDirectories) { 251 | $Path 252 | } 253 | 254 | Get-DirectoryChildItem -Path $Path -Filter $Filter -Force:$Force -Recurse | 255 | ForEach-Object { 256 | if ($_.Attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) { 257 | if ($IncludeDirectories) { 258 | $_.FullName 259 | } 260 | } elseif ($IncludeFiles) { 261 | $_.FullName 262 | } 263 | } 264 | } 265 | 266 | function Get-PathPrefix { 267 | [CmdletBinding()] 268 | param([string]$Pattern) 269 | 270 | # Note, unable to search root directories is a limitation due to subtleties of this function 271 | # and downstream code in Get-PathIterator that short-circuits when the path prefix is empty. 272 | # This function uses Path.GetDirectoryName() to determine the path prefix, which will yield 273 | # empty in some cases. See the following examples of Path.GetDirectoryName() input => output: 274 | # C:/ => 275 | # C:/hello => C:\ 276 | # C:/hello/ => C:\hello 277 | # C:/hello/world => C:\hello 278 | # C:/hello/world/ => C:\hello\world 279 | # C: => 280 | # C:hello => C: 281 | # C:hello/ => C:hello 282 | # / => 283 | # /hello => \ 284 | # /hello/ => \hello 285 | # //hello => 286 | # //hello/ => 287 | # //hello/world => 288 | # //hello/world/ => \\hello\world 289 | 290 | $index = $Pattern.IndexOfAny([char[]]@('*'[0], '?'[0])) 291 | if ($index -eq -1) { 292 | # If no wildcards are found, return the directory name portion of the path. 293 | # If there is no directory name (file name only in pattern), this will return empty string. 294 | return [System.IO.Path]::GetDirectoryName($Pattern) 295 | } 296 | 297 | [System.IO.Path]::GetDirectoryName($Pattern.Substring(0, $index)) 298 | } 299 | 300 | function Test-IsIncludePattern { 301 | [CmdletBinding()] 302 | param( 303 | [Parameter(Mandatory = $true)] 304 | [ref]$Pattern) 305 | 306 | # Include patterns start with +: or anything except -: 307 | # Exclude patterns start with -: 308 | if ($Pattern.value.StartsWith("+:")) { 309 | # Remove the prefix. 310 | $Pattern.value = $Pattern.value.Substring(2) 311 | $true 312 | } elseif ($Pattern.value.StartsWith("-:")) { 313 | # Remove the prefix. 314 | $Pattern.value = $Pattern.value.Substring(2) 315 | $false 316 | } else { 317 | # No prefix, so leave the string alone. 318 | $true; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/LocalizationFunctions.ps1: -------------------------------------------------------------------------------- 1 | $script:resourceStrings = @{ } 2 | 3 | <# 4 | .SYNOPSIS 5 | Gets a localized resource string. 6 | 7 | .DESCRIPTION 8 | Gets a localized resource string and optionally formats the string with arguments. 9 | 10 | If the format fails (due to a bad format string or incorrect expected arguments in the format string), then the format string is returned followed by each of the arguments (delimited by a space). 11 | 12 | If the lookup key is not found, then the lookup key is returned followed by each of the arguments (delimited by a space). 13 | 14 | .PARAMETER Require 15 | Writes an error to the error pipeline if the endpoint is not found. 16 | #> 17 | function Get-LocString { 18 | [CmdletBinding()] 19 | param( 20 | [Parameter(Mandatory = $true, Position = 1)] 21 | [string]$Key, 22 | [Parameter(Position = 2)] 23 | [object[]]$ArgumentList = @( )) 24 | 25 | # Due to the dynamically typed nature of PowerShell, a single null argument passed 26 | # to an array parameter is interpreted as a null array. 27 | if ([object]::ReferenceEquals($null, $ArgumentList)) { 28 | $ArgumentList = @( $null ) 29 | } 30 | 31 | # Lookup the format string. 32 | $format = '' 33 | if (!($format = $script:resourceStrings[$Key])) { 34 | # Warn the key was not found. Prevent recursion if the lookup key is the 35 | # "string resource key not found" lookup key. 36 | $resourceNotFoundKey = 'PSLIB_StringResourceKeyNotFound0' 37 | if ($key -ne $resourceNotFoundKey) { 38 | Write-Warning (Get-LocString -Key $resourceNotFoundKey -ArgumentList $Key) 39 | } 40 | 41 | # Fallback to just the key itself if there aren't any arguments to format. 42 | if (!$ArgumentList.Count) { return $key } 43 | 44 | # Otherwise fallback to the key followed by the arguments. 45 | $OFS = " " 46 | return "$key $ArgumentList" 47 | } 48 | 49 | # Return the string if there aren't any arguments to format. 50 | if (!$ArgumentList.Count) { return $format } 51 | 52 | try { 53 | [string]::Format($format, $ArgumentList) 54 | } catch { 55 | Write-Warning (Get-LocString -Key 'PSLIB_StringFormatFailed') 56 | $OFS = " " 57 | "$format $ArgumentList" 58 | } 59 | } 60 | 61 | <# 62 | .SYNOPSIS 63 | Imports resource strings for use with Get-VstsLocString. 64 | 65 | .DESCRIPTION 66 | Imports resource strings for use with Get-VstsLocString. The imported strings are stored in an internal resource string dictionary. Optionally, if a separate resource file for the current culture exists, then the localized strings from that file then imported (overlaid) into the same internal resource string dictionary. 67 | 68 | Resource strings from the SDK are prefixed with "PSLIB_". This prefix should be avoided for custom resource strings. 69 | 70 | .Parameter LiteralPath 71 | JSON file containing resource strings. 72 | 73 | .EXAMPLE 74 | Import-VstsLocStrings -LiteralPath $PSScriptRoot\Task.json 75 | 76 | Imports strings from messages section in the JSON file. If a messages section is not defined, then no strings are imported. Example messages section: 77 | { 78 | "messages": { 79 | "Hello": "Hello you!", 80 | "Hello0": "Hello {0}!" 81 | } 82 | } 83 | 84 | .EXAMPLE 85 | Import-VstsLocStrings -LiteralPath $PSScriptRoot\Task.json 86 | 87 | Overlays strings from an optional separate resource file for the current culture. 88 | 89 | Given the task variable System.Culture is set to 'de-DE'. This variable is set by the agent based on the current culture for the job. 90 | Given the file Task.json contains: 91 | { 92 | "messages": { 93 | "GoodDay": "Good day!", 94 | } 95 | } 96 | Given the file resources.resjson\de-DE\resources.resjson: 97 | { 98 | "loc.messages.GoodDay": "Guten Tag!" 99 | } 100 | 101 | The net result from the import command would be one new key-value pair added to the internal dictionary: Key = 'GoodDay', Value = 'Guten Tag!' 102 | #> 103 | function Import-LocStrings { 104 | [CmdletBinding()] 105 | param( 106 | [Parameter(Mandatory = $true)] 107 | [string]$LiteralPath) 108 | 109 | # Validate the file exists. 110 | if (!(Test-Path -LiteralPath $LiteralPath -PathType Leaf)) { 111 | Write-Warning (Get-LocString -Key PSLIB_FileNotFound0 -ArgumentList $LiteralPath) 112 | return 113 | } 114 | 115 | # Load the json. 116 | Write-Verbose "Loading resource strings from: $LiteralPath" 117 | $count = 0 118 | if ($messages = (Get-Content -LiteralPath $LiteralPath -Encoding UTF8 | Out-String | ConvertFrom-Json).messages) { 119 | # Add each resource string to the hashtable. 120 | foreach ($member in (Get-Member -InputObject $messages -MemberType NoteProperty)) { 121 | [string]$key = $member.Name 122 | $script:resourceStrings[$key] = $messages."$key" 123 | $count++ 124 | } 125 | } 126 | 127 | Write-Verbose "Loaded $count strings." 128 | 129 | # Get the culture. 130 | $culture = Get-TaskVariable -Name "System.Culture" -Default "en-US" 131 | 132 | # Load the resjson. 133 | $resjsonPath = "$([System.IO.Path]::GetDirectoryName($LiteralPath))\Strings\resources.resjson\$culture\resources.resjson" 134 | if (Test-Path -LiteralPath $resjsonPath) { 135 | Write-Verbose "Loading resource strings from: $resjsonPath" 136 | $count = 0 137 | $resjson = Get-Content -LiteralPath $resjsonPath -Encoding UTF8 | Out-String | ConvertFrom-Json 138 | foreach ($member in (Get-Member -Name loc.messages.* -InputObject $resjson -MemberType NoteProperty)) { 139 | if (!($value = $resjson."$($member.Name)")) { 140 | continue 141 | } 142 | 143 | [string]$key = $member.Name.Substring('loc.messages.'.Length) 144 | $script:resourceStrings[$key] = $value 145 | $count++ 146 | } 147 | 148 | Write-Verbose "Loaded $count strings." 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/LongPathFunctions.ps1: -------------------------------------------------------------------------------- 1 | ######################################## 2 | # Private functions. 3 | ######################################## 4 | function ConvertFrom-LongFormPath { 5 | [CmdletBinding()] 6 | param([string]$Path) 7 | 8 | if ($Path) { 9 | if ($Path.StartsWith('\\?\UNC')) { 10 | # E.g. \\?\UNC\server\share -> \\server\share 11 | return $Path.Substring(1, '\?\UNC'.Length) 12 | } elseif ($Path.StartsWith('\\?\')) { 13 | # E.g. \\?\C:\directory -> C:\directory 14 | return $Path.Substring('\\?\'.Length) 15 | } 16 | } 17 | 18 | return $Path 19 | } 20 | function ConvertTo-LongFormPath { 21 | [CmdletBinding()] 22 | param( 23 | [Parameter(Mandatory = $true)] 24 | [string]$Path) 25 | 26 | [string]$longFormPath = Get-FullNormalizedPath -Path $Path 27 | if ($longFormPath -and !$longFormPath.StartsWith('\\?')) { 28 | if ($longFormPath.StartsWith('\\')) { 29 | # E.g. \\server\share -> \\?\UNC\server\share 30 | return "\\?\UNC$($longFormPath.Substring(1))" 31 | } else { 32 | # E.g. C:\directory -> \\?\C:\directory 33 | return "\\?\$longFormPath" 34 | } 35 | } 36 | 37 | return $longFormPath 38 | } 39 | 40 | # TODO: ADD A SWITCH TO EXCLUDE FILES, A SWITCH TO EXCLUDE DIRECTORIES, AND A SWITCH NOT TO FOLLOW REPARSE POINTS. 41 | function Get-DirectoryChildItem { 42 | [CmdletBinding()] 43 | param( 44 | [string]$Path, 45 | [ValidateNotNullOrEmpty()] 46 | [Parameter()] 47 | [string]$Filter = "*", 48 | [switch]$Force, 49 | [VstsTaskSdk.FS.FindFlags]$Flags = [VstsTaskSdk.FS.FindFlags]::LargeFetch, 50 | [VstsTaskSdk.FS.FindInfoLevel]$InfoLevel = [VstsTaskSdk.FS.FindInfoLevel]::Basic, 51 | [switch]$Recurse) 52 | 53 | $stackOfDirectoryQueues = New-Object System.Collections.Stack 54 | while ($true) { 55 | $directoryQueue = New-Object System.Collections.Queue 56 | $fileQueue = New-Object System.Collections.Queue 57 | $findData = New-Object VstsTaskSdk.FS.FindData 58 | $longFormPath = (ConvertTo-LongFormPath $Path) 59 | $handle = $null 60 | try { 61 | $handle = [VstsTaskSdk.FS.NativeMethods]::FindFirstFileEx( 62 | [System.IO.Path]::Combine($longFormPath, $Filter), 63 | $InfoLevel, 64 | $findData, 65 | [VstsTaskSdk.FS.FindSearchOps]::NameMatch, 66 | [System.IntPtr]::Zero, 67 | $Flags) 68 | if (!$handle.IsInvalid) { 69 | while ($true) { 70 | if ($findData.fileName -notin '.', '..') { 71 | $attributes = [VstsTaskSdk.FS.Attributes]$findData.fileAttributes 72 | # If the item is hidden, check if $Force is specified. 73 | if ($Force -or !$attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Hidden)) { 74 | # Create the item. 75 | $item = New-Object -TypeName psobject -Property @{ 76 | 'Attributes' = $attributes 77 | 'FullName' = (ConvertFrom-LongFormPath -Path ([System.IO.Path]::Combine($Path, $findData.fileName))) 78 | 'Name' = $findData.fileName 79 | } 80 | # Output directories immediately. 81 | if ($item.Attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) { 82 | $item 83 | # Append to the directory queue if recursive and default filter. 84 | if ($Recurse -and $Filter -eq '*') { 85 | $directoryQueue.Enqueue($item) 86 | } 87 | } else { 88 | # Hold the files until all directories have been output. 89 | $fileQueue.Enqueue($item) 90 | } 91 | } 92 | } 93 | 94 | if (!([VstsTaskSdk.FS.NativeMethods]::FindNextFile($handle, $findData))) { break } 95 | 96 | if ($handle.IsInvalid) { 97 | throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @( 98 | [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 99 | Get-LocString -Key PSLIB_EnumeratingSubdirectoriesFailedForPath0 -ArgumentList $Path 100 | )) 101 | } 102 | } 103 | } 104 | } finally { 105 | if ($handle -ne $null) { $handle.Dispose() } 106 | } 107 | 108 | # If recursive and non-default filter, queue child directories. 109 | if ($Recurse -and $Filter -ne '*') { 110 | $findData = New-Object VstsTaskSdk.FS.FindData 111 | $handle = $null 112 | try { 113 | $handle = [VstsTaskSdk.FS.NativeMethods]::FindFirstFileEx( 114 | [System.IO.Path]::Combine($longFormPath, '*'), 115 | [VstsTaskSdk.FS.FindInfoLevel]::Basic, 116 | $findData, 117 | [VstsTaskSdk.FS.FindSearchOps]::NameMatch, 118 | [System.IntPtr]::Zero, 119 | $Flags) 120 | if (!$handle.IsInvalid) { 121 | while ($true) { 122 | if ($findData.fileName -notin '.', '..') { 123 | $attributes = [VstsTaskSdk.FS.Attributes]$findData.fileAttributes 124 | # If the item is hidden, check if $Force is specified. 125 | if ($Force -or !$attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Hidden)) { 126 | # Collect directories only. 127 | if ($attributes.HasFlag([VstsTaskSdk.FS.Attributes]::Directory)) { 128 | # Create the item. 129 | $item = New-Object -TypeName psobject -Property @{ 130 | 'Attributes' = $attributes 131 | 'FullName' = (ConvertFrom-LongFormPath -Path ([System.IO.Path]::Combine($Path, $findData.fileName))) 132 | 'Name' = $findData.fileName 133 | } 134 | $directoryQueue.Enqueue($item) 135 | } 136 | } 137 | } 138 | 139 | if (!([VstsTaskSdk.FS.NativeMethods]::FindNextFile($handle, $findData))) { break } 140 | 141 | if ($handle.IsInvalid) { 142 | throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @( 143 | [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 144 | Get-LocString -Key PSLIB_EnumeratingSubdirectoriesFailedForPath0 -ArgumentList $Path 145 | )) 146 | } 147 | } 148 | } 149 | } finally { 150 | if ($handle -ne $null) { $handle.Dispose() } 151 | } 152 | } 153 | 154 | # Output the files. 155 | $fileQueue 156 | 157 | # Push the directory queue onto the stack if any directories were found. 158 | if ($directoryQueue.Count) { $stackOfDirectoryQueues.Push($directoryQueue) } 159 | 160 | # Break out of the loop if no more directory queues to process. 161 | if (!$stackOfDirectoryQueues.Count) { break } 162 | 163 | # Get the next path. 164 | $directoryQueue = $stackOfDirectoryQueues.Peek() 165 | $Path = $directoryQueue.Dequeue().FullName 166 | 167 | # Pop the directory queue if it's empty. 168 | if (!$directoryQueue.Count) { $null = $stackOfDirectoryQueues.Pop() } 169 | } 170 | } 171 | 172 | function Get-FullNormalizedPath { 173 | [CmdletBinding()] 174 | param( 175 | [Parameter(Mandatory = $true)] 176 | [string]$Path) 177 | 178 | [string]$outPath = $Path 179 | [uint32]$bufferSize = [VstsTaskSdk.FS.NativeMethods]::GetFullPathName($Path, 0, $null, $null) 180 | [int]$lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 181 | if ($bufferSize -gt 0) { 182 | $absolutePath = New-Object System.Text.StringBuilder([int]$bufferSize) 183 | [uint32]$length = [VstsTaskSdk.FS.NativeMethods]::GetFullPathName($Path, $bufferSize, $absolutePath, $null) 184 | $lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 185 | if ($length -gt 0) { 186 | $outPath = $absolutePath.ToString() 187 | } else { 188 | throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @( 189 | $lastWin32Error 190 | Get-LocString -Key PSLIB_PathLengthNotReturnedFor0 -ArgumentList $Path 191 | )) 192 | } 193 | } else { 194 | throw (New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList @( 195 | $lastWin32Error 196 | Get-LocString -Key PSLIB_PathLengthNotReturnedFor0 -ArgumentList $Path 197 | )) 198 | } 199 | 200 | if ($outPath.EndsWith('\') -and !$outPath.EndsWith(':\')) { 201 | $outPath = $outPath.TrimEnd('\') 202 | } 203 | 204 | $outPath 205 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Minimatch.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v1/ps_modules/VstsTaskSdk/Minimatch.dll -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/OutFunctions.ps1: -------------------------------------------------------------------------------- 1 | # TODO: It would be better if the Out-Default function resolved the underlying Out-Default 2 | # command in the begin block. This would allow for supporting other modules that override 3 | # Out-Default. 4 | $script:outDefaultCmdlet = $ExecutionContext.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\Out-Default") 5 | 6 | ######################################## 7 | # Public functions. 8 | ######################################## 9 | function Out-Default { 10 | [CmdletBinding(ConfirmImpact = "Medium")] 11 | param( 12 | [Parameter(ValueFromPipeline = $true)] 13 | [System.Management.Automation.PSObject]$InputObject) 14 | 15 | begin { 16 | #Write-Host '[Entering Begin Out-Default]' 17 | $__sp = { & $script:outDefaultCmdlet @PSBoundParameters }.GetSteppablePipeline() 18 | $__sp.Begin($pscmdlet) 19 | #Write-Host '[Leaving Begin Out-Default]' 20 | } 21 | 22 | process { 23 | #Write-Host '[Entering Process Out-Default]' 24 | if ($_ -is [System.Management.Automation.ErrorRecord]) { 25 | Write-Verbose -Message 'Error record:' 4>&1 | Out-Default 26 | Write-Verbose -Message (Remove-TrailingNewLine (Out-String -InputObject $_ -Width 2147483647)) 4>&1 | Out-Default 27 | Write-Verbose -Message 'Script stack trace:' 4>&1 | Out-Default 28 | Write-Verbose -Message "$($_.ScriptStackTrace)" 4>&1 | Out-Default 29 | Write-Verbose -Message 'Exception:' 4>&1 | Out-Default 30 | Write-Verbose -Message $_.Exception.ToString() 4>&1 | Out-Default 31 | Write-TaskError -Message $_.Exception.Message 32 | } elseif ($_ -is [System.Management.Automation.WarningRecord]) { 33 | Write-TaskWarning -Message (Remove-TrailingNewLine (Out-String -InputObject $_ -Width 2147483647)) 34 | } elseif ($_ -is [System.Management.Automation.VerboseRecord] -and !$global:__vstsNoOverrideVerbose) { 35 | foreach ($private:str in (Format-DebugMessage -Object $_)) { 36 | Write-TaskVerbose -Message $private:str 37 | } 38 | } elseif ($_ -is [System.Management.Automation.DebugRecord] -and !$global:__vstsNoOverrideVerbose) { 39 | foreach ($private:str in (Format-DebugMessage -Object $_)) { 40 | Write-TaskDebug -Message $private:str 41 | } 42 | } else { 43 | # TODO: Consider using out-string here to control the width. As a security precaution it would actually be best to set it to max so wrapping doesn't interfere with secret masking. 44 | $__sp.Process($_) 45 | } 46 | 47 | #Write-Host '[Leaving Process Out-Default]' 48 | } 49 | 50 | end { 51 | #Write-Host '[Entering End Out-Default]' 52 | $__sp.End() 53 | #Write-Host '[Leaving End Out-Default]' 54 | } 55 | } 56 | 57 | ######################################## 58 | # Private functions. 59 | ######################################## 60 | function Format-DebugMessage { 61 | [CmdletBinding()] 62 | param([psobject]$Object) 63 | 64 | $private:str = Out-String -InputObject $Object -Width 2147483647 65 | $private:str = Remove-TrailingNewLine $private:str 66 | "$private:str".Replace("`r`n", "`n").Replace("`r", "`n").Split("`n"[0]) 67 | } 68 | 69 | function Remove-TrailingNewLine { 70 | [CmdletBinding()] 71 | param($Str) 72 | if ([object]::ReferenceEquals($Str, $null)) { 73 | return $Str 74 | } elseif ($Str.EndsWith("`r`n")) { 75 | return $Str.Substring(0, $Str.Length - 2) 76 | } else { 77 | return $Str 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/PSGetModuleInfo.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v1/ps_modules/VstsTaskSdk/PSGetModuleInfo.xml -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/de-de/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Der Containerpfad wurde nicht gefunden: \"{0}\".", 3 | "loc.messages.PSLIB_EndpointAuth0": "\"{0}\"-Dienstendpunkt-Anmeldeinformationen", 4 | "loc.messages.PSLIB_EndpointUrl0": "\"{0}\"-Dienstendpunkt-URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Fehler beim Aufzählen von Unterverzeichnissen für den folgenden Pfad: \"{0}\"", 6 | "loc.messages.PSLIB_FileNotFound0": "Die Datei wurde nicht gefunden: \"{0}\".", 7 | "loc.messages.PSLIB_Input0": "\"{0}\"-Eingabe", 8 | "loc.messages.PSLIB_InvalidPattern0": "Ungültiges Muster: \"{0}\"", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Der Blattpfad wurde nicht gefunden: \"{0}\".", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Fehler bei der Normalisierung bzw. Erweiterung des Pfads. Die Pfadlänge wurde vom Kernel32-Subsystem nicht zurückgegeben für: \"{0}\"", 11 | "loc.messages.PSLIB_PathNotFound0": "Der Pfad wurde nicht gefunden: \"{0}\".", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Der Prozess \"{0}\" wurde mit dem Code \"{1}\" beendet.", 13 | "loc.messages.PSLIB_Required0": "Erforderlich: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Fehler beim Zeichenfolgenformat.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Der Zeichenfolgen-Ressourcenschlüssel wurde nicht gefunden: \"{0}\".", 16 | "loc.messages.PSLIB_TaskVariable0": "\"{0}\"-Taskvariable" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/en-US/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_AgentVersion0Required": "Agent version {0} or higher is required.", 3 | "loc.messages.PSLIB_ContainerPathNotFound0": "Container path not found: '{0}'", 4 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' service endpoint credentials", 5 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' service endpoint URL", 6 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Enumerating subdirectories failed for path: '{0}'", 7 | "loc.messages.PSLIB_FileNotFound0": "File not found: '{0}'", 8 | "loc.messages.PSLIB_Input0": "'{0}' input", 9 | "loc.messages.PSLIB_InvalidPattern0": "Invalid pattern: '{0}'", 10 | "loc.messages.PSLIB_LeafPathNotFound0": "Leaf path not found: '{0}'", 11 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for: '{0}'", 12 | "loc.messages.PSLIB_PathNotFound0": "Path not found: '{0}'", 13 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Process '{0}' exited with code '{1}'.", 14 | "loc.messages.PSLIB_Required0": "Required: {0}", 15 | "loc.messages.PSLIB_StringFormatFailed": "String format failed.", 16 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "String resource key not found: '{0}'", 17 | "loc.messages.PSLIB_TaskVariable0": "'{0}' task variable" 18 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/es-es/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "No se encuentra la ruta de acceso del contenedor: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Credenciales del punto de conexión de servicio '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL del punto de conexión de servicio '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "No se pudieron enumerar los subdirectorios de la ruta de acceso: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "Archivo no encontrado: '{0}'", 7 | "loc.messages.PSLIB_Input0": "Entrada '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Patrón no válido: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "No se encuentra la ruta de acceso de la hoja: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "No se pudo normalizar o expandir la ruta de acceso. El subsistema Kernel32 no devolvió la longitud de la ruta de acceso para: '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "No se encuentra la ruta de acceso: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "El proceso '{0}' finalizó con el código '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Se requiere: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Error de formato de cadena.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "No se encuentra la clave de recurso de la cadena: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variable de tarea '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/fr-fr/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Le chemin du conteneur est introuvable : '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Informations d'identification du point de terminaison de service '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL du point de terminaison de service '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Échec de l'énumération des sous-répertoires pour le chemin : '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "Fichier introuvable : {0}.", 7 | "loc.messages.PSLIB_Input0": "Entrée '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Modèle non valide : '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Le chemin feuille est introuvable : '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Échec de la normalisation/l'expansion du chemin. La longueur du chemin n'a pas été retournée par le sous-système Kernel32 pour : '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "Chemin introuvable : '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Le processus '{0}' s'est arrêté avec le code '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Obligatoire : {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Échec du format de la chaîne.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Clé de la ressource de type chaîne introuvable : '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variable de tâche '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/it-IT/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Percorso del contenitore non trovato: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "Credenziali dell'endpoint servizio '{0}'", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL dell'endpoint servizio '{0}'", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "L'enumerazione delle sottodirectory per il percorso '{0}' non è riuscita", 6 | "loc.messages.PSLIB_FileNotFound0": "File non trovato: '{0}'", 7 | "loc.messages.PSLIB_Input0": "Input di '{0}'", 8 | "loc.messages.PSLIB_InvalidPattern0": "Criterio non valido: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Percorso foglia non trovato: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "La normalizzazione o l'espansione del percorso non è riuscita. Il sottosistema Kernel32 non ha restituito la lunghezza del percorso per '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "Percorso non trovato: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Il processo '{0}' è stato terminato ed è stato restituito il codice '{1}'.", 13 | "loc.messages.PSLIB_Required0": "Obbligatorio: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Errore nel formato della stringa.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "La chiave della risorsa stringa non è stata trovata: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "Variabile dell'attività '{0}'" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ja-jp/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "コンテナーのパスが見つかりません: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' サービス エンドポイントの資格情報", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' サービス エンドポイントの URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "パス '{0}' のサブディレクトリを列挙できませんでした", 6 | "loc.messages.PSLIB_FileNotFound0": "ファイルが見つかりません: '{0}'", 7 | "loc.messages.PSLIB_Input0": "'{0}' 入力", 8 | "loc.messages.PSLIB_InvalidPattern0": "使用できないパターンです: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "リーフ パスが見つかりません: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "パスの正規化/展開に失敗しました。Kernel32 サブシステムからパス '{0}' の長さが返されませんでした", 11 | "loc.messages.PSLIB_PathNotFound0": "パスが見つかりません: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "プロセス '{0}' がコード '{1}' で終了しました。", 13 | "loc.messages.PSLIB_Required0": "必要: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "文字列のフォーマットに失敗しました。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "文字列のリソース キーが見つかりません: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' タスク変数" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ko-KR/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "컨테이너 경로를 찾을 수 없음: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' 서비스 끝점 자격 증명", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' 서비스 끝점 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "경로에 대해 하위 디렉터리를 열거하지 못함: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "{0} 파일을 찾을 수 없습니다.", 7 | "loc.messages.PSLIB_Input0": "'{0}' 입력", 8 | "loc.messages.PSLIB_InvalidPattern0": "잘못된 패턴: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Leaf 경로를 찾을 수 없음: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "경로 정규화/확장에 실패했습니다. 다음에 대해 Kernel32 subsystem에서 경로 길이를 반환하지 않음: '{0}'", 11 | "loc.messages.PSLIB_PathNotFound0": "경로를 찾을 수 없음: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "'{1}' 코드로 '{0}' 프로세스가 종료되었습니다.", 13 | "loc.messages.PSLIB_Required0": "필수: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "문자열을 포맷하지 못했습니다.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "문자열 리소스 키를 찾을 수 없음: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' 작업 변수" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/ru-RU/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "Путь к контейнеру не найден: \"{0}\".", 3 | "loc.messages.PSLIB_EndpointAuth0": "Учетные данные конечной точки службы \"{0}\"", 4 | "loc.messages.PSLIB_EndpointUrl0": "URL-адрес конечной точки службы \"{0}\"", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Сбой перечисления подкаталогов для пути: \"{0}\".", 6 | "loc.messages.PSLIB_FileNotFound0": "Файл не найден: \"{0}\".", 7 | "loc.messages.PSLIB_Input0": "Входные данные \"{0}\".", 8 | "loc.messages.PSLIB_InvalidPattern0": "Недопустимый шаблон: \"{0}\".", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "Путь к конечному объекту не найден: \"{0}\".", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "Сбой нормализации и расширения пути. Длина пути не была возвращена подсистемой Kernel32 для: \"{0}\".", 11 | "loc.messages.PSLIB_PathNotFound0": "Путь не найден: \"{0}\".", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "Процесс \"{0}\" завершил работу с кодом \"{1}\".", 13 | "loc.messages.PSLIB_Required0": "Требуется: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "Сбой формата строки.", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "Ключ ресурса строки не найден: \"{0}\".", 16 | "loc.messages.PSLIB_TaskVariable0": "Переменная задачи \"{0}\"" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/zh-CN/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路径:“{0}”", 3 | "loc.messages.PSLIB_EndpointAuth0": "“{0}”服务终结点凭据", 4 | "loc.messages.PSLIB_EndpointUrl0": "“{0}”服务终结点 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "枚举路径的子目录失败:“{0}”", 6 | "loc.messages.PSLIB_FileNotFound0": "找不到文件: {0}。", 7 | "loc.messages.PSLIB_Input0": "“{0}”输入", 8 | "loc.messages.PSLIB_InvalidPattern0": "无效的模式:“{0}”", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "找不到叶路径:“{0}”", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路径规范化/扩展失败。路径长度不是由“{0}”的 Kernel32 子系统返回的", 11 | "loc.messages.PSLIB_PathNotFound0": "找不到路径:“{0}”", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "过程“{0}”已退出,代码为“{1}”。", 13 | "loc.messages.PSLIB_Required0": "必需: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "字符串格式无效。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字符串资源关键字:“{0}”", 16 | "loc.messages.PSLIB_TaskVariable0": "“{0}”任务变量" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/Strings/resources.resjson/zh-TW/resources.resjson: -------------------------------------------------------------------------------- 1 | { 2 | "loc.messages.PSLIB_ContainerPathNotFound0": "找不到容器路徑: '{0}'", 3 | "loc.messages.PSLIB_EndpointAuth0": "'{0}' 服務端點認證", 4 | "loc.messages.PSLIB_EndpointUrl0": "'{0}' 服務端點 URL", 5 | "loc.messages.PSLIB_EnumeratingSubdirectoriesFailedForPath0": "為路徑列舉子目錄失敗: '{0}'", 6 | "loc.messages.PSLIB_FileNotFound0": "找不到檔案: '{0}'", 7 | "loc.messages.PSLIB_Input0": "'{0}' 輸入", 8 | "loc.messages.PSLIB_InvalidPattern0": "模式無效: '{0}'", 9 | "loc.messages.PSLIB_LeafPathNotFound0": "找不到分葉路徑: '{0}'", 10 | "loc.messages.PSLIB_PathLengthNotReturnedFor0": "路徑正規化/展開失敗。Kernel32 子系統未傳回 '{0}' 的路徑長度", 11 | "loc.messages.PSLIB_PathNotFound0": "找不到路徑: '{0}'", 12 | "loc.messages.PSLIB_Process0ExitedWithCode1": "處理序 '{0}' 以返回碼 '{1}' 結束。", 13 | "loc.messages.PSLIB_Required0": "必要項: {0}", 14 | "loc.messages.PSLIB_StringFormatFailed": "字串格式失敗。", 15 | "loc.messages.PSLIB_StringResourceKeyNotFound0": "找不到字串資源索引鍵: '{0}'", 16 | "loc.messages.PSLIB_TaskVariable0": "'{0}' 工作變數" 17 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/ToolFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Asserts the agent version is at least the specified minimum. 4 | 5 | .PARAMETER Minimum 6 | Minimum version - must be 2.104.1 or higher. 7 | #> 8 | function Assert-Agent { 9 | [CmdletBinding()] 10 | param( 11 | [Parameter(Mandatory = $true)] 12 | [version]$Minimum) 13 | 14 | if (([version]'2.104.1').CompareTo($Minimum) -ge 1) { 15 | Write-Error "Assert-Agent requires the parameter to be 2.104.1 or higher." 16 | return 17 | } 18 | 19 | $agent = Get-TaskVariable -Name 'agent.version' 20 | if (!$agent -or $Minimum.CompareTo([version]$agent) -ge 1) { 21 | Write-Error (Get-LocString -Key 'PSLIB_AgentVersion0Required' -ArgumentList $Minimum) 22 | } 23 | } 24 | 25 | <# 26 | .SYNOPSIS 27 | Asserts that a path exists. Throws if the path does not exist. 28 | 29 | .PARAMETER PassThru 30 | True to return the path. 31 | #> 32 | function Assert-Path { 33 | [CmdletBinding()] 34 | param( 35 | [Parameter(Mandatory = $true)] 36 | [string]$LiteralPath, 37 | [Microsoft.PowerShell.Commands.TestPathType]$PathType = [Microsoft.PowerShell.Commands.TestPathType]::Any, 38 | [switch]$PassThru) 39 | 40 | if ($PathType -eq [Microsoft.PowerShell.Commands.TestPathType]::Any) { 41 | Write-Verbose "Asserting path exists: '$LiteralPath'" 42 | } else { 43 | Write-Verbose "Asserting $("$PathType".ToLowerInvariant()) path exists: '$LiteralPath'" 44 | } 45 | 46 | if (Test-Path -LiteralPath $LiteralPath -PathType $PathType) { 47 | if ($PassThru) { 48 | return $LiteralPath 49 | } 50 | 51 | return 52 | } 53 | 54 | $resourceKey = switch ($PathType) { 55 | ([Microsoft.PowerShell.Commands.TestPathType]::Container) { "PSLIB_ContainerPathNotFound0" ; break } 56 | ([Microsoft.PowerShell.Commands.TestPathType]::Leaf) { "PSLIB_LeafPathNotFound0" ; break } 57 | default { "PSLIB_PathNotFound0" } 58 | } 59 | 60 | throw (Get-LocString -Key $resourceKey -ArgumentList $LiteralPath) 61 | } 62 | 63 | <# 64 | .SYNOPSIS 65 | Executes an external program. 66 | 67 | .DESCRIPTION 68 | Executes an external program and waits for the process to exit. 69 | 70 | After calling this command, the exit code of the process can be retrieved from the variable $LASTEXITCODE. 71 | 72 | .PARAMETER Encoding 73 | This parameter not required for most scenarios. Indicates how to interpret the encoding from the external program. An example use case would be if an external program outputs UTF-16 XML and the output needs to be parsed. 74 | 75 | .PARAMETER RequireExitCodeZero 76 | Indicates whether to write an error to the error pipeline if the exit code is not zero. 77 | #> 78 | function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS? 79 | [CmdletBinding()] 80 | param( 81 | [ValidatePattern('^[^\r\n]*$')] 82 | [Parameter(Mandatory = $true)] 83 | [string]$FileName, 84 | [ValidatePattern('^[^\r\n]*$')] 85 | [Parameter()] 86 | [string]$Arguments, 87 | [string]$WorkingDirectory, 88 | [System.Text.Encoding]$Encoding, 89 | [switch]$RequireExitCodeZero) 90 | 91 | Trace-EnteringInvocation $MyInvocation 92 | $isPushed = $false 93 | $originalEncoding = $null 94 | try { 95 | if ($Encoding) { 96 | $originalEncoding = [System.Console]::OutputEncoding 97 | [System.Console]::OutputEncoding = $Encoding 98 | } 99 | 100 | if ($WorkingDirectory) { 101 | Push-Location -LiteralPath $WorkingDirectory -ErrorAction Stop 102 | $isPushed = $true 103 | } 104 | 105 | $FileName = $FileName.Replace('"', '').Replace("'", "''") 106 | Write-Host "##[command]""$FileName"" $Arguments" 107 | Invoke-Expression "& '$FileName' --% $Arguments" 108 | Write-Verbose "Exit code: $LASTEXITCODE" 109 | if ($RequireExitCodeZero -and $LASTEXITCODE -ne 0) { 110 | Write-Error (Get-LocString -Key PSLIB_Process0ExitedWithCode1 -ArgumentList ([System.IO.Path]::GetFileName($FileName)), $LASTEXITCODE) 111 | } 112 | } finally { 113 | if ($originalEncoding) { 114 | [System.Console]::OutputEncoding = $originalEncoding 115 | } 116 | 117 | if ($isPushed) { 118 | Pop-Location 119 | } 120 | 121 | Trace-LeavingInvocation $MyInvocation 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/TraceFunctions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Writes verbose information about the invocation being entered. 4 | 5 | .DESCRIPTION 6 | Used to trace verbose information when entering a function/script. Writes an entering message followed by a short description of the invocation. Additionally each bound parameter and unbound argument is also traced. 7 | 8 | .PARAMETER Parameter 9 | Wildcard pattern to control which bound parameters are traced. 10 | #> 11 | function Trace-EnteringInvocation { 12 | [CmdletBinding()] 13 | param( 14 | [Parameter(Mandatory = $true)] 15 | [System.Management.Automation.InvocationInfo]$InvocationInfo, 16 | [string[]]$Parameter = '*') 17 | 18 | Write-Verbose "Entering $(Get-InvocationDescription $InvocationInfo)." 19 | $OFS = ", " 20 | if ($InvocationInfo.BoundParameters.Count -and $Parameter.Count) { 21 | if ($Parameter.Count -eq 1 -and $Parameter[0] -eq '*') { 22 | # Trace all parameters. 23 | foreach ($key in $InvocationInfo.BoundParameters.Keys) { 24 | Write-Verbose " $($key): '$($InvocationInfo.BoundParameters[$key])'" 25 | } 26 | } else { 27 | # Trace matching parameters. 28 | foreach ($key in $InvocationInfo.BoundParameters.Keys) { 29 | foreach ($p in $Parameter) { 30 | if ($key -like $p) { 31 | Write-Verbose " $($key): '$($InvocationInfo.BoundParameters[$key])'" 32 | break 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | # Trace all unbound arguments. 40 | if (@($InvocationInfo.UnboundArguments).Count) { 41 | for ($i = 0 ; $i -lt $InvocationInfo.UnboundArguments.Count ; $i++) { 42 | Write-Verbose " args[$i]: '$($InvocationInfo.UnboundArguments[$i])'" 43 | } 44 | } 45 | } 46 | 47 | <# 48 | .SYNOPSIS 49 | Writes verbose information about the invocation being left. 50 | 51 | .DESCRIPTION 52 | Used to trace verbose information when leaving a function/script. Writes a leaving message followed by a short description of the invocation. 53 | #> 54 | function Trace-LeavingInvocation { 55 | [CmdletBinding()] 56 | param( 57 | [Parameter(Mandatory = $true)] 58 | [System.Management.Automation.InvocationInfo]$InvocationInfo) 59 | 60 | Write-Verbose "Leaving $(Get-InvocationDescription $InvocationInfo)." 61 | } 62 | 63 | <# 64 | .SYNOPSIS 65 | Writes verbose information about paths. 66 | 67 | .DESCRIPTION 68 | Writes verbose information about the paths. The paths are sorted and a the common root is written only once, followed by each relative path. 69 | 70 | .PARAMETER PassThru 71 | Indicates whether to return the sorted paths. 72 | #> 73 | function Trace-Path { 74 | [CmdletBinding()] 75 | param( 76 | [string[]]$Path, 77 | [switch]$PassThru) 78 | 79 | if ($Path.Count -eq 0) { 80 | Write-Verbose "No paths." 81 | if ($PassThru) { 82 | $Path 83 | } 84 | } elseif ($Path.Count -eq 1) { 85 | Write-Verbose "Path: $($Path[0])" 86 | if ($PassThru) { 87 | $Path 88 | } 89 | } else { 90 | # Find the greatest common root. 91 | $sorted = $Path | Sort-Object 92 | $firstPath = $sorted[0].ToCharArray() 93 | $lastPath = $sorted[-1].ToCharArray() 94 | $commonEndIndex = 0 95 | $j = if ($firstPath.Length -lt $lastPath.Length) { $firstPath.Length } else { $lastPath.Length } 96 | for ($i = 0 ; $i -lt $j ; $i++) { 97 | if ($firstPath[$i] -eq $lastPath[$i]) { 98 | if ($firstPath[$i] -eq '\') { 99 | $commonEndIndex = $i 100 | } 101 | } else { 102 | break 103 | } 104 | } 105 | 106 | if ($commonEndIndex -eq 0) { 107 | # No common root. 108 | Write-Verbose "Paths:" 109 | foreach ($p in $sorted) { 110 | Write-Verbose " $p" 111 | } 112 | } else { 113 | Write-Verbose "Paths: $($Path[0].Substring(0, $commonEndIndex + 1))" 114 | foreach ($p in $sorted) { 115 | Write-Verbose " $($p.Substring($commonEndIndex + 1))" 116 | } 117 | } 118 | 119 | if ($PassThru) { 120 | $sorted 121 | } 122 | } 123 | } 124 | 125 | ######################################## 126 | # Private functions. 127 | ######################################## 128 | function Get-InvocationDescription { 129 | [CmdletBinding()] 130 | param([System.Management.Automation.InvocationInfo]$InvocationInfo) 131 | 132 | if ($InvocationInfo.MyCommand.Path) { 133 | $InvocationInfo.MyCommand.Path 134 | } elseif ($InvocationInfo.MyCommand.Name) { 135 | $InvocationInfo.MyCommand.Name 136 | } else { 137 | $InvocationInfo.MyCommand.CommandType 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.dll -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v1/ps_modules/VstsTaskSdk/VstsTaskSdk.psd1 -------------------------------------------------------------------------------- /vsts-variable-set/v1/ps_modules/VstsTaskSdk/lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": { 3 | "PSLIB_AgentVersion0Required": "Agent version {0} or higher is required.", 4 | "PSLIB_ContainerPathNotFound0": "Container path not found: '{0}'", 5 | "PSLIB_EndpointAuth0": "'{0}' service endpoint credentials", 6 | "PSLIB_EndpointUrl0": "'{0}' service endpoint URL", 7 | "PSLIB_EnumeratingSubdirectoriesFailedForPath0": "Enumerating subdirectories failed for path: '{0}'", 8 | "PSLIB_FileNotFound0": "File not found: '{0}'", 9 | "PSLIB_Input0": "'{0}' input", 10 | "PSLIB_InvalidPattern0": "Invalid pattern: '{0}'", 11 | "PSLIB_LeafPathNotFound0": "Leaf path not found: '{0}'", 12 | "PSLIB_PathLengthNotReturnedFor0": "Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for: '{0}'", 13 | "PSLIB_PathNotFound0": "Path not found: '{0}'", 14 | "PSLIB_Process0ExitedWithCode1": "Process '{0}' exited with code '{1}'.", 15 | "PSLIB_Required0": "Required: {0}", 16 | "PSLIB_StringFormatFailed": "String format failed.", 17 | "PSLIB_StringResourceKeyNotFound0": "String resource key not found: '{0}'", 18 | "PSLIB_TaskVariable0": "'{0}' task variable" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3ca44a28-62de-4c60-8d77-a99065b95a8a", 3 | "name": "VariableSetTask", 4 | "friendlyName": "Set Variable", 5 | "description": "Sets a variable.", 6 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 7 | "category": "Utility", 8 | "author": "Jesse Houwing", 9 | "version": { 10 | "Major": 1, 11 | "Minor": 5, 12 | "Patch": 0 13 | }, 14 | "visibility": [ 15 | "Build", 16 | "Release" 17 | ], 18 | "minimumAgentVersion": "1.83.0", 19 | "groups": [ 20 | { 21 | "name": "Advanced", 22 | "displayName": "Advanced", 23 | "isExpanded": false 24 | } 25 | ], 26 | "inputs": [ 27 | { 28 | "defaultValue": "", 29 | "helpMarkdown": "Variable to set. Only supply the variable name and do not include `$(...)`.", 30 | "label": "Variable", 31 | "name": "VariableName", 32 | "required": true, 33 | "type": "string", 34 | "aliases": ["variableName"] 35 | }, 36 | { 37 | "defaultValue": "", 38 | "helpMarkdown": "The value to assign to the variable.", 39 | "label": "Value", 40 | "name": "Value", 41 | "required": false, 42 | "type": "string" 43 | }, 44 | { 45 | "defaultValue": false, 46 | "helpMarkdown": "Save variable as a secret.", 47 | "label": "Is Secret", 48 | "name": "IsSecret", 49 | "required": true, 50 | "type": "boolean" 51 | }, 52 | { 53 | "defaultValue": false, 54 | "helpMarkdown": "Use tasklib to set value. (Escapes `\\r`, `\\n` and `%`)", 55 | "label": "Use tasklib", 56 | "name": "useTasklib", 57 | "default": "false", 58 | "type": "boolean", 59 | "groupName": "Advanced" 60 | } 61 | ], 62 | "instanceNameFormat": "Set variable: $(VariableName) to: '$(Value)'", 63 | "execution": { 64 | "Node": { 65 | "target": "vsts-variable-set.js", 66 | "argumentFormat": "" 67 | }, 68 | "PowerShell": { 69 | "target": "$(currentDirectory)\\vsts-variable-set.v1.ps1", 70 | "argumentFormat": "", 71 | "platforms": [ 72 | "windows" 73 | ] 74 | }, 75 | "PowerShell3": { 76 | "target": "$(currentDirectory)\\vsts-variable-set.v3.ps1", 77 | "argumentFormat": "" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-set/v1/vsts-variable-set.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | 3 | const variable = tl.getInput("VariableName", true); 4 | const value = tl.getInput("Value"); 5 | const isSecret = tl.getBoolInput("isSecret") || false; 6 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 7 | 8 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 9 | if (useTaskLib) { 10 | tl.command("build.updatebuildnumber", null, value); 11 | } else { 12 | console.log(`##vso[build.updatebuildnumber]${value}`); 13 | } 14 | 15 | console.log(`Set buildnumber to: ${value}`); 16 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 17 | } else { 18 | if (useTaskLib) { 19 | tl.setVariable(variable, value, isSecret); 20 | } else { 21 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };]${value}`); 22 | } 23 | 24 | console.log(`Set ${variable} to: ${value}`); 25 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${value}`); 26 | } 27 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/vsts-variable-set.v1.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param( 3 | [ValidateNotNullOrEmpty()] 4 | [Parameter(Mandatory=$true)] 5 | $VariableName, 6 | [Parameter(Mandatory=$false)] 7 | $Value = "" 8 | [Parameter(Mandatory=$false)] 9 | $IsSecret = $false 10 | ) 11 | 12 | Write-Verbose "Entering script $($MyInvocation.MyCommand.Name)" 13 | Write-Verbose "Parameter Values" 14 | $PSBoundParameters.Keys | %{ Write-Verbose "$_ = $($PSBoundParameters[$_])" } 15 | 16 | Write-Output "Setting '$VariableName' to '$Value'." 17 | 18 | if ($VariableName -eq "Build.BuildNumber") 19 | { 20 | Write-Host "##vso[build.updatebuildnumber]$Value" 21 | } 22 | else 23 | { 24 | Write-Host "##vso[task.setvariable variable=$($VariableName);issecret=$($IsSecret)]$Value" 25 | } 26 | 27 | Write-Host "##vso[task.complete result=Succeeded;]DONE" 28 | -------------------------------------------------------------------------------- /vsts-variable-set/v1/vsts-variable-set.v3.ps1: -------------------------------------------------------------------------------- 1 | Write-Verbose "Entering script $($MyInvocation.MyCommand.Name)" 2 | Write-Verbose "Parameter Values" 3 | 4 | $VariableName = Get-VstsInput -name "VariableName" 5 | $Value = Get-VstsInput -name "Value" -default "" 6 | $IsSecret = Get-VstsInput -name "IsSecret" -default $false -AsBool 7 | 8 | Write-Output "Setting '$VariableName' to '$Value'." 9 | 10 | if ($VariableName -eq "Build.BuildNumber") 11 | { 12 | Write-VstsUpdateBuildNumber -value $Value 13 | } 14 | else 15 | { 16 | Set-VstsTaskVariable -name $VariableName -value $Value -Secret $IsSecret 17 | } 18 | 19 | Write-VstsSetResult -Result "Succeeded" -message "DONE" 20 | -------------------------------------------------------------------------------- /vsts-variable-set/v2/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v2/icon.png -------------------------------------------------------------------------------- /vsts-variable-set/v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-set", 3 | "version": "0.0.0", 4 | "description": "Set Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^3.4.0" 11 | }, 12 | "scripts": { 13 | "initdev:npm": "npm install", 14 | "initdev": "npm run initdev:npm", 15 | "build": "tsc && npm dedupe && npm prune --production" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^16.18.126" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vsts-variable-set/v2/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3ca44a28-62de-4c60-8d77-a99065b95a8a", 3 | "name": "VariableSetTask", 4 | "friendlyName": "Set Variable", 5 | "description": "Sets a variable.", 6 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 7 | "category": "Utility", 8 | "author": "Jesse Houwing", 9 | "version": { 10 | "Major": 2, 11 | "Minor": 1, 12 | "Patch": 0 13 | }, 14 | "visibility": [ 15 | "Build", 16 | "Release" 17 | ], 18 | "minimumAgentVersion": "1.83.0", 19 | "groups": [ 20 | { 21 | "name": "Advanced", 22 | "displayName": "Advanced", 23 | "isExpanded": false 24 | } 25 | ], 26 | "inputs": [ 27 | { 28 | "defaultValue": "", 29 | "helpMarkdown": "Variable to set. Only supply the variable name and do not include `$(...)`.", 30 | "label": "Variable", 31 | "name": "VariableName", 32 | "required": true, 33 | "type": "pickList", 34 | "options": { 35 | "": "", 36 | "build.buildnumber": "Build.BuildNumber", 37 | "release.releasename": "Release.ReleaseName" 38 | }, 39 | "properties": { 40 | "EditableOptions": "True" 41 | }, 42 | "aliases": ["variableName"] 43 | }, 44 | { 45 | "defaultValue": false, 46 | "helpMarkdown": "Backwards compat for Azure DevOps Server 2019 and older", 47 | "label": "Use Set Variable (backwards compat)", 48 | "name": "useSetVariableForReleaseName", 49 | "type": "boolean", 50 | "required": false, 51 | "visibleRule": "VariableName=release.releasename" 52 | }, 53 | { 54 | "defaultValue": "", 55 | "helpMarkdown": "The value to assign to the variable.", 56 | "label": "Value", 57 | "name": "Value", 58 | "required": false, 59 | "type": "string", 60 | "aliases": ["value"] 61 | }, 62 | { 63 | "defaultValue": false, 64 | "helpMarkdown": "Save variable as a secret.", 65 | "label": "Is Secret", 66 | "name": "IsSecret", 67 | "required": false, 68 | "type": "boolean", 69 | "visibleRule": "VariableName!=release.releasename && VariableName!=build.buildnumber" 70 | }, 71 | { 72 | "defaultValue": false, 73 | "helpMarkdown": "Save variable as an output.", 74 | "label": "Is Output", 75 | "name": "IsOutput", 76 | "required": false, 77 | "type": "boolean", 78 | "visibleRule": "VariableName!=release.releasename && VariableName!=build.buildnumber" 79 | }, 80 | { 81 | "defaultValue": true, 82 | "helpMarkdown": "Use tasklib to set value. (Escapes `\\r`, `\\n` and `%`)", 83 | "label": "Use tasklib", 84 | "name": "useTasklib", 85 | "default": "true", 86 | "type": "boolean", 87 | "groupName": "Advanced" 88 | } 89 | ], 90 | "instanceNameFormat": "Set variable: $(VariableName) to: '$(Value)'", 91 | "execution": { 92 | "Node": { 93 | "target": "vsts-variable-set.js", 94 | "argumentFormat": "" 95 | }, 96 | "Node10": { 97 | "target": "vsts-variable-set.js", 98 | "argumentFormat": "" 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /vsts-variable-set/v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-set/v2/vsts-variable-set.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | 3 | const variable = tl.getInput("VariableName", true); 4 | const value = tl.getInput("Value"); 5 | const isSecret = tl.getBoolInput("isSecret") || false; 6 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 7 | const useSetVariableForReleaseName = tl.getBoolInput("useSetVariableForReleaseName") || false; 8 | const isOutput = tl.getBoolInput("isOutput") || false; 9 | 10 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 11 | if (useTaskLib) { 12 | tl.updateBuildNumber(value); 13 | } else { 14 | console.log(`##vso[build.updatebuildnumber]${value}`); 15 | } 16 | 17 | console.log(`Set buildnumber to: ${value}`); 18 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 19 | } else if (!useSetVariableForReleaseName && variable.search(/^release[._]releasename$/i) >= 0) { 20 | if (useTaskLib) { 21 | tl.updateReleaseName(value); 22 | } else { 23 | console.log(`##vso[release.updatereleasename]${value}`); 24 | } 25 | 26 | console.log(`Set release name to: ${value}`); 27 | tl.setResult(tl.TaskResult.Succeeded, `Set release name to: ${value}`); 28 | } else { 29 | if (useTaskLib) { 30 | tl.setVariable(variable, value, isSecret, isOutput); 31 | const newValue=tl.getVariable(variable); 32 | console.log(`Set ${variable} to: ${newValue}`); 33 | } else { 34 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };isOutput=${ isOutput ? 'true' : 'false' };]${value}`); 35 | } 36 | 37 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${value}`); 38 | } 39 | -------------------------------------------------------------------------------- /vsts-variable-set/v3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-set/v3/icon.png -------------------------------------------------------------------------------- /vsts-variable-set/v3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-set", 3 | "version": "0.0.0", 4 | "description": "Set Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^5.2.1" 11 | }, 12 | "scripts": { 13 | "initdev:npm": "npm install", 14 | "initdev": "npm run initdev:npm", 15 | "build": "tsc && npm dedupe && npm prune --production" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20.19.18" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vsts-variable-set/v3/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3ca44a28-62de-4c60-8d77-a99065b95a8a", 3 | "name": "VariableSetTask", 4 | "friendlyName": "Set Variable", 5 | "description": "Sets a variable.", 6 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 7 | "category": "Utility", 8 | "author": "Jesse Houwing", 9 | "version": { 10 | "Major": 3, 11 | "Minor": 0, 12 | "Patch": 0 13 | }, 14 | "visibility": [ 15 | "Build", 16 | "Release" 17 | ], 18 | "minimumAgentVersion": "2.206.1", 19 | "groups": [ 20 | { 21 | "name": "Advanced", 22 | "displayName": "Advanced", 23 | "isExpanded": false 24 | } 25 | ], 26 | "inputs": [ 27 | { 28 | "defaultValue": "", 29 | "helpMarkdown": "Variable to set. Only supply the variable name and do not include `$(...)`.", 30 | "label": "Variable", 31 | "name": "VariableName", 32 | "required": true, 33 | "type": "pickList", 34 | "options": { 35 | "": "", 36 | "build.buildnumber": "Build.BuildNumber", 37 | "release.releasename": "Release.ReleaseName" 38 | }, 39 | "properties": { 40 | "EditableOptions": "True" 41 | }, 42 | "aliases": [ 43 | "variableName" 44 | ] 45 | }, 46 | { 47 | "defaultValue": false, 48 | "helpMarkdown": "Backwards compat for Azure DevOps Server 2019 and older", 49 | "label": "Use Set Variable (backwards compat)", 50 | "name": "useSetVariableForReleaseName", 51 | "type": "boolean", 52 | "required": false, 53 | "visibleRule": "VariableName=release.releasename" 54 | }, 55 | { 56 | "defaultValue": "value", 57 | "helpMarkdown": "Take the value from the input or an environment variable.", 58 | "label": "From", 59 | "name": "From", 60 | "required": true, 61 | "type": "pickList", 62 | "options": { 63 | "value": "value", 64 | "env": "env" 65 | } 66 | }, 67 | { 68 | "defaultValue": "", 69 | "helpMarkdown": "The value to assign to the variable.", 70 | "label": "Value", 71 | "name": "Value", 72 | "required": false, 73 | "type": "string", 74 | "aliases": [ 75 | "value" 76 | ], 77 | "visibleRule": "From=value" 78 | }, 79 | { 80 | "defaultValue": "", 81 | "helpMarkdown": "The value to assign to the variable.", 82 | "label": "Environment Variable", 83 | "name": "Env", 84 | "required": true, 85 | "type": "string", 86 | "aliases": [ 87 | "Env", 88 | "Environment" 89 | ], 90 | "visibleRule": "From=env" 91 | }, 92 | { 93 | "defaultValue": false, 94 | "helpMarkdown": "Save variable as a secret.", 95 | "label": "Is Secret", 96 | "name": "IsSecret", 97 | "required": false, 98 | "type": "boolean", 99 | "visibleRule": "VariableName!=release.releasename && VariableName!=build.buildnumber" 100 | }, 101 | { 102 | "defaultValue": false, 103 | "helpMarkdown": "Save variable as an output.", 104 | "label": "Is Output", 105 | "name": "IsOutput", 106 | "required": false, 107 | "type": "boolean", 108 | "visibleRule": "VariableName!=release.releasename && VariableName!=build.buildnumber" 109 | }, 110 | { 111 | "defaultValue": true, 112 | "helpMarkdown": "Use tasklib to set value. (Escapes `\\r`, `\\n` and `%`)", 113 | "label": "Use tasklib", 114 | "name": "useTasklib", 115 | "default": "true", 116 | "type": "boolean", 117 | "groupName": "Advanced" 118 | } 119 | ], 120 | "instanceNameFormat": "Set variable: $(VariableName) to: '$(Value)'", 121 | "execution": { 122 | "Node16": { 123 | "target": "vsts-variable-set.js", 124 | "argumentFormat": "" 125 | }, 126 | "Node10": { 127 | "target": "vsts-variable-set.js", 128 | "argumentFormat": "" 129 | }, 130 | "Node20_1": { 131 | "target": "vsts-variable-set.js", 132 | "argumentFormat": "" 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /vsts-variable-set/v3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-set/v3/vsts-variable-set.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | 3 | const variable = tl.getInput("VariableName", true); 4 | 5 | function getValue() 6 | { 7 | const from = tl.getInput("From") || "value"; 8 | switch (from) 9 | { 10 | case "value": 11 | { 12 | return tl.getInput("Value"); 13 | } 14 | case "env": 15 | { 16 | return process.env[tl.getInput("Env", true)]; 17 | } 18 | default: 19 | { 20 | return ""; 21 | } 22 | } 23 | } 24 | 25 | const value = getValue() 26 | const isSecret = tl.getBoolInput("isSecret") || false; 27 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 28 | const useSetVariableForReleaseName = tl.getBoolInput("useSetVariableForReleaseName") || false; 29 | const isOutput = tl.getBoolInput("isOutput") || false; 30 | 31 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 32 | if (useTaskLib) { 33 | tl.updateBuildNumber(value); 34 | } else { 35 | console.log(`##vso[build.updatebuildnumber]${value}`); 36 | } 37 | 38 | console.log(`Set buildnumber to: ${value}`); 39 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 40 | } else if (!useSetVariableForReleaseName && variable.search(/^release[._]releasename$/i) >= 0) { 41 | if (useTaskLib) { 42 | tl.updateReleaseName(value); 43 | } else { 44 | console.log(`##vso[release.updatereleasename]${value}`); 45 | } 46 | 47 | console.log(`Set release name to: ${value}`); 48 | tl.setResult(tl.TaskResult.Succeeded, `Set release name to: ${value}`); 49 | } else { 50 | if (useTaskLib) { 51 | tl.setVariable(variable, value, isSecret); 52 | const newValue=tl.getVariable(variable); 53 | console.log(`Set ${variable} to: ${newValue}`); 54 | } else { 55 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };isOutput=${ isOutput ? 'true' : 'false' };]${value}`); 56 | } 57 | 58 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${value}`); 59 | } 60 | -------------------------------------------------------------------------------- /vsts-variable-transform/v1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-transform/v1/icon.png -------------------------------------------------------------------------------- /vsts-variable-transform/v1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-transform", 3 | "version": "0.0.0", 4 | "description": "Transform Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^3.4.0", 11 | "core-js": "^3.45.1" 12 | }, 13 | "scripts": { 14 | "initdev:npm": "npm install", 15 | "initdev": "npm run initdev:npm", 16 | "build": "tsc && npm dedupe && npm prune --production" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^16.18.126" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vsts-variable-transform/v1/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9ccfb3bb-7ec0-4626-8599-164b1f95f933", 3 | "name": "VariableTransformTask", 4 | "friendlyName": "Transform value", 5 | "description": "Transform value and assign to variable.", 6 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 7 | "category": "Utility", 8 | "author": "Jesse Houwing", 9 | "version": { 10 | "Major": 1, 11 | "Minor": 5, 12 | "Patch": 0 13 | }, 14 | "visibility": [ 15 | "Build", 16 | "Release" 17 | ], 18 | "minimumAgentVersion": "1.83.0", 19 | "groups": [ 20 | { 21 | "name": "Manipulation", 22 | "displayName": "Manipulation (executed in displayed order)", 23 | "isExpanded": true 24 | }, 25 | { 26 | "name": "Transformation", 27 | "displayName": "Transformation", 28 | "isExpanded": true 29 | }, 30 | { 31 | "name": "Advanced", 32 | "displayName": "Advanced", 33 | "isExpanded": false 34 | } 35 | ], 36 | "inputs": [ 37 | { 38 | "defaultValue": "", 39 | "helpMarkdown": "Input value. You can use other variables as input as with any other text input using the `$(...)` notation anywhere in the value.", 40 | "label": "Input Value", 41 | "name": "value", 42 | "required": false, 43 | "type": "string" 44 | }, 45 | { 46 | "defaultValue": "", 47 | "helpMarkdown": "Variable to set. Only supply the variable name and do not include `$(...)`.", 48 | "label": "Output Variable", 49 | "name": "variableName", 50 | "required": true, 51 | "type": "string" 52 | }, 53 | { 54 | "defaultValue": false, 55 | "helpMarkdown": "Save variable as a secret.", 56 | "label": "Is Secret", 57 | "name": "IsSecret", 58 | "required": true, 59 | "type": "boolean" 60 | }, 61 | 62 | { 63 | "name": "transformAction", 64 | "type": "pickList", 65 | "label": "Action", 66 | "required": true, 67 | "helpMarkDown": "Optionally transform/escape the value. Pick from base64, uri, uri component or slashes.", 68 | "defaultValue": "none", 69 | "options": { 70 | "none": "None", 71 | "base64": "Base 64", 72 | "uri": "Uri", 73 | "uriComponent": "Uri Component", 74 | "slashes": "Slashes" 75 | }, 76 | "groupName": "Transformation" 77 | }, 78 | 79 | { 80 | "name": "encodeOrDecode", 81 | "type": "radio", 82 | "label": "Direction", 83 | "required": true, 84 | "helpMarkDown": "Pick between encode or decode using the selcted action.", 85 | "options": { 86 | "encodeString": "Encode", 87 | "decodeString": "Decode" 88 | }, 89 | "groupName": "Transformation", 90 | "defaultValue": "encodeString", 91 | "visibleRule": "transformAction != none" 92 | }, 93 | { 94 | "name": "pointInTime", 95 | "type": "radio", 96 | "label": "Apply Transformation", 97 | "required": true, 98 | "helpMarkDown": "Apply the selected transformation before or after the basic string manipulations. Generally you'd use before when encoding and after when decoding.", 99 | "options": { 100 | "beforeManipulation": "Before", 101 | "afterManipulation": "After manipulation" 102 | }, 103 | "defaultValue": "afterManipulation", 104 | "visibleRule": "transformAction != none", 105 | "groupName": "Transformation" 106 | }, 107 | 108 | 109 | { 110 | "name": "searchReplace", 111 | "type": "boolean", 112 | "label": "Search and Replace", 113 | "helpMarkDown": "Search and replace values.", 114 | "groupName": "Manipulation" 115 | }, 116 | { 117 | "name": "searchReplaceMethod", 118 | "type": "pickList", 119 | "label": "Method", 120 | "required": true, 121 | "defaultValue": "basic", 122 | "helpMarkDown": "Choose from basic (exact match), regex replace or regex match (to extract a value).", 123 | "options": { 124 | "basic": "Basic", 125 | "regex": "Regex Replace", 126 | "match": "Regex Match" 127 | }, 128 | "groupName": "Manipulation", 129 | "visibleRule": "searchReplace=true" 130 | }, 131 | 132 | { 133 | "name": "searchValue", 134 | "type": "multiLine", 135 | "label": "Search", 136 | "helpMarkDown": "Value or Expression to search. If you selected Regex Replace or Regex Match, you can use the Javascript regex syntax. See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.10].", 137 | "groupName": "Manipulation", 138 | "visibleRule": "searchReplace=true" 139 | }, 140 | 141 | { 142 | "name": "replacementValue", 143 | "type": "multiLine", 144 | "label": "Replacement", 145 | "helpMarkDown": "Replacement value. Use `$1` to use the value of a capturing group. See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11].", 146 | "groupName": "Manipulation", 147 | "visibleRule": "searchReplace=true && searchReplaceMethod!=match" 148 | }, 149 | 150 | { 151 | "name": "regexOptions", 152 | "type": "string", 153 | "label": "Regex Options", 154 | "helpMarkDown": "Specifies the regex options. Use `gim`, `g` - global replace, `i` - case insensitive, `m` - multiline (dot matches newline). See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.10.4.1].", 155 | "groupName": "Manipulation", 156 | "visibleRule": "searchReplace=true && searchReplaceMethod!=basic" 157 | }, 158 | 159 | 160 | 161 | 162 | 163 | 164 | { 165 | "name": "trim", 166 | "type": "boolean", 167 | "label": "Trim", 168 | "helpMarkDown": "Trims all whitespace around the value.", 169 | "groupName": "Manipulation" 170 | }, 171 | 172 | { 173 | "name": "slice", 174 | "type": "boolean", 175 | "label": "Slice", 176 | "helpMarkDown": "Allows you to select a part of the value.", 177 | "groupName": "Manipulation" 178 | }, 179 | { 180 | "name": "sliceLeft", 181 | "type": "string", 182 | "required": true, 183 | "label": "Left", 184 | "helpMarkDown": "Number of characters to remove from the start of the value.", 185 | "groupName": "Manipulation", 186 | "visibleRule": "slice=true" 187 | }, 188 | { 189 | "name": "sliceRight", 190 | "type": "string", 191 | "required": true, 192 | "label": "Right", 193 | "helpMarkDown": "Number of characters to remove from the end of the value. (use negative notation, e.g. `-1`).", 194 | "groupName": "Manipulation", 195 | "visibleRule": "slice=true" 196 | }, 197 | 198 | { 199 | "name": "substring", 200 | "label": "Substring", 201 | "type": "boolean", 202 | "helpMarkDown": "Allows you to select a part of the value.", 203 | "groupName": "Manipulation" 204 | }, 205 | { 206 | "name": "substringType", 207 | "type": "radio", 208 | "label": "Type", 209 | "helpMarkDown": "Select the type of method to use to take the part of value.", 210 | "groupName": "Manipulation", 211 | "default": "substring", 212 | "options": { 213 | "substring": "Substring", 214 | "left": "Left", 215 | "right": "Right" 216 | }, 217 | "visibleRule": "substring=true" 218 | }, 219 | { 220 | "name": "substringStart", 221 | "type": "string", 222 | "required": true, 223 | "label": "Start", 224 | "helpMarkDown": "Start the substring at the index specified.", 225 | "groupName": "Manipulation", 226 | "visibleRule": "substring=true && substringType=substring" 227 | }, 228 | { 229 | "name": "substringLength", 230 | "type": "string", 231 | "required": false, 232 | "label": "Length", 233 | "helpMarkDown": "Take this number of characters from the specified index (substring), beginning of the string (left) or end of the string (right).", 234 | "groupName": "Manipulation", 235 | "visibleRule": "substring=true" 236 | }, 237 | 238 | { 239 | "name": "casing", 240 | "type": "boolean", 241 | "label": "Change Case", 242 | "helpMarkDown": "Change the case of the value (always uses Invariant).", 243 | "groupName": "Manipulation" 244 | }, 245 | { 246 | "name": "casingType", 247 | "type": "radio", 248 | "label": "Type", 249 | "helpMarkDown": "Choose either upper or lowercase.", 250 | "groupName": "Manipulation", 251 | "options": { 252 | "toUpper": "Upper", 253 | "toLower": "Lower Case" 254 | }, 255 | "visibleRule": "casing=true" 256 | }, 257 | 258 | { 259 | "name": "pad", 260 | "type": "boolean", 261 | "label": "Pad", 262 | "helpMarkDown": "Pad the value", 263 | "groupName": "Manipulation" 264 | }, 265 | { 266 | "name": "padType", 267 | "type": "radio", 268 | "label": "Type", 269 | "required": true, 270 | "helpMarkDown": "Pads either left or right side of the value.", 271 | "defaultValue": "left", 272 | "groupName": "Manipulation", 273 | "options": { 274 | "left": "Left", 275 | "right": "Right" 276 | }, 277 | "visibleRule": "pad=true" 278 | }, 279 | { 280 | "name": "padChar", 281 | "type": "string", 282 | "label": "Character", 283 | "helpMarkDown": "Use this single character as padding. If none is specified, space ' ' is used.", 284 | "groupName": "Manipulation", 285 | "visibleRule": "pad=true" 286 | }, 287 | { 288 | "name": "padLength", 289 | "type": "string", 290 | "required": true, 291 | "label": "Length", 292 | "helpMarkDown": "Pad the value to the specified length is reached. If the value is already the same length or longer, the value will not be changed.", 293 | "groupName": "Manipulation", 294 | "visibleRule": "pad=true" 295 | }, 296 | 297 | { 298 | "defaultValue": false, 299 | "helpMarkdown": "Use tasklib to set value. (Escapes `\\r`, `\\n` and `%`)", 300 | "label": "Use tasklib", 301 | "name": "useTasklib", 302 | "default": "false", 303 | "type": "boolean", 304 | "groupName": "Advanced" 305 | } 306 | ], 307 | "instanceNameFormat": "Transform value '$(value)' and assign to variable: $(variableName)", 308 | "execution": { 309 | "Node": { 310 | "target": "vsts-variable-transform.js", 311 | "argumentFormat": "" 312 | } 313 | } 314 | } -------------------------------------------------------------------------------- /vsts-variable-transform/v1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-transform/v1/vsts-variable-transform.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | import "core-js"; 3 | 4 | const transformAction = tl.getInput("transformAction", true); 5 | let value = tl.getInput("value") || ""; 6 | const isSecret = tl.getBoolInput("isSecret") || false; 7 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 8 | 9 | if (transformAction !== "none") { 10 | tl.debug("Transformation selected."); 11 | const pointInTime = tl.getInput("pointInTime", true); 12 | if (pointInTime === "beforeManipulation") { 13 | tl.debug("Applying selected manipulations."); 14 | value = applyManipulations(value); 15 | } 16 | 17 | tl.debug("Applying selected transformation."); 18 | const option = tl.getInput("encodeOrDecode", false); 19 | switch (option) { 20 | case "encodeString": 21 | value = encodeString(value); 22 | break; 23 | 24 | case "decodeString": 25 | value = decodeString(value); 26 | break; 27 | } 28 | 29 | if (pointInTime === "afterManipulation") { 30 | tl.debug("Applying selected manipulations."); 31 | value = applyManipulations(value); 32 | } 33 | } else { 34 | tl.debug("Applying selected manipulations."); 35 | value = applyManipulations(value); 36 | } 37 | 38 | const variable = tl.getInput("variableName", true); 39 | 40 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 41 | if (useTaskLib) { 42 | tl.command("build.updatebuildnumber", null, value); 43 | } else { 44 | console.log(`##vso[build.updatebuildnumber]${value}`); 45 | } 46 | 47 | console.log(`Set buildnumber to: ${value}`); 48 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 49 | } else { 50 | if (useTaskLib) { 51 | tl.setVariable(variable, value, isSecret); 52 | } else { 53 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };]${value}`); 54 | } 55 | 56 | const newValue=tl.getVariable(variable); 57 | console.log(`Set ${variable} to: ${newValue}`); 58 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${newValue}`); 59 | } 60 | 61 | function applyManipulations(value: string): string { 62 | if (tl.getBoolInput("searchReplace", false)) { 63 | tl.debug("Applying selected Search & Replace."); 64 | value = searchAndReplace(value); 65 | } 66 | 67 | if (tl.getBoolInput("trim", false)) { 68 | tl.debug("Applying selected Trim."); 69 | value = value.trim(); 70 | } 71 | 72 | if (tl.getBoolInput("slice", false)) { 73 | tl.debug("Applying selected Slice."); 74 | const left = tl.getInput("sliceLeft", true); 75 | const right = tl.getInput("sliceRight", true); 76 | 77 | if (right) { 78 | value = value.slice(+left, +right); 79 | } else { 80 | value = value.slice(+left); 81 | } 82 | } 83 | 84 | if (tl.getBoolInput("substring", false)) { 85 | tl.debug("Applying selected Substring."); 86 | const substringType = tl.getInput("substringType", true); 87 | let length = 0; 88 | 89 | switch (substringType) { 90 | case "substring": 91 | { 92 | const start = +tl.getInput("substringStart", true); 93 | length = +tl.getInput("substringLength", false); 94 | 95 | if (length) { 96 | value = value.substring(start, length); 97 | } else { 98 | value = value.substring(start); 99 | } 100 | break; 101 | } 102 | case "left": 103 | length = +tl.getInput("substringLength", true); 104 | if (value.length > length) { 105 | value = value.substring(0, length); 106 | } 107 | break; 108 | case "right": 109 | length = +tl.getInput("substringLength", true); 110 | if (value.length > length) { 111 | value = value.substring(value.length - length); 112 | } 113 | break; 114 | } 115 | } 116 | 117 | if (tl.getBoolInput("casing", false)) { 118 | tl.debug("Applying selected Change Case."); 119 | const casingType = tl.getInput("casingType", true); 120 | 121 | switch (casingType) { 122 | case "toUpper": 123 | value = value.toUpperCase(); 124 | break; 125 | case "toLower": 126 | value = value.toLowerCase(); 127 | break; 128 | } 129 | } 130 | 131 | if (tl.getBoolInput("pad", false)) { 132 | tl.debug("Applying selected Pad."); 133 | const padType = tl.getInput("padType", true); 134 | let padCharacter = tl.getInput("padChar", false); 135 | if (!padCharacter) { 136 | padCharacter = " "; 137 | } 138 | 139 | const padLength = +tl.getInput("padLength", true); 140 | switch (padType) { 141 | case "left": 142 | value = value.padStart(padLength, padCharacter); 143 | break; 144 | case "right": 145 | value = value.padEnd(padLength, padCharacter); 146 | break; 147 | } 148 | } 149 | 150 | return value; 151 | } 152 | 153 | function searchAndReplace(value: string): string { 154 | const method = tl.getInput("searchReplaceMethod", true); 155 | const search = tl.getInput("searchValue") || ""; 156 | const replacement = tl.getInput("replacementValue") || ""; 157 | 158 | if (method === "basic") { 159 | return value.split(search).join(replacement); 160 | } else { 161 | const regexOptions = tl.getInput("regexOptions", false); 162 | let searchExpression: RegExp; 163 | 164 | if (regexOptions) { 165 | searchExpression = new RegExp(search, regexOptions); 166 | } else { 167 | searchExpression = new RegExp(search); 168 | } 169 | 170 | if (method === "match") { 171 | const result = value.match(searchExpression); 172 | if (!result || result.length === 0) { 173 | tl.warning("Found no matches"); 174 | return ""; 175 | } else { 176 | return result[0]; 177 | } 178 | } 179 | if (method === "regex") { 180 | return value.replace(searchExpression, replacement); 181 | } 182 | } 183 | return value; 184 | } 185 | 186 | function encodeString(value: string): string { 187 | const method = tl.getInput("transformAction", true); 188 | 189 | switch (method) { 190 | case "uri": 191 | return encodeURI(value); 192 | case "uriComponent": 193 | return encodeURIComponent(value); 194 | case "base64": 195 | { 196 | const buffer = new Buffer(value); 197 | return buffer.toString("base64"); 198 | } 199 | case "slashes": 200 | return addSlashes(value); 201 | } 202 | 203 | return "NOT IMPLEMENTED"; 204 | } 205 | 206 | function decodeString(value: string): string { 207 | const method = tl.getInput("transformAction", true); 208 | 209 | switch (method) { 210 | case "uri": 211 | return decodeURI(value); 212 | case "uriComponent": 213 | return decodeURIComponent(value); 214 | case "base64": 215 | { 216 | const buffer = new Buffer(value, "base64"); 217 | return buffer.toString(); 218 | } 219 | case "slashes": 220 | return stripSlashes(value); 221 | } 222 | 223 | return "NOT IMPLEMENTED"; 224 | } 225 | 226 | function stripSlashes(str: string): string { 227 | return str.replace(/\\(.?)/g, (s:string, n1:string) => { 228 | switch (n1) { 229 | case "\\": 230 | return "\\"; 231 | case "0": 232 | return "\u0000"; 233 | case "": 234 | return ""; 235 | default: 236 | return n1; 237 | } 238 | }); 239 | } 240 | 241 | function addSlashes(str: string): string { 242 | return str.replace(/[\\"']/g, "\\$&") 243 | .replace(/\u0000/g, "\\0"); 244 | } 245 | -------------------------------------------------------------------------------- /vsts-variable-transform/v2/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-transform/v2/icon.png -------------------------------------------------------------------------------- /vsts-variable-transform/v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-transform", 3 | "version": "0.0.0", 4 | "description": "Transform Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^3.4.0", 11 | "core-js": "^3.45.1" 12 | }, 13 | "scripts": { 14 | "initdev:npm": "npm install", 15 | "initdev": "npm run initdev:npm", 16 | "build": "tsc && npm dedupe && npm prune --production" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^16.18.126" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vsts-variable-transform/v2/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "9ccfb3bb-7ec0-4626-8599-164b1f95f933", 3 | "name": "VariableTransformTask", 4 | "friendlyName": "Transform value", 5 | "description": "Transform value and assign to variable.", 6 | "helpMarkDown": "Version: #{Build.BuildNumber}#. [More Information](https://github.com/jessehouwing/azure-pipelines-variable-tasks/wiki)", 7 | "category": "Utility", 8 | "author": "Jesse Houwing", 9 | "version": { 10 | "Major": 2, 11 | "Minor": 1, 12 | "Patch": 0 13 | }, 14 | "visibility": [ 15 | "Build", 16 | "Release" 17 | ], 18 | "minimumAgentVersion": "1.83.0", 19 | "groups": [ 20 | { 21 | "name": "Manipulation", 22 | "displayName": "Manipulation (executed in displayed order)", 23 | "isExpanded": true 24 | }, 25 | { 26 | "name": "Transformation", 27 | "displayName": "Transformation", 28 | "isExpanded": true 29 | }, 30 | { 31 | "name": "Advanced", 32 | "displayName": "Advanced", 33 | "isExpanded": false 34 | } 35 | ], 36 | "inputs": [ 37 | { 38 | "defaultValue": "", 39 | "helpMarkdown": "Input value. You can use other variables as input as with any other text input using the `$(...)` notation anywhere in the value.", 40 | "label": "Input Value", 41 | "name": "value", 42 | "required": false, 43 | "type": "string" 44 | }, 45 | { 46 | "defaultValue": "", 47 | "helpMarkdown": "Variable to set. Only supply the variable name and do not include `$(...)`.", 48 | "label": "Variable", 49 | "name": "variableName", 50 | "required": true, 51 | "type": "pickList", 52 | "options": { 53 | "": "", 54 | "build.buildnumber": "Build.BuildNumber", 55 | "release.releasename": "Release.ReleaseName" 56 | }, 57 | "properties": { 58 | "EditableOptions": "True" 59 | } 60 | }, 61 | { 62 | "defaultValue": false, 63 | "helpMarkdown": "Backwards compat for Azure DevOps Server 2019 and older", 64 | "label": "Use Set Variable (backwards compat)", 65 | "name": "useSetVariableForReleaseName", 66 | "type": "boolean", 67 | "required": false, 68 | "visibleRule": "variableName=release.releasename" 69 | }, 70 | { 71 | "defaultValue": false, 72 | "helpMarkdown": "Save variable as a secret.", 73 | "label": "Is Secret", 74 | "name": "IsSecret", 75 | "required": false, 76 | "type": "boolean", 77 | "visibleRule": "variableName!=release.releasename && variableName!=build.buildnumber" 78 | }, 79 | { 80 | "defaultValue": false, 81 | "helpMarkdown": "Save variable as an output.", 82 | "label": "Is Output", 83 | "name": "IsOutput", 84 | "required": false, 85 | "type": "boolean", 86 | "visibleRule": "variableName!=release.releasename && variableName!=build.buildnumber" 87 | }, 88 | { 89 | "name": "transformAction", 90 | "type": "pickList", 91 | "label": "Action", 92 | "required": false, 93 | "helpMarkDown": "Optionally transform/escape the value. Pick from base64, uri, uri component or slashes.", 94 | "defaultValue": "none", 95 | "options": { 96 | "none": "None", 97 | "base64": "Base 64", 98 | "uri": "Uri", 99 | "uriComponent": "Uri Component", 100 | "slashes": "Slashes" 101 | }, 102 | "groupName": "Transformation" 103 | }, 104 | { 105 | "name": "encodeOrDecode", 106 | "type": "radio", 107 | "label": "Direction", 108 | "required": true, 109 | "helpMarkDown": "Pick between encode or decode using the selcted action.", 110 | "options": { 111 | "encodeString": "Encode", 112 | "decodeString": "Decode" 113 | }, 114 | "groupName": "Transformation", 115 | "defaultValue": "encodeString", 116 | "visibleRule": "transformAction != none" 117 | }, 118 | { 119 | "name": "pointInTime", 120 | "type": "radio", 121 | "label": "Apply Transformation", 122 | "required": true, 123 | "helpMarkDown": "Apply the selected transformation before or after the basic string manipulations. Generally you'd use before when encoding and after when decoding.", 124 | "options": { 125 | "beforeManipulation": "Before", 126 | "afterManipulation": "After manipulation" 127 | }, 128 | "defaultValue": "afterManipulation", 129 | "visibleRule": "transformAction != none", 130 | "groupName": "Transformation" 131 | }, 132 | { 133 | "name": "searchReplace", 134 | "type": "boolean", 135 | "label": "Search and Replace", 136 | "helpMarkDown": "Search and replace values.", 137 | "groupName": "Manipulation" 138 | }, 139 | { 140 | "name": "searchReplaceMethod", 141 | "type": "pickList", 142 | "label": "Method", 143 | "required": true, 144 | "defaultValue": "basic", 145 | "helpMarkDown": "Choose from basic (exact match), regex replace or regex match (to extract a value).", 146 | "options": { 147 | "basic": "Basic", 148 | "regex": "Regex Replace", 149 | "match": "Regex Match" 150 | }, 151 | "groupName": "Manipulation", 152 | "visibleRule": "searchReplace=true" 153 | }, 154 | { 155 | "name": "searchValue", 156 | "type": "multiLine", 157 | "label": "Search", 158 | "helpMarkDown": "Value or Expression to search. If you selected Regex Replace or Regex Match, you can use the Javascript regex syntax. See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.10].", 159 | "groupName": "Manipulation", 160 | "visibleRule": "searchReplace=true" 161 | }, 162 | { 163 | "name": "replacementValue", 164 | "type": "multiLine", 165 | "label": "Replacement", 166 | "helpMarkDown": "Replacement value. Use `$1` to use the value of a capturing group. See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4.11].", 167 | "groupName": "Manipulation", 168 | "visibleRule": "searchReplace=true && searchReplaceMethod!=match" 169 | }, 170 | { 171 | "name": "regexOptions", 172 | "type": "string", 173 | "label": "Regex Options", 174 | "helpMarkDown": "Specifies the regex options. Use `gim`, `g` - global replace, `i` - case insensitive, `m` - multiline (dot matches newline). See also: (Ecmascript reference)[http://www.ecma-international.org/ecma-262/5.1/#sec-15.10.4.1].", 175 | "groupName": "Manipulation", 176 | "visibleRule": "searchReplace=true && searchReplaceMethod!=basic" 177 | }, 178 | { 179 | "name": "trim", 180 | "type": "boolean", 181 | "label": "Trim", 182 | "helpMarkDown": "Trims all whitespace around the value.", 183 | "groupName": "Manipulation" 184 | }, 185 | { 186 | "name": "slice", 187 | "type": "boolean", 188 | "label": "Slice", 189 | "helpMarkDown": "Allows you to select a part of the value.", 190 | "groupName": "Manipulation" 191 | }, 192 | { 193 | "name": "sliceLeft", 194 | "type": "string", 195 | "required": true, 196 | "label": "Left", 197 | "helpMarkDown": "Number of characters to remove from the start of the value.", 198 | "groupName": "Manipulation", 199 | "visibleRule": "slice=true" 200 | }, 201 | { 202 | "name": "sliceRight", 203 | "type": "string", 204 | "required": true, 205 | "label": "Right", 206 | "helpMarkDown": "Number of characters to remove from the end of the value. (use negative notation, e.g. `-1`).", 207 | "groupName": "Manipulation", 208 | "visibleRule": "slice=true" 209 | }, 210 | { 211 | "name": "substring", 212 | "label": "Substring", 213 | "type": "boolean", 214 | "helpMarkDown": "Allows you to select a part of the value.", 215 | "groupName": "Manipulation" 216 | }, 217 | { 218 | "name": "substringType", 219 | "type": "radio", 220 | "label": "Type", 221 | "helpMarkDown": "Select the type of method to use to take the part of value.", 222 | "groupName": "Manipulation", 223 | "default": "substring", 224 | "options": { 225 | "substring": "Substring", 226 | "left": "Left", 227 | "right": "Right" 228 | }, 229 | "visibleRule": "substring=true" 230 | }, 231 | { 232 | "name": "substringStart", 233 | "type": "string", 234 | "required": true, 235 | "label": "Start", 236 | "helpMarkDown": "Start the substring at the index specified.", 237 | "groupName": "Manipulation", 238 | "visibleRule": "substring=true && substringType=substring" 239 | }, 240 | { 241 | "name": "substringLength", 242 | "type": "string", 243 | "required": false, 244 | "label": "Length", 245 | "helpMarkDown": "Take this number of characters from the specified index (substring), beginning of the string (left) or end of the string (right).", 246 | "groupName": "Manipulation", 247 | "visibleRule": "substring=true" 248 | }, 249 | { 250 | "name": "casing", 251 | "type": "boolean", 252 | "label": "Change Case", 253 | "helpMarkDown": "Change the case of the value (always uses Invariant).", 254 | "groupName": "Manipulation" 255 | }, 256 | { 257 | "name": "casingType", 258 | "type": "radio", 259 | "label": "Type", 260 | "helpMarkDown": "Choose either upper or lowercase.", 261 | "groupName": "Manipulation", 262 | "options": { 263 | "toUpper": "Upper", 264 | "toLower": "Lower Case" 265 | }, 266 | "visibleRule": "casing=true" 267 | }, 268 | { 269 | "name": "pad", 270 | "type": "boolean", 271 | "label": "Pad", 272 | "helpMarkDown": "Pad the value", 273 | "groupName": "Manipulation" 274 | }, 275 | { 276 | "name": "padType", 277 | "type": "radio", 278 | "label": "Type", 279 | "required": true, 280 | "helpMarkDown": "Pads either left or right side of the value.", 281 | "defaultValue": "left", 282 | "groupName": "Manipulation", 283 | "options": { 284 | "left": "Left", 285 | "right": "Right" 286 | }, 287 | "visibleRule": "pad=true" 288 | }, 289 | { 290 | "name": "padChar", 291 | "type": "string", 292 | "label": "Character", 293 | "helpMarkDown": "Use this single character as padding. If none is specified, space ' ' is used.", 294 | "groupName": "Manipulation", 295 | "visibleRule": "pad=true" 296 | }, 297 | { 298 | "name": "padLength", 299 | "type": "string", 300 | "required": true, 301 | "label": "Length", 302 | "helpMarkDown": "Pad the value to the specified length is reached. If the value is already the same length or longer, the value will not be changed.", 303 | "groupName": "Manipulation", 304 | "visibleRule": "pad=true" 305 | }, 306 | { 307 | "defaultValue": false, 308 | "helpMarkdown": "Use tasklib to set value. (Escapes `\\r`, `\\n` and `%`)", 309 | "label": "Use tasklib", 310 | "name": "useTasklib", 311 | "default": "false", 312 | "type": "boolean", 313 | "groupName": "Advanced" 314 | } 315 | ], 316 | "instanceNameFormat": "Transform value '$(value)' and assign to variable: $(variableName)", 317 | "execution": { 318 | "Node": { 319 | "target": "vsts-variable-transform.js", 320 | "argumentFormat": "" 321 | }, 322 | "Node10": { 323 | "target": "vsts-variable-transform.js", 324 | "argumentFormat": "" 325 | } 326 | } 327 | } -------------------------------------------------------------------------------- /vsts-variable-transform/v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-transform/v2/vsts-variable-transform.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | import "core-js"; 3 | 4 | const transformAction = tl.getInput("transformAction", false) || "none"; 5 | let value = tl.getInput("value") || ""; 6 | const isSecret = tl.getBoolInput("isSecret") || false; 7 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 8 | const variable = tl.getInput("variableName", true); 9 | const useSetVariableForReleaseName = tl.getBoolInput("useSetVariableForReleaseName") || false; 10 | const isOutput = tl.getBoolInput("isOutput") || false; 11 | 12 | if (transformAction !== "none") { 13 | tl.debug("Transformation selected."); 14 | const pointInTime = tl.getInput("pointInTime", true); 15 | if (pointInTime === "beforeManipulation") { 16 | tl.debug("Applying selected manipulations."); 17 | value = applyManipulations(value); 18 | } 19 | 20 | tl.debug("Applying selected transformation."); 21 | const option = tl.getInput("encodeOrDecode", false); 22 | switch (option) { 23 | case "encodeString": 24 | value = encodeString(value); 25 | break; 26 | 27 | case "decodeString": 28 | value = decodeString(value); 29 | break; 30 | } 31 | 32 | if (pointInTime === "afterManipulation") { 33 | tl.debug("Applying selected manipulations."); 34 | value = applyManipulations(value); 35 | } 36 | } else { 37 | tl.debug("Applying selected manipulations."); 38 | value = applyManipulations(value); 39 | } 40 | 41 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 42 | if (useTaskLib) { 43 | tl.updateBuildNumber(value); 44 | } else { 45 | console.log(`##vso[build.updatebuildnumber]${value}`); 46 | } 47 | 48 | console.log(`Set buildnumber to: ${value}`); 49 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 50 | } else if (!useSetVariableForReleaseName && variable.search(/^release[._]releasename$/i) >= 0) { 51 | if (useTaskLib) { 52 | tl.updateReleaseName(value); 53 | } else { 54 | console.log(`##vso[release.updatereleasename]${value}`); 55 | } 56 | 57 | console.log(`Set release name to: ${value}`); 58 | tl.setResult(tl.TaskResult.Succeeded, `Set release name to: ${value}`); 59 | } else { 60 | if (useTaskLib) { 61 | tl.setVariable(variable, value, isSecret, isOutput); 62 | const newValue=tl.getVariable(variable); 63 | console.log(`Set ${variable} to: ${newValue}`); 64 | } else { 65 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };isOutput=${ isOutput ? 'true' : 'false' };]${value}`); 66 | } 67 | 68 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${value}`); 69 | } 70 | 71 | function applyManipulations(value: string): string { 72 | if (tl.getBoolInput("searchReplace", false)) { 73 | tl.debug("Applying selected Search & Replace."); 74 | value = searchAndReplace(value); 75 | } 76 | 77 | if (tl.getBoolInput("trim", false)) { 78 | tl.debug("Applying selected Trim."); 79 | value = value.trim(); 80 | } 81 | 82 | if (tl.getBoolInput("slice", false)) { 83 | tl.debug("Applying selected Slice."); 84 | const left = tl.getInput("sliceLeft", true); 85 | const right = tl.getInput("sliceRight", true); 86 | 87 | if (right) { 88 | value = value.slice(+left, +right); 89 | } else { 90 | value = value.slice(+left); 91 | } 92 | } 93 | 94 | if (tl.getBoolInput("substring", false)) { 95 | tl.debug("Applying selected Substring."); 96 | const substringType = tl.getInput("substringType", true); 97 | let length = 0; 98 | 99 | switch (substringType) { 100 | case "substring": 101 | { 102 | const start = +tl.getInput("substringStart", true); 103 | length = +tl.getInput("substringLength", false); 104 | 105 | if (length) { 106 | value = value.substring(start, length); 107 | } else { 108 | value = value.substring(start); 109 | } 110 | break; 111 | } 112 | case "left": 113 | length = +tl.getInput("substringLength", true); 114 | if (value.length > length) { 115 | value = value.substring(0, length); 116 | } 117 | break; 118 | case "right": 119 | length = +tl.getInput("substringLength", true); 120 | if (value.length > length) { 121 | value = value.substring(value.length - length); 122 | } 123 | break; 124 | } 125 | } 126 | 127 | if (tl.getBoolInput("casing", false)) { 128 | tl.debug("Applying selected Change Case."); 129 | const casingType = tl.getInput("casingType", true); 130 | 131 | switch (casingType) { 132 | case "toUpper": 133 | value = value.toUpperCase(); 134 | break; 135 | case "toLower": 136 | value = value.toLowerCase(); 137 | break; 138 | } 139 | } 140 | 141 | if (tl.getBoolInput("pad", false)) { 142 | tl.debug("Applying selected Pad."); 143 | const padType = tl.getInput("padType", true); 144 | let padCharacter = tl.getInput("padChar", false); 145 | if (!padCharacter) { 146 | padCharacter = " "; 147 | } 148 | 149 | const padLength = +tl.getInput("padLength", true); 150 | switch (padType) { 151 | case "left": 152 | value = value.padStart(padLength, padCharacter); 153 | break; 154 | case "right": 155 | value = value.padEnd(padLength, padCharacter); 156 | break; 157 | } 158 | } 159 | 160 | return value; 161 | } 162 | 163 | function searchAndReplace(value: string): string { 164 | const method = tl.getInput("searchReplaceMethod", true); 165 | const search = tl.getInput("searchValue") || ""; 166 | const replacement = tl.getInput("replacementValue") || ""; 167 | 168 | if (method === "basic") { 169 | return value.split(search).join(replacement); 170 | } else { 171 | const regexOptions = tl.getInput("regexOptions", false); 172 | let searchExpression: RegExp; 173 | 174 | if (regexOptions) { 175 | searchExpression = new RegExp(search, regexOptions); 176 | } else { 177 | searchExpression = new RegExp(search); 178 | } 179 | 180 | if (method === "match") { 181 | const result = value.match(searchExpression); 182 | if (!result || result.length === 0) { 183 | tl.warning("Found no matches"); 184 | return ""; 185 | } else { 186 | return result[0]; 187 | } 188 | } 189 | if (method === "regex") { 190 | return value.replace(searchExpression, replacement); 191 | } 192 | } 193 | return value; 194 | } 195 | 196 | function encodeString(value: string): string { 197 | const method = tl.getInput("transformAction", true); 198 | 199 | switch (method) { 200 | case "uri": 201 | return encodeURI(value); 202 | case "uriComponent": 203 | return encodeURIComponent(value); 204 | case "base64": 205 | { 206 | const buffer = new Buffer(value); 207 | return buffer.toString("base64"); 208 | } 209 | case "slashes": 210 | return addSlashes(value); 211 | } 212 | 213 | return "NOT IMPLEMENTED"; 214 | } 215 | 216 | function decodeString(value: string): string { 217 | const method = tl.getInput("transformAction", true); 218 | 219 | switch (method) { 220 | case "uri": 221 | return decodeURI(value); 222 | case "uriComponent": 223 | return decodeURIComponent(value); 224 | case "base64": 225 | { 226 | const buffer = new Buffer(value, "base64"); 227 | return buffer.toString(); 228 | } 229 | case "slashes": 230 | return stripSlashes(value); 231 | } 232 | 233 | return "NOT IMPLEMENTED"; 234 | } 235 | 236 | function stripSlashes(str: string): string { 237 | return str.replace(/\\(.?)/g, (s:string, n1:string) => { 238 | switch (n1) { 239 | case "\\": 240 | return "\\"; 241 | case "0": 242 | return "\u0000"; 243 | case "": 244 | return ""; 245 | default: 246 | return n1; 247 | } 248 | }); 249 | } 250 | 251 | function addSlashes(str: string): string { 252 | return str.replace(/[\\"']/g, "\\$&") 253 | .replace(/\u0000/g, "\\0"); 254 | } 255 | -------------------------------------------------------------------------------- /vsts-variable-transform/v3/.taskkey: -------------------------------------------------------------------------------- 1 | 542150ae-84b7-4391-ab24-034b9a6e09dc -------------------------------------------------------------------------------- /vsts-variable-transform/v3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessehouwing/azure-pipelines-variable-tasks/f9a10e99721c3e1d0740ea28486a48c1462384ce/vsts-variable-transform/v3/icon.png -------------------------------------------------------------------------------- /vsts-variable-transform/v3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vsts-variable-transform", 3 | "version": "0.0.0", 4 | "description": "Transform Variable", 5 | "author": { 6 | "name": "Jesse Houwing", 7 | "email": "jesse.houwing@gmail.com" 8 | }, 9 | "dependencies": { 10 | "azure-pipelines-task-lib": "^4.17.3" 11 | }, 12 | "scripts": { 13 | "initdev:npm": "npm install", 14 | "initdev": "npm run initdev:npm", 15 | "build": "tsc && npm dedupe && npm prune --production" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^24.6.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vsts-variable-transform/v3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": ".", 6 | "rootDir": "." 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } -------------------------------------------------------------------------------- /vsts-variable-transform/v3/vsts-variable-transform.ts: -------------------------------------------------------------------------------- 1 | import * as tl from "azure-pipelines-task-lib/task"; 2 | 3 | const transformAction = tl.getInput("transformAction", false) || "none"; 4 | 5 | function getValue() 6 | { 7 | const from = tl.getInput("From") || "value"; 8 | switch (from) 9 | { 10 | case "value": 11 | { 12 | return tl.getInput("Value"); 13 | } 14 | case "env": 15 | { 16 | return process.env[tl.getInput("Env", true)]; 17 | } 18 | default: 19 | { 20 | return ""; 21 | } 22 | } 23 | } 24 | 25 | let value = getValue() || ""; 26 | 27 | const isSecret = tl.getBoolInput("isSecret") || false; 28 | const useTaskLib = tl.getBoolInput("useTasklib") || false; 29 | const variable = tl.getInput("variableName", true); 30 | const useSetVariableForReleaseName = tl.getBoolInput("useSetVariableForReleaseName") || false; 31 | const isOutput = tl.getBoolInput("isOutput") || false; 32 | 33 | if (transformAction !== "none") { 34 | tl.debug("Transformation selected."); 35 | const pointInTime = tl.getInput("pointInTime", true); 36 | if (pointInTime === "beforeManipulation") { 37 | tl.debug("Applying selected manipulations."); 38 | value = applyManipulations(value); 39 | } 40 | 41 | tl.debug("Applying selected transformation."); 42 | const option = tl.getInput("encodeOrDecode", false); 43 | switch (option) { 44 | case "encodeString": 45 | value = encodeString(value); 46 | break; 47 | 48 | case "decodeString": 49 | value = decodeString(value); 50 | break; 51 | } 52 | 53 | if (pointInTime === "afterManipulation") { 54 | tl.debug("Applying selected manipulations."); 55 | value = applyManipulations(value); 56 | } 57 | } else { 58 | tl.debug("Applying selected manipulations."); 59 | value = applyManipulations(value); 60 | } 61 | 62 | if (variable.search(/^Build[._]BuildNumber$/i) >= 0) { 63 | if (useTaskLib) { 64 | tl.updateBuildNumber(value); 65 | } else { 66 | console.log(`##vso[build.updatebuildnumber]${value}`); 67 | } 68 | 69 | console.log(`Set buildnumber to: ${value}`); 70 | tl.setResult(tl.TaskResult.Succeeded, `Set buildnumber to: ${value}`); 71 | } else if (!useSetVariableForReleaseName && variable.search(/^release[._]releasename$/i) >= 0) { 72 | if (useTaskLib) { 73 | tl.updateReleaseName(value); 74 | } else { 75 | console.log(`##vso[release.updatereleasename]${value}`); 76 | } 77 | 78 | console.log(`Set release name to: ${value}`); 79 | tl.setResult(tl.TaskResult.Succeeded, `Set release name to: ${value}`); 80 | } else { 81 | if (useTaskLib) { 82 | tl.setVariable(variable, value, isSecret); 83 | const newValue=tl.getVariable(variable); 84 | console.log(`Set ${variable} to: ${newValue}`); 85 | } else { 86 | console.log(`##vso[task.setvariable variable=${variable};isSecret=${ isSecret ? 'true' : 'false' };isOutput=${ isOutput ? 'true' : 'false' };]${value}`); 87 | } 88 | 89 | tl.setResult(tl.TaskResult.Succeeded, `Set ${variable} to: ${value}`); 90 | } 91 | 92 | function applyManipulations(value: string): string { 93 | if (tl.getBoolInput("searchReplace", false)) { 94 | tl.debug("Applying selected Search & Replace."); 95 | value = searchAndReplace(value); 96 | } 97 | 98 | if (tl.getBoolInput("trim", false)) { 99 | tl.debug("Applying selected Trim."); 100 | value = value.trim(); 101 | } 102 | 103 | if (tl.getBoolInput("slice", false)) { 104 | tl.debug("Applying selected Slice."); 105 | const left = tl.getInput("sliceLeft", true); 106 | const right = tl.getInput("sliceRight", true); 107 | 108 | if (right) { 109 | value = value.slice(+left, +right); 110 | } else { 111 | value = value.slice(+left); 112 | } 113 | } 114 | 115 | if (tl.getBoolInput("substring", false)) { 116 | tl.debug("Applying selected Substring."); 117 | const substringType = tl.getInput("substringType", true); 118 | let length = 0; 119 | 120 | switch (substringType) { 121 | case "substring": 122 | { 123 | const start = +tl.getInput("substringStart", true); 124 | length = +tl.getInput("substringLength", false); 125 | 126 | if (length) { 127 | value = value.substring(start, length); 128 | } else { 129 | value = value.substring(start); 130 | } 131 | break; 132 | } 133 | case "left": 134 | length = +tl.getInput("substringLength", true); 135 | if (value.length > length) { 136 | value = value.substring(0, length); 137 | } 138 | break; 139 | case "right": 140 | length = +tl.getInput("substringLength", true); 141 | if (value.length > length) { 142 | value = value.substring(value.length - length); 143 | } 144 | break; 145 | } 146 | } 147 | 148 | if (tl.getBoolInput("casing", false)) { 149 | tl.debug("Applying selected Change Case."); 150 | const casingType = tl.getInput("casingType", true); 151 | 152 | switch (casingType) { 153 | case "toUpper": 154 | value = value.toUpperCase(); 155 | break; 156 | case "toLower": 157 | value = value.toLowerCase(); 158 | break; 159 | } 160 | } 161 | 162 | if (tl.getBoolInput("pad", false)) { 163 | tl.debug("Applying selected Pad."); 164 | const padType = tl.getInput("padType", true); 165 | let padCharacter = tl.getInput("padChar", false); 166 | if (!padCharacter) { 167 | padCharacter = " "; 168 | } 169 | 170 | const padLength = +tl.getInput("padLength", true); 171 | switch (padType) { 172 | case "left": 173 | value = value.padStart(padLength, padCharacter); 174 | break; 175 | case "right": 176 | value = value.padEnd(padLength, padCharacter); 177 | break; 178 | } 179 | } 180 | 181 | return value; 182 | } 183 | 184 | function searchAndReplace(value: string): string { 185 | const method = tl.getInput("searchReplaceMethod", true); 186 | const search = tl.getInput("searchValue") || ""; 187 | const replacement = tl.getInput("replacementValue") || ""; 188 | 189 | if (method === "basic") { 190 | return value.split(search).join(replacement); 191 | } else { 192 | const regexOptions = tl.getInput("regexOptions", false); 193 | let searchExpression: RegExp; 194 | 195 | if (regexOptions) { 196 | searchExpression = new RegExp(search, regexOptions); 197 | } else { 198 | searchExpression = new RegExp(search); 199 | } 200 | 201 | if (method === "match") { 202 | const result = value.match(searchExpression); 203 | if (!result || result.length === 0) { 204 | tl.warning("Found no matches"); 205 | return ""; 206 | } else { 207 | return result[0]; 208 | } 209 | } 210 | if (method === "regex") { 211 | return value.replace(searchExpression, replacement); 212 | } 213 | } 214 | return value; 215 | } 216 | 217 | function encodeString(value: string): string { 218 | const method = tl.getInput("transformAction", true); 219 | 220 | switch (method) { 221 | case "uri": 222 | return encodeURI(value); 223 | case "uriComponent": 224 | return encodeURIComponent(value); 225 | case "base64": 226 | { 227 | const buffer = Buffer.from(value); 228 | return buffer.toString("base64"); 229 | } 230 | case "slashes": 231 | return addSlashes(value); 232 | } 233 | 234 | return "NOT IMPLEMENTED"; 235 | } 236 | 237 | function decodeString(value: string): string { 238 | const method = tl.getInput("transformAction", true); 239 | 240 | switch (method) { 241 | case "uri": 242 | return decodeURI(value); 243 | case "uriComponent": 244 | return decodeURIComponent(value); 245 | case "base64": 246 | { 247 | const buffer = Buffer.from(value, "base64"); 248 | return buffer.toString(); 249 | } 250 | case "slashes": 251 | return stripSlashes(value); 252 | } 253 | 254 | return "NOT IMPLEMENTED"; 255 | } 256 | 257 | function stripSlashes(str: string): string { 258 | return str.replace(/\\(.?)/g, (s:string, n1:string) => { 259 | switch (n1) { 260 | case "\\": 261 | return "\\"; 262 | case "0": 263 | return "\u0000"; 264 | case "": 265 | return ""; 266 | default: 267 | return n1; 268 | } 269 | }); 270 | } 271 | 272 | function addSlashes(str: string): string { 273 | return str.replace(/[\\"']/g, "\\$&") 274 | .replace(/\u0000/g, "\\0"); 275 | } 276 | --------------------------------------------------------------------------------