diff --git a/.github/workflows/Publish-AadAuthenticationFactory.yml b/.github/workflows/Publish-AadAuthenticationFactory.yml
index 06128e3..2d85c3e 100644
--- a/.github/workflows/Publish-AadAuthenticationFactory.yml
+++ b/.github/workflows/Publish-AadAuthenticationFactory.yml
@@ -32,15 +32,16 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
- - uses: nuget/setup-nuget@v1
+ - name: Set up .NET SDK
+ uses: actions/setup-dotnet@v4
with:
- nuget-version: 'latest'
+ dotnet-version: 10.0.x
- name: Setup Packages
shell: pwsh
run: |
"Setting up packages"
- &"$env:GITHUB_WORKSPACE\Workflow\SetupPackages.ps1" -RootPath "$env:GITHUB_WORKSPACE" -NugetPath $env:nuget
+ &"$env:GITHUB_WORKSPACE\Workflow\SetupPackages.ps1" -RootPath "$env:GITHUB_WORKSPACE"
- name: Build Module
uses: GreyCorbel/PsModuleActions/build-module@v1
diff --git a/.github/workflows/Test-AadAuthenticationFactory.yml b/.github/workflows/Test-AadAuthenticationFactory.yml
new file mode 100644
index 0000000..bbaa263
--- /dev/null
+++ b/.github/workflows/Test-AadAuthenticationFactory.yml
@@ -0,0 +1,122 @@
+name: Test-AadAuthenticationFactory
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ name: PowerShell 7 on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - windows-latest
+ - ubuntu-latest
+ - macos-latest
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Set up .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Install Linux broker runtime dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ sudo apt-get update
+ sudo apt-get install --yes --no-install-recommends libwebkit2gtk-4.1-0
+
+ - name: Restore module dependencies
+ shell: pwsh
+ run: |
+ $setupPackages = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'SetupPackages.ps1'
+ & $setupPackages -RootPath $env:GITHUB_WORKSPACE
+
+ - name: Build module
+ shell: pwsh
+ run: |
+ $buildModule = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'BuildModule.ps1'
+ & $buildModule -RootPath $env:GITHUB_WORKSPACE -ModuleName AadAuthenticationFactory
+
+ - name: Run Pester tests
+ shell: pwsh
+ run: |
+ $runTests = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'RunTests.ps1'
+ & $runTests -RootPath $env:GITHUB_WORKSPACE -ModuleName AadAuthenticationFactory -ExcludeTag integration -CI
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: pester-results-${{ matrix.os }}
+ path: TestResults.xml
+ if-no-files-found: warn
+
+ confidential-client-integration:
+ name: Confidential client integration
+ if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
+ runs-on: ubuntu-latest
+ environment: confidential-client-integration
+ permissions:
+ contents: read
+ id-token: write
+ env:
+ AAD_TEST_TENANT_ID: ${{ secrets.AAD_TEST_TENANT_ID }}
+ AAD_TEST_SCOPE: ${{ secrets.AAD_TEST_SCOPE }}
+ AAD_TEST_CLIENT_ID: ${{ secrets.AAD_TEST_CLIENT_ID }}
+ AAD_TEST_CLIENT_SECRET: ${{ secrets.AAD_TEST_CLIENT_SECRET }}
+ AAD_TEST_EXPECTED_AUD: ${{ secrets.AAD_TEST_EXPECTED_AUD }}
+ AAD_TEST_OIDC_AUDIENCE: ${{ secrets.AAD_TEST_OIDC_AUDIENCE }}
+ AAD_TEST_OIDC_SUBJECT: ${{ secrets.AAD_TEST_OIDC_SUBJECT }}
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v4
+
+ - name: Set up .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Restore module dependencies
+ shell: pwsh
+ run: |
+ $setupPackages = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'SetupPackages.ps1'
+ & $setupPackages -RootPath $env:GITHUB_WORKSPACE
+
+ - name: Build module
+ shell: pwsh
+ run: |
+ $buildModule = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'BuildModule.ps1'
+ & $buildModule -RootPath $env:GITHUB_WORKSPACE -ModuleName AadAuthenticationFactory
+
+ - name: Run confidential client integration test
+ shell: pwsh
+ run: |
+ $runTests = Join-Path $env:GITHUB_WORKSPACE 'Workflow' 'RunTests.ps1'
+ & $runTests `
+ -RootPath $env:GITHUB_WORKSPACE `
+ -ModuleName AadAuthenticationFactory `
+ -Tag integration `
+ -RequireIntegrationConfiguration `
+ -CI
+
+ - name: Upload integration test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: confidential-client-integration-results
+ path: TestResults.xml
+ if-no-files-found: warn
diff --git a/.gitignore b/.gitignore
index eb2540c..5a16096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ Generated\ Files/
# NUnit
*.VisualState.xml
TestResult.xml
+TestResults.xml
nunit-*.xml
# Build Results of an ATL Project
diff --git a/Commands/Internal/Get-MsalBrokerPlatformConfiguration.ps1 b/Commands/Internal/Get-MsalBrokerPlatformConfiguration.ps1
new file mode 100644
index 0000000..34c3b92
--- /dev/null
+++ b/Commands/Internal/Get-MsalBrokerPlatformConfiguration.ps1
@@ -0,0 +1,80 @@
+function Get-MsalBrokerPlatformConfiguration {
+ $architecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
+
+ if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
+ $runtime = switch ($architecture) {
+ 'X64' { @{ Rid = 'win-x64'; NativeLibrary = 'msalruntime.dll' } }
+ 'X86' { @{ Rid = 'win-x86'; NativeLibrary = 'msalruntime_x86.dll' } }
+ 'Arm64' { @{ Rid = 'win-arm64'; NativeLibrary = 'msalruntime_arm64.dll' } }
+ default { $null }
+ }
+
+ if ($null -eq $runtime) {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on Windows architecture '$architecture'. Supported architectures: X64, X86, Arm64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'Windows'
+ Architecture = $architecture
+ RuntimeIdentifier = $runtime.Rid
+ NativeLibraryFileName = $runtime.NativeLibrary
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Windows
+ RedirectUri = $null
+ UseDefaultRedirectUri = $true
+ UseParentWindow = $true
+ ListOperatingSystemAccounts = $true
+ }
+ }
+
+ if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
+ if ($architecture -ne 'X64') {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on Linux architecture '$architecture' because the bundled native runtime only supports X64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'Linux'
+ Architecture = $architecture
+ RuntimeIdentifier = 'linux-x64'
+ NativeLibraryFileName = 'libmsalruntime.so'
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Linux
+ RedirectUri = $null
+ UseDefaultRedirectUri = $true
+ UseParentWindow = $false
+ ListOperatingSystemAccounts = $true
+ }
+ }
+
+ if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
+ $runtime = switch ($architecture) {
+ 'X64' { @{ Rid = 'osx-x64'; NativeLibrary = 'msalruntime.dylib' } }
+ 'Arm64' { @{ Rid = 'osx-arm64'; NativeLibrary = 'msalruntime_arm64.dylib' } }
+ default { $null }
+ }
+
+ if ($null -eq $runtime) {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on macOS architecture '$architecture'. Supported architectures: X64, Arm64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'macOS'
+ Architecture = $architecture
+ RuntimeIdentifier = $runtime.Rid
+ NativeLibraryFileName = $runtime.NativeLibrary
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::OSX
+ RedirectUri = 'msauth.com.msauth.unsignedapp://auth'
+ UseDefaultRedirectUri = $false
+ UseParentWindow = $false
+ ListOperatingSystemAccounts = $false
+ }
+ }
+
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on operating system '$([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)'."
+ )
+}
diff --git a/Commands/Internal/Get-MsalRuntimeRidFolder.ps1 b/Commands/Internal/Get-MsalRuntimeRidFolder.ps1
index 3602b98..5d3ff3e 100644
--- a/Commands/Internal/Get-MsalRuntimeRidFolder.ps1
+++ b/Commands/Internal/Get-MsalRuntimeRidFolder.ps1
@@ -1,29 +1,3 @@
function Get-MsalRuntimeRidFolder {
- # returns one of: win-x64, win-x86, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64
- $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
-
- if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
- switch ($arch) {
- 'X64' { return 'win-x64' }
- 'X86' { return 'win-x86' }
- 'Arm64' { return 'win-arm64' }
- default { return 'win-x64' }
- }
- }
- elseif ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
- switch ($arch) {
- 'X64' { return 'linux-x64' }
- 'Arm64' { return 'linux-arm64' }
- default { return 'linux-x64' }
- }
- }
- elseif ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
- switch ($arch) {
- 'X64' { return 'osx-x64' }
- 'Arm64' { return 'osx-arm64' }
- default { return 'osx-x64' }
- }
- }
-
- return $null
+ (Get-MsalBrokerPlatformConfiguration).RuntimeIdentifier
}
\ No newline at end of file
diff --git a/Commands/Internal/Import-MsalNativeRuntime.ps1 b/Commands/Internal/Import-MsalNativeRuntime.ps1
index 484e47a..1f3a010 100644
--- a/Commands/Internal/Import-MsalNativeRuntime.ps1
+++ b/Commands/Internal/Import-MsalNativeRuntime.ps1
@@ -3,36 +3,52 @@ function Import-MsalNativeRuntime {
[Parameter(Mandatory)] [string] $ModuleRoot
)
- $rid = Get-MsalRuntimeRidFolder
- if ([string]::IsNullOrEmpty($rid)) { return }
-
- # IMPORTANT: your folder is lowercase "runtimes"
- $nativeDir = [Path]::Combine($ModuleRoot, 'runtimes', $rid, 'native')
- if (-not (Test-Path $nativeDir)) { return }
-
- $candidate = Get-ChildItem -Path $nativeDir -File |
- Where-Object {
- $_.Name -match '^msalruntime' -and $_.Extension -in @('.dll','.so','.dylib')
- } |
- Select-Object -First 1
+ $platform = Get-MsalBrokerPlatformConfiguration
+ $nativePath = [Path]::Combine(
+ $ModuleRoot,
+ 'runtimes',
+ $platform.RuntimeIdentifier,
+ 'native',
+ $platform.NativeLibraryFileName
+ )
- if (-not $candidate) {
- Write-Verbose "MSAL native runtime not found in $nativeDir"
- return
+ if (-not (Test-Path -LiteralPath $nativePath -PathType Leaf)) {
+ throw [System.IO.FileNotFoundException]::new(
+ "MSAL broker native runtime is missing for OS '$($platform.OperatingSystem)', architecture '$($platform.Architecture)', RID '$($platform.RuntimeIdentifier)'. Expected file: '$nativePath'.",
+ $nativePath
+ )
}
- if ($PSEdition -eq 'Core') {
- [System.Runtime.InteropServices.NativeLibrary]::Load($candidate.FullName) | Out-Null
+ if ($script:MsalNativeRuntimePath -eq $nativePath -and $script:MsalNativeRuntimeHandle -ne [IntPtr]::Zero) {
+ return $platform
}
- else {
- # Windows PowerShell 5.1 is Windows-only; LoadLibrary is fine
- if ($null -eq ('Kernel32' -as [type])) {
- $helperPath = [Path]::Combine($ModuleRoot, 'Helpers', 'Kernel32.cs')
- $helperDefinition = Get-Content $helperPath -Raw
- Add-Type -TypeDefinition $helperDefinition -ReferencedAssemblies @('System.Runtime.InteropServices') -WarningAction SilentlyContinue -IgnoreWarnings
+
+ try {
+ if ($PSEdition -eq 'Core') {
+ $nativeHandle = [System.Runtime.InteropServices.NativeLibrary]::Load($nativePath)
+ }
+ else {
+ if ($null -eq ('Kernel32' -as [type])) {
+ $helperPath = [Path]::Combine($ModuleRoot, 'Helpers', 'Kernel32.cs')
+ $helperDefinition = Get-Content $helperPath -Raw
+ Add-Type -TypeDefinition $helperDefinition -ReferencedAssemblies @('System.Runtime.InteropServices') -WarningAction SilentlyContinue -IgnoreWarnings
+ }
+
+ $nativeHandle = [Kernel32]::LoadLibrary($nativePath)
+ if ($nativeHandle -eq [IntPtr]::Zero) {
+ $errorCode = [Kernel32]::GetLastError()
+ throw [System.ComponentModel.Win32Exception]::new([int]$errorCode)
+ }
}
- [Kernel32]::LoadLibrary($candidate.FullName) | Out-Null
}
+ catch {
+ $message = "Failed to load MSAL broker native runtime for OS '$($platform.OperatingSystem)', architecture '$($platform.Architecture)', RID '$($platform.RuntimeIdentifier)' from '$nativePath'. $($_.Exception.Message)"
+ throw [System.DllNotFoundException]::new($message, $_.Exception)
+ }
+
+ $script:MsalNativeRuntimePath = $nativePath
+ $script:MsalNativeRuntimeHandle = $nativeHandle
+ Write-Information "Loaded MSAL native runtime: $nativePath"
- Write-Information "Loaded MSAL native runtime: $($candidate.FullName)"
+ return $platform
}
\ No newline at end of file
diff --git a/Commands/Internal/Init.ps1 b/Commands/Internal/Init.ps1
index fcccc3f..3148544 100644
--- a/Commands/Internal/Init.ps1
+++ b/Commands/Internal/Init.ps1
@@ -18,6 +18,8 @@ function Init {
if ($null -eq $script:AadAuthenticationFactories -or -not ($script:AadAuthenticationFactories -is [hashtable])) {
$script:AadAuthenticationFactories = @{}
}
+ $script:MsalNativeRuntimePath = $null
+ $script:MsalNativeRuntimeHandle = [IntPtr]::Zero
# Determine whether MSAL is already loaded
$msalAlreadyLoaded = $false
@@ -111,7 +113,8 @@ function Init {
}
# --------------------------------------------------------
- # Load Broker (if not loaded) + native runtime (cross-platform)
+ # Load the managed broker assembly. Its platform runtime is loaded lazily
+ # when a broker factory is requested so other authentication modes remain usable.
# --------------------------------------------------------
$brokerTypePresent = ($null -ne ('Microsoft.Identity.Client.Broker.BrokerExtension' -as [type]))
if (-not $brokerTypePresent) {
@@ -121,15 +124,12 @@ function Init {
Write-Warning ("MSAL version in session is {0} but module broker is {1}. Skipping broker load to avoid version conflicts. Broker-based auth may be unavailable; browser/device-code fallback still works." -f $msalLoadedVersion, $brokerVersion)
}
else {
- # Load broker extension assembly
if (-not (Test-Path $brokerDll)) {
Write-Warning "Broker DLL not found at $brokerDll. Broker-based auth will be unavailable."
}
else {
Add-Type -Path $brokerDll -ErrorAction Stop | Out-Null
- # Load native runtime for broker (Windows/Linux/macOS)
- Import-MsalNativeRuntime -ModuleRoot $moduleRoot
}
}
}
diff --git a/Commands/Public/New-AadAuthenticationFactory.ps1 b/Commands/Public/New-AadAuthenticationFactory.ps1
index 32b04a0..702db6d 100644
--- a/Commands/Public/New-AadAuthenticationFactory.ps1
+++ b/Commands/Public/New-AadAuthenticationFactory.ps1
@@ -363,19 +363,29 @@ Creates a public client factory that uses the OS broker where available.
break
}
{$_ -in 'WAM','Broker'} {
-
- $flowType = [AuthenticationFlow]::PublicClientWithWam
- $os =
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Windows -bor
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Linux -bor
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::OSX
+ if ($null -eq ('Microsoft.Identity.Client.Broker.BrokerExtension' -as [type])) {
+ throw [InvalidOperationException]::new(
+ 'MSAL broker authentication is unavailable because the managed Microsoft.Identity.Client.Broker assembly was not loaded. Import AadAuthenticationFactory before modules that load a different MSAL version.'
+ )
+ }
- $brokerOptions = [Microsoft.Identity.Client.BrokerOptions]::new($os)
+ $brokerPlatform = Import-MsalNativeRuntime -ModuleRoot $PSScriptRoot
+ $flowType = [AuthenticationFlow]::PublicClientWithWam
+ $brokerOptions = [Microsoft.Identity.Client.BrokerOptions]::new($brokerPlatform.BrokerOperatingSystem)
$brokerOptions.Title = "AadAuthenticationFactory"
- $brokerOptions.ListOperatingSystemAccounts = $true
+ $brokerOptions.ListOperatingSystemAccounts = $brokerPlatform.ListOperatingSystemAccounts
$builder = [Microsoft.Identity.Client.Broker.BrokerExtension]::WithBroker($builder,$brokerOptions)
- $builder = $builder.WithParentActivityOrWindow([ParentWindowHelper]::ConsoleWindowHandleProvider)
- $builder = $builder.WithRedirectUri("http://localhost")
+
+ if ($brokerPlatform.UseParentWindow) {
+ $builder = $builder.WithParentActivityOrWindow([ParentWindowHelper]::ConsoleWindowHandleProvider)
+ }
+
+ if ($brokerPlatform.UseDefaultRedirectUri) {
+ $builder = $builder.WithDefaultRedirectUri()
+ }
+ else {
+ $builder = $builder.WithRedirectUri($brokerPlatform.RedirectUri)
+ }
break
}
Default {
diff --git a/Module/AadAuthenticationFactory/AadAuthenticationFactory.psm1 b/Module/AadAuthenticationFactory/AadAuthenticationFactory.psm1
index cf3a51e..9589493 100644
--- a/Module/AadAuthenticationFactory/AadAuthenticationFactory.psm1
+++ b/Module/AadAuthenticationFactory/AadAuthenticationFactory.psm1
@@ -957,19 +957,29 @@ Creates a public client factory that uses the OS broker where available.
break
}
{$_ -in 'WAM','Broker'} {
-
- $flowType = [AuthenticationFlow]::PublicClientWithWam
- $os =
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Windows -bor
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Linux -bor
- [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::OSX
+ if ($null -eq ('Microsoft.Identity.Client.Broker.BrokerExtension' -as [type])) {
+ throw [InvalidOperationException]::new(
+ 'MSAL broker authentication is unavailable because the managed Microsoft.Identity.Client.Broker assembly was not loaded. Import AadAuthenticationFactory before modules that load a different MSAL version.'
+ )
+ }
- $brokerOptions = [Microsoft.Identity.Client.BrokerOptions]::new($os)
+ $brokerPlatform = Import-MsalNativeRuntime -ModuleRoot $PSScriptRoot
+ $flowType = [AuthenticationFlow]::PublicClientWithWam
+ $brokerOptions = [Microsoft.Identity.Client.BrokerOptions]::new($brokerPlatform.BrokerOperatingSystem)
$brokerOptions.Title = "AadAuthenticationFactory"
- $brokerOptions.ListOperatingSystemAccounts = $true
+ $brokerOptions.ListOperatingSystemAccounts = $brokerPlatform.ListOperatingSystemAccounts
$builder = [Microsoft.Identity.Client.Broker.BrokerExtension]::WithBroker($builder,$brokerOptions)
- $builder = $builder.WithParentActivityOrWindow([ParentWindowHelper]::ConsoleWindowHandleProvider)
- $builder = $builder.WithRedirectUri("http://localhost")
+
+ if ($brokerPlatform.UseParentWindow) {
+ $builder = $builder.WithParentActivityOrWindow([ParentWindowHelper]::ConsoleWindowHandleProvider)
+ }
+
+ if ($brokerPlatform.UseDefaultRedirectUri) {
+ $builder = $builder.WithDefaultRedirectUri()
+ }
+ else {
+ $builder = $builder.WithRedirectUri($brokerPlatform.RedirectUri)
+ }
break
}
Default {
@@ -1501,72 +1511,142 @@ function Get-AssemblyVersionFromPath {
return $null
}
}
-function Get-MsalRuntimeRidFolder {
- # returns one of: win-x64, win-x86, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64
- $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
+function Get-MsalBrokerPlatformConfiguration {
+ $architecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
- switch ($arch) {
- 'X64' { return 'win-x64' }
- 'X86' { return 'win-x86' }
- 'Arm64' { return 'win-arm64' }
- default { return 'win-x64' }
+ $runtime = switch ($architecture) {
+ 'X64' { @{ Rid = 'win-x64'; NativeLibrary = 'msalruntime.dll' } }
+ 'X86' { @{ Rid = 'win-x86'; NativeLibrary = 'msalruntime_x86.dll' } }
+ 'Arm64' { @{ Rid = 'win-arm64'; NativeLibrary = 'msalruntime_arm64.dll' } }
+ default { $null }
+ }
+
+ if ($null -eq $runtime) {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on Windows architecture '$architecture'. Supported architectures: X64, X86, Arm64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'Windows'
+ Architecture = $architecture
+ RuntimeIdentifier = $runtime.Rid
+ NativeLibraryFileName = $runtime.NativeLibrary
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Windows
+ RedirectUri = $null
+ UseDefaultRedirectUri = $true
+ UseParentWindow = $true
+ ListOperatingSystemAccounts = $true
}
}
- elseif ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
- switch ($arch) {
- 'X64' { return 'linux-x64' }
- 'Arm64' { return 'linux-arm64' }
- default { return 'linux-x64' }
+
+ if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
+ if ($architecture -ne 'X64') {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on Linux architecture '$architecture' because the bundled native runtime only supports X64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'Linux'
+ Architecture = $architecture
+ RuntimeIdentifier = 'linux-x64'
+ NativeLibraryFileName = 'libmsalruntime.so'
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::Linux
+ RedirectUri = $null
+ UseDefaultRedirectUri = $true
+ UseParentWindow = $false
+ ListOperatingSystemAccounts = $true
}
}
- elseif ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
- switch ($arch) {
- 'X64' { return 'osx-x64' }
- 'Arm64' { return 'osx-arm64' }
- default { return 'osx-x64' }
+
+ if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
+ $runtime = switch ($architecture) {
+ 'X64' { @{ Rid = 'osx-x64'; NativeLibrary = 'msalruntime.dylib' } }
+ 'Arm64' { @{ Rid = 'osx-arm64'; NativeLibrary = 'msalruntime_arm64.dylib' } }
+ default { $null }
+ }
+
+ if ($null -eq $runtime) {
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on macOS architecture '$architecture'. Supported architectures: X64, Arm64."
+ )
+ }
+
+ return [pscustomobject]@{
+ OperatingSystem = 'macOS'
+ Architecture = $architecture
+ RuntimeIdentifier = $runtime.Rid
+ NativeLibraryFileName = $runtime.NativeLibrary
+ BrokerOperatingSystem = [Microsoft.Identity.Client.BrokerOptions+OperatingSystems]::OSX
+ RedirectUri = 'msauth.com.msauth.unsignedapp://auth'
+ UseDefaultRedirectUri = $false
+ UseParentWindow = $false
+ ListOperatingSystemAccounts = $false
}
}
- return $null
+ throw [PlatformNotSupportedException]::new(
+ "MSAL broker authentication is not supported on operating system '$([System.Runtime.InteropServices.RuntimeInformation]::OSDescription)'."
+ )
+}
+function Get-MsalRuntimeRidFolder {
+ (Get-MsalBrokerPlatformConfiguration).RuntimeIdentifier
}
function Import-MsalNativeRuntime {
param(
[Parameter(Mandatory)] [string] $ModuleRoot
)
- $rid = Get-MsalRuntimeRidFolder
- if ([string]::IsNullOrEmpty($rid)) { return }
-
- # IMPORTANT: your folder is lowercase "runtimes"
- $nativeDir = [Path]::Combine($ModuleRoot, 'runtimes', $rid, 'native')
- if (-not (Test-Path $nativeDir)) { return }
-
- $candidate = Get-ChildItem -Path $nativeDir -File |
- Where-Object {
- $_.Name -match '^msalruntime' -and $_.Extension -in @('.dll','.so','.dylib')
- } |
- Select-Object -First 1
+ $platform = Get-MsalBrokerPlatformConfiguration
+ $nativePath = [Path]::Combine(
+ $ModuleRoot,
+ 'runtimes',
+ $platform.RuntimeIdentifier,
+ 'native',
+ $platform.NativeLibraryFileName
+ )
- if (-not $candidate) {
- Write-Verbose "MSAL native runtime not found in $nativeDir"
- return
+ if (-not (Test-Path -LiteralPath $nativePath -PathType Leaf)) {
+ throw [System.IO.FileNotFoundException]::new(
+ "MSAL broker native runtime is missing for OS '$($platform.OperatingSystem)', architecture '$($platform.Architecture)', RID '$($platform.RuntimeIdentifier)'. Expected file: '$nativePath'.",
+ $nativePath
+ )
}
- if ($PSEdition -eq 'Core') {
- [System.Runtime.InteropServices.NativeLibrary]::Load($candidate.FullName) | Out-Null
+ if ($script:MsalNativeRuntimePath -eq $nativePath -and $script:MsalNativeRuntimeHandle -ne [IntPtr]::Zero) {
+ return $platform
}
- else {
- # Windows PowerShell 5.1 is Windows-only; LoadLibrary is fine
- if ($null -eq ('Kernel32' -as [type])) {
- $helperPath = [Path]::Combine($ModuleRoot, 'Helpers', 'Kernel32.cs')
- $helperDefinition = Get-Content $helperPath -Raw
- Add-Type -TypeDefinition $helperDefinition -ReferencedAssemblies @('System.Runtime.InteropServices') -WarningAction SilentlyContinue -IgnoreWarnings
+
+ try {
+ if ($PSEdition -eq 'Core') {
+ $nativeHandle = [System.Runtime.InteropServices.NativeLibrary]::Load($nativePath)
+ }
+ else {
+ if ($null -eq ('Kernel32' -as [type])) {
+ $helperPath = [Path]::Combine($ModuleRoot, 'Helpers', 'Kernel32.cs')
+ $helperDefinition = Get-Content $helperPath -Raw
+ Add-Type -TypeDefinition $helperDefinition -ReferencedAssemblies @('System.Runtime.InteropServices') -WarningAction SilentlyContinue -IgnoreWarnings
+ }
+
+ $nativeHandle = [Kernel32]::LoadLibrary($nativePath)
+ if ($nativeHandle -eq [IntPtr]::Zero) {
+ $errorCode = [Kernel32]::GetLastError()
+ throw [System.ComponentModel.Win32Exception]::new([int]$errorCode)
+ }
}
- [Kernel32]::LoadLibrary($candidate.FullName) | Out-Null
}
+ catch {
+ $message = "Failed to load MSAL broker native runtime for OS '$($platform.OperatingSystem)', architecture '$($platform.Architecture)', RID '$($platform.RuntimeIdentifier)' from '$nativePath'. $($_.Exception.Message)"
+ throw [System.DllNotFoundException]::new($message, $_.Exception)
+ }
+
+ $script:MsalNativeRuntimePath = $nativePath
+ $script:MsalNativeRuntimeHandle = $nativeHandle
+ Write-Information "Loaded MSAL native runtime: $nativePath"
- Write-Information "Loaded MSAL native runtime: $($candidate.FullName)"
+ return $platform
}
function Init {
param()
@@ -1588,6 +1668,8 @@ function Init {
if ($null -eq $script:AadAuthenticationFactories -or -not ($script:AadAuthenticationFactories -is [hashtable])) {
$script:AadAuthenticationFactories = @{}
}
+ $script:MsalNativeRuntimePath = $null
+ $script:MsalNativeRuntimeHandle = [IntPtr]::Zero
# Determine whether MSAL is already loaded
$msalAlreadyLoaded = $false
@@ -1681,7 +1763,8 @@ function Init {
}
# --------------------------------------------------------
- # Load Broker (if not loaded) + native runtime (cross-platform)
+ # Load the managed broker assembly. Its platform runtime is loaded lazily
+ # when a broker factory is requested so other authentication modes remain usable.
# --------------------------------------------------------
$brokerTypePresent = ($null -ne ('Microsoft.Identity.Client.Broker.BrokerExtension' -as [type]))
if (-not $brokerTypePresent) {
@@ -1691,15 +1774,12 @@ function Init {
Write-Warning ("MSAL version in session is {0} but module broker is {1}. Skipping broker load to avoid version conflicts. Broker-based auth may be unavailable; browser/device-code fallback still works." -f $msalLoadedVersion, $brokerVersion)
}
else {
- # Load broker extension assembly
if (-not (Test-Path $brokerDll)) {
Write-Warning "Broker DLL not found at $brokerDll. Broker-based auth will be unavailable."
}
else {
Add-Type -Path $brokerDll -ErrorAction Stop | Out-Null
- # Load native runtime for broker (Windows/Linux/macOS)
- Import-MsalNativeRuntime -ModuleRoot $moduleRoot
}
}
}
diff --git a/Readme.md b/Readme.md
index 9e7746b..a29ca7a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -12,7 +12,9 @@ Module supports standard AAD tenants as well as AAD B2C tenants. Module has been
**Obsolete flow:** The Resource Owner Password flow is obsolete and support for it will be removed in the next release because Microsoft's initiative to enforce multifactor authentication is incompatible with password-only sign-in. Use an interactive, device code, broker, confidential client, or managed identity flow instead.
-_Note_: Some authentication methods are not available in all scenarios (e.g. WAM and Windows integrated authentication only work on Windows)
+_Note_: Some authentication methods are not available in all scenarios (e.g. Windows integrated authentication only works on Windows).
+
+Broker authentication supports Windows x64, x86, and ARM64; macOS x64 and ARM64; and Linux x64. Linux ARM64 is not supported because the MSAL native runtime package does not currently include that runtime. macOS broker authentication requires an enrolled device with Company Portal, uses the unsigned-script redirect URI `msauth.com.msauth.unsignedapp://auth`, and interactive calls must run on the main thread.
Module comes with commands:
diff --git a/Tests/AadAuthenticationFactory.Tests.ps1 b/Tests/AadAuthenticationFactory.Tests.ps1
index 3a309f7..076b22b 100644
--- a/Tests/AadAuthenticationFactory.Tests.ps1
+++ b/Tests/AadAuthenticationFactory.Tests.ps1
@@ -2,7 +2,12 @@ Set-StrictMode -Version Latest
BeforeAll {
$repoRoot = Split-Path -Parent $PSScriptRoot
- $moduleManifestPath = Join-Path $repoRoot 'Module\AadAuthenticationFactory\AadAuthenticationFactory.psd1'
+ $moduleManifestPath = [System.IO.Path]::Combine(
+ $repoRoot,
+ 'Module',
+ 'AadAuthenticationFactory',
+ 'AadAuthenticationFactory.psd1'
+ )
if (-not (Test-Path $moduleManifestPath)) {
throw "Module manifest not found at $moduleManifestPath"
@@ -62,6 +67,36 @@ Describe 'Factory lifecycle' {
$retrievedFactory | Should -Not -BeNullOrEmpty
$retrievedFactory | Should -Be $createdFactory
}
+
+ It 'loads the native runtime and creates a broker factory' {
+ $module = Get-Module -Name AadAuthenticationFactory
+ $platform = & $module { Get-MsalBrokerPlatformConfiguration }
+ $expectedPath = [System.IO.Path]::Combine(
+ $module.ModuleBase,
+ 'runtimes',
+ $platform.RuntimeIdentifier,
+ 'native',
+ $platform.NativeLibraryFileName
+ )
+ $factoryName = "PesterBrokerFactory_$([Guid]::NewGuid().ToString('N'))"
+
+ $factory = New-AadAuthenticationFactory `
+ -TenantId 'organizations' `
+ -AuthMode Broker `
+ -DefaultScopes @('https://management.azure.com/.default') `
+ -Name $factoryName
+ $loadedRuntime = & $module {
+ [pscustomobject]@{
+ Path = $script:MsalNativeRuntimePath
+ Handle = $script:MsalNativeRuntimeHandle
+ }
+ }
+
+ $factory | Should -Not -BeNullOrEmpty
+ $expectedPath | Should -Exist
+ $loadedRuntime.Path | Should -Be $expectedPath
+ $loadedRuntime.Handle | Should -Not -Be ([IntPtr]::Zero)
+ }
}
Describe 'Confidential client integration' -Tag 'integration' {
@@ -107,5 +142,49 @@ Describe 'Confidential client integration' -Tag 'integration' {
$payload.aud | Should -Not -BeNullOrEmpty
}
}
-}
+ Describe 'Federated credential integration' -Tag 'integration' {
+ BeforeAll {
+ $script:federatedConfig = [pscustomobject]@{
+ TenantId = $env:AAD_TEST_TENANT_ID
+ Scope = $env:AAD_TEST_SCOPE
+ ClientId = $env:AAD_TEST_CLIENT_ID
+ ExpectedAudience = $env:AAD_TEST_EXPECTED_AUD
+ OidcAudience = $env:AAD_TEST_OIDC_AUDIENCE
+ OidcSubject = $env:AAD_TEST_OIDC_SUBJECT
+ }
+ }
+
+ It 'exchanges a GitHub OIDC credential for an Entra access token' {
+ $separator = if ($env:ACTIONS_ID_TOKEN_REQUEST_URL.Contains('?')) { '&' } else { '?' }
+ $encodedAudience = [Uri]::EscapeDataString($script:federatedConfig.OidcAudience)
+ $requestUri = "$($env:ACTIONS_ID_TOKEN_REQUEST_URL)$separator" + "audience=$encodedAudience"
+ $response = Invoke-RestMethod `
+ -Method Get `
+ -Uri $requestUri `
+ -Headers @{ Authorization = "Bearer $env:ACTIONS_ID_TOKEN_REQUEST_TOKEN" } `
+ -ErrorAction Stop
+ $githubToken = $response.value
+ $githubPayload = Test-AadToken -Token $githubToken -PayloadOnly
+
+ $githubToken | Should -Not -BeNullOrEmpty
+ $githubPayload.iss | Should -Be 'https://token.actions.githubusercontent.com'
+ $githubPayload.aud | Should -Be $script:federatedConfig.OidcAudience
+ $githubPayload.sub | Should -Be $script:federatedConfig.OidcSubject
+
+ $factory = New-AadAuthenticationFactory `
+ -TenantId $script:federatedConfig.TenantId `
+ -ClientId $script:federatedConfig.ClientId `
+ -Assertion $githubToken `
+ -DefaultScopes @($script:federatedConfig.Scope)
+
+ $tokenResult = Get-AadToken -Factory $factory -ErrorAction Stop
+ $payload = Test-AadToken -Token $tokenResult -PayloadOnly
+
+ $tokenResult | Should -Not -BeNullOrEmpty
+ $tokenResult.AccessToken | Should -Not -BeNullOrEmpty
+ $payload | Should -Not -BeNullOrEmpty
+ $payload.aud | Should -Be $script:federatedConfig.ExpectedAudience
+ }
+ }
+}
diff --git a/Workflow/Dependencies.csproj b/Workflow/Dependencies.csproj
new file mode 100644
index 0000000..a91fcc0
--- /dev/null
+++ b/Workflow/Dependencies.csproj
@@ -0,0 +1,20 @@
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Workflow/RunTests.ps1 b/Workflow/RunTests.ps1
index 40fae39..d018ad1 100644
--- a/Workflow/RunTests.ps1
+++ b/Workflow/RunTests.ps1
@@ -1,13 +1,16 @@
param(
[string]$RootPath = (Split-Path -Parent $PSScriptRoot),
[string]$ModuleName = 'AadAuthenticationFactory',
+ [string[]]$Tag,
+ [string[]]$ExcludeTag,
+ [switch]$RequireIntegrationConfiguration,
[switch]$CI
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
-$moduleManifestPath = Join-Path $RootPath "Module\$ModuleName\$ModuleName.psd1"
+$moduleManifestPath = [System.IO.Path]::Combine($RootPath, 'Module', $ModuleName, "$ModuleName.psd1")
$testsPath = Join-Path $RootPath 'Tests'
if (-not (Test-Path $moduleManifestPath)) {
@@ -18,6 +21,27 @@ if (-not (Test-Path $testsPath)) {
throw "Tests folder not found at $testsPath"
}
+if ($RequireIntegrationConfiguration.IsPresent) {
+ $requiredVariables = @(
+ 'AAD_TEST_TENANT_ID',
+ 'AAD_TEST_SCOPE',
+ 'AAD_TEST_CLIENT_ID',
+ 'AAD_TEST_CLIENT_SECRET',
+ 'AAD_TEST_EXPECTED_AUD',
+ 'AAD_TEST_OIDC_AUDIENCE',
+ 'AAD_TEST_OIDC_SUBJECT',
+ 'ACTIONS_ID_TOKEN_REQUEST_URL',
+ 'ACTIONS_ID_TOKEN_REQUEST_TOKEN'
+ )
+ $missingVariables = $requiredVariables.Where{
+ [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_))
+ }
+
+ if ($missingVariables.Count -gt 0) {
+ throw "Required integration environment variables are missing: $($missingVariables -join ', ')."
+ }
+}
+
$minimumPesterVersion = [Version]'5.5.0'
$pester = Get-Module -ListAvailable -Name Pester |
Sort-Object Version -Descending |
@@ -35,6 +59,14 @@ $configuration.Run.Path = $testsPath
$configuration.Run.PassThru = $true
$configuration.Output.Verbosity = 'Detailed'
+if (@($Tag).Count -gt 0) {
+ $configuration.Filter.Tag = $Tag
+}
+
+if (@($ExcludeTag).Count -gt 0) {
+ $configuration.Filter.ExcludeTag = $ExcludeTag
+}
+
if ($CI.IsPresent) {
$configuration.TestResult.Enabled = $true
$configuration.TestResult.OutputFormat = 'NUnitXml'
diff --git a/Workflow/SetupPackages.ps1 b/Workflow/SetupPackages.ps1
index ffe9cd4..7ae83a2 100644
--- a/Workflow/SetupPackages.ps1
+++ b/Workflow/SetupPackages.ps1
@@ -1,44 +1,100 @@
using namespace System.IO
-param
-(
- [string]$RootPath,
- [string]$nugetPath
+param(
+ [Parameter(Mandatory)]
+ [string]$RootPath
)
-$packagesDir = [Path]::Combine($RootPath,'packages')
-$modulePath = [Path]::Combine($RootPath,'Module','AadAuthenticationFactory')
-$sharedPath = [Path]::Combine($modulePath,'shared')
-
-&"$nugetPath" restore ([Path]::Combine($RootPath,'Workflow','packages.config')) -packagesDirectory $packagesDir | Out-Null
-"Updating packages in the module"
-$packages = ([xml](get-content -path ([Path]::Combine($RootPath,'Workflow','packages.config')) -raw)).packages.package
-$packages
-if(-not (Test-Path -Path $sharedPath)) { New-Item -ItemType Directory -Path $sharedPath | Out-Null}
-if(-not (Test-Path -Path ([Path]::Combine($sharedPath, 'net462')))) { New-Item -ItemType Directory -Path ([Path]::Combine($sharedPath, 'net462')) | Out-Null}
-if(-not (Test-Path -Path ([Path]::Combine($sharedPath, 'netstandard2.0')))) { New-Item -ItemType Directory -Path ([Path]::Combine($sharedPath, 'netstandard2.0')) | Out-Null}
-
-foreach($pkg in $packages.Where{$_ -ne 'Microsoft.Identity.Client.NativeInterop'})
-{
- "Processing: $($pkg.id) - $($pkg.version)"
- $packageFolder = [Path]::Combine($packagesDir, "$($pkg.id)`.$($pkg.version)")
- if(Test-Path -Path ([Path]::Combine($packageFolder,'lib','net462',"$($pkg.id)`.dll")))
- {
- " .NET Framework"
- Copy-Item -Path ([Path]::Combine($packageFolder,'lib','net462',"$($pkg.id)`.dll")) -Destination ([Path]::Combine($sharedPath,'net462')) -Force
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+$dependencyProject = [Path]::Combine($RootPath, 'Workflow', 'Dependencies.csproj')
+$packagesDir = [Path]::Combine($RootPath, 'packages')
+$modulePath = [Path]::Combine($RootPath, 'Module', 'AadAuthenticationFactory')
+$sharedPath = [Path]::Combine($modulePath, 'shared')
+
+if (-not (Test-Path -LiteralPath $dependencyProject -PathType Leaf)) {
+ throw "Dependency project not found at '$dependencyProject'."
+}
+
+& dotnet restore $dependencyProject --packages $packagesDir --nologo
+if ($LASTEXITCODE -ne 0) {
+ throw "dotnet restore failed with exit code $LASTEXITCODE."
+}
+
+$packageReferences = ([xml](Get-Content -LiteralPath $dependencyProject -Raw)).
+ Project.ItemGroup.PackageReference
+$targetFrameworks = @('net462', 'netstandard2.0')
+
+foreach ($targetFramework in $targetFrameworks) {
+ New-Item -ItemType Directory -Path ([Path]::Combine($sharedPath, $targetFramework)) -Force | Out-Null
+}
+
+foreach ($packageReference in $packageReferences) {
+ $packageId = [string]$packageReference.Include
+ $packageVersion = [string]$packageReference.Version
+ $packageFolder = [Path]::Combine(
+ $packagesDir,
+ $packageId.ToLowerInvariant(),
+ $packageVersion.ToLowerInvariant()
+ )
+
+ if (-not (Test-Path -LiteralPath $packageFolder -PathType Container)) {
+ throw "Restored package '$packageId' version '$packageVersion' was not found at '$packageFolder'."
}
- if(Test-Path -Path ([Path]::Combine($packageFolder,'lib','netstandard2.0',"$($pkg.id)`.dll")))
- {
- " .NET Core"
- Copy-Item -Path ([Path]::Combine($packageFolder,'lib','netstandard2.0',"$($pkg.id)`.dll")) -Destination ([Path]::Combine($sharedPath,'netstandard2.0')) -Force
+
+ Write-Host "Processing: $packageId - $packageVersion"
+ $copiedAssembly = $false
+
+ foreach ($targetFramework in $targetFrameworks) {
+ $assemblyPath = [Path]::Combine($packageFolder, 'lib', $targetFramework, "$packageId.dll")
+ if (Test-Path -LiteralPath $assemblyPath -PathType Leaf) {
+ Copy-Item -LiteralPath $assemblyPath -Destination ([Path]::Combine($sharedPath, $targetFramework)) -Force
+ $copiedAssembly = $true
+ Write-Host " $targetFramework"
+ }
}
+
+ if (-not $copiedAssembly) {
+ throw "Package '$packageId' version '$packageVersion' does not contain a supported module assembly."
+ }
+}
+
+$nativeInteropReference = $packageReferences |
+ Where-Object Include -EQ 'Microsoft.Identity.Client.NativeInterop' |
+ Select-Object -First 1
+
+if ($null -eq $nativeInteropReference) {
+ throw 'Microsoft.Identity.Client.NativeInterop is missing from the dependency project.'
+}
+
+$nativeInteropFolder = [Path]::Combine(
+ $packagesDir,
+ ([string]$nativeInteropReference.Include).ToLowerInvariant(),
+ ([string]$nativeInteropReference.Version).ToLowerInvariant()
+)
+$runtimeSource = [Path]::Combine($nativeInteropFolder, 'runtimes')
+
+if (-not (Test-Path -LiteralPath $runtimeSource -PathType Container)) {
+ throw "MSAL native runtime directory was not restored at '$runtimeSource'."
}
-#runtimes
-foreach($pkg in $packages.Where{$_.id -eq 'Microsoft.Identity.Client.NativeInterop'})
-{
- "Processing runtimes for: $($pkg.id) - $($pkg.version)"
- $packageFolder = [Path]::Combine($packagesDir, "$($pkg.id)`.$($pkg.version)")
- Copy-Item -Path ([Path]::Combine($packageFolder,'runtimes')) -Destination $modulePath -Recurse -Force
+Copy-Item -LiteralPath $runtimeSource -Destination $modulePath -Recurse -Force
+
+$requiredNativeAssets = @(
+ @('linux-x64', 'libmsalruntime.so'),
+ @('osx-arm64', 'msalruntime_arm64.dylib'),
+ @('osx-x64', 'msalruntime.dylib'),
+ @('win-arm64', 'msalruntime_arm64.dll'),
+ @('win-x64', 'msalruntime.dll'),
+ @('win-x86', 'msalruntime_x86.dll')
+)
+
+foreach ($asset in $requiredNativeAssets) {
+ $assetPath = [Path]::Combine($modulePath, 'runtimes', $asset[0], 'native', $asset[1])
+ if (-not (Test-Path -LiteralPath $assetPath -PathType Leaf)) {
+ throw "Required MSAL native runtime asset is missing at '$assetPath'."
+ }
}
+Write-Host 'Module dependencies restored successfully.'
diff --git a/Workflow/packages.config b/Workflow/packages.config
deleted file mode 100644
index 7733c85..0000000
--- a/Workflow/packages.config
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file