i was thinking, where this value stored ? you can get them via registry, WMI, and also Api but where they real found ? { like old ver command } so, i arrived to this. Ver command -> Return `Microsoft Windows [Version 10.0.19044.5854]` Major -> 10, Minor -> 0, Build -> 19044, UBR -> 5854 this is the ps1 equ code. { For NT 10.0 -> Windows 10 & Up} Latest version, include lot of improvent's and fail safe check's, fit to powershell 3.0 01/06/2025 ~ Changes Re-Build from scrath add new fail safe way, that work even on nt 4.0 still read from memory, just from other memory [peb] nice catch from abbodi1406 also add new way to fetch ubr value. Spoiler: Source Code Code: using namespace System using namespace System.Collections.Generic using namespace System.Drawing using namespace System.IO using namespace System.IO.Compression using namespace System.Management.Automation using namespace System.Management.Automation.CmdletBindingAttribute using namespace System.Net using namespace System.Numerics using namespace System.Reflection using namespace System.Reflection.Emit using namespace System.Runtime.InteropServices using namespace System.Security.AccessControl using namespace System.Security.Principal using namespace System.ServiceProcess using namespace System.Text using namespace System.Text.RegularExpressions using namespace System.Threading using namespace System.Windows.Forms Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms $ProgressPreference = 'SilentlyContinue' # Check if $PSVersionTable or $PSVersionTable.PSVersion is null, or Major version is invalid if ($null -eq $PSVersionTable -or $null -eq $PSVersionTable.PSVersion -or $null -eq $PSVersionTable.PSVersion.Major) { cls Write-Host Write-Host "Unable to determine PowerShell version." -ForegroundColor Green Write-Host "This script requires PowerShell 5.0 or higher!" -ForegroundColor Green Write-Host Read-Host "Press Enter to exit..." Read-Host return } # Check if PowerShell version is lower than 5.0 if ($PSVersionTable.PSVersion.Major -lt 5) { cls Write-Host Write-Host "This script requires PowerShell 5.0 or higher!" -ForegroundColor Green Write-Host "Windows 10 & Above are supported." -ForegroundColor Green Write-Host Read-Host "Press Enter to exit..." Read-Host return } # Check if the current user is System or an Administrator $IsSystem = [WindowsIdentity]::GetCurrent().IsSystem $principal = [WindowsPrincipal]::new([WindowsIdentity]::GetCurrent()) # If the user is neither System nor an Administrator, show a warning and stop the script if (!$IsSystem -and -not $principal.IsInRole([WindowsBuiltInRole]::Administrator)) { cls Write-Host Write-Host "This script must be run as Administrator or System!" -ForegroundColor Green Write-Host "Please run this script as Administrator." -ForegroundColor Green Write-Host "(Right-click and select 'Run as Administrator')" -ForegroundColor Green Write-Host Read-Host "Press Enter to exit..." Read-Host return } cls Write-Host Write-Host "=========================================" -ForegroundColor Green Write-Host " PowerShell version is valid," -ForegroundColor Cyan Write-Host " Proceeding with the script..." -ForegroundColor Cyan Write-Host " All system checks passed. Let's roll!" -ForegroundColor Green Write-Host "=========================================" -ForegroundColor Green Write-Host Start-Sleep 4 # Kernel-Mode Windows Versions # https://www.geoffchappell.com/studies/windows/km/versions.htm # ZwQuerySystemInformation # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm # RtlGetNtVersionNumbers Function # The RtlGetNtVersionNumbers function gets Windows version numbers directly from NTDLL. # https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrinit/getntversionnumbers.htm # RtlGetVersion function (wdm.h) # https://learn.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion # Process Environment Block (PEB) # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm # KUSER_SHARED_DATA # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm # KUSER_SHARED_DATA structure (ntddk.h) # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data # The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. # The only formal definition among headers in the Windows Driver Kit (WDK) or the Software Development Kit (SDK) is in assembly language headers: KS386. # INC from the WDK and KSAMD64. # INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address. # That they also define USER_SHARED_DATA for the kernel-mode address suggests that they too are intended for kernel-mode programming, # albeit of a sort that is at least aware of what address works for user-mode access. # Among relatively large structures, # the KUSER_SHARED_DATA is highly unusual for having exactly the same layout in 32-bit and 64-bit Windows. # This is because the one instance must be simultaneously accessible by both 32-bit and 64-bit code on 64-bit Windows, # and it's desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows. # 2.2.9.6 OSEdition Enumeration # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mde2/d92ead8f-faf3-47a8-a341-1921dc2c463b # Full Table, include Name, Sku, etc etc provide by [abbodi1406] $Global:productTypeTable = @' ProductID,OSEdition,DWORD ultimate,PRODUCT_ULTIMATE,0x00000001 homebasic,PRODUCT_HOME_BASIC,0x00000002 homepremium,PRODUCT_HOME_PREMIUM,0x00000003 enterprise,PRODUCT_ENTERPRISE,0x00000004 homebasicn,PRODUCT_HOME_BASIC_N,0x00000005 business,PRODUCT_BUSINESS,0x00000006 serverstandard,PRODUCT_STANDARD_SERVER,0x00000007 serverdatacenter,PRODUCT_DATACENTER_SERVER,0x00000008 serversbsstandard,PRODUCT_SMALLBUSINESS_SERVER,0x00000009 serverenterprise,PRODUCT_ENTERPRISE_SERVER,0x0000000A starter,PRODUCT_STARTER,0x0000000B serverdatacentercore,PRODUCT_DATACENTER_SERVER_CORE,0x0000000C serverstandardcore,PRODUCT_STANDARD_SERVER_CORE,0x0000000D serverenterprisecore,PRODUCT_ENTERPRISE_SERVER_CORE,0x0000000E serverenterpriseia64,PRODUCT_ENTERPRISE_SERVER_IA64,0x0000000F businessn,PRODUCT_BUSINESS_N,0x00000010 serverweb,PRODUCT_WEB_SERVER,0x00000011 serverhpc,PRODUCT_CLUSTER_SERVER,0x00000012 serverhomestandard,PRODUCT_HOME_SERVER,0x00000013 serverstorageexpress,PRODUCT_STORAGE_EXPRESS_SERVER,0x00000014 serverstoragestandard,PRODUCT_STORAGE_STANDARD_SERVER,0x00000015 serverstorageworkgroup,PRODUCT_STORAGE_WORKGROUP_SERVER,0x00000016 serverstorageenterprise,PRODUCT_STORAGE_ENTERPRISE_SERVER,0x00000017 serverwinsb,PRODUCT_SERVER_FOR_SMALLBUSINESS,0x00000018 serversbspremium,PRODUCT_SMALLBUSINESS_SERVER_PREMIUM,0x00000019 homepremiumn,PRODUCT_HOME_PREMIUM_N,0x0000001A enterprisen,PRODUCT_ENTERPRISE_N,0x0000001B ultimaten,PRODUCT_ULTIMATE_N,0x0000001C serverwebcore,PRODUCT_WEB_SERVER_CORE,0x0000001D servermediumbusinessmanagement,PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT,0x0000001E servermediumbusinesssecurity,PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY,0x0000001F servermediumbusinessmessaging,PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING,0x00000020 serverwinfoundation,PRODUCT_SERVER_FOUNDATION,0x00000021 serverhomepremium,PRODUCT_HOME_PREMIUM_SERVER,0x00000022 serverwinsbv,PRODUCT_SERVER_FOR_SMALLBUSINESS_V,0x00000023 serverstandardv,PRODUCT_STANDARD_SERVER_V,0x00000024 serverdatacenterv,PRODUCT_DATACENTER_SERVER_V,0x00000025 serverenterprisev,PRODUCT_ENTERPRISE_SERVER_V,0x00000026 serverdatacentervcore,PRODUCT_DATACENTER_SERVER_CORE_V,0x00000027 serverstandardvcore,PRODUCT_STANDARD_SERVER_CORE_V,0x00000028 serverenterprisevcore,PRODUCT_ENTERPRISE_SERVER_CORE_V,0x00000029 serverhypercore,PRODUCT_HYPERV,0x0000002A serverstorageexpresscore,PRODUCT_STORAGE_EXPRESS_SERVER_CORE,0x0000002B serverstoragestandardcore,PRODUCT_STORAGE_STANDARD_SERVER_CORE,0x0000002C serverstorageworkgroupcore,PRODUCT_STORAGE_WORKGROUP_SERVER_CORE,0x0000002D serverstorageenterprisecore,PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE,0x0000002E startern,PRODUCT_STARTER_N,0x0000002F professional,PRODUCT_PROFESSIONAL,0x00000030 professionaln,PRODUCT_PROFESSIONAL_N,0x00000031 serversolution,PRODUCT_SB_SOLUTION_SERVER,0x00000032 serverforsbsolutions,PRODUCT_SERVER_FOR_SB_SOLUTIONS,0x00000033 serversolutionspremium,PRODUCT_STANDARD_SERVER_SOLUTIONS,0x00000034 serversolutionspremiumcore,PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE,0x00000035 serversolutionem,PRODUCT_SB_SOLUTION_SERVER_EM,0x00000036 serverforsbsolutionsem,PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM,0x00000037 serverembeddedsolution,PRODUCT_SOLUTION_EMBEDDEDSERVER,0x00000038 serverembeddedsolutioncore,PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE,0x00000039 professionalembedded,PRODUCT_PROFESSIONAL_EMBEDDED,0x0000003A serveressentialmanagement,PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT,0x0000003B serveressentialadditional,PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL,0x0000003C serveressentialmanagementsvc,PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC,0x0000003D serveressentialadditionalsvc,PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC,0x0000003E serversbspremiumcore,PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE,0x0000003F serverhpcv,PRODUCT_CLUSTER_SERVER_V,0x00000040 embedded,PRODUCT_EMBEDDED,0x00000041 startere,PRODUCT_STARTER_E,0x00000042 homebasice,PRODUCT_HOME_BASIC_E,0x00000043 homepremiume,PRODUCT_HOME_PREMIUM_E,0x00000044 professionale,PRODUCT_PROFESSIONAL_E,0x00000045 enterprisee,PRODUCT_ENTERPRISE_E,0x00000046 ultimatee,PRODUCT_ULTIMATE_E,0x00000047 enterpriseeval,PRODUCT_ENTERPRISE_EVALUATION,0x00000048 prerelease,PRODUCT_PRERELEASE,0x0000004A servermultipointstandard,PRODUCT_MULTIPOINT_STANDARD_SERVER,0x0000004C servermultipointpremium,PRODUCT_MULTIPOINT_PREMIUM_SERVER,0x0000004D serverstandardeval,PRODUCT_STANDARD_EVALUATION_SERVER,0x0000004F serverdatacentereval,PRODUCT_DATACENTER_EVALUATION_SERVER,0x00000050 prereleasearm,PRODUCT_PRODUCT_PRERELEASE_ARM,0x00000051 prereleasen,PRODUCT_PRODUCT_PRERELEASE_N,0x00000052 enterpriseneval,PRODUCT_ENTERPRISE_N_EVALUATION,0x00000054 embeddedautomotive,PRODUCT_EMBEDDED_AUTOMOTIVE,0x00000055 embeddedindustrya,PRODUCT_EMBEDDED_INDUSTRY_A,0x00000056 thinpc,PRODUCT_THINPC,0x00000057 embeddeda,PRODUCT_EMBEDDED_A,0x00000058 embeddedindustry,PRODUCT_EMBEDDED_INDUSTRY,0x00000059 embeddede,PRODUCT_EMBEDDED_E,0x0000005A embeddedindustrye,PRODUCT_EMBEDDED_INDUSTRY_E,0x0000005B embeddedindustryae,PRODUCT_EMBEDDED_INDUSTRY_A_E,0x0000005C professionalplus,PRODUCT_PRODUCT_PROFESSIONAL_PLUS,0x0000005D serverstorageworkgroupeval,PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER,0x0000005F serverstoragestandardeval,PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER,0x00000060 corearm,PRODUCT_CORE_ARM,0x00000061 coren,PRODUCT_CORE_N,0x00000062 corecountryspecific,PRODUCT_CORE_COUNTRYSPECIFIC,0x00000063 coresinglelanguage,PRODUCT_CORE_SINGLELANGUAGE,0x00000064 core,PRODUCT_CORE,0x00000065 professionalwmc,PRODUCT_PROFESSIONAL_WMC,0x00000067 mobilecore,PRODUCT_MOBILE_CORE,0x00000068 embeddedindustryeval,PRODUCT_EMBEDDED_INDUSTRY_EVAL,0x00000069 embeddedindustryeeval,PRODUCT_EMBEDDED_INDUSTRY_E_EVAL,0x0000006A embeddedeval,PRODUCT_EMBEDDED_EVAL,0x0000006B embeddedeeval,PRODUCT_EMBEDDED_E_EVAL,0x0000006C coresystemserver,PRODUCT_NANO_SERVER,0x0000006D servercloudstorage,PRODUCT_CLOUD_STORAGE_SERVER,0x0000006E coreconnected,PRODUCT_CORE_CONNECTED,0x0000006F professionalstudent,PRODUCT_PROFESSIONAL_STUDENT,0x00000070 coreconnectedn,PRODUCT_CORE_CONNECTED_N,0x00000071 professionalstudentn,PRODUCT_PROFESSIONAL_STUDENT_N,0x00000072 coreconnectedsinglelanguage,PRODUCT_CORE_CONNECTED_SINGLELANGUAGE,0x00000073 coreconnectedcountryspecific,PRODUCT_CORE_CONNECTED_COUNTRYSPECIFIC,0x00000074 connectedcar,PRODUCT_CONNECTED_CAR,0x00000075 industryhandheld,PRODUCT_INDUSTRY_HANDHELD,0x00000076 ppipro,PRODUCT_PPI_PRO,0x00000077 serverarm64,PRODUCT_ARM64_SERVER,0x00000078 education,PRODUCT_EDUCATION,0x00000079 educationn,PRODUCT_EDUCATION_N,0x0000007A iotuap,PRODUCT_IOTUAP,0x0000007B serverhi,PRODUCT_CLOUD_HOST_INFRASTRUCTURE_SERVER,0x0000007C enterprises,PRODUCT_ENTERPRISE_S,0x0000007D enterprisesn,PRODUCT_ENTERPRISE_S_N,0x0000007E professionals,PRODUCT_PROFESSIONAL_S,0x0000007F professionalsn,PRODUCT_PROFESSIONAL_S_N,0x00000080 enterpriseseval,PRODUCT_ENTERPRISE_S_EVALUATION,0x00000081 enterprisesneval,PRODUCT_ENTERPRISE_S_N_EVALUATION,0x00000082 iotuapcommercial,PRODUCT_IOTUAPCOMMERCIAL,0x00000083 mobileenterprise,PRODUCT_MOBILE_ENTERPRISE,0x00000085 analogonecore,PRODUCT_HOLOGRAPHIC,0x00000087 holographic,PRODUCT_HOLOGRAPHIC_BUSINESS,0x00000088 professionalsinglelanguage,PRODUCT_PRO_SINGLE_LANGUAGE,0x0000008A professionalcountryspecific,PRODUCT_PRO_CHINA,0x0000008B enterprisesubscription,PRODUCT_ENTERPRISE_SUBSCRIPTION,0x0000008C enterprisesubscriptionn,PRODUCT_ENTERPRISE_SUBSCRIPTION_N,0x0000008D serverdatacenternano,PRODUCT_DATACENTER_NANO_SERVER,0x0000008F serverstandardnano,PRODUCT_STANDARD_NANO_SERVER,0x00000090 serverdatacenteracor,PRODUCT_DATACENTER_A_SERVER_CORE,0x00000091 serverstandardacor,PRODUCT_STANDARD_A_SERVER_CORE,0x00000092 serverdatacentercor,PRODUCT_DATACENTER_WS_SERVER_CORE,0x00000093 serverstandardcor,PRODUCT_STANDARD_WS_SERVER_CORE,0x00000094 utilityvm,PRODUCT_UTILITY_VM,0x00000095 serverdatacenterevalcor,PRODUCT_DATACENTER_EVALUATION_SERVER_CORE,0x0000009F serverstandardevalcor,PRODUCT_STANDARD_EVALUATION_SERVER_CORE,0x000000A0 professionalworkstation,PRODUCT_PRO_WORKSTATION,0x000000A1 professionalworkstationn,PRODUCT_PRO_WORKSTATION_N,0x000000A2 serverazure,PRODUCT_AZURE_SERVER,0x000000A3 professionaleducation,PRODUCT_PRO_FOR_EDUCATION,0x000000A4 professionaleducationn,PRODUCT_PRO_FOR_EDUCATION_N,0x000000A5 serverazurecor,PRODUCT_AZURE_SERVER_CORE,0x000000A8 serverazurenano,PRODUCT_AZURE_NANO_SERVER,0x000000A9 enterpriseg,PRODUCT_ENTERPRISEG,0x000000AB enterprisegn,PRODUCT_ENTERPRISEGN,0x000000AC businesssubscription,PRODUCT_BUSINESS,0x000000AD businesssubscriptionn,PRODUCT_BUSINESS_N,0x000000AE serverrdsh,PRODUCT_SERVERRDSH,0x000000AF cloud,PRODUCT_CLOUD,0x000000B2 cloudn,PRODUCT_CLOUDN,0x000000B3 hubos,PRODUCT_HUBOS,0x000000B4 onecoreupdateos,PRODUCT_ONECOREUPDATEOS,0x000000B6 cloude,PRODUCT_CLOUDE,0x000000B7 andromeda,PRODUCT_ANDROMEDA,0x000000B8 iotos,PRODUCT_IOTOS,0x000000B9 clouden,PRODUCT_CLOUDEN,0x000000BA iotedgeos,PRODUCT_IOTEDGEOS,0x000000BB iotenterprise,PRODUCT_IOTENTERPRISE,0x000000BC modernpc,PRODUCT_LITE,0x000000BD iotenterprises,PRODUCT_IOTENTERPRISES,0x000000BF systemos,PRODUCT_XBOX_SYSTEMOS,0x000000C0 nativeos,PRODUCT_XBOX_NATIVEOS,0x000000C1 gamecorexbox,PRODUCT_XBOX_GAMEOS,0x000000C2 gameos,PRODUCT_XBOX_ERAOS,0x000000C3 durangohostos,PRODUCT_XBOX_DURANGOHOSTOS,0x000000C4 scarletthostos,PRODUCT_XBOX_SCARLETTHOSTOS,0x000000C5 keystone,PRODUCT_XBOX_KEYSTONE,0x000000C6 cloudhost,PRODUCT_AZURE_SERVER_CLOUDHOST,0x000000C7 cloudmos,PRODUCT_AZURE_SERVER_CLOUDMOS,0x000000C8 cloudcore,PRODUCT_AZURE_SERVER_CLOUDCORE,0x000000C9 cloudeditionn,PRODUCT_CLOUDEDITIONN,0x000000CA cloudedition,PRODUCT_CLOUDEDITION,0x000000CB winvos,PRODUCT_VALIDATION,0x000000CC iotenterprisesk,PRODUCT_IOTENTERPRISESK,0x000000CD iotenterprisek,PRODUCT_IOTENTERPRISEK,0x000000CE iotenterpriseseval,PRODUCT_IOTENTERPRISESEVAL,0x000000CF agentbridge,PRODUCT_AZURE_SERVER_AGENTBRIDGE,0x000000D0 nanohost,PRODUCT_AZURE_SERVER_NANOHOST,0x000000D1 wnc,PRODUCT_WNC,0x000000D2 serverazurestackhcicor,PRODUCT_AZURESTACKHCI_SERVER_CORE,0x00000196 serverturbine,PRODUCT_DATACENTER_SERVER_AZURE_EDITION,0x00000197 serverturbinecor,PRODUCT_DATACENTER_SERVER_CORE_AZURE_EDITION,0x00000198 '@ | ConvertFrom-Csv function New-IntPtr { param( [Parameter(Mandatory=$true)] [int]$Size, [Parameter(Mandatory=$false)] [int]$InitialValue = 0, [switch]$WriteSizeAtZero ) # Allocate unmanaged memory $ptr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($Size) # Zero memory $Global:type::RtlZeroMemory.Invoke($ptr, [UIntPtr]::new($Size)) # Write size at offset 0 if requested if ($WriteSizeAtZero) { [Marshal]::WriteInt32($ptr, 0, $Size) } # Otherwise, if size == 4 and initial value != 0, write initial value elseif ($Size -eq 4 -and $InitialValue -ne 0) { [Marshal]::WriteInt32($ptr, 0, $InitialValue) } return $ptr } Function Init-NTDLL { $Method = [PSCustomObject]@{ attributes = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl CallingConventions = [CallingConventions]::Standard nativeCallConv = [CallingConvention]::Winapi nativeCharSet = [CharSet]::Unicode ImplAttributes = [MethodImplAttributes]::PreserveSig TypeAttributes = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed } # Define dynamic type and methods before main try-finally $asmName = New-Object AssemblyName "NativeOSVersionAssembly" $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly($asmName, [AssemblyBuilderAccess]::Run) $mod = $asm.DefineDynamicModule("NativeOSVersionModule") $tb = $mod.DefineType("NativeMethods", $Method.TypeAttributes) $NtdllFunctions = @( @{ Name = "RtlGetVersion"; Dll = "ntdll.dll"; ReturnType = [int]; Parameters = [IntPtr] }, @{ Name = "RtlGetCurrentPeb"; Dll = "ntdll.dll"; ReturnType = [IntPtr]; Parameters = @() }, @{ Name = "RtlGetProductInfo"; Dll = "ntdll.dll"; ReturnType = [bool]; Parameters = [Type[]]@([UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) }, @{ Name = "RtlGetNtVersionNumbers"; Dll = "ntdll.dll"; ReturnType = [void]; Parameters = ([IntPtr], [IntPtr], [IntPtr]) }, @{ Name = "RtlZeroMemory"; Dll = "ntdll.dll"; ReturnType = [void]; Parameters = [Type[]]@([IntPtr], [UIntPtr]) }, @{ Name = "RtlGetProcessHeaps"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr]) }, @{ Name = "NtQueryInformationProcess"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([IntPtr], [UInt32], [IntPtr], [UInt32], [IntPtr]) }, @{ Name = "NtQuerySystemInformation"; Dll = "ntdll.dll"; ReturnType = [UInt32]; Parameters = [Type[]]@([UInt32], [IntPtr], [UInt32], [IntPtr]) } ) foreach ($func in $NtdllFunctions) { $tb.DefinePInvokeMethod($func.Name, $func.Dll, $Method.attributes, $Method.CallingConventions, $func.ReturnType, $func.Parameters, $Method.nativeCallConv, $Method.nativeCharSet ).SetImplementationFlags($Method.ImplAttributes) } return $tb.CreateType() } Function Init-osVersion { try { # 0x026C, ULONG NtMajorVersion; NT 4.0 and higher $NtMajorVersion = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x26C)) # 0x0270, ULONG NtMinorVersion; NT 4.0 and higher $NtMinorVersion = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x270)) # 0x0260, ULONG NtBuildNumber; NT 10.0 & higher $NtBuildNumber = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x0260)) if (($NtMajorVersion -lt 10) -or ( $NtBuildNumber -lt 10240)) { # this offset for nt 10.0 & Up # NT 6.3 end in 9600, # nt 10.0 start with 10240 (RTM) # Before, we stop throw, # Try read from PEB memory. if ([Intptr]::Zero -eq $Global:PebPtr) { throw } $offset = if ([IntPtr]::Size -eq 8) { 0x120 } else { 0x0AC } $NtBuildNumber = [int][Marshal]::ReadInt16($Global:PebPtr, $offset) # 0xAC, 0x0120, USHORT OSBuildNumber; 4.0 and higher if ($NtBuildNumber -lt 1381) { throw } } # Retrieve the OS version details return [PSCustomObject]@{ Major = $NtMajorVersion Minor = $NtMinorVersion Build = $NtBuildNumber UBR = $Global:ubr Version = ($NtMajorVersion,$NtMinorVersion,$NtBuildNumber) } } catch {} # Fallback: API try { $osVersion = Get-NativeOSVersion if ($null -eq $Global:osVersion) { Throw } return $osVersion } catch {} # Fallback: REGISTRY try { $major = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentMajorVersionNumber -ErrorAction SilentlyContinue).CurrentMajorVersionNumber $minor = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentMinorVersionNumber -ErrorAction SilentlyContinue).CurrentMinorVersionNumber $build = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentBuildNumber -ErrorAction SilentlyContinue).CurrentBuildNumber $osVersion = [PSCustomObject]@{ Major = [int]$major Minor = [int]$minor Build = [int]$build UBR = $Global:ubr Version = @([int]$major, [int]$minor, [int]$build) } return $osVersion } catch {} cls Write-Host write-host "Failed to retrieve OS version from all methods." Write-Host read-host exit 1 } Function Get-NativeOSVersion { # Pre-allocate memory $majorPtr = New-IntPtr -Size 4 -InitialValue 0 $minorPtr = New-IntPtr -Size 4 -InitialValue 0 $buildPtr = New-IntPtr -Size 4 -InitialValue 0 $versionInfoPtr = New-IntPtr -Size 284 -WriteSizeAtZero try { # First API: RtlGetNtVersionNumbers try { $Global:type::RtlGetNtVersionNumbers($majorPtr, $minorPtr, $buildPtr) $major = [Marshal]::ReadInt32($majorPtr) $minor = [Marshal]::ReadInt32($minorPtr) $build = [Marshal]::ReadInt32($buildPtr) -band 0xFFFF if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} # Second API: RtlGetVersion try { $status = $Global:type::RtlGetVersion($versionInfoPtr) if ($status -ne 0) { throw } $major = [Marshal]::ReadInt32($versionInfoPtr, 4) $minor = [Marshal]::ReadInt32($versionInfoPtr, 8) $build = [Marshal]::ReadInt32($versionInfoPtr, 12) if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} } finally { [Marshal]::FreeHGlobal($majorPtr) [Marshal]::FreeHGlobal($minorPtr) [Marshal]::FreeHGlobal($buildPtr) [Marshal]::FreeHGlobal($versionInfoPtr) } return $null } function Get-ProductID { # DigitalProductId4, WCHAR szEditionType[260]; $DigitalProductId4 = Parse-DigitalProductId4 if ($DigitalProductId4 -and $DigitalProductId4.EditionType -and (-not [String]::IsNullOrWhiteSpace($DigitalProductId4.EditionType))) { return $DigitalProductId4.EditionType } # You can also use the OperatingSystemSKU property of the Win32_OperatingSystem WMI class. [INT]$productType = 0 $null = [INT]::TryParse($OperatingSystemInfo.ProductType,[ref]$productType) if ($productType -ne 0) { # Lookup in the table by converting the hex string to int $match = $Global:productTypeTable | Where-Object { [Convert]::ToInt32($_.DWORD, 16) -eq $productType } return $match.ProductID } try { $status = $false $productTypePtr = New-IntPtr -Size 4 -InitialValue 0 $status = $Global:type::RtlGetProductInfo( $Global:OperatingSystemInfo.dwOSMajorVersion, $Global:OperatingSystemInfo.dwOSMinorVersion, $Global:OperatingSystemInfo.dwSpMajorVersion, $Global:OperatingSystemInfo.dwSpMinorVersion, $productTypePtr) if (-not $status) { throw } [INT]$productType = [Marshal]::ReadInt32($productTypePtr) $match = $Global:productTypeTable | Where-Object { [Convert]::ToInt32($_.DWORD, 16) -eq $productType } return $match.ProductID } catch { } Finally { # Clean up by freeing the allocated memory [Marshal]::FreeHGlobal($productTypePtr) } # Key: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion Propery: EditionID $EditionID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name EditionID -ErrorAction SilentlyContinue).EditionID if ($EditionID) { return $EditionID } cls Write-Host write-host "Failed to Edition ID version from all methods." Write-Host read-host exit 1 } function Get-StringFromBytes { param( [byte[]]$array, [int]$start, [int]$length ) if ($start + $length -le $array.Length) { return [System.Text.Encoding]::Unicode.GetString($array, $start, $length).TrimEnd([char]0) } else { Write-Warning "Requested string range $start to $($start + $length) exceeds array length $($array.Length)" return "" } } function Get-AsciiString { param([byte[]]$array, [int]$start, [int]$length) if ($start + $length -le $array.Length) { return [System.Text.Encoding]::ASCII.GetString($array, $start, $length).TrimEnd([char]0) } else { Write-Warning "Requested ASCII string range $start to $($start + $length) exceeds array length $($array.Length)" return "" } } function Get-DigitalProductKey { param ( [Parameter(Mandatory=$true)] [byte[]]$bCDKeyArray ) # Clone to avoid modifying the original array $bCDKey = $bCDKeyArray.Clone() # Check if it's Windows 8 $isWin8 = (($bCDKey[14] -shr 3) -band 1) # Modify byte at index 14 of bCDKey $bCDKey[14] = $bCDKey[14] -band 0xF7 # Characters used in Windows key $Chars = "BCDFGHJKMPQRTVWXY2346789" $KeyOutput = "" # Standard Base24 decoding from bCDKey for ($i = 24; $i -ge 0; $i--) { $Cur = 0 for ($X = 14; $X -ge 0; $X--) { $Cur = $Cur * 256 $Cur += $bCDKey[$X] # Read directly from bCDKey $bCDKey[$X] = [math]::Floor($Cur / 24) $Cur = $Cur % 24 } $KeyOutput = $Chars[$Cur] + $KeyOutput $Last = $Cur } # If it's Windows 8, put "N" in the right place if ($isWin8 -eq 1) { $keypart1 = $KeyOutput.Substring(1, $Last) $insert = "N" $KeyOutput = $keypart1 + $insert + $KeyOutput.Substring($Last + 1) } # Divide keys into 5-character parts $a = $KeyOutput.Substring(0, 5) $b = $KeyOutput.Substring(5, 5) $c = $KeyOutput.Substring(10, 5) $d = $KeyOutput.Substring(15, 5) $e = $KeyOutput.Substring(20, 5) # Join them again adding dashes return "$a-$b-$c-$d-$e" } function Get-LatestUBR { param ( [bool]$AutoMode = $false, [switch]$UseWinSxS, [switch]$UsePackages ) # Ensure that either -UseWinSxS or -UsePackages is specified, or AutoMode is enabled if (-not $AutoMode -and -not ($UsePackages -or $UseWinSxS)) { Write-Error "You must specify either -UseWinSxS or -UsePackages." return } if (-not $Global:CurrentVersion) { $Global:CurrentVersion = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' } $maxUbr = 0 $FileNames = @() $winsxsFilter = '*_10.*_none_*' $WinSxSPath = 'C:\Windows\WinSxS' $winsxsRegex = [regex]'10\.\d+\.\d+\.(\d+)' $packagefilter = '*~10.*' $PackagesPath = 'C:\Windows\Servicing\Packages' $packageRegex = [regex]'(10|11)\.\d+\.\d+\.(\d+)' $Global:CurrentVersion = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' function Get-LatestLCUDate { try { $session = New-Object -ComObject Microsoft.Update.Session $searcher = $session.CreateUpdateSearcher() $count = $searcher.GetTotalHistoryCount() $updates = $searcher.QueryHistory(0, $count) $cumulativeUpdates = $updates | Where-Object { $_.Title -like '*Cumulative Update*' -and $_.ResultCode -eq 2 } if ($cumulativeUpdates.Count -gt 0) { return ($cumulativeUpdates | Sort-Object Date -Descending | Select-Object -First 1).Date.Date } } catch {} return $null } function Get-FilesForLatestMonth { param ( [switch]$UseWinSxS, [switch]$UsePackages ) if ($UseWinSxS) { $PackagesPath = 'C:\Windows\WinSxS' $isFolderScan = $true } elseif ($UsePackages) { $PackagesPath = 'C:\Windows\Servicing\Packages' $isFolderScan = $false } else { Write-Error "Specify either -UseWinSxS or -UsePackages." return } $fileList = @() $latestYearMonth = $null $targetDate = Get-LatestLCUDate $dirInfo = [System.IO.DirectoryInfo] $PackagesPath if ($isFolderScan) { foreach ($folder in $dirInfo.EnumerateDirectories($winsxsFilter)) { $lastModified = $folder.LastWriteTime $ym = $lastModified.ToString('yyyy-MM') $ymd = $lastModified.Date # Prefer exact match on LCU date if available if ($targetDate -and $ymd -eq $targetDate) { $fileList += $folder.Name } elseif (-not $targetDate) { if (-not $latestYearMonth -or $ym -gt $latestYearMonth) { $latestYearMonth = $ym $fileList = @() } if ($ym -eq $latestYearMonth) { $fileList += $folder.Name } } } } else { foreach ($file in $dirInfo.EnumerateFiles($packagefilter)) { $lastModified = $file.LastWriteTime $ym = $lastModified.ToString('yyyy-MM') if (-not $latestYearMonth -or $ym -gt $latestYearMonth) { $latestYearMonth = $ym $fileList = @() } if ($ym -eq $latestYearMonth) { $fileList += $file.Name } } } return $fileList } function Get-UBRFromFolderName { param ( [Parameter(Mandatory=$true)] [string]$folderName ) try { $folderParts = $folderName.Split('_') if ($folderParts.Length -ge 4) { $versionPart = $folderParts[3] $versionParts = $versionPart.Split('.') if ($versionParts.Length -ge 4) { $ubr = $versionParts[3] return [int]$ubr } } return 0 } catch { Write-Warning "Error processing folder name: $folderName. Returning 0." return 0 } } function Get-UBRFromFileName { param ( [Parameter(Mandatory=$true)] [string]$fileName ) # Extract the part of the file name that contains the version info (before the extension) $fileNameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($fileName) # Split by '~' and check if version part exists $parts = $fileNameWithoutExt.Split('~') if ($parts.Length -gt 2) { $versionPart = $parts[-1] # The last part should contain version info like '10.0.19041.5854' # Split the version part by '.' to get version numbers $versionParts = $versionPart.Split('.') # Check for 4 parts and ensure it starts with "10" if ($versionParts.Length -eq 4 -and $versionParts[0] -eq '10') { # Extract UBR (fourth part of version) return [int]$versionParts[3] } } # Return 0 if version doesn't match "10.x.x.x" format return 0 } # Handle AutoMode if ($AutoMode) { # If AutoMode is enabled, leave this block empty for now } elseif ($UsePackages) { # Get the files for the latest month from the Packages folder $FileNames = Get-FilesForLatestMonth -UsePackages } elseif ($UseWinSxS) { # Get the files for the latest month from the WinSxS folder $FileNames = Get-FilesForLatestMonth -UseWinSxS } # Check if any files were found if (($UsePackages -or $UseWinSxS) -and $FileNames.Count -eq 0) { # No files found, returning 0 return 0 } # Further processing can go here, if required if ($AutoMode) { # --------------------- # >> Auto mode BEGIN << # --------------------- # 1) Scan Packages folder first $dirInfo = [System.IO.DirectoryInfo] $PackagesPath try { foreach ($file in $dirInfo.EnumerateFiles($packagefilter)) { $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) $match = $packageRegex.Match($nameWithoutExt) #$ubr = Get-UBRFromFileName $file.Name if ($match.Success) { $ubr = [int]$match.Groups[2].Value if ($ubr -gt $maxUbr) { $maxUbr = $ubr } } } } catch { # Ignore access or IO errors per extension scan } # keep for debug purpose # $maxUbr = 0 if ($maxUbr -gt 0) { return $maxUbr } # 2) Try update history COM + WinSxS folder scan $targetDate = Get-LatestLCUDate $dirInfo = [System.IO.DirectoryInfo] $WinSxSPath foreach ($folder in $dirInfo.EnumerateDirectories($winsxsFilter)) { if (-not $targetDate -or ( $folder.LastWriteTime.Date -eq $targetDate)) { $match = $winsxsRegex.Match($folder.Name) #$ubr = Get-UBRFromFolderName -folderName $folder.Name if ($match.Success) { $ubr = [int]$match.Groups[1].Value if ($ubr -gt $maxUbr) { $maxUbr = $ubr } } } } if ($maxUbr -gt 0) { return $maxUbr } # -------------------- # >> Auto mode END << # -------------------- } else { # --------------------- # >> Manu mode BEGIN << # --------------------- foreach ($fileName in $FileNames) { if ($UsePackages) { $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($fileName) $match = $packageRegex.Match($nameWithoutExt) #$ubr = Get-UBRFromFileName $fileName if ($match.Success) { $ubr = [int]$match.Groups[2].Value if ($ubr -gt $maxUbr) { $maxUbr = $ubr } } } elseif ($UseWinSxS) { $match = $winsxsRegex.Match($fileName) #$ubr = Get-UBRFromFolderName -folderName $fileName if ($match.Success) { $ubr = [int]$match.Groups[1].Value if ($ubr -gt $maxUbr) { $maxUbr = $ubr } } } } if ($maxUbr -gt 0) { return $maxUbr } # -------------------- # >> Manu mode END << # -------------------- } # 3) Fallback: read UBR from registry last (slowest) try { $regUbr = (Get-ItemProperty -Path $Global:CurrentVersion -Name UBR -ErrorAction Continue).UBR if ($regUbr -and ($regUbr -gt $maxUbr)) { return $regUbr } } catch { } return 0 } function Parse-DigitalProductId { param ( [string]$RegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" ) try { $digitalProductId = (Get-ItemProperty -Path $RegistryPath -ErrorAction Stop).DigitalProductId } catch { Write-Warning "Failed to read DigitalProductId from registry path $RegistryPath" return $null } if (-not $digitalProductId) { Write-Warning "DigitalProductId property not found in registry." return $null } # Ensure byte array $byteArray = if ($digitalProductId -is [byte[]]) { $digitalProductId } else { [byte[]]$digitalProductId } # Define offsets and lengths for each field in one hashtable $offsets = @{ uiSize = @{ Offset = 0; Length = 4 } MajorVersion = @{ Offset = 4; Length = 2 } MinorVersion = @{ Offset = 6; Length = 2 } ProductId = @{ Offset = 8; Length = 24 } EditionId = @{ Offset = 36; Length = 16 } bCDKey = @{ Offset = 52; Length = 16 } } # Extract components safely $uiSize = [BitConverter]::ToUInt32($byteArray, $offsets.UISize.Offset) $productId = Get-AsciiString -array $byteArray -start $offsets.ProductId.Offset -length $offsets.ProductId.Length $editionId = Get-AsciiString -array $byteArray -start $offsets.EditionId.Offset -length $offsets.EditionId.Length # Extract bCDKey array for product key decoding $bCDKeyArray = $byteArray[$offsets.bCDKey.Offset..($offsets.bCDKey.Offset + $offsets.bCDKey.Length - 1)] # Decode Digital Product Key (placeholder function - implement accordingly) $digitalProductKey = Get-DigitalProductKey -bCDKeyArray $bCDKeyArray # Extract MajorVersion and MinorVersion from byte array $majorVersion = [BitConverter]::ToUInt16($byteArray, $offsets.MajorVersion.Offset) $minorVersion = [BitConverter]::ToUInt16($byteArray, $offsets.MinorVersion.Offset) # Return structured object return [PSCustomObject]@{ UISize = $uiSize MajorVersion = $majorVersion MinorVersion = $minorVersion ProductId = $productId EditionId = $editionId DigitalKey = $digitalProductKey } } function Parse-DigitalProductId4 { param( [string]$RegistryPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" ) # Retrieve DigitalProductId4 from registry try { $digitalProductId4 = (Get-ItemProperty -Path $RegistryPath -ErrorAction Stop).DigitalProductId4 } catch { Write-Warning "Failed to read DigitalProductId4 from registry path $RegistryPath" return $null } if (-not $digitalProductId4) { Write-Warning "DigitalProductId4 property not found in registry." return $null } # Convert directly to byte array if not already one $byteArray = if ($digitalProductId4 -is [byte[]]) { $digitalProductId4 } else { [byte[]]$digitalProductId4 } # Offsets dictionary for structured fields with length included $offsets = @{ uiSize = @{ Offset = 0; Length = 4 } MajorVersion = @{ Offset = 4; Length = 2 } MinorVersion = @{ Offset = 6; Length = 2 } AdvancedPid = @{ Offset = 8; Length = 128 } ActivationId = @{ Offset = 136; Length = 128 } EditionType = @{ Offset = 280; Length = 520 } EditionId = @{ Offset = 888; Length = 128 } KeyType = @{ Offset = 1016; Length = 128 } EULA = @{ Offset = 1144; Length = 128 } bCDKey = @{ Offset = 808; Length = 16 } } # Extract values $uiSize = if ($byteArray.Length -ge 4) { [BitConverter]::ToUInt32($byteArray, 0) } else { 0 } $advancedPid = Get-StringFromBytes -array $byteArray -start $offsets.AdvancedPid.Offset -length $offsets.AdvancedPid.Length $activationId = Get-StringFromBytes -array $byteArray -start $offsets.ActivationId.Offset -length $offsets.ActivationId.Length $editionType = Get-StringFromBytes -array $byteArray -start $offsets.EditionType.Offset -length $offsets.EditionType.Length $editionId = Get-StringFromBytes -array $byteArray -start $offsets.EditionId.Offset -length $offsets.EditionId.Length $keyType = Get-StringFromBytes -array $byteArray -start $offsets.KeyType.Offset -length $offsets.KeyType.Length $eula = Get-StringFromBytes -array $byteArray -start $offsets.EULA.Offset -length $offsets.EULA.Length # Extract bCDKey array used for key retrieval $bCDKeyOffset = $offsets.bCDKey.Offset $bCDKeyLength = $offsets.bCDKey.Length $bCDKeyArray = $byteArray[$bCDKeyOffset..($bCDKeyOffset + $bCDKeyLength - 1)] # Extract MajorVersion and MinorVersion from byte array $majorVersion = [BitConverter]::ToUInt16($byteArray, $offsets.MajorVersion.Offset) $minorVersion = [BitConverter]::ToUInt16($byteArray, $offsets.MinorVersion.Offset) # Call to external helper to decode the Digital Product Key # You need to define this function based on your key decoding logic $digitalProductKey = Get-DigitalProductKey -bCDKeyArray $bCDKeyArray # Return a structured object return [PSCustomObject]@{ UISize = $uiSize MajorVersion = $majorVersion MinorVersion = $minorVersion AdvancedPID = $advancedPid ActivationID = $activationId EditionType = $editionType EditionID = $editionId KeyType = $keyType EULA = $eula DigitalKey = $digitalProductKey } } # INIT NTDLL Function $Global:type = Init-NTDLL $Global:PebPtr = $Global:type::RtlGetCurrentPeb() $Global:CurrentVersion = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $Global:OperatingSystem = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue $Global:ubr = try { Get-LatestUBR -AutoMode $true } catch { 0 } $Global:osVersion = Init-osVersion $Global:OperatingSystemInfo = [PSCustomObject]@{ ProductType = try { $Global:OperatingSystem.OperatingSystemSKU } catch { $null }; dwOSMajorVersion = $Global:osVersion.Major dwOSMinorVersion = $Global:osVersion.Minor dwSpMajorVersion = try {$Global:OperatingSystem.ServicePackMajorVersion} catch {0}; dwSpMinorVersion = try {$Global:OperatingSystem.ServicePackMinorVersion} catch {0}; } $Global:osVersion Get-NativeOSVersion Get-ProductID
if we like to make it stylish version. PSCustomObject object ? sure. Code: #requires -Version 5.1 #requires -RunAsAdministrator using namespace System.Runtime.InteropServices # $$$$$$$$$$$$$$$$$$$$$$$$ Read ME $$$$$$$$$$$$$$$$$$$$$$$$ # KUSER_SHARED_DATA # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm # KUSER_SHARED_DATA structure (ntddk.h) # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data # The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. # The only formal definition among headers in the Windows Driver Kit (WDK) or the Software Development Kit (SDK) is in assembly language headers: KS386. # INC from the WDK and KSAMD64. # INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address. # That they also define USER_SHARED_DATA for the kernel-mode address suggests that they too are intended for kernel-mode programming, # albeit of a sort that is at least aware of what address works for user-mode access. # Among relatively large structures, # the KUSER_SHARED_DATA is highly unusual for having exactly the same layout in 32-bit and 64-bit Windows. # This is because the one instance must be simultaneously accessible by both 32-bit and 64-bit code on 64-bit Windows, # and it’s desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows. # 0x026C,ULONG NtMajorVersion,4.0 and higher # 0x0270,ULONG NtMinorVersion,4.0 and higher # 0x0260,ULONG NtBuildNumber,10.0 and higher cls Write-Host $Global:osVersion = [PSCustomObject]@{ Major = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x26C)) Minor = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x270)) Build = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x260)) UBR = gpv 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name UBR -EA 0 Version = 0x26C,0x270,0x260 | % { [Marshal]::ReadInt32(0x7FFE0000 + $_) } } "Microsoft Windows" "Major Version : $($osVersion.Major)" "Minor Version : $($osVersion.Minor)" "Build Number : $($osVersion.Build)" "UBR : $($osVersion.UBR)" Write-Host return
Darki have fail safe version, using Direct memory, fail, registry, fail API, fail .. well Code: #requires -Version 5.1 #requires -RunAsAdministrator # RtlGetNtVersionNumbers Function # The RtlGetNtVersionNumbers function gets Windows version numbers directly from NTDLL. # https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrinit/getntversionnumbers.htm # RtlGetVersion function (wdm.h) # https://learn.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion # KUSER_SHARED_DATA # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm # KUSER_SHARED_DATA structure (ntddk.h) # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data # The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. # The only formal definition among headers in the Windows Driver Kit (WDK) or the Software Development Kit (SDK) is in assembly language headers: KS386. # INC from the WDK and KSAMD64. # INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address. # That they also define USER_SHARED_DATA for the kernel-mode address suggests that they too are intended for kernel-mode programming, # albeit of a sort that is at least aware of what address works for user-mode access. # Among relatively large structures, # the KUSER_SHARED_DATA is highly unusual for having exactly the same layout in 32-bit and 64-bit Windows. # This is because the one instance must be simultaneously accessible by both 32-bit and 64-bit code on 64-bit Windows, # and it’s desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows. using namespace System.Reflection using namespace System.Reflection.Emit using namespace System.Runtime.InteropServices function Get-NativeOSVersion { # Pre-allocate memory $majorPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4) $minorPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4) $buildPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4) $versionInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(284) [System.Runtime.InteropServices.Marshal]::WriteInt32($versionInfoPtr, 0, 284) # Define dynamic type and methods before main try-finally $asmName = New-Object AssemblyName "NativeOSVersionAssembly" $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly($asmName, [AssemblyBuilderAccess]::Run::Run) $mod = $asm.DefineDynamicModule("NativeOSVersionModule") $tb = $mod.DefineType("NativeMethods", [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed) # RtlGetNtVersionNumbers $tb.DefinePInvokeMethod("RtlGetNtVersionNumbers", "ntdll.dll", [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl, [CallingConventions]::Standard, [void], [Type[]]@([IntPtr], [IntPtr], [IntPtr]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Unicode ).SetImplementationFlags([MethodImplAttributes]::PreserveSig) # RtlGetVersion $tb.DefinePInvokeMethod("RtlGetVersion", "ntdll.dll", [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl, [CallingConventions]::Standard, [int], [Type[]]@([IntPtr]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Unicode ).SetImplementationFlags([MethodImplAttributes]::PreserveSig) $type = $tb.CreateType() try { # First API: RtlGetNtVersionNumbers try { $type::RtlGetNtVersionNumbers($majorPtr, $minorPtr, $buildPtr) $major = [System.Runtime.InteropServices.Marshal]::ReadInt32($majorPtr) $minor = [System.Runtime.InteropServices.Marshal]::ReadInt32($minorPtr) $build = [System.Runtime.InteropServices.Marshal]::ReadInt32($buildPtr) -band 0xFFFF if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} # Second API: RtlGetVersion try { $status = $type::RtlGetVersion($versionInfoPtr) if ($status -ne 0) { throw } $major = [System.Runtime.InteropServices.Marshal]::ReadInt32($versionInfoPtr, 4) $minor = [System.Runtime.InteropServices.Marshal]::ReadInt32($versionInfoPtr, 8) $build = [System.Runtime.InteropServices.Marshal]::ReadInt32($versionInfoPtr, 12) if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} } finally { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($majorPtr) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($minorPtr) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($buildPtr) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($versionInfoPtr) } return $null } $Global:CurrentVersion = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $Global:ubr = try { Get-ItemPropertyValue $Global:CurrentVersion -Name UBR -ea 1 } catch { 0 } try { $Global:osVersion = [PSCustomObject]@{ Major = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x26C)) Minor = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x270)) Build = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x260)) UBR = $Global:ubr Version = 0x26C,0x270,0x260 | ForEach-Object { [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + $_)) } } } catch { # Fallback: API try { $Global:osVersion = Get-NativeOSVersion if ($null -eq $Global:osVersion) { Throw } } catch { # Fallback: REGISTRY try { $major = Get-ItemPropertyValue $Global:CurrentVersion -Name CurrentMajorVersionNumber -ea 1 $minor = Get-ItemPropertyValue $Global:CurrentVersion -Name CurrentMinorVersionNumber -ea 1 $build = Get-ItemPropertyValue $Global:CurrentVersion -Name CurrentBuildNumber -ea 1 $Global:osVersion = [PSCustomObject]@{ Major = [int]$major Minor = [int]$minor Build = [int]$build UBR = $Global:ubr Version = @([int]$major, [int]$minor, [int]$build) } } catch { Write-Warning "Failed to retrieve OS version from all methods." $Global:osVersion = [PSCustomObject]@{ Major = 0 Minor = 0 Build = 0 UBR = 0 Version = @(0,0,0) } } } }
Why restrict to admin PS 5.1 though? it works fine in PS 2.0 (changing syntax) and/or non-admin you might consider using RtlGetNtVersionNumbers instead RtlGetVersion
Updated code. include abbodi1406 suggestion Also lower version to ps 3.0 & Up And direct memory access, check for wrong value (nt10 only) Also, using both ntdll api. so get build info Also, improve [int] check, to get right value (less tha nt10) Also, fix Get-NativeOSVersion, fill any intptr with Zero's, so, we can validate against real value Spoiler: Source Code Code: # RtlGetNtVersionNumbers Function # The RtlGetNtVersionNumbers function gets Windows version numbers directly from NTDLL. # https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrinit/getntversionnumbers.htm # RtlGetVersion function (wdm.h) # https://learn.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion # KUSER_SHARED_DATA # https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm # KUSER_SHARED_DATA structure (ntddk.h) # https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data # The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. # The only formal definition among headers in the Windows Driver Kit (WDK) or the Software Development Kit (SDK) is in assembly language headers: KS386. # INC from the WDK and KSAMD64. # INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address. # That they also define USER_SHARED_DATA for the kernel-mode address suggests that they too are intended for kernel-mode programming, # albeit of a sort that is at least aware of what address works for user-mode access. # Among relatively large structures, # the KUSER_SHARED_DATA is highly unusual for having exactly the same layout in 32-bit and 64-bit Windows. # This is because the one instance must be simultaneously accessible by both 32-bit and 64-bit code on 64-bit Windows, # and it's desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows. using namespace System.IO using namespace System.Net using namespace System.Text using namespace System.Threading using namespace System.Reflection using namespace System.IO.Compression using namespace System.ServiceProcess using namespace System.Reflection.Emit using namespace System.Security.Principal using namespace System.Text.RegularExpressions using namespace System.Runtime.InteropServices # Check if $PSVersionTable or $PSVersionTable.PSVersion is null, or Major version is invalid if ($null -eq $PSVersionTable -or $null -eq $PSVersionTable.PSVersion -or $null -eq $PSVersionTable.PSVersion.Major) { cls Write-Host Write-Host "Unable to determine PowerShell version." -ForegroundColor Green Write-Host "This script requires PowerShell 3.0 or higher." -ForegroundColor Green Start-Sleep 3 return } # Check if PowerShell version is lower than 3.0 if ($PSVersionTable.PSVersion.Major -lt 3) { cls Write-Host Write-Host "This script requires PowerShell 3.0 or higher!" -ForegroundColor Green Write-Host "Windows 8 & Above are supported." -ForegroundColor Green Start-Sleep 3 return } # Check if the current user is System or an Administrator $IsSystem = [WindowsIdentity]::GetCurrent().IsSystem $principal = [WindowsPrincipal]::new([WindowsIdentity]::GetCurrent()) # If the user is neither System nor an Administrator, show a warning and stop the script if (!$IsSystem -and -not $principal.IsInRole([WindowsBuiltInRole]::Administrator)) { cls Write-Host Write-Host "This script must be run as Administrator or System!" -ForegroundColor Green Write-Host "Please run this script as Administrator." -ForegroundColor Green Write-Host "(Right-click and select 'Run as Administrator')" -ForegroundColor Green Write-Host Read-Host "Press Enter to exit..." return } function Get-NativeOSVersion { # Pre-allocate memory $majorPtr = [Marshal]::AllocHGlobal(4) $minorPtr = [Marshal]::AllocHGlobal(4) $buildPtr = [Marshal]::AllocHGlobal(4) # Initialize the allocated memory with zeros (4 bytes for each DWORD) [Marshal]::WriteInt32($majorPtr, 0, 0) [Marshal]::WriteInt32($minorPtr, 0, 0) [Marshal]::WriteInt32($buildPtr, 0, 0) # Allocate memory for the OSVERSIONINFO structure (284 bytes) $versionInfoPtr = [Marshal]::AllocHGlobal(284) # Zero out the allocated memory to avoid garbage data [Marshal]::Copy([byte[]]@(0) * 284, 0, $versionInfoPtr, 284) # Write the size of the structure (284 bytes) to the beginning of the allocated memory [Marshal]::WriteInt32($versionInfoPtr, 0, 284) $Method = [PSCustomObject]@{ attributes = [MethodAttributes]::Public -bor [MethodAttributes]::Static -bor [MethodAttributes]::PinvokeImpl CallingConventions = [CallingConventions]::Standard nativeCallConv = [CallingConvention]::Winapi nativeCharSet = [CharSet]::Unicode ImplAttributes = [MethodImplAttributes]::PreserveSig TypeAttributes = [TypeAttributes]::Public -bor [TypeAttributes]::Abstract -bor [TypeAttributes]::Sealed } # Define dynamic type and methods before main try-finally $asmName = New-Object AssemblyName "NativeOSVersionAssembly" $asm = [AppDomain]::CurrentDomain.DefineDynamicAssembly($asmName, [AssemblyBuilderAccess]::Run::Run) $mod = $asm.DefineDynamicModule("NativeOSVersionModule") $tb = $mod.DefineType("NativeMethods", $Method.TypeAttributes) $api_builder = @( ("RtlGetVersion","ntdll.dll",[int], [IntPtr]), ("RtlGetNtVersionNumbers","ntdll.dll",[void], ([IntPtr], [IntPtr], [IntPtr]))) ForEach ($API in $api_builder) { $tb.DefinePInvokeMethod($API[0], $API[1], $Method.attributes, $Method.CallingConventions, $API[2], $API[3], $Method.nativeCallConv, $Method.nativeCharSet ).SetImplementationFlags($Method.ImplAttributes) } $type = $tb.CreateType() try { # First API: RtlGetNtVersionNumbers try { $type::RtlGetNtVersionNumbers($majorPtr, $minorPtr, $buildPtr) $major = [Marshal]::ReadInt32($majorPtr) $minor = [Marshal]::ReadInt32($minorPtr) $build = [Marshal]::ReadInt32($buildPtr) -band 0xFFFF if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} # Second API: RtlGetVersion try { $status = $type::RtlGetVersion($versionInfoPtr) if ($status -ne 0) { throw } $major = [Marshal]::ReadInt32($versionInfoPtr, 4) $minor = [Marshal]::ReadInt32($versionInfoPtr, 8) $build = [Marshal]::ReadInt32($versionInfoPtr, 12) if ($major -eq 0 -and $minor -eq 0 -and $build -eq 0) { throw } return [PSCustomObject]@{ Major = $major Minor = $minor Build = $build UBR = $Global:ubr Version = @($major, $minor, $build) } } catch {} } finally { [Marshal]::FreeHGlobal($majorPtr) [Marshal]::FreeHGlobal($minorPtr) [Marshal]::FreeHGlobal($buildPtr) [Marshal]::FreeHGlobal($versionInfoPtr) } return $null } $Global:CurrentVersion = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $Global:ubr = try { (Get-ItemProperty -Path $Global:CurrentVersion -Name UBR -ErrorAction SilentlyContinue).UBR } catch { 0 } try { # Initialize build version to $null $buildVersion = $null $parseSuccess = $false # ULONG NtBuildNumber; 10.0 and higher # Attempt to read the build version from system memory $buildPointer = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x260)) $parseSuccess = [int]::TryParse($buildPointer, [ref]$buildVersion) # Check if parsing the build version failed if (-not $parseSuccess -or $buildVersion -eq $null) { throw "Failed to retrieve the build version from system memory." } # Ensure the build version is greater than or equal to Windows 10 (build 10240 or higher) if ($buildVersion -lt 10240) { throw "The operating system version is below Windows 10 (build 10240), which is not supported." } # Store base address for future use $baseAddress = 0x7FFE0000 # Retrieve the OS version details $Global:osVersion = [PSCustomObject]@{ Major = [Marshal]::ReadInt32([IntPtr]($baseAddress + 0x26C)) Minor = [Marshal]::ReadInt32([IntPtr]($baseAddress + 0x270)) Build = $buildVersion UBR = $Global:ubr Version = (0x26C, 0x270, 0x260) | ForEach-Object { [Marshal]::ReadInt32([IntPtr]($baseAddress + $_)) } } } catch { # Fallback: API try { $Global:osVersion = Get-NativeOSVersion if ($null -eq $Global:osVersion) { Throw } } catch { # Fallback: REGISTRY try { $major = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentMajorVersionNumber -ErrorAction SilentlyContinue).CurrentMajorVersionNumber $minor = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentMinorVersionNumber -ErrorAction SilentlyContinue).CurrentMinorVersionNumber $build = (Get-ItemProperty -Path $Global:CurrentVersion -Name CurrentBuildNumber -ErrorAction SilentlyContinue).CurrentBuildNumber $Global:osVersion = [PSCustomObject]@{ Major = [int]$major Minor = [int]$minor Build = [int]$build UBR = $Global:ubr Version = @([int]$major, [int]$minor, [int]$build) } } catch { cls Write-Host write-host "Failed to retrieve OS version from all methods." Write-Host Start-Sleep 4 return } } } $Global:osVersion Get-NativeOSVersion
Just figured out something If on case of not nt10.0, offset is reserved But we can read major value (nt4.0 & up) So major value less than 10, Don't read build offset B'Cause major is NT version!! So simple and so stupid at the same time anyway, fix main thread. Spoiler: Fixed Code Code: try { # 0x026C, ULONG NtMajorVersion; NT 4.0 and higher $NtMajorVersion = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x26C)) # 0x0270, ULONG NtMinorVersion; NT 4.0 and higher $NtMinorVersion = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x270)) # 0x0260, ULONG NtBuildNumber; NT 10.0 & higher $NtBuildNumber = [int]0 $BuildNumber = [Marshal]::ReadInt32([IntPtr](0x7FFE0000 + 0x0260)) [int]::TryParse($BuildNumber, [ref] $NtBuildNumber) if (($NtMajorVersion -lt 10) -or ( $NtBuildNumber -lt 10240)) { # this offset for nt 10.0 & Up # NT 6.3 end in 9600, # nt 10.0 start with 10240 (RTM) throw } # Retrieve the OS version details $Global:osVersion = [PSCustomObject]@{ Major = $NtMajorVersion Minor = $NtMinorVersion Build = $NtBuildNumber UBR = $Global:ubr Version = ($NtMajorVersion,$NtMinorVersion,$NtBuildNumber) } } catch { # Fallback: API
it's confirmed under nt10.0, like w7,w8,w8.1, offset return value 0 in that case, we use API, but for most system -> this offset will return build number. and it is probably the location, where the api take it info
30/05/2025 ~ Changes Re-Build from scrath add new fail safe way, that work even on nt 4.0 still read from memory, just from other memory [peb] nice catch from abbodi1406
update the script, now with creative way to fetch ube value, very intersting way. i didn't see anyone think of it yet it take about 0.09 m's to get it - not from registry
new way to get UBR, no enum 999999 File's target spefic simple regex --> *-edition*10.*.*.* in Manifests & Packages, Packages its better contail less files' (2K) compare to Manifests ---> like 20K files ????? i will give credit on first idea to abbodi1406, result is, enum on 20-30 file's only b'cause EnumerateFiles-> filter =>> low level ignore 9999999 file's what i get in return is this small list Code: WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5678.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5678.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-Package~31bf3856ad364e35~amd64~~10.0.19041.5794.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-Package~31bf3856ad364e35~amd64~~10.0.19041.5794.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-WOW64-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-WOW64-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-WOW64-Package~31bf3856ad364e35~amd64~~10.0.19041.5794.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-Removable-WOW64-Package~31bf3856ad364e35~amd64~~10.0.19041.5794.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-WOW64-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-WOW64-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-WOW64-Package~31bf3856ad364e35~amd64~~10.0.19041.5848.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionPack-Professional-WOW64-Package~31bf3856ad364e35~amd64~~10.0.19041.5848.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-Editions-EnterpriseSN-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5854.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-Editions-EnterpriseSN-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5854.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-Editions-EnterpriseSN-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-Editions-EnterpriseSN-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionSpecific-EnterpriseSN-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5678.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionSpecific-EnterpriseSN-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.5678.mum WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionSpecific-EnterpriseSN-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.cat WARNING: C:\Windows\servicing\Packages\Microsoft-Windows-EditionSpecific-EnterpriseSN-Package~31bf3856ad364e35~amd64~~10.0.19041.5854.mum WARNING: C:\Windows\WinSxS\Manifests\amd64_microsoft-windows-edition-transmogrifier_31bf3856ad364e35_10.0.19041.5678_none_8a2c4126f07f6b63.manifest WARNING: C:\Windows\WinSxS\Manifests\amd64_microsoft-windows-editions-enterprisesn_31bf3856ad364e35_10.0.19041.5854_none_a714aa12e924ac74.manifest WARNING: C:\Windows\WinSxS\Manifests\wow64_microsoft-windows-edition-transmogrifier_31bf3856ad364e35_10.0.19041.5678_none_9480eb7924e02d5e.manifest WARNING: UBR VALUE: 5854 WARNING: Get-LatestUBR completed in 32 ms 5854 and now our function return result in 10 m's. crazy. Code: function Get-LatestUBR { $UBR = 0 $wildcardPattern = '*-edition*10.*.*.*' $regexVersion = [regex]'10\.0\.\d+\.(\d+)' $Manifestsfolder = 'C:\Windows\WinSxS\Manifests' $Packagessfolder = 'C:\Windows\servicing\Packages' $sw = [System.Diagnostics.Stopwatch]::StartNew() try { $files = [System.IO.Directory]::EnumerateFiles( $Packagessfolder, $wildcardPattern, [System.IO.SearchOption]::TopDirectoryOnly) foreach ($file in $files) { Write-Warning $file $match = $regexVersion.Match($file) if ($match.Success) { $candidateUBR = [int]$match.Groups[1].Value if ($candidateUBR -gt $UBR) { $UBR = $candidateUBR } } } } catch { Write-Warning "Failed to enumerate files" } # debug purpose only # $UBR = 0 try { if ($UBR -eq 0) { $files = [System.IO.Directory]::EnumerateFiles( $Manifestsfolder, $wildcardPattern, [System.IO.SearchOption]::TopDirectoryOnly) foreach ($file in $files) { Write-Warning $file $match = $regexVersion.Match($file) if ($match.Success) { $candidateUBR = [int]$match.Groups[1].Value if ($candidateUBR -gt $UBR) { $UBR = $candidateUBR } } } } } catch { Write-Warning "Failed to enumerate files" } if ($UBR -eq 0) { # Fallback to registry read try { $regPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $UBR = Get-ItemPropertyValue -Path $regPath -Name UBR -ErrorAction Stop Write-Warning "Fallback: Got UBR from registry: $UBR" } catch { } } $sw.Stop() Write-Warning "UBR VALUE: ${UBR}" Write-Warning "Get-LatestUBR completed in $($sw.ElapsedMilliseconds) ms" return $UBR }