Skip to content

Instantly share code, notes, and snippets.

@SMSAgentSoftware
Created November 28, 2024 18:06
Show Gist options
  • Save SMSAgentSoftware/e3f38160d4ef048c8b95308975b96046 to your computer and use it in GitHub Desktop.
Save SMSAgentSoftware/e3f38160d4ef048c8b95308975b96046 to your computer and use it in GitHub Desktop.
Example function for how to obtain an access token for Microsoft Entra using the Web Account Manager (WAM) broker in Windows
Function Get-EntraAccessTokenWithWAM {
[CmdletBinding()]
param (
[Parameter()]
[string]
$ClientId = "14d82eec-204b-4c2f-b7e8-296a70dab67e", # Microsoft Graph PowerShell
[Parameter()]
[string[]]
$Scopes = "https://graph.microsoft.com/.default",
[Parameter()]
[string]
$RedirectUri = "http://localhost"
)
# Makre sure we're running in PowerShell Core edition
If ($PSEdition -ne "Core")
{
Write-Warning "This function is only supported in PowerShell Core"
return
}
# Check for Az.Accounts module
$AzAccountsModule = Get-Module -Name Az.Accounts -ListAvailable
If ($null -eq $AzAccountsModule)
{
try
{
Install-Module -Name Az.Accounts -Force -AllowClobber -Scope CurrentUser -Repository PSGallery -ErrorAction Stop
}
catch
{
throw "Failed to install Az.Accounts module: $_"
}
}
# Import Az.Accounts module
try
{
Import-Module Az.Accounts -ErrorAction Stop
}
catch
{
throw $_
}
# Find the location of the Azure.Common assembly
$LoadedAssemblies = [System.AppDomain]::CurrentDomain.GetAssemblies() | Select -ExpandProperty Location
$AzureCommon = $LoadedAssemblies |
Where-Object { $_ -match "\\Modules\\Az.Accounts\\" -and $_ -match "Microsoft.Azure.Common" }
$AzureCommonLocation = $AzureCommon.TrimEnd("Microsoft.Azure.Common.dll")
# Locate the required assemblies
$mima = Get-ChildItem -Path $AzureCommonLocation -Filter "Microsoft.IdentityModel.Abstractions.dll" -Recurse -File | Select -ExpandProperty FullName
$mic = Get-ChildItem -Path $AzureCommonLocation -Filter "Microsoft.Identity.Client.dll" -Recurse -File | Select -ExpandProperty FullName
$micb = Get-ChildItem -Path $AzureCommonLocation -Filter "Microsoft.Identity.Client.Broker.dll" -Recurse -File | Select -ExpandProperty FullName
$micni = Get-ChildItem -Path $AzureCommonLocation -Filter "Microsoft.Identity.Client.NativeInterop.dll" -Recurse -File | Select -ExpandProperty FullName
$micem = Get-ChildItem -Path $AzureCommonLocation -Filter "Microsoft.Identity.Client.Extensions.Msal.dll" -Recurse -File | Select -ExpandProperty FullName
$sscpd = Get-ChildItem -Path $AzureCommonLocation -Filter "System.Security.Cryptography.ProtectedData.dll" -Recurse -File | Select -ExpandProperty FullName
# This next one need to come from the .Net Core installation being used
$RuntimeFrameworkMajorVersion = [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription.Split()[-1].Split(".")[0]
$dotNetDirectory = Get-ChildItem -Path "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref" -Filter "$RuntimeFrameworkMajorVersion.*" -Directory |
Sort-Object -Property Name -Descending | Select -First 1
$sdts = Get-ChildItem -Path $dotNetDirectory -Filter "System.Diagnostics.TraceSource.dll" -Recurse -File | Select -ExpandProperty FullName
# Load the assemblies
try
{
[void][System.Reflection.Assembly]::LoadFrom($mima)
[void][System.Reflection.Assembly]::LoadFrom($mic)
[void][System.Reflection.Assembly]::LoadFrom($micb)
[void][System.Reflection.Assembly]::LoadFrom($micni)
[void][System.Reflection.Assembly]::LoadFrom($sscpd)
[void][System.Reflection.Assembly]::LoadFrom($sdts)
[void][System.Reflection.Assembly]::LoadFrom($micem)
}
catch
{
throw $_
}
# C# code to do the work since PS will not recognize the .WithBroker() extension method from the broker library
$code = @"
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.Identity.Client.NativeInterop;
using Microsoft.Identity.Client.Extensions.Msal;
public class PublicClientAppHelper
{
// This gets the window handle of the console window
[DllImport("user32.dll", ExactSpelling = true)]
public static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
public enum GetAncestorFlags
{
GetParent = 1,
GetRoot = 2,
GetRootOwner = 3
}
public static IntPtr GetConsoleOrTerminalWindow()
{
IntPtr consoleHandle = GetConsoleWindow();
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);
return handle;
}
// Setup a cache for the tokens
private static BrokerOptions brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows);
private static string cacheFileName = "msalcache.bin";
private static string cacheFilePath = Path.Combine(MsalCacheHelper.UserRootDirectory, cacheFileName);
private static string cacheDir = Path.GetDirectoryName(cacheFilePath);
private static StorageCreationProperties storageProperties = new StorageCreationPropertiesBuilder(cacheFileName, cacheDir).Build();
// Method for retrieving the access token
public static async Task<string> GetAccessTokenWithWAM(string clientId, string redirectUri, string[] scopes)
{
IPublicClientApplication publicClientApp = PublicClientApplicationBuilder.Create(clientId)
.WithBroker(brokerOptions)
.WithParentActivityOrWindow(GetConsoleOrTerminalWindow)
.WithRedirectUri(redirectUri)
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false);
cacheHelper.RegisterCache(publicClientApp.UserTokenCache);
var accounts = await publicClientApp.GetAccountsAsync();
var existingAccount = accounts.FirstOrDefault();
AuthenticationResult result;
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(90)); // Automatic cancellation after 90 seconds
// try the cache first, fallback to interactive if necessary
try
{
result = existingAccount != null
? await publicClientApp.AcquireTokenSilent(scopes, existingAccount).ExecuteAsync(cancellationTokenSource.Token)
: await publicClientApp.AcquireTokenInteractive(scopes).ExecuteAsync(cancellationTokenSource.Token);
}
catch (MsalUiRequiredException)
{
result = await publicClientApp.AcquireTokenInteractive(scopes).ExecuteAsync(cancellationTokenSource.Token);
}
return result.AccessToken;
}
}
"@
# List of assemblies we need to reference
$assemblies = @($mima, $mic, $micb, $micni, $micem, $sscpd, $sdts, "netstandard", "System.Linq")
# Get the access token
try
{
# If the type already exists in the current session
$token = [PublicClientAppHelper]::GetAccessTokenWithWAM($ClientId, $RedirectUri, $Scopes).GetAwaiter().GetResult()
return $token
}
catch
{
if ($_.FullyQualifiedErrorId -eq "TypeNotFound")
{
try
{
# Add the type if it doesn't exist yet
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp -ErrorAction Stop
$token = [PublicClientAppHelper]::GetAccessTokenWithWAM($ClientId, $RedirectUri, $Scopes).GetAwaiter().GetResult()
return $token
}
catch
{
throw $_
}
}
else
{
throw $_
}
}
}
## Example usage ##
# Obtain an access token for the Microsoft Graph API using the Microsoft Graph PowerShell SDK app
Get-EntraAccessTokenWithWAM
# Obtain an access token for the Azure SQL Database using the Azure CLI app
Get-EntraAccessTokenWithWAM -Scopes "https://database.windows.net/.default" -ClientId "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
# Obtain an access token for the Azure SQL Database using the Azure PowerShell app
Get-EntraAccessTokenWithWAM -Scopes "https://database.windows.net/.default" -ClientId "1950a258-227b-4e31-a9cf-717495945fc2"
@SMSAgentSoftware
Copy link
Author

Getting below error when trying: Exception: C:\temp\Get-EntraAccessTokenWithWAM.ps1:73 Line | 73 | [void][System.Reflection.Assembly]::LoadFrom($sdts) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Exception calling "LoadFrom" with "1" argument(s): "Value cannot be null. (Parameter 'assemblyFile')"

Check lines 60-63 - make sure it can find "System.Diagnostics.TraceSource.dll" in your .Net core install directory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment