I started a side project on a Discord server where I have loads of automation plugged into specific channels for updates I want. After about the 5th runbook, things became a bit messy. I needed a solution that kept my github repo in sync with my Azure Automation accounts runbooks, but I also needed to be able to run code locally without hardcoding variables into the scripts. This is what I came up with:
Enterprise App Registrion To Github Repo

Add your github creds as a federated credential – Pick your branch/ organization on setup.

Things needed for the script:
Client ID of the app registration
Tenant ID of your Entra tenant
Subscription ID of your Azure subscription
Automation Account Name
Name Of The Resource Group The Account Lives In
sync-automation.yml
name: Sync Runbooks to Azure Automation
on:
push:
branches: [ "main" ]
paths:
- "runbooks/**.ps1"
- ".github/workflows/sync-automation.yml"
workflow_dispatch: {}
permissions:
id-token: write
contents: read
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AUTOMATION_ACCOUNT: ${{ vars.AUTOMATION_ACCOUNT }}
RESOURCE_GROUP: ${{ vars.AUTOMATION_RESOURCE_GROUP }}
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
- name: Azure login (OIDC) + Az PowerShell
uses: azure/login@v2
with:
client-id: ${{ env.AZURE_CLIENT_ID }}
tenant-id: ${{ env.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
enable-AzPSSession: true
- name: Install Az.Automation
shell: pwsh
run: |
Install-Module Az.Accounts -Force -Scope CurrentUser
Install-Module Az.Automation -Force -Scope CurrentUser
- name: Verify Az context (PowerShell)
shell: pwsh
run: |
$ctx = Get-AzContext
if (-not $ctx) { throw "No Az context; PowerShell not logged in." }
"Az Account: $($ctx.Account.Id)"
"Subscription: $($ctx.Subscription.Id)"
- name: Import + publish runbooks
shell: pwsh
run: |
$rg = "${{ env.RESOURCE_GROUP }}"
$acc = "${{ env.AUTOMATION_ACCOUNT }}"
if (-not $rg -or -not $acc) { throw "Missing RESOURCE_GROUP or AUTOMATION_ACCOUNT." }
Write-Host "Current working directory: $(Get-Location)"
$runbooks = Get-ChildItem "runbooks" -Filter *.ps1 -Recurse -File
if (-not $runbooks) { Write-Host "No runbooks found under ./runbooks"; exit 0 }
foreach ($rb in $runbooks) {
$name = [IO.Path]::GetFileNameWithoutExtension($rb.Name)
Write-Host "=== Importing/Updating runbook: $name from $($rb.FullName)"
Import-AzAutomationRunbook `
-ResourceGroupName $rg `
-AutomationAccountName $acc `
-Name $name `
-Type PowerShell `
-Path $rb.FullName `
-LogProgress:$true -LogVerbose:$true `
-Force
Publish-AzAutomationRunbook -ResourceGroupName $rg -AutomationAccountName $acc -Name $name
Write-Host "✅ Published: $name"
}
- name: Sync configs → Automation Variables (PROD)
shell: pwsh
run: |
# Ensure modules exist (already installed above, but safe to re-run)
Install-Module Az.Accounts -Force -Scope CurrentUser
Install-Module Az.Automation -Force -Scope CurrentUser
# Az context is already established via azure/login enable-AzPSSession: true
$rg = "${{ env.RESOURCE_GROUP }}"
$acc = "${{ env.AUTOMATION_ACCOUNT }}"
./tools/Sync-ConfigsToAutomationVariables.ps1 `
-ConfigFolderPath "./configs" `
-ResourceGroupName $rg `
-AutomationAccountName $acc `
-AutomationPrefix "PROD"
For each of my runbooks I have a like-named JSON file that contains the variables. You can see in the last block, Github Actions will look at each of the config files, strip the variables and create them as automation variables in Azure. Once setup correctly, your commits will look like this:

And when we click into one of these and click into it again we get:


It takes a few minutes to kick off but you can see it from the runbook section updating each runbook one-by-one


