I'm running a YAML pipeline in Azure DevOps on a Windows agent (windows-latest). I'm trying to run a what-if deployment using the Azure CLI with the following step:
- task: AzureCLI@2
displayName: 'What if Bicep file'
inputs:
azureSubscription: '$(azureServiceConnection)'
scriptType: 'ps'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group what-if
--resource-group rg-inbound-integration-dev--mode Incremental--template-file Global/infra/main.bicep--parameters Global/infra/main-dev.bicepparam
But the task fails with the following error: ERROR: [WinError 2] The system cannot find the file specified
I used the AzureCLI@2 task to run a what-if deployment using az deployment group what-if in a YAML pipeline. Initially, I set scriptType: bash, which caused the [WinError 2] error. I then tried switching to ps and pscore and modified the inlineScript to be a single line to avoid multi-line parsing issues. I confirmed that the Bicep file and parameters file exist and are correctly referenced. I also attempted to run the same command using a powershell task instead of AzureCLI@2, but the same error persisted. The az CLI is available and works in other steps and pipelines, so it's not a missing tool issue globally on the agent.
I used scriptType: bash in the AzureCLI@2 task because the Azure CLI what-if examples in official Microsoft documentation and other community samples typically use Bash syntax (e.g., multi-line az deployment group what-if commands). I expected the Azure DevOps windows-latest agent to support Bash through the Git installation (which usually includes Git Bash at C:\Program Files\Git\bin\bash.exe).
Since Bash is often preinstalled and usable on Windows agents, I assumed this would work seamlessly. However, it turns out the agent failed to locate the necessary files or interpreter, causing the [WinError 2] error. This helped me realize that even though Bash is sometimes present, its availability or integration with AzureCLI@2 may not be consistent across all agents or contexts.
I expected the Azure CLI what-if command to run and output the planned changes from the Bicep file deployment to the specified resource group — similar to how az deployment group create would run, but in "dry-run" mode to preview changes.
This is my yaml code
variables:
dotNetVersion: '8.0.x'
buildConfiguration: 'Release'
includeCoverageReport: true
azureServiceConnection: 'Dynamics-ERP-DEV (4ce4b7e8-cc95-40ed-9567-46843a458d4b)'
location: 'West Europe'
# sonarProjectKey: '<to be determined>'
pool:
vmImage: 'windows-latest'
trigger:
paths:
include:
- Global/*
- Common/*
jobs:
- job: DotNetBuild
steps:
#- task: SonarCloudPrepare@1
# condition: ne('${{ variables.sonarProjectKey }}', '')
# inputs:
# SonarCloud: 'SonarCloud Xebia'
# organization: 'Xebia'
# scannerMode: 'MSBuild'
# projectKey: '${{ variables.sonarProjectKey }}'
# extraProperties: |
# sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/coverage.opencover.xml
- task: UseDotNet@2
inputs:
version: '${{ variables.dotNetVersion }}'
includePreviewVersions: True
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
arguments: '--configuration ${{ variables.buildConfiguration }}'
workingDirectory: $(System.DefaultWorkingDirectory)/Global
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
arguments: '--no-build --configuration $(buildConfiguration) --settings ./coveragerunsettings.runsettings'
workingDirectory: $(System.DefaultWorkingDirectory)/Global
- script: |
cat $(Agent.TempDirectory)/*/coverage.cobertura.xml
displayName: 'Show Cobertura file'
#- task: SonarCloudAnalyze@1
# condition: ne('${{ variables.sonarProjectKey }}', '')
#- task: SonarCloudPublish@1
# condition: ne('${{ variables.sonarProjectKey }}', '')
# inputs:
# pollingTimeoutSec: '300'
- task: DotNetCoreCLI@2
displayName: 'Publish Employee Import Azure Function'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
command: publish
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)/EmployeeImport'
projects: '**/Middleware.FnO.EmployeeInfo.csproj'
zipAfterPublish: true
publishWebProjects: false
modifyOutputPath: false
- task: DotNetCoreCLI@2
displayName: 'Publish D&B Integration Azure Function'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
command: publish
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)/DnBIntegration'
projects: '**/Middleware.FnO.DnB.csproj'
zipAfterPublish: true
publishWebProjects: false
modifyOutputPath: false
#- task: DotNetCoreCLI@2
# displayName: 'Publish NonWorkingTime Azure Function'
# condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
# inputs:
# command: publish
# arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)/NonWorkingTime'
# projects: '**/Middleware.NonWorkingTime.csproj'
# zipAfterPublish: true
# publishWebProjects: false
# modifyOutputPath: false
- task: PublishTestResults@2
displayName: 'Publish test results'
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '*.trx'
searchFolder: '$(Agent.TempDirectory)'
buildConfiguration: '${{ variables.buildConfiguration }}'
- task: PublishCodeCoverageResults@2
displayName: 'Publish coverage report'
condition: eq(${{ variables.includeCoverageReport }}, 'true')
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/*/coverage.cobertura.xml'
reportDirectory: '$(Agent.TempDirectory)/coveragereport'
- script: |
dotnet tool install -g dotnet-reportgenerator-globaltool
displayName: 'Install ReportGenerator Tool'
- script: |
reportgenerator -h
displayName: 'Install ReportGenerator Tool'
- script: |
reportgenerator -reports:$(Agent.TempDirectory)/*/coverage.cobertura.xml -targetdir:$(Build.SourcesDirectory)/CodeCoverage -reporttypes:HtmlInline_AzurePipelines;Cobertura -filefilters:-**/obj/**
displayName: 'Generate Corbertura Report'
- task: PublishBuildArtifacts@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
displayName: 'Publish EmployeeImport dotnet artifacts'
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/EmployeeImport'
artifactName: 'EmployeeImport'
- task: PublishBuildArtifacts@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
displayName: 'Publish DnbIntegration dotnet artifacts'
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/DnBIntegration'
artifactName: 'DnBIntegration'
- job: BicepPreparation
steps:
- script: |
az bicep build --file Global/infra/main.bicep
displayName: 'Lint Bicep file'
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'Validate Bicep file'
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '$(azureServiceConnection)'
location: '$(location)'
action: 'Create Or Update Resource Group'
csmFile: 'Global/infra/main.bicep'
csmParametersFile: 'Global/infra/main-dev.bicepparam'
resourceGroupName: 'rg-inbound-integration-dev'
deploymentMode: 'Validation'
- task: AzureCLI@2
displayName: 'What if Bicep file'
inputs:
azureSubscription: '$(azureServiceConnection)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group what-if \
--resource-group rg-inbound-integration-dev \
--mode Incremental \
--template-file Global/infra/main.bicep \
--parameters 'Global/infra/main-dev.bicepparam'
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'Validate Bicep file for TEST'
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '$(azureServiceConnection)'
location: '$(location)'
action: 'Create Or Update Resource Group'
csmFile: 'Global/infra/main.bicep'
csmParametersFile: 'Global/infra/main-test.bicepparam'
resourceGroupName: 'rg-inbound-integration-test'
deploymentMode: 'Validation'
- task: AzureCLI@2
displayName: 'What if Bicep file for TEST'
inputs:
azureSubscription: '$(azureServiceConnection)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group what-if \
--resource-group rg-inbound-integration-test \
--mode Incremental \
--template-file Global/infra/main.bicep \
--parameters 'Global/infra/main-test.bicepparam'
- task: ArchiveFiles@2
displayName: 'Archive files'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/Global/infra/'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/infra_$(Build.BuildId).zip
replaceExistingArchive: true
- task: ArchiveFiles@2
displayName: 'Archive common files'
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/Common/infra/'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/infra_common_$(Build.BuildId).zip
replaceExistingArchive: true
- publish: $(Build.ArtifactStagingDirectory)/infra_$(Build.BuildId).zip
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
artifact: infra
- publish: $(Build.ArtifactStagingDirectory)/infra_common_$(Build.BuildId).zip
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
artifact: infra_common