If you manage Microsoft 365, Exchange Online, or Microsoft Entra ID with automation, you have likely relied on Windows PowerShell 5.1 for years. However, Microsoft has officially initiated the phase-out timeline for running the Microsoft Graph PowerShell SDK on Windows PowerShell 5.1, steering all enterprise automation toward PowerShell 7.x (Core) ahead of the upcoming Graph SDK v3.0 release.
Continuing to run Microsoft Graph scripts on legacy Windows PowerShell 5.1 introduces memory bloat, slower REST API serialization, and imminent deprecation roadblocks.
In this guide, we walk through setting up PowerShell 7.x alongside 5.1, configuring certificate-based authentication for unattended automation, and scheduling production jobs cleanly.
---
Why Move to PowerShell 7 for Microsoft Graph?
Windows PowerShell 5.1 is tied to the legacy .NET Framework (4.x), which was frozen years ago. In contrast, PowerShell 7 runs on modern .NET Core, offering major advantages for cloud administration:
1. Massive Performance Boost: API payload parsing (ConvertFrom-Json and Invoke-RestMethod) is up to 3x–5x faster.
2. Parallel Execution: Native support for ForEach-Object -Parallel allows multi-threaded tenant reporting without complex runspace pools.
3. No SDK Module Collisions: PowerShell 7 isolates modules and avoids .NET assembly binding errors frequently experienced when running multiple Azure and Graph submodules together.
4. Guaranteed Support: Future Microsoft Graph SDK versions (starting with v3.0) will require modern PowerShell runtimes.
---
Step 1: Installing PowerShell 7 in Enterprise Environments
PowerShell 7 installs side-by-side with Windows PowerShell 5.1—it does not overwrite powershell.exe. Instead, it runs as pwsh.exe.
Option A: Install via WinGet (Quickest)
winget install --id Microsoft.PowerShell --source winget --accept-package-agreements --accept-source-agreements
Option B: Silent MSI Installation (for Servers & Task Workers)
For headless servers or management jump boxes, download the x64 MSI and run a silent installation with telemetry disabled:
$msiUrl = "https://github.com/PowerShell/PowerShell/releases/download/v7.4.5/PowerShell-7.4.5-win-x64.msi"
$installerPath = "$env:TEMP\PowerShell-7.msi"
Invoke-WebRequest -Uri $msiUrl -OutFile $installerPath -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i `"$installerPath`" /qn /norestart ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1" -Wait
Remove-Item $installerPath -Force
Verify your installation:
pwsh -NoProfile -Command "$PSVersionTable.PSVersion"
---
Step 2: Installing the Microsoft Graph SDK in PowerShell 7
Once PowerShell 7 is installed, launch pwsh.exe. We recommend installing only the required submodules (such as Authentication, Users, and Identity.DirectoryManagement) rather than the monolithic Microsoft.Graph umbrella module to keep load times fast.
# Run inside pwsh (PowerShell 7)
$modules = @(
"Microsoft.Graph.Authentication",
"Microsoft.Graph.Users",
"Microsoft.Graph.Identity.DirectoryManagement",
"Microsoft.Graph.Reports"
)
foreach ($mod in $modules) {
if (-not (Get-Module -ListAvailable -Name $mod)) {
Write-Host "Installing $mod..." -ForegroundColor Cyan
Install-Module -Name $mod -Scope AllUsers -Repository PSGallery -Force -AllowClobber
}
}
---
Step 3: Unattended Certificate-Based Authentication
Enterprise automation should never use hardcoded credentials or interactive prompts. Instead, register an enterprise application in Entra ID and authenticate using an X.509 certificate.
1. Connect Using Certificate Thumbprint (Stored in Local Machine Store)
If the certificate is installed in Cert:\LocalMachine\My:
$tenantId = "your-tenant-id-here"
$appId = "your-client-app-id-here"
$certThumb = "A1B2C3D4E5F678901234567890ABCDEF12345678"
Connect-MgGraph -ClientId $appId -TenantId $tenantId -CertificateThumbprint $certThumb -NoWelcome
2. Connect Using a Local .pfx or Certificate Object
For servers where the certificate file is secured in a credential vault or encrypted file path:
$tenantId = "your-tenant-id-here"
$appId = "your-client-app-id-here"
$pfxPath = "C:\Certs\MgAutomationCert.pfx"
$pfxPassword = ConvertTo-SecureString "YourSecurePassphrase" -AsPlainText -Force
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxPath, $pfxPassword)
Connect-MgGraph -ClientId $appId -TenantId $tenantId -Certificate $cert -NoWelcome
---
Step 4: Production-Ready Script Template for PowerShell 7
Here is a robust, production-ready script template that connects to Microsoft Graph in PowerShell 7, processes inactive users using parallel processing, and logs outputs cleanly:
<#
.SYNOPSIS
Production template for unattended Microsoft Graph automation under PowerShell 7.
.NOTES
Author: Sunil Chauhan (LAB 365)
Runtime: pwsh (PowerShell 7.x)
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$LogPath = "C:\Logs\GraphAutomation.log"
)
# Ensure script is running in PowerShell 7+
if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Error "This script must run under PowerShell 7.x or higher. Current version: $($PSVersionTable.PSVersion)"
exit 1
}
$tenantId = "11112222-3333-4444-5555-666677778888"
$clientId = "aaaabbbb-cccc-dddd-eeee-ffff00001111"
$certThumb = "A1B2C3D4E5F678901234567890ABCDEF12345678"
# Configure logging
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$line = "[$timestamp] [$Level] $Message"
Write-Host $line
Add-Content -Path $LogPath -Value $line -ErrorAction SilentlyContinue
}
try {
Write-Log "Connecting to Microsoft Graph via Certificate..."
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -CertificateThumbprint $certThumb -NoWelcome -ErrorAction Stop
Write-Log "Connected successfully."
# Fetch users with specific properties
Write-Log "Querying tenant user accounts..."
$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled, SignInActivity -Select Id, DisplayName, UserPrincipalName, AccountEnabled, SignInActivity
Write-Log "Retrieved $($users.Count) user records."
# Leverage PowerShell 7 Parallel execution to inspect sign-in activity
$thresholdDate = (Get-Date).AddDays(-90)
$inactiveUsers = $users | ForEach-Object -Parallel {
$user = $_
$lastSignIn = $user.SignInActivity.LastSignInDateTime
if ($lastSignIn -and [datetime]$lastSignIn -lt ($using:thresholdDate)) {
[PSCustomObject]@{
DisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
AccountEnabled = $user.AccountEnabled
LastSignInDateTime = $lastSignIn
}
}
} -ThrottleLimit 10
Write-Log "Found $($inactiveUsers.Count) users inactive for over 90 days."
# Export report
$reportPath = "C:\Reports\InactiveUsers-$(Get-Date -Format 'yyyyMMdd').csv"
$inactiveUsers | Export-Csv -Path $reportPath -NoTypeInformation
Write-Log "Report exported to: $reportPath"
} catch {
Write-Log "FATAL ERROR: $($_.Exception.Message)" "ERROR"
exit 1
} finally {
Write-Log "Disconnecting Graph session..."
Disconnect-MgGraph | Out-Null
Write-Log "Automation job completed."
}
---
Step 5: Scheduling the Job in Windows Task Scheduler
When scheduling PowerShell 7 automation, remember that the executable changes from powershell.exe to pwsh.exe.
Configure your Scheduled Task Action with the following parameters:
- Program/script:
C:\Program Files\PowerShell\7\pwsh.exe
- Add arguments (optional):
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\GraphAutomation.ps1"
- Start in:
C:\Scripts\
---
Summary Checklist
| Action Item | Windows PowerShell 5.1 | PowerShell 7.x (Recommended) |
|---|---|---|
| Executable | powershell.exe | pwsh.exe |
| Graph SDK v3.0 Compatibility | Deprecated / Discontinued | Fully Supported |
| Multi-threading | Requires runspaces | Built-in ForEach-Object -Parallel |
| REST Serialization | Slow .NET 4.8 engine | High-speed .NET Core engine |
| Authentication | Interactive / Legacy | App Registration + Certificate |