Friday, April 20, 2018

List Azure AD groups via CSOM in Powershell

Recently I showed how to list Azure AD/O365 groups in Powershell using REST Graph API: see List Azure AD groups via Rest Graph API in Powershell. In this post I will show how to do the same thing using CSOM. It may be interesting especially in part of how to obtain access token based on client id and client secret via CSOM and how it differs from REST approach.

In order to fully understand the code you may also need to check another article: Avoiding StackOverflowException when use assembly binding redirect in PowerShell. It shows how to use C#-based assembly binding redirect in PowerShell (native Powershell assembly binding redirect which is described here Use specific version of Sharepoint client object model in PowerShell via assembly binding redirection causes StackOverflowException when you try to use Graph API).

So here is the script:

param
(
	[Parameter(Mandatory=$true)]
	$Tenant,
	[Parameter(Mandatory=$true)]
	$ClientId,
	[Parameter(Mandatory=$true)]
	$ClientSecret
)

$currentDir = Convert-Path(Get-Location)
$dllCommonDir = resolve-path($currentDir + "\..\Assemblies\Common\")
$pnpCoreDir = resolve-path($currentDir + "\..\packages\SharePointPnPCoreOnline.2.22.1801.0\lib\net45\")
$graphDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.1.7.0\lib\net45\")
$graphCoreDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.Core.1.7.0\lib\net45\")
$newtonJsonDir = resolve-path($currentDir + "\..\packages\Newtonsoft.Json.10.0.3\lib\net45\")

$AssemblyNewtonJson = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($newtonJsonDir, "Newtonsoft.Json.dll"))
$AssemblyGraphCore = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphCoreDir, "Microsoft.Graph.Core.dll"))
$AssemblyGraph = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphDir, "Microsoft.Graph.dll"))
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllCommonDir, "Microsoft.Identity.Client.dll"))
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($pnpCoreDir, "OfficeDevPnP.Core.dll"))

if (!("Redirector" -as [type]))
{
$source = 
@'
using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

public class Redirector
{
    public readonly string[] ExcludeList;

    public Redirector(string[] ExcludeList = null)
    {
        this.ExcludeList  = ExcludeList;
        this.EventHandler = new ResolveEventHandler(AssemblyResolve);
    }

    public readonly ResolveEventHandler EventHandler;

    protected Assembly AssemblyResolve(object sender, ResolveEventArgs resolveEventArgs)
    {
        //Console.WriteLine("Attempting to resolve: " + resolveEventArgs.Name); // remove this after its verified to work
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var pattern  = "PublicKeyToken=(.*)$";
            var info     = assembly.GetName();
            var included = ExcludeList == null || !ExcludeList.Contains(resolveEventArgs.Name.Split(',')[0], StringComparer.InvariantCultureIgnoreCase);

            if (included && resolveEventArgs.Name.StartsWith(info.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                if (Regex.IsMatch(info.FullName, pattern))
                {
                    var Matches        = Regex.Matches(info.FullName, pattern);
                    var publicKeyToken = Matches[0].Groups[1];

                    if (resolveEventArgs.Name.EndsWith("PublicKeyToken=" + publicKeyToken, StringComparison.InvariantCultureIgnoreCase))
                    {
                        //Console.WriteLine("Redirecting lib to: " + info.FullName); // remove this after its verified to work
                        return assembly;
                    }
                }
            }
        }

        return null;
    }
}
'@

    $type = Add-Type -TypeDefinition $source -PassThru 
}

try
{
    $redirector = [Redirector]::new($null)
    [System.AppDomain]::CurrentDomain.add_AssemblyResolve($redirector.EventHandler)
}
catch
{
    #.net core uses a different redirect method
    Write-Warning "Unable to register assembly redirect(s). Are you on ARM (.Net Core)?"
}

function GetAccessToken($tenant, $clientId, $clientSecret)
{
	$appCredentials = New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList $clientSecret
	$aadLoginUri = New-Object System.Uri -ArgumentList "https://login.microsoftonline.com/"
	$authorityUri = New-Object System.Uri -ArgumentList $aadLoginUri, $tenant
	$authority = $authorityUri.AbsoluteUri
	$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
	$clientApplication = New-Object Microsoft.Identity.Client.ConfidentialClientApplication($clientId, $authority, $redirectUri, $appCredentials, $null, $null)
	[string[]]$defaultScope = @("https://graph.microsoft.com/.default")
	$authenticationResult = $clientApplication.AcquireTokenForClientAsync($defaultScope).Result
	return $authenticationResult.AccessToken
}

function ListGroups($accessToken)
{
	$existingGroups = [OfficeDevPnP.Core.Framework.Graph.UnifiedGroupsUtility]::ListUnifiedGroups($accessToken, "", "")
	foreach($group in $existingGroups)
	{
		Write-Host $group.DisplayName
	}
}

$accessToken = GetAccessToken $Tenant $ClientId $ClientSecret
ListGroups $accessToken

No comments:

Post a Comment