Rate this post

===========================================================================
Updated 10 September 2013: tested with Windows 2012 R2 RTM and the script functions as in R2 Preview. Outlook Anywhere bug in the Preview code has been fixed and Outlook now works with RTM. Updated the script to correct Autodiscover ExternalURL
===========================================================================

In this post I will be discussing deploying a highly available Windows 2012 R2 Preview ADFS and Web Application Proxy solution using only PowerShell. This was done as a proof of concept to compare the time taken as well as complexity to build and configure a Reverse Proxy solution to replace a UAG 2010 array. Those of you who have worked with UAG will know that just installing and applying UAG updates and Service Packs takes over an hour – before you even get to the application configuration which is totally manual. For a client recently with 6 trunks publishing Exchange, Lync, Office Web Apps, FIM and SharePoint with the same applications published on multiple domain names it would take over 8 hours of mind numbing, RSI inducing and error prone manual input. The configuration I have built here is for Exchange 2013, Lync 2013 and Office Web Apps Server only, but adding SharePoint and FIM would add about 30 seconds to the installation time.

The other purpose of these scripts is to get something that is repeatable and easily configurable which gives certainty of the configuration and avoids human error. Note that this is a proof of concept so I have not put in much error checking – all code should be tested in a lab environment.

The end result on the ADFS side is an ADFS farm with two servers and a Non-Claims-Aware Relying Party Trust for Exchange.

ADFS Relying Party Trusts 2

And on the Web Application Proxy side we have a cluster of servers with two ADFS preauthenticated applications and 10 Pass-through applications.

WAP Exchange and Lync Applications

Background Reading

These scripts are based on information, functions and script extracts from some of my previous blog posts so if you want more information and background then I recommend you review these:

Windows 2012 R2 Preview Web Application Proxy – Exchange 2013 Publishing Tests for the Web Application Proxy overview

Publish Lync 2013 with 2012 R2 Preview Web Application Proxy for the Lync configuration and SNI requirements

SSL SAN Certificate Request and Import from PowerShell for the PowerShell based certificate requests

Outlook Anywhere NTLM SSO with UAG 2010 KCD for the KCD configuration

Overview of the Scripts

For ease of installation I have split this into four PowerShell scripts which do the following:

Setup-ADFSserver1.ps1 which is run on the first internal ADFS server
  1. User input of a password for exporting the ADFS certificate to .PFX with private key
  2. User input of the credentials for the ADFS service account
  3. Either import the ADFS certificate from a PFX file, or if used for testing – generate a certificate request .ini file for ADFS, requests a certificate from an online CA and exports the certificate as a PFX file to a file share
  4. Install an ADFS farm using WID
  5. Add a non claims aware relying party trust for Exchange using a permit all issuance rule
Setup-ADFSserver2.ps1 which is run on the secondary ADFS server
  1. User input of the password for importing the ADFS certificate
  2. User input of the credentials for the ADFS service account
  3. Import the ADFS certificate
  4. Add the server as an additional node to the ADFS farm
Setup-WAPserver1.ps1 which is run on the first Web Application Proxy server
  1. User input of the password for importing the ADFS certificate
  2. User input of a password for exporting the Web Application Proxy certificate to .PFX with private key
  3. User input of the credentials for an administrator of the ADFS server (not the ADFS service account)
  4. Import the ADFS certificate
  5. Either import the Web Application Proxy certificate from a PFX file, or if used for testing – generate a certificate request .ini file for the Web Application Proxy, requests a certificate from an online CA and exports the certificate as a PFX file to a file share
  6. Install the Web Application Proxy and add it as an ADFS proxy
  7. Add application publishing rules for Lync and Office Web Apps Server
  8. Add application publishing rules for Exchange
  9. Add SPN for KCD (Kerberos Constrained Delegation) to allow the WAP server to delegate credentials for the Exchange server
  10. Set the default certificate binding for clients that do not support SNI (Server Name Indication)
Setup-WAPserver2.ps1 which is run on the secondary WAP server
  1. User input of the password for importing the ADFS certificate
  2. User input of the password for importing the Web Application Proxy certificate to .PFX with private key
  3. User input of the credentials for an administrator of the ADFS server (not the ADFS service account)
  4. Import the ADFS certificate
  5. Import the WAP certificate
  6. Install the Web Application Proxy and add it as an ADFS proxy
  7. Add SPN for KCD (Kerberos Constrained Delegation) to allow the WAP server to delegate credentials for the Exchange server
  8. Set the default certificate binding for clients that do not support SNI (Server Name Indication)

In my testing the first ADFS server took on average 2 minutes 15 seconds, the second ADFS server 2 minutes 15 seconds, the first WAP server 2 minutes 45 and the second WAP server 2 minutes 30. That is a total of 9 minutes and 45 seconds for a highly available ADFS and Reverse Proxy solution which is a whole lot better than configuring UAG.

Note that I am giving the option of using an internal Certificate Authority for the deployment by specifying ‘-OnlineRequest’ in the scripts. This was done to help me with testing, however in the real world you would use a public certificate on the Web Application Proxy for the publishing rules as well as ADFS. I normally use the same public ADFS certificate on the internal ADFS server to keep the configuration consistent and allow non-domain joined clients and mobile devices connecting via internal Wifi to connect without having to import an internal root certificate.

Prerequisites

  • Four Windows 2012 R2 Preview servers which are joined to the domain and use internal DNS
  • In order to publish Exchange OWA and ECP applications with ADFS authentication, the Web Application Proxy servers must be domain joined in order to perform KCD. This makes the scripts easier too as they can place and retrieve certificates from a share and request online certificates from the enterprise Certificate Authority
  • Certificate PFX files for ADFS and Web Application Proxy exported as PFX files with a private key (unless OnlineRequest is specified in which case the script will request certificates from an Enterprise online Certificate Authority)
  • The ADFS farm FQDN (adfs2.showcase.kloud.com.au in this case) DNS A record must exist in DNS before configuration. As an aside – this should be an A record not a CNAME to allow domain joined clients with the ADFS FQDN in the IE trusted sites to authenticate automatically
  • Exchange and Lync already configured
  • One or more load balancers to spread the traffic load across the Web Application Proxy ‘cluster’ and the ADFS farm servers and perform health checks to provide high availability

Scripts

First, a note about the credential prompts. I could have entered the credentials into the script as plain text and converted them to a secure string, but I think that is bad practice as you are dealing with certificates and service accounts where the passwords will not change. Leaving text files with passwords on disk in plain text is a bad thing. Another option is to input the credentials once and save them to the registry in an encrypted form but that is not really relevant here as you will typically run the script only once per server. A third option would be to use remote PowerShell and input credentials once before running the configuration on all servers.

Edit the ‘### Variables’ section of each script and run from an elevated PowerShell prompt.

Setup-ADFSserver1.ps1

Usage:

  • .\Setup-ADFSserver1.ps1
  • .\Setup-ADFSserver1.ps1 –OnlineRequest

[sourcecode language=”powershell”]
param (
[switch]$OnlineRequest
)

$Start = get-date

### Variables
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSdisplayName = "Kloud Showcase ADFS 2"
$ADFSuser = "kloud\ADFSfarm2012R2"
$CertificateLocalPath = "C:\Certificates\"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$RelyingPartyTrustExchangeName = "Exchange"
$RelyingPartyTrustExchangeURI = "https://ex2010-01.kloud.internal/owa"
$RelyingPartyTrustExchangeIssuanceRule = "@RuleTemplate = `"AllowAllAuthzRule`"" # Escape quotes
$RelyingPartyTrustExchangeIssuanceRule += "`n => issue(Type = `"http://schemas.microsoft.com/authorization/claims/permit`", Value = `"true`");" # New line and escape quotes
# Only for Online Request
$CertificateAuthority = "dc01.kloud.internal\kloud-Showcase-CA"

### Preparation
$PfxPasswordADFS = Get-Credential "PFX password" -message "Enter the password for the ADFS PFX certificate"
if ($PfxPasswordADFS -eq $null) {
write-host "No password entered, exiting"
exit
}
$ADFSuserCredential = Get-Credential $ADFSuser -message "Enter the password for the ADFS service account"
if ($ADFSuserCredential -eq $null) {
write-host "No password entered, exiting"
exit
}

if ((Test-Path $CertificateLocalPath) -eq $false) {
mkdir $CertificateLocalPath
}

$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS

$CertificateADFSsubjectCN = "CN=" + $CertificateADFSsubject
cd $CertificateLocalPath

function New-CertificateRequest {
param (
[Parameter(Mandatory=$true, HelpMessage = "Please enter the subject beginning with CN=")]
[ValidatePattern("CN=")]
[string]$subject,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the SAN domains as a comma separated list")]
[array]$SANs,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$OnlineCA,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$CATemplate = "WebServer"
)

### Preparation
$subjectDomain = $subject.split(‘,’)[0].split(‘=’)[1]
if ($subjectDomain -match "\*.") {
$subjectDomain = $subjectDomain -replace "\*", "star"
}
$CertificateINI = "$subjectDomain.ini"
$CertificateREQ = "$subjectDomain.req"
$CertificateRSP = "$subjectDomain.rsp"
$CertificateCER = "$subjectDomain.cer"

### INI file generation
new-item -type file $CertificateINI -force
add-content $CertificateINI ‘[Version]’
add-content $CertificateINI ‘Signature="$Windows NT$"’
add-content $CertificateINI ”
add-content $CertificateINI ‘[NewRequest]’
$temp = ‘Subject="’ + $subject + ‘"’
add-content $CertificateINI $temp
add-content $CertificateINI ‘Exportable=TRUE’
add-content $CertificateINI ‘KeyLength=2048’
add-content $CertificateINI ‘KeySpec=1’
add-content $CertificateINI ‘KeyUsage=0xA0’
add-content $CertificateINI ‘MachineKeySet=True’
add-content $CertificateINI ‘ProviderName="Microsoft RSA SChannel Cryptographic Provider"’
add-content $CertificateINI ‘ProviderType=12’
add-content $CertificateINI ‘SMIME=FALSE’
add-content $CertificateINI ‘RequestType=PKCS10’
add-content $CertificateINI ‘[Strings]’
add-content $CertificateINI ‘szOID_ENHANCED_KEY_USAGE = "2.5.29.37"’
add-content $CertificateINI ‘szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"’
add-content $CertificateINI ‘szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"’
if ($SANs) {
add-content $CertificateINI ‘szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"’
add-content $CertificateINI ‘[Extensions]’
add-content $CertificateINI ‘2.5.29.17 = "{text}"’

foreach ($SAN in $SANs) {
$temp = ‘_continue_ = "dns=’ + $SAN + ‘&"’
add-content $CertificateINI $temp
}
}

### Certificate request generation
if (test-path $CertificateREQ) {del $CertificateREQ}
certreq -new $CertificateINI $CertificateREQ

### Online certificate request and import
if ($OnlineCA) {
if (test-path $CertificateCER) {del $CertificateCER}
if (test-path $CertificateRSP) {del $CertificateRSP}
certreq -submit -attrib "CertificateTemplate:$CATemplate" -config $OnlineCA $CertificateREQ $CertificateCER

certreq -accept $CertificateCER
}
}

### Script

if ($OnlineRequest) {

# Certificate creation
New-CertificateRequest -subject $CertificateADFSsubjectCN -OnlineCA $CertificateAuthority

# Certificate export
$ADFScertificate = dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}
if (($ADFScertificate.count) -gt 1) {
write-host -foregroundcolor Yellow "`n`nDuplicate certifcates! Delete all certificates with the subject $CertificateADFSsubject"
exit
}
Export-PfxCertificate -Cert $ADFScertificate -FilePath $CertificateADFSremotePath -Password $PfxPasswordADFS.Password
}
else {
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password
}

# ADFS Install
Add-WindowsFeature ADFS-Federation -IncludeManagementTools
Import-Module ADFS
$CertificateThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubjectCN}).thumbprint
Install-AdfsFarm -CertificateThumbprint $CertificateThumbprint -FederationServiceDisplayName $ADFSdisplayName -FederationServiceName $CertificateADFSsubject -ServiceAccountCredential $ADFSuserCredential

# ADFS Non Claims Aware Relying Party Trust
Add-AdfsNonClaimsAwareRelyingPartyTrust -Name $RelyingPartyTrustExchangeName -Identifier $RelyingPartyTrustExchangeURI -IssuanceAuthorizationRules $RelyingPartyTrustExchangeIssuanceRule

$Finish = get-date
$Elapsed = $finish – $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed
[/sourcecode]

Setup-ADFSserver2.ps1

Usage: .\Setup-ADFSserver2.ps1

[sourcecode language=”powershell”]
$Start = get-date

### Variables
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSprimaryServer = "2012R2ADFS1.kloud.internal"
$ADFSuser = "kloud\ADFSfarm2012R2"
$CertificateLocalPath = "C:\Certificates\"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"

### Preparation
$PfxPasswordADFS = Get-Credential "PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
write-host "No password entered, exiting"
exit
}
$ADFSuserCredential = Get-Credential $ADFSuser -message "Enter the password for the ADFS service account"
if ($ADFSuserCredential -eq $null) {
write-host "No password entered, exiting"
exit
}

$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS

### Script
# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password
$CertificateThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $ADFSfqdn}).thumbprint

# ADFS Install
Add-WindowsFeature ADFS-Federation -IncludeManagementTools
Import-Module ADFS
Add-AdfsFarmNode -CertificateThumbprint $CertificateThumbprint -ServiceAccountCredential $ADFSuserCredential -PrimaryComputerName $ADFSprimaryServer -PrimaryComputerPort 80

$Finish = get-date
$Elapsed = $finish – $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed
[/sourcecode]

Setup-WAPserver1.ps1

Usage:

  • .\Setup-WAPserver1.ps1
  • .\Setup-WAPserver1.ps1 –OnlineRequest

[sourcecode language=”powershell”]
param (
[switch]$OnlineRequest
)

$Start = get-date

### Variables
# WAP and ADFS
$CertificateWAPsubject = "mail.showcase.kloud.com.au"
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$CertificateWAPsans = "mail.showcase.kloud.com.au","autodiscover.showcase.kloud.com.au","lyncws.showcase.kloud.com.au","lyncdiscover.showcase.kloud.com.au","meet.showcase.kloud.com.au","dialin.showcase.kloud.com.au","owas.showcase.kloud.com.au","teams.showcase.kloud.com.au","my.showcase.kloud.com.au"
$ADFSadministrator = "kloud\administrator"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$CertificateLocalPath = "C:\Certificates\"
# Lync
$LyncDomain = "showcase.kloud.com.au"
$LyncWebServicesRoot = "lyncws."
$OfficeWebAppsRoot = "owas."
# Exchange
$AdfsExchangeRelyingParty = "Exchange"
$ExchangeExternalURL = "https://mail.showcase.kloud.com.au/"
$ExchangeAutodiscoverURL = "https://autodiscover.showcase.kloud.com.au/"
$ExchangeInternalURL = "https://ex2010-01.kloud.internal/"
$ExchangeSPN = "http/ex2010-01.kloud.internal"
#SNI
$SniIPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all
# Only for Online Request
$CertificateAuthority = "dc01.kloud.internal\kloud-Showcase-CA"

### Preparation
$PfxPasswordADFS = Get-Credential "ADFS PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
write-host "No password entered, exiting"
exit
}
$PfxPasswordWAP = Get-Credential "WAP PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordWAP -eq $null) {
write-host "No password entered, exiting"
exit
}
$ADFScredentials = Get-Credential $ADFSadministrator -message "Enter the password for the ADFS service account"
if ($ADFScredentials -eq $null) {
write-host "No password entered, exiting"
exit
}
$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateWAP = "$CertificateWAPsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS
$CertificateWAPremotePath = $CertificateRemotePath + $CertificateWAP
$CertificateWAPsubjectCN = "CN=" + $CertificateWAPsubject

function New-CertificateRequest {
param (
[Parameter(Mandatory=$true, HelpMessage = "Please enter the subject beginning with CN=")]
[ValidatePattern("CN=")]
[string]$subject,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the SAN domains as a comma separated list")]
[array]$SANs,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$OnlineCA,
[Parameter(Mandatory=$false, HelpMessage = "Please enter the Online Certificate Authority")]
[string]$CATemplate = "WebServer"
)

### Preparation
$subjectDomain = $subject.split(‘,’)[0].split(‘=’)[1]
$CertificateINI = "$subjectDomain.ini"
$CertificateREQ = "$subjectDomain.req"
$CertificateRSP = "$subjectDomain.rsp"
$CertificateCER = "$subjectDomain.cer"

### INI file generation
new-item -type file $CertificateINI -force
add-content $CertificateINI ‘[Version]’
add-content $CertificateINI ‘Signature="$Windows NT$"’
add-content $CertificateINI ”
add-content $CertificateINI ‘[NewRequest]’
$temp = ‘Subject="’ + $subject + ‘"’
add-content $CertificateINI $temp
add-content $CertificateINI ‘Exportable=TRUE’
add-content $CertificateINI ‘KeyLength=2048’
add-content $CertificateINI ‘KeySpec=1’
add-content $CertificateINI ‘KeyUsage=0xA0’
add-content $CertificateINI ‘MachineKeySet=True’
add-content $CertificateINI ‘ProviderName="Microsoft RSA SChannel Cryptographic Provider"’
add-content $CertificateINI ‘ProviderType=12’
add-content $CertificateINI ‘SMIME=FALSE’
add-content $CertificateINI ‘RequestType=PKCS10’
add-content $CertificateINI ‘[Strings]’
add-content $CertificateINI ‘szOID_ENHANCED_KEY_USAGE = "2.5.29.37"’
add-content $CertificateINI ‘szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"’
add-content $CertificateINI ‘szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"’
if ($SANs) {
add-content $CertificateINI ‘szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"’
add-content $CertificateINI ‘[Extensions]’
add-content $CertificateINI ‘2.5.29.17 = "{text}"’

foreach ($SAN in $SANs) {
$temp = ‘_continue_ = "dns=’ + $SAN + ‘&"’
add-content $CertificateINI $temp
}
}

### Certificate request generation
if (test-path $CertificateREQ) {del $CertificateREQ}
certreq -new $CertificateINI $CertificateREQ

### Online certificate request and import
if ($OnlineCA) {
if (test-path $CertificateCER) {del $CertificateCER}
if (test-path $CertificateRSP) {del $CertificateRSP}
certreq -submit -attrib "CertificateTemplate:$CATemplate" -config $OnlineCA $CertificateREQ $CertificateCER

certreq -accept $CertificateCER
}
}

### Script

# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password

if ($OnlineRequest) {
# Request Web Application Proxy certificate
New-CertificateRequest -subject $CertificateWAPsubjectCN -SANs $CertificateWAPsans -OnlineCA $CertificateAuthority

# Export Web Application Proxy certificate
$WAPcertificate = dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateWAPsubject}
if (($WAPcertificate.count) -gt 1) {
write-host -foregroundcolor Yellow "`n`nDuplicate certifcates! Delete all certificates with the subject $CertificateWAPsubject"
exit
}
Export-PfxCertificate -Cert $WAPcertificate -FilePath $CertificateWAPremotePath -Password $PfxPasswordWAP.Password
}
else {
Import-PfxCertificate –FilePath $CertificateWAPremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordWAP.Password
}

# Add Web Application Proxy Role
Install-WindowsFeature Telnet-Client, RSAT-AD-PowerShell, Web-Application-Proxy -IncludeManagementTools

# Web Application Proxy Configuration Wizard
$CertificateADFSThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}).thumbprint
Install-WebApplicationProxy -CertificateThumbprint $CertificateADFSThumbprint -FederationServiceName $CertificateADFSsubject -FederationServiceTrustCredential $ADFScredentials

## Web Application Proxy Applications
$CertificateWAPThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateWAPsubject}).thumbprint

# Lync
Add-WebApplicationProxyApplication -Name ‘Lync Web Services’ -ExternalPreAuthentication PassThrough -ExternalUrl "https://$LyncWebServicesRoot$LyncDomain/" -BackendServerUrl ("https://"+$LyncWebServicesRoot+$Lyncdomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Lync Lyncdiscover’ -ExternalPreAuthentication PassThrough -ExternalUrl "https://lyncdiscover.$LyncDomain/" -BackendServerUrl ("https://lyncdiscover."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Lync Dialin’ -ExternalPreAuthentication PassThrough -ExternalUrl "https://dialin.$LyncDomain/" -BackendServerUrl ("https://dialin."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Lync Meet’ -ExternalPreAuthentication PassThrough -ExternalUrl "https://meet.$LyncDomain/" -BackendServerUrl ("https://meet."+$LyncDomain+":4443/") -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Office Web Apps Server’ -ExternalPreAuthentication PassThrough -ExternalUrl "https://$OfficeWebAppsRoot$LyncDomain/" -BackendServerUrl ("https://"+$OfficeWebAppsRoot+$LyncDomain+"/") -ExternalCertificateThumbprint $CertificateWAPThumbprint

# Exchange

Add-WebApplicationProxyApplication -Name ‘Exchange OWA’ -ExternalPreAuthentication ADFS -ADFSRelyingPartyName $AdfsExchangeRelyingParty -ExternalUrl $ExchangeExternalURL"owa/" -BackendServerUrl $ExchangeInternalURL"owa/" -BackendServerAuthenticationSpn $ExchangeSPN -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange ECP’ -ExternalPreAuthentication ADFS -ADFSRelyingPartyName $AdfsExchangeRelyingParty -ExternalUrl $ExchangeExternalURL"ecp/" -BackendServerUrl $ExchangeInternalURL"ecp/" -BackendServerAuthenticationSpn $ExchangeSPN -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange Autodiscover’ -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeAutodiscoverURL"autodiscover/" -BackendServerUrl $ExchangeInternalURL"autodiscover/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange Outlook Anywhere’ -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"rpc/" -BackendServerUrl $ExchangeInternalURL"rpc/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange OAB’ -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"OAB/" -BackendServerUrl $ExchangeInternalURL"OAB/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange EWS’ -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"EWS/" -BackendServerUrl $ExchangeInternalURL"EWS/" -ExternalCertificateThumbprint $CertificateWAPThumbprint
Add-WebApplicationProxyApplication -Name ‘Exchange ActiveSync’ -ExternalPreAuthentication PassThrough -ExternalUrl $ExchangeExternalURL"Microsoft-Server-ActiveSync/" -BackendServerUrl $ExchangeInternalURL"Microsoft-Server-ActiveSync/" -ExternalCertificateThumbprint $CertificateWAPThumbprint

## Exchange SPN for KCD

Import-Module -Name ActiveDirectory
$WAPserver = Get-ADComputer (hostname)
Set-ADObject $WAPserver.DistinguishedName -Add @{"msDS-AllowedToDelegateTo" = $ExchangeSPN}
Set-ADObject $WAPserver.DistinguishedName -Replace @{"userAccountControl" = 16781312}

# Set default SNI binding
sleep 15 # sleep for 15 seconds to allow WAP bindings to be set
$certsList = @()
$certs = netsh http show sslcert | where {$_ -match ":port" -or $_ -match "Certificate Hash" -or $_ -match "Application ID"}
$certs = $certs.replace(" : ","#").replace(" ","")
$certs | foreach {
if ($_ -match ":port") {
$tempObject = New-Object -Type PSObject
$tempObject | Add-Member -Type noteproperty -Name Binding -value $_.split("#")[1]
}
elseif ($_ -match "CertificateHash") {
$tempObject | Add-Member -Type noteproperty -Name CertificateHash -value ($_.split("#")[1]-replace " ")
}
else {
$tempObject | Add-Member -Type noteproperty -Name ApplicationID -value ($_.split("#")[1]-replace " ")
$certsList += $tempObject
}
}

$match = $certsList | where {$_.Binding -match $CertificateWAPsubject}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

write-host -ForegroundColor Green "`n`nDefault bindings before changes"
netsh http show sslcert | where {$_ -match "ip:port"}

$command = "http add sslcert ipport=$SniIPport certhash=$CertificateHash appid=$ApplicationID"
write-host -ForegroundColor Yellow "`n`nAdding Certificate"
$command | netsh

write-host -ForegroundColor Yellow "`n`nDefault bindings after changes. Check that $SniIPport exists"
netsh http show sslcert | where {$_ -match "ip:port"}

$Finish = get-date
$Elapsed = $finish – $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed
[/sourcecode]

Setup-WAPserver2.ps1

Usage: .\Setup-WAPserver2.ps1

[sourcecode language=”powershell”]
$Start = get-date

### Variables
$CertificateWAPsubject = "mail.showcase.kloud.com.au"
$CertificateADFSsubject = "adfs2.showcase.kloud.com.au"
$ADFSadministrator = "kloud\administrator"
$CertificateRemotePath = "\\dc01.kloud.internal\certificates\"
$CertificateLocalPath = "C:\Certificates\"
$ExchangeSPN = "http/ex2010-01.kloud.internal"
$SniIPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all

### Preparation
$PfxPasswordADFS = Get-Credential "ADFS PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordADFS -eq $null) {
write-host "No password entered, exiting"
exit
}
$PfxPasswordWAP = Get-Credential "WAP PFX password" -message "Enter the password for the PFX certificate"
if ($PfxPasswordWAP -eq $null) {
write-host "No password entered, exiting"
exit
}
$ADFScredentials = Get-Credential $ADFSadministrator -message "Enter the password for the ADFS service account"
if ($ADFScredentials -eq $null) {
write-host "No password entered, exiting"
exit
}
$CertificateADFS = "$CertificateADFSsubject.pfx"
$CertificateWAP = "$CertificateWAPsubject.pfx"
$CertificateADFSremotePath = $CertificateRemotePath + $CertificateADFS
$CertificateWAPremotePath = $CertificateRemotePath + $CertificateWAP
$CertificateWAPsubjectCN = "CN=" + $CertificateWAPsubject

### Script

# Import ADFS Certificate
if ((Test-Path $CertificateLocalPath) -eq $false) {
mkdir $CertificateLocalPath
}
cd $CertificateLocalPath
Import-PfxCertificate –FilePath $CertificateADFSremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordADFS.Password

# Import WAP Certificate
Import-PfxCertificate –FilePath $CertificateWAPremotePath -CertStoreLocation cert:\localMachine\my -Password $PfxPasswordWAP.Password

# Add Web Application Proxy Role
Install-WindowsFeature Telnet-Client, RSAT-AD-PowerShell, Web-Application-Proxy -IncludeManagementTools

# Web Application Proxy Configuration Wizard
$CertificateADFSThumbprint = (dir Cert:\LocalMachine\My | where {$_.subject -match $CertificateADFSsubject}).thumbprint
Install-WebApplicationProxy -CertificateThumbprint $CertificateADFSThumbprint -FederationServiceName $CertificateADFSsubject -FederationServiceTrustCredential $ADFScredentials

## Exchange SPN for KCD
Import-Module -Name ActiveDirectory
$WAPserver = Get-ADComputer (hostname)
Set-ADObject $WAPserver.DistinguishedName -Add @{"msDS-AllowedToDelegateTo" = $ExchangeSPN}
Set-ADObject $WAPserver.DistinguishedName -Replace @{"userAccountControl" = 16781312}

# Set default SNI binding
sleep 15 # sleep for 15 seconds to allow WAP bindings to be set
$certsList = @()
$certs = netsh http show sslcert | where {$_ -match ":port" -or $_ -match "Certificate Hash" -or $_ -match "Application ID"}
$certs = $certs.replace(" : ","#").replace(" ","")
$certs | foreach {
if ($_ -match ":port") {
$tempObject = New-Object -Type PSObject
$tempObject | Add-Member -Type noteproperty -Name Binding -value $_.split("#")[1]
}
elseif ($_ -match "CertificateHash") {
$tempObject | Add-Member -Type noteproperty -Name CertificateHash -value ($_.split("#")[1]-replace " ")
}
else {
$tempObject | Add-Member -Type noteproperty -Name ApplicationID -value ($_.split("#")[1]-replace " ")
$certsList += $tempObject
}
}

$match = $certsList | where {$_.Binding -match $CertificateWAPsubject}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

write-host -ForegroundColor Green "`n`nDefault bindings before changes"
netsh http show sslcert | where {$_ -match "ip:port"}

$command = "http add sslcert ipport=$SniIPport certhash=$CertificateHash appid=$ApplicationID"
write-host -ForegroundColor Yellow "`n`nAdding Certificate"
$command | netsh

write-host -ForegroundColor Yellow "`n`nDefault bindings after changes. Check that $SniIPport exists"
netsh http show sslcert | where {$_ -match "ip:port"}

$Finish = get-date
$Elapsed = $finish – $start
"Elapsed time: {0:mm} minutes and {0:ss} seconds" -f $Elapsed
[/sourcecode]

Add the servers to the load balancer, external DNS entries and you should be able to login to OWA and join a Lync meeting.

Category:
ADFS, Communication and Collaboration, Exchange, Lync
Tags:
,

17
Leave a Reply

Leave a Reply

  Subscribe  
newest oldest most voted
Notify of
Andreas Neubauer
Guest
Andreas Neubauer

Marc,
it looks like your scripts will save a lot of time, to get Exchange and Lync external services working.

I have two Questions:
Is it possible to use your scripts without any problems on Servers 2012R2 in different Language Version than english, please?
You are publishing Exchange 2013 and Lync 2013, but will it also work if we are using the older Exchange 2010 and Lync 2010? Must I have a look to anything?

Thanks
Andreas

Ambers
Guest
Ambers

I hadn’t see your scripts until now and I can’t wait to use them. I installed using the wizard. Do you know how the ADFS ProxyTrust certificates that are autogenerated by the WAP configuration wizard function? I have them in the private store on each of two WAP servers. I was expecting to see one of each of these certs in the AdfsTrustedDevices store on the ADFS servers, but on the load balanced ADFS farm servers I see both certificates multiple times on the first server, and erratic results on the second server in the farm. I seemingly get a… Read more »

Stive
Guest
Stive

Hi Marc, is it still a requirement to install the ADFS Role on a DC?

UC Warrior
Guest

Hi Marc,
Great post thanks. You mention that “In order to publish Exchange OWA and ECP applications with ADFS authentication, the Web Application Proxy servers must be domain joined in order to perform KCD”
As most people intend putting their WAP servers in DMZ they will not be willing to put domain joined servers in DMZ so I can see this as been a show stopper.

yoda-ict
Guest

Hello Marc, We are running an Exchange 2012 SP3 farm for customers. Currently all customers use our active directory as account storage and login using their UPN. However, we got a request from a prospect if it’s possible if they use our hosted exchange mailboxes but with their own usernames + passwords. So my question is; could we deliver this through ADFS Web App proxy? We are already runing 2012 R2 domain controllers. What’s not clear to me is how this will work if the customers account will change his/her password, will it still work on our end? And is… Read more »

Follow Us!

Kloud Solutions Blog - Follow Us!