Search code examples
azurepowershellazure-devopssonarqubeazure-powershell

Create Powershell env variables using built in System variables


I have a Powershell script that will make a GET request to SonarQube's REST API to check if the Quality Gate passes or fails. If the Quality Gate fails, the pipeline will fail. I am able to make this work when only looking at the master branch however, I am trying to look at all branches and pull requests.

My pipeline Powershell script:

  - job:
  
    pool:
      name: 'POEM-GBT-Agent'

    variables:
    - group: SonarQube

    displayName: 'SonarQube API'
    
    steps:
    - checkout: none
    - powershell: |
        $token = [System.Text.Encoding]::UTF8.GetBytes("$(SONARQUBE_API_TOKEN)" + ":")
        $base64 = [System.Convert]::ToBase64String($token)
        
        $basicAuth = [string]::Format("Basic {0}", $base64)
        $headers = @{ Authorization = $basicAuth }

        if ($(System.PullRequest.PullRequestId)) {
           $param = "pullRequest=$(System.PullRequest.PullRequestId)"
        }
        else {
          $param = "branch=$env:$BRANCH_NAME"
        }
        
        $result = Invoke-RestMethod -Method Get -Uri https://sonarqube.tjx.com/api/qualitygates/project_status?projectKey=$(sonarProjectKey)"&"$param -Headers $headers
        $result | ConvertTo-Json | Write-Host
        
        if ($result.projectStatus.status -ne "OK") {
          Write-Host "##vso[task.logissue type=error]Quality Gate Failed"
          Write-Host "##vso[task.complete result=Failed]"
        }

        env:
          BRANCH_NAME: replace('$(Build.SourceBranch)', 'refs/heads/', '')

This results in an error saying:

+   $param = "branch=$env:$BRANCH_NAME"
+                    ~~~~~
Variable reference is not valid. ':' was not followed by a valid variable name 
character. Consider using ${} to delimit the name.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : InvalidVariableReferenceWithDrive

After receiving this error I changed my conditional statement to:

        if ($(System.PullRequest.PullRequestId)) {
           $param = "pullRequest=$(System.PullRequest.PullRequestId)"
        }
        else {
          $param = "branch=${env}:$BRANCH_NAME"
        }

After changing my conditional I get this error:

System.PullRequest.PullRequestId : The term 'System.PullRequest.PullRequestId' 
is not recognized as the name of a cmdlet, function, script file, or operable 
program. Check the spelling of the name, or if a path was included, verify 
that the path is correct and try again.
At D:\GBT\agent\Workspace\_temp\0a756446-474a-4d58-94ff-ad25e38c3c7a.ps1:9 
char:7
+ if ($(System.PullRequest.PullRequestId)) {
+       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (System.PullRequest.PullRequestI 
   d:String) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CommandNotFoundException

I am trying to set $param to pullRequest=1234 if the Git PR is affected, otherwise, I want to set $param to something like branch=feature/my-branch-name.


Solution

  • You have two problems here. I'll delve into each below.


    The term 'System.PullRequest.PullRequestId' is not recognized

    Under your job step, define some environment variables. I have limited experience with Azure DevOps (will reference as ADO from here on) but you will need two under your powershell environment variable definitions (you already have BRANCH_NAME but I will include it below):

    - job:
      steps:
      - powershell: |
          YOUR
          CODE
          HERE
    
        env:
          BRANCH_NAME: replace('$(Build.SourceBranch)', 'refs/heads/', '')
          PULL_REQUEST_ID: $(System.PullRequest.PullRequestId)
    

    You need to define PULL_REQUEST_ID as an env var because your rendered PowerShell script won't render $() as an inserted value from ADO. This is likely by design as $() is syntax used in other programming languages, including PowerShell. This is the crux of your issue where System.PullRequest.PullRequestId can't be found as a command; PowerShell literally tries to use that as a program name which it can't find.

    Within your script, you can then reference the pull request ID with:

    $env:PULL_REQUEST_ID
    

    Variable reference is not valid. ':' was not followed by a valid variable name character

    The issue is distinct from the issue above, but the solution is the same. Just reference the environment variable as:

    $env:BRANCH_NAME
    

    You don't need the extra $, as this just confuses the parser.

    Now, you're not asking for this, but if you needed a conditional environment variable name (e.g. the name of the environment variable is sourced from some other ADO variable), you need to use a different syntax to access the environment variable, as the $ confuses the parser when accessed via the $env: syntax. Read on if you are curious about this.


    If you did need to access an environment variable whose name is conditional

    PowerShell doesn't allow special characters in variable names without specifying the variable name as a string. The notable exceptions are _ (no special meaning) and :, the latter of which is used as a separator for variables defined within a PSDrive. Environment variables are accessible via the Environment provider, which is accessed under the Env PSDrive. You can see this by running Get-PSDrive, there is an Environment provider exposed under the Env drive.

    You can reference PSProvider variables in a few ways, the most common way is $drive:path (the variable name is technically considered a path node under the provider). So to reference the UserProfile variable, you can use:

    $env:UserProfile # ====> Returns the path to your user profile directory
    

    The problem with your code is you have the following:

    $env:$VariableName
    

    In this case the intent is to get an environment variable value whose name is based on another variable's value, but this confuses the parser for this syntax, as $ is now being translated as a literal portion of the variable name, which is invalid. Normally you would use the ${} syntax to avoid this... but you can't here, because the confused portion should be rendered as part of that same variable. In this case, you need to use an alternative approach to access the environment variable, go through the provider directly.

    To use UserProfile as an example again:

    $envVarName = 'UserProfile'
    $envVarValue = (Get-ChildItem Env:/$envVarName).Value
    

    This is the other way to get a value from a PSProvider; traverse its contents as a drive. This works somewhat like a filesystem, though Get-ChildItem will return different properties for different provider types than the FileInfo or DirectoryInfo objects you may be used to from the Filesystem provider. It's a bit cumbersome, but luckily it's not a scenario one often needs to account for.