Use Azure DevOps service principal details in Azure Powershell task - azure-devops

When writing an Azure CLI script in a Azure DevOps pipeline you can get access to the the serviceprincpal id, key and tenantid. Is there a way to get this info in a Azure Powershell task?

The addSpnToEnvironment input which adds service principal id and key of the Azure endpoint you chose to the script's context is one option available only in Azure ClI Task, but not Azure Powershell Task.
Is there a way to get this info in a Azure Powershell task?
As an alternative workaround, we can define job-scoped variables in Azure ClI Task, check this document.
Steps to test:
1.Using latest Azure CLI task 2.0-preview and choose Powershell type. Try inline script like this:
Write-Host "##vso[task.setvariable variable=SpId;]$env:servicePrincipalId"
Write-Host "##vso[task.setvariable variable=SpKey;]$env:servicePrincipalKey"
Write-Host "##vso[task.setvariable variable=TenantId;]$env:tenantId"
Write-Host "##vso[task.setvariable variable=TestVar;]JustForTest"
2.Then add one Azure Powershell task after Azure CLI Task to test:
Write-Host $env:SpId
Write-Host $env:SpKey
Write-Host $env:TenantId
Write-Host $env:TestVar
3.Output:
So if you define the job-scoped variables using Write-Host "##vso[task.setvariable variable=xxx]xxx"(Powershell) or echo "##vso[task.setvariable variable=xxx]xxx"(Batch), you can then use something like $env:VariableName to access the info. The *** in log is because these are secrets projected by Azure Devops, so they're masked.

You can invoke a powershell script via Azure CLI task and use the 'addSpnToEnvironment' flag as explained in accepted answer.
- task: AzureCLI#2
displayName: 'Custom ps script'
inputs:
azureSubscription: ${{ parameters.serviceConnection }}
addSpnToEnvironment: true # Adds DevOps SP details to context
scriptType: pscore
scriptPath: '$(System.DefaultWorkingDirectory)/somescript.ps1'

Related

How to connect to azure devops from powershell in yaml pipelines?

We are trying to execute this az devops service-endpoint list in a powershell script added in a pipeline in yaml from Azure DevOps
$SubscriptionId = az devops service-endpoint list --organization "https://dev.azure.com/xxxxx" --project $ProjectName --query "[?data.subscriptionName==$SubscriptionName].data.subscriptionId" -o tsv
For executing the command above, The logs tell that we need to login using az devops login --organization https://dev.azure.com/xxxx/ , how do we do this in the pipeline?
Also we use the system access token variable for logging in like this:
$env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN
Thank you Tejas Nagchandi. Posting your suggestion as an answer so that it will be helpful for other community members as well who is facing similar kind of issues.
Below is the sample code of Azure CLI task and try to use that by which you can pass a service connection as input and with the help of this task no need to use az-login as well.
- task: AzureCLI#2
displayName: Azure CLI
inputs:
azureSubscription: <Name of the Azure Resource Manager service connection>
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
az --version
az account show
for further details check the Azure CLI task documentation.

##[error]Required: 'ConnectedServiceNameARM' input --- Azure DevOps

##[error]Required: 'ConnectedServiceNameARM' input
This is in Azure DevOps using YAML inline script.
Need help with what to enter to fix this error? I am really new at YAML. This is a inline YAML and what tried seems to break the YAML script. The ConnectedServiceNameARM is just the Azure Subscription name? My service connection in azure devops has a working azure subscription name so I am wondering what is wrong?
Also need this YAML code to run so that the output file is placed in agent/_work/_tasks folder and not the artifacts folder. How would I move the file from the _tasks/Powershell folder to something that can be copied to share?
trigger:
- main
pool:
name: 'CloudUiPath001'
demands:
- agent.name -equals UiPathAgent01
steps:
- task: AzurePowerShell#3
displayName: 'Azure PowerShell script: InlineScript'
inputs:
ScriptType: InlineScript
Inline: |
$filePath='C:\Program Files (x86)\UiPath\Studio'
$dir=(New-Object -com scripting.filesystemobject).getFolder($filePath).ShortPath
$ProjectFilePath= "$(System.DefaultWorkingDirectory)/_TESTREPO7/project.json"
$ExecutableFilePath=$dir+"\UiPath.Studio.CommandLine.exe"
$OutputFilePath=".\$(Get-Date -Format 'yyyy-MM-dd-HH-mm-ss')-Workflow-Analysis.json"
#This was an attempt to write the filename to a pipeline variable: Feel free to continue on this path if possible
Write-Host "##vso[task.setvariable variable=jsonFilePath]$OutputFilePath"
Write-Output "$(Get-Date -Format 'HH:mm:ss') - STARTED - Workflow Analyzer CLI Script"
$Command = "$ExecutableFilePath analyze -p $ProjectFilePath"
Invoke-Expression $Command | Out-File -FilePath $OutputFilePath
Write-Output "$(Get-Date -Format 'HH:mm:ss') - COMPLETED - Workflow Analyzer CLI Script"
azurePowerShellVersion: LatestVersion
How do I fix his error within a INLINE YAML script. I am new to YAML and when I tried to enter a input I got errors.
##[error]Required: 'ConnectedServiceNameARM' input
According to your AzurePowerShell task definition, you don’t seem to specify the azureSubscription field.
steps:
- task: AzurePowerShell#5
displayName: 'Azure PowerShell script: InlineScript'
inputs:
azureSubscription: 'xxx'
ScriptType: InlineScript
Inline: xxx
azurePowerShellVersion: 'LatestVersion'
You can click the Settings shown in the figure below to specify the subscription.
About Azure PowerShell task, please refer to this document for details.
to go around this error change from task: AzurePowerShell#5 to pwsh:
- pwsh: |
InlineScript
displayName: 'Azure PowerShell script: InlineScript'

Get secret value from azure key vault in variable in powershell script

I need to set secret value from azure key vault into a variable to be used in further task. I tried below script but it gives error: Could not find the modules: 'Az.Accounts' with Version: ''.
I understand that I can use variable directly but it needs to be set to a common variable since I am building generic pipeline
Script I tried:
- task: AzurePowerShell#4
inputs:
azureSubscription: 'SUB1'
ScriptType: 'InlineScript'
Inline: |
$secret = Get-AzKeyVaultSecret -VaultName '$(KeyVaultName)' -Name $(sqlServerAdminUsername)
$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret.SecretValue)
try {
$SqlServerUsername = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)
}
azurePowerShellVersion: 'LatestVersion'
pwsh: true
condition: and(succeeded(), eq(variables['EnvironmentType'], 'Dev'))
Check out this similar issue.
Fixes can be:
Try switching to version 3 and 5 of Powershell task and run.
Run the Powershell V4 task in Linux hosted Agents instead of Windows.
Run this powershell screipt from Azure CLI task.

Why Azure DevOps masks some non secret variables in the log output?

I am logging several guids in the Azure DevOps output, please observe:
So, the *** (secret) *** is my output, because I know the particular field is a secret. However, the ClientId and TenantId are masked by Azure DevOps on its own accord and I do not understand how Azure DevOps decided it should be masked.
These are not build variables, just some Write-Host statements.
We are using on prem Azure DevOps Server 2019.
EDIT 1
Here is the Powershell script that runs:
function Initialize-AzureSqlDB(
[parameter(Mandatory)][ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]$DBScriptsSource,
[parameter(Mandatory)][string]$SqlServerName,
[Parameter(Mandatory)][string]$SqlServerResourceGroup,
[Parameter(Mandatory)][string]$SubscriptionId,
[Parameter(Mandatory)][string]$ClientId,
[Parameter(Mandatory)][string]$ClientSecret,
[Parameter(Mandatory)][string]$TenantId,
[string]$DBNamePrefix = "${env:UserName}_"
)
{
# Some code that does Write-Host for all the script parameters
...
The script is invoked from a YAML build template. The actual Service principal credentials are found in the ARM_... environment variables. Here is the YAML code:
- powershell: Initialize-AzureSqlDB -DBScriptsSource ${{ parameters.dbScriptLocation }} -DBNamePrefix ${{ parameters.dbNamePrefix }} -TenantId $env:ARM_TENANT_ID -SubscriptionId $env:ARM_SUBSCRIPTION_ID -ClientId $env:ARM_CLIENT_ID -ClientSecret $env:ARM_CLIENT_SECRET -SqlServerName $env:SqlServerName -SqlServerResourceGroup $env:SqlServerResourceGroup
displayName: Initializing Database(s).
condition: eq(variables['RunDBUpgrade'],true)
Now neither ClientId nor TenantId are defined as build variables. Their actual values do come from an exiting Service Connection Endpoint in Azure DevOps, but even there neither are secret.
So, it is unclear to me how Azure DevOps decides to mask them and not SubscriptionId, for instance. I, for my part, do not want either of them to be masked.
Can anyone explain how Azure DevOps decides what to mask here?
EDIT 2
So, I checked our extension and it contains the following code:
Write-Host "##vso[task.setvariable variable=ARM_SUBSCRIPTION_ID;]$($se.data.subscriptionId)"
Write-Host "##vso[task.setvariable variable=ARM_SUBSCRIPTION_NAME;]$($se.data.subscriptionName)"
Write-Host "##vso[task.setvariable variable=ARM_TENANT_ID;]$($se.auth.parameters.tenantid)"
Write-Host "##vso[task.setvariable variable=ARM_CLIENT_ID;isSecret=true]$($se.auth.parameters.serviceprincipalid)"
Write-Host "##vso[task.setvariable variable=ARM_CLIENT_SECRET;isSecret=true]$($se.auth.parameters.serviceprincipalkey)"
That can explain how ClientId gets masked, but not the TenantId. Now there is a possibility that the code in the master branch does not exactly match the one installed in server. I will make sure the task is redeployed and then we will know.

Use Azure CLI within Azure Powershell Task

I want to create a Powershell script which executes some AzureRm... commands and follows those up with some Az commands. Reason being that some commands are only available via Az.
When trying to execute these scripts in a release pipeline, the script always fails with the following error:
ERROR: Please run 'az login' to setup account.
Executing the Az commands in a Azure CLI task work as expected, because Az Login is executed by the task.
I don't want to pass the secret required to login to the script if at all possible. I would rather fall back to separating the scripts into two steps in the pipeline.
Is it possible to use the Azcommands within a Azure Powershell task without passing the secrets manually?
Minimal example:
Create a new release pipeline
Add a task Azure PowerShell
Use inline script
As script, execute az account show
The short term solution I already had in place was passing the ServicePrincipal information into the powershell script and executing az login manually (same as Bevan's answer below).
My long term solution was to replace all Azure CLI calls with "Az Powershell" commands.
Luckily, most commands are available by now.
A couple of commands don't have an equivalent commandlet. But if they are available via ARM, you can figure out an alternative command with Powershell.
Many of them involve using New-AzResource/New-AzureRmResource or Invoke-AzResourceAction/Invoke-AzureRmResourceAction
How to create a Linux AppService Plan with New-AzAppServicePlan?
Manage Azure Cosmos DB SQL API resources using PowerShell
# AzureCLI
az cosmosdb list-keys
# Powershell:
$keys = Invoke-AzResourceAction -Action listKeys `
-ResourceType "Microsoft.DocumentDb/databaseAccounts" -ApiVersion "2015-04-08" `
-ResourceGroupName $resourceGroupName -Name $accountName
When I have mixed commands I put this into my Azure Powershell task
az login --service-principal --username "$(ServicePrincipal)" --password "$(AzureDevOps-ServicePrincipal-Secret)" --tenant "$(Azure_Tenant)"
I have my SP and Tenant IDs as a variables and the Secret for the SP stored in Azure KeyVault linked to a Library Variable group. You can alternatively just stored the secret in a normal Variable/Variable Group and hit the padlock icon to secure it.
You may need to run az account set -s $(SubscriptionName) if the SP has access to multiple subscriptions in the same tenant.
I figured out this approach - store credentials in job scoped variables (currently only an Azure CLI task allows that) and then re-use in Azure PowerShell task:
- task: AzureCLI#2
displayName: 'Azure CLI - get credentials'
inputs:
azureSubscription: 'SUBSCRIPTIONNAME'
scriptType: 'pscore'
scriptLocation: 'inlineScript'
addSpnToEnvironment: true
inlineScript: |
Write-Host "##vso[task.setvariable variable=ARM_CLIENT_ID]$($env:servicePrincipalId)"
Write-Host "##vso[task.setvariable variable=ARM_CLIENT_SECRET]$($env:servicePrincipalKey)"
Write-Host "##vso[task.setvariable variable=ARM_TENANT_ID]$($env:tenantId)"
- task: AzurePowerShell#5
displayName: 'collector'
inputs:
azurePowerShellVersion: LatestVersion
azureSubscription: 'SUBSCRIPTIONNAME'
pwsh: true
scriptType: inlineScript
inline: |
az login --service-principal --username "$($env:ARM_CLIENT_ID)" --password "$($env:ARM_CLIENT_SECRET)" --tenant "$($env:ARM_TENANT_ID)"
./mixedscript.ps1
Anyway, it wont work like that, because you have to authenticate to az utility separately. az cli and powershell do not share connection information. you can try and use az step with some command before powershell step. that would force az to auth and after that you can use it inside powershell ste.