Back to Resources

Level Verified

Windows 11 Upgrade Hardware Check Script

Created by

Level

Type

Script

Category

Patching

Platforms
WindowsApple iOSLinux

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.

Description

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.

Script

1<#
2This resource is provided as a convenience for Level users. We cannot 
3guarantee it will work in all environments. Please test before deploying 
4to your production environment. We welcome contributions to our community 
5library
6
7Level Library
8https://level.io/library/script-windows-11-upgrade-hardware-check
9#>
10$exitCode = 0
11
12[int]$MinOSDiskSizeGB = 64
13[int]$MinMemoryGB = 4
14[Uint32]$MinClockSpeedMHz = 1000
15[Uint32]$MinLogicalCores = 2
16[Uint16]$RequiredAddressWidth = 64
17
18$PASS_STRING = "PASS"
19$FAIL_STRING = "FAIL"
20$FAILED_TO_RUN_STRING = "FAILED TO RUN"
21$UNDETERMINED_CAPS_STRING = "UNDETERMINED"
22$UNDETERMINED_STRING = "Undetermined"
23$CAPABLE_STRING = "Capable"
24$NOT_CAPABLE_STRING = "Not capable"
25$CAPABLE_CAPS_STRING = "CAPABLE"
26$NOT_CAPABLE_CAPS_STRING = "NOT CAPABLE"
27$STORAGE_STRING = "Storage"
28$OS_DISK_SIZE_STRING = "OSDiskSize"
29$MEMORY_STRING = "Memory"
30$SYSTEM_MEMORY_STRING = "System_Memory"
31$GB_UNIT_STRING = "GB"
32$TPM_STRING = "TPM"
33$TPM_VERSION_STRING = "TPMVersion"
34$PROCESSOR_STRING = "Processor"
35$SECUREBOOT_STRING = "SecureBoot"
36$I7_7820HQ_CPU_STRING = "i7-7820hq CPU"
37
38# Function for colored console output
39function Write-Color {
40    param(
41        [string]$Message,
42        [string]$Color
43    )
44    $colorHash = @{
45        'Red' = [ConsoleColor]::Red
46        'Green' = [ConsoleColor]::Green
47        'Yellow' = [ConsoleColor]::Yellow
48    }
49
50    Write-Host $Message -ForegroundColor $colorHash[$Color]
51}
52
53# Function to print results to console with colors
54function Print-CheckResult {
55    param(
56        [string]$CheckName,
57        [string]$Status,
58        [string]$Details
59    )
60    switch ($Status) {
61        "PASS" { Write-Color "$($CheckName): $($Status); $($Details)" "Green" }
62        "FAIL" { Write-Color "$($CheckName): $($Status); $($Details)" "Red" }
63        default { Write-Color "$($CheckName): $($Status); $($Details)" "Yellow" }
64    }
65}
66
67# return returnCode is -1 when an exception is thrown. 1 if the value does not meet requirements. 0 if successful. -2 default, script didn't run.
68$outObject = @{ returnCode = -2; returnResult = $FAILED_TO_RUN_STRING; returnReason = ""; logging = "" }
69
70# NOT CAPABLE(1) state takes precedence over UNDETERMINED(-1) state
71function Private:UpdateReturnCode {
72    param(
73        [Parameter(Mandatory = $true)]
74        [ValidateRange(-2, 1)]
75        [int] $ReturnCode
76    )
77
78    Switch ($ReturnCode) {
79        0 {
80            if ($outObject.returnCode -eq -2) {
81                $outObject.returnCode = $ReturnCode
82            }
83        }
84        1 {
85            $outObject.returnCode = $ReturnCode
86        }
87        -1 {
88            if ($outObject.returnCode -ne 1) {
89                $outObject.returnCode = $ReturnCode
90            }
91        }
92    }
93}
94
95$Source = @"
96using Microsoft.Win32;
97using System;
98using System.Runtime.InteropServices;
99
100public class CpuFamilyResult
101{
102    public bool IsValid { get; set; }
103    public string Message { get; set; }
104}
105
106public class CpuFamily
107{
108    [StructLayout(LayoutKind.Sequential)]
109    public struct SYSTEM_INFO
110    {
111        public ushort ProcessorArchitecture;
112        ushort Reserved;
113        public uint PageSize;
114        public IntPtr MinimumApplicationAddress;
115        public IntPtr MaximumApplicationAddress;
116        public IntPtr ActiveProcessorMask;
117        public uint NumberOfProcessors;
118        public uint ProcessorType;
119        public uint AllocationGranularity;
120        public ushort ProcessorLevel;
121        public ushort ProcessorRevision;
122    }
123
124    [DllImport("kernel32.dll")]
125    internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
126
127    public enum ProcessorFeature : uint
128    {
129        ARM_SUPPORTED_INSTRUCTIONS = 34
130    }
131
132    [DllImport("kernel32.dll")]
133    [return: MarshalAs(UnmanagedType.Bool)]
134    static extern bool IsProcessorFeaturePresent(ProcessorFeature processorFeature);
135
136    private const ushort PROCESSOR_ARCHITECTURE_X86 = 0;
137    private const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12;
138    private const ushort PROCESSOR_ARCHITECTURE_X64 = 9;
139
140    private const string INTEL_MANUFACTURER = "GenuineIntel";
141    private const string AMD_MANUFACTURER = "AuthenticAMD";
142    private const string QUALCOMM_MANUFACTURER = "Qualcomm Technologies Inc";
143
144    public static CpuFamilyResult Validate(string manufacturer, ushort processorArchitecture)
145    {
146        CpuFamilyResult cpuFamilyResult = new CpuFamilyResult();
147
148        if (string.IsNullOrWhiteSpace(manufacturer))
149        {
150            cpuFamilyResult.IsValid = false;
151            cpuFamilyResult.Message = "Manufacturer is null or empty";
152            return cpuFamilyResult;
153        }
154
155        string registryPath = "HKEY_LOCAL_MACHINE\\Hardware\\Description\\System\\CentralProcessor\\0";
156        SYSTEM_INFO sysInfo = new SYSTEM_INFO();
157        GetNativeSystemInfo(ref sysInfo);
158
159        switch (processorArchitecture)
160        {
161            case PROCESSOR_ARCHITECTURE_ARM64:
162
163                if (manufacturer.Equals(QUALCOMM_MANUFACTURER, StringComparison.OrdinalIgnoreCase))
164                {
165                    bool isArmv81Supported = IsProcessorFeaturePresent(ProcessorFeature.ARM_SUPPORTED_INSTRUCTIONS);
166
167                    if (!isArmv81Supported)
168                    {
169                        string registryName = "CP 4030";
170                        long registryValue = (long)Registry.GetValue(registryPath, registryName, -1);
171                        long atomicResult = (registryValue >> 20) & 0xF;
172
173                        if (atomicResult >= 2)
174                        {
175                            isArmv81Supported = true;
176                        }
177                    }
178
179                    cpuFamilyResult.IsValid = isArmv81Supported;
180                    cpuFamilyResult.Message = isArmv81Supported ? "" : "Processor does not implement ARM v8.1 atomic instruction";
181                }
182                else
183                {
184                    cpuFamilyResult.IsValid = false;
185                    cpuFamilyResult.Message = "The processor isn't currently supported for Windows 11";
186                }
187
188                break;
189
190            case PROCESSOR_ARCHITECTURE_X64:
191            case PROCESSOR_ARCHITECTURE_X86:
192
193                int cpuFamily = sysInfo.ProcessorLevel;
194                int cpuModel = (sysInfo.ProcessorRevision >> 8) & 0xFF;
195                int cpuStepping = sysInfo.ProcessorRevision & 0xFF;
196
197                if (manufacturer.Equals(INTEL_MANUFACTURER, StringComparison.OrdinalIgnoreCase))
198                {
199                    try
200                    {
201                        cpuFamilyResult.IsValid = true;
202                        cpuFamilyResult.Message = "";
203
204                        if (cpuFamily >= 6 && cpuModel <= 95 && !(cpuFamily == 6 && cpuModel == 85))
205                        {
206                            cpuFamilyResult.IsValid = false;
207                            cpuFamilyResult.Message = "";
208                        }
209                        else if (cpuFamily == 6 && (cpuModel == 142 || cpuModel == 158) && cpuStepping == 9)
210                        {
211                            string registryName = "Platform Specific Field 1";
212                            int registryValue = (int)Registry.GetValue(registryPath, registryName, -1);
213
214                            if ((cpuModel == 142 && registryValue != 16) || (cpuModel == 158 && registryValue != 8))
215                            {
216                                cpuFamilyResult.IsValid = false;
217                            }
218                            cpuFamilyResult.Message = "PlatformId " + registryValue;
219                        }
220                    }
221                    catch (Exception ex)
222                    {
223                        cpuFamilyResult.IsValid = false;
224                        cpuFamilyResult.Message = "Exception:" + ex.GetType().Name;
225                    }
226                }
227                else if (manufacturer.Equals(AMD_MANUFACTURER, StringComparison.OrdinalIgnoreCase))
228                {
229                    cpuFamilyResult.IsValid = true;
230                    cpuFamilyResult.Message = "";
231
232                    if (cpuFamily < 23 || (cpuFamily == 23 && (cpuModel == 1 || cpuModel == 17)))
233                    {
234                        cpuFamilyResult.IsValid = false;
235                    }
236                }
237                else
238                {
239                    cpuFamilyResult.IsValid = false;
240                    cpuFamilyResult.Message = "Unsupported Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision;
241                }
242
243                break;
244
245            default:
246                cpuFamilyResult.IsValid = false;
247                cpuFamilyResult.Message = "Unsupported CPU category. Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision;
248                break;
249        }
250        return cpuFamilyResult;
251    }
252}
253"@
254
255# Storage
256try {
257    $osDrive = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -Property SystemDrive
258    $osDriveSize = Get-WmiObject -Class Win32_LogicalDisk -filter "DeviceID='$($osDrive.SystemDrive)'" | Select-Object @{Name = "SizeGB"; Expression = { $_.Size / 1GB -as [int] } }  
259
260    if ($null -eq $osDriveSize) {
261        UpdateReturnCode -ReturnCode 1
262        $outObject.returnReason += $logFormatReturnReason -f $STORAGE_STRING
263        $outObject.logging += $logFormatWithBlob -f $STORAGE_STRING, "Storage is null", $FAIL_STRING
264        Print-CheckResult $STORAGE_STRING $FAIL_STRING "Storage is null"
265        $exitCode = 1
266    }
267    elseif ($osDriveSize.SizeGB -lt $MinOSDiskSizeGB) {
268        UpdateReturnCode -ReturnCode 1
269        $outObject.returnReason += $logFormatReturnReason -f $STORAGE_STRING
270        $outObject.logging += $logFormatWithUnit -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, ($osDriveSize.SizeGB), $GB_UNIT_STRING, $FAIL_STRING
271        Print-CheckResult $STORAGE_STRING $FAIL_STRING "OSDiskSize=$($osDriveSize.SizeGB)GB, Minimum Required=$MinOSDiskSizeGB GB"
272        $exitCode = 1
273    }
274    else {
275        $outObject.logging += $logFormatWithUnit -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, ($osDriveSize.SizeGB), $GB_UNIT_STRING, $PASS_STRING
276        Print-CheckResult $STORAGE_STRING $PASS_STRING "OSDiskSize=$($osDriveSize.SizeGB)GB"
277        UpdateReturnCode -ReturnCode 0
278    }
279}
280catch {
281    UpdateReturnCode -ReturnCode -1
282    $outObject.logging += $logFormat -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
283    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
284    Print-CheckResult $STORAGE_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
285    $exitCode = 1
286}
287
288# Memory (bytes)
289try {
290    $memory = Get-WmiObject Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum | Select-Object @{Name = "SizeGB"; Expression = { $_.Sum / 1GB -as [int] } }
291
292    if ($null -eq $memory) {
293        UpdateReturnCode -ReturnCode 1
294        $outObject.returnReason += $logFormatReturnReason -f $MEMORY_STRING
295        $outObject.logging += $logFormatWithBlob -f $MEMORY_STRING, "Memory is null", $FAIL_STRING
296        Print-CheckResult $MEMORY_STRING $FAIL_STRING "Memory is null"
297        $exitCode = 1
298    }
299    elseif ($memory.SizeGB -lt $MinMemoryGB) {
300        UpdateReturnCode -ReturnCode 1
301        $outObject.returnReason += $logFormatReturnReason -f $MEMORY_STRING
302        $outObject.logging += $logFormatWithUnit -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, ($memory.SizeGB), $GB_UNIT_STRING, $FAIL_STRING
303        Print-CheckResult $MEMORY_STRING $FAIL_STRING "SystemMemory=$($memory.SizeGB)GB, Minimum Required=$MinMemoryGB GB"
304        $exitCode = 1
305    }
306    else {
307        $outObject.logging += $logFormatWithUnit -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, ($memory.SizeGB), $GB_UNIT_STRING, $PASS_STRING
308        Print-CheckResult $MEMORY_STRING $PASS_STRING "SystemMemory=$($memory.SizeGB)GB"
309        UpdateReturnCode -ReturnCode 0
310    }
311}
312catch {
313    UpdateReturnCode -ReturnCode -1
314    $outObject.logging += $logFormat -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
315    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
316    Print-CheckResult $MEMORY_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
317    $exitCode = 1
318}
319
320# TPM
321try {
322    $tpm = Get-Tpm
323
324    if ($null -eq $tpm) {
325        UpdateReturnCode -ReturnCode 1
326        $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING
327        $outObject.logging += $logFormatWithBlob -f $TPM_STRING, "TPM is null", $FAIL_STRING
328        Print-CheckResult $TPM_STRING $FAIL_STRING "TPM is null"
329        $exitCode = 1
330    }
331    elseif ($tpm.TpmPresent) {
332        $tpmVersion = Get-WmiObject -Class Win32_Tpm -Namespace root\CIMV2\Security\MicrosoftTpm | Select-Object -Property SpecVersion
333
334        if ($null -eq $tpmVersion.SpecVersion) {
335            UpdateReturnCode -ReturnCode 1
336            $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING
337            $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, "null", $FAIL_STRING
338            Print-CheckResult $TPM_STRING $FAIL_STRING "TPMVersion=null"
339            $exitCode = 1
340        }
341
342        $majorVersion = $tpmVersion.SpecVersion.Split(",")[0] -as [int]
343        if ($majorVersion -lt 2) {
344            UpdateReturnCode -ReturnCode 1
345            $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING
346            $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpmVersion.SpecVersion), $FAIL_STRING
347            Print-CheckResult $TPM_STRING $FAIL_STRING "TPMVersion=$($tpmVersion.SpecVersion)"
348            $exitCode = 1
349        }
350        else {
351            $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpmVersion.SpecVersion), $PASS_STRING
352            Print-CheckResult $TPM_STRING $PASS_STRING "TPMVersion=$($tpmVersion.SpecVersion)"
353            UpdateReturnCode -ReturnCode 0
354        }
355    }
356    else {
357        if ($tpm.GetType().Name -eq "String") {
358            UpdateReturnCode -ReturnCode -1
359            $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
360            $outObject.logging += $logFormatException -f $tpm
361            Print-CheckResult $TPM_STRING $UNDETERMINED_CAPS_STRING "TPM information undetermined"
362        }
363        else {
364            UpdateReturnCode -ReturnCode  1
365            $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING
366            $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpm.TpmPresent), $FAIL_STRING
367            Print-CheckResult $TPM_STRING $FAIL_STRING "TPM not present"
368        }
369        $exitCode = 1
370    }
371}
372catch {
373    UpdateReturnCode -ReturnCode -1
374    $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
375    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
376    Print-CheckResult $TPM_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
377    $exitCode = 1
378}
379
380# CPU Details
381$cpuDetails;
382try {
383    $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0]
384
385    if ($null -eq $cpuDetails) {
386        UpdateReturnCode -ReturnCode 1
387        $exitCode = 1
388        $outObject.returnReason += $logFormatReturnReason -f $PROCESSOR_STRING
389        $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, "CpuDetails is null", $FAIL_STRING
390        Print-CheckResult $PROCESSOR_STRING $FAIL_STRING "CpuDetails is null"
391    }
392    else {
393        $processorCheckFailed = $false
394
395        # AddressWidth
396        if ($null -eq $cpuDetails.AddressWidth -or $cpuDetails.AddressWidth -ne $RequiredAddressWidth) {
397            UpdateReturnCode -ReturnCode 1
398            $processorCheckFailed = $true
399            Print-CheckResult $PROCESSOR_STRING $FAIL_STRING "AddressWidth=$($cpuDetails.AddressWidth), Required=$RequiredAddressWidth"
400            $exitCode = 1
401        }
402
403        # ClockSpeed is in MHz
404        if ($null -eq $cpuDetails.MaxClockSpeed -or $cpuDetails.MaxClockSpeed -le $MinClockSpeedMHz) {
405            UpdateReturnCode -ReturnCode 1;
406            $processorCheckFailed = $true
407            Print-CheckResult $PROCESSOR_STRING $FAIL_STRING "MaxClockSpeed=$($cpuDetails.MaxClockSpeed)MHz, Minimum Required=$MinClockSpeedMHz MHz"
408            $exitCode = 1
409        }
410
411        # Number of Logical Cores
412        if ($null -eq $cpuDetails.NumberOfLogicalProcessors -or $cpuDetails.NumberOfLogicalProcessors -lt $MinLogicalCores) {
413            UpdateReturnCode -ReturnCode 1
414            $processorCheckFailed = $true
415            Print-CheckResult $PROCESSOR_STRING $FAIL_STRING "NumberOfLogicalCores=$($cpuDetails.NumberOfLogicalProcessors), Minimum Required=$MinLogicalCores"
416            $exitCode = 1
417        }
418
419        # CPU Family
420        Add-Type -TypeDefinition $Source
421        $cpuFamilyResult = [CpuFamily]::Validate([String]$cpuDetails.Manufacturer, [uint16]$cpuDetails.Architecture)
422
423        $cpuDetailsLog = "{AddressWidth=$($cpuDetails.AddressWidth); MaxClockSpeed=$($cpuDetails.MaxClockSpeed); NumberOfLogicalCores=$($cpuDetails.NumberOfLogicalProcessors); Manufacturer=$($cpuDetails.Manufacturer); Caption=$($cpuDetails.Caption); $($cpuFamilyResult.Message)}"
424
425        if (!$cpuFamilyResult.IsValid) {
426            UpdateReturnCode -ReturnCode 1
427            $processorCheckFailed = $true
428            Print-CheckResult $PROCESSOR_STRING $FAIL_STRING "$($cpuFamilyResult.Message)"
429            $exitCode = 1
430        }
431
432        if ($processorCheckFailed) {
433            $outObject.returnReason += $logFormatReturnReason -f $PROCESSOR_STRING
434            $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, ($cpuDetailsLog), $FAIL_STRING
435        }
436        else {
437            $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, ($cpuDetailsLog), $PASS_STRING
438            Print-CheckResult $PROCESSOR_STRING $PASS_STRING "$cpuDetailsLog"
439            UpdateReturnCode -ReturnCode 0
440        }
441    }
442}
443catch {
444    UpdateReturnCode -ReturnCode -1
445    $outObject.logging += $logFormat -f $PROCESSOR_STRING, $PROCESSOR_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
446    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
447    Print-CheckResult $PROCESSOR_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
448    $exitCode = 1
449}
450
451# SecureBooot
452try {
453    $isSecureBootEnabled = Confirm-SecureBootUEFI
454    $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $CAPABLE_STRING, $PASS_STRING
455    Print-CheckResult $SECUREBOOT_STRING $PASS_STRING "Secure Boot is enabled"
456    UpdateReturnCode -ReturnCode 0
457}
458catch [System.PlatformNotSupportedException] {
459    # PlatformNotSupportedException "Cmdlet not supported on this platform." - SecureBoot is not supported or is non-UEFI computer.
460    UpdateReturnCode -ReturnCode 1
461    $outObject.returnReason += $logFormatReturnReason -f $SECUREBOOT_STRING
462    $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $NOT_CAPABLE_STRING, $FAIL_STRING
463    Print-CheckResult $SECUREBOOT_STRING $FAIL_STRING "SecureBoot is not supported or is non-UEFI computer"
464    $exitCode = 1
465}
466catch [System.UnauthorizedAccessException] {
467    UpdateReturnCode -ReturnCode -1
468    $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
469    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
470    Print-CheckResult $SECUREBOOT_STRING $UNDETERMINED_CAPS_STRING "Exception: UnauthorizedAccessException"
471    $exitCode = 1
472}
473catch {
474    UpdateReturnCode -ReturnCode -1
475    $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
476    $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
477    Print-CheckResult $SECUREBOOT_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
478    $exitCode = 1
479}
480
481# i7-7820hq CPU
482try {
483    $supportedDevices = @('surface studio 2', 'precision 5520')
484    $systemInfo = @(Get-WmiObject -Class Win32_ComputerSystem)[0]
485
486    if ($null -ne $cpuDetails) {
487        if ($cpuDetails.Name -match 'i7-7820hq cpu @ 2.90ghz'){
488            $modelOrSKUCheckLog = $systemInfo.Model.Trim()
489            if ($supportedDevices -contains $modelOrSKUCheckLog){
490                $outObject.logging += $logFormatWithBlob -f $I7_7820HQ_CPU_STRING, $modelOrSKUCheckLog, $PASS_STRING
491                Print-CheckResult $I7_7820HQ_CPU_STRING $PASS_STRING "Device Model: $modelOrSKUCheckLog"
492                $outObject.returnCode = 0
493                $exitCode = 0
494            }
495        }
496    }
497}
498catch {
499    if ($outObject.returnCode -ne 0){
500        UpdateReturnCode -ReturnCode -1
501        $outObject.logging += $logFormatWithBlob -f $I7_7820HQ_CPU_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING
502        $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)"
503        Print-CheckResult $I7_7820HQ_CPU_STRING $UNDETERMINED_CAPS_STRING "Exception: $($_.Exception.Message)"
504        $exitCode = 1
505    }
506}
507
508# Final Result
509Switch ($outObject.returnCode) {
510    0 { 
511        $outObject.returnResult = $CAPABLE_CAPS_STRING
512        Write-Color "All checks passed. Device is eligible for Windows 11." "Green"
513    }
514    1 { 
515        $outObject.returnResult = $NOT_CAPABLE_CAPS_STRING
516        Write-Color "One or more checks failed. Device is not eligible for Windows 11." "Red"
517    }
518    -1 { 
519        $outObject.returnResult = $UNDETERMINED_CAPS_STRING
520        Write-Color "Some checks could not be determined." "Yellow"
521    }
522    -2 { 
523        $outObject.returnResult = $FAILED_TO_RUN_STRING
524        Write-Color "The script failed to run." "Red"
525    }
526}
527
528#$outObject | ConvertTo-Json -Compress
529
530# Exit with appropriate code
531if ($exitCode -eq 1) {
532    exit 1
533} else {
534    exit 0
535}

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

FAQ

  • 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.

Included with this Script:

Below is a list of what you can expect to find when importing this Script.

Script details:

The following data and settings will be imported with your script.

Script Name

Windows 11 Upgrade - Hardware Check

Description

This PowerShell script evaluates a system's compatibility with Windows 11 by checking hardware and firmware requirements such as disk size, memory, TPM version, processor architecture, secure boot status, and specific CPU models. It provides a detailed pass/fail report for each check and outputs whether the device is capable, not capable, or undetermined for running Windows 11.

Language

PowerShell

Timeout (In Seconds)

300

Run As

Local system

Import into Level

Related resources

Explore more automations, scripts, and policies to further enhance your IT operations.

View all resources