SharePoint 2013 or 2016 and ADFS

Ever wondered how to configure SharePoint to use ADFS for user authentication? Googled it and found it confusing? Me too! Don’t despare though… the Powershell is pretty straightforward and it only gets easier the more often you do it…

Export ADFS Signing Certificate

First of all log in to the ADFS server and export the signing certificate.  The following powershell should be ran as administrator and will export the certificate to c:\ADFSSigning.cer.


$certBytes=(Get-AdfsCertificate -CertificateType Token-Signing)[0].Certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)

[System.IO.File]::WriteAllBytes("c:\ADFSSigning.cer", $certBytes)

If your ADFS signing certificate was issued by a certificate authority and not self-signed by ADFS, you must ensure the entire certificate chain is trusted by SharePoint as well. I won’t cover this process here, but you can refer to another post on the topic here.

Add ADFS Relying Party Trust

While you are on your ADFS server, you may as well create the relying party trust in, you guessed it, powershell. But first you need to make a txt file with the following contents. For ease, lets say c:\rules.txt. These are the transformation rules for the relying party. I find this is all that is really required to start with as User Profile Sync will grab the rest.


@RuleTemplate = "PassThroughClaims"
@RuleName = "SharePoint Attributes"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"), query = ";mail,mail,sn,givenName;{0}", param = c.Value);

Then edit the variables in the powershell below and execute it. Here a quick explanation of the variables.
$rules – The path to the rules.txt files you have just created.
$name – The name of the relying party trust.
$urn – If you don’t know what this is, just leave it.
$webapp – The URL for the first web application you are going to use. I’ll show you how to add another application later. Don’t put a trailing slash on the URL.

$rules = "c:\rules.txt"
$name = "SharePoint Site 1"
$urn = "urn:sharepoint:site1"
$webapp = "https://site1.domain.local"
$endpoint = $webapp + "/_trust/"
[string[]] $urnCollection = $urn, $webapp

Add-AdfsRelyingPartyTrust -Name $name -ProtocolProfile WSFederation -WSFedEndpoint $endpoint -Identifier $urnCollection -IssuanceTransformRulesFile $rules

You can now go and check in the ADFS console and yor new trust should be listed under Relying Party Trusts.

Add Token Signing Certificate to SharePoint

Log in to the SharePoint server that hosts central admin and copy the ADFSSigning.cer file to the C drive then open the SharePoint Management Shell as administrator. The following powershell will import the certificate so that SharePoint trusts it.


$path = “C:\ADFSSigning.cer”
$root = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($path)
New-SPTrustedRootAuthority -Name "ADFS Token Signing Cert" -Certificate $cert
//Keep this window open for the next step

Again, of your ADFS signing certificate was issued by a Certificate Authority instead of being self-signed by ADFS, you must make sure SharePoint trusts all other certificates in the chain.

Create The Authentication Provider In SharePoint

To add ADFS as a Authentication Provider to SharePoint, use the following powershell in the same windows that you imported the certificate in:

//Map the email address, UPN, Group Memberships and SID from ADFS
$emailClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" -IncomingClaimTypeDisplayName "EmailAddress" -SameAsIncoming
$upnClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" -IncomingClaimTypeDisplayName "UPN" -SameAsIncoming
$roleClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" -IncomingClaimTypeDisplayName "Role" -SameAsIncoming
$roleClaimMap = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" -IncomingClaimTypeDisplayName "Role" -SameAsIncoming

//Update the following to match the details entered earlier if you changed them
$realm = "urn:sharepoint:site1"
$signInURL = "https://adfs.domain.local/adfs/ls"
$ap = New-SPTrustedIdentityTokenIssuer -Name ADFS -Description ADFS -realm $realm -ImportTrustCertificate $cert -ClaimsMappings $emailClaimMap,$upnClaimMap,$roleClaimMap,$sidClaimMap -SignInUrl $signInURL -IdentifierClaim $emailClaimmap.InputClaimType

Providing you don’t get any error, SharePoint should now be able to use ADFS as a authentication provider.

Change SharePoint Application to Claims Based

Now you need to make one of your SharePoint web applications use ADFS for authentication. There are a number of caveats with this though:

  • You will lose access to the web application as your permission is set on the account SharePoint knows of from windows authentication. I’ll show you how to fix this soon.
  • Your user profile will not be associated with your account any more. This is again because the User Profile Service has synced your profile with Active Directory using your windows account.
  • Search will be unable to crawl the SharePoint site as it doesn’t support claims based authentication.

I’ll show you how to fix these things in a future post, otherwise this post will end up being a monster.

For now, I’d recommend performing these steps on a newly created SharePoint Web Application of your choosing. The commands assume a site called https://site1.domain.local.

Open up the SharePoint management shell as administrator and run the following commands:

$webApp = Get-SPWebApplication -Identity "https://site1.domain.local"
$sts = Get-SPTrustedIdentityTokenIssuer "ADFS"
Set-SPWebApplication -Identity $webApp -AuthenticationProvider $sts -Zone "Default"

If you now try to access the site you should be redirected to your ADFS sign-in page. Once you login, you will probably get the “This site hasn’t been shared with you” message. Keep reading for a fix.

Changing More SharePoint Applications to Claims Based

The above powershell will certainly allow you to change another web application across to claims based authentication, but with one little issue. Upon doing so, every time you try to access the second web application you will end up back at the first one after you login. This is because the realm of the second web app is different, and ADFS will just send you straight to the first site configured. Luckily thought there is no need to mess on with certificates for the second site.

On the SharePoint server, open the SharePoint Management Shell as admin and use the following commands to add another realm to the authentication provider:


$urn = "urn:sharepoint:site2"

$ap = Get-SPTrustedIdentityTokenIssuer
$uri = new-object System.Uri("https://site2.domain.local")
$ap.ProviderRealms.Add($uri, $urn)
$ap.Update()

Then you need to add another Relying Party Trust in ADFS to handle requests for the second SharePoint site. To do this, follow the steps in the section of this post Add ADFS Relying Party Trust, but remember to update the strings in the powershell commands to represent your second site.

Regain Access to Site With Claims Based Authentication

Now that your site(s) are claims based authentication enabled, you need to re-add yourself as a site collection administrator. The following powershell will set your ADFS formatted account as the secondary site collection owner. You can also do this in central admin but I won’t go into that route in this post.


Set-SPSite –Identity "https://site1.domain.local" –SecondaryOwnerAlias "emailaddress@domain.local"

Updating the Signing Certificate in SharePoint

For whatever reason Microsoft hasn’t given, SharePoint can’t use the Federation Metadata issued by ADFS to update the Signing Certificate when it is renewed at the end of its validity period, leaving it up to the administrator to do this manually.

The process of updating the certificate isn’t particularly complex. It’s basically export the new certificate, then install it and import it on the SharePoint server before updating the Trusted Token Issuer in SharePoint to use the new certificate. The problem is though that if you forget to update the certificate in the brief period between the ADFS server renewing it’s certificate and the old certificate expiring, nobody will be able to login to SharePoint. And trust me, you WILL forget at least once.

Fortunately, Jesus Fernandez has a solution over on MSDN in the form of a powershell script that can be scheduled to run on the SharePoint server. The script reads the afor mentioned Federation Metadata from ADFS and downloads the current token signing certificate. If it is different to the one SharePoint is using, it adds it to SharePoint and updates the Token Issuer in SharePoint to use the new certificate. Nifty huh? I’d strongly recommend this as a solid option.

Workflow Manager Configuration Error

I recently attempted to configure Workflow Manager 1.0 and Service Bus 1.0 for use by SharePoint 2016, using a certificate issued by our domain CA instead of self-generated certificates. I ran into the following error though.

System.Management.Automation.CmdletInvocationException: Could not successfully send message to scope ‘/WF_Management’ despite multiple retires over a timespan of 00:02:07.8300000.. The exception of the last retry is: A recoverable error occurred while interacting with Service Bus. Recreate the communication objects and retry the operation. For more details, see the inner exception..  —> System.TimeoutException: Could not successfully send message to scope ‘/WF_Management’ despite multiple retries over a timespan of 00:02:07.8300000.. The exception of the last retry is: A recoverable error occurred while interacting with Service Bus. Recreate the communication objects and retry the operation. For more details, see the inner exception..  —> System.OperationCanceledException: A recoverable error occurred while interacting with Service Bus. Recreate the communication objects and retry the operation. For more details, see the inner exception. —> Microsoft.ServiceBus.Messaging.MessagingCommunicationException: Identity check failed for outgoing message. The expected DNS identity of the remote endpoint was ‘WFMServer1.contoso.com’ but the remote endpoint provided DNS claim ‘WFMServer3.contoso.com’. If this is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity ‘WFMServer3.contoso.com’ as the Identity property of EndpointAddress when creating channel proxy.  —> System.ServiceModel.Security.MessageSecurityException: Identity check failed for outgoing message. The expected DNS identity of the remote endpoint was ‘WFMServer1.contoso.com’ but the remote endpoint provided DNS claim ‘WFMServer3.contoso.com’. If this is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity ‘WFMServer3.contoso.com’ as the Identity property of EndpointAddress when creating channel proxy.

To cut a long troubleshooting story short, the problem was with the certificate I had requested from the CA. More specifically the DNS extension. I had the following DNS entries in the certificate.

  1. *.contoso.com
  2. WFMServer1.contoso.com
  3. WFMServer2.contoso.com
  4. WFMServer3.contoso.com

For what ever reason, WFM only seems to look at the final DNS entry when trying to add the host to the WFM farm. To confirm this, I tried the installation from all three hosts and it worked fine on WFMServer3.contoso.com, but not the other 2.

I’m still not entirely sure if it was WFM or SB that was causing this issue, but I fixed it by simply revoking the certificate on our CA and re-installing SB and WFM using a certificate with wfm.contoso.com as the Common Name and DNS entries in the following order:

  1. WFMServer1.contoso.com
  2. WFMServer2.contoso.com
  3. WFMServer3.contoso.com
  4. *.contoso.com

 

Making SharePoint Trust Enterprise CAs

If you have an enterprise PKI, or even just a single CA, that issues certificates to services such as ADFS or Workflow Manager that you intend to use with your SharePoint Farm, it might be worth while importing you Root and Intermediate CA Certificates into SharePoint to make it trust the other services.

Currently we have a PKI setup similar to the following:

-Enterprise Root CA (Offline)
—Enterprise Intermediate CA (Offline)
——Web Services CA (Online)
——Authentication CA (Online)

The Root and the Intermediate CA are offline and secured except for planned maintenance, such as publishing new certificate revocation lists. The Web Services CA is the CA that issues the certificates we use for web services like Workflow Manager, and the Authentication CA issues the ADFS signing Certificate, which also needs to be trusted by SharePoint.

First of all, you need to grab a copy of the certificate of each CA in the Chain. If you have your PKI configured correctly, they should be readily available so I won’t go into that process. If you are exporting them from the CA servers, DO NOT export private keys, SharePoint doesn’t need them and that would be a major security concern. Once you have them, copy them to a directory on your SharePoint server(s).

Then, make sure the certificates are in the trusted root certificate authorities on every server in your farm. the easiest was to do this is to publish the certificates using group policy, but you can do this manually if you like. Simply follow these steps on each server in the farm:

  1. Double click the first certificate.
    cert_overview.jpeg
  2. Click Install.
  3. Select “Local Machine” and click Next.
  4. Click Yes on AUC prompt.
  5. Select “Place all certificates in the following store”.
  6. Click Browse and select “Trusted Root Certificate Authorities” or “Intermediate Certificate Authorities” depending on if the certificate is at the root of the chain.
  7. Click Next.
  8. Click Finish.
  9. Repeat for each certificate in the chain.

On your SharePoint server execute the following powershell commands for each certificate, changing “C:\RootCA.cer” to the path of each certificate, and “Enterprise Root CA” to a friendly name for each CA.


$path = “C:\RootCA.cer”
$root = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($path)
New-SPTrustedRootAuthority -Name "Enterprise Root CA" -Certificate $cert

If you don’t like powershell or are unable to access powershell for whatever reason, you can also install the certificates from Central Admin.:

  1. Navigate to Central Admin -> Security -> Manage Trust.
  2. Click “New”.sharepoint_ca_add_trust.jpeg
  3. Add a friendly Name for the certificate.
  4. Click Browse and select your certificate.
  5. Click OK.
  6. Repeat for each certificate in your PKI chain.

 

C# WinFroms Splash Screen

One fo the first challenges I faced as a C# developer writing windows forms applications was the concept of a splash screen with a progress bar. While in principle it seemed simple, opening a different form in a separate thread and making its controls updatable from the first thread was alien to me.

I decided to (finally) write this post after seeing a lot of requests for help around this subject on various forums. The main thing new developers struggle with when attempting their very first splash screen are delegates.

To start with, either create a blank Windows Forms Application in Visual Studio or follow the steps below in the project you are adding a splash screen to. If you are creating a new project, you will find Form1.cs, which we will leave as is for this tutorial. Imagine that Form1 is going to be the main window of your application.

Now go and add a new Windows Form to your project and call is SplashScreen.cs. Add a Progress Bar control to your new form and resize the form around it. Now change the FormBorderStyle property for the form to None and StartPosition to CenterScreen. I’ve also changed the BAckColor property to red to make it a bit more visible for this tutorial.

SplashForm.jpeg

Now head into the code view for SplashScreen.cs (right click it in the solution explorer windows and click View Code).

The code for the splash screen is reasonably straight forward if you know how delegates and threads work. Check out the comments for some information on what each section does.

using System;
using System.Threading;
using System.Windows.Forms;

namespace SplashScreenTutorial
{
public partial class SplashScreen : Form
{
//The delegates required to invoke methods from a different thread
private delegate void CloseDelegate();
private delegate void UpdateDelegate(string txt);

private SplashScreen _splashInstance;

//The initializer for the SplashScreen Class
public SplashScreen(int max)
{
InitializeComponent();
progressBar1.Minimum = 0;
progressBar1.Maximum = max;
}

//Private method used to display the splash creen form.
private void ShowForm(object max)
{
_splashInstance = new SplashScreen(Convert.ToInt32(max));
Application.Run(_splashInstance);
}

//Public method that spawns a new thread and calls the ShowForm() method to lauch the splash screen in the new thread.
public void ShowSplashScreen(int max)
{
if (_splashInstance != null)
return;
Thread thread = new Thread(ShowForm);
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start(max);
}

//Public bool to check if the splash screen is ready.
public bool Ready()
{
return (_splashInstance != null) && (_splashInstance.Created);
}

//Public method used to update the progress bar progress and text from your main application thread.
public void UpdateProgress(string txt)
{
_splashInstance.Invoke(new UpdateDelegate(UpdateProgressInternal), txt);

}

//Public method used to close the splash screen from your main application thread.
public void CloseForm()
{
_splashInstance.Invoke(new CloseDelegate(CloseFormInternal));
}

//The private method invoked by the delegate to update the progress bar.
private void UpdateProgressInternal(string txt)
{
_splashInstance.progressBar1.Value++;
_splashInstance.progressBar1.Text = txt;
}

//The private method invoked by the delegate to close the splash screen.
private void CloseFormInternal()
{
_splashInstance.Close();
}

}
}

Next you need to add a bit of code to your Program.cs file to call the splash screen and update it between each task during your application startup process. Make sure it goes before Application.Run(Form1).


static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

//Instantiate a new instance of SplashScreen
SplashScreen splash = new SplashScreen(4);
//Display the splash screen with a max value for the progress bar
splash.ShowSplashScreen(4);

//Wait for the form to be loaded.
while (!splash.Ready())
{
//Loop to wait for the splash screen to be ready before trying to
update it. We will get a null reference exception if the splash screen isn't ready yet.
}

//Update the progress bar
splash.UpdateProgress("Loading Step 1");
//Perform action
Thread.Sleep(1000); //Lets pretend this is doing something constructive
//Update the progress bar
splash.UpdateProgress("Loading Step 2");
//Perform action
Thread.Sleep(1000); //Lets pretend this is doing something constructive
//Update the progress bar
splash.UpdateProgress("Loading Step 3");
//Perform action
Thread.Sleep(1000); //Lets pretend this is doing something constructive
//Update the progress bar
splash.UpdateProgress("Loading Step 4");
//Perform action
Thread.Sleep(1000); //Lets pretend this is doing something constructive
//Close the Splash Screen
splash.CloseForm();

//Launch your main application form
Application.Run(new Form1());
}

That’s all there is to it. I’m not going to go into a load of detail about threads and delegates in this post. To be honest it’s mainly because I can’t be bothered because both subjects are tedious and boring.

And for all those who are still reading, the example project is available here for download.

Controlling the Startech VS421HDPIP with C#

As part of a project I have been working on to spec and install a video conferencing solution I purchased a couple of Startech VS421HDPIP units to switch between camera inputs. I chose this unit for a couple of reasons; it has enough inputs for the project, it’s capable of doing picture in picture, it can be controlled via telnet or RS232, our supplier could get it for half the RRP and it’s made by Startech, who’s kit has never left us in an awkward situation.

When I first hooked it up it just worked and the presets that were built-in were decent. Unfortunately it didn’t have presets to support the use cases I required, but I expected that from reading the manual prior to purchasing them. The presets can be changed from the web interface, which is the only downside to this unit… its utter pants. Fortunately it supports some pretty advanced commands via telnet so all is not lost. After playing with the telnet prompt for a while I figured that I could probably write some form of application to change the video output layout from a PC, which would certainly make it more user-friendly. The VC solution is going to be installed in a remote office and anything to make operation easier for the end users is a big plus. This will also allow me to have basically unlimited “presets” to cover more use cases without reconfiguration. Bonus.

Driver

I decided that the application should probably allow control of the PTZ cameras too to encompass the entire VC solution in one control panel, so a simple “driver” style API would be the best bet for controlling each component. The driver for the Startech VS421HDPIP only took two hours to write, including an application to test the class, and is incredibly simple to use. It is available to download at the end of this post. Here’s an example of how to use it.

//create a new instance of the VS421HDPIP class.
VS421HDPIP Conn = new VS421HDPIP();

//set the unit IP address and port
Conn.Ip = IPAddress.Parse(“192.168.1.6”;
Conn.Port = 23;

//open the connection to the unit.
Conn.Open();

//execute commands. See list of commands in the accompanying blog post.
Conn.PowerOn();  //Power on the unit from standby
Conn.Recall(VS421HDPIP.WindowState.Fav1);  //Recall the Fav.1 Preset
Conn.SetImagePosition(VS421HDPIP.InputChannel.Input2, 1420, 790); //Sets the possition of channel 2.

//always close the connection when done. The unit only allows one session at a time and not closing a session will result in you having to reboot the unit or waiting for the session timeout on the unit before opening a new connection.
Conn.Close();

Unfortunately the telnet prompt doesn’t let you retrieve information. Maybe this is something Startech will add to future models but it is what it is.

Commands

The commands I implemented are basically identical to the commands listed in the manual for the VS421HDPIP. I won’t go over available parameters for each here. The parameters are similar to those listed in the manual as well so I’m sure they’d be easy to figure out with intellisense.

  • PowerOn() – Power on the unit from standby.
  • PowerOff() – Place the unit in standby.
  • SetOutResolution(Resolution res) – Sets the output resolution.
  • OsdDisable() – Disable the On Screen Display.
  • OsdEnable() – Enable the On Screen Display.
  • OsdHOffset(int offset) – Sets the horizontal offset of the OSD.
  • OsdVOffset(int offset) – Sets the verticle offset of the OSD.
  • OsdTimeout(int timeout) – Sets the timeout of the OSD.
  • OsdGain(int gain) – Sets the gain of the OSD.
  • SetBrightness(InputChannel chan, int level) – Sets the brightness level of an input.
  • SetContrast(InputChannel chan, int level) – Sets the contrast level of an input.
  • SetSaturation(InputChannel chan, int level) – Sets the saturation level of an input.
  • SetHue(InputChannel chan, int level) – Set the hue level of an input.
  • Mute() – Mutes the audio output of the unit.
  • Unmute() – Unmutes the audio output of the unit.
  • SetImageSize(InputChannel chan, int h, int v) – Sets the size of a channel.
  • SetImagePosition(InputChannel chan, int h, int v) – Sets the position of a channel.
  • ChannelImageOn(InputChannel chan) – Turns a channel on.
  • ChannelImageOff(InputChannel chan) – Turns a channel off.
  • ChannelPriority(InputChannel chan, int priority) – Sets a channel’s priority.
  • SetChannelLabel(InputChannel chan, string label) – Sets a channel’s label.
  • StoreCurrentConfiguration(Favorite loc) – Stores current settings to a preset.
  • MirrorOn() – Makes the unit mirror it’s output (rear projection).
  • MirrorOff() – Disables the output mirror feature.
  • SetOutputRotation(Rotation value) – Rotate unit output (ceiling hung projector).
  • SetFadeDuration(Fadetime time) – Sets fadetime between windows.
  • SendTelnetCommand(string command) – Send a telnet command to the unit.

Download

I have provided the driver I wrote in case it is useful to anybody. I do so with absolutely no warranty or support and you use it at your own risk.

Compiled DLL
Visual Studio Project Files