________  ________  ________  ________  ________  ________  ________ 
 ╱        ╲╱        ╲╱    ╱   ╲╱        ╲╱    ╱   ╲╱        ╲╱    ╱   ╲
╱        _╱    ╱    ╱         ╱         ╱         ╱    ╱    ╱         ╱
╱       ╱╱        _╱         ╱       --╱╲__      ╱         ╱         ╱ 
╲_____╱╱ ╲____╱___╱╲________╱╲________╱   ╲_____╱╲___╱____╱╲__╱_____╱  

Script – Powershell Prep for Azure & Graph

The following script prepares the environment with the latest Graph API and relevant Az.* modules. System level modules will be removed to prevent conflicts and to contain versioning within the user profile. This script should be run within a fresh PS session and contains $requiredModules fit for managing and deploying Azure Virtual Desktop resources.

Note: Module “ActiveDirectory” is provided through RSAT which can be obtained from Microsoft on Windows 10 or enabled through Apps & Features on Windows 11.

PowerShell
# Clear output
Clear-Host

# Text style functions
function formatInfo { process { Write-Host "[$(Get-Date -Format 'MM/dd/yyyy HH:mm:ss')] [#] $_" -ForegroundColor cyan } }
function formatSuccess { process { Write-Host "[$(Get-Date -Format 'MM/dd/yyyy HH:mm:ss')] [+] $_" -ForegroundColor green } }
function formatWarning { process { Write-Host "[$(Get-Date -Format 'MM/dd/yyyy HH:mm:ss')] [!] $_" -ForegroundColor yellow } }
function formatError { process { Write-Host "[$(Get-Date -Format 'MM/dd/yyyy HH:mm:ss')] [-] $_" -ForegroundColor red } }
function formatPrompt { process { Write-Host "[$(Get-Date -Format 'MM/dd/yyyy HH:mm:ss')] [?] $_" -ForegroundColor gray -NoNewline } }
function formatBreak { process { Write-Host $_ -ForegroundColor white -BackgroundColor black } }

# Confirm user PS context
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Output "You are running PowerShell as Local Administrator. Please exit and run as a Named Account" | formatError
    Write-Output ""
    read-host "Press ENTER to continue..."
    $host.Exit()
}

# MOTD
Write-Output "#####################################" | formatBreak
Write-Output "## Az / Graph Module Update Script ##" | formatBreak
Write-Output "##---------------------------------##" | formatBreak
Write-Output "##         ~ Version 1.1 ~         ##" | formatBreak
Write-Output "#####################################`n" | formatBreak

# Set execution policy
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser -Force

# Suppress environment warnings
$ProgressPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
Set-Item -Path Env:\SuppressAzurePowerShellBreakingChangeWarnings -Value $true
Set-Item -Path Env:\SuppressAzureRmModulesRetiringWarning -Value $true

# Fix PS 5.1 Memory Limitation
$maximumfunctioncount = '32768'

#####################
## Session Modules ##
#####################

Write-Output "Verifying Installed Modules" | formatInfo

[System.Collections.ArrayList]$currentProviders = Get-PackageProvider
[System.Collections.ArrayList]$currentModules = Get-Module
[System.Collections.ArrayList]$installedModules = Get-Module -ListAvailable

# Check for NuGet package provider
if ("NuGet" -notin $currentProviders.Name) {
    $version = (($currentProviders | Where-Object { $_.Name -eq "NuGet" }).Version).ToString()
    Write-Output "Installed Package Provider ${provider} [${version}]" | formatSuccess
}

# Check for RSAT / AD Modules
if ("ActiveDirectory" -notin $installedModules.Name) {
    Write-Output "Module ActiveDirectory not installed. Please exit and install RSAT modules" | formatError
    Write-Output ""
    read-host "Press ENTER to continue..."
    $host.Exit()
}

# Delete conflicting system level modules (All modules should be installed at the currentUser scope)
if (Get-ChildItem 'C:\Program Files\WindowsPowerShell\Modules\' | Where-Object { ($_.PSIsContainer) -and (($_.Name -like "Microsoft.Graph*") -or ($_.Name -like "Az.*") -or ($_.Name -like "AzureRM*")) }) {
    Start-Process powershell.exe -Verb Runas -WindowStyle hidden -Wait -ArgumentList "-Command `
        Remove-Item 'C:\Program Files\WindowsPowerShell\Modules\Az.*' -Force -Recurse;`
        Remove-Item 'C:\Program Files\WindowsPowerShell\Modules\AzureRM*' -Force -Recurse;`
        Remove-Item 'C:\Program Files\WindowsPowerShell\Modules\Microsoft.Graph.*' -Force -Recurse;"
    Write-Output "Removed Modules Az.*, AzureRM*, and Microsoft.Graph* Forcefully" | formatError
}

$graphModules = Find-Module -Name 'Microsoft.Graph*'
[Version]$latestGraphVersion = ($graphModules | Where-Object Name -eq "Microsoft.Graph").Version

# If an older version of Graph Authentication exists, ALL other graph modules must be removed before the Auth module can be removed (Regardless of versions)
if ($installedModules | Where-Object { ($_.Name -eq "Microsoft.Graph.Authentication") -and ($_.Version -ne $latestGraphVersion) }) {
    $modules = $installedModules | Where-Object { ($_.Name -like "Microsoft.Graph*") -and ($_.Name -ne "Microsoft.Graph.Authentication") }  | Select Name -Unique
    Foreach ($module in $modules) {
        $moduleName = $module.Name
        $versions = $installedModules | Where-Object { $_.Name -eq $moduleName }
        Foreach ($version in $versions) {
            $moduleVersion = $version.Version
            Uninstall-Module $moduleName -RequiredVersion $moduleVersion
            $installedModules.Remove(($installedModules | Where-Object { ($_.Name -eq $moduleName) -and ($_.Version -eq $moduleVersion) }))
            Write-Output "Removed Module ${moduleName} [${moduleVersion}]" | formatError
        }
    }

    # Sanity Check Manual Deletion
    $psPath = "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules"
    $modules = Get-ChildItem $psPath | Where-Object { ($_.PSIsContainer) -and ($_.Name -like "Microsoft.Graph*") -and ($_.Name -ne "Microsoft.Graph.Authentication") }
    Foreach ($module in $modules) {
        $module | Remove-Item
    }

    # Remove Graph Authentication Module
    $moduleName = "Microsoft.Graph.Authentication"
    $modules = $installedModules | Where-Object { $_.Name -eq $moduleName }
    Foreach ($module in $modules) {
        $moduleVersion = $module.Version
        Uninstall-Module $moduleName -RequiredVersion $moduleVersion
        $installedModules.Remove(($installedModules | Where-Object { ($_.Name -eq $moduleName) -and ($_.Version -eq $moduleVersion) }))
        Write-Output "Removed Module ${moduleName} [${moduleVersion}]" | formatError
    }
}

# Remove conflicting module versions in current scope
$conflictModules = @('AzFilesHybrid','AzureAD','AzureADPreview')

if (Compare-Object -Referenceobject $installedModules -DifferenceObject $conflictModules -IncludeEqual | Where-Object { $_.sideIndicator -eq "==" }){
    Foreach ($conflictModule in $conflictModules) {
        if ($installedModules.Name -contains $conflictModule) {
            if ($conflictModule -eq "AzFilesHybrid") {
                Remove-Item "$($env:USERPROFILE)\Documents\WindowsPowerShell\Modules\AzFilesHybrid" -Force -Recurse | Out-Null
                $installedModules.Remove(($installedModules | Where-Object { $_.Name -eq $conflictModule }))
                Write-Output "Removed Module ${conflictModule} Forcefully" | formatError
            } else {
                $modules = $installedModules | Where-Object { $_.Name -eq $conflictModule }
                Foreach ($module in $modules) {
                    $moduleVersion = $module.Version
                    Uninstall-Module $conflictModule -RequiredVersion $moduleVersion
                    $installedModules.Remove(($installedModules | Where-Object { ($_.Name -eq $conflictModule) -and ($_.Version -eq $moduleVersion) }))
                    Write-Output "Removed Module ${conflictModule} [${moduleVersion}]" | formatError
                }
            }
        }
    }
}

# Remove all Az.* modules that do not match the latest version
$requiredModules = @('Az.Accounts','Az.Billing','Az.DesktopVirtualization','Az.Network','Az.Resources','Az.Storage','Az.Subscription','Az.PrivateDns')
$azModules = Find-Module -Name 'Az.*' | Where-Object { $_.Name -in $requiredModules }

Foreach ($requiredModule in $requiredModules) {
    [Version]$latestVersion = ($azModules | Where-Object { $_.Name -eq $requiredModule }).Version
    $staleModules = $installedModules | Where-Object { ($_.Name -eq $requiredModule) -and ($_.Version -lt $latestVersion) }

    if ($staleModules) {
        Foreach ($staleModule in $staleModules) {
            $moduleVersion = $staleModule.Version
            Uninstall-Module $requiredModule -RequiredVersion $moduleVersion
            $installedModules.Remove(($installedModules | Where-Object { ($_.Name -eq $requiredModule) -and ($_.Version -eq $moduleVersion) }))
            Write-Output "Removed Module ${requiredModule} [${moduleVersion}]" | formatError
        }
    }
}

# Populate required list with all available graph modules
$requiredGraphModules = $graphModules | Where-Object { ($_.Version -eq $latestGraphVersion) -and ($_.Name -ne "Microsoft.Graph") -and ($_.Name -notmatch "Microsoft.Graph.Beta*") }
Foreach ($graphObj in $requiredGraphModules) {
    $requiredModules += $graphObj.Name
}

# Install required modules
foreach ($module in ($requiredModules | Sort-Object)) {
    if ($module -notin $installedModules.Name) {
        Install-Module $module -Scope CurrentUser -Repository PSGallery -AllowClobber -Force 3>$null
        if ($module -like "AZ.*") {
            $version = ($azModules | Where-Object { $_.Name -eq $module }).Version
        } else {
            $version = ($graphModules | Where-Object { $_.Name -eq $module }).Version
        }
        Write-Output "Installed Module ${module} [${version}]" | formatSuccess
    }
}

# Set Config -- This must be done last to avoid importing stale modules prior to updates
Update-AzConfig -DisplaySurveyMessage $false | Out-Null
Update-AzConfig -DisplayBreakingChangeWarning $false | Out-Null
Update-AzConfig -DisableInstanceDiscovery $true | Out-Null
Update-AzConfig -EnableLoginByWam $false | Out-Null
Update-AzConfig -LoginExperienceV2 Off | Out-Null