<# .SYNOPSIS Installs (or updates) the PowerShell modules used to administer Microsoft 365. .DESCRIPTION Jump-box / workstation bootstrap. Prepares the PowerShell Gallery, then installs the current Microsoft 365 admin modules: Microsoft.Graph Entra, users, groups, licenses, CA, Intune (Graph) ExchangeOnlineManagement Exchange Online + Security & Compliance (Connect-IPPSSession) MicrosoftTeams Teams admin PnP.PowerShell SharePoint / OneDrive (PowerShell 7+) Microsoft.Online.SharePoint.PowerShell Classic SharePoint Online (Windows PowerShell 5.1) Microsoft.PowerApps.Administration.PowerShell Microsoft.PowerApps.PowerShell Power Platform maker/admin MicrosoftPowerBIMgmt Power BI / Fabric tenant admin Optional: Microsoft.Graph.Beta Preview Graph cmdlets (-IncludeBeta) Az.Accounts, Az.Resources Azure / PIM for Azure RBAC (-IncludeAzure) Does NOT install retired MSOnline or AzureAD / AzureADPreview modules. .PARAMETER Scope CurrentUser (default, no elevation) or AllUsers (elevated session required). .PARAMETER IncludeBeta Also install Microsoft.Graph.Beta. .PARAMETER IncludeAzure Also install Az.Accounts and Az.Resources. .PARAMETER SkipSharePointLegacy Skip Microsoft.Online.SharePoint.PowerShell even on Windows PowerShell 5.1. .EXAMPLE .\Install-M365Modules.ps1 .EXAMPLE .\Install-M365Modules.ps1 -Scope AllUsers -IncludeAzure -IncludeBeta #> [CmdletBinding(SupportsShouldProcess = $true)] param( [ValidateSet('CurrentUser', 'AllUsers')] [string]$Scope = 'CurrentUser', [switch]$IncludeBeta, [switch]$IncludeAzure, [switch]$SkipSharePointLegacy ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' function Write-Step { param([string]$Message, [string]$Color = 'Cyan') Write-Host '' Write-Host "==> $Message" -ForegroundColor $Color } function Test-IsElevated { $id = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]$id return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Get-InstalledModuleVersion { param([string]$Name, [string]$InstallScope) $found = Get-Module -ListAvailable -Name $Name -ErrorAction SilentlyContinue | Sort-Object Version -Descending | Select-Object -First 1 if (-not $found) { return $null } return $found.Version } function Install-RequiredModule { param( [Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Workload, [string]$InstallScope, [switch]$AllowClobber ) $row = [pscustomobject]@{ Module = $Name Workload = $Workload Status = 'Skipped' Installed = '' Notes = '' } try { $gallery = Find-Module -Name $Name -Repository PSGallery -ErrorAction Stop $current = Get-InstalledModuleVersion -Name $Name -InstallScope $InstallScope $row.Installed = if ($current) { $current.ToString() } else { '(none)' } if ($current -and $current -ge $gallery.Version) { $row.Status = 'Up to date' $row.Notes = "Gallery $($gallery.Version)" Write-Host (" {0,-42} {1}" -f $Name, $row.Status) -ForegroundColor DarkGray return $row } $action = if ($current) { "Update $Name $($current) -> $($gallery.Version)" } else { "Install $Name $($gallery.Version)" } if (-not $PSCmdlet.ShouldProcess($Name, $action)) { $row.Status = 'WhatIf' $row.Notes = $action return $row } $params = @{ Name = $Name Repository = 'PSGallery' Scope = $InstallScope Force = $true AllowClobber = [bool]$AllowClobber SkipPublisherCheck = $false ErrorAction = 'Stop' } Install-Module @params $after = Get-InstalledModuleVersion -Name $Name -InstallScope $InstallScope $row.Installed = if ($after) { $after.ToString() } else { $gallery.Version.ToString() } $row.Status = if ($current) { 'Updated' } else { 'Installed' } Write-Host (" {0,-42} {1} ({2})" -f $Name, $row.Status, $row.Installed) -ForegroundColor Green } catch { $row.Status = 'Failed' $row.Notes = $_.Exception.Message Write-Host (" {0,-42} FAILED {1}" -f $Name, $row.Notes) -ForegroundColor Red } return $row } Write-Host '' Write-Host ' Microsoft 365 admin module installer' -ForegroundColor White Write-Host ' IT Outside the box' -ForegroundColor DarkGray Write-Host '' $psMajor = $PSVersionTable.PSVersion.Major Write-Host (" PowerShell {0}" -f $PSVersionTable.PSVersion) Write-Host (" Scope {0}" -f $Scope) Write-Host (" Edition {0}" -f $PSVersionTable.PSEdition) if ($Scope -eq 'AllUsers' -and -not (Test-IsElevated)) { throw 'AllUsers requires an elevated PowerShell session (Run as administrator).' } if ($PSVersionTable.PSEdition -eq 'Core' -and $psMajor -lt 7) { Write-Warning 'PowerShell 7.2+ is recommended for Microsoft.Graph and PnP.PowerShell.' } Write-Step 'TLS 1.2 and NuGet / PSGallery' try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { Write-Warning "Could not set TLS 1.2: $($_.Exception.Message)" } if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null } $repo = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue if (-not $repo) { throw 'PowerShell Gallery (PSGallery) is not registered on this machine.' } if ($repo.InstallationPolicy -ne 'Trusted') { if ($PSCmdlet.ShouldProcess('PSGallery', 'Set InstallationPolicy Trusted')) { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted } } Write-Host ' Gallery is ready.' -ForegroundColor DarkGray Write-Host ' Not installing MSOnline or AzureAD (retired). Use Microsoft.Graph instead.' -ForegroundColor Yellow $results = New-Object System.Collections.Generic.List[object] Write-Step 'Core Microsoft 365 modules' $core = @( @{ Name = 'Microsoft.Graph'; Workload = 'Entra / Graph / Intune'; AllowClobber = $true } @{ Name = 'ExchangeOnlineManagement'; Workload = 'Exchange Online + Purview SCC'; AllowClobber = $true } @{ Name = 'MicrosoftTeams'; Workload = 'Teams'; AllowClobber = $true } @{ Name = 'Microsoft.PowerApps.Administration.PowerShell'; Workload = 'Power Platform admin'; AllowClobber = $true } @{ Name = 'Microsoft.PowerApps.PowerShell'; Workload = 'Power Apps maker'; AllowClobber = $true } @{ Name = 'MicrosoftPowerBIMgmt'; Workload = 'Power BI / Fabric'; AllowClobber = $true } ) foreach ($mod in $core) { $results.Add((Install-RequiredModule -Name $mod.Name -Workload $mod.Workload -InstallScope $Scope -AllowClobber:$mod.AllowClobber)) } Write-Step 'SharePoint / OneDrive' if ($psMajor -ge 7) { $results.Add((Install-RequiredModule -Name 'PnP.PowerShell' -Workload 'SharePoint / OneDrive (PS7)' -InstallScope $Scope -AllowClobber)) Write-Host ' Classic Microsoft.Online.SharePoint.PowerShell is skipped on PowerShell 7. Use PnP.PowerShell.' -ForegroundColor DarkGray } else { Write-Host ' PnP.PowerShell requires PowerShell 7+. Install pwsh from https://aka.ms/powershell and re-run.' -ForegroundColor Yellow $skipPnp = [pscustomobject]@{ Module = 'PnP.PowerShell'; Workload = 'SharePoint / OneDrive (PS7)'; Status = 'Skipped'; Installed = ''; Notes = 'Needs PowerShell 7+' } $results.Add($skipPnp) if (-not $SkipSharePointLegacy) { $results.Add((Install-RequiredModule -Name 'Microsoft.Online.SharePoint.PowerShell' -Workload 'SharePoint Online (WinPS 5.1)' -InstallScope $Scope -AllowClobber)) } } if ($IncludeBeta) { Write-Step 'Graph beta' $results.Add((Install-RequiredModule -Name 'Microsoft.Graph.Beta' -Workload 'Graph beta' -InstallScope $Scope -AllowClobber)) } if ($IncludeAzure) { Write-Step 'Azure (optional)' $results.Add((Install-RequiredModule -Name 'Az.Accounts' -Workload 'Azure login' -InstallScope $Scope -AllowClobber)) $results.Add((Install-RequiredModule -Name 'Az.Resources' -Workload 'Azure RBAC / resources' -InstallScope $Scope -AllowClobber)) } Write-Step 'Summary' $results | Format-Table Module, Workload, Status, Installed, Notes -AutoSize $failed = @($results | Where-Object Status -eq 'Failed') if ($failed.Count) { Write-Host ("Finished with {0} failure(s). Re-run after fixing gallery / proxy / TLS." -f $failed.Count) -ForegroundColor Red exit 1 } Write-Host '' Write-Host 'Next: connect with the matching cmdlet' -ForegroundColor Cyan Write-Host @' Connect-MgGraph -NoWelcome Connect-ExchangeOnline Connect-IPPSSession Connect-MicrosoftTeams Connect-PnPOnline -Interactive -Url "https://contoso-admin.sharepoint.com" Connect-SPOService -Url "https://contoso-admin.sharepoint.com" Add-PowerAppsAccount Connect-PowerBIServiceAccount '@ -ForegroundColor Gray Write-Host 'Done.' -ForegroundColor Green exit 0