Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Clean Reinstall of the Microsoft Graph PowerShell Modules

When Graph cmdlets start throwing assembly errors, it's almost always mixed module versions. This removes every copy and puts back one matching set.

AT A GLANCERepair-GraphModule.ps1
What it does
Finds every Microsoft.Graph module installed through PowerShellGet, uninstalls all versions in a safe order, reinstalls the SDK (and optionally the beta modules), then checks that everything is on the same version.
Requires
  • PowerShell 7.2+ or Windows PowerShell 5.1
  • PowerShellGet (built in) and access to the PowerShell Gallery
Permissions
None for CurrentUser installs. Run elevated for AllUsers, or to remove copies that were installed for all users.
Runs on
Windows, macOS, Linux
Tested
Parse-checked and dry-run with mocked PowerShellGet cmdlets in PowerShell 7.4

Part 1 of the thread The Microsoft Graph toolbox

You run Connect-MgGraph and get "Could not load file or assembly Microsoft.Graph.Authentication". Or Get-MgUser suddenly complains about a method that doesn't exist. Nine times out of ten, the cause is the same: you've got more than one version of the Graph modules installed, and PowerShell loaded the authentication module from one and the users module from another.

The obvious fix, Uninstall-Module Microsoft.Graph -AllVersions, doesn't do much. Microsoft.Graph is just a small roll-up module. The thirty-odd Microsoft.Graph.* sub-modules that do the actual work stay right where they were, still mismatched.

This script removes all of them, every version, with Microsoft.Graph.Authentication last since everything else depends on it. Then it reinstalls a clean set and checks the versions line up. It also catches the long-retired Microsoft.Graph.Intune module, if it's still lurking, since it matches the same name pattern. (The original version of this post also closed all your PSSessions first. That doesn't hurt, but it doesn't help either. Remote sessions have nothing to do with which modules are loaded locally.)

Repair-GraphModule.ps1Download
<#
.SYNOPSIS
    Removes every installed version of the Microsoft Graph PowerShell SDK and installs one
    clean, matching set.
.DESCRIPTION
    Uninstall-Module Microsoft.Graph only removes the small roll-up module. The 30-plus
    Microsoft.Graph.* sub-modules stay behind, and mismatched versions of those are the usual
    cause of "Could not load file or assembly" and "method not found" errors. This script
    finds every Microsoft.Graph* module installed through PowerShellGet, removes all versions
    (Microsoft.Graph.Authentication last, since everything depends on it), reinstalls, and
    checks that the versions line up. Supports -WhatIf.
.PARAMETER Scope
    Where to reinstall: CurrentUser (no admin needed) or AllUsers (run elevated).
.PARAMETER IncludeBeta
    Also reinstall Microsoft.Graph.Beta. Beta modules are always removed if they're found.
.PARAMETER SkipInstall
    Only remove. Useful if you want to install a specific version yourself afterwards.
.EXAMPLE
    .\Repair-GraphModule.ps1 -WhatIf
.EXAMPLE
    .\Repair-GraphModule.ps1 -Scope AllUsers -IncludeBeta
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [ValidateSet('CurrentUser', 'AllUsers')]
    [string]$Scope = 'CurrentUser',
    [switch]$IncludeBeta,
    [switch]$SkipInstall
)

# Loaded assemblies can't be unloaded, so a module that's in use can't be removed cleanly.
$loaded = @(Get-Module -Name 'Microsoft.Graph*')
if ($loaded) {
    throw "Graph modules are loaded in this session ($($loaded.Name -join ', ')). Open a fresh window with 'pwsh -NoProfile' (or 'powershell -NoProfile') and run this again."
}

$installed = @(Get-InstalledModule -Name 'Microsoft.Graph*' -ErrorAction SilentlyContinue)
Write-Verbose "Found $($installed.Count) Graph module(s) installed through PowerShellGet."

# Everything else first, the authentication module last.
$ordered = @($installed | Where-Object Name -ne 'Microsoft.Graph.Authentication') + @($installed | Where-Object Name -eq 'Microsoft.Graph.Authentication')

foreach ($module in $ordered) {
    $row = [pscustomobject]@{ Module = $module.Name; Action = 'Uninstall (all versions)'; Version = $module.Version; Result = $null }
    if ($PSCmdlet.ShouldProcess($module.Name, 'Uninstall all versions')) {
        try {
            Uninstall-Module -Name $module.Name -AllVersions -Force -ErrorAction Stop
            $row.Result = 'Done'
        }
        catch { $row.Result = "Failed: $($_.Exception.Message)" }
    }
    else { $row.Result = 'WhatIf' }
    $row
}

if ($SkipInstall) { return }

$toInstall = @('Microsoft.Graph')
if ($IncludeBeta) { $toInstall += 'Microsoft.Graph.Beta' }

foreach ($name in $toInstall) {
    $row = [pscustomobject]@{ Module = $name; Action = "Install ($Scope)"; Version = $null; Result = $null }
    if ($PSCmdlet.ShouldProcess($name, "Install for $Scope")) {
        try {
            Install-Module -Name $name -Scope $Scope -Repository PSGallery -AllowClobber -Force -ErrorAction Stop
            $row.Version = (Get-InstalledModule -Name $name).Version
            $row.Result  = 'Done'
        }
        catch { $row.Result = "Failed: $($_.Exception.Message)" }
    }
    else { $row.Result = 'WhatIf' }
    $row
}

# Sanity check: every Graph sub-module should be on the same version.
if (-not $WhatIfPreference) {
    $versions = @(Get-InstalledModule -Name 'Microsoft.Graph*' -ErrorAction SilentlyContinue | Where-Object Name -notlike 'Microsoft.Graph.Beta*' | Select-Object -ExpandProperty Version -Unique)
    if ($versions.Count -gt 1) {
        Write-Warning "Graph modules are still on mixed versions: $($versions -join ', '). Check for copies in other module paths with: Get-Module Microsoft.Graph* -ListAvailable | Select-Object Name, Version, ModuleBase"
    }
    elseif ($versions.Count -eq 1) {
        Write-Host "All Microsoft.Graph modules are on version $($versions[0])." -ForegroundColor Green
    }
}

Parameters

ParameterTypeDefaultWhat it's for
-ScopestringCurrentUserWhere to reinstall. CurrentUser needs no admin rights. AllUsers does, and makes the modules available to every account on the machine, including scheduled tasks running as SYSTEM.
-IncludeBetaswitch—Also reinstall Microsoft.Graph.Beta. Beta modules are removed either way if they're found.
-SkipInstallswitch—Remove everything and stop, so you can install a specific version yourself.
-WhatIfswitch—List what would be removed and installed without doing any of it.

Run it

See what's installed and what would happen.

.\Repair-GraphModule.ps1 -WhatIf

The usual fix, from a fresh window with no profile.

pwsh -NoProfile -File .\Repair-GraphModule.ps1

A shared admin box or automation server, beta modules included.

.\Repair-GraphModule.ps1 -Scope AllUsers -IncludeBeta

Pin to a specific release afterwards.

.\Repair-GraphModule.ps1 -SkipInstall; Install-Module Microsoft.Graph -RequiredVersion 2.25.0 -Scope CurrentUser

What you'll see

Example outputvalues are illustrative
Module                         Action                   Version    Result
------                         ------                   -------    ------
Microsoft.Graph                Uninstall (all versions) 2.19.0     Done
Microsoft.Graph.Groups         Uninstall (all versions) 2.19.0     Done
Microsoft.Graph.Intune         Uninstall (all versions) 6.1907.1.0 Done
Microsoft.Graph.Users          Uninstall (all versions) 2.24.0     Done
Microsoft.Graph.Authentication Uninstall (all versions) 2.24.0     Done
Microsoft.Graph                Install (CurrentUser)    2.30.0     Done

All Microsoft.Graph modules are on version 2.30.0.

How it works

  1. Refuse to fight a loaded module. If any Microsoft.Graph* module is already in the session, the script stops and tells you to open a fresh window. Trying to uninstall in-use modules is how you end up with half-deleted folders.
  2. Find everything. Get-InstalledModule -Name 'Microsoft.Graph*' catches the roll-up, every sub-module, the beta modules and the old Microsoft.Graph.Intune.
  3. Remove in dependency order. Everything goes first, Microsoft.Graph.Authentication goes last, each with -AllVersions. A module that can't be removed becomes a Failed row instead of stopping the run.
  4. Reinstall one set. Install-Module Microsoft.Graph pulls every sub-module at the same version, plus Microsoft.Graph.Beta if you asked for it.
  5. Check the result. It compares the versions of everything that's installed now and warns if they still don't match.

Take it further

  • Only install what you use. If a script only needs users and groups, install Microsoft.Graph.Users and Microsoft.Graph.Groups (they pull in Microsoft.Graph.Authentication on their own). It's much faster and gives version drift less to work with.
  • Move to PSResourceGet. Install-PSResource and Uninstall-PSResource are the newer replacements for the PowerShellGet cmdlets and noticeably quicker with a module set this size.
  • Pin versions on automation servers. Scheduled jobs are happiest with a known version. Install it with -RequiredVersion and update on purpose, not whenever someone runs Update-Module.

Things that'll trip you up

  • Start from a clean session. Once a Graph module is loaded, its DLLs are locked until the process exits. The script refuses to run if any are loaded. Open a new window with -NoProfile, because a profile that imports Graph will load it before you get a prompt.
  • Windows PowerShell and PowerShell 7 keep separate modules. They use different module folders. If you use both, run the script in each one, or you'll fix one and wonder why the other is still broken.
  • AllUsers copies need admin to remove. If some of the modules were installed for all users, a non-elevated run shows them as Failed. Run it again as administrator to finish the job.
  • It only sees what PowerShellGet installed. Modules copied into a folder by hand, or bundled inside another tool, won't show up in Get-InstalledModule. If the version warning still fires, the command it suggests will show you where those strays live.
  • It takes a while. The full SDK is a lot of modules. Removing and reinstalling it can take several minutes, and that's normal. Nothing's hung.