Let’s start by saying the PowerShell is one of the most opaque and crappy languages that was ever created. I’m glad some people like it – but as far as me, it sucks.
- Why have two different syntaxes for environment variables – sometimes “$env” and sometimes “env”.
- Why special string escape character (backtick – “`”). Tradition escape character (“\”) is used in so many languages: Bash, C, C++, Java, PHP, Python, Ruby, SQL, Verilog.
Adding PowerShell to Azure YAML Build Script
Adding PowerShell scripts is easy. The example below shows how to create a script with embedded PowerShell code.
- task: PowerShell@2 displayName: 'Copy necessary generated files to artifacts.' inputs: targetType: 'inline' script: | Write-Host "Hello, world"
You can easily add PowerShell templates that can be called from other scripts by providing any necessary input parameters.
parameters: # "Major.Minor.Build" - name: mmb type: string steps: # Compute build number parts and if is Fortify build. - task: PowerShell@2 displayName: 'Process build information' inputs: targetType: 'inline' script: | write-host "mmb: ${{parameters.mmb}}" # Echo value into build log.
Example YAML Script Using PowerShell
Azure has numerous rules for accessing variable in a PowerShell script. Below are examples in a YAML file.
variables: BuildPlatform: 'Any CPU' DropStagingDirectory: '$(Build.ArtifactStagingDirectory)/drop' ... jobs: - job: BuildAll workspace: clean: all pool: name: '...' - steps - checkout: self - checkout: 'other repo' # Create artifact drop folder. - task: PowerShell@2 displayName: 'Drop: Create dest folder' inputs: targetType: inline script: | Write-Host "Build.ArtifactStagingDirectory: $(Build.ArtifactStagingDirectory)" # Echo value to build log $path = "$(Build.ArtifactStagingDirectory)\drop" if( -Not (Test-Path -Path "$(path)\drop")) { Write-Host "Creating drop folder" New-Item -path "$(Build.ArtifactStagingDirectory)" -name "drop" -ItemType "directory" Write-Host "Created folder: $(Build.ArtifactStagingDirectory)/drop/" } else { Write-Host "Folder already exists: $(Build.ArtifactStagingDirectory)/drop/" }
Access Variables and Environment Settings
From Microsoft: In a pipeline, template expression variables (${{ variables.var }}
) get processed at compile time, before runtime starts. Macro syntax variables ($(var)
) get processed during runtime before a task runs. Runtime expressions ($[variables.var]
) also get processed during runtime but are intended to be used with conditions and expressions. When you use a runtime expression, it must take up the entire right side of a definition.
# Copy current value of Build.SourceBranch to local variable. $sourceBranch = "$(Build.SourceBranch)" # Access environment variable. $branchname = "$env:BUILD_SOURCEBRANCH" $platform = "$env:BuildPlatform" # Copy initial value of variable locally. # This will be value before ANY changes by "##vso[task.setvariable variable=x]" $sourceBranch = "${{Build.SourceBranch}}"
PowerShell Strings and Variables
As in the previous section, you can embed variables in strings. Be aware that if the variable name has trailing characters, PowerShell can’t figure out the variable you want to embed. Also, if you misspell a variable name, PowerShell will simply output an empty string in that location.
$sourceBranch = "$(Build.SourceBranch)" # Write-Host "sb: $sourceBranch--$(Build.SourceBranch)" # Outputs: "sb: refs/heads/private/bob/it_FDC--refs/heads/private/bob/it_FDC" HWrite-Host "sb: $sourceBranchff--$(Build.SourceBranch)" # Output: "sb: --refs/heads/private/bob/it_FDC" # Notice first variable ($sourceBranchff) doesn't exist so rendered as "". Write-Host "sb: $($sourceBranch)ff--$(Build.SourceBranch)" # Output: "sb: refs/heads/private/bob/it_FDCff--refs/heads/private/bob/it_FDC" # Wrapped $sourceBranch in $() so trailing "ff" is ignored when determining the variable name.
The same rules apply to environment variables – but you must use the correct “$env” formatted name. PowerShell is not case sensitive, but you must change the “.” characters in the names to “_” characters. For example, the variable Build.SourceBranch
becomes the variable Build_SourceBranch
.
# Use this line to display all environment variables to console. Get-ChildItem env: # Example output. Includes 310 items for me. # BUILD_ARTIFACTSTAGINGDIRECTORY C:\a\1\a # BUILD_BINARIESDIRECTORY C:\a\1\b # BUILD_BUILDID 476273 Write-Host "sb: $($env:BUILD_SOURCEBRANCH)ff--$(Build.SourceBranch)" # Output: "sb: refs/heads/private/bob/it_FDCff--refs/heads/private/bob/it_FDC" Write-Host "sb: $env:BUILD_SOURCEbranch--$(Build.SourceBranch)" # Output: "sb: refs/heads/private/bob/it_FDC--refs/heads/private/bob/it_FDC"
Modify Variables
You can change the value of variables and environment settings by using the task.setvariable
. The simplest approach is to modify the value and in later tasks, the new value will be used if the code uses macro syntax ($(var)
). Note that the original values is still available as ${{ var }}
.
# Update CG_VERSION_REV with value of "$revision". Write-Host "##vso[task.setvariable variable=CG_VERSION_REV;]$revision
Determine If Environment Variable Exists and Is Not Empty String
In my case, I want to determine if a variable exists in the environment. I don’t care what its value is since I’m using its existence as a flag. Important to note is that environment variables are always string types. If the current value of variable is “”, then “Test-Path” will return $False
.
# If "FORCE_FORTIFY_BUILD" exists in environment, set variable to $True. # (We don't care about value - just that it exists.) $forceFortifyBuild = Test-Path env:FORCE_FORTIFY_BUILD # If user wants debug information added to build (by checking "Enable system diagnostics"), # variable will be set to "True". $debugBuild = Test-Path env:SYSTEM_DEBUG