Publish a self-contained .NET app on Azure DevOps

I had a problem today that I ended up spending quite some time resolving. We have some internal CLI's that we have been migrating to .NET Core lately. Rather than having to clone the code and build it when needed, I wanted to set up an automated build, producing a self-contained console app on our Azure DevOps server.

When setting up builds on Azure DevOps, I pretty much follow the same pattern over and over again. The first step is usually setting up a dotnet restore task:

Notice the custom feed selected in the Feeds to use section. The reason I'm mentioning this is that it will cause problems in a minute.

Next, I add a dotnet build step:

So far so good. To produce a self-containing output, you can use dotnet publish together with the --runtime option. In my case I want to produce a self-containing program for 64 bit Windows, why the command looks like this:

dotnet publish --runtime win-x64

Adding that command as a build step is easy:

Unfortunately, the build failed on Azure DevOps with the following error:

error NETSDK1047: Assets file '...\obj\project.assets.json' doesn't have a target for '.NETCoreApp,Version=v5.0/win-x64'. Ensure that restore has run and that you have included 'net5.0' in the TargetFrameworks for your project. You may also need to include 'win-x64' in your project's RuntimeIdentifiers.

When looking through my csproj file, the project doesn't include a runtime as proposed by the error message:

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

To fix the error, I simply add a RuntimeIdentifiers element:

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>net5.0</TargetFramework>
  <RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
</PropertyGroup>

This fixed the build error just to reveal a new error in the dotnet publish step:

C:\Program Files\dotnet\sdk\5.0.301\NuGet.targets(123,5): error : Unable to load the service index for source .../_packaging/Elmah.Io.Shared/nuget/v3/index.json.
C:\Program Files\dotnet\sdk\5.0.301\NuGet.targets(123,5): error : Response status code does not indicate success: 401 (Unauthorized).

Remember the dotnet restore step which pointed out a custom NuGet feed to (also) restore packages from? Unfortunately, neither build or publish has this option, why I need to disable restore on those steps (no need to restore and build multiple times anyway). This can be done by modifying the build step:

dotnet build --runtime win-x64 --no-restore

This tells the build command to build for the win-x64 runtime and don't do NuGet restore. We need the runtime parameter since dotnet publish will no longer restore and build if required. The --no-restore parameter instructs build not to restore NuGet packages.

Similar, I extended the publish command:

dotnet publish --runtime win-x64 --no-build mycli.csproj

By including the --no-build parameter, neither restore or build is executed as part of this step.

The changes produce a nice self-contained .NET 5 console app. The final step is to publish the generated zip-file using a Publish build artifacts step:

When anyone needs the newest build of the CLI, he/she can download it by navigating to the most recent build and clicking the Artifacts button: