Provisioning Users for Lync / Skype for Business with FIM / MIM using the Granfeldt PowerShell Management Agent

Forefront / Microsoft Identity Manager contains numerous Management Agents (MA’s) out of the box. However, a MA for Lync / Skype for Business isn’t one of them.

Over the years I’ve accomplished lifecycle management for users in Lync via FIM using methods that aren’t strictly best practice / supported (e.g. calling PowerShell from within a Management Agent Extension to enable/disable/manage policies). Whilst this functionally works the ability for end customers to maintain the implementation for changes is limited.

Overview

In this blog post I’ll document how you can enable an Active Directory User for Lync / Skype for Business utilising Søren Granfeldt’s extremely versatile PowerShell Management Agent. I’ll show you how to do the minimum of enabling a user. Understanding how this is done you can then easily then extend the functionality for lifecycle management (e.g. change mobility, federation, voice policies for users based on managed attributes, and de-provisioning).

My Lync / Skype for Business PS MA is used in conjunction with an Active Directory MA and Declarative Rules in the MIM Portal.

Getting Started with the Granfeldt PowerShell Management Agent

First up, you can get it from here. Søren’s documentation is pretty good but does assume you have a working knowledge of FIM/MIM and this blog post is no different. Configuration tasks like adding additional attributes the User Object Class in the MIM Portal, updating MPR’s, flow rules, Workflows, Sets etc are assumed knowledge and if not is easily Bing’able for you to work it out.

Three items I had to work out that I’ll save you the pain of are;

  • You must have a Password.ps1 file. Even though we’re not doing password management on this MA, the PS MA requires a file for this field. The .ps1 doesn’t need to have any logic/script inside it. It just needs to be present
  • The credentials you give the MA to run the scripts as, needs to be in the format of just ‘accountname’ NOT ‘domain\accountname’. I’m using the service account that I’ve used for the Active Directory MA. The target system is the same directory service and the account has the permissions required (you’ll need to add the service account to the appropriate Lync role group for user management)
  • The path to the scripts in the PS MA Config must not contain spaces and be in old-skool 8.3 format. I’ve chosen to store my scripts in an appropriately named subdirectory under the MIM Extensions directory. Tip: from a command shell use dir /x to get the 8.3 directory format name. Mine looks like C:\PROGRA~1\MICROS~4\2010\SYNCHR~1\EXTENS~2\Lync

Schema Script (schema.ps1)

As I’m using the OOTB (out of the box) Active Directory MA to provision the AD account and only showing provisioning, the schema only consists of the attributes needed to know the state of the user with respect to enablement and the attributes needed to enable an account.

Password Script (password.ps1)

Just a placeholder file as detailed above.

Import Script (import.ps1)

I’m using msDS-cloudExtensionAttribute20 as a breadcrumb attribute. I’m not exporting anything else on the Lync MA but we need to export something to trigger the Export script. It exists in the Import script as well for the confirming import.
On the $request line you’ll see I have a filter for the User Object Class and users with an account name starting with ‘U’. For the customer I wrote this for, all users start with U. You can obviously also have Filters on the MA. I have a few there as well, but limiting the size of the returned array makes things quicker to run full imports.

Export Script (export.ps1)

In the working example below I’ve hard-coded the RegistrarPool into the Export Script. If your environment contains more than one RP then you could have the desired pool as a flow rule on your AD MA that creates AD users on so you can make it an initial flow attribue.

A key item is I’m also using Remote PowerShell for Lync. That way I don’t need to have the PS Module for Lync on the MIM server itself.

Wiring it all together

In order to wire the functionality together there are the usual number of configuration steps to be completed. Below I’ve shown a number of the key points associated with making it all work.

Basically create the PS MA, import attributes from the PS MA, add any additional attributes to the Portal Schema, update the Portal Filter to allow Administrators to use the attribute, update the Synchronisation MPR to allow the Sync Engine to flow in the new attribute, create the Set used for the transition, create your Synchronisation Rule, create your Lync Workflow, create your Lync MPR, create your MA Run Profiles and let it loose.

Management Agent Configuration

As per the tips above, the format for the script paths must be without spaces etc. I’m using 8.3 format and I’m using the same service account as my AD MA.

Password script must be specified but as we’re not doing password management its empty as detailed above.

If your schema.ps1 file is formatted correctly you can select your attributes.

My join rule is simple. AccountName (which as you’ll see in the Import.ps1 is aligned with sAMAccountName) to AccountName in the MetaVerse.

My import flows are a combination of logic used for other parts of my solution, and a Boolean flag to determine if the user in enabled for Lync or not (used for my Transition Set and my Export script).

Synchronisation Rules

My Lync Outbound Sync rule doesn’t need to be very complex. All it is doing is sync’ing out my breadcrumb attribute. I’m flowing out the objectGUID out to ms-dsCloudExtensionAttribute20.

Set

I created a Set that I use as a transition to trigger provisioning to Lync. My Set looks to see if the user account is active and in AD (I have a Boolean attribute in the MIM Portal that is set based on an advanced flow rule in the Sync engine that has some logic to determine if employment date as sourced from my HR Management Agent is current),

Workflow

An action based workflow that will use the trigger the Synchronisation rule on the Lync MA.

MPR

The Transition Set MPR.

And the Policy Workflow created above.

Summary

Using the Granfeldt Powershell MA it is easy to enable users for Lync.

Follow Darren on Twitter @darrenjrobinson

Federated User – Presence Unknown

Here at Kloud we have just been busy updating our Skype for Business Public Certificate before it expired. Our SAN certificate provided by GoDaddy is used on our Edge Server and Reverse Proxy for all external communication to be encrypted with TLS or HTTPS.

After updating our certificate and restarting services to make the certificate take effect, we started to get some feedback from Kloudies (Kloud Employees) of federated contacts showing up with ‘Presence Unknown’ in their contacts list. These were contacts which they had previously been communicating with prior to the certificate change. The head scratcher was that it wasn’t all federated partner domains.

Here is some of the findings and troubleshooting we performed to get to the resolution of the issue.

First thing to always test is the inbuilt PowerShell cmdlets on your server itself.

Test-CsFederatedPartner -Domain contoso.com -TargetFqdn kloudedge.kloud.local

Successful domains appear with the following lines:
Target Fqdn   : kloudedge.kloud.local
Result        : Success
Latency       : 00:00:00
Error Message :
Diagnosis     :

While domains that had the issue would have the following output:
Diagnosis:
ErrorCode=1047,Source=sip.kloud.com.au,Reason=Failed to complete TLS negotiation with a federated peer server,fqdn=SIP.Contoso.COM:5061,peer-type=FederatedPartner,winsock-code=10054,ip-address=xx.xx.xx.xx,winsock-info=The peer forced closure of the connection

From the above message we could see that the peer’s (being the federated partner) Access Edge server was actively closing the connection from our server.

But why?? We haven’t done anything wrong….

This can become a hard thing to troubleshoot if you don’t have access or know the SFB administrator at the partner organisation. Lucky for us this isn’t the case for at least one of our troubled SIP domains.

Investigating the Partner Edge Server we did the following:

  1. Start > Run mmc.
  2. File > Add/Remove Snap-in… > Add… > Certificates (Add) > Service Account  > Skype for Business Server Access Edge (Finish) > Close > OK.
  3. In the mmc, navigate to RtcSrv\Accepted Certificates > Certificates.
  4. Look for the certificate named “sip.kloud.com.au”

What we found was still a copy of the certificate that had just been expired, but no new certificate with the up to date expiration. We wanted to give this Edge Server a little helping hand and investigate why it would potentially not have added the new certificate into this store. Upon copying the new sip.kloud.com.au.cer file to a location on the server and opening the certificate for viewing, we assessed the path of certificate. Bingo! The certificate path was not validated.

The server was missing the GoDaddy Intermediates that are associated with our new certificate. Upon adding the new GoDaddy intermediate certificates to the intermediate store location and restarting the Acess Edge Service to make certificate settings take effect, instant success. The new sip.kloud.com.au certificate was downloaded from our Kloud Edge Server into the Accepted Certificates store of this Edge Server whom we were talking with and presence was restored both ways.

So now we have made the assumption of knowing all the federated partners we associate with that don’t patch their Edge Server with Windows Update. I hope I’m not talking about you.

Edge Servers can easily be the servers that you forget to put in a patching cycle due to being in a DMZ restrict zone, also they are workgroup machines so they won’t take the default group policy settings from WSUS. But here is a prime example of how it could have negative impact on your company if you don’t. So the only way to fix the issue is to spread the word. I don’t have access to everyone. Until they read this blog I can’t chat with them online. The moral of the story;

Patch your Edge Server so they can have up to date certificate information or get left behind while the rest of the Skype for Business world keeps moving on

 

Skype for Business Online to On-Premises Migration

Okay guys – you’ve been told “lets move everyone back from the cloud! We need Enterprise Voice for our users” This will go against most Microsoft sales materials as we should be looking towards cloud.

If you are part of an organisation that has been birthed out of Skype for Business Online (SFBO) as part of your Office 365 subscription, it would make sense that you would have never had on-premises Lync or SFB servers in your Active Directory domain. Very little configuration is needed in SFBO and a busy administrators would have loved enabling the license SKU for SFBO for each users and then wiped their hands of it. Its just that easy, enable and forget.

The main limitation around SFBO is the need for an IP-PBX and/or PSTN connectivity. The time may have come for your organisation to leverage your Microsoft agreement even further and look to your existing technology/application catalogue and see that Skype for Business can fill the requirements of your aging PBX. This trigger point is usually when the PBX asset has reached capacity and there is a cost trade off;

  • Throw more money at the dusty old PBX box for extra expanision cards and possibly cabling to terminal
  • Spend the money to start new in the world of VOIP, but look to an existing technology that can provide this functionality (and hopefully more) before looking elsewhere

Telephones have been around for a long time, its nothing new. Picking up, making, transferring calls is all pretty standard stuff we have been doing for few decades now. If I’m going to invest money in something I should be asking for more! more! more! How do I make sure that my choice keeps my organisation staying relevant in the way it communicates for the next chapter?

Hello Skype for Business as PBX replacement.

That’s enough of ramble. You have come here to understand the process of moving users back from the cloud because there is less documented about this procedure than too the cloud.

Current Environment

  • On-premises Domain Services
  • DirSync/AADSync Server
  • Office 365 Tenant
    • Skype for Business Online SKU enabled
  • DNS for my domain.com.au
    • Lyncdiscover.domain.com CNAME webdir.online.lync.com
    • SIP.domain.com. CNAME sipdir.online.lync.com
    • _sipfederationtls._tcp.domain.com SERVICE sipfed.online.lync.com
    • _sip._tls.domain.com SERVICE sipdir.online.lync.com

Current_Environment

In this scenario we will look at the steps needed to specifically enable Hybrid and move users back to on-premises.

  1. Add on-premises infrastructure
  2. Connect Hybrid SFB with Office 365 and on-premises
  3. Move Enterprise Voice users back

Add On-premises Infrastructure

  • Add your Front End, Edge and Reverse Proxy Infrastructure
    1. Build the Servers as per TechNet, but leave the SIP Address’s DNS zones to not effect internal and external clients just yet
    2. All discover records should still point to Microsoft (sipdir, webdir etc)
  • Confiure your Edge and Reverse Proxy with Public Certificates
    1. Test the port authentication as best you can;
      1. Telnet to Edge Ports
      2. Test Reverse Proxy URLs
      3. Remote Connectivity Analyzer for Edge
  • Configure Edge for Federation
    1. Assign your Edge as the Federation Route in your Topology Builder
    2. Configure Edge Specific Configuration
Set-CSAccessEdgeConfiguration -AllowOutsideUsers 1 -AllowFederatedUsers 1 -UseDnsSrvRouting -EnablePartnerDiscovery $true
  • Recreate Allowed Federated Domains in On-premises
    1. If you do a get-csalloweddomain in Office 365 you may not get all the correct information specific for your tenant back to you.
      1. If you have federation with only allowed/block lists, you may need to recreate these as there is no nice way of piping the cmdlets from ‘get’ to ‘new’.
      2. Allow open federation to accommodate for all traffic is the simplist approach for migration
  • Set the Global Remote Access and Federation User Policy to Allow
Get-CsExternalAccessPolicy -Identity Global | Set-CsExternalAccessPolicy -EnableFederationAccess $True -EnableOutsideAccess $True

 

Connect Hybrid Skype for Business

  • Remove existing Lync/Skype for Business Hosting Rule
Get-CSHostingProvider -Identity <SFB/Lync Online> | Remove-CSHostingProvider
  • Recreate the Provider with Hybrid Specific Configuration
New-CSHostingProvider -Identity SFBOnline -ProxyFqdn "sipfed.online.lync.com" -Enabled $true -EnabledSharedAddressSpace $true -HostsOCSUsers $true -VerificationLevel UseSourceVerification -IsLocal $false -AutodiscoverUrl https://webdir.online.lync.com/Autodiscover/AutodiscoverService.svc/root
  • Update External/Public DNS Records
    1. Remember that only updating external DNS records means that your internal users can functions ‘as-is’ until your happy with the progress
      1. Edge Names (SIP Access/Web Conference/ AV  FQDNs)
      2. External Web Services FQDN
      3. Dialin FQDN
      4. Meeting FQDN
      5. LyncDiscover FQDN
      6. SRV _sipfederationtls._tcp.domain.com
      7. SRV _sip._tls.domain.com
    2. Remote users that aren’t previously authenticated could have an issue logging in at the time of the change

Test Process

Join On-premises Pilot User with Online Account

This makes your on-premises deployment aware of active directory accounts that are currently cloud enabled.

  • Run the following cmdlet in SFB Management Shell connected to on-premises servers to test a user
Enable-CsUser -Identity &lt;accountname&gt; -SipAddress "sip:<sipaddress>" -HostingProviderProxyFqdn "sipfed.online.lync.com" -verbose
  • Synchronise AADSync/DirSync
    1. Login to Directory Sync Server
    2. Run >  Delta Import And Delta Sync on the Active Directory Connector
    3. You will see an update count that includes your object

Move a Pilot Users back

This step will actually move the user from SFB Online back to your on-premises pool with contact lists intact. This is initiated from the on-premises server and will need authentication for the Office 365 tenant to perfom the task.

  • Run the following cmdlet in Powershell connected to both on-premises and online sessions
Import-Module LyncOnlineConnector
$credential = Get-Credential
$session = New-CsOnlineSession -Credential $credential
Import-PSSession $session -AllowClobber
  • Get the Online Admin URL for your tenant
    1. Log into Office 365 Portal
    2. Check the URL presented in the address bar, will be admin0x. Where x = a letter specific for you
  • Move the User back
Move-CsUser -Identity <UPN> -Target <FE Pool Name> -Credential $cred -HostedMigrationOverrideURL https://admin0f.online.lync.com/HostedMigration/hostedmigrationservice.svc

Enable All Users

If the above to Pilot Tests worked we need to scale up our migration batches. We need to mass produce the following cmdlet in SFB Management Shell connecting on-premises user accounts to the corresponding online account;

Enable-CsUser -Identity <accountname> -SipAddress "sip:&lt;sipaddress&gt;" -HostingProviderProxyFqdn "sipfed.online.lync.com" -verbose

To do this practically I used the UPN value which I knew that would resolve to the correct users values in on-premises and Office 365 because they are synced from the source. I also could then understand the logic that the users UPN is in this case the primary SMTP/Mail value and therefor matching SIP Address for Skype for Business that I needed.

  • Get all the Office 365 users that are enabled for Skype for Business
Get-CSOnlineUser | ? {$_.SipAddress -notlike $Null} | Select SipAddress, DisplayName | Export-CSV -Path C:\temp\OnlineUsers.csv -NoTypeInformation
  1. This will give you a list of ‘real’ SFBO users that are licensed but are also registered SFB logins
  2. Review the list for deleted users that haven’t been removed properly, there SIPAddress will include GUID style login, these lines can be removed as we do not wish to migrate them.

Lets leverage this list of known online users and enable their joining in on-premises with a ForEach loop example below;


$Users = Import-Csv C:\updates\OnlineUsers.csv

ForEach($User in $Users)
{
$SipAddress = $user.sipaddress
$UPN = $SipAddress.replace("sip:", "")
$Enable = Enable-CsUser -Identity $UPN -SipAddress $SipAddress -HostingProviderProxyFqdn "sipfed.online.lync.com"
}
  • Update Azure Active Directory of the changes by another AADSync/DirSync > Delta Import & Delta Sync
  • Update Internal DNS to point all associated SFB records to on-premises Skype for Business Server(s)
    1. SRV _sipinternaltls._tcp.domain.com.au
    2. Lyncdiscoverinternal.domain.com.au
    3. SIP.domain.com.au
  • Add Additional A Records
    1. Meet.domain.com.au
    2. Dialin.domain.com.au
    3. Pool Name
    4. SFB Web Service URL Names
    5. Admin URL

Visual Indication of Success

Log into your on-premises SFB Admin Control Panel and run a blank user search to discover all users. Noticed the ‘Homed’ field should say ‘SFBOnline’ 

Move All Users

Leveraging the same list of users, run the move cmdlet like the example;


ForEach($User in $Users)
{

$Displname = $user.displayname
$SipAddress = $user.sipaddress
$UPN = $SipAddress.replace("sip:", "")
$Move = Move-CsUser -Identity $UPN -Target &amp;amp;amp;amp;amp;amp;lt;FE Pool Name&amp;amp;amp;amp;amp;amp;gt; -Credential $credential -HostedMigrationOverrideURL https://admin0f.online.lync.com/HostedMigration/hostedmigrationservice.svc -Confirm:$false
if($Move -eq $False)
{
Write-host "User $SipAddress didn't move!!"
}
}

Status

To get visual status while you move all the users, log into your Office 365 Skype for Business Administration Portal and view the details. Continually refresh the page to see the value for “users synced and homed online” go down as each user becomes enabled on-premises.

SFB_User_Stats

 

 

 

Log into your on-premises SFB Admin Control Panel and run a blank user search with a additional filter for Homed or Registrar Pool / is equal to / <registrar server name>.

Client Experience

The client should be unknowning of your changes being made in Office 365 and on-premises until you perform the move-csuser request for their account. During this period a redirect message will be sent to the client with a new registrar server FQDN and a automated logout and login will happen. If the user doesn’t have their client in the forground of their desktop, then this will happen silently in the background. The redirect in my move request had the users logged out and back in within about 1-2 seconds.

Skype for Business External Authentication

Microsoft Lync/Skype for Business has revolutionised the way people can communicate and collaborate in the workplace. With light weight and portable form factors coming into their own, devices have enabled businesses to rethink their communication strategy. Lync not only enables users to communicate using great device form factors, but also from wherever they may be located. The sense of a roaming lync identity brings freedom to how people choose to collaborate and office spaces, desks and name tags mounted above them, seem like a necessity of the past. The enablement of remote connectivity across these devices is pivotal in a Lync deployment, but sometimes isn’t entirely understood. In some circumstances security is of high concern for all forms of connectivity that can be done over the public internet, but you wouldn’t want to go without it. To remove remote access for users would be crippling the UC strategy that you were trying to put in place.

When we think about Lync/SFB with external authentication we first must articulate that there’s more than one form of authentication a user can attempt and there is many device types they can attempt authentication with. Therefore it can also be said that there is more than one endpoint and port on the edge of the corporate network listening, waiting and proxying these forms of authentication. What we need to do is make sure that each case is in a controlled and known measure to best suit your deployment.

Question: “So Arran what is secure?”

Answer: “Well the security policy should govern what is and isn’t classified as secure for you.”

The common device(s) attempting authentication are:

  1. Lync Office Client
  2. Mobile/Tablet app
  3. Windows 8 Store App

With these authentication types:

  1. NTLM
  2. TLS-DSK
  3. Passive (ADFS)

We aren’t going to talk about Kerberos cause we are concerned with external logins.

NTLM

NTLM is usually well understood as a simple challenge/response authentication but if we look at it in Lync it means that every time a web ticket expires the same challenge authentication must be presented. Usually to make this simple to the end-user we allow them to cache/save the password to the device for re-authentication on our behalf.

NTLM

 

TLS-DSK

NTLM will generally be a big ‘NO’ straight away if these conversations have started with a security team, so let’s look at Transport Layer Security Derived Session Key (TLS-DSK) as a certificate based authentication. Every Lync Front End Server is issuing a Lync User Certificate upon initial successful authentication and once the certificate is saved, the stored AD Credentials aren’t needed for the validity of the certificate which can range from 8 hours to 365 days (your choice). A key point to make about TLS-DSK is that if I have multiple devices I will receive my certificate for each. I will then use my certificate on each device to authenticate to Lync as me. Additionally the certificate I have stored is only trusted by Lync, not my entire domain or ADFS and can’t be used across other application or services.

TLSDSK

TLS-DSK allows us to move away from the simple challenge authentication and subsequent re-authentications all together.

TLS-DSK is great!

 

By disabling NTLM on external registration (shown in the diagram above with Green – Internal and Blue -External) we can then understand that a client has to have obtained a Lync certificate from the internal Front End Servers when on-premises and not provisioned through an Edge proxy. The certificate can NOT be issued from external locations due to the authentication process breaking when the client requests a web ticket to start the process. Currently Skype for Business does not do this natively. The client NTLM authentication against the web services is via the Simple URLs which is controlled via a Reverse Proxy. Currently there are only a few out the box solutions for this, Lync Solutions and Skype Shield are worth investigating.

If you go the extra mile to not allow NTLM authentication from your external network, you are then protected via additional forms of on-premises security;

  • The individual has gotten physical access to a site location through a perimeter locked entrance
  • The individual has gotten network layer 2 connectivity with a device on a access switch
  • The individual has an approved domain joined computer to gain access to domain services on the appropriate VLAN
  • The individual has supplied correct credentials of a provisioned Lync user account.

Sounds like a job for Tom Cruise hanging from the roof if you ask me. The end result is a Lync user certificate in the user store of the trusted machine. We should now be happy for the user to go out in the world with this device knowing that themselves and the managed device are who we think.

Passive

What if you would NOT like Lync to do any authentication.

“I have a centralised authentication services called Active Directory Federation Services (ADFS) and I would like to use it with Lync”.

Lync can be integrated with ADFS as your Secure Token Service (STS) and also provide a second factor if needed. You can then leverage forms based authentication or smart cards. There are a few tricks to enabling Lync Server with passive authentication, first of all to enable passive we must disable all other forms of authentication. As this is a ‘big bang’ approach to effectively disable all other authentication protocols per pool make sure you plan and test appropriately.

Passive

Lync Mobile Clients

Lync is a free download in each of the mobile stores and we can’t control if a trusted or untrusted device downloads it. So for our external access of the Lync 2013 mobile client we must feel comfortable with the authentication, which comes in the 3 forms that are available;

  • NTLM
  • Lync Certificate
  • Passive

NTLM once again maybe too basic to meet our requirements and we need to look at what else is on offer, James Frost has a great comment at the bottom of this post about products that can be used. As James points out that the certificate web service URL is exposed to the internet via the reverse proxy you are using. Depending on what type of device you are using will indicate what might be possible for you to achieve with validation of approved devices or authentication attempts. The reverse proxy is breaking the connection from front to backend and this is a perfect time to inspect it if necessary. ADFS can be a big leap just to comply with remote authentication with the Lync app, instead we can protect our users and devices via whipping the initial NTLM credentials from the devices disk and memory via PowerShell mobility in band provisioning policy;


Set-CsMobilityPolicy -AllowSaveCredentials <$true/$false>

We can also disable EWS requests which does simple NTLM requests to exchange inside the app also;


Set-CsMobilityPolicy - AllowExchangeConnectivity <$true/$false>

Where does the authentication happen?

When I stated that there is “more than one endpoint on the edge of the corporate network, waiting and proxying these forms of authentication” this is because clients behave differently. Lync Office Client directs its authentication requests to the Lync Edge servers with SIP over TLS, while all mobile device apps will use a reverse proxy that has a published URL rule as all its signalling traffic is SIP over HTTPS while on a 3G/4G data network. So there is more then one ingress point for authentication to monitor. The Edge Server is inspecting all packets, while a Reverse Proxy can inspect the HTTPS traffics as it’s also splitting the traffic from frontend to backend.

Lync External Auth Flow

Summary

Options are available to change the way Lync can authenticate to your network. A greater understanding of the authentication endpoints, protocols and flows will aid you in being on top of your environment and smoothly rolling out external devices that are secure and trusted.

Lync 2013 Basic Client – the forgotten client

I’ve had conversations with customers lately whom are looking to use Lync Server 2013 and currently don’t want to move their desktop SOE to Office 2013 suite with Lync 2013 Client. This can be a project in itself and one that IT Admins aren’t always prepared to look at. Whether this is because of the analysis needed to roll out the suite or they still are in an agreement that only allows them to Office 2010. Standing up a Lync Server 2013 environment and then rolling out the Lync 2010 client just makes me feel sad for all the end users, as they look at their ‘new‘ client which too many others around the world would look like the ‘old‘ client that we were happy to uninstall a few years ago.

Some IT Admins may have the approach

“I’m not an enterprise voice user and I don’t know the adoption of Lync Meetings yet. So I’m just expecting my users to use IM/P and potentially ‘some‘ video. Who cares what client I use”

Well in some context this statement is right, but with the wrong approach. The new Lync client isn’t just white in colour and offer a minimalistic design, it also adds a lot of additional functionality that is key to the user experience with 2013 backend servers pulling the strings. I think the general miss conception is that the server should provide all the grunt and the client should just be the shell to display the media stream. Well that isn’t entirely true, the Lync Server 2013 AVMCU does control rate matching and upscaling of video streams in a much better way on server 2013, but without the updated client endpoints, a 2010 client with none optimized video codec will never get the optimal stream it deserves like a 720p/1080p@30fps resolution for P2P or Lync Meeting video even if the client/network permits. A Lync 2010 client alongside a Lync 2013 client with same hardware and OS would present a VGA@15fps media stream to the user, while the Lync 2013 client could do the full 1080p@30fps. Also the conference/meeting experience will be hampered for the individual with only smaller selection of meeting modalities on offer at the lesser resolution. So in short, it will do less with only a single participant stream and in lesser quality on same hardware and OS (ouch).

“So how do I take advantage of these features without worrying about my Office Suite licensing? I don’t want to go back to my license rep and fight with them over how I get to 2013 from my current arrangement just to get Lync rolled out to my end users”.

The answer is the Lync 2013 Basic Client which offers a lot of feature sets minus some enterprise voice goodies, oh and by the way it’s FREE! I repeat again….FREE. Free in the terms of it’s a public Microsoft Download “Drive away no more to pay”.

Microsoft Lync Basic 2013 (32 Bit)

Microsoft Lync Basic 2013 (64 Bit)

Let’s look at the Basic Client in a line up against my current Office 2013 O365 Pro Plus client with my E3 subscription which entitles me to run the Office 2013 suite.

Full Vs Basic 2013 Clients

I’m seeing double….or close enough.

As I was halfway through writing this blog, Microsoft released an update allowing some nice additional features for the Basic Client that puts it on par with the full client for conferencing/meeting experience. Now your meetings on the basic client can support a full range of experiences with the big inclusion of multi-party video gallery.

http://support2.microsoft.com/kb/2998659

  • Users can record a conversation from the … menu in a conversation window.
  • Users can use the gallery view to see all users’ video instead of only the active speaker’s in a video conversation that has more than two people.
  • Users can switch mode between gallery and speaker.

These are some missing parts to the puzzle for the Basic Client that previously had it a clear step behind its bigger brother for feature parity.

If you’re looking at rolling out a Lync 2013 PoC to a user base to see if it is going to be adopted positively in your organisation, I recommend using the Lync 2013 Basic Client to meet your needs over the Lync Office 2010 client ever day of the week. It will far exceed the 2010 client for standard client access and user adoption will be positive. If you knew that you could buy the car with the ‘options pack‘ at no extra cost, I’d take it!

So what would your clients look like if you only had purchased Lync Server 2013 backend with no Office 2013 suite?

  • Desktop – Lync 2013 Basic Client (Free)
  • Tablet/Win8 App – Lync App from Store (Free)
  • Mobile – iOS, Android, Windows Phone from Stores (Free)

Comparison between 2013 clients here

Happy Lync’ing.

Hybrid Exchange 2007/2013 and Lync EWS Integration

I came across an interesting issue recently with a client currently running Exchange 2007 and looking to migrate to Exchange Online. Since Update Rollup 10 for Exchange 2007 Service Pack 3, it has become possible to coexist Exchange 2oo7 and Exchange 2013.

After installing Exchange 2013 as the Hybrid server, this particular client ran into an issue with the Lync 2013 client losing EWS integration with any mailboxes that still reside on Exchange 2007. The net effect of this is that any users that hadn’t been migrated from Exchange 2007 to Exchange 2013 or Office 365 would have to rely on Outlook MAPI (and Outlook being open) for anything to do with the Personal Information Manager feature of Lync.

Microsoft have confirmed this is a known issue and there is no workaround or hotfix. It’s worth noting that the issue is isolated to a 2013 Hybrid configuration in coexistence with Exchange 2007 only, so a Hybrid Exchange 2010/2007 environment might be the more appropriate solution here.
Update: Microsoft have released a KB article stating that the workaround is to migrate the Exchange 2007 mailboxes to Exchange 2013 http://support.microsoft.com/kb/2941221

Symptoms:

The Lync client will try to connect to Exchange but will prompt with the following:

Legacy versions of Exchange are unable to proxy requests to Exchange 2013, however Exchange 2013 can proxy to legacy versions. This means that a key step of a Hybrid implementation is pointing the Autodiscover service to Exchange 2013, which is when this issue manifested.

In the image below you can clearly see there is an error with the conversation icon and the alert in the bottom right corner is an EWS integration error. The Calendar icon is also missing from the top row.

In Lync “Configuration Information” you’ll notice that there is no server listed for the “Contact List Provider” and under “EWS Information” it shows the status as “EWS not deployed”

Delving into this a little further, looking at the IIS logs on the Exchange 2013 Hybrid server (or the Exchange 2013 server hosting the CAS role), you can see that there is a successful 200 response to the initial authentication request:

2014-03-26 00:36:48 192.168.30.5 POST /autodiscover/autodiscover.svc &CorrelationID=<empty>;&cafeReqId=680dcd6d-2d78-452c-acc2-7659fdc97343; 443 CONTOSO\test1 192.168.10.100 OC/15.0.4569.1503+(Microsoft+Lync) – 200 0 0 15

However when you get to the Autodiscover logs, you can see that the Autodiscover XML file is never actually downloaded by the Lync client due to the mailbox being on Exchange 2007.

2014-03-26T00:36:48.196Z,680dcd6d-2d78-452c-acc2-7659fdc97343,15,0,847,30,,Negotiate,True,test@contoso.com,,OC/15.0.4569.1503 (Microsoft Lync),192.168.10.100,EXCHANGE13,EXCHANGE13.CONTOSO.LOCAL,GetUserSettings,200,InvalidRequest,0,0,1,,,,,GlobalThrottlingPolicy_d74d65fa-cdb4-4e06-be99-6f259ea1158a,,,0,8,0,8,,0,15.63,ADSessionSettingsFromAddress=0;ADRecipientSessionFindBySid=15.63;RequestedUser=test@contoso.com;S:ServiceCommonMetadata.RequestSize=1527;S:WLM.Bal=300000;S:WLM.BT=Ews;S:BudgetMetadata.MaxConn=27;S:BudgetMetadata.MaxBurst=300000;S:BudgetMetadata.BeginBalance=300000;S:BudgetMetadata.Cutoff=3000000;S:BudgetMetadata.RechargeRate=900000;S:BudgetMetadata.IsServiceAct=False;S:BudgetMetadata.LiveTime=00:01:00.2078770;S:BudgetMetadata.EndBalance=300000;Dbl:WLM.TS=15.63;I32:ATE.C[DC01.CONTOSO.LOCAL]=1;F:ATE.AL[DC01.CONTOSO.LOCAL]=0;I32:ADS.C[DC01]=1;F:ADS.AL[DC01]=4.9612,,ErrorMessage=The SOAP-based AutoDiscover service is not available for mailboxes on Exchange 2007.;

Lync_DirectAccess_2

Lync 2010 DirectAccess Audio Video and Application Sharing Troubleshooting

A customer recently had a problem with Windows 2012 DirectAccess connected clients performing desktop sharing, audio and video conversations with internal clients. DirectAccess is a Windows service that is part of the Remote Access role that allows domain joined clients to access internal resources over the internet as if they were on the LAN. DirectAccess does this by providing seamless VPN connectivity without any user input. Lync can work over DirectAccess (and Lync 2013 works a lot better as it supports IPv6) but because the traffic is encrypted and is real time communication, it is recommended to use the Lync Edge server for connectivity rather than sending the traffic over the DirectAccess VPN. For more information see this NextHop article Enabling Lync Media to Bypass a VPN Tunnel.

The Problem

The primary issue the client was facing was that when users were out of the office using DirectAccess, application sharing / remote desktop failed with an error ‘Sharing failed to connect due to network issues. Try again later’. This meant that internal support staff could not provide assistance for remote users without third party tools. Audio and video would not work either when using DirectAccess.

Troubleshooting

Not having used DirectAccess before, I had a quick scan over the DirectAccess config and went into some testing. I saw that even with DirectAccess disconnected (forcefully disconnect as it starts up automatically) the client could not share programs from Lync and audio/video would fail. So I ignored DirectAccess and started concentrating on the Lync Edge. TMG was being used as the firewall with a DMZ leg that contained the Lync Edge server. I validated all required ports were open as per Reference Architecture 1: Port Summary for Single Consolidated Edge which they were but I still saw a lot of denied traffic on TMG from the Lync Edge internal interface to the internal client IP address.

Client logging showed some strange behaviour in the failing session. The SDP (Session Description Protocol) candidate list in the SIP invite did not contain any information about the public IP address of the remote client or the Lync Edge server, it would just give the private IP as a host (local address):

a=candidate:1 1 TCP-PASS 2120613887 192.168.0.102 15380 typ host
a=candidate:1 2 TCP-PASS 2120613374 192.168.0.102 15380 typ host
a=candidate:2 1 TCP-ACT 2121006591 192.168.0.102 10295 typ host
a=candidate:2 2 TCP-ACT 2121006078 192.168.0.102 10295 typ host

An example of a correct SDP candidate list with the media relay (TURN) and reflexive (STUN) addresses:

a=candidate:1 1 TCP-PASS 2120613887 192.168.0.102 1105 typ host
a=candidate:1 2 TCP-PASS 2120613374 192.168.0.102 1105 typ host
a=candidate:2 1 TCP-ACT 2121006591 192.168.0.102 19166 typ host
a=candidate:2 2 TCP-ACT 2121006078 192.168.0.102 19166 typ host
a=candidate:3 1 TCP-PASS 6556159 203.x.x.x 55741 typ relay raddr 101.x.x.x rport 36324
a=candidate:3 2 TCP-PASS 6556158 203.x.x.x 55741 typ relay raddr 101.x.x.x rport 36324
a=candidate:4 1 TCP-ACT 7076607 203.x.x.x 55741 typ relay raddr 101.x.x.x rport 36324
a=candidate:4 2 TCP-ACT 7076094 203.x.x.x 55741 typ relay raddr 101.x.x.x rport 36324
a=candidate:5 1 TCP-ACT 1684797695 101.x.x.x 36324 typ srflx raddr 192.168.0.102 rport 29176
a=candidate:5 2 TCP-ACT 1684797182 101.x.x.x 36324 typ srflx raddr 192.168.0.102 rport 29176

For a great overview of the Lync Edge media negotiation and media traversal for remote clients which goes into detail on TURN and STUN, see the article by Jeff Schertz Lync Edge STUN versus TURN. The failing application sharing session would only list the private 192.168.0.x address, not the STUN or TURN candidates and would fail with an error:

ms-client-diagnostics: 23; reason=”Call failed to establish due to a media connectivity failure when one endpoint is internal and the other is remote”;CallerMediaDebug=”application-sharing:ICEWarn=0x80020,LocalSite=192.168.0.102:15380,RemoteSite=10.x.x.x:7342,RemoteMR=203.x.x.x:52241,PortRange=1025:65000,RemoteMRTCPPort=52241,LocalLocation=1,RemoteLocation=2,FederationType=0″

A successful session would reference the public IP address as the LocalSite and the Lync Edge server as the Local Media Relay:

ms-client-diagnostics: 51007;reason=”Callee media connectivity diagnosis info”;CalleeMediaDebug=”application-sharing:ICEWarn=0x0,LocalSite=101.x.x.x:14747,LocalMR=203.x.x.x:59191,RemoteSite=10.30.3.146:7500,RemoteMR=203.x.x.x:54525,PortRange=1025:65000,LocalMRTCPPort=59191,RemoteMRTCPPort=54525,LocalLocation=1,RemoteLocation=2,FederationType=0″

This led me down a long path of firewall troubleshooting where I experimented with opening the high TCP and UDP ports I could see being blocked by TMG but it did not help. The firewall being the problem was reinforced by finding this article Application Layer Firewall Blocks Lync Application Sharing but in the end the firewall had nothing to do with the problem. I then started digging in Snooper into the ‘Traces’ tab of the client .uccapilog and found a line stating:

‘ResolveHostName – Name resolution for avconf.domain.com failed’

I had first tested nslookup, ping and telnet to the Edge from my laptop and I knew that desktop sharing and audio/video federation worked. I had not tested it from the remote client as I knew it was setup correctly. When testing from the remote DirectAccess client (with DirectAccess disabled), I could telnet to the IP address on port 443 and could get nslookup to resolve the name to IP, but if I tried to ping the name it would not resolve similar to this screenshot:

Lync_DirectAccess_1

It says it cannot find the host, but DNS resolution is working so it should be able to find the host. This was something I hadn’t seen before and pointed at configuration specific to the remote DirectAccess client I was testing with. I added the AV Edge name and IP into the hosts file – desktop sharing and audio/video worked straight away. This led me to an old OCS and DirectAccess configuration guide Split-Brain DNS: Configuring DirectAccess for Office Communications Server (OCS) which matched the client’s split DNS configuration.

The Solution

The article above talks about the DNS Name Resolution Policy Table (NRPT). Running ‘netsh namespace show policy’ showed that entries existed for sip.domain.com, sipinternal.domain.com, _sipinternaltls._tcp.domain.com etc. as mentioned in that OCS article. However the Web Conferencing Edge and AV Edge names were not on the list.

In the DirectAccess configuration under ‘Step 3 Infrastructure Servers’:

Lync_DirectAccess_2

We added webconf.domain.com and avconf.domain.com to the DNS Server Addresses list with a blank DNS server entry similar to the screenshot below. This forces the client to use the DNS of its primary connection, not internal DNS for these specific entries.

Lync_DirectAccess_3

After applying the configuration and updating the GPO, I run ‘gpupdate /force’ on the client (over the internet) which updated the NRPT table similar to this where the ‘DirectAccess (DNS Servers)’ is blank and ‘DirectAccess (Proxy Settings)’ is set to ‘Use default browser settings’:

Lync_DirectAccess_4

After that the client can resolve the IP for avconf.domain.com and application sharing as well as audio and video worked while using DirectAccess. The problem was that the client had the same namespace internally and externally and the default DirectAccess configuration forces all .domain.com resolution to the internal DNS servers. Even when DirectAccess is not connected the NRPT configuration remains active. This is why even though it could resolve the address using the public DNS of the mobile hotspot, when it came to actually using the DNS entry for Lync, ping or telnet the DirectAccess NRPT configuration overrides it.

A simple solution, but not obvious when troubleshooting with DirectAccess turned off. The takeaway from this is that all Lync external namespaces should be added to the DirectAccess NRPT bypass list. That means all of the Lync Edge interfaces as well as the Reverse Proxy entries for external web services, meet, dialin, lyncdiscover (for the Windows Store Lync clients) and Office Web Apps Server.

WAP Exchange and Lync Applications

PowerShell Deployment of Web Application Proxy and ADFS in Under 10 Minutes

===========================================================================
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
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

Setup-ADFSserver2.ps1

Usage: .\Setup-ADFSserver2.ps1

$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

Setup-WAPserver1.ps1

Usage:

  • .\Setup-WAPserver1.ps1
  • .\Setup-WAPserver1.ps1 –OnlineRequest
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

Setup-WAPserver2.ps1

Usage: .\Setup-WAPserver2.ps1

$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

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

WAP applications

Publish Lync 2013 with 2012 R2 Preview Web Application Proxy

I discussed the new Windows 2012 R2 Preview Web Application Proxy (WAP) remote access role in a previous post Windows 2012 R2 Preview Web Application Proxy – Exchange 2013 Publishing Tests. I showed how to publish Exchange 2013 (except for Outlook Anywhere which isn’t working) and a claims based application.

In this post I am going to cover:

Publishing Lync Applications

Lync has a few different namespaces that need to be published:

  • Lync External Web Services (which includes the Lync Web App and Lync Scheduler)
  • Lync meeting join
  • Lync dialin page
  • Lyncdiscover for client autodiscover
  • Office Web Apps Server for PowerPoint sharing

Lync applications cannot use Preauthentication and have to use Pass-through which lets the backend server provide authentication. Setup the Web Application Proxy by installing the Remote Access Web Application Proxy role service as mentioned in the previous post. Install the certificate and run the configuration wizard to setup the server as an ADFS proxy.

Note that there is a dependency on a Windows 2012 R2 Preview ADFS farm. Even though we are not using Preauthentication, WAP cannot be setup without being configured as an ADFS Proxy.

The Lync applications can be published manually like this – note the port 4443 on the Backend server URL:

Or using a script:

$domain = "marc.kloud.com.au"
$webServices = "lyncws."
$certificate = "F7F6B300FE4D569FD598E1C9722571CF9AD780DD"

Add-WebApplicationProxyApplication -Name 'Lync Web Services' -ExternalPreAuthentication PassThrough -ExternalUrl "https://$webServices$domain/" -BackendServerUrl ("https://"+$webServices+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Lyncdiscover' -ExternalPreAuthentication PassThrough -ExternalUrl "https://lyncdiscover.$domain/" -BackendServerUrl ("https://lyncdiscover."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Dialin' -ExternalPreAuthentication PassThrough -ExternalUrl "https://dialin.$domain/" -BackendServerUrl ("https://dialin."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Lync Meet' -ExternalPreAuthentication PassThrough -ExternalUrl "https://meet.$domain/" -BackendServerUrl ("https://meet."+$domain+":4443/") -ExternalCertificateThumbprint $certificate
Add-WebApplicationProxyApplication -Name 'Office Web Apps Server' -ExternalPreAuthentication PassThrough -ExternalUrl 'https://owas.marc.kloud.com.au/' -BackendServerUrl 'https://owas.marc.kloud.com.au/' -ExternalCertificateThumbprint $certificate

Important Configuration

There are three main things to be aware of when publishing Lync and Office Web Apps Server:

  • The Office Web Apps Server published application must have the same host name for ‘ExternalURL’ and ‘BackendServerURL’
  • The Lync 2013 mobile clients do not support Server Name Indication (SNI)
  • As we are publishing a full domain, not a sub path, you cannot use the same backend host name. So make sure that the backend servers have certificates for all the public names too

Office Web Apps Server URLs

Web Application Proxy supports header rewriting/translation in order to publish different internal names to the external names. It does not yet support body rewriting which is what I think the warning at the top of the diagram above is all about. It links to http://technet.microsoft.com/library/cc732148(WS.10).aspx but that content does not exist yet. I initially thought the problem may be the Office Web Apps Farm InternalURL configuration which had the internal server name shown below:

But that was not an issue. If I set the OWAS publishing rule on the WAP server with different external and internal URLs:

The client gets a referral to the Office Web Apps Server external URL https://owas.marc.kloud.com.au/m/ but the response body contains some X-Headers and javascript URLs for the backend URL of https://marc-owas1.marc.kloud.com.au/m/ URLs as you can see on the right hand side in the diagram below (click for a larger version). The client then tries to open a connection to the internal hostname which does not exist in DNS.

WAP OWAS Fiddler 2

In order to make this work, the Web Application Proxy publishing rule for Office Web Apps Server must have the same ‘ExternalURL’ and ‘BackendServerURL’.

Lync mobile clients do not support SNI

Server Name Indication (SNI) is a feature that is supported by just about every client browser (but not at all on Windows XP), but has only been supported by Microsoft web servers since IIS 8 in Windows 2012. SNI allows multiple certificates to be used on a single IP and port combination similar to the way host headers work with HTTP. With HTTPS traffic, the client needs to perform the certificate handshake before it sends the host header, so the server does not know which site is being requested and therefore which certificate to present. Lync 2013 and the Lync Windows Store App support SNI (I think because they use Internet Explorer underneath) however the Lync mobile clients do not.

Update 19 July 2013: After additional testing it was confirmed that Windows Phone 8 Lync Mobile 2013 and ActiveSync clients do support SNI, so the following problem will not be seen if you only use Windows Phone 8. iOS has supported SNI since version 4 and Android since version 3 (Honeycomb) and you can test this in a browser by going to an SNI test site like https://alice.sni.velox.ch/ – however the Lync and ActiveSync clients do not provide the SNI extension when connecting on either mobile OS (iOS does send the SNI name for autodiscover but not when it connects to the Microsoft-Server-Activesync URI).

The Problem

As I was testing this on a lab I did not have public DNS or a public certificate. I used the Lync 2013 client on my Android phone, modified the hosts file and imported the root certificate. The Lync mobile client would not connect and I did not see any traffic hitting the WAP application in the WAP performance counters. When viewing the client side logs I saw this:

HttpConnection: javax.net.ssl.SSLException: SSL handshake aborted: ssl=0x5886b8e0: I/O error during system call, Connection reset by peer

I installed Wireshark to see what was happening on the WAP server and what I saw pointed me at the problem:

Working: A web browser sends the ‘server_name’ extension containing the host name as can be seen below.

Not working: The Lync mobile client does not send the ‘server_name’ extension (neither does the Exchange ActiveSync client) as can be seen below:

The problem is that the client does not specify a server name and WAP does not specify a default certificate. I can’t change the client function (although I hope Microsoft will add SNI support soon), so the only option is to change the server.

The Solution

Finding the solution took a lot of searching. This TechNet blog Server Name Indication (SNI) with IIS 8 (Windows Server 2012) pointed me in the right direction when talking about how IIS 8 has a way to add a legacy SSL binding to support non-SNI compliant clients. Web Application Proxy however is not based on IIS. That blog mentioned using netsh to view the HTTP SSL bindings:


netsh http show sslcert

This lists all bindings including Remote PowerShell and ADFS

As well as the Web Application Proxy published applications

The MSDN article How to: Configure a Port with an SSL Certificate showed how to add a new binding. The trick is to add an IP:port binding in addition to the Hostname:port which acts as a legacy non-SNI binding. As all of the WAP applications have the same certificate and Application ID, I reused those and created the new binding:


netsh http add sslcert ipport=0.0.0.0:443 certhash=f7f6b300fe4d569fd598e1c9722571cf9ad780dd appid={f955c070-e044-456c-ac00-e9e4275b3f04}

This assumes that all names are on the single certificate as it sets the default binding on all IP addresses. If you had multiple certificates you would need multiple IP addresses and set a binding for each as you would have in TMG or UAG. This really reduces the effectiveness of SNI, so in order to not complicate the configuration or waste IPv4 addresses the clients need to be updated.

Bonus Feature

As mentioned by my colleague in An Overview of Server Name Indication (SNI) and Creating an IIS SNI Web SSL Binding Using PowerShell in Windows Server 2012, it should be possible to script and automate all of our configuration. I thought it would be a quick process to automatically create the legacy binding, but had to use good old PowerShell text parsing to get it working. This is not the most elegant script, but I am a self-taught scripter and this works for me. It does the following:

  • takes the netsh output and pulls out the IP:port or Hostname:port as well as the Certificate Hash and Application ID
  • removes the split and all spaces
  • creates an object for each existing binding
  • matches the existing WAP binding that has the correct certificate
  • creates the netsh command for creating the binding
  • runs the netsh command (netsh creation cannot be run directly in PowerShell which is why the command is piped to netsh)
  • shows the new binding information
$hostName = "mail.marc.kloud.com.au" # Name of existing hostname to match
$IPport = "0.0.0.0:443" # IP and port to bind to. 0.0.0.0:443 matches all

$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 $hostName}
$CertificateHash = $match.CertificateHash
$ApplicationID = $match.ApplicationID

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

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

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

I save this as ‘Set-DefaultCertificate.ps1’ and the result of running the script is shown below.

Lync on Azure

I originally setup my lab on Azure as it was the fastest way for me to get up and running with Server 2012 R2 Preview. I then wanted to test publishing Lync, so installed a Lync Standard Edition server. Note that Lync is not listed as supported software on Azure Microsoft server software support for Windows Azure Virtual Machines. Lync Front End servers can run on Azure but Lync Edge servers cannot because they need two distinct network interfaces – one for internal communication and one for external. Azure does not support more than one network interface and also does not support adding additional IP addresses to a network interface, so there is no way to install a Lync Edge server.

I had setup Lync 2013 before in Azure, but this time I hit an error installing the SQL databases as part of step 2 of the ‘Install or Update Lync Server System’ in the Lync Deployment Wizard where it tries to create and attach the rtcxds database:

InstallDatabaseInternalFailure: An internal error has occurred while trying to create or update the database

Error: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding

InstallDatabaseInternalFailure: An error occurred while applying SQL script for the feature BackendStore

I could see the rtcxds.mdf and rtcxds.ldf files being created, but after 10 minutes it would fail with a timeout error and the databases would be removed. I initially thought it was an environmental issue as I had tried to use Pat Richard’s Set-Cs2013Features script from http://www.ehloworld.com/1697 which preinstalls the SQL Express instances and patches. So I installed another Windows 2012 server and just let Lync do the SQL install – same problem. I went back to a Windows 2008 R2 server and still had the same problem. Sorry to doubt your script Pat J

I tried a whole lot of things to do with the NT AUTHORITY\NETWORK SERVICE as there were some errors regarding that, but eventually I discovered the reason based on the last comment on this TechNet support forum post http://social.technet.microsoft.com/Forums/lync/en-US/f8d960d3-eaea-442a-8a03-da8b38ca7e1a/backendstore-database-creation-fails-lync-2013

Azure Operating System disks have read/write host cache enabled – primarily aimed at helping read performance. This means that a write is cached and not written to disk immediately. When using Active Directory Domain Controllers on Azure Placement of the Windows Server AD DS database and SYSVOL states:

Unlike Operating System disk drives, data disk drives do not cache writes by default. Data disk drives that are attached to a VM use write-through caching. Write-through caching makes sure the write is committed to durable Windows Azure Storage before the transaction is complete from the perspective of the VM’s operating system. It provides durability, at the expense of slightly slower writes.

SQL has a similar requirement for write-through caching. The rtcxds database and log files are each created as 4 GB and it seems that creating the 8 GB of rtcxds files puts too much pressure on the write cached network storage.

So the solution is to attach a new empty disk with at least 10 GB (this is suitable for a test) to the Azure VM with no read or write cache

Initialise the disk, format it and give it a drive letter. Then install the databases from PowerShell, specifying the data disk:


Install-CsDatabase -LocalDatabases -DatabasePaths F:\CsData

Run step 2 from ‘Install or Update Lync Server System’ in the Lync Deployment Wizard again and it succeeds.

Summary

2012 R2 Preview Web Application Proxy fully supports publishing Lync 2013 and Office Web Apps Server 2013 as long as the default binding is set and the OWAS publishing rule internal and external URLs are the same. The following was tested successfully:

  • Meeting join
  • Dialin page
  • Lync Web App
  • Lync Web Scheduler
  • Lyncdiscover
  • Lync 2013 mobile client
  • Lync Windows Store App (Lync MX)
  • Office Web Apps upload and presentation
  • Lync 2013 client Address book web search (tested with an Edge server outside of Azure)
  • Lync 2013 client Address book group expansion (tested with an Edge server outside of Azure)

The Web Application Proxy looks very promising as a Reverse Proxy for the primary Microsoft applications I work with. SharePoint has not been tested yet, but that should work in either ADFS Preauthentication or Pass-through mode as long as the URLs match all the way through. I am looking forward to hopefully not having to use UAG again.

Skype Integration with Lync Server and Lync Online

Skype integration with Lync has been teased since the first public beta preview of Lync 2013 in July 2012. Microsoft mentioned it in Lync 2013 Preview TechNet documentation (no longer available) and in a blog post by the Lync team UPDATE: Skype and so much more: Why we’re so excited about the new Lync which implied it would be available at launch. Lync 2013 became publicly available without Skype integration which was a bit disappointing after being talked about as one of the many features. Timeframes for Lync to Skype integration were confirmed during the inaugural Lync Conference in February 2013:

  • June 2013 for IM (Instant Messaging), presence and voice
  • sometime before September 2014 for video

Skype to Lync federation was available in a limited form around November 2012 with Skype 6.0 for Windows Live Messenger users who merged their Skype and Microsoft Accounts (previously Windows Live ID) but it did not allow for Lync contacts to be added to Skype after the migration Lync Federation will continue as Windows Live Messenger transitions to Skype.

Lync and Skype integration has been quietly available since at least March when Matt Landis posted about getting it working. I tested IMP and voice after reading Matt’s post and it was functional but presence seemed a bit flaky and adding contacts was error prone. No changes had to be made to Lync 2013 server to get it working.

Official Release

IM, presence and voice integration was officially announced in May 2013 on the Lync Team blog Lync-Skype connectivity available today and the Skype blog Skype and Lync: Connecting the Living Room to the Board Room. This was a bit earlier than previously announced which was a nice surprise. Hopefully video support comes sooner too.

Skype Configuration

For this integration to work, Skype users need to use the latest Skype client (6.3 on Windows) and it is only available on the Windows desktop and Mac clients for now with support coming soon for other devices. The second requirement is to sign into Skype with a Microsoft Account. Skype accounts can be merged with a Microsoft Account so that all contacts are brought across. Contacts can be added in Skype using the SIP address of the Lync user which is typically the same as their email address.

Lync Server On-Premise Configuration

If Lync is already configured for federation and PIC (Public IM Connectivity) and the user’s Lync ‘External Access Policy’ allows ‘Public Provider Access’, there should not be anything else that needs to be done as Skype federation is using the underlying Windows Messenger PIC infrastructure. Lync users can enter the Microsoft Account SIP address in the ‘Find someone…’ box and add them to the contacts list. You can see presence below with Skype showing that the contact is a Lync contact, and Lync showing the Skype user as Available and on Skype.

Lync 2013 administrators can make the adding of contacts slightly easier by replacing the old MSN PIC provider in the Lync Management Shell and adding some new settings. Run the following commands specified in the Provisioning Guide for Lync-Skype Connectivity guide. Note that the guide states ‘Messenger’ as the identity, but typically this is called ‘MSN’.

Remove-CsPublicProvider -Identity MSN
New-CsPublicProvider -Identity Skype -ProxyFqdn federation.messenger.msn.com -IconUrl "https://images.edge.messenger.live.com/Messenger_16x16.png" -VerificationLevel 2 -Enabled 1

You should end up with an output of ‘Get-CsPublicProvider’ similar to the screenshot below.

After signing out of Lync and signing in again, this gives the option to ‘Add a Contact Not in My Organization’ with the Skype logo as shown below.

If federation or PIC are not already configured, they will need to be done as per standard Lync federation and PIC configuration. The provisioning guide has more information about how to set this up.

Lync Online Configuration

Lync Online Enterprise subscribers who have External Access (Federation) and Public IM enabled do not have to make any changes. Lync Online Small Business subscribers only have one option – to turn ‘External Communication’ on or off. It seems that this button did different things depending on when you signed up for Office 365, or what it was set to when you migrated from Office 365 Wave 14 (2010 versions) to Wave 15 (2013 versions). The provisioning guide mentions the problem briefly and this Office 365 blog goes into a lot more detail Small Business admins: Action may be required to turn on Lync-Skype connectivity

If ‘External Communication’ is enabled but Lync to Skype federation is not working – go into the Office 365 ‘Lync Online Control Panel’ or ‘Lync Admin Center’ depending on your version, turn off ‘External Communication’ and turn it on again. That will enable federation and PIC.

A troubleshooting guide for Office 365 has been published Troubleshooting Lync-Skype Connectivity which covers toggling the ‘External Communication’ setting as well as some client side fixes. One to note is that there is a known issue where sometimes a Lync call to Skype fails and this is due to be fixed in a Lync 2013 Cumulative Update.

A few things to note

Non Standard Microsoft Accounts

One potential gotcha is mentioned in the provisioning guide. If a Skype user has a custom domain for their Microsoft Account that is not the typical @hotmail.com, @outlook.com or @live.com (or is on this list of standard domains http://support.microsoft.com/kb/897567), when adding the contact to Lync you need to make a few changes. Using the example in the provisioning guide, if the Microsoft Account is ‘bob@contoso.com’ the account must be added as ‘bob(contoso.com)@msn.com’ which is going to confuse a lot of people.

Presence States

Some presence states don’t match up between Lync and Skype. If a Skype user sets their presence to ‘Do not disturb’ they receive a notification:

However this sets the presence shown in Lync to ‘Busy’. Lync can still send IM messages which appear as notifications in Skype.

The second disconnect is that a Lync user setting presence to ‘Busy’ shows up as ‘Do not disturb’ in Skype as you can see below.

This does not stop a Skype from initiating an IM conversation. If a Lync client sets their presence to ‘Do Not Disturb’, the experience is as usual for Lync and no notifications will appear.

There is also a known issue where if a Skype user adds a Lync contact, they often can’t see the presence of the Lync user for up to 5 minutes. As stated in the troubleshooting guide, wait 5 minutes or sign out of Skype and sign in again.

Single Client

One of the imagined benefits of this Lync and Skype integration is that you should not have to be signed into both clients. Ignoring the lack of video support for a moment…

If you use Lync at work then you have the client available to you on just about any device you own. So you can just start using Lync and do away with the Skype client right? Your first step is to try and add all of your Skype contacts to Lync but you realise you can’t see their Microsoft Account address. Even exporting the Skype contacts only shows the ‘old’ Skype user name. I cannot see any way to find this address, so the workarounds seem to be either asking your contacts for their Microsoft Account address or requesting all of your Skype contacts to add your Lync account to their contacts.

The Future of Lync and Skype

It will be interesting to see how this integration plays out going forward. Video will round out the functionality in the future and some people are calling for Lync to go away and be replaced by Skype completely. I think that the separation of Lync as the corporate tool and Skype as the consumer tool is an advantage. I like having the ability to keep work and personal life separate, unless I choose to add a contact that crosses the divide.

A lot of Lync organisations do not want their users to have Public IM access and some block federation for particular users. Lync can be used as a corporate only system for some users, while offering everything Skype does along with corporate managed PSTN breakout for Enterprise Voice and rich conferencing for other users.

These work / personal dividing lines are getting more and more blurred every day.