Add functions to check and repair Microsoft.Graph module environment for compatibility
This commit is contained in:
+195
-12
@@ -151,6 +151,172 @@ function Test-PremiumAuthReportLicense {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-GraphModuleEnvironment {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Checks the Microsoft.Graph modules this script depends on for the
|
||||||
|
most common cause of hard-to-diagnose runtime failures: multiple
|
||||||
|
mismatched versions of the same module installed side by side.
|
||||||
|
.DESCRIPTION
|
||||||
|
When several versions of a Microsoft.Graph module are installed at
|
||||||
|
once, PowerShell can end up loading one version's assembly into the
|
||||||
|
session and then, mid-script, trying to auto-load a different
|
||||||
|
module that depends on a different version - which fails with
|
||||||
|
"assembly already loaded" or "could not load file or assembly"
|
||||||
|
errors. This surfaces as scattered per-user warnings deep into a
|
||||||
|
run rather than one clear message, so it's worth catching upfront.
|
||||||
|
.PARAMETER RequiredModuleNames
|
||||||
|
Names of the Microsoft.Graph modules this script depends on.
|
||||||
|
.OUTPUTS
|
||||||
|
[bool] $true if the environment looks consistent, $false if a
|
||||||
|
problem was found (details and a fix are written via Write-Warning).
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string[]]
|
||||||
|
$RequiredModuleNames
|
||||||
|
)
|
||||||
|
|
||||||
|
$requiredModuleNames = $RequiredModuleNames
|
||||||
|
|
||||||
|
# @(...) around each pipeline (not just an [array] cast on the variable)
|
||||||
|
# is required here: when a pipeline produces zero output, PowerShell
|
||||||
|
# assigns $null even to an [array]-typed variable, not an empty array -
|
||||||
|
# only wrapping the pipeline itself in @(...) guarantees a real array.
|
||||||
|
[array]$installed = @(Get-Module -ListAvailable -Name $requiredModuleNames -ErrorAction SilentlyContinue)
|
||||||
|
# Piped through Select-Object rather than direct dot-access ($installed.Name):
|
||||||
|
# member-enumeration on an empty array throws under StrictMode, and
|
||||||
|
# $installed IS legitimately empty on a machine with none of these
|
||||||
|
# modules installed at all.
|
||||||
|
[array]$installedNames = @($installed | Select-Object -ExpandProperty Name)
|
||||||
|
[array]$missingNames = @($requiredModuleNames | Where-Object { $_ -notin $installedNames })
|
||||||
|
[array]$allVersions = @($installed | Select-Object -ExpandProperty Version -Unique)
|
||||||
|
$hasVersionMismatch = $allVersions.Count -gt 1
|
||||||
|
|
||||||
|
# Display-only reference: whichever version most of the required
|
||||||
|
# modules currently have installed. Not the authoritative "correct"
|
||||||
|
# version (Repair-GraphModuleEnvironment resolves that separately from
|
||||||
|
# the Beta module's manifest / the gallery) - just enough to flag which
|
||||||
|
# rows are the odd ones out when printing the table below.
|
||||||
|
$referenceVersion = $null
|
||||||
|
if ($installed.Count -gt 0) {
|
||||||
|
$referenceVersion = ($installed | Group-Object Version | Sort-Object Count -Descending | Select-Object -First 1).Name
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host 'Checking required Microsoft.Graph modules...' -ForegroundColor Cyan
|
||||||
|
$nameColumnWidth = 48
|
||||||
|
foreach ($name in $requiredModuleNames) {
|
||||||
|
[array]$moduleVersions = @($installed | Where-Object { $_.Name -eq $name } | Select-Object -ExpandProperty Version | Sort-Object -Descending)
|
||||||
|
|
||||||
|
$isOk = $false
|
||||||
|
if ($moduleVersions.Count -eq 1) {
|
||||||
|
if ($moduleVersions[0].ToString() -eq $referenceVersion) {
|
||||||
|
$isOk = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isOk) {
|
||||||
|
Write-Host (" [OK] {0,-$nameColumnWidth} {1}" -f $name, $moduleVersions[0]) -ForegroundColor Green
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$versionDisplay = if ($moduleVersions.Count -gt 0) { $moduleVersions -join ', ' } else { 'not installed' }
|
||||||
|
Write-Host (" [FAIL] {0,-$nameColumnWidth} {1}" -f $name, $versionDisplay) -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingNames.Count -eq 0 -and -not $hasVersionMismatch) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning 'Microsoft.Graph module check found a problem (see [FAIL] rows above) likely to cause runtime failures (e.g. "assembly already loaded" errors partway through the report).'
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Repair-GraphModuleEnvironment {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installs missing Microsoft.Graph modules and removes mismatched
|
||||||
|
versions of the ones this script depends on.
|
||||||
|
.DESCRIPTION
|
||||||
|
Determines the single version every required module must share -
|
||||||
|
the exact version Microsoft.Graph.Beta.Identity.SignIns' own
|
||||||
|
manifest pins for Microsoft.Graph.Authentication (a RequiredVersion,
|
||||||
|
not just a minimum), or, if that module isn't installed at all yet,
|
||||||
|
the latest version currently published on the gallery. For each
|
||||||
|
required module, it then installs that exact version if missing and
|
||||||
|
offers to remove any other installed version of it.
|
||||||
|
|
||||||
|
Every install/uninstall changes state outside this repository (and
|
||||||
|
removing a module version can affect other scripts on the machine
|
||||||
|
that depend on it), so this supports -WhatIf/-Confirm and prompts
|
||||||
|
for confirmation by default (ConfirmImpact = 'High').
|
||||||
|
.PARAMETER RequiredModuleNames
|
||||||
|
Names of the Microsoft.Graph modules this script depends on.
|
||||||
|
#>
|
||||||
|
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string[]]
|
||||||
|
$RequiredModuleNames
|
||||||
|
)
|
||||||
|
|
||||||
|
$betaModuleName = 'Microsoft.Graph.Beta.Identity.SignIns'
|
||||||
|
$betaModule = Get-Module -ListAvailable -Name $betaModuleName -ErrorAction SilentlyContinue |
|
||||||
|
Sort-Object Version -Descending | Select-Object -First 1
|
||||||
|
|
||||||
|
if ($betaModule) {
|
||||||
|
$pin = $betaModule.RequiredModules | Where-Object { $_.Name -eq 'Microsoft.Graph.Authentication' } | Select-Object -First 1
|
||||||
|
$targetVersion = if ($pin) { $pin.Version } else { $betaModule.Version }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Write-Host "Looking up the latest published version of $betaModuleName..." -ForegroundColor Cyan
|
||||||
|
$targetVersion = (Find-Module -Name $betaModuleName -ErrorAction Stop).Version
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not look up the latest version of $betaModuleName from the gallery: $_"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Target Microsoft.Graph module version: $targetVersion" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
foreach ($name in $RequiredModuleNames) {
|
||||||
|
[array]$copies = @(Get-Module -ListAvailable -Name $name -ErrorAction SilentlyContinue)
|
||||||
|
$hasTarget = $copies | Where-Object { $_.Version -eq $targetVersion }
|
||||||
|
|
||||||
|
if (-not $hasTarget) {
|
||||||
|
if ($PSCmdlet.ShouldProcess("$name $targetVersion", 'Install-Module')) {
|
||||||
|
try {
|
||||||
|
Install-Module -Name $name -RequiredVersion $targetVersion -Scope CurrentUser -Force -Confirm:$false -AllowClobber -ErrorAction Stop
|
||||||
|
Write-Host "Installed $name $targetVersion" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not install $name ${targetVersion}: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "$name $targetVersion was not installed - the script cannot continue without it."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[array]$stale = @($copies | Where-Object { $_.Version -ne $targetVersion })
|
||||||
|
foreach ($old in $stale) {
|
||||||
|
if ($PSCmdlet.ShouldProcess("$name $($old.Version)", 'Uninstall-Module')) {
|
||||||
|
try {
|
||||||
|
Uninstall-Module -Name $name -RequiredVersion $old.Version -Force -Confirm:$false -ErrorAction Stop
|
||||||
|
Write-Host "Removed $name $($old.Version)" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not remove $name $($old.Version) - it may be installed system-wide (Scope AllUsers) and need an elevated PowerShell session to remove: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ConvertTo-FriendlyAuthMethod {
|
function ConvertTo-FriendlyAuthMethod {
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
@@ -410,23 +576,34 @@ $($methodRows -join "`n")
|
|||||||
|
|
||||||
# --- Main --------------------------------------------------------------
|
# --- Main --------------------------------------------------------------
|
||||||
try {
|
try {
|
||||||
# Fail fast and loudly if a module this script depends on isn't
|
# Fail fast and loudly if the Microsoft.Graph modules this script
|
||||||
# installed OR can't actually be loaded (e.g. an assembly-version
|
# depends on aren't installed OR can't actually be loaded (e.g. an
|
||||||
# conflict with another installed Microsoft.Graph module version),
|
# assembly-version conflict between mismatched module versions),
|
||||||
# rather than letting every per-user call into it fail silently later
|
# rather than letting every per-user call into them fail silently later
|
||||||
# and produce an incomplete/misleading report. Microsoft.Graph.Beta.
|
# and produce an incomplete/misleading report. If a problem is found,
|
||||||
# Identity.SignIns provides Get-MgBetaUserAuthenticationSignInPreference,
|
# offer to fix it (install what's missing, remove mismatched versions)
|
||||||
# which reads each user's actual default sign-in method - there is no
|
# rather than just describing the fix and stopping.
|
||||||
# v1.0/GA equivalent, this data is beta-only in Microsoft Graph.
|
$requiredGraphModuleNames = @(
|
||||||
$requiredBetaModule = 'Microsoft.Graph.Beta.Identity.SignIns'
|
'Microsoft.Graph.Authentication'
|
||||||
if (-not (Get-Module -ListAvailable -Name $requiredBetaModule)) {
|
'Microsoft.Graph.Beta.Identity.SignIns'
|
||||||
throw "Required module '$requiredBetaModule' is not installed. It's needed to read each user's actual default sign-in method. Install it with: Install-Module $requiredBetaModule -Scope CurrentUser"
|
'Microsoft.Graph.Identity.DirectoryManagement'
|
||||||
|
'Microsoft.Graph.Identity.SignIns'
|
||||||
|
'Microsoft.Graph.Reports'
|
||||||
|
'Microsoft.Graph.Users'
|
||||||
|
)
|
||||||
|
if (-not (Test-GraphModuleEnvironment -RequiredModuleNames $requiredGraphModuleNames)) {
|
||||||
|
Repair-GraphModuleEnvironment -RequiredModuleNames $requiredGraphModuleNames
|
||||||
|
if (-not (Test-GraphModuleEnvironment -RequiredModuleNames $requiredGraphModuleNames)) {
|
||||||
|
throw 'Microsoft.Graph module environment still has a problem after the repair attempt - see the warnings above. Close this terminal window, open a new one, and try again; if it still fails, some of the old versions may be installed system-wide (Scope AllUsers) and need to be removed from an elevated PowerShell session.'
|
||||||
}
|
}
|
||||||
|
Write-Host 'Microsoft.Graph module environment repaired.' -ForegroundColor Green
|
||||||
|
}
|
||||||
|
$requiredBetaModule = 'Microsoft.Graph.Beta.Identity.SignIns'
|
||||||
try {
|
try {
|
||||||
Import-Module -Name $requiredBetaModule -ErrorAction Stop
|
Import-Module -Name $requiredBetaModule -ErrorAction Stop
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw "Required module '$requiredBetaModule' is installed but failed to load - this is usually caused by multiple mismatched Microsoft.Graph module versions installed side by side (an assembly-version conflict), not a missing module. Run: Get-Module -ListAvailable 'Microsoft.Graph*' | Group-Object Name | Where-Object { (`$_.Group.Version | Sort-Object -Unique).Count -gt 1 } to find the mismatched ones, update them all to the same version, and remove the old ones. Underlying error: $_"
|
throw "Required module '$requiredBetaModule' failed to load even though the version check passed - see the underlying error for details: $_"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Verbose 'Connecting to Microsoft Graph...'
|
Write-Verbose 'Connecting to Microsoft Graph...'
|
||||||
@@ -624,7 +801,13 @@ catch {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
# -ErrorAction SilentlyContinue does not suppress "command not found" -
|
||||||
|
# that's a terminating parse/discovery-time error, not a stream event -
|
||||||
|
# so guard explicitly for the case where the script failed before
|
||||||
|
# Microsoft.Graph.Authentication was ever loaded (e.g. missing modules).
|
||||||
|
if (Get-Command -Name Disconnect-MgGraph -ErrorAction SilentlyContinue) {
|
||||||
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
|
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user