Skip to content

Commit 0b7bfc4

Browse files
committed
Fixed macos loading issues and warning
1 parent ee8c8a8 commit 0b7bfc4

File tree

2 files changed

+141
-36
lines changed

2 files changed

+141
-36
lines changed

src/powershell/Initialize-Dependencies.ps1

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,11 @@ function Initialize-Dependencies {
131131
Write-Host -Object "`r`n" -ForegroundColor Yellow
132132
Write-Host -Object '⚠️ Warning: The ZeroTrustAssessment module is designed to run on Windows, in PowerShell 7.'
133133
Write-Host -Object 'Some pillars require modules that can only run on Windows PowerShell (Windows PowerShell 5.1) with implicit remoting.' -ForegroundColor Yellow
134-
# skipping module installation.
134+
Write-Host -Object 'The following Windows-only modules will not be available: AipService, Microsoft.Online.SharePoint.PowerShell' -ForegroundColor Yellow
135135
#endregion
136136
}
137-
elseif (-not $SkipModuleInstallation.IsPresent)
137+
138+
if (-not $SkipModuleInstallation.IsPresent)
138139
{
139140
Write-Host -Object "`r`n"
140141
Write-Host -Object ('Resolving {0} dependencies...' -f $allModuleDependencies.Count) -ForegroundColor Green
@@ -229,7 +230,7 @@ function Initialize-Dependencies {
229230
else {
230231
Write-Host -Object "`r`n"
231232
Write-Host -Object 'Asserting MSAL loading order for dependencies...' -ForegroundColor Green
232-
$helperPath = Join-Path -Path $PSScriptRoot -ChildPath "private\utility\Get-ModuleImportOrder.ps1" -Resolve -ErrorAction Stop
233+
$helperPath = Join-Path -Path $PSScriptRoot -ChildPath "private/utility/Get-ModuleImportOrder.ps1" -Resolve -ErrorAction Stop
233234
. $helperPath
234235
Write-Verbose -Message ('Module with DLLs to load: {0}' -f (([Microsoft.PowerShell.Commands.ModuleSpecification[]]$moduleManifest.RequiredModules).Name -join ', '))
235236
# This method does not necessarily load the right dll (it ignores the load logic from the modules)
@@ -261,6 +262,74 @@ function Initialize-Dependencies {
261262
}
262263
}
263264
}
265+
266+
#region Pre-load shared DLLs (newest version wins) to prevent type mismatch errors
267+
# Multiple modules ship different versions of shared DLLs like Microsoft.IdentityModel.Abstractions.dll.
268+
# .NET loads the first version it encounters into the default AssemblyLoadContext, and subsequent loads of
269+
# the same assembly name are ignored. If an older version loads first (e.g., EXO's 8.6.0.0), then code
270+
# compiled against a newer version (e.g., Graph's Azure.Identity expecting 8.6.1.0) will fail with
271+
# MissingMethodException because the type signatures don't match.
272+
# Fix: find and pre-load the newest version of each shared DLL across all dependency modules.
273+
#
274+
# IMPORTANT: Only pre-load low-level type-definition DLLs that are shared across all modules in the
275+
# default AssemblyLoadContext. Do NOT pre-load higher-level libraries like Azure.Identity.dll or
276+
# Azure.Core.dll here — Az.Accounts uses its own AssemblyLoadContext (ALC) to isolate its versions
277+
# of those DLLs, and pre-loading them into the default context interferes with ALC type unification,
278+
# causing "Entry point was not found" errors during Azure authentication.
279+
$sharedDllNames = @(
280+
'Microsoft.IdentityModel.Abstractions.dll'
281+
)
282+
283+
# Collect all module base directories from the import order candidates
284+
$moduleBaseDirs = $msalToLoadInOrder | ForEach-Object { Split-Path -Path $_.DLLPath -Parent }
285+
# Also include all candidate modules' full ModuleBase for broader search
286+
$allModuleCandidates = $msalToLoadInOrder | ForEach-Object {
287+
$candidateModule = Get-Module -Name $_.Name -ListAvailable | Select-Object -First 1
288+
if ($candidateModule) { $candidateModule.ModuleBase }
289+
}
290+
$searchDirs = @($moduleBaseDirs) + @($allModuleCandidates) | Where-Object { $_ } | Select-Object -Unique
291+
292+
foreach ($dllName in $sharedDllNames) {
293+
$assemblyShortName = [System.IO.Path]::GetFileNameWithoutExtension($dllName)
294+
295+
# Skip if already loaded
296+
if ([System.AppDomain]::CurrentDomain.GetAssemblies().Where{ $_.GetName().Name -eq $assemblyShortName }) {
297+
Write-Verbose -Message ("Shared DLL {0} is already loaded, skipping." -f $dllName)
298+
continue
299+
}
300+
301+
# Search all dependency module directories recursively for this DLL
302+
$allCopies = foreach ($dir in $searchDirs) {
303+
$searchRoot = $dir
304+
# Walk up to the module version root to search all subdirectories (e.g., Dependencies/, netCore/, lib/)
305+
$parentDir = Split-Path -Path $dir -Parent
306+
if ($parentDir -and (Test-Path $parentDir)) { $searchRoot = $parentDir }
307+
308+
Get-ChildItem -Path $searchRoot -Filter $dllName -File -Recurse -Force -ErrorAction SilentlyContinue
309+
}
310+
311+
if (-not $allCopies) {
312+
Write-Verbose -Message ("No copies of {0} found in dependency modules." -f $dllName)
313+
continue
314+
}
315+
316+
# Pick the newest version
317+
$newestDll = $allCopies |
318+
Where-Object { $_.VersionInfo.FileVersion } |
319+
Sort-Object -Property { [version]($_.VersionInfo.FileVersion) } -Descending |
320+
Select-Object -First 1
321+
322+
if ($newestDll) {
323+
try {
324+
$null = [System.Reflection.Assembly]::LoadFrom($newestDll.FullName)
325+
Write-Host -Object (' ✅ Pre-loaded {0} v{1} from {2}' -f $dllName, $newestDll.VersionInfo.FileVersion, $newestDll.DirectoryName) -ForegroundColor Green
326+
}
327+
catch {
328+
Write-Debug -Message ("Failed to pre-load shared DLL {0}: {1}" -f $newestDll.FullName, $_)
329+
}
330+
}
331+
}
332+
#endregion
264333
}
265334
}
266335
catch {

src/powershell/public/Connect-ZtAssessment.ps1

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,25 @@ function Connect-ZtAssessment {
117117
}
118118

119119

120-
$OrderedImport = Get-ModuleImportOrder -Name @('Az.Accounts', 'ExchangeOnlineManagement', 'Microsoft.Graph.Authentication', 'Microsoft.Online.SharePoint.PowerShell', 'AipService')
120+
$moduleNamesForImportOrder = @('Az.Accounts', 'ExchangeOnlineManagement', 'Microsoft.Graph.Authentication')
121+
if ($IsWindows) {
122+
$moduleNamesForImportOrder += @('Microsoft.Online.SharePoint.PowerShell', 'AipService')
123+
}
124+
$OrderedImport = Get-ModuleImportOrder -Name $moduleNamesForImportOrder
125+
126+
# Build the ordered list of service module names to connect.
127+
# Start with the MSAL-ordered modules, then append any expected modules not found by Get-ModuleImportOrder
128+
# so that connection is always attempted for requested services even if MSAL DLL detection fails.
129+
$connectionOrder = @($OrderedImport.Name)
130+
foreach ($name in $moduleNamesForImportOrder) {
131+
if ($name -notin $connectionOrder) {
132+
$connectionOrder += $name
133+
}
134+
}
121135

122-
Write-Verbose "Import Order: $($OrderedImport.Name -join ', ')"
136+
Write-Verbose "Import Order: $($connectionOrder -join ', ')"
123137

124-
switch ($OrderedImport.Name) {
138+
switch ($connectionOrder) {
125139
'Microsoft.Graph.Authentication' {
126140
if ($Service -contains 'Graph' -or $Service -contains 'All') {
127141
Write-Host "`nConnecting to Microsoft Graph" -ForegroundColor Yellow
@@ -133,6 +147,22 @@ function Connect-ZtAssessment {
133147
$contextTenantId = (Get-MgContext).TenantId
134148
}
135149
catch {
150+
$isMsalConflict = $false
151+
if ($_.Exception -is [System.MissingMethodException] -or $_.Exception.InnerException -is [System.MissingMethodException]) {
152+
$msalException = if ($_.Exception.InnerException -is [System.MissingMethodException]) { $_.Exception.InnerException } else { $_.Exception }
153+
if ($msalException.Message -like "*Microsoft.Identity.Client*") {
154+
$isMsalConflict = $true
155+
}
156+
}
157+
# Also detect the common pattern where the MissingMethodException is wrapped in another exception
158+
if (-not $isMsalConflict -and $_.Exception.Message -like "*Method not found*Microsoft.Identity.Client*") {
159+
$isMsalConflict = $true
160+
}
161+
162+
if ($isMsalConflict) {
163+
Write-Warning "DLL conflict detected: an incompatible version of Microsoft.Identity.Client.dll is loaded in this session."
164+
Write-Warning "Please RESTART your PowerShell session and import ZeroTrustAssessment before any other module, then run Connect-ZtAssessment again."
165+
}
136166
Stop-PSFFunction -Message "Failed to authenticate to Graph" -ErrorRecord $_ -EnableException $true -Cmdlet $PSCmdlet
137167
}
138168

@@ -195,7 +225,7 @@ function Connect-ZtAssessment {
195225
try {
196226
if ($UseDeviceCode -and $PSVersionTable.PSEdition -eq 'Desktop') {
197227
Write-Host 'The Exchange Online module in Windows PowerShell does not support device code flow authentication.' -ForegroundColor Red
198-
Write-Host '💡Please use the Exchange Online module in PowerShell Core.' -ForegroundColor Yellow
228+
Write-Host 'Please use the Exchange Online module in PowerShell Core.' -ForegroundColor Yellow
199229
}
200230
elseif ($UseDeviceCode) {
201231
Connect-ExchangeOnline -ShowBanner:$false -Device:$UseDeviceCode -ExchangeEnvironmentName $ExchangeEnvironmentName
@@ -376,40 +406,46 @@ function Connect-ZtAssessment {
376406
}
377407

378408
if ($Service -contains 'SharePointOnline' -or $Service -contains 'All') {
379-
Write-Host "`nConnecting to SharePoint Online" -ForegroundColor Yellow
380-
Write-PSFMessage 'Connecting to SharePoint Online'
381-
382-
# Determine Admin URL
383-
$adminUrl = $SharePointAdminUrl
384-
if (-not $adminUrl) {
385-
# Try to infer from Graph context
386-
if ($contextTenantId) {
387-
try {
388-
$org = Invoke-ZtGraphRequest -RelativeUri 'organization'
389-
$initialDomain = $org.verifiedDomains | Where-Object { $_.isInitial } | Select-Object -ExpandProperty name -First 1
390-
if ($initialDomain) {
391-
$tenantName = $initialDomain.Split('.')[0]
392-
$adminUrl = "https://$tenantName-admin.sharepoint.com"
393-
Write-Verbose "Inferred SharePoint Admin URL: $adminUrl"
409+
# SharePoint Online module only works on Windows
410+
if (-not $IsWindows) {
411+
Write-PSFMessage 'Skipping SharePoint Online connection - Microsoft.Online.SharePoint.PowerShell module is only supported on Windows.' -Level Warning
412+
}
413+
else {
414+
Write-Host "`nConnecting to SharePoint Online" -ForegroundColor Yellow
415+
Write-PSFMessage 'Connecting to SharePoint Online'
416+
417+
# Determine Admin URL
418+
$adminUrl = $SharePointAdminUrl
419+
if (-not $adminUrl) {
420+
# Try to infer from Graph context
421+
if ($contextTenantId) {
422+
try {
423+
$org = Invoke-ZtGraphRequest -RelativeUri 'organization'
424+
$initialDomain = $org.verifiedDomains | Where-Object { $_.isInitial } | Select-Object -ExpandProperty name -First 1
425+
if ($initialDomain) {
426+
$tenantName = $initialDomain.Split('.')[0]
427+
$adminUrl = "https://$tenantName-admin.sharepoint.com"
428+
Write-Verbose "Inferred SharePoint Admin URL: $adminUrl"
429+
}
430+
}
431+
catch {
432+
Write-Verbose "Failed to infer SharePoint Admin URL from Graph: $_"
394433
}
395-
}
396-
catch {
397-
Write-Verbose "Failed to infer SharePoint Admin URL from Graph: $_"
398434
}
399435
}
400-
}
401436

402-
if (-not $adminUrl) {
403-
Write-Warning "SharePoint Admin URL not provided and could not be inferred. Skipping SharePoint connection."
404-
}
405-
else {
406-
try {
407-
Connect-SPOService -Url $adminUrl -ErrorAction Stop
408-
Write-Verbose "Successfully connected to SharePoint Online."
437+
if (-not $adminUrl) {
438+
Write-Warning "SharePoint Admin URL not provided and could not be inferred. Skipping SharePoint connection."
409439
}
410-
catch {
411-
Write-Host "`nFailed to connect to SharePoint Online: $_" -ForegroundColor Red
412-
Write-PSFMessage "Failed to connect to SharePoint Online: $_" -Level Error
440+
else {
441+
try {
442+
Connect-SPOService -Url $adminUrl -ErrorAction Stop
443+
Write-Verbose "Successfully connected to SharePoint Online."
444+
}
445+
catch {
446+
Write-Host "`nFailed to connect to SharePoint Online: $_" -ForegroundColor Red
447+
Write-PSFMessage "Failed to connect to SharePoint Online: $_" -Level Error
448+
}
413449
}
414450
}
415451
}

0 commit comments

Comments
 (0)