AD Fun Services – List all the members of an ADFS farm

In Windows Server 2012 R2, the ADFS database actually does not keep track of the servers member of the farm. It is a stateless farm were every node happen to share the same database (if a SQL server is used) or the same copy of the database (if it is WID). The only thing stored in the database is the FQDN of the primary server. And if you are using WID, when you add a new server to an existing farm, you enter the name of the primary server for the initial copy of the database but that's it. The server doesn't really keep track of anything after this...

Our options to list the ADFS servers

Is that a goner or is there hope? There are some indirect ways to gather the list of ADFS server in a farm and we are trying to list them here (if you think of others, feel free to share it in the comments section):

  1. gMSA
    If you are using a group managed service account for the farm, you can simply parse the msDS-GroupMSAMembership attribute of the account. Well, you don't really have to parse it the hard way, you can just call the following PowerShell cmdLets:

     Get-ADServiceAccount `
       -Identity "<your gMSA account's name>" `
       -Properties PrincipalsAllowedToRetrieveManagedPassword `
       | Select-Object PrincipalsAllowedToRetrieveManagedPassword
    

    It is possible that the output appear inaccurate. For example if you added a node to the farm and then removed it. The computer account might still have the permission to retrieve the password. Or if the gMSA account is mutualized with other services, other computer accounts than the ADFS farm members might also show up.

  2. SSL certificatesThe ADFS role requires that you install the SSL certificate (the Service-Communication certificate type) first. So if you have a well defined process to request and manage certificate, and that you have stuck to this process, you might be able to check your change record database and look for what are the computers listed...

  3. Windows' role
    You can run a script against all your Windows Server 2012 R2 servers or use SCCM (or any equivalent product, or even SCOM) to detect whether or not the role is installed.

    
    Get-WindowsFeature -Name "ADFS-Federation" -ComputerName adfs01
    # Output
    # Display Name Name Install State
    # ------------ ---- -------------
    # [X] Active Directory Federation Services ADFS-Federation Installed
    

    That is quite laborious though...

  4. Security audit
    If you are using WID, the secondary node will by default get an updated copy of the database every 5 minutes from the primary server. Which means that if you have enabled the Success Logon audit subcategory you could see an event 4624 in the security event logs of the primary server. Here is an example, we can see that on my ADFS primary server I have the correct audit category enabled:

     C:\>auditpol /get /subcategory:Logon
    System audit policy
    Category/Subcategory                      Setting
    Logon/Logoff
      Logon                                   Success and Failure
    

    As a result, I should have an 4624 every 5 minutes on my ADFS server showing where the connections using the ADFS service account are coming from... Well it is not necessarily an ADFS secondary server triggering the connection, but if you do not mutualize the service account, there are good chances that might just do the trick. Here is a PowerShell script showing how to look for the source of authentication of my ADFS service account on my ADFS primary server (in my example, the service account is called svc_adfs and I'll do the lookup with an XPath filter based on the user's SID):

     $SID = (Get-ADUser -Identity svc_adfs).SID.Value
    $filter = @"
    *[
        System[(EventID='4624')]
        and
        EventData[Data[@Name='WorkstationName']!='']
        and
        EventData[Data[@Name='TargetUserSid']='$SID']
    ]
    "@
    Get-WinEvent `
        -ComputerName ADFS01 `
        -LogName Security `
        -FilterXPath $filter `
        | ForEach-Object {
            $_.Properties[ 11 ]
        }
    #Output
    #Value
    #-----
    #ADFS02
    #ADFS03
    #ADFS02
    #ADFS03
    
  5. Load balancer configuration
    If you don't recall what are your ADFS servers, maybe the team in charge of the load balancer that you likely have deployed knows about it (since they have to configured the different virtual servers and endpoints on their ends). So ask them :)

What about the WAP servers?

For the WAP server, it's also tricky... If you know one WAP server, you can retrieve the configuration containing the list of all nodes this way:

 (Get-WebApplicationProxyConfiguration).ConnectedServersName
#Output
#adfsproxy001
#adfsproxy002
#adfsproxy100.contoso.com

Note that it is possible that it returns nodes that are no longer a part of the WAP farm... But anyhow, that is IF you already know where is at least one WAP server. If you'd like to list them from the ADFS server, you can try to query the database directly and parse the information... Note that the following PowerShell script is for a scenario with WID, if you are using SQL server for the database, you need to adapt it.

 #Connect to the local WID, this does not require the SQL PowerShell module
$_db_connection = New-Object -TypeName System.Data.SqlClient.SqlConnection
$_db_connection.ConnectionString = "Server=\\.\pipe\MICROSOFT##WID\tsql\query;Database=AdfsConfiguration;Integrated Security=True;"
$_db_connection.Open()
#Create a command and run a T-SQL query
$_db_command = $_db_connection.CreateCommand()
$_db_command.CommandText = "SELECT [Value] FROM [AdfsConfiguration].[IdentityServerPolicy].[WebApplicationProxyData]"
#Load the results in a table
$_db_results = $_db_command.ExecuteReader()
$_data_table = New-Object -TypeName System.Data.DataTable
$_data_table.Load($_db_results)
$_data_value = $_data_table.Value
$_data_table.Dispose()
#Parse the output
$_base64_pad = '=' * ((4 - ($_data_value.length % 4)) % 4)
$_base64_decode = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($_data_value + $_base64_pad))
#Display the results
([xml]$_base64_decode).Configuration.GlobalConfig.ConnectedServersName.Split(":")
#Close the connection with the local DB
$_db_connection.Close()
#Output
#adfsproxy001
#adfsproxy002
#adfsproxy100.contoso.com

When the trust with an ADFS server is created, the ADFS server will store the certificates in the AdfsTrustedDevices store. So the following might also do the trick:

 dir Cert:\LocalMachine\AdfsTrustedDevices | Where-Object { $_.Subject -like "CN=ADFS ProxyTrust*" }
#Output
#    Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\AdfsTrustedDevices
#
#
#Thumbprint                                Subject
#----------                                -------
#D99012FE25D7782013931AF079E5A6767A66A889  CN=ADFS ProxyTrust - adfsproxy001
#A218FADA6BC8AE11C7AE1C802FD2F043B6EBA08B  CN=ADFS ProxyTrust - adfsproxy002
#391D7A251A4E45914F68E220C57BB5806EB7DF42  CN=ADFS ProxyTrust - adfsproxy100.contoso.com

What about Windows Server 2016 ADFS?

Because the new version of ADFS introduces the notion of farm behavior level, we need to keep track of the nodes to know what are their version.

 (Get-AdfsFarmInformation).FarmNodes
#Output
#FQDN                       BehaviorLevel HeartbeatTimestamp    NodeType
#----                       ------------- ------------------    --------
#ADFS2k16-1.ad.piaudonn.com             3 9/13/2016 11:06:00 PM PrimaryComputer
#ADFS2k16-2.ad.piaudonn.com             3 9/13/2016 11:23:00 PM SecondaryComputer

Unfortunately, this does not keep track of the WAP servers.