This post describes how to force Windows Authentication when accessing a SharePoint 2013 Mixed-Mode web application via CSOM from within a PowerShell script.
Overview
I’ve been doing a lot of work with SharePoint Online Dedicated customers lately, and have to use CSOM wrapped in PowerShell to perform a number of site collection maintenance and administrative tasks (if you haven’t used CSOM + PowerShell, you should, and Chris O’Brien’s post is a great place to start). With SPO-D, customers can enable mixed-mode authentication and support partner access via forms auth, however this breaks the traditional method of authenticating using the ClientContext because SharePoint displays the authentication method dialog (windows, or forms). Steve Peschka wrote a great article on how to solve this in C# and in web services, and this post builds on that by providing an implementation for use within PowerShell.
The Challenge
Steve’s post uses an event handler to add an HTTP Header to the request and force Windows Authentication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
]//Create the client context. ClientContext ctx = new ClientContext(MixedUrlTxt.Text); //Configure the handler that will add the header. ctx.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>(ctx_MixedAuthRequest); //Set the Windows credentials. ctx.AuthenticationMode = ClientAuthenticationMode.Default; ctx.Credentials = System.Net.CredentialCache.DefaultCredentials; //Event Handler void ctx_MixedAuthRequest(object sender, WebRequestEventArgs e) { try { //Add the header that tells SharePoint to use Windows authentication. e.WebRequestExecutor.RequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f"); } catch (Exception ex) { MessageBox.Show("Error setting authentication header: " + ex.Message); } } |
In PowerShell, normally you add an event handler to a .NET object using the Register-ObjectEvent command, and this was my first attempt at solving this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll" Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll" $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url); $credentials = New-Object System.Net.NetworkCredential($username, $password, $domain); $clientContext.Credentials = $credentials; $clientContext.AuthenticationMode = [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default $eventAction = { Write-Host "In event handler..." $event.SourceArgs[1].WebRequestExecutor.RequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f") } Register-ObjectEvent -InputObject $clientContext -EventName "ExecutingWebRequest" -SourceIdentifier "OnWebRequestExecuting" -Action $eventAction $site = $clientContext.Site $clientContext.Load($site) $clientContext.ExecuteQuery() |
However, after modifying the headers collection and tracing in Fiddler, the event handler would get triggered, but the custom header would never get added to the request. It turns out that this approach schedules a PSEventJob to run your script block, which doesn’t end up returning the modified event argument back to the original source event object. Prompted by a good hint from this StackOverflow post, I was able to make use of PowerShell’s ability to create on-the-fly .net code and execute it. I wrapped it up in a function that you can call in your scripts. First you add the function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Function HandleMixedModeWebApplication() { param([Parameter(Mandatory=$true)][object]$clientContext) Add-Type -TypeDefinition @" using System; using Microsoft.SharePoint.Client; namespace Toth.SPOHelpers { public static class ClientContextHelper { public static void AddRequestHandler(ClientContext context) { context.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>(RequestHandler); } private static void RequestHandler(object sender, WebRequestEventArgs e) { //Add the header that tells SharePoint to use Windows authentication. e.WebRequestExecutor.RequestHeaders.Remove("X-FORMS_BASED_AUTH_ACCEPTED"); e.WebRequestExecutor.RequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f"); } } } "@ -ReferencedAssemblies "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll", "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"; [Toth.SPOHelpers.ClientContextHelper]::AddRequestHandler($clientContext); } |
Then, you create your ClientContext and call the function to add the event handler:
1 2 3 4 5 6 7 8 9 10 |
" ] Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll" Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll" $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url); $clientContext.Credentials = $credentials.GetNetworkCredential(); $clientContext.AuthenticationMode = [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default # Mixed-mode web app handling HandleMixedModeWebApplication $clientContext; |
Below is a full gist with the solution:
They told me we could not use PowerShell to connect to our SharePoint environment. I insisted it had to be possible. Just like you I first tried to translate the C# event to PowerShell, but it didn’t work. However, the final solution you provided did work! Thanks for this great post, it proves that we can use PowerShell (even in our environment) to interact with SharePoint.