Level Verified

Windows 11 Upgrade Hardware Check Script

Quickly assess whether your endpoints meet Windows 11 hardware requirements. This script validates disk space, memory, CPU specs, TPM version, and Secure Boot status to help you identify upgrade-ready devices.

Import into Level

Problem overview

Manually verifying each component required for Windows 11—such as disk space, memory, TPM version, and CPU capabilities—can be both time-consuming and prone to human error. This script streamlines that process by automatically detecting and reporting hardware readiness, helping IT teams and MSPs quickly identify machines suitable for Windows 11 upgrades.

PowerShell 300s timeout Runs as Local system Windows
<#
Level Library
Windows 11 25H2 Upgrade Readiness Check

Checks whether a Windows 11 23H2 device appears ready for a Windows 11 25H2 feature update.

Exit codes:
0 = Ready / already on 25H2
1 = Not ready / unsupported / not applicable
#>

$exitCode = 0

function Write-Color {
    param(
        [string]$Message,
        [string]$Color
    )

    $colorHash = @{
        'Red'    = [ConsoleColor]::Red
        'Green'  = [ConsoleColor]::Green
        'Yellow' = [ConsoleColor]::Yellow
    }

    Write-Host $Message -ForegroundColor $colorHash[$Color]
}

function Print-CheckResult {
    param(
        [string]$CheckName,
        [string]$Status,
        [string]$Details
    )

    switch ($Status) {
        "PASS" { Write-Color "$($CheckName): $($Status); $($Details)" "Green" }
        "FAIL" { Write-Color "$($CheckName): $($Status); $($Details)" "Red" }
        default { Write-Color "$($CheckName): $($Status); $($Details)" "Yellow" }
    }
}

# OS / Version Guard
try {
    $os = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
    $productName = $os.ProductName
    $displayVersion = $os.DisplayVersion
    $currentBuild = $os.CurrentBuild
    $ubr = $os.UBR

    Print-CheckResult "OS" "PASS" "Detected $productName $displayVersion, Build $currentBuild.$ubr"

    if ($productName -notlike "*Windows 11*") {
        Print-CheckResult "OS" "FAIL" "This check is intended for Windows 11 devices only."
        exit 1
    }

    if ($displayVersion -eq "25H2") {
        Print-CheckResult "OS" "PASS" "Device is already running Windows 11 25H2."
        exit 0
    }

    if ($displayVersion -ne "23H2") {
        Print-CheckResult "OS" "FAIL" "Expected Windows 11 23H2, but found $displayVersion."
        exit 1
    }
}
catch {
    Print-CheckResult "OS" "FAIL" "Unable to determine Windows version. $($_.Exception.Message)"
    exit 1
}

[int]$MinOSDiskSizeGB = 64
[int]$MinMemoryGB = 4
[uint32]$MinClockSpeedMHz = 1000
[uint32]$MinLogicalCores = 2
[uint16]$RequiredAddressWidth = 64

# Storage
try {
    $osDrive = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty SystemDrive
    $osDriveSize = Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID='$osDrive'" |
        Select-Object @{Name = "SizeGB"; Expression = { $_.Size / 1GB -as [int] } }

    if ($null -eq $osDriveSize) {
        Print-CheckResult "Storage" "FAIL" "Storage is null"
        $exitCode = 1
    }
    elseif ($osDriveSize.SizeGB -lt $MinOSDiskSizeGB) {
        Print-CheckResult "Storage" "FAIL" "OSDiskSize=$($osDriveSize.SizeGB)GB, Minimum Required=$MinOSDiskSizeGB GB"
        $exitCode = 1
    }
    else {
        Print-CheckResult "Storage" "PASS" "OSDiskSize=$($osDriveSize.SizeGB)GB"
    }
}
catch {
    Print-CheckResult "Storage" "UNDETERMINED" "Exception: $($_.Exception.Message)"
    $exitCode = 1
}

# Memory
try {
    $memory = Get-WmiObject Win32_PhysicalMemory |
        Measure-Object -Property Capacity -Sum |
        Select-Object @{Name = "SizeGB"; Expression = { $_.Sum / 1GB -as [int] } }

    if ($null -eq $memory) {
        Print-CheckResult "Memory" "FAIL" "Memory is null"
        $exitCode = 1
    }
    elseif ($memory.SizeGB -lt $MinMemoryGB) {
        Print-CheckResult "Memory" "FAIL" "SystemMemory=$($memory.SizeGB)GB, Minimum Required=$MinMemoryGB GB"
        $exitCode = 1
    }
    else {
        Print-CheckResult "Memory" "PASS" "SystemMemory=$($memory.SizeGB)GB"
    }
}
catch {
    Print-CheckResult "Memory" "UNDETERMINED" "Exception: $($_.Exception.Message)"
    $exitCode = 1
}

# TPM
try {
    $tpm = Get-Tpm

    if ($null -eq $tpm -or -not $tpm.TpmPresent) {
        Print-CheckResult "TPM" "FAIL" "TPM not present"
        $exitCode = 1
    }
    else {
        $tpmVersion = Get-WmiObject -Class Win32_Tpm -Namespace root\CIMV2\Security\MicrosoftTpm |
            Select-Object -ExpandProperty SpecVersion

        if ([string]::IsNullOrWhiteSpace($tpmVersion)) {
            Print-CheckResult "TPM" "FAIL" "TPMVersion=null"
            $exitCode = 1
        }
        else {
            $majorVersion = $tpmVersion.Split(",")[0] -as [int]

            if ($majorVersion -lt 2) {
                Print-CheckResult "TPM" "FAIL" "TPMVersion=$tpmVersion"
                $exitCode = 1
            }
            else {
                Print-CheckResult "TPM" "PASS" "TPMVersion=$tpmVersion"
            }
        }
    }
}
catch {
    Print-CheckResult "TPM" "UNDETERMINED" "Exception: $($_.Exception.Message)"
    $exitCode = 1
}

# CPU
try {
    $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0]

    if ($null -eq $cpuDetails) {
        Print-CheckResult "Processor" "FAIL" "CPU details are null"
        $exitCode = 1
    }
    else {
        $processorCheckFailed = $false

        if ($null -eq $cpuDetails.AddressWidth -or $cpuDetails.AddressWidth -ne $RequiredAddressWidth) {
            Print-CheckResult "Processor" "FAIL" "AddressWidth=$($cpuDetails.AddressWidth), Required=$RequiredAddressWidth"
            $processorCheckFailed = $true
        }

        if ($null -eq $cpuDetails.MaxClockSpeed -or $cpuDetails.MaxClockSpeed -lt $MinClockSpeedMHz) {
            Print-CheckResult "Processor" "FAIL" "MaxClockSpeed=$($cpuDetails.MaxClockSpeed)MHz, Minimum Required=$MinClockSpeedMHz MHz"
            $processorCheckFailed = $true
        }

        if ($null -eq $cpuDetails.NumberOfLogicalProcessors -or $cpuDetails.NumberOfLogicalProcessors -lt $MinLogicalCores) {
            Print-CheckResult "Processor" "FAIL" "LogicalCores=$($cpuDetails.NumberOfLogicalProcessors), Minimum Required=$MinLogicalCores"
            $processorCheckFailed = $true
        }

        if ($cpuDetails.AddressWidth -eq $RequiredAddressWidth `
            -and $cpuDetails.MaxClockSpeed -ge $MinClockSpeedMHz `
            -and $cpuDetails.NumberOfLogicalProcessors -ge $MinLogicalCores) {
            Print-CheckResult "Processor" "PASS" "$($cpuDetails.Name)"
        }
        else {
            $exitCode = 1
        }
    }
}
catch {
    Print-CheckResult "Processor" "UNDETERMINED" "Exception: $($_.Exception.Message)"
    $exitCode = 1
}

# Secure Boot
try {
    $secureBootEnabled = Confirm-SecureBootUEFI

    if ($secureBootEnabled -eq $true) {
        Print-CheckResult "SecureBoot" "PASS" "Secure Boot is enabled"
    }
    else {
        Print-CheckResult "SecureBoot" "FAIL" "Secure Boot is not enabled"
        $exitCode = 1
    }
}
catch [System.PlatformNotSupportedException] {
    Print-CheckResult "SecureBoot" "FAIL" "Secure Boot is not supported or device is not using UEFI"
    $exitCode = 1
}
catch [System.UnauthorizedAccessException] {
    Print-CheckResult "SecureBoot" "UNDETERMINED" "Unable to determine Secure Boot state due to access restrictions"
    $exitCode = 1
}
catch {
    Print-CheckResult "SecureBoot" "UNDETERMINED" "Exception: $($_.Exception.Message)"
    $exitCode = 1
}

# Final Result
if ($exitCode -eq 0) {
    Write-Color "All checks passed. Device appears ready for Windows 11 25H2." "Green"
    exit 0
}
else {
    Write-Color "One or more checks failed. Device is not ready for Windows 11 25H2." "Red"
    exit 1
}

This script checks critical Windows 11 eligibility parameters, including available storage, system memory, TPM presence and version, processor architecture, and Secure Boot availability. It provides clear pass/fail feedback for each requirement, then consolidates the results into an overall readiness status, allowing you to see at a glance if a system can safely upgrade.

If any check fails, the script calls attention to that failure so you can investigate or remediate the issue. It also identifies indeterminate factors when certain details can't be retrieved, making troubleshooting straightforward.

Use cases

  • Quickly evaluating a fleet of devices for Windows 11 upgrade readiness
  • Identifying hardware deficits (e.g., insufficient disk space or missing TPM) prior to scheduled migrations
  • Generating hardware compliance reports for clients or management
  • Integrating with an RMM to automate upgrade pre-checks across multiple endpoints

Recommendations

  • We’d recommend reviewing our Windows 11 Upgrade Automation, which makes use of this script as well.
  • Test in a lab environment before running on production systems
  • Configure a script-based monitor in Level to trigger this check on demand when you need to validate a specific endpoint
  • Alternatively, create a scheduled automation in Level to run this script regularly and track hardware readiness over time
  • Ensure devices are connected and powered to avoid incomplete checks
  • Review the script output logs for detailed pass/fail statuses and potential remediation steps

Frequently asked questions.

What happens if some hardware information is inaccessible?

The script will mark those checks as undetermined, allowing you to investigate further or run it again with elevated permissions.

Can I rely on this script for guaranteed Windows 11 compatibility?

The script checks known hardware requirements, but it cannot account for every nuance in unique environments. Always confirm with official Windows documentation.

How often should I run this check?

You can run it on demand for new or recently upgraded devices, or set a scheduled automation in Level to consistently track readiness across all endpoints.

Ready when you are.

No credit card. No sales call. Just sign up and start managing.