Office 365 HTML Attachment Mail Flow Rules

Apr 30, 2023 by:   Tim Stanley

Office 365 will by default filter out emails that have attachments that are executables (exe, scr, ps1, cmd, etc.) as well as an unencrypted zip file that contains executables. Office 365 allows HTML files as attachments to email because these are considered to be potentially valid email from some vendors. Attackers will attach HTML files that contain malicious javascript and will try to mask the origin of the email and it's contents. If they can convince you to download the attachment and open it, this can cause you some serious problems. This article is a summary of how to create an Office 365 Exchange Mail Flow rule to add some warning to users.

Exchange Admin Center Mail Flow Rules

Navigate to the Exchange Admin Center. Select Rules, + Add a rule

The new rule should have the following key entries:

  1. Apply this rule if any attachment matches HTML, HTML, html or htm.
  2. Prepend the subject of the message with [ATTACHMENT-FAIL]
  3. Apply and prepend a disclaimer
  4. Generate and Incident report and send it to the domain administrators.
<span style="color:red"><b><p>
This message has an HTML attachment and may be invalid or contain a virus. 
USE EXTREME CAUTION in opening any attachment.

Below is an image of the rule.

Once the rule is created, it will be disabled by default. The rule needs to be enabled to process the rules.


Related Items

Office 365 SPF Mail Flow Rules

Apr 30, 2023 by:   Tim Stanley

The Sender Policy Framework (SPF) allows one to configure DNS entries to list valid sources of truth for Office 365 email. Attackers will often try to send email from outside a domain to recipients inside the domain by pretending they are another person within the company. Creating an SPF rule that flags messages from outside the company pretending to be inside the company alerts users. This article is a summary of how to create an Office 365 Exhcange Mail Flow rule to warn users of SPF invalid messages.

Configure the SPF DNS Entry

A DNS entry is required to list the valid sources from where email can be sent. Below is an example SPF record for an Office 365 account.

v=spf1 -all

Configure the Exchange Admin Center Mail Flow Rules

Navigate to the Exchange Admin Center. Select Rules, + Add a rule

The new rule should have the following key entries:

  1. Apply this rule if the message headers 'Authentication-Results' includes 'spf-permerror' or 'Received-SPF:Fail' or 'spf-fail' or 'SPF:Fail'
  2. The sender domain is {}
  3. Prepend the subject with [SPF-FAIL]
  4. Apply a disclaimer to the message and prepend the appropriate prepend warning text.
  5. Generate and incident and send it to the domain administrators.

Sample prepend warning text.

<span style="color:red"><p><b>
[SPF-FAIL] WARNING, This messages does not appear to be a valid mail message. 
USE EXTREME CAUTION in opening any attachment.

Below are the images used in Office 365 to configure a new rule.

Enable the rule.

Related Items

Deriving Version Information from Git for Incremental Builds

Apr 24, 2023 by:   Tim Stanley

If you want to generate a unique build number for each build in Azure Devops, Git, Team City, or other environments consistently, it can be challenging. This article is a summary of how to generate a unique build number when using git repositories, across multiple environments.

Microsoft Guidance

Microsoft has some guidance for versioning of libraries at [1].

Microsoft also has some guidance on package versioning of pre-releases at [2]

Some key points:

  • CONSIDER using SemVer 2.0.0 to version your NuGet package version.
  • CONSIDER only including a major version in the AssemblyVersion. aka This helps reduce binding redirects.
  • DO use an AssemblyFileVersion the format Major.Minor.Build.Revision for file version.
  • Older versions of Visual Studio raise a build warning if this version doesn't follow the format Major.Minor.Build.Revision. The warning can be safely ignored.
  • And implied, CONSIDER making the FileVersion and PackageVersion unique for each build.

โš ๏ธ Warning: If you push your packages to a NuGet server, Azure Artifacts, or GitHub Artifacts, the PackageVersion must be unique on each build.

๐Ÿ“ Note: If a PackageVersion is not unique on each build, the nuget cache must be cleared between builds or the restore will not pick up the new package..

๐Ÿ“ Note: New style CSPROJ files that use the dotnet SDK (Microsoft.Net.SDK) style projects do not require the AssemblyInfo.cs file. Old style CSPROJ files that use the MSBUILD xml format, use AssembyInfo.cs to control default version information.

๐Ÿ“ Note: Traditional Microsoft build tooling used versioning in the format: ....

Semantic Versioning 2.0

Semantic Versioning [3] provides a versioning scheme useful for libraries and packages.

Key points semantic versioning:

  • It effects a PUBLIC API, not internals, or dependencies.
  • {major}.{minor}.{match}-{prerelease}+{metadata}.
  • Major number changes for any public API breaking non backward compatible change.
  • Uses the following format
<valid semver> ::= <version core>
                 | <version core> "-" <pre-release>
                 | <version core> "+" <build>
                 | <version core> "-" <pre-release> "+" <build>

Semantic Versioning Examples:

  • Public Release Examples: 1.0.0, 7.0.2, 7.0.3.
  • Prerelease Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92, 1.0.0-x-y-z.--.
  • Metadata Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD.
  • Precedence of prerelease is less than without prerelease, Example: 1.0.0-alpha < 1.0.0.
  • Precedence of prerelease is alphabetic, Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.

Versioning Tools


The MinVersion package is good if you want use semantic versioning 2.0 and want to embed the logic for the generation into MSBUILD properties for a project. The MinVersion CLI also provides a string that uses the same algorithm. Both the MinVersion package and the MinVersion CLI derive a version from a git tag in a special format (i.e. '7.0.2', 'ver-7.02') [4]

dotnet add package MinVer --version 4.3.0 [5]

dotnet tool install --global minver-cli --version 4.3.0


GitVersion [6] provides both a command line and MSBUILD package to set properties for a project during a build. GitVersion also has support for GitHub Actions and Azure Pipelines.

Git version provides some very powerful options for continuous builds and for builds from Visual Studio Like MinVersion, the GitVersion package is good if you want use semantic versioning 2.0 and want to embed the logic for the generation into MSBUILD properties for a project.


Nerdbank.GitVersioning [7] adds precise, semver-compatible git commit information to every assembly, VSIX, NuGet and NPM package, and more. It implicitly supports all cloud build services and CI server software because it simply uses git itself and integrates naturally in MSBuild, gulp and other build scripts.

NBGV uses a version.json file along with settings in a Directory.Build.Props file to set versioning. This means it will work with dotnet command line calls, as well as will Visual Studio Builds (if the package is added to a project).

PowerShell Git Versioning

MinVersion and GitVersion provide some compelling and powerful options, particularly if there is a desire to use the NuGet package so that version information is consistent across Visual Studio.

I have releases that are only built using command line tools, but I want to be able to build from Linux, Mac, Windows, and in Team City, GitHub Actions, and Azure Pipelines without having to adopt a new versioning strategy or change significant build scripts.

I use one of three methods for building releases, all of which are command line related:

  1. A PowerShell command line that calls dotnet msbuild, invoked by Team City build agents.
  2. GitHub Actions.
  3. Azure Pipelines.

GitVersion and MinVersion provide the semantic versioning calculations to come up with an appropriate package versioning scheme. If we simply let the git tag used by those patterns be used without the calculations, then the package versioning scheme can be simplified.

I have simplified the MinVersion and GitVersion logic even further so that with a simple and appropriate tag, an appropriate RTM version, continuous release, or pre-release can be generated, all using the same construct, and all derived from the latest tag in a git repository.

To create a version, we need a few things:

  1. A git tag with a base version number with three digits {major}.{minor}.{build} or {major}.{minor}.{patch}.
  2. A count to be used for incremental build number {revision}.
  3. An indicator if this is a pre-release or not.

By placing a tag on a git commit that contains the first three digits of a version number, then we can then count how many git commits have been made since that version number. That git tag can also include pre-release information if needed.

Git Tags

By using a pattern prefix for a tag such as 'ver-*', then it makes it easy to search the tags in a git repository and find the latest in PowerShell. This pattern of tags for versioning is used by some other tools mentioned above.

Create a tag with a prefix matching the pattern 'ver-*'.

git tag -a ver-7.0.2 -m "set version to 7.0.2"

Find the last tag with the prefix 'ver-*' powershell

$tag = git tag --list 'ver-*' | Select-Object -Last 1

Now, find the count of how many git commits have been made since the above tag.

$hash = git show $tag --format="%H" -s | Select-Object -Last 1
$line = ("git show {0}..HEAD --format=""%H"" -s" -f $hash)
$items = Invoke-Expression $line
$count = $items.Count

๐Ÿ“ Note: Git push and Visual Studio git push do not normally push tags. If creating a local tag, you must use the --tags command to push tags to the cloud after the tags are created locally.

git push --tags

Tag Examples:

  1. RTM: 'ver-7.0.2'
  2. Pre-Release: 'ver-7.0.3-alpha', 'ver-7.0.3-alpha.0', 'ver-7.0.3-beta.0', 'ver-7.0.3-prerelease.0',
  3. Release Candidate: Pre-Release: 'ver-7.0.3-rc', 'ver-7.0.3-rc.0',
  4. Continuous Build: 'ver-7.0.2'

Get-GitVersion Function

A PowerShell function that given a git semver compatible tag with at least three digits for .. or .., will return a compatible array of Microsoft matching AssumblyVersion, FileVersion, and PackageVersion strings.

function  Get-GitVersion
	param ( [string] $VersionInput, [int] $Count )

	$revision = $count
	$ver = $VersionInput.Replace("ver-", "")

	$va = $ver.Split(".")
	$major = $va[0]
	$minor = $va[1]
	# Visual Studio / MSBUILD - {major}.{minor}.{build}.{revision}
	# SEMVER - {major}.{minor}.{match}-{prerelease}+{metadata}

	# $va[2] can contain a - if using semver
	[bool] $isSemVer = $va[2].Contains("-")
	if ($isSemVer)
		$pr = $va[2].Split("-")
		$build = $pr[0]
	else {
		$build = $va[2]

	$av = ("{0}.{1}.{2}.{3}" -f $major,$minor,$build,$revision)
	$fv = ("{0}.{1}.{2}.{3}" -f $major,$minor,$build,$revision)
	if (($Count -gt 0) -or ($isSemVer))
		$pv = ("{0}.{1}.{2}.{3}" -f $va[0], $va[1], $va[2], $revision)
		$pv = ("{0}.{1}.{2}" -f $va[0], $va[1], $va[2])

	$Versions = @($av, $fv, $pv)
	return $Versions

Calling the function is a matter of gluing the elements together:

$versions = Get-GitVersion -VersionInput  $tag -Count $count
  • $versions[0] contains AssemblyVersion
  • $versions[1] contains FileVersion
  • $versions[2] contains PackageVersion

Test Samples

Below are several tag examples and what will be returned by the AssemblyVersion, FileVersion, and PackageVersions.

ag = ver-0.0.0; Count = 0
AssemblyVersion =
FileVersion =
PackageVersion = 0.0.0
Tag = ver-7.0.2; Count = 0
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.2
Tag = ver-7.0.2; Count = 3
AssemblyVersion =
FileVersion =
PackageVersion =
Tag = ver-; Count = 3
AssemblyVersion =
FileVersion =
PackageVersion =
Tag = ver-7.0.2-dev; Count = 0
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.2-dev.0
Tag = ver-7.0.2-dev; Count = 5
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.2-dev.5
Tag = ver-7.0.3-dev; Count = 3
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.3-dev.3
Tag = ver-7.0.2-1; Count = 0
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.2-1.0
Tag = ver-7.0.2-1; Count = 2
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.2-1.2
minver --default-pre-release-identifiers 'dev' --tag-prefix "ver-"
MinVer: Using { Commit: 87103c9, Tag: 'ver-7.0.2', Version: 7.0.2, Height: 38 }.
MinVer: Calculated version 7.0.3-dev.38.
Tag = 7.0.3-dev; Count = 38
AssemblyVersion =
FileVersion =
PackageVersion = 7.0.3-dev.38

Dotnet MSBUILD Parameters

Now we can pass those parameters for the version information to the build

$AssemblyVersion = $versions[0]
$FileVersion = $versions[1]
$PackageVersion = $versions[2]
dotnet msbuild $Solution -p:Configuration="Release" `
-p:AssemblyVersion=$($AssemblyVersion) `
-p:FileVersion=$($FileVersion) `

Package Reference Changes

To have a project reference both a wildcard version, and a wildcard pre-release version, an additional specifier is needed.

To Reference only a wildcard published version, use a single wildcard character.

<PackageReference Include="subsystem-1.libA" Version="7.*" />
<PackageReference Include="subsystem-1.libA" Version="[7.*, )" />

To Reference both a wildcard published version, and a pre-release version, use a two wildcard characters with a hyphen '-' separating them. This will pick up the latest package available (published or pre-release).

<PackageReference Include="subsystem-1.libA" Version="7.*-*" />
<PackageReference Include="subsystem-1.libA" Version="[7.*-*, )" />

CI/CD Counters

Several systems provide a unique build counter that is triggered on each build.

Team City

Team City provides a build.counter value that is incremented each time a build is triggered. It can be reset or set to a specific number to start with, but always increments on each build (success or failure).

Configuring General Settings | TeamCity On-Premises Documentation ( [8]

GitHub Actions

GitHub Actions provides an environment variable GITHUB_RUN_NUMBER that increments on each build. There does not appear to be a way to control it. [9]

When using GitHub Actions, the fetch-depth must be explicitly set so that the tags beyond the first commit will be available to the script.

      - uses: actions/checkout@v3
            fetch-depth: 0

GitHub Action Sample

A GitHub Action to invoke a powershell command

name: UX - Build and deploy to Azure Web App - tim-stanley-x

      - main

    runs-on: ubuntu-latest

      - uses: actions/checkout@v3
            fetch-depth: 0

      - name: Set up .NET Core
        uses: actions/setup-dotnet@v3
          dotnet-version: '7.x'

      - name: PowerShell Build
        shell: pwsh
        run: |

      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v3
          name: .net-app
          path: ${{env.DOTNET_ROOT}}/myapp
          if-no-files-found: error

Azure Pipelines

Azure has a Build.BuildNumber and within it a $(Rev:r) number.

Build.BuildNumber [10] $(Rev:r) [11]

The Rev: value is incremented on each new day if using the default value. When a build starts, if nothing else in the build number has changed, the Rev integer value is incremented by one.

When using Azure Pipelines, the fetchDepth must be explicitly set so that the tags beyond the first commit will be available to the script.

Azure Pipelines Sample

- checkout: self
  fetchDepth: 0  # the depth of commits to ask Git to fetch, 0 = full depth, so we get tags.
  fetchTags: true

- task: PowerShell@2
  displayName: 'Build'
     Build.Agent: "AZURE"
     Build.Configuration: "Release"
     Build.PublishArtifacts: "true"
     Build.Branch: $(Build.SourceBranchName)
     Build.Azure.BuildNumber: $(Build.BuildNumber)
    filePath: "$(System.DefaultWorkingDirectory)/gitbuild.ps1"
    failOnStderr: true
    showWarnings: true
    pwsh: true # Use PowerShell.Core

PowerShell Script Sample

The powershell script gitbuild.ps1 referenced by the GitHub Action and AzurePipeline

function  Get-GitVersion
	param ( [string] $VersionInput, [int] $Count )

	$revision = $count
	$ver = $VersionInput.Replace("ver-", "")

	$va = $ver.Split(".")
	$major = $va[0]
	$minor = $va[1]
	# Visual Studio / MSBUILD - {major}.{minor}.{build}.{revision}
	# SEMVER - {major}.{minor}.{match}-{prerelease}+{metadata}

	# $va[2] can contain a - if using semver
	[bool] $isSemVer = $va[2].Contains("-")
	if ($isSemVer)
		$pr = $va[2].Split("-")
		$build = $pr[0]
	else {
		$build = $va[2]

	$av = ("{0}.{1}.{2}.{3}" -f $major,0,0,0)
	$fv = ("{0}.{1}.{2}.{3}" -f $major,$minor,$build,$revision)
	if (($Count -gt 0) -or ($isSemVer))
		$pv = ("{0}.{1}.{2}.{3}" -f $va[0], $va[1], $va[2], $revision)
		$pv = ("{0}.{1}.{2}" -f $va[0], $va[1], $va[2])

	$Versions = @($av, $fv, $pv)
	return $Versions

# Calculate the version numbers from git tags and commits
# obtain the latest git tag
$tag = git tag --list ver-* | Select-Object -Last 1
[int] $count = 0
if ($null -ne $tag)
	# calculate how many commits since the tag
	$hash = git show $tag --format="%H" -s | Select-Object -Last 1
	$line = ("git show {0}..HEAD --format=""%H"" -s" -f $hash)
	$items = Invoke-Expression $line
	if ($null -ne $items)
		$count = $items.Count

	Write-Host ("Tag={0}" -f $tag)
	Write-Host ("Count={0}" -f $count)

	$versions = Get-GitVersion -VersionInput  $tag -Count $count
	$AssemblyVersion = $versions[0]
	$FileVersion = $versions[1]
	$PackageVersion = $versions[2]
else # No tag found, set some defaults
	Write-Host("No tag found, using defaults")
	$AssemblyVersion = ""
	$FileVersion = ""
	$PackageVersion = ""

Write-Host ("FileVersion = {0}" -f $FileVersion)

$p = "./artifacts/logs/"
if (-Not (Test-Path -Path $p))
    $d = New-Item -ItemType Directory $p

# Restore
Write-Host ("dotnet restore")
dotnet restore ./Source/ContentEngine.Mvc.sln `

# Build
Write-Host ("dotnet msbuild")
dotnet msbuild -p:Configuration=Release ./Source/ContentEngine.Mvc.sln `
    -p:AssemblyVersion=$($AssemblyVersion) `
    -p:FileVersion=$($FileVersion) `
    -p:Version=$($PackageVersion) `
    -p:AllowedOutputExtensionsInPackageBuildOutputFolder=\""".dll;.exe;.winmd;.json;.pri;.xml\""" `
    -p:IncludeSymbols=true `
    -p:SymbolPackageFormat=snupkg `
    -nodeReuse:false `

# set the output directory for publish
Write-Host ("env:DOTNET_ROOT = {0}" -f ${env:DOTNET_ROOT})
if ($null -ne ${env:DOTNET_ROOT}) {$d = ${env:DOTNET_ROOT}}
else {$d = $PWD}

# Publish
Write-Host ("dotnet publish")
dotnet publish --configuration Release ./Source/ContentEngine.Mvc/ContentEngine.Mvc.csproj -o $d/myapp --no-build


Related Items

Building an Azure Dev Test Lab - Networking

Mar 11, 2023 by:   Tim Stanley

This is part of a series on Building an Azure Dev Test Lab.

There are many things that can be done in Azure without setting up Azure Networking. But, there are also many reasons to setup a private Azure network. This article is a summary of what Azure Services are required to get Azure networking in place.

What Is Azure Networking?

In the context of this article, Azure networking refers to the Azure systems and services that are required for one Azure service or virtual machine to communicate to another Azure service or virtual machine over a private (not public) network.

Azure Networking Not Required

You can do any of the following without any private Azure networking:

  1. Authenticate users using Azure Authentication for Office 365, Azure Devops, and external SAML authentication.
  2. Push code to/from Azure Devops repositories.
  3. Push deployments from Azure Devops to Azure Services / Websites.
  4. Host Azure Websites on the Internet.

Azure Networking Required

Your Azure system may be a candidate for a private Azure network, if you need any one of the following:

  1. Join Azure VM's to an Azure Active Directory Domain Service. You'll need AADDS.
  2. You want VM to VM traffic on a private network. You'll need an Azure Virtual Network.
  3. You want a VNET / VLAN. You'll need an Azure Virtual Network.
  4. You want an external VPN into a private Azure network. You'll need and Azure Gateway or a FortiGate Virtual Appliance Firewall
  5. You want a WAN VPN from Azure to your premises network. You'll need and Azure Gateway or a FortiGate Virtual Appliance Firewall
  6. You want to run legacy applications in the cloud (legacy equates to Windows Services, Websites, or other Windows applications not designed for the cloud).

Azure Active Directory Domain Services (AADDS)

AADDS [1] is the fundamental building block for an Azure Dev Test lab. It provides the "Domain Controller" functionality in Azure (but it's not really a Domain Controller). AADDS allows you to use managed domain servicesโ€”such as Windows Domain Join for VM's, group policy, LDAP, and Kerberos authentication (simple sign-in using Azure AD credentials).

Steps to Create AADS Services

  1. Create a subscription and resource group to assign to the AADDS.
  2. Define region (CENTRAL-US, EAST-US, WEST-US, etc.).
  3. Pick SKU: Standard, Premium, Enterprise (Premium, Enterprise allow resource domains).
  4. Create Azure AD Domain Services (, or, but could be
  5. Add a new network, aadds-vnet (must be,, or
  6. Create new group AAD DC Administrators.
  7. Configure DNS (after AAADS allocated) which will configure DNS for above VNET to point to (2) AD services DC's.
  8. Must have Azure AD Cloud Sync password write-back enabled.
  9. Enable Azure AD Password sync to enable password hash synchronization.

For a more detailed explanation on setting up AADS Services, refer to this youtube video:

Lets Get One Thing Straight | Azure AD Domain Services

Azure Virtual Network

Azure Virtual Network [2] allows you to create your own private network in the cloud. Just like in your on-premises network, there are numerous security reasons you don't want everything you do on the public internet. Azure Virtual Network provides:

  • IPSEC or VPN / WAN from your on-premises network to Azure.
  • Your own DNS services.
  • Your own IP addresses.
  • Network Address Translation (NAT)

Azure Bastion

Azure Bastion [3] provides the services to allow remote desktop connections to an Azure VM, without requiring a VPN, or without requiring the RDP port 3389 to be exposed on the open internet. VPN's can cut bandwidth by 30-40%.

Azure Bastion

Azure Virtual Machines

Azure Virtual Machines [4] allow you to get your legacy applications in the cloud. These are not your typical VM's. Azure VM's can provide:

  • up to 416 vCPUs
  • up to 12 TB of memory
  • up to 3.7 million local storage IOPS per VM.
  • up to 30 Gbps Ethernet.
  • up to 200 Gbps InfiniBand internet.

Linux VM's will be cheaper than Windows VM's. Reserved Virtual Machines will be cheaper than Pay as you go.

Azure Dev-Test box [5] provides another VM capable of running a developer class workstation. A Dev-Test box template can be setup and configured once and then developers can pull from a pool of boxes. A Dev-Test lab can be configured to shut down unused VM's during certain hours to minimize expenses.

Codespaces [6] also provides another VM capable of running developer scenarios. Azure Codespaces have been migrated to GitHub Codespaces [6]. GitHub uses GitHub Codespaces [7] to develop GitHub. In under 10 seconds, you can spin up a Codespace. If your application development process can use Visual Studio Code, Codespaces eliminates the need for developer machine by running a virtual developer machine in the cloud. It's significantly cheaper than a full private network.

Azure VPN Gateway

Azure VPN Gateway [8] provides the ability create a site to site IPSEC VPN or a point to site VPN from anywhere to your Azure network.

Azure Firewall

Azure Firewall [9] protects your Azure Virtual Network resources in the cloud. It protects your Azure private network just like an on-premises firewall would.

Fortinet FortiGate Virtual Appliance Firewall

If you have Fortinet firewalls and routers, then it may be easier to use a custom VM and purchase a VM version of the a FortiGate Virtual Appliance [10]. The Fortigate FG-VM02V VM support 2 vCPU cores and supports 15 Gbps bandwidth. I've used the FortiGate 60E at multiple sites for years. It provide a great Firewall, and stellar support. The Firewall keeps your private network private.

Azure Front Door

Azure Front Door [11] provides a Content Delivery Network (CDN), Load balancing and failover, Web Application Firewall, DDOS and bot protection. If your running a production website, Azure Front Door can provide the necessary protection. CloudFlare [12] is another vendor that can provide website and API protection.

Lab Scenarios

That's a lot of services for Azure Networking. Let's take a look at some scenarios and see what is required. I'm going to preface this with Microsoft is continually changing what is present in Azure., so this information is based on Q1 2023 Azure information.

LAB 1 Public Website / API Development

This would be a thin Azure system and would include the following:

  • Azure Devops Git repositories for code (requires a Visual Studio or Stakeholder license)
  • Azure Pipelines (included with Azure Devops)
  • Visual Studio code (free)
  • Azure Dev App Services / Websites
  • Azure Test App Services / Websites

This configuration does not require any private Azure networking.

LAB 2 Private Web Dev / Test APIs

This would be a private network Azure system and would include the following:

  • Azure Devops Git repositories for code (requires a Visual Studio or Stakeholder license)
  • Azure Pipelines (included with Azure Devops)
  • Private Dev Server API's (Azure Websites) running on Azure AADS account.
  • Private Test Server API's (Azure Websites) running on Azure AADS account.
  • Developers have an AD joined machine and account.

Since this configuration is a private network, an Azure network is required. Typical services would include:

  1. Azure Active Directory Domain Services (AADDS)
  2. Azure Cloud Sync (to mirror AD accounts to Azure).
  3. Azure Virtual Network with private IP's.
  4. Azure Websites assigned a private IP.
  5. Azure Front Door, or Azure Firewall

LAB 3 Private Web Dev / Test APIs with Azure SQL

This would be a private network Azure system and would include the following:

  • Azure Devops Git repositories for code (requires a Visual Studio or Stakeholder license)
  • Azure Pipelines (included with Azure Devops)
  • Private Dev Server API's (Windows VM, Linux VM's)
  • Private Test Server API's (Windows VM, Linux VM's)
  • Developers have an AD joined machine and account.
  • Dev Azure SQL database instances.
  • Test Azure SQL database instances.
  • Developers use private Azure VM's to develop / test.
  • Site to Site VPN (WAN)

Since this configuration is a private network with VM's, an Azure network is required. Typical services would include:

  1. Azure Active Directory Domain Services (AADDS)
  2. Azure Cloud Sync (to mirror AD accounts to Azure).
  3. Azure Virtual Network with private IP's.
  4. Azure VM's assigned a private IP.
  5. Azure Bastion for remote VM access.
  6. Azure SQL instances assigned a private IP.
  7. Azure Gateway
  8. Azure Firewall or FortiGate Virtual Appliance Firewall for Site to Site VPN.

๐Ÿ“ Note: If you have a need for multiple deployment slots (production, staging, development) other than the default production slot, you need to have a Standard, Premium, or Isolated plan [13]. You can work around this limitation by having two individual sites, and manually changing the DNS records to switch between the sites.


Related Items

Building an Azure Dev Test Lab - Data Centers

Mar 07, 2023 by:   Tim Stanley

This is part of a series on Building an Azure Dev Test Lab for software development.

Azure has Data Centers located all over the globe. But not all Data Centers have all the same features or price structure. Before you begin to setup Azure services, make sure the Data Center you select for your Azure Products and Services support what you will need and that you know the pricing.

Where are Azure Data Centers Located?

The Microsoft Azure Global Infrastructure site [1] lists the Data Centers Azure provides. It is important to do a little and planning to know all the services you need from a data center before you begin to set something up.

Explore the Azure Global Infrastructure site to get more details.

Compliance and Data Residency

Knowing what specific country or standard compliance rules are required for storage, and data compliance are important for production data, but rarely so for a dev test lab. Azure Compliance [2] and Data Compliance [3] lists over 90 country specific compliance guidelines. This can be important when trying to conform to GDPR or other country specific data laws and guidelines.

Service Availability

Each Azure Data Center provides a different range of services. For example, the Central US and East US regions support Zonal DR with Azure Site Recovery , but East US and North Central US do not. Make sure that all the services you are planning on are supported within the Data Center where you plan on deploying.


Azure Pricing [4] for example lists that West US 3 does not have an App Service F1 Free or D1 Shared plan option whereas West US 2 does.

Linux or Windows

Azure Pricing for Windows and Linux can vary significantly.

Azure App Service pricing for Windows (March 2023):

Azure App Service pricing for Linux [5] (March 2023):

For a Dev Test Lab, a Linux solution is going to be cheaper than a Windows one.

  • Basic dev/test Linux: $12.41 / mo.
  • Basic dev/test Windows: $54.75 / mo.


Related Items

Building An Azure Dev Test Lab - Azure Devops Migration

Feb 14, 2023 by:   Tim Stanley

This is part of a series on Building an Azure Dev Test Lab for software development.

This is a summary of how to migrate from an on-site Microsoft Team Foundation Server (TFS), aka Visual Studio Team Services, aka Azure Devops Server to Azure Git repositories. This article does not cover topics of Azure automated builds, automated tests, or automated deployments. These same steps would apply if migrating to GitHub Enterprise.

The scope of this article is limited to source control. The migration of TFS Work items is not supported by the git-tfs tool.

Microsoft has many names for TFS that are all the same services, just a different name at one point in time. I use the name TFS to refer this product.

  • Microsoft TFS Server
  • Visual Studio Team Services (VSTS)
  • Azure Devops Server

TFS Terms

TFS uses the term "Collections" as the highest organization level that corresponds to the Azure git repository term "Projects".

TFS uses the term "Projects" that correspond to Azure git "Repositories" aka repos. An TFS "Project" and a git "Repository" exist underneath a TFS "Collection" or an Azure git "Project".

Information Required for Migration from TFS to Git

Identify all the TFS Collections and Projects to be exported to git

Pick one non mission critical small to medium size TFS Project to migrate to Azure Devops git first. If you run into issues, you can always start the export migration process again. This allows you to get something working and get experience on all the steps before trying to tackle larger more complex TFS Projects.

After identifying the first TFS project to export, prioritize any active collections and project that need to be migrated first, and inactive ones last. Using the history in TFS can show what activity has occurred and when. The general recommendation would be to export every TFS Collection, so that TFS can be shut down when the work is complete

Migrating one collection at a time allows you to get moving forward with Azure without a long calendar time to do so. If you need to run a dual repository scenario (both TFS and git active at the same time), you can run a script on a recurring basis to mirror from TFS to git.

Decide the git branch to TFS branch name mapping

Decide a git default branch naming convention such as main (recommended) or master. Set your local default git user configuration to use the correct default name [1]. This needs to be done and configured before you run git-tfs.

Download and Install Git-TFS

The git tool git-tfs [2][3] can be used to export TFS change history to git.

Note: It is best to use git-tfs on a client that is on the exact same local area network as the TFS server. When exporting large projects, timeouts can occur if using a remote TFS server. It is also best if that client is a virtual machine that can be dedicated for use to mirror from TFS to git while repositories while migration is underway.

Note: git-tfs will create a git change-set for each TFS change-set. This git-tfs change-set will contain the TFS collection, project, and TFS change-set as part of a comment added to the git change-set. This is very helpful in determining where a repository was migrated from.

Decide which branches to export

The general recommendation would be to export every project, and branch in a TFS collection. Exporting every project and branch in a collection is going to take longer to export from TFS, and the git repository may be large.

Note: exporting a large TFS project using git-tfs with 20,000 changes might take two-three days to create a local git repository the first time.

The git to TFS tool cannot export a branch that's been renamed. The git-tfs list-remote-branches command will list branches that it is able to export, and you can use that information for your planning.

git tfs list-remote-branches http://tfs:8080/tfs/DefaultCollection

If TFS is using a folder instead of a branch, the folder can be modified to become a branch in TFS and then exported using git-tfs.

The tool TFS to git can export all branches, or a single branch, but there does not appear to be a way to export multiple branches.

Decide how much history to preserve

History is usually very important to find out what happened, when, and by whom. The recommendation would be to preserve all history where possible.

If your exported repository with all changes is too big, you may need to limit history to the most recent set of changes (for example the last year), instead of exporting all history. If you decide to limit history, you will need to identify which specific change-set to use for exporting on each project.

Decide if TFS to git mirroring is required

Git-tfs supports the ability to mirror any TFS changes to a git repository on a regular basis. Git-tfs mirroring is recommended and a good way to get a read-only copy of source into git and verify all change history and changes migrate first.

โš ๏ธ Warning: git-tfs mirroring only worked one way (TFS to Git). I was not able to make two-way round-trip mirroring from git back to TFS work. At the time I performed migration (2022), any changes to a git repository used for TFS to Git mirroring, broke the mirroring capability.

I was able to take a git-tfs repository and create a new git branch and make changes to it on the git side without breaking TFS mirroring to git. I could then use git merge to merge the changes from the main TFS branch into a development branch in git. This allowed the TFS and git repositories to be used at the same time on different branches. If using this approach, don't expect the git branch to ever migrate into TFS and expect TFS mirroring to work.

Decide what to do about binaries in source control

Git is great for storing text files efficiently and quickly, but using it to store binary files (releases, dll references, etc.), make the repository grow large quickly, and that takes longer to transfer on clone / pull operations. Instead of storing binaries, using a Nuget server, Azure artifact storage, Azure file storage, or Git-LFS are the preferred options for storing binaries.

Decide a default Azure working path

While it is strongly recommend not to embed paths into source control, there are some Microsoft tools or systems that require a path (configuration files, test suites, etc.). Standardizing the path for all developers and build systems is the only way to solve this until those tools and configurations can all handle relative paths.

Example standardized azure path:


Information Required to configure Azure Devops Git

Identify new Active Directory Security Groups and naming conventions

I use the following security group naming convention for Azure.

Pattern: -. * DSG - Domain Security Group (set at the AD domain level, and mirrored to Azure) * ASG - Azure Security Group (set at the Azure group level) * AZDO - Azure Devops group (set at a git repository level) * Security Group Name * TFS Collection or Azure Project Name * .Team - Read/Write permissions * .ReadOnly - Read Only permissions * .Admin - Administrator permissions

DSG-.Team - Defined at Active Directory Level in AD ASG-.Team - Defined at Azure Level in Azure AZDO-.Team defined at the level AZDO-.Read Only defined at the level

Identify new Azure Git Projects and naming conventions

When migrating, from TFS to git it can be helpful for users to have similar names that correspond to the old TFS names, such as:

  • A git project name with a name or abbreviation similar to the TFS Collection
  • A git repository name with a name or abbreviation similar to the TFS Project

Identify new Azure Security Groups

It is strongly recommended and encouraged that almost always users should never be assigned direct permissions to an Azure project or repository. Instead, assign permissions to groups and then assign users to those groups.

โš ๏ธ Warning: In 2022, Azure Devops only supports one level of group assignment. It does not support hierarchical groups.

Some suggested group conventions:

  • Use a region in the group name: ASG-.Team, i.e. ASG-DEV-US.Team, ASG-DEV-EMEA.Team for all developers in the US or all developers in Europe, Middle East, and Asia.
  • Separate contractors from employees, i.e. ASG-DEV-Employees.Team, ASG-DEV-Contractors.Team

Identify new Azure Git Project Group Names

It is suggested to create project groups and assign those to the appropriate Azure role permissions instead of assigning user directly to Azure roles. This will make it easier in the future to manage permissions. AZDO-.Admin defined at the level AZDO-.Team defined at the level AZDO-.ReadOnly defined at the level

Decide in what region your Azure Devops organization is to be hosted.

๐Ÿ“ Note all Azure services are available in all regions. Before you create your Azure Devops organization, review the capabilities for you region. Likewise, not all regions have the same cost structure.

Refer to the List of Azure Regions [4] for more details about where they are located and what they can provide before selecting where your Azure Devops organization will be hosted.

Define a Git Branching Strategy

There is much debate about what is the best strategy for branches and it's a hotly debated item.

A branching strategy for a GitHub Open-Source project needs to restrict who can check in code, make sure a pull request builds properly, and needs to be reviewed carefully before allowing it got be pushed upstream.

A small company with few team members, on the other hand, needs to be as efficient as possible. A strategy with a single branch and all releases being pulled off the main branch and hot fixes built from a _releases_release branch is probably more suitable for that company than something for an open-source project.

The term Trunk Based Development [5] has been used to describe a git branching strategy to avoid git merge hell. Trunk Based Development describes itself as:

A source-control branching model, where developers collaborate on code in a single branch called โ€˜trunkโ€™ *, resist any pressure to create other long-lived development branches by employing documented techniques. They therefore avoid merge hell, do not break the build, and live happily ever after.

Microsoft has published some of their tips on how to Select an effective branching strategy [6]

Microsoft also has published some tips on How Microsoft plans with DevOps [7], and on Git Branching Guidance [19]

After doing a bit of research, I came to a conclusion of the following branch strategy.

  • main โ€“ builds from here, this is always the tip for most current development
  • releases/{name} โ€“ builds from here
  • users/{username}/{description} - no builds from here, users can share
  • features/{name} โ€“ no builds from here

Decide How to handle Git Training

Git is similar to other source control systems. Visual Studio 2022 provides several UI integrations that make TFS UI operations done on TFS very similar to the kind of operations needed in git. VS 2022 UI git integrations, in my opinion, make the training curve much simpler.

Having a regional goto person in the same time zone to ask git questions can also be helpful to team members. Git merges and resolving merge conflicts can be intimidating the first time.

There are multiple links at, and youtube that provide extensive information for git.

Performing TFS to Git Migration

The git tfs list-remote-branches command is used to list all branches that can potentially be exported. Convert any TFS folder to branches if they are not listed.

git tfs list-remote-branches http://tfs:8080/tfs/DefaultCollection

โš ๏ธ Warning: A branch that has been renamed in TFS usually cannot be exported by git-tfs.

Target branch is always main (or git default) Do this on a machine that is on the same network as the TFS server or timeouts will occur. git-tfs will put changeset comments for git commits in all changesets pulled from TFS

To extract TFS to a Local Git Repository All Branches

The git-tfs clone command may take several hours or days.

$tfsCollection = "Collection"
$tfsProject = "Project"
$tfsBranch = "Main"
git-tfs clone --with-labels --branches=all $tfsurl $tfsproject $dest
cd $dest
git tfs bootstrap

To extract TFS to a Local Git Repository Specific Branch

git-tfs clone --with-labels $/Project/Branch C:\Azure\{organization}\{project}\{repo}

๐Ÿ“ Note: The git-tfs clone command will create a default branch on the local repository.

To extract TFS to a Local Git Repository Specific Change-set

git-tfs clone --with-labels --from 21263 $/Project/TFS-Branch 

๐Ÿ“ Note: The git-tfs clone command will create a default branch on the local repository. The local repository branch will be the git default (main or master), and that will not match the TFS branch your pulling from.

Create a new Azure organization

If you have not already, use Azure Devops to create a new Azure Devops organization. The Azure Devops Link has an option to create a multiple new organizations in your Azure tenant. Make sure you know what regions you want your new Azure Devops organization hosted in before you create the organization.

Create a new Azure or Git Project.

Within the organization, create a new Azure Git Project. An Azure Devops Project can contain multiple repositories. Once the repository is created, create the new repository groups (AZDO-.Team, .ReadOnly, .Admin) and assign those groups to the appropriate roles.

Create a new Azure Git Repository

Within the Azure Devops Project, create a new Azure Git repository. It will inherit the Azure Git Project permissions.

โš ๏ธ Warning: Do not create a file for the repository at the Azure cloud level or this will cause a merge which will break TFS to git mirroring.

Push the Local Git Repository to the Cloud

Once the cloud repository is configured and roles and groups are assigned, the local git-tfs repository can be pushed to the cloud. This should be done on the same client workstation where the git-tfs clone command was executed.

The url used for the push can be found on the Azure repository clone repo tab.

git remote add origin
git push -u origin --all

To mirror a TFS repository to Git

Below are the general overall steps to run a script on a scheduled basis to mirror from TFS to git. The git-tfs pull and git push commands perform the heavy lifting, the other steps are there just to show diagnostic information.

git-tfs pull --branches=auto
# Switch to branch to push
git checkout $Branch
# Show status
git status
git push -u origin --all --verbose
# Show branches
git branch -a --verbose"

If you need to perform development on a git branch 'main-dev' while TFS to git mirroring is being done on 'main', you can use the git merge command to merge changes from the main branch into main-dev.

git checkout main
git pull
git checkout main-dev
git pull
git merge main

Once TFS to git migration is complete, and mirroring is no longer required. Those changes in main-dev can be merged back into the main branch.

Add a .gitconfig

TFS only tracks files added to TFS. Git tracks all files added to a folder, unless an entry in the .gitconfig says to ignore it. To inform git of which configs to add, a .gitconfig file needs to be added to a project. If using a c-sharp project, use a c-sharp .gitconfig as the starting point. That prevents some of the visual studio build files and folder (such as /bin or /obj) from getting checked in.

Azure Devops Go Live checklist

It's helpful to have a go-live checklist before having users switch over to git. Below are some suggestions.

  1. Add .gitconfig to all repositories and all active branches
  2. Test builds from git before turning off tfs to git mirroring.
  3. Validate all users have appropriate permissions to Git repositories.
  4. Set TFS to block checkins on all roles (all users, administrators, services, build agents).
  5. Turn off TFS to Git replication for all repositories

Azure Devops or GitHub Enterprise

Everything in this article covers migration specific to Azure Devops. But what about GitHub Enterprise services? GitHub Enterprise provides similar services to Azure Devops. Both are owned by Microsoft. What should one use? The answer will probably vary over time.

Microsoft purchased GitHub Enterprise in 2018 [12]. Few companies can afford to keep two products that perform the same business function, so the question is which one will win over time. So far, Microsoft seems to be providing a long term role for both.

As of 2022, new features such GitHub Security features [15] as GitHub Advanced Security [16], Dependabot [17], and GitHub CoPilot [18] are not available in Azure. These are additional features in GitHub that require additional subscription costs.

Azure Devops and GitHub have different pedigrees. is focused on Open Source and Azure Devops on Enterprise source control management.

An organization that has Visual Studio Professional Licensing gets Azure Devops licensing included. As of 2022, Those same users have to pay additional license fees to use GitHub Enterprise for source control. That additional cost makes GitHub Enterprise a harder leap for organizations with a Visual Studio License already. GitHub Enterprise in 2022 provides SAML Azure integration, but it's not as seamless as the Azure Devops group integration with Azure.

Azure Devops Work Items Migration Tools

From my research, migration of TFS work items to git is significantly more complex than migrating source code projects. naked Agility has published Azure DevOps Migration Tools [13] to help migrate work items from TFS to Azure or Github.


Related Items

Building an Azure Dev Test Lab - Authentication

Feb 11, 2023 by:   Tim Stanley

This is part of a series on Building an Azure Dev Test Lab for software development.

This is a summary of how to migrate from an Active Directory (AD) on premises system to Azure Cloud, how to migrate Active Directory users and groups to Azure using AD Cloud Sync, how to use a single sign-on for AD and Azure, how to enable Multi Factor Authentication (MFA) for Azure authentication and set some appropriate security policies for MFA.

Information Required for the Migration

  • Active Directory User Principal Name (UPN): example: * - listed in Active Directory Domains and Trusts
  • List of Active Directory Users to Migrate to Azure
  • List of Active Directory Groups to Migrate to Azure
  • Azure Primary Domain example: - listed in Azure Active Directory, Overview

Prepare Active Directory for Migration

Prepare the Active Directory UPN Suffix to Match the Azure Primary Domain

To find out the Azure Primary domain, navigate to the Azure Active Directory portal, in the Overview tab, which should list the Azure Primary domain. For example:

Azure Overview

To find out the Active Directory UPN, on the domain controller, launch the Active Directory Domains and Trusts, select the domain, right click properties to list the UPN Suffixes. For example,

By convention, the UPN should map to the user email name. But, if an AD and Azure system have been setup with different values, these must be resolved before migrating to Azure.

If the UPN Suffix does not match the Azure Primary domain, add a new UPN suffix with the exact name as the Azure Primary domain, for example: *

Active Directory Domains and Trusts

Now modify all users and set their UPN Suffix to the same name as the UPN Suffix just added, which must be the same as the Azure Primary domain.

On the domain controller, launch Active Directory users and computers, select the user, right click, select properties, and select the Account tab. The User logon name will have a dropdown listing the UPN suffixes that can be selected.

When any new user is added, the UPN suffix must be set to the same value as the Azure Primary domain.

User domain properties

A Powershell script can also be run on the domain controller to update the user UPN suffix.

$LocalUsers = Get-ADUser -Filter {UserPrincipalName -like '*'} -Properties userPrincipalName -ResultSetSize $null
$LocalUsers | Select userPrincipalName,Name,SamAccountName
$LocalUsers | foreach {$newUpn = $_.UserPrincipalName.Replace("",""); $_ | Set-ADUser -UserPrincipalName $newUpn}
$LocalUsers = Get-ADUser -Filter {UserPrincipalName -like '*'} -Properties userPrincipalName -ResultSetSize $null
$LocalUsers | Select userPrincipalName,Name,SamAccountName

Set Each users Active Directory Email property

Each user must have an email property defined that matches the Azure / Office 365 email address that will match.

On the domain controller, launch Active Directory users and computers, select the user, right click, select properties, and select the General tab. The User Email property must be set. For example

When configuring the user, I also recommend configuring any office or mobile phone numbers, and addresses in the Telephones and Address tabs. These Active Directory properties set on the domain controller will get pushed to Azure (AD is the primary source for this information).

User email properties

Prepare Active Directory Using the IdFix tool

Once the users UPN and email Active Directory properties are set, the next step is to run the IdFix tool.

Microsoft recommends running the IdFix tool on Active Directory to prepare for migration to Azure. Download the MSI, install it, and run the IdFix tool. [1]

IdFix is used to perform discovery and remediation of identity objects and their attributes in an on-premises Active Directory environment in preparation for migration to Azure Active Directory. IdFix is intended for the Active Directory administrators responsible for directory synchronization with Azure Active Directory.

The purpose of IdFix is to reduce the time involved in remediating the Active Directory errors reported by Azure AD Connect. Our focus is on enabling the customer to accomplish the task in a simple expedient fashion without relying upon subject matter experts.

The Microsoft Office 365 IdFix tool provides the customer with the ability to identify and remediate object errors in their Active Directory in preparation for deployment to Azure Active Directory or Office 365. They will then be able to successfully synchronize users, contacts, and groups from the on-premises Active Directory into Azure Active Directory.

Create an OU to be Synced with Azure

On the domain controller using Azure Users and Computers app, create a new OU Container "AzureADConnect". This will be used when AD Cloud Sync is configured. Add a Users and Groups OU under AzureADConnect.

Active Directory OU

Move all users identified in the information gathering phase that will have both AD and Azure logins under the Users OU.

Move all security groups identified in the information gathering phase that will be used in both AD and Azure to the Groups OU.

Configure AD Cloud Sync

Prepare Azure for Migration

Azure AD Cloud Sync replaces the old Azure AD Sync tool. Azure AD Cloud Sync is much simpler to configure and install. Refer to Prerequisites for Azure AD Connect cloud sync [7] for more details.

Disable Any Existing AD Sync

  1. Disable the Connect sync scheduler by opening an admin PowerShell prompt and running

    Set-ADSyncScheduler -SyncCycleEnabled $false
  2. Stop the Connect-related services and change their startup type from Automatic (Delayed Start) to Disabled

    Microsoft Azure AD Sync

    • Azure AD Connect Health Sync Insights Service
    • Azure AD Connect Health Sync Monitoring Service
  3. Disable any monthly password rotation scheduled task

Install AD Cloud Sync

Download the agent from your Azure portal at Azure AD Connect cloud sync [4]

Refer to the Microsoft instructions on Install the Azure AD Connect provisioning agent [5]

I recommend letting the AD Cloud Sync installer select the gMSA account. If you install AD Cloud Sync on two systems, I recommend letting the installer select the gMSA account.

Once the AD Cloud Sync is installed, it will be listed on the site Azure AD Connect cloud sync [4] on the tab On-premises provisioning agents.

Configure Conditional Access Policies

Microsoft outlines information on conditional access policies here:

The following types of conditional access policies are recommended:

  • Internal user - enforce MFA (Require multifactor authentication for all users)
  • External user - enforce MFA (Require multifactor authentication for guests)
  • IP Location

Azure Conditional access policies are defined at Conditional Access [8]

IP Location based policies are used when a business has a static IP at an office location, and the company wants to create a policy that the "IP Location" acts as the MFA. This makes using Azure more user friendly when working at a company office location.

When an external MFA user from an outside Office 365 organization enrolls in an MFA, their primary email configured as a guest must match their exact UPN on their Office 365 system, not an alias.

Creating a conditional access policy requires an Azure AD Premium P1 license. Refer to the Microsoft Conditional Access overview above for more detailed information.

Below are the steps to create a conditional access policy for External users. Create a Create A New Policy from a template. Select "Require multifactor authentication for guest access"

Active Directory Overview

Set the Policy state to On to have the policy immediately take affect. Do not turn the policy on until your users have been informed how to enroll in MFA. Select next, on the Review + create, you will need to set Enable security defaults to No.

Active Directory Domains and Trusts

There are currently two legacy Azure administrative options available to enforce MFA. These are older options prior to Microsoft publishing conditional access policies and these may go away at some point. Do not enable or enforce MFA using these options until the users have been informed on how to enroll in MFA.

User/Service Multi-Factor Authentication Settings

The link for these two options is at: Azure, Users, Per User MFA [9]

Select the Users tab, select the user, Enable MFA. Once enabled, the next logon from the user will require MFA enrollment. The terms are slightly confusing, "Enable MFA" actually means enforce on next logon.

To specify an IP Location where MFA is not required, select the service settings tab. Recommended settings are:

  • app passwords: Do not allow users to create app passwords to sign in to non-browser apps.
  • trusted ips: skip muti-factor authentication for request from federated users on my intranet (specify an IP range such as
  • verification options: enable all options
  • remember multi-factor authentication on trusted device: allow users to remember multi-factor authentication on devices they trust: 60 days.

Multi Factor Authentication

Enrolling Users in MFA

Users need to be informed ahead of time how to enroll in MFA so that when MFA enforcement is turned on, they know how to quickly update their account and login using MFA. Users can quickly navigate to the Microsoft user account security info tab by using the following url: [10]


Related Items