SharePoint High-Trust App Only Context from PowerShell

Wictor WilĂ©n wrote a great post on getting an “app-only” client context for SharePoint Add-ins using ACS – Low Trust Add-ins. I wanted to do something similar with High-Trust Server-2-Server Provider-Hosted Add-ins. I needed the app-only context so that I could use the Tenant API to create on-premises site collections from PowerShell. With the help of Fiddler and some mad PowerShell skills, I came up with the following function.

Function GetAppOnlyContext {
    Param([string]$clientId, [string]$issuerId, [string]$certPath, [string]$certPwd, [scriptblock]$GetAppOnlyContextCB);
    [System.Reflection.Assembly]::LoadWithPartialName("System") | Out-Null;
    [System.Reflection.Assembly]::LoadWithPartialName("System.Collections") | Out-Null;
    [System.Reflection.Assembly]::Load("System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") | Out-Null;
    [System.Reflection.Assembly]::Load("Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35") | Out-Null;
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.IdentityModel.Extensions") | Out-Null;
    # Get the realm.
    $realm = "";
    $headers = @{Authorization = "Bearer "};
    Try {
        $x = Invoke-WebRequest -Uri "$($tenantUrl)_vti_bin/client.svc" -Headers $headers -Method POST -UseBasicParsing;
    } Catch {
        # We expect a 401 here.
        $realm = $_.Exception.Response.Headers["WWW-Authenticate"].Substring(7).Split(",")[0].Split("=")[1].Trim("`"");
    }
    $issuer = "$($issuerId)@$($realm)";
    $nameId = "$($clientId)@$($realm)";
    $uri = New-Object System.Uri($tenantUrl);
    $audience = "00000003-0000-0ff1-ce00-000000000000/$($uri.Authority)@$($realm)";
    # Get the claims
    $actorClaims = New-Object System.Collections.Generic.List[Microsoft.IdentityModel.S2S.Tokens.JsonWebTokenClaim];
    $claim = New-Object Microsoft.IdentityModel.S2S.Tokens.JsonWebTokenClaim("nameid", $nameId);
    $actorClaims.Add($claim);
    # Get the signing credentials
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, $certPwd);
    $signingCreds = New-Object Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials(`
        $cert, [System.IdentityModel.Tokens.SecurityAlgorithms]::RsaSha256Signature, `
        [System.IdentityModel.Tokens.SecurityAlgorithms]::Sha256Digest);
    # Create the token.    
    $token = New-Object Microsoft.IdentityModel.S2S.Tokens.JsonWebSecurityToken(`
        $issuer, $audience, [System.DateTime]::UtcNow, [System.DateTime]::UtcNow.AddHours(12), $actorClaims, $signingCreds);
    $tokenString = (New-Object Microsoft.IdentityModel.S2S.Tokens.JsonWebSecurityTokenHandler).WriteTokenAsString($token);
    [Microsoft.SharePoint.Client.ClientContext]$clientContext = $null;
    Try {
        # Create the client context.
        $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($tenantUrl);
        $clientContext.AuthenticationMode = [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Anonymous;
        $clientContext.FormDigestHandlingEnabled = $false;
        AddRequestHandler -clientContext $clientContext -token $tokenString;
        if ($GetAppOnlyContextCB -ne $null) { &$GetAppOnlyContextCB -clientContext $clientContext; }
    } Finally {
        if ($clientContext -ne $null) { $clientContext.Dispose(); }
    }
}

Function AddRequestHandler {
    Param([Microsoft.SharePoint.Client.ClientContext]$clientContext, [string]$token);
    Add-Type -TypeDefinition @"
using System;
using Microsoft.SharePoint.Client;
namespace SPHelper {
    public static class ClientContextHelper {
        private static string _token = "";
        public static void AddRequestHandler(ClientContext ctx, string token) {
            _token = token;
            ctx.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>(RequestHandler);
        }
        private static void RequestHandler(object sender, WebRequestEventArgs e) {
            e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + _token;
        }
    }
}
"@ -ReferencedAssemblies "$env:dp0\DLLs\Microsoft.SharePoint.Client.dll", "$env:dp0\DLLs\Microsoft.SharePoint.Client.Runtime.dll";
    [SPHelper.ClientContextHelper]::AddRequestHandler($clientContext, $token);
}