Windows 11 Compatibility

Written By Mikel from Gorelo

Last updated 16 days ago

Here’s a way you can create a list of assets that are Windows 11 compatible or not.

Step 1: Create the custom asset field

  1. Navigate to Settings -> Assets -> Custom Fields

  2. Add custom field with the following details

    • Name: Windows 11 Compatibility

    • Variable: Windows11Compatibility

Step 2: Create the Tags

  1. Navigate to Settings → Tags

  2. Select Asset Policy Tags from the top

  3. Click ‘Create an Assets Tag’ down the bottom-left:

    • Tag: Win11Compatible

    • Property: Registry Key

    • Condition: Equals

    • Path: HKEY_LOCAL_MACHINE\SOFTWARE\Windows11Compatibility\Status

    • Value: Compatible

  4. Click ‘Save

  5. Click ‘Create an Assets Tag’ down the bottom-left:

    • Tag: Win11NotCompatible

    • Property: Registry Key

    • Condition: Equals

    • Path: HKEY_LOCAL_MACHINE\SOFTWARE\Windows11Compatibility\Status

    • Value: NotCompatible

  6. Click ‘Save

  7. Click ‘Create an Assets Tag’ down the bottom-left:

    • Tag: Win11CheckFailed

    • Property: Registry Key

    • Condition: Equals

    • Path: HKEY_LOCAL_MACHINE\SOFTWARE\Windows11Compatibility\Status

    • Value: CheckFailed

  8. Click ‘Save

Step 3: Create the script

  1. Navigate to Scripts

  2. Create a script with the following details:

    • Name: Windows 11 Compatibility

    • Content:

Example
# Windows 11 Compatibility Check # Adapted from Microsoft's HardwareReadiness.ps1 # Registry path and name $RegistryPath = "HKLM:\SOFTWARE\Windows11Compatibility" $RegistryName = "Status" # Function to write to registry function Write-Win11Registry { param( [string]$Status ) try { # Create registry path if it doesn't exist if (!(Test-Path $RegistryPath)) { New-Item -Path $RegistryPath -Force | Out-Null } # Write the value New-ItemProperty -Path $RegistryPath -Name $RegistryName -Value $Status -PropertyType String -Force | Out-Null } catch { Write-Output "Failed to write to registry: $($_.Exception.Message)" } } $Source = @" using Microsoft.Win32; using System; using System.Runtime.InteropServices; public class CpuFamilyResult { public bool IsValid { get; set; } public string Message { get; set; } } public class CpuFamily { [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public ushort ProcessorArchitecture; ushort Reserved; public uint PageSize; public IntPtr MinimumApplicationAddress; public IntPtr MaximumApplicationAddress; public IntPtr ActiveProcessorMask; public uint NumberOfProcessors; public uint ProcessorType; public uint AllocationGranularity; public ushort ProcessorLevel; public ushort ProcessorRevision; } [DllImport("kernel32.dll")] internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo); public enum ProcessorFeature : uint { ARM_SUPPORTED_INSTRUCTIONS = 34 } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsProcessorFeaturePresent(ProcessorFeature processorFeature); private const ushort PROCESSOR_ARCHITECTURE_X86 = 0; private const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; private const ushort PROCESSOR_ARCHITECTURE_X64 = 9; private const string INTEL_MANUFACTURER = "GenuineIntel"; private const string AMD_MANUFACTURER = "AuthenticAMD"; private const string QUALCOMM_MANUFACTURER = "Qualcomm Technologies Inc"; public static CpuFamilyResult Validate(string manufacturer, ushort processorArchitecture) { CpuFamilyResult cpuFamilyResult = new CpuFamilyResult(); if (string.IsNullOrWhiteSpace(manufacturer)) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Manufacturer is null or empty"; return cpuFamilyResult; } string registryPath = "HKEY_LOCAL_MACHINE\\Hardware\\Description\\System\\CentralProcessor\\0"; SYSTEM_INFO sysInfo = new SYSTEM_INFO(); GetNativeSystemInfo(ref sysInfo); switch (processorArchitecture) { case PROCESSOR_ARCHITECTURE_ARM64: if (manufacturer.Equals(QUALCOMM_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { bool isArmv81Supported = IsProcessorFeaturePresent(ProcessorFeature.ARM_SUPPORTED_INSTRUCTIONS); if (!isArmv81Supported) { string registryName = "CP 4030"; long registryValue = (long)Registry.GetValue(registryPath, registryName, -1); long atomicResult = (registryValue >> 20) & 0xF; if (atomicResult >= 2) { isArmv81Supported = true; } } cpuFamilyResult.IsValid = isArmv81Supported; cpuFamilyResult.Message = isArmv81Supported ? "" : "Processor does not implement ARM v8.1 atomic instruction"; } else { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "The processor isn't currently supported for Windows 11"; } break; case PROCESSOR_ARCHITECTURE_X64: case PROCESSOR_ARCHITECTURE_X86: int cpuFamily = sysInfo.ProcessorLevel; int cpuModel = (sysInfo.ProcessorRevision >> 8) & 0xFF; int cpuStepping = sysInfo.ProcessorRevision & 0xFF; if (manufacturer.Equals(INTEL_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { try { cpuFamilyResult.IsValid = true; cpuFamilyResult.Message = ""; if (cpuFamily >= 6 && cpuModel <= 95 && !(cpuFamily == 6 && cpuModel == 85)) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = ""; } else if (cpuFamily == 6 && (cpuModel == 142 || cpuModel == 158) && cpuStepping == 9) { string registryName = "Platform Specific Field 1"; int registryValue = (int)Registry.GetValue(registryPath, registryName, -1); if ((cpuModel == 142 && registryValue != 16) || (cpuModel == 158 && registryValue != 8)) { cpuFamilyResult.IsValid = false; } cpuFamilyResult.Message = "PlatformId " + registryValue; } } catch (Exception ex) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Exception:" + ex.GetType().Name; } } else if (manufacturer.Equals(AMD_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { cpuFamilyResult.IsValid = true; cpuFamilyResult.Message = ""; if (cpuFamily < 23 || (cpuFamily == 23 && (cpuModel == 1 || cpuModel == 17))) { cpuFamilyResult.IsValid = false; } } else { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Unsupported Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision; } break; default: cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Unsupported CPU category. Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision; break; } return cpuFamilyResult; } } "@ try { $exitCode = 0 [int]$MinOSDiskSizeGB = 64 [int]$MinMemoryGB = 4 [Uint32]$MinClockSpeedMHz = 1000 [Uint32]$MinLogicalCores = 2 [Uint16]$RequiredAddressWidth = 64 # Initialize result object $outObject = @{ returnCode = -2 returnResult = "FAILED TO RUN" returnReason = "" logging = "" } # Check Storage $osDrive = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -Property SystemDrive $osDriveSize = Get-WmiObject -Class Win32_LogicalDisk -filter "DeviceID='$($osDrive.SystemDrive)'" | Select-Object @{Name = "SizeGB"; Expression = { $_.Size / 1GB -as [int] } } if ($osDriveSize.SizeGB -lt $MinOSDiskSizeGB) { $outObject.returnReason += "Storage, " } # Check Memory $memory = Get-WmiObject Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum | Select-Object @{Name = "SizeGB"; Expression = { $_.Sum / 1GB -as [int] } } if ($memory.SizeGB -lt $MinMemoryGB) { $outObject.returnReason += "Memory, " } # Check TPM $tpm = Get-Tpm if (!$tpm.TpmPresent) { $outObject.returnReason += "TPM, " } else { $tpmVersion = Get-WmiObject -Class Win32_Tpm -Namespace root\CIMV2\Security\MicrosoftTpm | Select-Object -Property SpecVersion if ($tpmVersion.SpecVersion) { $majorVersion = $tpmVersion.SpecVersion.Split(",")[0] -as [int] if ($majorVersion -lt 2) { $outObject.returnReason += "TPM Version, " } } } # Check CPU and add CPU Family check Add-Type -TypeDefinition $Source $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0] if ($null -eq $cpuDetails) { $outObject.returnReason += "Processor (Not Found), " } else { $processorCheckFailed = $false # AddressWidth if ($null -eq $cpuDetails.AddressWidth -or $cpuDetails.AddressWidth -ne $RequiredAddressWidth) { $processorCheckFailed = $true } # ClockSpeed is in MHz if ($null -eq $cpuDetails.MaxClockSpeed -or $cpuDetails.MaxClockSpeed -le $MinClockSpeedMHz) { $processorCheckFailed = $true } # Number of Logical Cores if ($null -eq $cpuDetails.NumberOfLogicalProcessors -or $cpuDetails.NumberOfLogicalProcessors -lt $MinLogicalCores) { $processorCheckFailed = $true } # CPU Family $cpuFamilyResult = [CpuFamily]::Validate([String]$cpuDetails.Manufacturer, [uint16]$cpuDetails.Architecture) if (!$cpuFamilyResult.IsValid) { $processorCheckFailed = $true } if ($processorCheckFailed) { $outObject.returnReason += "Processor, " } } # Check SecureBoot try { $null = Confirm-SecureBootUEFI } catch { $outObject.returnReason += "SecureBoot, " } # Check for i7-7820hq exception try { $supportedDevices = @('surface studio 2', 'precision 5520') $systemInfo = @(Get-WmiObject -Class Win32_ComputerSystem)[0] if ($cpuDetails.Name -match 'i7-7820hq cpu @ 2.90ghz') { $modelOrSKUCheckLog = $systemInfo.Model.Trim() if ($supportedDevices -contains $modelOrSKUCheckLog) { $outObject.returnReason = "" # Clear any CPU-related failures } } } catch { # Ignore i7 exception check failures } # Set final result $WIN11COMPATIBLE = $outObject.returnReason.Length -eq 0 # Output result to object $outputObject = [pscustomobject]@{ WIN11COMPATIBLE = $WIN11COMPATIBLE IncompatibleItems = $outObject.returnReason.TrimEnd(", ") } # Write to Gorelo custom field $result = if ($WIN11COMPATIBLE) { "Compatible" } else { "Not Compatible: $($outObject.returnReason.TrimEnd(', '))" } GoreloAction -SetCustomField -Name 'asset.Windows11Compatibility' -Value $result # Write to Registry if ($WIN11COMPATIBLE) { Write-Win11Registry -Status "Compatible" } else { Write-Win11Registry -Status "NotCompatible" } # Return the output object $outputObject } catch { # Create error output object $outputObject = [pscustomobject]@{ WIN11COMPATIBLE = $false IncompatibleItems = "Error running compatibility check: $($_.Exception.Message)" } # Write error to Gorelo custom field GoreloAction -SetCustomField -Name 'asset.Windows11Compatibility' -Value "Error running check: $($_.Exception.Message)" # Write error to Registry Write-Win11Registry -Status "CheckFailed" # Return the error output object $outputObject }

Step 4: Deploy the script via a policy

  1. Navigate to Policies

  2. Edit an existing policy that covers the assets you want to store BitLocker Recovery Keys for

  3. Add the 'Windows 11 Compatibility' script and set to repeat daily at your preferred time

  4. Save and Distribute the policy

Step 5: Create the View on the Asset List

  1. Navigate to Assets

  2. Click ‘Create view’ down the bottom left and use the following details:

    • Title: Win11 Compatible

    • Tags: Win11Compatible

  3. Click ‘Save

  4. Do the same for Win11 NotCompatible and Win11 CheckFailed