system.ps1
|
#Requires -Version 5.0 # Shared native methods - compiled once and reused by Get-SystemMemory / Get-SystemUptime if ($null -eq ('SysInfoNative' -as [type])) { Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public class SysInfoNative { [StructLayout(LayoutKind.Sequential)] public struct MEMORYSTATUSEX { public uint dwLength; public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); [DllImport("kernel32.dll", SetLastError = true)] public static extern ulong GetTickCount64(); } '@ -ErrorAction Stop } $script:SysInfoInvariantCulture = [System.Globalization.CultureInfo]::InvariantCulture function Format-SysInfoInvariant { [OutputType([string])] [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $Format, [Parameter(ValueFromRemainingArguments = $true)] [object[]] $ArgumentList ) return [string]::Format($script:SysInfoInvariantCulture, $Format, $ArgumentList) } function Resolve-WindowsProductName { [OutputType([string])] [CmdletBinding()] param( [Parameter(Mandatory = $false)] [AllowNull()] [string] $ProductName, [Parameter(Mandatory = $false)] [int] $CurrentBuild = 0 ) if ([string]::IsNullOrWhiteSpace($ProductName)) { return $ProductName } if ($CurrentBuild -ge 22000 -and $ProductName -like 'Windows 10*') { return $ProductName -replace '^Windows 10', 'Windows 11' } return $ProductName } function Get-OSBuildNumber { <# .SYNOPSIS Returns the Windows build number as an integer. .DESCRIPTION Reads CurrentBuild from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion. This is a single cheap registry read - far faster than Get-CimInstance or any WMI-based approach. Returns e.g. 22621 (22H2), 22631 (23H2). .EXAMPLE PS> Get-OSBuildNumber 22621 .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([int])] [CmdletBinding()] param() $build = Get-ItemPropertyValue -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'CurrentBuild' -ErrorAction Stop return [int]$build } function Get-OSDisplayVersion { <# .SYNOPSIS Returns the Windows feature-update display name (e.g. "22H2", "23H2"). .DESCRIPTION Reads DisplayVersion from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion. Falls back to ReleaseId on older builds that lack DisplayVersion. .EXAMPLE PS> Get-OSDisplayVersion 23H2 .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([string])] [CmdletBinding()] param() try { $display = Get-ItemPropertyValue -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'DisplayVersion' -ErrorAction Stop if ($display) { return $display } } catch { Write-Verbose "DisplayVersion was not available; falling back to ReleaseId." } $releaseId = Get-ItemPropertyValue -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'ReleaseId' -ErrorAction Stop return $releaseId } function Get-OSEdition { <# .SYNOPSIS Returns the Windows edition SKU (e.g. "Professional", "Enterprise"). .DESCRIPTION Reads EditionID from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion. .EXAMPLE PS> Get-OSEdition Professional .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([string])] [CmdletBinding()] param() return Get-ItemPropertyValue -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID' -ErrorAction Stop } function Get-OSProductName { <# .SYNOPSIS Returns the full Windows product name string. .DESCRIPTION Reads ProductName from HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion. Returns e.g. "Windows 11 Pro" or "Windows 10 Enterprise". .EXAMPLE PS> Get-OSProductName Windows 11 Pro .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([string])] [CmdletBinding()] param() $key = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $props = Get-ItemProperty -LiteralPath $key -ErrorAction Stop return Resolve-WindowsProductName -ProductName $props.ProductName -CurrentBuild ([int]$props.CurrentBuild) } function Get-OSVersionInfo { <# .SYNOPSIS Returns a complete snapshot of Windows version metadata from the registry. .DESCRIPTION Performs a single Get-ItemProperty call against HKLM:\SOFTWARE\Microsoft\ Windows NT\CurrentVersion and returns all relevant fields in one object. Far cheaper than calling the individual Get-OS* functions when you need multiple values. .EXAMPLE PS> Get-OSVersionInfo .EXAMPLE PS> Get-OSVersionInfo | Format-List .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param() $key = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $props = Get-ItemProperty -LiteralPath $key -ErrorAction Stop $installDate = $null if ($props.InstallDate) { try { $unixEpochUtc = [DateTime]::SpecifyKind([DateTime]'1970-01-01T00:00:00Z', [DateTimeKind]::Utc) $installDate = $unixEpochUtc.AddSeconds([int64]$props.InstallDate).ToLocalTime() } catch { Write-Verbose "Unable to convert InstallDate registry value: $($props.InstallDate)" } } $currentBuild = [int]$props.CurrentBuild [PSCustomObject]@{ ProductName = Resolve-WindowsProductName -ProductName $props.ProductName -CurrentBuild $currentBuild EditionID = $props.EditionID DisplayVersion = $props.DisplayVersion CurrentBuild = $currentBuild UBR = if ($null -ne $props.UBR) { [int]$props.UBR } else { 0 } ReleaseId = $props.ReleaseId BuildBranch = $props.BuildBranch InstallDate = $installDate RegisteredOwner = $props.RegisteredOwner } } function Get-SystemMemory { <# .SYNOPSIS Returns physical memory statistics - total, available, used, and load percentage. .DESCRIPTION Uses kernel32!GlobalMemoryStatusEx via P/Invoke (no CIM/WMI overhead). Returns an object with human-readable GiB values and the raw bytes. .EXAMPLE PS> Get-SystemMemory .EXAMPLE PS> Get-SystemMemory | Format-List .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param() $memInfo = New-Object SysInfoNative+MEMORYSTATUSEX $memInfo.dwLength = [System.Runtime.InteropServices.Marshal]::SizeOf($memInfo) if (-not [SysInfoNative]::GlobalMemoryStatusEx([ref]$memInfo)) { Write-Error 'GlobalMemoryStatusEx failed.' return $null } $totalGiB = [math]::Round($memInfo.ullTotalPhys / 1GB, 2) $availableGiB = [math]::Round($memInfo.ullAvailPhys / 1GB, 2) $usedGiB = [math]::Round(($memInfo.ullTotalPhys - $memInfo.ullAvailPhys) / 1GB, 2) [PSCustomObject]@{ TotalBytes = $memInfo.ullTotalPhys AvailableBytes = $memInfo.ullAvailPhys UsedBytes = $memInfo.ullTotalPhys - $memInfo.ullAvailPhys LoadPercent = $memInfo.dwMemoryLoad TotalGiB = Format-SysInfoInvariant '{0:0.##}' $totalGiB AvailableGiB = Format-SysInfoInvariant '{0:0.##}' $availableGiB UsedGiB = Format-SysInfoInvariant '{0:0.##}' $usedGiB } } function Get-SystemDisk { <# .SYNOPSIS Returns disk usage information for fixed volumes backed by physical disks. .DESCRIPTION Uses Win32_DiskDrive associations to include only volumes that resolve back to a physical disk. Provider-backed and cloud-mounted drives such as Google Drive are ignored. Filters to fixed drives by default and returns total size, free space, used space, and the filesystem type. .PARAMETER All Include non-fixed volumes when they are backed by a physical disk. .EXAMPLE PS> Get-SystemDisk .EXAMPLE PS> Get-SystemDisk -All .EXAMPLE PS> Get-SystemDisk | Format-Table -AutoSize .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject[]])] [CmdletBinding()] param( [Parameter(Mandatory = $false)] [switch] $All = $false ) $logicalDisksByDeviceId = @{} try { $physicalDisks = Get-CimInstance -ClassName Win32_DiskDrive -ErrorAction Stop foreach ($physicalDisk in $physicalDisks) { $partitions = Get-CimAssociatedInstance -InputObject $physicalDisk -Association Win32_DiskDriveToDiskPartition -ErrorAction Stop foreach ($partition in $partitions) { $logicalDisks = Get-CimAssociatedInstance -InputObject $partition -Association Win32_LogicalDiskToPartition -ErrorAction Stop foreach ($logicalDisk in $logicalDisks) { if (-not $All -and [int]$logicalDisk.DriveType -ne 3) { continue } if ([string]::IsNullOrWhiteSpace($logicalDisk.DeviceID)) { continue } $logicalDisksByDeviceId[$logicalDisk.DeviceID] = $logicalDisk } } } } catch { Write-Verbose "Physical disk association inventory failed: $($_.Exception.Message)" $logicalDisks = Get-CimInstance -ClassName Win32_LogicalDisk -ErrorAction SilentlyContinue foreach ($logicalDisk in $logicalDisks) { if (-not $All -and [int]$logicalDisk.DriveType -ne 3) { continue } if ([string]::IsNullOrWhiteSpace($logicalDisk.DeviceID)) { continue } $logicalDisksByDeviceId[$logicalDisk.DeviceID] = $logicalDisk } } foreach ($deviceId in ($logicalDisksByDeviceId.Keys | Sort-Object)) { $disk = $logicalDisksByDeviceId[$deviceId] if ($null -eq $disk.Size -or [uint64]$disk.Size -eq 0) { continue } $totalBytes = [uint64]$disk.Size $freeBytes = [uint64]$disk.FreeSpace $totalGiB = [math]::Round($totalBytes / 1GB, 2) $freeGiB = [math]::Round($freeBytes / 1GB, 2) $usedGiB = [math]::Round(($totalBytes - $freeBytes) / 1GB, 2) $percentFree = [math]::Round($freeBytes * 100.0 / $totalBytes, 1) [PSCustomObject]@{ Name = $disk.DeviceID Label = $disk.VolumeName Type = switch ([int]$disk.DriveType) { 2 { 'Removable' } 3 { 'Fixed' } 4 { 'Network' } 5 { 'CDRom' } 6 { 'Ram' } default { 'Unknown' } } FileSystem = $disk.FileSystem TotalGiB = Format-SysInfoInvariant '{0:0.##}' $totalGiB FreeGiB = Format-SysInfoInvariant '{0:0.##}' $freeGiB UsedGiB = Format-SysInfoInvariant '{0:0.##}' $usedGiB PercentFree = Format-SysInfoInvariant '{0:0.#}' $percentFree TotalBytes = $totalBytes FreeBytes = $freeBytes } } } function Get-Hostname { <# .SYNOPSIS Returns the computer hostname. .DESCRIPTION Uses [System.Net.Dns]::GetHostName() and resolves it to a fully-qualified domain name when joined to a domain. Returns both the short hostname and, if different, the FQDN. .EXAMPLE PS> Get-Hostname .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param() $hostname = [System.Net.Dns]::GetHostName() $fqdn = $null try { $entry = [System.Net.Dns]::GetHostEntry($hostname) $fqdn = $entry.HostName if ($fqdn -eq $hostname) { $fqdn = $null } } catch { Write-Verbose "Unable to resolve FQDN for hostname: $hostname" } [PSCustomObject]@{ Hostname = $hostname FQDN = $fqdn } } function Get-SystemUptime { <# .SYNOPSIS Returns the system uptime (time since last boot). .DESCRIPTION Uses kernel32!GetTickCount64 via P/Invoke for a non-wrapping, high- precision uptime value - no CIM/WMI overhead. Returns the raw tick count, total milliseconds, and a human-readable breakdown. .EXAMPLE PS> Get-SystemUptime .EXAMPLE PS> Get-SystemUptime | Format-List .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param() $ticksMs = [SysInfoNative]::GetTickCount64() $span = [TimeSpan]::FromMilliseconds($ticksMs) [PSCustomObject]@{ TotalMilliseconds = $ticksMs Days = $span.Days Hours = $span.Hours Minutes = $span.Minutes Seconds = $span.Seconds TotalHours = Format-SysInfoInvariant '{0:0.#}' ([math]::Round($span.TotalHours, 1)) TotalDays = Format-SysInfoInvariant '{0:0.#}' ([math]::Round($span.TotalDays, 1)) Display = Format-SysInfoInvariant '{0}d {1:D2}h {2:D2}m {3:D2}s' $span.Days $span.Hours $span.Minutes $span.Seconds } } function Get-SystemInfo { <# .SYNOPSIS Returns a comprehensive system information snapshot (fetch-style). .DESCRIPTION Assembles OS version, memory, disk, hostname, and uptime into a single structured object. Disk data is resolved through physical disk associations; other data sources use registry reads, .NET APIs, and lightweight P/Invoke where possible. .EXAMPLE PS> Get-SystemInfo | Format-List .EXAMPLE PS> Get-SystemInfo .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param() $_os = Get-OSVersionInfo $_mem = Get-SystemMemory $_disks = Get-SystemDisk $_host = Get-Hostname $_up = Get-SystemUptime [PSCustomObject]@{ OSProductName = $_os.ProductName OSEdition = $_os.EditionID OSVersion = $_os.DisplayVersion OSBuild = $_os.CurrentBuild OSUBRev = $_os.UBR Hostname = $_host.Hostname FQDN = $_host.FQDN TotalMemoryGiB = $_mem.TotalGiB MemoryLoadPct = $_mem.LoadPercent Disks = ($_disks | ForEach-Object { Format-SysInfoInvariant '{0} {1}GiB/{2}GiB ({3}% free)' $_.Name $_.FreeGiB $_.TotalGiB $_.PercentFree }) -join ' | ' Uptime = $_up.Display InstallDate = $_os.InstallDate } } function Get-SystemPaths { <# .SYNOPSIS Returns standard winkit directory paths for tools, config, cache, data, and logs. .DESCRIPTION Provides a single structured lookup for the five canonical winkit folders. The -Name parameter controls the subdirectory name used under each root. Defaults to 'winkit' so callers can omit it for standard usage. .PARAMETER Name Subdirectory name under each root. Defaults to 'winkit'. .EXAMPLE PS> $paths = Get-SystemPaths PS> $paths.Data C:\ProgramData\winkit .EXAMPLE PS> Get-SystemPaths -Name 'myapp' | Format-List .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([PSCustomObject])] [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string] $Name = 'winkit' ) return [PSCustomObject]@{ Home = Join-Path -Path $env:USERPROFILE -ChildPath $Name Config = Join-Path -Path $env:APPDATA -ChildPath $Name Cache = Join-Path -Path $env:LOCALAPPDATA -ChildPath $Name Data = Join-Path -Path $env:ProgramData -ChildPath $Name Logs = Join-Path -Path $env:LOCALAPPDATA -ChildPath "$Name\logs" } } function Test-HostApplicability { <# .SYNOPSIS Returns $true if the current host meets all supplied applicability constraints. .DESCRIPTION Gates a setting or operation by build number, OS edition, and processor bitness. Any constraint that is not supplied is treated as "no restriction." All supplied constraints must pass — the check is a logical AND across them. Builds on the existing Get-OSBuildNumber and Get-OSEdition helpers in this file so it shares the same cheap registry-read path. .PARAMETER MinBuild Minimum Windows build number required (inclusive). Example: 22000 for Windows 11+. .PARAMETER MaxBuild Maximum Windows build number allowed (inclusive). Example: 22621 for Windows 11 22H2 and below. .PARAMETER Edition OS edition(s) that qualify. Accepts an array of strings such as @('Enterprise', 'Professional'). .PARAMETER Bitness Required processor architecture: x64 or x86. .OUTPUTS [bool] .EXAMPLE PS> if (-not (Test-HostApplicability -MinBuild 22000)) { Write-Log -Message 'Requires Windows 11+'; exit 0 } .EXAMPLE PS> Test-HostApplicability -Edition @('ServerStandard', 'ServerDatacenter') -Bitness x64 .LINK https://github.com/adnoctem/winkit/lib/system.ps1 .NOTES Author: MVProwess <info@mvprowess.com> License: MIT #> [OutputType([bool])] [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [int] $MinBuild, [Parameter(Mandatory = $false)] [int] $MaxBuild, [Parameter(Mandatory = $false)] [string[]] $Edition, [Parameter(Mandatory = $false)] [ValidateSet('x64', 'x86')] [string] $Bitness ) $build = Get-OSBuildNumber if ($PSBoundParameters.ContainsKey('MinBuild') -and $build -lt $MinBuild) { return $false } if ($PSBoundParameters.ContainsKey('MaxBuild') -and $build -gt $MaxBuild) { return $false } if ($PSBoundParameters.ContainsKey('Edition') -and $Edition.Count -gt 0) { $currentEdition = Get-OSEdition if ($currentEdition -notin $Edition) { return $false } } if ($PSBoundParameters.ContainsKey('Bitness')) { $is64Bit = [Environment]::Is64BitOperatingSystem if (($Bitness -eq 'x64' -and -not $is64Bit) -or ($Bitness -eq 'x86' -and $is64Bit)) { return $false } } return $true } |