Configure Server 2016 ADFS and WAP with custom ports using Powershell

A pull request for Chris Gardner’s WebApplicationProxyDSC is now inbound after a frustrating week of trying to automate the configuration of ADFS and WAP on a Server 2016 lab.

With Server 2016, the PowerShell commands to configure the ADFS and WAP servers include switches to specify a non-default port. I need to do this because the servers are behind a NetNat on a server hosting several labs, so port 443 is not available to me and I must use a different port.

This should be simple: Specify the SSLPort switch on the Install-ADFSFarm command and the HttpsPort on the Install-WebApplicationProxy command. However, when I do that, the WAP configuration fails with an error that it cannot read the FederationMetadata from the proxy.

I tried all manner of things to diagnose why this was failing and in the end, the fix is a crazy hack that should not work!

The proxy installation, despite accepting the custom port parameter, does not build the URLs correctly for the ADFS service, so is still trying to call port 443. You can set these URLs on a configured WAP service using the Set-WebApplicationProxyConfiguration command. However, when you run this command with no configured proxy, it fails.

Or so you think…

On the ADFS Server:

  1. Install-AdfsFarm specifiying the CertificateThumbprint, Credential, FederationServiceDisplayName, FederationServiceName and SSLPort params

On the WAP Server:

  1. Install-WebApplicationProxy specifiying the HttpsPort switch,  CertificateThumbprint, FederationServiceName and FederationServiceTrustCredential params.
  2. Set-WebApplicationProxyConfiguration specifying the ADFSUrl, OAuthAuthenticationURL and ADFSSignOutURL parameters with the correct URLs for your ADFS server (which include the port in the Url).
  3. Re-run the command in step 1.

Despite the fact that step 2 says it failed, it seems to set enough information for step 3 to succeed. My experience, however, is that only doing steps 2 and 3 does not work. Weird!

As a side note, testing this lot is a lot easier if you remember that the idpinitiatedsignon.aspx page we all normally use for testing ADFS is disabled by default in Server 2016. Turn it on with Set-AdfsProperties -EnableIdPInitiatedSignonPage $true

What I wish I had known when I started developing Lability DevTest Lab Environments

At Black Marble we have been migrating our DevTest labs to from on-premises TFS Lab Management to a mixture of on-premise and Azure hosted Lability defined Labs as discussed by Rik Hepworth on his blog. I have only been tangentially involved in this effort until recently, consuming the labs but not creating the definitions.

So this post is one of those I do where I don’t want to forget things I learnt the hard way, or to put it another way asking Rik or Chris after watching a 2 hour environment deploy fail for the Xth time.

  • You can’t log tool much. The log files are your friends, both the DSC ones and any generated by tools triggered by DSC. This is because most of the configuration process is done during boots so there is no UI to watch.
  • The DSC log is initially created in working folder the .MOF file is in on the target VM; but after a reboot (e.g. after joining a domain) the next and subsequent DSC log files are created in  C:WindowsSystem32ConfigurationConfigurationStatus
  • Make sure you specify the full path for any bespoke logging you do, relative paths make it too easy to lose the log file
  • Stupid typos get you every time, many will be spotted when the MOF file is generated, but too many such as ones in command lines or arguments are only spotted when you deploy an environment. Also too many of these don’t actually cause error messages, they just mean nothing happens. So if you expect a script/tool to be run and it doesn’t check the log and the definition for mismatches in names.
  • If you are using the Package DSC Resource to install an EXE or MSI couple of gotcha’s
    • For MSIs the ProductName parameter must exactly match the one in the MSI definition, and this must match the GUID ProductCode.  Both of these can be found using the Orca tool

      image

    • Package MongoDb {

      PsDscRunAsCredential = $DomainCredentialsAtDomain

      DependsOn = '[Package]VCRedist'

      Ensure = 'Present'

      Arguments = "/qn /l*v c:bootstrapMongoDBInstall.log INSTALLLOCATION=`"C:Program FilesMongoDBServer3.6`""

      Name = "MongoDB 3.6.2 2008R2Plus SSL (64 bit)"

      Path = "c:bootstrapmongodb-win32-x86_64-2008plus-ssl-3.6.2-signed.msi"

      ProductId = "88B5F0D8-0692-4D86-8FF4-FB3CDBC6B40F"

      ReturnCode = 0

      }

    • For EXEs the ProductName does not appear to be as critical, but you still need the Product ID. You can get this with PowerShell on a machine that already has the EXE installed
    • Get-WmiObject Win32_Product | Format-Table IdentifyingNumber, Name, Version
  • I had network issues, they could mostly be put does to incorrect Network Address Translation. In my case this should have been setup when Lability was initially configured, the commands ran OK creating a virtual switch and NetNat, but I ended up with a Windows failback network address of 169.x.x.x when I should have had an address of 192.168.x.x on my virtual switch. So if in doubt check the settings on your virtual switch, in the Windows ‘Networking and Share Center’ before you start doubting your environment definitions.

Hope these pointers help others, as well as myself, next time Lability definitions are written

Creating test data for my Generate Release Notes Extension for use in CI/CD process

As part of the continued improvement to my CI/CD process I needed to provide a means so that whenever I test my Generate Release Notes Task, within it’s CI/CD process, new commits and work item associations are made. This is required because the task only picks up new commits and work items since the last successful running of a given build. So if the last release of the task extension was successful then the next set of tests have no associations to go in the release notes, not exactly exercising all the code paths!

In the past I added this test data by hand, a new manual commit to the repo prior to a release; but why have a dog and bark yourself? Better to automate the process.

This can done using a PowerShell file, run inline or stored in the builds source repo and run within a VSTS build. The code is shown below, you can pass in the required parameters, but I set sensible default for my purposes

For this PowerShell code to work you do need make some security changes to allow the build agent service user to write to the Git repo. This is documented by Microsoft.

The PowerShell task to run this code is placed in a build as the only task

image

This build is then triggered as part of the release process

image

Note that the triggering of this build has to be such that it runs on a non-blocking build agent as discussed in my previous posts. In my case I trigger the build to add the extra commits and work items just before triggering the validation build on my private Azure hosted agent.

Now, there is no reason you can’t just run the PowerShell directly within the release if you wanted to. I chose to use a build so that the build could be reused between different VSTS extension CI/CD pipelines; remember I have two Generate Release Note Extensions, PowerShell and NodeJS Based.

So another step to fully automating the whole release process.

Setting Enroll Permissions on ADCS Certificate Template using DSC

As part of the work I have been doing around generating and managing lab environments using Lability and DSC, one of the things I needed to do was change the permissions on a certificate template within a DSC configuration. Previously, when deploying to Azure, I used the PSPKI PowerShell modules within code executed by the Custom Script extension. I was very focused on sticking with DSC this time, which ruled out PSPKI. Whilst there is a DSC module available to configure Certificate Services itself, this does not extend to managing Certificate Templates.

Nobody seemed to have done exactly this before. I used the following links as references in creating the code:

Get Effective template permissions with PowerShell by Vadims Podans

Duplicate AD Object Without Active Directory PS Tools

Add Object Specific ACEs using Active Directory PowerShell

Using Scripts to Manage Active Directory Security

The script finds the WebServer template and grants the Enroll extended permission to the Domain Computers AD group. This allows me to use xCertificate in the DSC configuration of domain member servers to request new certificates using the WebServer template.

Here is the code I include in my DSC configuration. $DomainCreds is a PSCredential object for the domain admin ( I create the AD domain in an earlier step using xActiveDirectory).

        #Enable Enroll on WebServer certificate template         Script EnableWebServerEnroll {             DependsOn = "[xAdcsCertificationAuthority]CertAuth"             PsDscRunAsCredential = $DomainCreds             GetScript = {                 return @{ 'Result' = $true}             }             TestScript = {                 #Find the webserver template in AD and grant the Enroll extended right to the Domain Computers                 $filter = "(cn=WebServer)"                 $ConfigContext = ([ADSI]"LDAP://RootDSE").configurationNamingContext                 $ConfigContext = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigContext"                  $ds = New-object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ConfigContext",$filter)                 $Template = $ds.Findone().GetDirectoryEntry()                 if ($Template -ne $null) {                     $objUser = New-Object System.Security.Principal.NTAccount("Domain Computers")                     # The following object specific ACE is to grant Enroll                     $objectGuid = New-Object Guid 0e10c968-78fb-11d2-90d4-00c04f79dc55                      ForEach ($AccessRule in $Template.ObjectSecurity.Access) {                         If ($AccessRule.ObjectType.ToString() -eq $objectGuid) {                             If ($AccessRule.IdentityReference -like "*$($objUser.Value)") {                                 Write-Verbose "TestScript: WebServer Template Enroll permission for Domain Computers exists. Returning True"                                 return $true                             }                         }                     }                 }                 return $false             }             SetScript = {                 #Find the webserver template in AD and grant the Enroll extended right to the Domain Computers                 $filter = "(cn=WebServer)"                 $ConfigContext = ([ADSI]"LDAP://RootDSE").configurationNamingContext                 $ConfigContext = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigContext"                  $ds = New-object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ConfigContext",$filter)                 $Template = $ds.Findone().GetDirectoryEntry()                  if ($Template -ne $null) {                     $objUser = New-Object System.Security.Principal.NTAccount("Domain Computers")                     # The following object specific ACE is to grant Enroll                     $objectGuid = New-Object Guid 0e10c968-78fb-11d2-90d4-00c04f79dc55                     $ADRight = [System.DirectoryServices.ActiveDirectoryRights]"ExtendedRight"                     $ACEType = [System.Security.AccessControl.AccessControlType]"Allow"                     $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $objUser,$ADRight,$ACEType,$objectGuid                     $Template.ObjectSecurity.AddAccessRule($ACE)                     $Template.commitchanges()                     Write-Verbose "SetScript: Completed WebServer additional permission"                 }             }         }

Running Pester PowerShell tests in the VSTS hosted build service

Updated 22 Mar 2016 This task is available in the VSTS Marketplace

If you are using Pester to unit test your PowerShell code then there is a good chance you will want to include it in your automated build process. To do this, you need to get Pester installed on your build machine. The usual options would be

If you own the build agent VM then any of these options are good, you can even write the NuGet restore into your build process itself. However there is a problem, both the first two options need administrative access as they put the Pester module in the $PSModules folder (under ‘Program Files’); so these can’t be used on VSTS’s hosted build system, where your are not an administrator

So this means you are left with copying the module (and associated functions folder) to some local working folder and running it manually; but do you really want to have to store the Pester module in your source repo?

My solution was to write a vNext build tasks to deploy the Pester files and run the Pester tests.

image_thumb[12]

The task takes two parameters

  • The root folder to look for test scripts with the naming convention  *.tests.ps1. Defaults to $(Build.SourcesDirectory)*
  • The results file name, defaults to $(Build.SourcesDirectory)Test-Pester.XML

The Pester task does not in itself upload the test results, it just throws and error if tests fails. It relies on the standard test results upload task. Add this task and set

  • it to look for nUnit format files
  • it already defaults to the correct file name pattern.
  • IMPORTANT: As the Pester task will stop the build on an error you need to set the ‘Always run’ to make sure the results are published.

image_thumb[11]

Once all this is added to your build you can see your Pester test results in the build summary

image_thumb[10]

image_thumb[14]

You can find the task in my vNextBuild repo