Type to search 97 articles.

    Practical engineering guidance

    Windows 11 readiness for an estate that is already out of support

    Windows 10 reached end of support in October 2025. A script that reports, per device, which Windows 11 requirement it fails — and says plainly which answers it cannot give you.

    Series: Intune and Configuration Manager

    • Windows
    • Intune
    • PowerShell
    • Troubleshooting

    Windows 10 reached end of support on 14 October 2025. As I write this in September 2026, a Windows 10 device without Extended Security Updates has been receiving no security updates for eleven months.

    Most organisations know this. What they often do not have is a per-device list of why the remaining machines have not moved, because “not capable” covers a TPM that is switched off in firmware — five minutes of work — and a processor that will never be supported, which is a purchase.

    Those two deserve different plans, and telling them apart is scriptable.

    What the deadline actually is

    For commercial Extended Security Updates, the programme runs three years from end of support: year one ended 13 October 2026, year two ends 12 October 2027, and year three ends 10 October 2028. It is sold per year and cumulatively — you cannot skip year one and buy year two. Eligible editions are Enterprise, Education and Pro in commercial use. Windows 10 LTSC is not part of it; it has its own lifecycle.

    The consumer programme, as Microsoft currently documents it, accepts enrolment until 12 October 2027, and devices already enrolled continue to that date. It is not available to domain-joined, Entra-joined or MDM-enrolled devices, which is precisely the estate most readers of this article manage — those devices need the commercial route.

    If this matters to your budget, confirm the current dates before quoting them. Microsoft has adjusted the consumer programme once already.

    The requirements a script can and cannot check

    Windows 11 requires a 1 GHz or faster 64-bit processor with two or more cores, 4 GB of memory, 64 GB of storage, UEFI firmware with Secure Boot capability, TPM 2.0, a DirectX 12 capable graphics adapter with a WDDM 2.0 driver, and a 720p display larger than 9 inches.

    Seven of those are readable locally. The eighth — whether the processor model is on Microsoft’s supported list — is not, because the list is published per Windows 11 version and changes with each release. A script that hard-codes “eighth generation or newer” is a script that will be wrong after the next update.

    So this script reports the processor and lets you compare it against the current list, rather than inventing a verdict. That is the difference between an inventory you can act on and one you have to re-check.

    The script

    Read-only. It changes nothing, reads local hardware and firmware state, and returns one object per device.

    <#
    .SYNOPSIS
        Reports Windows 11 hardware readiness and Windows 10 support status for a device.
    
    .DESCRIPTION
        Read-only. Checks the documented Windows 11 requirements that can be determined locally
        (processor cores and speed, memory, system disk, UEFI, Secure Boot capability, TPM) and
        reports the operating system's support position. Makes no changes.
    
        Run elevated: Confirm-SecureBootUEFI and the TPM interfaces require administrative
        rights and report misleading results without them.
    
        Deploy it through Intune, Configuration Manager or a scheduled task and collect the
        objects centrally; it is deliberately one device per run.
    
    .PARAMETER ReportPath
        Optional CSV path. When omitted the object is returned to the pipeline only.
    
    .EXAMPLE
        .\Get-Windows11Readiness.ps1 -Verbose
    
    .EXAMPLE
        .\Get-Windows11Readiness.ps1 -ReportPath C:\Reports\readiness.csv
    
    .NOTES
        Read-only: this script makes no changes.
        ProcessorSupported is deliberately reported as 'check list' rather than decided: Microsoft
        publishes the supported processor list per Windows 11 version and updates it, so the model
        is reported for comparison instead.
        TpmSpecVersion is best-effort; where it cannot be read the TPM presence and readiness
        fields still apply.
    #>
    
    #requires -Version 5.1
    #requires -RunAsAdministrator
    
    [CmdletBinding()]
    param(
        [ValidateScript({ Test-Path -Path (Split-Path -Path $_ -Parent) -PathType Container })]
        [string]$ReportPath
    )
    
    $ErrorActionPreference = 'Stop'
    $minimum = @{ Cores = 2; ClockMhz = 1000; MemoryGb = 4; StorageGb = 64 }
    
    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    $computer = Get-CimInstance -ClassName Win32_ComputerSystem
    $processor = @(Get-CimInstance -ClassName Win32_Processor)[0]
    $systemDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($env:SystemDrive)'"
    
    $build = [int]($os.BuildNumber)
    $isWindows11 = $build -ge 22000
    
    # Firmware type: UEFI is required. PowerShell exposes it as an environment variable set by
    # the loader, which is cheaper and more reliable than parsing setup logs.
    $firmware = if ($env:firmware_type) { $env:firmware_type } else { 'unknown' }
    
    $secureBootCapable = try {
        # $true = enabled, $false = supported but off; an exception means the platform has no
        # Secure Boot at all, which is a hardware finding rather than a script failure.
        [bool](Confirm-SecureBootUEFI -ErrorAction Stop)
    }
    catch {
        Write-Verbose "Secure Boot not available: $($_.Exception.Message)"
        $null
    }
    
    $tpm = try { Get-Tpm -ErrorAction Stop } catch { Write-Warning "TPM state unreadable: $($_.Exception.Message)"; $null }
    
    $tpmSpec = try {
        # Best-effort: this class exposes the specification version (1.2 vs 2.0), which the
        # TrustedPlatformModule cmdlet does not report directly.
        $wmiTpm = Get-CimInstance -Namespace 'root\cimv2\security\microsofttpm' -ClassName Win32_Tpm -ErrorAction Stop
        ($wmiTpm.SpecVersion -split ',')[0].Trim()
    }
    catch {
        Write-Verbose "TPM specification version unreadable: $($_.Exception.Message)"
        'unknown'
    }
    
    $memoryGb = [math]::Round($computer.TotalPhysicalMemory / 1GB, 1)
    $storageGb = [math]::Round($systemDrive.Size / 1GB, 1)
    
    $failures = [System.Collections.Generic.List[string]]::new()
    if ($processor.NumberOfCores -lt $minimum.Cores) { $failures.Add("processor cores ($($processor.NumberOfCores))") }
    if ($processor.MaxClockSpeed -lt $minimum.ClockMhz) { $failures.Add("processor speed ($($processor.MaxClockSpeed) MHz)") }
    if ($processor.AddressWidth -ne 64) { $failures.Add('processor is not 64-bit') }
    if ($memoryGb -lt $minimum.MemoryGb) { $failures.Add("memory ($memoryGb GB)") }
    if ($storageGb -lt $minimum.StorageGb) { $failures.Add("system disk ($storageGb GB)") }
    if ($firmware -ne 'UEFI') { $failures.Add("firmware ($firmware)") }
    if ($null -eq $secureBootCapable) { $failures.Add('no Secure Boot support') }
    if (-not $tpm -or -not $tpm.TpmPresent) { $failures.Add('no TPM present') }
    elseif ($tpmSpec -ne 'unknown' -and $tpmSpec -notlike '2.0*') { $failures.Add("TPM specification $tpmSpec") }
    
    $result = [pscustomobject]@{
        Device             = $env:COMPUTERNAME
        CollectedUtc       = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm')
        OperatingSystem    = $os.Caption
        Build              = $build
        IsWindows11        = $isWindows11
        SupportPosition    = if ($isWindows11) { 'Windows 11 - in support' }
        elseif ($os.Caption -match 'LTSC|LTSB') { 'Windows 10 LTSC - separate lifecycle, check it' }
        else { 'Windows 10 - out of support since 14 October 2025 unless covered by ESU' }
        Processor          = if ($processor.Name) { $processor.Name.Trim() } else { 'unknown' }
        ProcessorCores     = $processor.NumberOfCores
        ProcessorMhz       = $processor.MaxClockSpeed
        ProcessorSupported = 'check list'
        MemoryGb           = $memoryGb
        SystemDiskGb       = $storageGb
        Firmware           = $firmware
        SecureBootEnabled  = $secureBootCapable
        TpmPresent         = if ($tpm) { $tpm.TpmPresent } else { $false }
        TpmReady           = if ($tpm) { $tpm.TpmReady } else { $false }
        TpmSpecVersion     = $tpmSpec
        HardwareVerdict    = if ($isWindows11) { 'Already Windows 11' }
        elseif ($failures.Count -eq 0) { 'Meets checkable requirements - confirm processor model' }
        else { 'Does not meet requirements' }
        FailedRequirements = if ($failures.Count -eq 0) { '' } else { $failures -join '; ' }
        RemediationClass   = if ($isWindows11) { 'none' }
        elseif ($failures -contains 'no TPM present' -or $failures.Count -eq 0) { 'investigate' }
        elseif ($failures -match 'TPM specification|no Secure Boot support|firmware') { 'firmware change may be possible' }
        else { 'hardware replacement likely' }
    }
    
    if ($ReportPath) {
        $result | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8 -Append
        Write-Verbose "Appended to $ReportPath"
    }
    
    if ($result.HardwareVerdict -eq 'Does not meet requirements') {
        Write-Warning "$($env:COMPUTERNAME): $($result.FailedRequirements)"
    }
    
    $result

    Reading it across an estate

    RemediationClass is the column that turns a list into a plan:

    firmware change may be possible. TPM disabled in firmware, Secure Boot off, or a machine still running legacy BIOS with the disk in MBR. These are often desk-side or remote firmware changes on hardware you already own, and they are the cheapest wins in the whole exercise. A device converted from BIOS to UEFI needs its disk converting too; that is a project with a rollback plan, not a quick fix.

    hardware replacement likely. Insufficient memory or an old processor. Budget, not engineering.

    investigate. Everything checkable passes, so the blocker is the processor model or something outside this script’s reach. Compare the reported processor against Microsoft’s supported list for the Windows 11 version you are deploying.

    At scale, Intune’s own Work from anywhere report in Endpoint analytics gives a Windows 11 hardware readiness status of Capable, Not capable, Upgraded or Unknown, with a reason column. Use it for the estate view, and this script where you need the specific numbers behind a verdict, or where devices are not Intune-managed. Note that a large “Unknown” count usually means inactive devices rather than a hardware problem: check the last check-in time before concluding anything.

    A warning about the bypass

    There is a widely circulated registry value that permits installing Windows 11 on hardware that does not meet the processor or TPM 2.0 requirement. Microsoft’s published position is that such a device is not supported, may not be entitled to updates, and that resulting damage is not covered by the manufacturer’s warranty.

    For a managed estate, installing an operating system that is documented as unsupported and possibly not entitled to receive updates, in order to escape an operating system that no longer receives updates, is not a solution. I have not verified the exact behaviour of that registry value against primary documentation, and this script does not set it.

    Verification and limits

    The Windows 10 end-of-support date, the commercial ESU year-end dates, the consumer programme enrolment end date and its exclusion of domain-joined, Entra-joined and MDM-enrolled devices, the Windows 11 hardware requirements, Get-Tpm and Confirm-SecureBootUEFI behaviour including the elevation requirement, the per-version supported processor lists, the Endpoint analytics readiness statuses, and Microsoft’s statement about unsupported-hardware installs were checked against current Microsoft documentation on 20 September 2026.

    The script was written for this article and statically analysed. It was not executed on a production estate, and no device data from a real estate appears here.

    Two limits are structural rather than accidental. The Win32_Tpm class used for the specification version is widely used but I could not confirm it on Microsoft Learn, so it is read inside a try/catch and reported as unknown when unavailable — presence and readiness come from the documented cmdlet and remain reliable. And ProcessorSupported always reads check list by design; treat any script that claims otherwise with suspicion, including one you write yourself next year.

    References