Handling ASPNETCORE_ENVIRONMENT Environment Variable in IIS

.NET Core has built-in functionality that allows you to set up an environment variable called ASPNETCORE_ENVIRONMENT that tells the application which appsettings.json to use. I’ve tried to use this functionality on VMs in Azure with no luck. My applications seem to ignore this variable setting on VMs. It did work for me when deployed as an app service.

If you host your website on a shared server, you may not have any rights to create system environment variables.

This tutorial is using Visual Studio 2022.

When a .NET Core application first runs, in theory, it looks for the ASPNETCORE_ENVIRONMENT environment variable and uses that to select the proper appsettings.json to use to run the application. By default, a new application in Visual Studio will create three files:

  • appsettings.json
  • appsettings.development.json
  • appsettings.production.json

if the application doesn’t find the environment variable, it defaults to appsettings.production.json

Here’s the kicker, if the application doesn’t find the environment variable, it defaults to appsettings.production.json. This functionality is entirely unacceptable to me. If you deploy your app to a new server, would you want it to default to production values? I don’t.

Fortunately, there is a way to control all this behavior right from within your application code using web.config transformations. Since .NET Core doesn’t really depend on web.config files, many developers with only a few years experience may not even know about web.config transformations.

web.config Transformations

The web.config file is a file that is read by Internet Information Services (IIS) that is merged with the local maching.config file and tells IIS how to handle certain things. Often, if you make configuration changes in IIS, all it really does is update the web.config file for that application.

There is an XML schema definition called “XML-Document-Transform” that is prefixed with “xdt” which allows you to have different versions of your web.config file which can actually update values in the base web.config file when you deploy an application. Since most of us don’t use this file any longer, we usually don’t look at it, nor do we normally set up transformations. We’re going to use this functionality to control which appsettings.json our application uses based on which configuration we use to deploy.

Visual Studio Configuration Manager

At the top of the Visual Studio IDE, there is a drop-down for configuration. Typically, it will say “Debug”. If you click the drop-down arrow, you can select “Configuration Manager…”, or optionally, you can right-click on the project name and select the same entry.

The default configurations can be viewed in the “Active solution configuration:” drop-down. Usually it’s just “Debug” and “Release”.

  1. Click the drop down and select “<New…>”.
  2. Let’s name it “UAT”
  3. Copy settings from: “Debug”
  4. Create new project configurations should be checked
  5. Click on “OK”
  6. Click on “Close”

You’ll notice that your drop-down at the top of Visual Studio now says “UAT”.

appsettings.config files

Later versions of the .NET Core, post version 3.1 don’t seem to create the appsettings.Production.json file by default. However, if you have an older project that has been upgraded, the file may still exist. I’ll be using a project from a post on Lookups to demonstrate this technique.

In your main web application:

  1. Right-click on your appsettings.json file and select “Copy”
  2. Right-click on your project name and select “Paste”
  3. Rename the file “appsettings – Copy.json” to “appsettings.UAT.json”

Your appsettings entries should now look like:

You can edit the appsettings.UAT.json file and modify any of the entries to match what you need for your UAT environment/server.

I highly recommend that you delete the appsettings.Production.json. I typically use an appsettings.PROD.json etc. This will prevent the application from accidentally defaulting to the production config when deploy to a new environment/server.

Creating the web.config transformation

Now let’s look at your current web.config file. There is a chance that your project doesn’t have one. Here is what my default one looks like, you can copy this into a new one you create, but make sure to update the arguments in the “aspNetCore” key.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<location path="." inheritInChildApplications="false">
		<system.webServer>
			<aspNetCore processPath="dotnet" arguments=".\YTG.MVC.Lookups.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
				<environmentVariables>
					<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
				</environmentVariables>
			</aspNetCore>
		</system.webServer>
	</location>
</configuration>

Once you have the file, it’s important that you have the entry:

<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />

This tells the runtime that when you run the application, it will use the “Development” configuration, which will default to picking up values in your appsettings.Development.json. This will work even though we didn’t create a configuration called Development, we just still have “Debug” for local running.

Visual Studio used to have the option to right-click your web.config file and select “Add Transformations”, but I don’t see it any longer in the latest version of Visual Studio 2022. So we’ll create the transform manually.

  1. Right-click on the web.config and select “Copy”
  2. Right-click on your project name and select “Paste”
  3. Rename the “web – Copy.config” file to “web.UAT.config”

Double click the web.UAT.config file and modify the second line “configuration” to look like this:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

This lets us use the xdt namespace.

Then modify the environmentVariable line to look like this:

<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="UAT" xdt:Locator="Match(name)" xdt:Transform="Replace" />

This tells the build process to replace the value with the same name from the web.config with the value from this file.

Now, when you publish this project, you can select the “UAT” configuration and it will push a web.config with your project that automatically tells your application to use the values in the appsettings.UAT.json file. If you publish from the command line or through Jenkins, that command would looks like this:

dotnet publish –configuration UAT

The web.config in your publish location should have the line:

<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="UAT" />

So now you no longer have to depend on that system environment variable existing on the server.

You can read more about this on Microsoft’s site:

https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/transform-webconfig?view=aspnetcore-7.0

error NETSDK1152: Found multiple publish output files with the same relative path

I started receiving this error on the .XML file that I have included in my builds for APIs in order to enhance the Swagger descriptions. This file is enabled in the Build tab of the project properties.

This is using Visual Studio 2019 and publishing through Azure Pipelines.

Build tab on the project properties menu

This file will get created at the project root and in the bin folder when the project builds. It seems that .NET 6 build tools no longer likes having duplicate files.

Method

I have the properties of the xml documentation file in the project set to:

Build Action: None

Copy to Output Directory: Copy if newer

Then I added this to the main project file where the error is occuring in the top PropertyGroup section :

<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>

This simulates the scenario that was available up to and through .NET 5.0.

The entire block looks like

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <PackageId>YTG.AppLookups.SYS.API</PackageId>
    <Version>2.0</Version>
    <Authors>Jack Yasgar</Authors>
    <Company>Yasgar Technology Group, Inc.</Company>
    <TargetFramework>net5.0</TargetFramework>
    <RootNamespace>YTG.AppLookups.SYS.API</RootNamespace>
    <UserSecretsId>6197b17a-8e5c-4487-9574-2691abb359bb</UserSecretsId>
    <DockerDefaultTargetOS>Windows</DockerDefaultTargetOS>
    <AssemblyVersion>2.0.0.0</AssemblyVersion>
    <Configurations>Debug;Release;RV4;QA;RV4QA;PROD;RV4YTGIPROD02;RV4YTGITEST01</Configurations>
    <Platforms>AnyCPU;x64</Platforms>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
  </PropertyGroup>

Microsoft’s Notice

IIS Express The specified port is in use

Port ##### is already being used by another application.

There are times when the random port selected for use by Visual Studio for IIS Express can cause an error of “The specified port is in use.” This could be because you have something installed on your device that is already using that port.

The specified port is in use. Port ##### is already being used by another application.

An error occurred launching IIS Express. Unable to launch the configured Visual Studio Development Web Server.Port ‘#####’ is in use.

In order to see if it’s really true, you can use the netsh CLI command line app. Run a command prompt and run this command:

netsh interface ipv4 show excludedportrange protocol=tcp

You’ll see a report such as this:

If the port you have setup in your launchSettings.json conflicts with one on this report, then change the value in your launch settings to a value no in this list and try again.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:5066",
      "sslPort": 0
    }
  }
}

In the above example launchSettings.json, change the applicationUrl setting to have a port that is not in use, such as http://localhost:8081 etc. If you’re going to use higher ports, remember to do a search on the web, as some ports are reserved for specials purposes. We know many of them that you should avoid, even if they don’t show in the above list:

  • 21 – FTP
  • 22 – SSH
  • 80 – Default Website
  • 443 – Secure Website
  • etc.

Basically, avoid any ports with 3 numbers or less to be safe. As you can see above, I’ve found that ports in the 5000-5999 have the least potential for conflicts with other applications and network functions.

error MSB3644: The reference assemblies for .NETFramework,Version=v4.5 were not found

error MSB3644 – An error on Azure Pipelines if your .NET Framework in your project is too old for Visual Studio 2022.

Microsoft .NET Framework

I hadn’t run a pipeline for a database project for several months since I was working on other projects. I received the failure message: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(1220,5): error MSB3644: The reference assemblies for .NETFramework,Version=v4.5 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks.

I was suspicious when I saw the reference to \2022\ in the path which turned out to be the cause of the issue. Azure pipelines were upgraded to use Visual Studio 2022 build scenarios.

I checked my SQL Project and found that it was set to .NET Framework 4.5. This is too old for Visual Studio 2022. I updated it to .NET Framework 4.7.2 and recompiled to make sure it didn’t cause any issues.

Visual Studio 2019 Target Framework Selection in Project Properties

I checked it in and merged. The job ran succuessfully.

Azure Pipelines Successful Build Notice

Storing .NET NuGet Packages in GitHub

I can’t believe that I didn’t write this post two years ago when I figured this out. I apologize to everyone that had to figure this out on their own.

When I first tried to create NuGet packages from my apps and upload them, I wasn’t using Jenkins. But no matter, the concepts were the hard part, not the application that was doing the creation and uploading (push).

Scenario

Your doing software development and have one or more components that you want to re-use in many projects. Whether it’s a collection of extensions or models for an API. The goal is to have a versioned download that other developers can grab which is versioned and easily added to their application. I won’t bore you here with why this is a good idea, even when many corporate developers argue about what a pain it is to use NuGet packages for this.

The main concern is that you don’t want this proprietary code to be publicly available on NuGet.org. The three choices that I’ve worked with are File Share, Azure Artifacts and GitHub Packages. I’m not going to discuss Azure Artifact here, because they actually are a little easier, because if your using Azure Devops, then the authentication is coordinated, where as in GitHub, it’s not so easy.

I spent many hours trying get this to work. After a few hours, I actually got a NuGet package to build and then it would upload to GitHub. Then the next day I would try it and it would fail again. I opened a ticket with GitHub and had a few email exchanges. The last email basically said “If you figure it out, let us know how you did it.” Well, I have to admit that I never did, as much as I don’t like the saying that “It’s not my job”, I’m not being paid to educate GitHub support staff.

GitHub Support: “If you figure it out, let us know how you did it.”

Anyway, enough with the small talk, let’s get down to it.

Concepts

I need to state that this solution applies to all versions of .NET Core and .NET 5.x versions that I’ve been using for a few years. If you’re trying to do this with older .NET Framework versions, then some of this may not apply.

There are three major things you’ll have to be aware of when creating a NuGet package.

Your Project File

The default project file in .NET Core or .NET 5.x is not sufficient to create a NuGet package, unless you hard code all the data in the {ApplicationName}.nuspec file covered below. I recommend embellishing the csproj file.

There are several things that a NuGet package requires, id (PackageId) , title, etc. being a few. You need to make sure that all this data is in your csproj file. I have a sample below:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>library</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <PackageId>YTG-Framework-V2</PackageId>
    <Version>2.1.1</Version>
    <Authors>Jack Yasgar</Authors>
    <Company>Yasgar Technology Group, Inc.</Company>
    <PackageDescription>Shared resources for all Yasgar Technology Group applications</PackageDescription>
    <RepositoryUrl>https://github.com/YTGI/YTG-Framework-V2</RepositoryUrl>
    <Description>Shared framework methods and objects used throughout the enterprise.</Description>
    <Copyright>@2021 - Yasgar Technology Group, Inc</Copyright>
    <AssemblyVersion>2.1.1.0</AssemblyVersion>
    <FileVersion>2.1.1.0</FileVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.3" />
    <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.3" />
  </ItemGroup>
</Project>

Almost every line in this file is important. The “MOST” important one is the “Version” key highlighted. When you first get your upload (nuget push) to work, it will work fine, but it will get an exception the second time of you don’t increment the version. The NuGet upload process ignores the “AssemblyVersion” and “FileVersion”.

{ApplicationName}.nuspec

This is the file that the nuget packager actually looks at to create a NuGet package. The file has a variable base syntax that will pull data from the csproj file, hence my recommendation that you use the csproj file as the source of truth. You have the option of hard coding values in here if you wish. Why use the variables you ask? Because, if you use the csproj file as the source of truth, then your {ApplicationName}.nuspec can have the same content in every project you have. I think that makes this process simpler if you plan to have several NuGet packages.

<?xml version="1.0" encoding="utf-8"?>
<package >
  <metadata>
    <id>$packageid$</id>
    <version>$version$</version>
    <title>$title$</title>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <license type="expression">MIT</license>
    <projectUrl>$repositoryurl$</projectUrl>
    <description>$packagedescription$</description>
    <releaseNotes>Release</releaseNotes>
    <copyright>$copyright$</copyright>
    <tags>Utilities Extensions</tags>
  </metadata>
</package>

As you can see above in my live example, the only thing you might want to adjust is the <tags> entry. All the rest will pull from the project file as variables.

Now for the next tricky part. Here’s where your existing knowledge may hurt you. If you said to yourself that you don’t need a NuGet.config file in your project, your right, if you just want to fetch packages. But if your creating packages on Azure Pipelines or GitHub Actions, you’ll need it. If you’re creating your package on Jenkins, then you can just have a reusable NuGet.config in the C:\Users\{serviceid}\AppData\Roaming\NuGet folder. {serviceid} is the service account that Jenkins is running as, often LocalSystem, which means the {serviceid} = “Default”.

Okay, so now our project is ready for for a NuGet package build. So how do we get it pushed up. Well, that’s different depending on your environment. I’ll show you a few.

Command Line

First thing is to make sure your NuGet package gets created. You can do that in the CLI (Command Prompt):

dotnet pack {yourprojectfolder}\{yourprojectname}.csproj --configuration Release --output nupkgs

This should build your project and put the NuGet package in a folder called “nupkgs”. If this doesn’t work, you need to review all the messages and review the above configuration steps. Remember, this process is not concerned that you’re in a hurry.

If you wind up with a .nupkg file in the folder, then pat yourself on the back, you’re most of the way there. Here’s where I ended up opening a ticket with GitHub, because I had a nupkg, but I couldn’t get it to upload consistently. It turned out that I, and GitHub support, didn’t understand the different credentials.

I and GitHub support didn’t understand the different credentials.

In your NuGet.confg, there are two different sections.

packageSourceCredentials

	<packageSourceCredentials>
		<github>
			<add key="Username" value="JYasgarYTGI" />
			<add key="ClearTextPassword" value="ghp_asdsfsfjAbunchofBScharacters" />
		</github>
	</packageSourceCredentials>

and apikeys

	<apikeys>
		<add key="https://nuget.pkg.github.com/YTGI/index.json" value="awholebunchofbullshit==" />
	</apikeys>

The “packageSourceCredentials” are exactly what they say, it is the credentials to READ the nuget packages. So that’s where I got sidetracked, it has nothing to do with uploading the package after it’s created.

In order to actually PUSH (upload) a file, you need to have apikeys credentials. It DOES NOT use your regular GitHub access in the “packageSourceCredentials ” to upload packages. That means you have to have another section in your nuget.config file that give you access to push files. This is the ef’d up thing, it is often the exact same credentials, but just in a different place.

RECOMMENDATION: You should create a different account that has is very generic to create API access. This will mitigate the issue of a developer leaving the team that has all the tokens under their account for push access.

GitHub Token

In order to get the proper credentials to use for push (uploading) the nuget package, you should log in as your generic (DevOps) account in GitHub if you have one. If not, use your current account. MAKE SURE YOU GO INTO THE “Settings” FROM YOUR ACCOUNT ICON IN THE UPPER RIGHT CORNER and not the “Settings” on the account or project menu.

Once you’re in there, click “Personal access tokens” and click “Generate new token”.

Give your new token a name, select the expiration days, I suggest 90, but “No expiration” is your decision.

Select the following options:

  • workflow
  • write:packages
  • read:packages
  • delete:packages
  • admin:org
  • admin:write:org
  • admin:read:org
  • user:user:email

Make sure you copy and SAVE the token, because you will never be able to see it again!

You can encode the password that needs access to push the NuGet package with the following command. Please note that if you don’t put the -ConfigFile entry, then it will update the version of the NutGet.config file in the C:\users folder and NOT the version in your project config. This causes the issue were it works when you do it, but all downstream deployments fail, i.e.: Jenkins, Azure etc.

The command to add this to your project nuget.confg file is:

nuget setApiKey ghp_{theapikeyprovidedbyGitHub} -source https://nuget.pkg.github.com/YTGI/index.json -ConfigFile nuget.config

I’m going to repeat that you must notice the -Configfile param. If you don’t add it, the command will update the nuget.config in our current user \roaming\nuget folder, which is not what you want if this project is being deployed from a different device, i.e. Jenkins or Azure.

GitHub NuGet Push

The PUSH process is the following:

nuget push %WORKSPACE%\nupkgs*.nupkg -ConfigFile "{yourprojectfolder}\nuget.config" -src https://nuget.pkg.github.com/{yourgithuborgname}/index.json -SkipDuplicate

Notice the -SkipDuplicate argument in the above CLI command. It will cause the push (upload) command to ignore the fact that you’re trying to upload a duplicate version instead of raising an error that will fail a build. Just keep in mind that if you forget to change your version in the csproj file, your change will not show up. It is against convention to make changes to an existing version, as that could cause real problems for consuming applications. If you made a mistake, or left something out and you want to get rid of the version you just built, you’ll need to go into GitHub packages, Versions and delete the version you just built to upload it again with the same version number.

If this command works, then you have setup your nuget.config file correctly and you’re good to go.

NOTE: It will sometimes take up to a minute or so for the package to show up in the GitHub Packages list, so be patient and don’t assume it didn’t work until you give it some time.

Warning NU1701 Package ‘Microsoft.AspNet.Mvc 5.2.7’

Warning NU1701 Package ‘Microsoft.AspNet.Mvc 5.2.7’ was restored using ‘.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8’ instead of the project target framework ‘net5.0’. This package may not be fully compatible with your project.

When I create an MVC project in Visual Studio, it automatically put a reference to Microsoft.AspNet.Mvc. Since I’m now using ASP.NET 5, I started to see several of these warnings stacked in my Warning list.

I right click on my solution and selected “Manage NuGet Packages for Solution…”

On the left, highlighted “Microsoft.AspNet.Mvc”, made sure the project(s) that have it referenced have a check mark next to them on the right.

Clicked “Uninstall”

Searched NugGet for “Microsoft.AspNetCore.Mvc” and installed it to the same projects I just removed the other.

I had to remove “using System.Web.Mvc” references. These broke some “AllowHtml” decoration attributes that I had to remove, as they are no longer needed in .NET Core.

I also had to edit the project file for the main web project and remove the following line:

<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />

Made sure the solution builds. If you have any errors, you’ll want to tackle them right away while you still remember what caused them!

SQL Table Structure Compare

We ran into a problem at work where we have a table, along with a history table, or some other form of almost identical table where the definitions need to be in sync.

Here’s the scenario:

We have a table, say “clients”. There is a requirement that we track any changes to this table in a “clients_history” table. This could be implemented using a trigger, or just a secondary save in your repository using EF. The implementation is irrelevant.

A few months later, a developer is working a user story where they need to add a field to clients to track if the company doesn’t want sales people to call on the client any longer.

The developer adds a field to the clients table:

ALTER TABLE dbo.clients ADD
     isdeadbeat bit NULL

Now there are several things that will happen:

  • The developer will not know or remember to add this field to the client_history table
  • Depending on how the trigger is coded, it could raise a difficult to troubleshoot error trying to insert into the clients_history table
  • The new field data will not be saved in the clients_history table

How can we resolve this issue. Well, there are dozens of ways, but what’s the easiest?

If you deploy your Database changes from a TFS project, you can add a post deployment script that runs after every deployment. You probably do this already for inserting values into lookup tables etc.

We’ll add a new script and insert the following code:

DECLARE @Count INT;
SELECT @Count = COUNT(*) FROM (
SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.clients_history')
EXCEPT 
SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.clients')
) AS A

IF (@Count > 0)
BEGIN
	RAISERROR('There is a mismatch between two tables',18,-1);
	RETURN
END

GO

When we run just the SELECT statements with the EXCEPT, we see the there are differences identified:

If we run the entire script, it will raise and exception to let the person doing the deployment know that there is an issue that needs to be addressed. You’ll just add a new statement for each table pair that you want to check, as they are identified.

Now call this new script with your Script.PostDeployment.sql file.

Now whenever someone publishes this script, if there are unwanted differences between the two files, they will be noted.

NOTES:

  • The table that is most likely to have changes that need to be sync’d, in this case, the clients table, should be first in the EXCEPT statement.
  • If there is a field that you want to ignore for some reason, you must provide a where clause.

Multiple App.Config files

Visual studio has had the ability to have multiple Web.config files for some time now. What about the need for multiple App.config files. Many posts online talk about Unit.Test apps, which is a great application for this, but some of us old guys still automate business processes and could really use multiple app.config files for deploying Windows Services too.

The technique here is is not the same as a Web.config transformation. In a transformation, you only put the exceptions in your sub Web.config’s using a replacement line in your Web.Release.config such as:

<appSettings xdt:Transform="Replace">

This technique is different, in that you must provide a complete copy of the .config file that you want for each type of build. While this is a maintenance issue, I highly suggest that you put comments in your .configs to remind folks to add their new or modified entries to each config. I think this is less of an issue then continually releasing a Windows Service that has the wrong file paths for your file import service.

You’ll need to edit your .csproj file manually. Make sure you don’t accidentally add this to the .csproj.vspscc file.  In many version of Visual Studio, you’ll already see a section at the bottom that is commented out.

<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->

Replace the “AfterBuild” section with the following entry, un-commenting it of course:.

<Target Name="AfterBuild">
<Delete Files="$(TargetDir)$(TargetFileName).config" />
<Copy SourceFiles="$(ProjectDir)\Config\App.$(Configuration).config"
DestinationFiles="$(TargetDir)$(TargetFileName).config" />
</Target>

Now open your project and create a “Config” folder to match the entry we just put in the .csproj file. If you already have an App.config file, copy it to the \Config folder and rename it App.Debug.config. Then copy it to the other build types that you have, for instance App.Release.config and App.UAT.config.

Edit each one and remove any commented entries you have that are not appropriate for the particular release that you’re building.

I recommend putting something like this in the top of each .config file:

<!-- This file is copied and renamed by the 'AfterBuild' MSBuild task -->

<!-- Make sure that any changes you make to this file you also propagate to
the other file in the \Config folder -->