Next & Previous

Everything that was wrong with my Azure DevOps Pipelines

I’m making notes because DevOps is one of those things where you spend a week writing the YAML and then you forget how it works and everyone else never knew how it worked and now nobody knows how it works. Here’s how I fixed all of yesterday’s mistakes.

SQL Migrations

I want to generate idempotent SQL migrations and run them on the test server so that its DB schema is up-to-date.

The mistakes I made:

–idempotent is a flag

By default, the SQL generated by dotnet ef migrations script is a “setup” kind of script; designed to be run on a bare-earth DB. It will error out if the tables already exist.

Instead, generate your migrations script with the --idempotent flag:

dotnet ef migrations script --idempotent --context $(dbContext) -o $(Build.ArtifactStagingDirectory)/Migrations.sql

You only need the context flag if you have more than one DBContext in your migrations project (I do).

The deployment group for a SQL deploy is supposed to be the SQL boxes

Maybe this is obvious but at least one or two other people didn’t think so. Comes the response:

you are trying to run a Deployment Group based task on a machine which is not your sql server. This is not the correct way to use a Deployment Group based task. You should configure the sql server against a deployment group and then run this task.

But we luddites also found that you can just switch off “execute within a transaction” and it works 👍👌🙆‍✅ next ticket please.

Copying config files

Instead of using the appsettings.{env}.json override system that is standard to a lot of new .Net projects, I want to curate and deploy flat configs for each environment. This means no messing with ASPNETCORE_ENVIRONMENT variable on the target servers, no web.configs, and no /p:Configuration flag in the build stage (simplifying builds).

The ‘Copy Files’ task in pipelines doesn’t let you choose the target file name; it’s meant for bulk-copying a load of files matching an expression from one directory to another. You can specify target directory, but not target file name.

Instead we can use PowerShell…

DevOps variables into PowerShell is a bit hard and very badly documented

Usually in DevOps tasks you can use a dollar-paren syntax to get built-in variables, like $(System.DefaultWorkingDirectory)/Server/drop/a/Output/

In PowerShell, this syntax is $env:System_DefaultWorkingDirectory instead.

That is, replace dots with underscores, and prefix with $env:

This works for some, but not all variables. The others, you need to manually configure in the ‘Environment Variables’ section of the wizard (and you may choose to do this for all of them, for script brevity).

These will end up in the YAML, looking like this:

    WorkingDirectory: $(System.DefaultWorkingDirectory)
    DeploymentID: $(Release.DeploymentID)

My finished PowerShell (redacted) uses Move-Item with the -Force flag to override the development appsettings with the configured environment one. Note that ConfigEnvironment is a custom variable baked into the Release pipeline:

Write-Host "System.DefaultWorkingDirectory is $env:WorkingDirectory"
Write-Host "ConfigEnvironment is $env:ConfigEnvironment"
Write-Host "Target config file is appsettings.$env:ConfigEnvironment.json"

$sourceLocation = "$env:WorkingDirectory/Server/drop/s/Contoso.Project/Config/appsettings.$env:ConfigEnvironment.json"
$destination = "C:\iis\\$env:DeploymentID\appsettings.json"

Write-Host "Set sourceLocation to $sourceLocation"
Write-Host "Set destination to $destination"
Write-Host "Moving config now"

# Move and replace
Move-Item -path $sourceLocation -destination $destination -Force

Write-Host "Done!"


Aw, it’s over for now. But don’t worry, today I’ll make new mistakes that I have to fix tomorrow!

Hope it helps 🌈🕊