Set up an Active Directory lab (part 3)

Let's continue on with creating our Active Directory lab in Azure. Part 1 described:

  • Creating the basics - resource group, storage account & VNet
  • Creating VM#0 - Bastion Host
  • Creating VM-A#1 - AD Domain Controller for the forest & domain 'corp.local'
  • Creating VM-A#2 - AD Domain Controller for the domain 'contoso.internal', part of 'corp.local' forest

Part 2 described:

  • Creating VM-A#3 - Member Server - IIS web server running AuthPage
  • Creating VM-A#4 - Member Server - Azure AD Connect
  • Creating VM-A#5 - Windows 10 client

This part - the finale - will cover old ground to create Domain B:

  • Create VM-B#1 - Domain Controller 'wingtip.root'
  • Create VM-B#2 - Member Server - IIS + Azure AD Connect
  • Create VM-B#3 - Windows 10 client

Create VM-B#1 - App01 - Web Server

  • VM-B-DC01
  • Unmanaged OS & data disk
  • Windows Server 2019
  • Static IP address
  • Create new AD forest 'wingtip.root'

Create NSG

$nsg5Name = "jr-trustlab-nsg-b-ad"

az network nsg create -n $nsg5Name -g $rgName
az network vnet subnet update -g $rgName -n $subnet4Name --vnet-name $vnetName --network-security-group $nsg5Name
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Allow_RDP" --priority 100 --access "allow" --source-address-prefixes $subnet99Range --destination-address-prefixes $subnet4Range --destination-port-ranges "3389" --protocol "TCP" --description "Allow RDP from Bastion"
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Allow_AD_TCP" --priority 110 --access "allow" --source-address-prefixes $subnet4Range $subnet5Range $subnet6Range --destination-address-prefixes $subnet4Range --destination-port-ranges 135 389 636 53 88 445 49152-65535 --protocol "TCP" --description "Allow AD traffic TCP"
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Allow_AD_UDP" --priority 111 --access "allow" --source-address-prefixes $subnet4Range $subnet5Range $subnet6Range --destination-address-prefixes $subnet4Range --destination-port-ranges 53 88 389 --protocol "UDP" --description "Allow AD traffic UDP"
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Allow_A_AD_TCP" --priority 120 --access "allow" --source-address-prefixes $vm2IPAddress $vm4IPAddress --destination-address-prefixes $subnet4Range --destination-port-ranges 135 389 636 53 88 445 49152-65535 --protocol "TCP" --description "Allow AD traffic TCP"
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Allow_A_AD_UDP" --priority 121 --access "allow" --source-address-prefixes $vm2IPAddress $vm4IPAddress --destination-address-prefixes $subnet4Range --destination-port-ranges 53 88 389 --protocol "UDP" --description "Allow AD traffic UDP"
az network nsg rule create --nsg-name $nsg5Name -g $rgName -n "Deny_Inbound" --priority 4000 --access "deny" --source-address-prefixes "*" --destination-address-prefixes $subnet4Range --destination-port-ranges "*" --protocol "*" --description "Deny inbound traffic"

$logWsName = "jr-trustlab-logs"
$flow5LogName = "jr-trustlab-flow-b-ad"
az network watcher flow-log create -n $flow5LogName -g $rgName --enabled true --nsg $nsg5Name --storage-account $storageName --location $region --format JSON --log-version 2 --retention 7 --traffic-analytics --workspace $logWsName

Create VM

$vmB1Image = "Win2019Datacenter"
$vmB1User = "lcladmin"
$vmB1Pass = $vmPass
$vmB1Size = "Standard_B2ms"
$vmB1DiskGuid = [guid]::NewGuid().ToString().Replace("-","").Substring(0,10)

az vm create -n $vmB1Name -g $rgName --image $vmB1Image --admin-username $vmB1User --admin-password $vmB1Pass --computer-name $vmB1Name --size $vmB1Size --vnet-name $vnetName --subnet $subnet4Name --private-ip-address $vmB1IPAddress --storage-account $storageName --use-unmanaged-disk --os-disk-name "$($vmB1Name)-OSdisk-$($vmB1DiskGuid)" --nsg '""' --public-ip-address '""'

# attach data disk
$diskB1Guid = [guid]::NewGuid().ToString().Replace("-","").Substring(0,10)
$diskB1Name = "$($vmB1Name)-DataDisk-$($diskB1Guid)"
$diskB1Size = "64"
$diskB1Cache = "None"
az vm unmanaged-disk attach -g $rgName --vm-name $vmB1Name --new --name $diskB1Name --size-gb $diskB1Size --caching $diskB1Cache

Install ADDS

  • Set static IP address
  • Install Active Directory Domain Services
  • Install forest 'wingtip.root'
$dcA2Domain="contoso.internal"
$dcBDomain="wingtip.root"
$dcBDomainNetbios="wingtip"
$dcBUPNSuffix="wingtip.net"
$dcBDomainDN = "DC=wingtip,DC=root"

$scriptBDC01_1 = @"
Set-TimeZone -id 'E. Australia Standard Time'
`$disks = Get-Disk | Where partitionstyle -eq 'raw'
If (`$disks) {
  `$disks | Initialize-Disk -PartitionStyle MBR -PassThru | New-Partition -UseMaximumSize -DriveLetter ""G"" | Format-Volume -FileSystem NTFS -NewFileSystemLabel ""Data"" -Confirm:`$false -Force
}
`$domain = ""$dcBDomain""
`$domainnetbios = ""$dcBDomainNetbios""
`$IP = ""$vmB1IPAddress""
`$MaskBits = 24
`$Gateway = ""$($vmB1IPAddress -split "\d{1,3}$" -join "1")""
`$DNS = ""$vmB1IPAddress""
`$IPType = ""IPv4""
`$adapter = Get-NetAdapter | ? {`$_.Status -eq ""up""}
`$interface = `$adapter | Get-NetIPInterface -AddressFamily `$IPType
If (`$interface.Dhcp -eq ""Enabled"") {
  Get-NetAdapterBinding -ComponentID ms_tcpip6 | Disable-NetAdapterBinding
  If ((`$adapter | Get-NetIPConfiguration).IPv4Address.IPAddress) {
    `$adapter | Remove-NetIPAddress -AddressFamily `$IPType -Confirm:`$false
  }
  If ((`$adapter | Get-NetIPConfiguration).Ipv4DefaultGateway) {
    `$adapter | Remove-NetRoute -AddressFamily `$IPType -Confirm:`$false
  }
  `$adapter | New-NetIPAddress -AddressFamily `$IPType -IPAddress `$IP -PrefixLength `$MaskBits -DefaultGateway `$Gateway
  `$adapter | Set-DnsClientServerAddress -ServerAddresses `$DNS
}

Install-WindowsFeature AD-Domain-Services, rsat-adds -IncludeAllSubFeature
Install-ADDSForest -DomainName `$domain -SafeModeAdministratorPassword (convertto-securestring '$vmB1Pass' -asplaintext -force)  -DomainMode Win2012 -DomainNetbiosName `$domainnetbios -ForestMode Win2012 -DatabasePath """G:\NTDS""" -SysvolPath """G:\SYSVOL""" -LogPath """G:\Logs""" -Force"

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB1Name -g $rgName --scripts $scriptBDC01_1

az vm restart -g $rgName -n $vmB1Name
  • Create OUs & test users
$scriptBDC01_2 = @"
try{
  Import-Module ActiveDirectory -ErrorAction Stop
}catch{
  throw ""Module ActiveDirectory not installed""
}

if ((Get-ADForest).UPNsuffixes -notcontains ""$dcBUPNSuffix""){
  Get-ADForest | Set-ADForest -UPNSuffixes @{add=""$dcBUPNSuffix""}
}

function New-ADOU {
  # http://www.alexandreviot.net/2015/04/27/active-directory-create-ou-using-powershell
  param([parameter(Mandatory=`$true)] [array]`$ouList)
  ForEach(`$OU in `$ouList){
    try{
      #Write-Host -Foregroundcolor Yellow `$OU.Name `$OU.Path
      New-ADOrganizationalUnit -Name ""`$(`$OU.Name)"" -Path ""`$(`$OU.Path)""
      #Write-Host -ForegroundColor Green ""OU `$OU.Name created""
    }catch{
       Write-Host `$error[0].Exception.Message
    }
  }
}
`$ouCSV = ""Name;Path
M_SERVERS;$dcBDomainDN
M_USERS;$dcBDomainDN
ServiceAccounts;OU=M_USERS,$dcBDomainDN
Staff;OU=M_USERS,$dcBDomainDN
M_WORKSTATIONS;$dcBDomainDN
M_GROUPS;$dcBDomainDN""
`$ouList = `$ouCSV | ConvertFrom-CSV -Delimiter "";""

New-ADOU `$ouList
New-ADGroup -Name ""Grp_AllStaff"" -SamAccountName Grp_AllStaff -GroupCategory Security -GroupScope Global -DisplayName ""All Staff"" -Path ""OU=M_GROUPS,$dcBDomainDN""

function Create-TestUsers {
  param(
    [parameter(Mandatory=`$true)] [array]`$UserList,
    [parameter(Mandatory=`$true)] [string]`$UserPass,
    [parameter(Mandatory=`$true)] [string]`$DomainSuffix,
    [parameter(Mandatory=`$true)] [string]`$OUPath
  )

  # https://365lab.net/2014/01/08/create-test-users-in-a-domain-with-powershell/
  `$departments = @(""IT"",""Finance"",""Logistics"",""Sourcing"",""Human Resources"")
  ForEach(`$user in `$userList){
    `$firstname = (Get-Culture).TextInfo.ToTitleCase(`$user.Firstname)
    `$lastname = (Get-Culture).TextInfo.ToTitleCase(`$user.Lastname)
    `$i = get-random -Minimum 0 -Maximum `$departments.count
    `$department = `$departments[`$i]
    `$username = `$firstname.Substring(0,2).tolower() + `$lastname.Substring(0,4).tolower()
    `$exit = 0
    `$count = 1
    do {
      try {
        `$userexists = Get-AdUser -Identity `$username
        `$username = `$firstname.Substring(0,2).tolower() + `$lastname.Substring(0,4).tolower() + `$count++
      } catch {
        `$exit = 1
      }
    } while (`$exit -eq 0)
    `$displayname = `$firstname + "" "" + `$lastname
    `$upn = `$username + ""@"" + `$DomainSuffix
    `$email = `$firstname + ""."" + `$lastname + ""@"" + `$DomainSuffix
    Write-Host ""Creating user `$username in `$OUPath""
    New-ADUser -Name `$displayName -DisplayName `$displayName -SamAccountName `$username -UserPrincipalName `$upn -EmailAddress `$email -GivenName `$firstname -Surname `$lastname -description ""Test User"" -Path `$OUPath -Enabled `$true -ChangePasswordAtLogon `$false -Department `$Department -AccountPassword (ConvertTo-SecureString `$userPass -AsPlainText -force)
  }
}

`$userOU = ""OU=Staff,OU=M_USERS,$dcBDomainDN""
`$userPass = '$userPass'
`$usersCSV = ""Firstname;Lastname
Aaron;Alfort
Abraham;Allendorf
Acea;Alsop
Adam;Amaker
Aidan;Angus
Ainslee;Annan
Alan;Annesley
Aleen;Appleby
Alexa;Arbuthnot
Alexander;Armitage
Alixandria;Artois
Aliyah;Arundel
Alka;Ashburton
Alp;Astor
Alyson;Athill""
`$userList = `$usersCSV | ConvertFrom-CSV -Delimiter "";""

`$users = Get-ADUser -Filter * -SearchBase `$userOU | select -expand samAccountName
If ((`$users) -eq `$null) {
  Create-TestUsers `$userList `$userPass ""$dcBUPNSuffix"" `$userOU
}
`$users = Get-ADUser -Filter * -SearchBase `$userOU | select -expand samAccountName
`$group = ""Grp_AllStaff""
Add-ADGroupMember -Identity `$group -Members `$users

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB1Name -g $rgName --scripts $scriptBDC01_2
  • Create DNS conditional forwarder for Domain A (for our domain trust)
  • Create service account for Azure AD Connect
$scriptBDC01_3 = @"
Add-DnsServerConditionalForwarderZone -Name ""$dcA2Domain"" -ReplicationScope ""Forest"" -MasterServers $vm2IPaddress

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB1Name -g $rgName --scripts $scriptBDC01_3
$scriptBDC01_4 = @"
`$displayName = ""svc_azuresync""
`$username = ""svc_azuresync""
`$upn = ""svc_azuresync@$dcB1Domain""
`$OUPath = ""OU=ServiceAccounts,OU=M_USERS,$dcBDomainDN""
`$userPass = '$vmPass'

New-ADUser -Name `$displayName -DisplayName `$displayName -SamAccountName `$username -UserPrincipalName `$upn -description ""AAD Connect"" -Path `$OUPath -Enabled `$true -ChangePasswordAtLogon `$false -Department `$Department -AccountPassword (ConvertTo-SecureString `$userPass -AsPlainText -force)

`$displayName = ""svc_a-azuresync""
`$username = ""svc_a-azuresync""
`$upn = ""svc_a-azuresync@$dcB1Domain""
`$OUPath = ""OU=ServiceAccounts,OU=M_USERS,$dcBDomainDN""
`$userPass = '$vmPass'

New-ADUser -Name `$displayName -DisplayName `$displayName -SamAccountName `$username -UserPrincipalName `$upn -description ""AAD Connect"" -Path `$OUPath -Enabled `$true -ChangePasswordAtLogon `$false -Department `$Department -AccountPassword (ConvertTo-SecureString `$userPass -AsPlainText -force)

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB1Name -g $rgName --scripts $scriptBDC01_4

Create VM-B#2 - IIS & Azure AD Connect

  • VM-B-APP01
  • Unmanaged OS disk
  • Windows Server 2019
  • Static IP address
  • Download & install Auth Page & Azure AD Connect install
  • 💔 Cannot perform an unattended install of AADC

Create NSG

$nsg6Name = "jr-trustlab-nsg-b-app"

az network nsg create -n $nsg6Name -g $rgName
az network vnet subnet update -g $rgName -n $subnet5Name --vnet-name $vnetName --network-security-group $nsg6Name
az network nsg rule create --nsg-name $nsg6Name -g $rgName -n "Allow_RDP" --priority 100 --access "allow" --source-address-prefixes $subnet99Range --destination-address-prefixes $subnet5Range --destination-port-ranges "3389" --protocol "TCP" --description "Allow RDP from Bastion"
az network nsg rule create --nsg-name $nsg6Name -g $rgName -n "Allow_HTTP" --priority 110 --access "allow" --source-address-prefixes $subnet4Range $subnet5Range --destination-address-prefixes $subnet5Range --destination-port-ranges 80 443 --protocol "TCP" --description "Allow HTTP traffic"
az network nsg rule create --nsg-name $nsg6Name -g $rgName -n "Allow_A_HTTP" --priority 120 --access "allow" --source-address-prefixes $subnet3Range --destination-address-prefixes $subnet5Range --destination-port-ranges 80 443 --protocol "TCP" --description "Allow HTTP traffic"
az network nsg rule create --nsg-name $nsg6Name -g $rgName -n "Deny_Inbound" --priority 4000 --access "deny" --source-address-prefixes "*" --destination-address-prefixes $subnet5Range --destination-port-ranges "*" --protocol "*" --description "Deny inbound traffic"

$logWsName = "jr-trustlab-logs"
$flow6LogName = "jr-trustlab-flow-b-app"
az network watcher flow-log create -n $flow6LogName -g $rgName --enabled true --nsg $nsg6Name --storage-account $storageName --location $region --format JSON --log-version 2 --retention 7 --traffic-analytics --workspace $logWsName

Create VM

$vmB2Image = "Win2019Datacenter"
$vmB2User = "lcladmin"
$vmB2Pass = $vmPass
$vmB2Size = "Standard_B2ms"
$vmB2DiskGuid = [guid]::NewGuid().ToString().Replace("-","").Substring(0,10)

az vm create -n $vmB2Name -g $rgName --image $vmB2Image --admin-username $vmB2User --admin-password $vmB2Pass --computer-name $vmB2Name --size $vmB2Size --vnet-name $vnetName --subnet $subnet5Name --private-ip-address $vmB2IPAddress --storage-account $storageName --use-unmanaged-disk --os-disk-name "$($vmB2Name)-OSdisk-$($vmB2DiskGuid)" --nsg '""' --public-ip-address '""'

Install IIS & Azure AD Connect

  • Set static IP address
  • Join domain 'wingtip.root'
$scriptBAPP01_1 = @"
Set-TimeZone -id 'E. Australia Standard Time'
`$IP = ""$vmB2IPAddress""
`$MaskBits = 24
`$Gateway = ""$($vmB2IPAddress -split "\d{1,3}$" -join "1")""
`$DNS = ""$vmB1IPAddress""
`$IPType = ""IPv4""
`$adapter = Get-NetAdapter | ? {`$_.Status -eq ""up""}
`$interface = `$adapter | Get-NetIPInterface -AddressFamily `$IPType
If (`$interface.Dhcp -eq ""Enabled"") {
  Get-NetAdapterBinding -ComponentID ms_tcpip6 | Disable-NetAdapterBinding
  If ((`$adapter | Get-NetIPConfiguration).IPv4Address.IPAddress) {
    `$adapter | Remove-NetIPAddress -AddressFamily `$IPType -Confirm:`$false
  }
  If ((`$adapter | Get-NetIPConfiguration).Ipv4DefaultGateway) {
    `$adapter | Remove-NetRoute -AddressFamily `$IPType -Confirm:`$false
  }
  `$adapter | New-NetIPAddress -AddressFamily `$IPType -IPAddress `$IP -PrefixLength `$MaskBits -DefaultGateway `$Gateway
  `$adapter | Set-DnsClientServerAddress -ServerAddresses `$DNS
}
Start-Sleep -Seconds 15 # Wait for DNS changes
Add-Computer -DomainName ""$dcBDomain"" -Credential (New-Object System.Management.Automation.PSCredential(""$vmB2Domain\$vmB1User"",(ConvertTo-SecureString '$vmB1Pass' -AsPlainText -Force))) -OUPath ""OU=M_SERVERS,$dcBDomainDN"" -Restart

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB2Name -g $rgName --scripts $scriptBAPP01_1
  • Install IIS
  • Install AuthPage
$scriptBAPP01_2 = @"
`$installPath = ""c:\install""
If ((Test-Path ""c:\inetpub\wwwroot"") -eq `$false) {
  `$IISFeatures = ""Web-WebServer"",""Web-Common-Http"",""Web-Default-Doc"",""Web-Http-Errors"",""Web-Http-Redirect"",""Web-Health"",""Web-Http-Logging"",""Web-Security"",""Web-Filtering"",""Web-Basic-Auth"",""Web-Client-Auth"",""Web-IP-Security"",""Web-Windows-Auth"",""Web-Net-Ext"",""Web-Net-Ext45"",""Web-Asp-Net"",""Web-Asp-Net45"",""Web-ISAPI-Ext"",""Web-ISAPI-Filter"",""Web-Mgmt-Tools"",""Web-Mgmt-Console""
  Install-WindowsFeature -Name `$IISFeatures -ErrorAction SilentlyContinue
} 

If ((Test-Path ""`$installPath\authpage\authpage\default.aspx"") -eq `$false) {
  mkdir `$installPath -Force
  Invoke-WebRequest -Uri ""https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/10/38/31/92/Authpage.zip"" -OutFile ""`$installPath\authpage.zip""
  Expand-Archive -LiteralPath ""`$installPath\authpage.zip"" -DestinationPath ""`$installPath\authpage""
}

If (Test-Path ""`$installPath\authpage\authpage\default.aspx"") {
  If (Test-Path ""c:\inetpub\wwwroot\iisstart.htm"") {
    Get-ChildItem -Path ""`$installPath\authpage\authpage"" -Recurse | Copy-Item -Destination ""c:\inetpub\wwwroot""
    Remove-Item ""c:\inetpub\wwwroot\iisstart.htm"" -ErrorAction SilentlyContinue
  }
} Else {
  Write-Output ""Extract of AuthPage failed""
}

If ((Test-Path ""c:\install\installer.flag"") -eq `$false) {
  Set-WebConfigurationProperty -Filter ""/system.webServer/security/authentication/anonymousAuthentication"" -Name Enabled -Value False -PSPath IIS:\ -location ""Default Web Site""
  Set-WebConfigurationProperty -Filter ""/system.webServer/security/authentication/windowsAuthentication"" -Name Enabled -Value True -PSPath IIS:\ -location ""Default Web Site""
  & `$ENV:windir\system32\inetsrv\appcmd.exe set AppPool DefaultAppPool -""processModel.identityType:NetworkService""
  `$Acl = (Get-Item ""c:\inetpub\wwwroot"").GetAccessControl(""Access"")
  `$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule(""NETWORK SERVICE"", ""ReadAndExecute"", ""ContainerInherit,ObjectInherit"", ""None"", ""Allow"")
  `$Acl.SetAccessRule(`$Ar)
  Set-Acl ""c:\inetpub\wwwroot"" `$Acl
  ""Install complete"" | Out-File ""c:\install\installer.flag""
} Else { Write-Output ""Install flag found - skipping setup tasks"" }
"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB2Name -g $rgName --scripts $scriptBAPP01_2
  • Install RSAT
  • Download AAD Connect installer
  • Set permissions for AAD Connect service account
  • AAD Connect needs to be manually installed and configured
$scriptBAPP01_3 = @"
Install-WindowsFeature rsat-adds -IncludeAllSubFeature
try{
  Import-Module ActiveDirectory -ErrorAction Stop
}catch{
  throw ""Module ActiveDirectory not installed""
}
`$installPath = ""c:\install""
If ((Test-Path `$installPath) -eq `$false) {mkdir `$installPath -Force}

If ((Test-Path ""`$installPath\AzureADConnect.msi"") -eq `$false) {
  try {
    `$URi = ""https://www.microsoft.com/en-us/download/confirmation.aspx?id=47594""
    `$downloadPage = Invoke-WebRequest -Uri `$URi  -usebasicparsing
    `$fileUri = (`$downloadPage.RawContent.Split('""') -like ""https://*AzureADConnect.msi"")[0]
    Invoke-WebRequest -Uri `$fileUri -OutFile ""`$installPath\AzureADConnect.msi""
    Write-Output (""File {0} size: {1}"" -f (gci ""`$installPath\AzureADConnect.msi"").Name, ((gci ""`$installPath\AzureADConnect.msi"").Length / 1MB).ToString("".""))
  } catch {
    Write-Host `$(`$_.Exception.Message)
    throw 'ERROR: Could not download file'
  }
}

If ((Test-Path ""`$env:ProgramFiles\Microsoft Azure Active Directory Connect\AzureADConnect.exe"") -eq `$false) {
  & ""`$ENV:windir\system32\msiexec.exe"" /i ""`$installPath\AzureADConnect.msi"" /qb-
}

try {
  `$URi = ""https://www.powershellgallery.com/api/v2/package/AADConnectPermissions/7.3""
  Invoke-WebRequest -Uri `$URi  -OutFile ""`$installPath\AADConnectPermissions.zip""
  If ((Test-Path ""`$installPath\AADCP\AADConnectPermissions.ps1"") -eq `$false) { Expand-Archive -Path ""`$installPath\AADConnectPermissions.zip"" -DestinationPath ""`$installPath\AADCP"" }
  & ""`$installPath\AADCP\AADConnectPermissions.ps1"" -User svc_azuresync -PasswordHashSync
  & ""`$installPath\AADCP\AADConnectPermissions.ps1"" -User svc_azuresync -msDsConsistencyGuid -ExchangeHybridWriteBackOUs ""OU=Staff,OU=M_USERS,$dcBDomainDN""
  & ""`$installPath\AADCP\AADConnectPermissions.ps1"" -User svc_azuresync -PasswordWriteBack -ExchangeHybridWriteBackOUs ""OU=Staff,OU=M_USERS,$dcBDomainDN""
  & ""`$installPath\AADCP\AADConnectPermissions.ps1"" -User svc_a-azuresync -msDsConsistencyGuid -ExchangeHybridWriteBackOUs ""OU=Staff,OU=M_USERS,$dcBDomainDN""
} catch {
  Write-Host `$(`$_.Exception.Message)
  throw 'ERROR: Could not download file'
}

"@
az vm run-command invoke --command-id RunPowerShellScript --name $vmB2Name -g $rgName --scripts $scriptBAPP01_3

Create VM-B#3 - Windows 10 Client

  • VM-B-CLIENT01
  • Unmanaged OS disk
  • Windows 10 Enterprise

Create NSG

$nsg7Name = "jr-trustlab-nsg-b-client"

az network nsg create -n $nsg7Name -g $rgName
az network vnet subnet update -g $rgName -n $subnet6Name --vnet-name $vnetName --network-security-group $nsg7Name
az network nsg rule create --nsg-name $nsg7Name -g $rgName -n "Allow_RDP" --priority 100 --access "allow" --source-address-prefixes $subnet99Range --destination-address-prefixes $subnet6Range --destination-port-ranges "3389" --protocol "TCP" --description "Allow RDP from Bastion"
az network nsg rule create --nsg-name $nsg7Name -g $rgName -n "Deny_Inbound" --priority 4000 --access "deny" --source-address-prefixes "*" --destination-address-prefixes $subnet6Range --destination-port-ranges "*" --protocol "*" --description "Deny inbound traffic"

$logWsName = "jr-trustlab-logs"
$flow7LogName = "jr-trustlab-flow-b-app"
az network watcher flow-log create -n $flow7LogName -g $rgName --enabled true --nsg $nsg7Name --storage-account $storageName --location $region --format JSON --log-version 2 --retention 7 --traffic-analytics --workspace $logWsName

Create VM

$vmB3Image = "MicrosoftWindowsDesktop:Windows-10:20h2-ent:19042.867.2103051748"
$vmB3User = "lcladmin"
$vmB3Pass = $vmPass
$vmB3Size = "Standard_B2ms"
$vmB3DiskGuid = [guid]::NewGuid().ToString().Replace("-","").Substring(0,10)

az vm create -n $vmB3Name -g $rgName --image $vmB3Image --admin-username $vmB3User --admin-password $vmB3Pass --computer-name $vmB3Name --size $vmB3Size --vnet-name $vnetName --subnet $subnet6Name --private-ip-address $vmB3IPAddress --storage-account $storageName --use-unmanaged-disk --os-disk-name "$($vmB3Name)-OSdisk-$($vmB3DiskGuid)" --nsg '""' --public-ip-address '""'
  • Set static IP and DNS server IP to VM-B-DC01
  • Join domain
  • Add "Grp_AllStaff" AD group to local "Remote Desktop Users" group to allow test user accounts access to RDP
$scriptBCLIENT01_1 = @"
Set-TimeZone -id 'E. Australia Standard Time'
`$IP = ""$vmB3IPAddress""
`$MaskBits = 24
`$Gateway = ""$($vmB3IPAddress -split "\d{1,3}$" -join "1")""
`$DNS = ""$vmB1IPAddress""
`$IPType = ""IPv4""
`$adapter = Get-NetAdapter | ? {`$_.Status -eq ""up""}
`$interface = `$adapter | Get-NetIPInterface -AddressFamily `$IPType
If (`$interface.Dhcp -eq ""Enabled"") {
  Get-NetAdapterBinding -ComponentID ms_tcpip6 | Disable-NetAdapterBinding
  If ((`$adapter | Get-NetIPConfiguration).IPv4Address.IPAddress) {
    `$adapter | Remove-NetIPAddress -AddressFamily `$IPType -Confirm:`$false
  }
  If ((`$adapter | Get-NetIPConfiguration).Ipv4DefaultGateway) {
    `$adapter | Remove-NetRoute -AddressFamily `$IPType -Confirm:`$false
  }
  `$adapter | New-NetIPAddress -AddressFamily `$IPType -IPAddress `$IP -PrefixLength `$MaskBits -DefaultGateway `$Gateway
  `$adapter | Set-DnsClientServerAddress -ServerAddresses `$DNS
}
Start-Sleep -Seconds 15 # Wait for DNS changes

Add-Computer -DomainName ""$dcBDomain"" -Credential (New-Object System.Management.Automation.PSCredential(""$dcBDomain\$vmB1User"",(ConvertTo-SecureString '$vmB1Pass' -AsPlainText -Force))) -OUPath ""OU=M_WORKSTATIONS,$dcBDomainDN"" -Restart
"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB3Name -g $rgName --scripts $scriptBCLIENT01_1

$scriptBCLIENT01_2 = @"
Add-LocalGroupMember -Group ""Remote Desktop Users"" -Member ""$dcBDomain\Grp_AllStaff""

"@

az vm run-command invoke --command-id RunPowerShellScript --name $vmB3Name -g $rgName --scripts $scriptBCLIENT01_2

Wrap up

That brings us to the end of the build. Next comes testing! RDP into the jump box using the public DNS name which you can get from the Portal, or from Azure CLI [az network public-ip].

az network public-ip list -g $rgName --query "[].dnsSettings" --output table

From the jump box you can RDP out to each server or client using its IP address, as summarised in Part 1:

Hostname Subnet IP Address Role
VM-A-DC01 domainA-ad (10.1.10/24) 10.1.10.10 Domain Controller 1 - Forest root
VM-A-DC02 domainA-ad (10.1.10/24) 10.1.10.11 Domain Controller 2 - Domain 2
VM-A-APP01 domainA-app (10.1.20/24) 10.1.20.20 Web Server
VM-A-APP02 domainA-app (10.1.20/24) 10.1.20.21 Azure AD Connect
VM-A-CLIENT01 domainA-client (10.1.30/24) 10.1.30.30 Windows 10 Client
VM-B-DC01 domainB-ad (10.1.110/24) 10.1.110.10 Domain Controller
VM-B-APP01 domainB-app (10.1.120/24) 10.1.120.20 Web Server & AAD Connect
VM-B-CLIENT01 domainB-client (10.1.130/24) 10.1.130.30 Windows 10 Client
VM-BAS01 public (10.1.200/24) DHCP Bastion Host / Jump Box

I'll be moving on to configuring the two-way trust between 'wingtip.root' and 'contoso.internal' for my testing. Reach out to me on Twitter @jraaschou if you want me to do a write up about AD trusts.

Below is script to create the trust programmatically.

$scriptADC02_TRUST_1 = @"
# Source: https://social.technet.microsoft.com/wiki/contents/articles/11911.active-directory-powershell-how-to-create-forest-trust.aspx
`$remoteDomain = ""$dcBDomain""
`$remoteAdmin = ""$vmB1User""
`$remoteAdminPassword = '$vmPass'
`$remoteContext = New-Object -TypeName ""System.DirectoryServices.ActiveDirectory.DirectoryContext"" -ArgumentList @( ""Domain"",`$remoteDomain, `$remoteAdmin, `$remoteAdminPassword)
try {
  `$remoteDomain =[System.DirectoryServices.ActiveDirectory.Domain]::getDomain(`$remoteContext)
  #Write-Host ""GetRemoteDomain: Succeeded for domain `$(`$remoteDomain)""
} catch {
  Write-Warning ""GetRemoteDomain: Failed:`n`tError: `$(`$(`$_.Exception).Message)""
} 

Write-Host ""Connected to Remote domain: `$(`$remoteDomain.Name)""
`$localDomain=[System.DirectoryServices.ActiveDirectory.Domain]::getCurrentDomain()
Write-Host ""Connected to Local domain: `$(`$localDomain.Name)""
try { 
  # `$localDomain.CreateTrustRelationship(`$remoteDomain,""Inbound"") # One way trust test
  `$localDomain.CreateTrustRelationship(`$remoteDomain,""Bidirectional"") # Two way trust test
  Write-Host ""CreateTrustRelationship: Succeeded for domain `$(`$remoteDomain)""
} catch {
  Write-Warning ""CreateTrustRelationship: Failed for domain `$(`$remoteDomain)`n`tError: `$(`$(`$_.Exception).Message)""
}

"@
az vm run-command invoke --command-id RunPowerShellScript --name $vm2Name -g $rgName --scripts $scriptADC02_TRUST_1