AI Assisted Development – Taming Azure Pipeline YAML (Part2)


Posted by Willy-Peter Schaub on Thu 18 December 2025

Artificial Intelligence post the thunderous impact of an asteroid colliding with our moon …

OK, in AI Assisted Development – Taming Azure Pipeline YAML I was flabbergasted by the supporting role that GitHub Copilot could plan. If you have not read that post, do so now - I will wait.


Part 2 - Where the real fun starts

In the past, I had to manually copy and paste every single innovation from one blueprint—specifically the 101 blueprint—into all the others. It was a tedious, mind-numbing process that felt like a guaranteed way to introduce errors. Honestly, it was the kind of work that makes you question your life choices.

Then I thought, why not let GitHub Copilot handle this for me? So I gave it a shot. Here is what I asked:

- Please review the git history.
- Review the changes made to the templates in the __101__ folder.
- Focus on the logic around azure DevOps agent pools and vmimage.
- Apply the same changes to the universal-artifact folder.
- Validate all changes.
- Explain all the changes you have made.
- Suggest improvements for future consideration.
Thank you.

And here is what Copilot came back with:

github copilot

And here is more of the copilot's journal:

Git history review

- Key commits impacting `__101__` pool logic:
  - 4e11edb: Renamed security review stage parameters; removed redundant env name 
    vars.
  - 3de0d4e, 8506828: Introduced coalesce for pool/image params; added dynamic 
    agent pool and vmImage configuration.
  - 5a26779: Updated blueprint version to 2.2.0 in start templates.

- Pattern established in `__101__`:
  - CI stage: conditional pool—include vmImage only when set; otherwise omit for 
    self-hosted.
  - CD stage: same conditional pattern; namePool and nameVM provided from config 
    via control.yml.

Validation of __101__ (post your patches)

- azure-pipeline-__101__-ci.yml: Correct conditional pool using 
  ${{ if ne(parameters.vmImage, '') }}; self-hosted omits vmImage.
- azure-pipeline-__101__-cd-stage.yml: Correct conditional pool with 
  namePool/nameVM.
- azure-pipeline-__101__-control.yml: Passes jobPool/vmImage to CI and 
  namePool/nameVM to each CD stage via coalesce(...). Security/QA/PreProd 
  automation use the same approach.
- azure-pipeline-__101__-config.yml: Security review and automation variables 
  now correctly named (securityReviewStagePool, securityReviewStageVmImage, 
  etc.). Looks consistent.

Applied changes to universal-artifact

- Updated CI pipeline to match __101__ conditional pool pattern:

- File: azure-pipeline-universal-artifact-ci.yml

- Changes:
  - Replaced hardcoded pool with:
     - ${{ if ne(parameters.vmImage, '') }}  pool: 
       name=${{ parameters.jobPool }}, vmImage=${{ parameters.vmImage }}
     - ${{ else }}  pool: name=${{ parameters.jobPool }}
  - Outcome: Supports both hosted and self-hosted agents.

- Updated CD stage template to include pool name and conditional vmImage:
  - File: azure-pipeline-universal-artifact-cd-stage.yml

- Changes:
  - Replaced pool: vmImage: ${{parameters.config.nameVM}} with conditional 
    block:
    - If nameVM not empty: set both namePool and nameVM.
    - Else: set only namePool.
  - Outcome: Matches __101__ pattern and correctly handles self-hosted 
    agents.

- Wired control template to pass pool/image values consistently:
  - File: azure-pipeline-universal-artifact-control.yml
  - Changes:
    - CI parameters: added jobPool and vmImage using 
      coalesce(variables.ciStagePool, 'Azure Pipelines') and 
      coalesce(variables.ciStageVmImage, 'ubuntu-latest').
    - CD stage parameters: added namePool and wrapped nameVM in 
      coalesce(...) for all stages (development, systemTest, staging, 
      production, CanadaCentral, CanadaEast).
    - Embedded stages (securityReview, securityAutomation, qaAutomation, 
      preProdAutomation): added namePool with coalesce(...) and retained 
      conditional vmImage.
    - Outcome: All stages now accept pool name and optional vmImage, 
      consistent with __101__.

Validation

- Static validation: No YAML or linter errors detected in updated 
  universal-artifact files.
  - Checked: azure-pipeline-universal-artifact-ci.yml, 
    azure-pipeline-universal-artifact-cd-stage.yml, 
    azure-pipeline-universal-artifact-control.yml.
- Behavioral expectation:
  - Hosted agents: vmImage non-empty  pool includes vmImage.
  - Self-hosted agents: set corresponding *StageVmImage to '' in 
    config or omit  pool omits vmImage.

Improvements for future consideration

- Centralize pool defaults:
  - Add top-level defaults per blueprint (e.g., defaultPool, 
    defaultVmImage) and let stage-specific variables override—reduces 
    duplication.
- Validation template:
  - Create a small reusable template to verify that when a custom 
    pool is used, vmImage is empty; and for hosted pools, vmImage is 
    set—fail fast with clear messages.
- Consistent OS images:
  - Align default images across blueprints (ubuntu-latest vs 
    windows-latest) based on toolchain needs; document per blueprint.
- Parameterize pool/image in start templates:
  - Expose ciStagePool/ciStageVmImage in *-start.yml to encourage 
    explicit selection at pipeline creation.

What I changed

- Added conditional pool logic to universal-artifact CI and CD stage
  templates, mirroring __101__.
- Ensured control wiring passes both namePool and nameVM per stage 
  using coalesce(...).
- Added jobPool/vmImage to universal-artifact CI invocation to support 
  dynamic agent selection.
- Enforced that embedded automation stages also receive namePool and 
  nameVM.

Optional next steps

- Do you want me to update azure-pipeline-universal-artifact-config.yml 
  to include *StagePool variables (like developmentStagePool etc.) and 
  align with the new control wiring? I can add those with sensible 
  defaults and a brief comment explaining hosted vs self-hosted 
  configuration.

After confirming with a resounding YES, here is the rest of the copilot's journal:

Acknowledged. Plan: Review git changes in __101__, apply the same 
pool/vmImage logic to universal-artifact, validate, then explain 
changes and suggest improvements.

Progress: Reviewed __101__ git history and templates, replicated the 
conditional pool logic across universal-artifact, and validated YAML. 
Next, Ill summarize what changed and future improvements.

Summary of git history review for __101__

- Key changes:
  - Introduced dynamic pool/image via coalesce(...) and conditional 
    pool blocks (commits 3de0d4e, 8506828).
  - Cleaned up security review parameter names and redundancy (4e11edb).
  - Updated start templates to blueprint version 2.2.0 (5a26779).
- Pattern confirmed:
  - CI uses ${{ if ne(parameters.vmImage, '') }} to include vmImage; 
    self-hosted agents omit vmImage.
  - CD stage uses ${{ if ne(parameters.config.nameVM, '') }} to 
    include vmImage with namePool; else only namePool.

Changes applied to universal-artifact

- Updated CI template to support dynamic pool/image with conditional 
  logic:
  - File: azure-pipeline-universal-artifact-ci.yml
  - Changes:
    - Replaced hardcoded pool with conditional blocks:
      - If parameters.vmImage not empty: pool: 
        name=${{ parameters.jobPool }}, 
        vmImage=${{ parameters.vmImage }}
      - Else: pool: name=${{ parameters.jobPool }}
    - Mirrors the __101__ CI pattern to support hosted and 
      self-hosted agents.
- Updated CD stage template to include namePool and conditional 
  vmImage:
  - File: azure-pipeline-universal-artifact-cd-stage.yml
  - Changes:
    - Replaced pool: vmImage: ${{parameters.config.nameVM}} with:
      - If nameVM not empty: pool: 
        name=${{parameters.config.namePool}}, 
        vmImage=${{parameters.config.nameVM}}
      - Else: pool: name=${{parameters.config.namePool}}
  - Aligns with __101__ CD stage behavior for self-hosted support.
- Wired control to pass pool/image to CI and all CD stages:
  - File: azure-pipeline-universal-artifact-control.yml
  - Changes:
    - CI invocation: added jobPool and vmImage using 
      coalesce(variables.ciStagePool, 'Azure Pipelines') and 
      coalesce(variables.ciStageVmImage, 'ubuntu-latest').
    - For each deployment stage (DV, SY, ST, PR, Canada 
      Central/East): added namePool via 
      coalesce(variables.*StagePool, 'Azure Pipelines') and 
      wrapped nameVM with coalesce(..., 'ubuntu-latest').
    - Embedded stages (Security Review, Security Automation, 
      QA Automation, PreProd Automation): added namePool with 
      coalesce(...) and retained conditional vmImage. This 
      ensures consistent agent configuration across all stages.
- Added missing configuration variables to match the new wiring:
  - File: azure-pipeline-universal-artifact-config.yml
  - Changes:
    - Added CI variables: ciStagePool, ciStageVmImage.
    - Added stage pool variables for all environments: 
      developmentStagePool, systemTestStagePool, 
      stagingStagePool, productionStagePool, 
      productionCanadaCentralStagePool, 
      productionCanadaEastStagePool.
    - Added security review and automation pools/images: 
      securityReviewStagePool, securityReviewStageVmImage, 
      securityAutomationStagePool, securityAutomationStageVmImage, 
      qaAutomationStagePool, qaAutomationStageVmImage, 
      preProdAutomationStagePool, preProdAutomationStageVmImage.
    - Defaults set to 'Azure Pipelines' and 'ubuntu-latest' to 
      mirror __101__ and allow easy override. For self-hosted, 
      set the corresponding *StageVmImage to ''.

Validation

- Static checks: No YAML errors reported in updated files:
  - azure-pipeline-universal-artifact-ci.yml
  - azure-pipeline-universal-artifact-cd-stage.yml
  - azure-pipeline-universal-artifact-control.yml
  - azure-pipeline-universal-artifact-config.yml
- Behavioral expectation:
  - Hosted agents: pool name + image used.
  - Self-hosted agents: pool name only; vmImage omitted when the 
    config value is set to empty.

Suggestions for future improvements

- Defaults centralization:
  - Define top-level defaultPool and defaultVmImage per blueprint 
    and reference them with coalesce(...) to reduce duplication 
    and ease global changes.
- Automated validation:
  - Add a validation step/template that asserts: if namePool is a 
    custom self-hosted pool, nameVM must be empty; for hosted 
    pools, nameVM must be setfail fast with clear messaging.
- Consistent OS defaults:
  - Standardize on ubuntu-latest unless Windows agents are 
    required; document rationale per blueprint.
- Start templates:
  - Expose CI pool/image in *-start.yml to make agent selection 
    explicit at creation time.

I am truly impressed!

Not only did my binary sidekick make all the changes, but it also double-checked everything and nailed it perfectly. Every single update was spot-on. I did not have to babysit the process or fix anything afterward. That is impressive.

This is a perfect example of how you can unlock immediate value with GitHub Copilot—no need to dive into Agentic AI or get lost in the whole “Vibe Coding” rabbit hole just yet. Start small, start smart, and let the tools do the heavy lifting.

Step by step, and suddenly you are working smarter, not harder.

My two cents

Does GitHub Copilot really boost my productivity? Honestly, the jury is still out for me—but here’s what I can say: I am getting things done faster and cleaner, and the quality is noticeably better, as long as I take the time to sanity-check what my digital sidekick spits out and make sure the logic holds up.

The real game-changer? I just upgraded a YAML template without the usual stress, endless troubleshooting, or those ridiculous spacing nightmares. Compare that to the hallucination circus I have had with ChatGPT—night and day. What is hitting me now is how my focus has shifted. Instead of wrestling with code for hours, I’m crafting prompts—feeding AI the best context so it can nail the right answer.

So, back to productivity: yes, I am more productive. No question. But here’s the catch—I worry about getting bogged down by process and teammates who have not built the same trust in AI yet. That is a hurdle, but it is temporary. The short answer? My productivity has skyrocketed. I am just one puzzle piece, sure—but this piece is sharper, faster, and way more effective than before.

That said, huge THANK YOU to WorkSafeBC for pushing us to the edge with processes and products. It has been a ride! Having the chance to dive deep with M365 and GitHub Copilot has been nothing short of inspiring. This journey has not just been encouraging; it has been eye-opening. We are not just ticking boxes, we are learning, adapting, and proving that technology can actually make life easier when you embrace it. Honestly, seeing how these tools transform the way we work has me fired up for what is next. Here is to pushing boundaries and making the most of every opportunity!