From 6287127a9b9c6e14d1a0c72bfdca2393f704bc80 Mon Sep 17 00:00:00 2001 From: David Segura Date: Mon, 12 Oct 2026 07:55:02 -0500 Subject: [PATCH 1/3] feat(device): enhance hardware inventory collection and display in OSDCoreDevice feat(datetime): improve synchronization logic for Device DateTime with internet time test(device): add comprehensive tests for Show-OSDCloudDeviceInfo functionality docs(device): update documentation for Show-OSDCloudDeviceInfo and related functions --- .../Initialize-OSDCoreDevice.Tests.ps1 | 195 ++++++++++++++++++ .../core-device/Initialize-OSDCoreDevice.ps1 | 52 ++++- .../core-device/Sync-OSDCoreDateTime.ps1 | 42 ++-- .../WinOS/Show-OSDCloudDeviceInfo.Tests.ps1 | 186 +++++++++++++++++ .../public/WinOS/Show-OSDCloudDeviceInfo.ps1 | 168 ++++++++++----- 5 files changed, 564 insertions(+), 79 deletions(-) create mode 100644 OSDCloud/private/core-device/Initialize-OSDCoreDevice.Tests.ps1 create mode 100644 OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.Tests.ps1 diff --git a/OSDCloud/private/core-device/Initialize-OSDCoreDevice.Tests.ps1 b/OSDCloud/private/core-device/Initialize-OSDCoreDevice.Tests.ps1 new file mode 100644 index 0000000..39c20c5 --- /dev/null +++ b/OSDCloud/private/core-device/Initialize-OSDCoreDevice.Tests.ps1 @@ -0,0 +1,195 @@ +BeforeAll { + $tokens = $null + $parseErrors = $null + $initializerAst = [System.Management.Automation.Language.Parser]::ParseFile( + "$PSScriptRoot\Initialize-OSDCoreDevice.ps1", [ref]$tokens, [ref]$parseErrors + ) + if ($parseErrors.Count -gt 0) { + throw ($parseErrors -join "`n") + } + $snapshotAssignment = $initializerAst.Find({ + param($node) + $node -is [System.Management.Automation.Language.AssignmentStatementAst] -and + $node.Left.Extent.Text -eq '$global:OSDCoreDevice' -and + $node.Right.Extent.Text -like '[[]ordered]*' + }, $true) + $snapshotMap = $snapshotAssignment.Find({ + param($node) + $node -is [System.Management.Automation.Language.HashtableAst] + }, $true) + # Evaluate only the actual snapshot mappings, not the initializer's hardware + # queries, clock synchronization, disk discovery, or diagnostic writes. + $inventoryKeys = @('DiskDrives', 'NetworkAdapters', 'Processors', 'PhysicalMemory', 'NativeOSArchitecture', 'TotalPhysicalMemory') + $inventoryMappings = @($snapshotMap.KeyValuePairs | Where-Object { + $_.Item1.Value -in $inventoryKeys + }) + $mappingScript = [scriptblock]::Create( + "[ordered]@{`n" + (($inventoryMappings | ForEach-Object { + $_.Item1.Extent.Text + ' = ' + $_.Item2.Extent.Text + }) -join "`n") + "`n}" + ) + $initializerFunction = $initializerAst.Find({ + param($node) + $node -is [System.Management.Automation.Language.FunctionDefinitionAst] + }, $true) + $statements = $initializerFunction.Body.EndBlock.Statements + for ($i = 0; $i -lt $statements.Count; $i++) { + if ($statements[$i] -is [System.Management.Automation.Language.AssignmentStatementAst] -and + $statements[$i].Left.Extent.Text -eq '$classWin32PhysicalMemory') { + $physicalMemoryCollection = [scriptblock]::Create( + ($statements[$i..($i + 2)] | ForEach-Object { $_.Extent.Text }) -join "`n" + ) + break + } + } +} + +Describe 'Initialize-OSDCoreDevice physical memory collection' { + BeforeEach { + $LogsPath = $TestDrive + Mock Out-File {} + } + + It 'queries modules once and exports the complete diagnostic inventory' { + Mock Get-CimInstance { + [pscustomobject]@{ DeviceLocator = 'DIMM1'; Speed = 5600; ConfiguredClockSpeed = 4800 } + [pscustomobject]@{ DeviceLocator = 'DIMM2'; Speed = 5600; ConfiguredClockSpeed = 4800 } + } + . $physicalMemoryCollection + $classWin32PhysicalMemory.Count | Should -Be 2 + $classWin32PhysicalMemory[0].DeviceLocator | Should -Be 'DIMM1' + Should -Invoke Get-CimInstance -Times 1 -Exactly -ParameterFilter { + $ClassName -eq 'Win32_PhysicalMemory' -and $ErrorAction -eq 'Stop' + } + Should -Invoke Out-File -Times 2 -Exactly -ParameterFilter { + $FilePath -eq (Join-Path $LogsPath 'Win32_PhysicalMemory.txt') + } + } + + It 'warns and leaves an empty inventory if module collection fails' { + Mock Get-CimInstance { throw 'Memory provider unavailable.' } + Mock Write-Warning {} + . $physicalMemoryCollection + $classWin32PhysicalMemory.Count | Should -Be 0 + Should -Invoke Write-Warning -Times 1 -Exactly -ParameterFilter { + $Message -like '*Unable to query Win32_PhysicalMemory:*Memory provider unavailable*' + } + } +} + +Describe 'Initialize-OSDCoreDevice display inventory mappings' { + BeforeEach { + $classWin32NetworkAdapterConfiguration = @() + $classWin32PhysicalMemory = @() + } + + It 'matches IPv4 addresses by adapter index and excludes IPv6 and unassigned addresses' { + $classWin32NetworkAdapter = @( + [pscustomobject]@{ Name = 'Ethernet'; Index = 1 } + [pscustomobject]@{ Name = 'Wi-Fi'; Index = 2 } + [pscustomobject]@{ Name = 'Disconnected'; Index = 3 } + [pscustomobject]@{ Name = 'IPv6 only'; Index = 4 } + ) + $classWin32NetworkAdapterConfiguration = @( + [pscustomobject]@{ Index = 2; IPAddress = @('192.168.1.20') } + [pscustomobject]@{ Index = 1; IPAddress = @('fe80::1', '10.0.0.10', '10.0.0.11') } + [pscustomobject]@{ Index = 4; IPAddress = @('fe80::4') } + ) + $snapshot = & $mappingScript + $snapshot.NetworkAdapters[0].IPv4Addresses | Should -Be @('10.0.0.10', '10.0.0.11') + $snapshot.NetworkAdapters[1].IPv4Addresses | Should -Be @('192.168.1.20') + $snapshot.NetworkAdapters[2].IPv4Addresses.Count | Should -Be 0 + $snapshot.NetworkAdapters[3].IPv4Addresses.Count | Should -Be 0 + $jsonSnapshot = $snapshot | ConvertTo-Json -Depth 10 -WarningVariable serializationWarnings | ConvertFrom-Json + @($serializationWarnings).Count | Should -Be 0 + $jsonSnapshot.NetworkAdapters[0].IPv4Addresses | Should -Be @('10.0.0.10', '10.0.0.11') + } + + It 'exposes all six fields from the existing collection variables' { + $inventoryMappings.Count | Should -Be 6 + $classWin32DiskDrive = [pscustomobject]@{ Model = 'Disk'; ExtraDiskField = 'retained' } + $classWin32NetworkAdapter = [pscustomobject]@{ Name = 'Ethernet'; ExtraNetworkField = 'retained' } + $classWin32Processor = [pscustomobject]@{ Name = 'CPU'; ExtraProcessorField = 'retained' } + $classWin32PhysicalMemory = [pscustomobject]@{ ConfiguredClockSpeed = 4800; Speed = 5600; Capacity = 8GB } + $classWin32OperatingSystem = [pscustomobject]@{ OSArchitecture = '64-bit' } + $classWin32ComputerSystem = [pscustomobject]@{ TotalPhysicalMemory = 6.5GB } + $OSArchitecture = 'override' + $snapshot = & $mappingScript + $snapshot.NativeOSArchitecture | Should -Be '64-bit' + $snapshot.TotalPhysicalMemory | Should -Be 6.5GB + $snapshot.DiskDrives[0].Model | Should -Be 'Disk' + $snapshot.NetworkAdapters[0].Name | Should -Be 'Ethernet' + $snapshot.Processors[0].Name | Should -Be 'CPU' + + $jsonSnapshot = $snapshot | ConvertTo-Json -Depth 10 | ConvertFrom-Json + $xmlPath = Join-Path $TestDrive 'inventory.xml' + $snapshot | Export-Clixml -Path $xmlPath + $xmlSnapshot = Import-Clixml -Path $xmlPath + foreach ($roundTrip in @($jsonSnapshot, $xmlSnapshot)) { + $roundTrip.DiskDrives[0].ExtraDiskField | Should -Be 'retained' + $roundTrip.NetworkAdapters[0].ExtraNetworkField | Should -Be 'retained' + $roundTrip.Processors[0].ExtraProcessorField | Should -Be 'retained' + $roundTrip.PhysicalMemory[0].ConfiguredClockSpeed | Should -Be 4800 + $roundTrip.PhysicalMemory[0].Speed | Should -Be 5600 + $roundTrip.PhysicalMemory[0].Capacity | Should -Be 8GB + $roundTrip.TotalPhysicalMemory | Should -Be 6.5GB + $roundTrip.NativeOSArchitecture | Should -Be '64-bit' + } + } + + It 'excludes deep CIM metadata without truncating JSON or losing hardware properties' { + $metadata = [pscustomobject]@{ ClassName = 'TestClass' } + $metadata | Add-Member -NotePropertyName Parent -NotePropertyValue $metadata + $classWin32DiskDrive = [pscustomobject]@{ + Model = 'Disk' + DeviceID = 'disk0' + Size = [uint64]1TB + Capabilities = [uint16[]]@(3, 4) + CimClass = $metadata + CimInstanceProperties = $metadata + CimSystemProperties = $metadata + } + $classWin32NetworkAdapter = $classWin32DiskDrive + $classWin32Processor = $classWin32DiskDrive + $classWin32PhysicalMemory = $classWin32DiskDrive + $classWin32OperatingSystem = $null + $classWin32ComputerSystem = $null + $snapshot = & $mappingScript + $json = $snapshot | ConvertTo-Json -Depth 10 -WarningVariable serializationWarnings + @($serializationWarnings).Count | Should -Be 0 + $roundTrip = $json | ConvertFrom-Json + foreach ($key in @('DiskDrives', 'NetworkAdapters', 'Processors', 'PhysicalMemory')) { + $snapshot[$key][0].PSObject.Properties.Name | Should -Not -Contain 'CimClass' + $snapshot[$key][0].PSObject.Properties.Name | Should -Not -Contain 'CimInstanceProperties' + $snapshot[$key][0].PSObject.Properties.Name | Should -Not -Contain 'CimSystemProperties' + $roundTrip.$key[0].Model | Should -Be 'Disk' + $roundTrip.$key[0].Size | Should -Be 1TB + $roundTrip.$key[0].Capabilities | Should -Be @(3, 4) + } + $classWin32DiskDrive.CimClass | Should -Not -BeNullOrEmpty + } + + It 'normalizes collected objects to arrays without null placeholders' -ForEach @( + @{ Count = 0 } + @{ Count = 1 } + @{ Count = 2 } + ) { + $objects = @( + [pscustomobject]@{ Name = 'One' } + [pscustomobject]@{ Name = 'Two' } + ) + $classWin32DiskDrive = $objects | Select-Object -First $Count + $classWin32NetworkAdapter = $objects | Select-Object -First $Count + $classWin32Processor = $objects | Select-Object -First $Count + $classWin32PhysicalMemory = $objects | Select-Object -First $Count + $classWin32OperatingSystem = $null + $classWin32ComputerSystem = $null + $snapshot = & $mappingScript + foreach ($key in @('DiskDrives', 'NetworkAdapters', 'Processors', 'PhysicalMemory')) { + ($snapshot[$key] -is [array]) | Should -BeTrue + $snapshot[$key].Count | Should -Be $Count + } + $snapshot.TotalPhysicalMemory | Should -BeNullOrEmpty + $snapshot.NativeOSArchitecture | Should -BeNullOrEmpty + } +} diff --git a/OSDCloud/private/core-device/Initialize-OSDCoreDevice.ps1 b/OSDCloud/private/core-device/Initialize-OSDCoreDevice.ps1 index 2741f6b..41c3e69 100644 --- a/OSDCloud/private/core-device/Initialize-OSDCoreDevice.ps1 +++ b/OSDCloud/private/core-device/Initialize-OSDCoreDevice.ps1 @@ -23,6 +23,15 @@ function Initialize-OSDCoreDevice { before workflow execution so downstream steps can rely on a consistent device state snapshot. + The snapshot includes all hardware properties in the Processors, + DiskDrives, and NetworkAdapters inventories for display consumers, + excluding CIM infrastructure metadata to keep diagnostic JSON serializable. + It also includes raw TotalPhysicalMemory bytes and NativeOSArchitecture + (without overrides). NetworkAdapters includes IPv4Addresses matched by + adapter index from the collected IP-enabled network configurations. + PhysicalMemory includes the installed memory module inventory, including + Speed and ConfiguredClockSpeed, without CIM infrastructure metadata. + .EXAMPLE Initialize-OSDCoreDevice @@ -55,6 +64,18 @@ function Initialize-OSDCoreDevice { - Updates global state in $global:OSDCoreDevice. Changelog: + - 2026-10-10 | pending | Collect physical memory module inventory. + Added Win32_PhysicalMemory diagnostics and the PhysicalMemory snapshot + array for memory speed and installed DIMM count display. + - 2026-10-10 | pending | Include per-adapter IPv4 addresses for device display. + Matched existing network configurations by adapter index and retained + only IPv4 addresses without issuing additional hardware queries. + - 2026-10-10 | pending | Prevent hardware inventory JSON truncation. + Excluded CIM infrastructure metadata from display inventory snapshots + while retaining all hardware properties and complete per-class logs. + - 2026-10-10 | pending | Expose existing hardware inventory for device display. + Added complete processor, disk drive, and network adapter arrays, + raw memory bytes, and native OS architecture without additional queries. - 2026-08-13 | pending | Add OSDeploy identity and license properties. Added nullable idOSDeployDevice, idRegisteredEmail, and idRegisteredLicense values from WinPE environment variables or local license discovery. @@ -237,6 +258,16 @@ function Initialize-OSDCoreDevice { $classWin32OperatingSystem | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_OperatingSystem.txt') -Width 4096 -Force $OSArchitecture = $classWin32OperatingSystem.OSArchitecture | ConvertTo-TrimmedString #================================================= + # Win32_PhysicalMemory + $classWin32PhysicalMemory = @() + try { + $classWin32PhysicalMemory = @(Get-CimInstance -ClassName Win32_PhysicalMemory -ErrorAction Stop | Select-Object -Property *) + } + catch { + Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to query Win32_PhysicalMemory: $($_.Exception.Message)" + } + $classWin32PhysicalMemory | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_PhysicalMemory.txt') -Width 4096 -Force + #================================================= # Win32_PnPEntity $classWin32PnPEntity = Get-CimInstance -ClassName Win32_PnPEntity | Select-Object -Property * $classWin32PnPEntityError = $classWin32PnPEntity | Where-Object { $_.Status -eq 'Error' } | Sort-Object HardwareID -Unique | Sort-Object Name @@ -765,6 +796,7 @@ function Initialize-OSDCoreDevice { ComputerSystemProduct = [System.String]$ComputerSystemProduct ComputerSystemSKU = [System.String]$ComputerSystemSKU ComputerSystemType = [System.String]$ComputerSystemType + DiskDrives = @($classWin32DiskDrive | Where-Object { $null -ne $_ } | Select-Object -Property * -ExcludeProperty CimClass, CimInstanceProperties, CimSystemProperties) HardwareHash = [System.String]$HardwareHash IsAutopilotSpec = [System.Boolean]$IsAutopilotSpec IsDesktop = [System.Boolean]$IsDesktop @@ -782,15 +814,34 @@ function Initialize-OSDCoreDevice { LocalDisk = $LocalDisk LocalPartition = $LocalPartition LocalVolume = $LocalVolume + NativeOSArchitecture = $classWin32OperatingSystem.OSArchitecture NetGateways = $NetGateways NetIPAddress = $NetIPAddress NetMacAddress = $NetMacAddress + NetworkAdapters = @($classWin32NetworkAdapter | Where-Object { $null -ne $_ } | + Select-Object -Property * -ExcludeProperty CimClass, CimInstanceProperties, CimSystemProperties | + ForEach-Object { + $adapterIndex = $_.Index + $ipv4Addresses = [string[]]@($classWin32NetworkAdapterConfiguration | + Where-Object { $null -ne $adapterIndex -and $_.Index -eq $adapterIndex } | + ForEach-Object { $_.IPAddress } | + Where-Object { + $address = $null + [System.Net.IPAddress]::TryParse($_, [ref]$address) -and + $address.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork + }) + $_ | Add-Member -NotePropertyName IPv4Addresses -NotePropertyValue $ipv4Addresses -PassThru + } + ) OSArchitecture = $OSArchitecture OSVersion = $classWin32OperatingSystem.Version + PhysicalMemory = @($classWin32PhysicalMemory | Where-Object { $null -ne $_ } | Select-Object -Property * -ExcludeProperty CimClass, CimInstanceProperties, CimSystemProperties) ProcessorArchitecture = $ProcessorArchitecture + Processors = @($classWin32Processor | Where-Object { $null -ne $_ } | Select-Object -Property * -ExcludeProperty CimClass, CimInstanceProperties, CimSystemProperties) SerialNumber = $SerialNumber SystemFirmwareHardwareId = $SystemFirmwareHardwareId TimeZone = $classWin32TimeZone.StandardName + TotalPhysicalMemory = $classWin32ComputerSystem.TotalPhysicalMemory TotalPhysicalMemoryGB = $TotalPhysicalMemoryGB TpmIsActivated = $DeviceTpmIsActivated TpmIsEnabled = $DeviceTpmIsEnabled @@ -809,7 +860,6 @@ function Initialize-OSDCoreDevice { idRegisteredEmail = $idRegisteredEmail idRegisteredLicense = $idRegisteredLicense } - Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Ready: OSDCoreDevice" #================================================= # Export OSDCoreDevice to XML and JSON for use in other scripts or workflows $OSDCoreDeviceClixmlPath = Join-Path -Path $LogsPath -ChildPath 'OSDCoreDevice.xml' diff --git a/OSDCloud/private/core-device/Sync-OSDCoreDateTime.ps1 b/OSDCloud/private/core-device/Sync-OSDCoreDateTime.ps1 index d58b58d..628caf7 100644 --- a/OSDCloud/private/core-device/Sync-OSDCoreDateTime.ps1 +++ b/OSDCloud/private/core-device/Sync-OSDCoreDateTime.ps1 @@ -1,22 +1,22 @@ function Sync-OSDCoreDateTime { <# .SYNOPSIS - Synchronizes the system datetime with the internet datetime from Google. + Synchronizes the Device DateTime with the internet datetime from Google. .DESCRIPTION - Retrieves the current datetime from Google's HTTP Date header and compares it with the local system datetime. + Retrieves the current datetime from Google's HTTP Date header and compares it with the local Device DateTime. If the datetime difference exceeds the specified threshold and the system is running in WinPE (X: drive), - the function will update the system datetime to match the internet datetime. + the function will update the Device DateTime to match the internet datetime. Note: Uses HTTP instead of HTTPS to avoid certificate validation issues that may occur when the - system datetime is significantly out of sync. + Device DateTime is significantly out of sync. .PARAMETER ThresholdMinutes The minimum time difference in minutes required to trigger a synchronization warning or action. Default is 5 minutes. .PARAMETER Force - When specified, actually updates the system datetime when the time difference exceeds the threshold. + When specified, actually updates the Device DateTime when the time difference exceeds the threshold. Without this parameter, the function only reports time differences without making changes. .PARAMETER PassThru @@ -26,20 +26,20 @@ function Sync-OSDCoreDateTime { .EXAMPLE Sync-OSDCoreDateTime - Checks the system datetime against the internet datetime and reports any differences (in WinPE environment). + Checks the Device DateTime against the internet datetime and reports any differences (in WinPE environment). .EXAMPLE Sync-OSDCoreDateTime -Force - Checks and actually updates the system datetime if the datetime difference exceeds the threshold. + Checks and actually updates the Device DateTime if the datetime difference exceeds the threshold. .EXAMPLE Sync-OSDCoreDateTime -ThresholdMinutes 30 -Force -PassThru - Uses a 30-minute threshold, updates the system datetime if needed, and returns detailed synchronization results. + Uses a 30-minute threshold, updates the Device DateTime if needed, and returns detailed synchronization results. .NOTES - This function only modifies the system datetime when running in Windows Preinstallation Environment (WinPE), + This function only modifies the Device DateTime when running in Windows Preinstallation Environment (WinPE), detected by checking if the system drive is X:. #> [CmdletBinding(SupportsShouldProcess)] @@ -115,40 +115,40 @@ function Sync-OSDCoreDateTime { } if ($result.LocalDateTime -and $result.InternetDateTime) { - Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] System DateTime: $($result.LocalDateTime)" + Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Device DateTime: $($result.LocalDateTime)" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Network DateTime: $($result.InternetDateTime)" $result.DifferenceMinutes = [math]::Round([math]::Abs(($result.InternetDateTime - $result.LocalDateTime).TotalMinutes)) if ($result.DifferenceMinutes -gt $ThresholdMinutes) { - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: Time difference of $($result.DifferenceMinutes) minutes exceeds $ThresholdMinutes minute threshold." + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: Time difference of $($result.DifferenceMinutes) minutes exceeds $ThresholdMinutes minute threshold." if ($result.IsWinPE) { if ($Force) { - if ($PSCmdlet.ShouldProcess("System DateTime", "Set to $($result.InternetDateTime)")) { - Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] System DateTime: Synchronize with internet datetime." + if ($PSCmdlet.ShouldProcess("Device DateTime", "Set to $($result.InternetDateTime)")) { + Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Device DateTime: Synchronize with internet datetime." try { $null = Set-Date -Date $result.InternetDateTime -ErrorAction Stop $result.ClockUpdated = $true - Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] System DateTime: Successfully updated to internet datetime." + Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Device DateTime: Successfully updated to internet datetime." } catch { - $result.ErrorMessage = "Failed to set System DateTime: $($_.Exception.Message)" - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: $($result.ErrorMessage)" + $result.ErrorMessage = "Failed to set Device DateTime: $($_.Exception.Message)" + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: $($result.ErrorMessage)" } } } else { - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: System datetime is $($result.DifferenceMinutes) minutes out of sync with internet time" - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: Use -Force parameter to update the system datetime" + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: Device DateTime is $($result.DifferenceMinutes) minutes out of sync with internet time" + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: Use -Force parameter to update the Device DateTime" } } else { - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: System datetime is $($result.DifferenceMinutes) minutes out of sync with internet time" - Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] System DateTime: Please synchronize your system datetime manually (not in WinPE environment)" + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: Device DateTime is $($result.DifferenceMinutes) minutes out of sync with internet time" + Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Device DateTime: Please synchronize your Device DateTime manually (not in WinPE environment)" } } else { - # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] System DateTime: Synchronized within threshold ($($result.DifferenceMinutes) minutes difference)." + # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Device DateTime: Synchronized within threshold ($($result.DifferenceMinutes) minutes difference)." } } diff --git a/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.Tests.ps1 b/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.Tests.ps1 new file mode 100644 index 0000000..52949ce --- /dev/null +++ b/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.Tests.ps1 @@ -0,0 +1,186 @@ +BeforeAll { + . "$PSScriptRoot\Show-OSDCloudDeviceInfo.ps1" + . "$PSScriptRoot\Get-OSDCloudModuleVersion.ps1" + . "$PSScriptRoot\..\..\private\core-device\Initialize-OSDCoreDevice.ps1" + $originalDevice = $global:OSDCoreDevice + $originalTitle = $host.UI.RawUI.WindowTitle +} + +AfterAll { + $global:OSDCoreDevice = $originalDevice + $host.UI.RawUI.WindowTitle = $originalTitle +} + +Describe 'Show-OSDCloudDeviceInfo snapshot display' { + BeforeEach { + $script:inventory = [ordered]@{ + OSVersion = '10.0.26100' + NativeOSArchitecture = '64-bit' + OSArchitecture = 'overridden' + ComputerName = 'MININT-TEST' + OSDManufacturer = 'Test Manufacturer' + OSDProduct = 'Test Product' + OSDModel = 'Test Model' + BiosVersion = '1.2.3' + BiosReleaseDate = '2026-10-01' + TotalPhysicalMemory = 8GB + TotalPhysicalMemoryGB = 8 + PhysicalMemory = @( + [pscustomobject]@{ ConfiguredClockSpeed = 4800; Speed = 5600 } + [pscustomobject]@{ ConfiguredClockSpeed = 4800; Speed = 5600 } + ) + Processors = @( + [pscustomobject]@{ Name = 'CPU One'; NumberOfLogicalProcessors = 8 } + [pscustomobject]@{ Name = 'CPU Two'; NumberOfLogicalProcessors = 16 } + ) + DiskDrives = @( + [pscustomobject]@{ Model = 'Disk One'; DeviceID = '\\.\PHYSICALDRIVE0'; Size = [uint64]8000000000000 } + [pscustomobject]@{ Model = 'Disk Two'; DeviceID = '\\.\PHYSICALDRIVE1'; Size = [uint64]512110190592 } + ) + NetworkAdapters = @( + [pscustomobject]@{ Name = 'Ethernet'; GUID = 'adapter-guid'; MACAddress = 'AA-BB-CC-DD-EE-FF' } + [pscustomobject]@{ Name = 'Hidden'; GUID = $null; MACAddress = $null } + ) + SerialNumber = 'private-serial' + UUID = 'private-uuid' + } + $global:OSDCoreDevice = $null + $script:lines = [System.Collections.Generic.List[string]]::new() + $script:warnings = [System.Collections.Generic.List[string]]::new() + Mock Initialize-OSDCoreDevice { $global:OSDCoreDevice = $script:inventory } + Mock Get-OSDCloudModuleVersion { [version]'26.10.10.0' } + Mock Write-Host { $script:lines.Add([string]$Object) } + Mock Write-Warning { $script:warnings.Add($Message) } + Mock Get-CimInstance { throw 'Display must not query hardware.' } + Mock New-Item { throw 'Display must not create log directories.' } + Mock Out-File { throw 'Display must not export diagnostics.' } + } + + It 'renders the complete snapshot in order without querying or exporting hardware' { + $output = @(Show-OSDCloudDeviceInfo) + $output.Count | Should -Be 0 + $script:lines[0] | Should -Match 'OSDCloud PowerShell Module 26.10.10.0$' + ($script:lines | Select-Object -Skip 1) | Should -Be @( + 'Host: MININT-TEST | 10.0.26100 64-bit' + 'Device: Test Manufacturer | Test Product | Test Model' + 'BIOS: 1.2.3 | 2026-10-01' + 'Processor: CPU One | 8 Logical' + 'Processor: CPU Two | 16 Logical' + 'Memory: 8 GB Total | 2 DIMMs at 4800 MHz' + 'Disk: \\.\PHYSICALDRIVE0 | 8000GB Disk One' + 'Disk: \\.\PHYSICALDRIVE1 | 512GB Disk Two' + 'NetAdapter AA-BB-CC-DD-EE-FF: Ethernet' + ) + ($script:lines -join "`n") | Should -Not -Match 'private-serial|private-uuid|overridden|Hidden' + $script:warnings.Count | Should -Be 0 + Should -Invoke Initialize-OSDCoreDevice -Times 1 -Exactly + Should -Invoke Get-CimInstance -Times 0 -Exactly + Should -Invoke New-Item -Times 0 -Exactly + Should -Invoke Out-File -Times 0 -Exactly + } + + It 'preserves display memory rounding and warnings for GB' -ForEach @( + @{ MemoryGB = 5; DisplayGB = 5; Warn = $true } + @{ MemoryGB = 5.49; DisplayGB = 5; Warn = $true } + @{ MemoryGB = 5.5; DisplayGB = 6; Warn = $false } + @{ MemoryGB = 6; DisplayGB = 6; Warn = $false } + @{ MemoryGB = 6.5; DisplayGB = 6; Warn = $false } + @{ MemoryGB = 7; DisplayGB = 7; Warn = $false } + ) { + $script:inventory.TotalPhysicalMemory = $MemoryGB * 1GB + $script:inventory.TotalPhysicalMemoryGB = 99 + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain "Memory: $DisplayGB GB Total | 2 DIMMs at 4800 MHz" + @($script:warnings | Where-Object { $_ -like '*requires at least 6 GB*' }).Count | + Should -Be ([int]$Warn) + } + + It 'appends one or multiple IPv4 addresses after the matching MAC address' { + $script:inventory.NetworkAdapters = @( + [pscustomobject]@{ Name = 'Ethernet'; GUID = 'wired'; MACAddress = 'AA-BB'; IPv4Addresses = @('10.0.0.10', '10.0.0.11') } + [pscustomobject]@{ Name = 'Wi-Fi'; GUID = 'wireless'; MACAddress = 'CC-DD'; IPv4Addresses = @('192.168.1.20') } + [pscustomobject]@{ Name = 'Disconnected'; GUID = 'offline'; MACAddress = 'EE-FF'; IPv4Addresses = @() } + ) + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'NetAdapter AA-BB: Ethernet [10.0.0.10, 10.0.0.11]' + $script:lines | Should -Contain 'NetAdapter CC-DD: Wi-Fi [192.168.1.20]' + $script:lines | Should -Contain 'NetAdapter EE-FF: Disconnected' + Should -Invoke Get-CimInstance -Times 0 -Exactly + } + + It 'warns instead of reporting zero when physical memory is missing' { + $script:inventory.TotalPhysicalMemory = $null + Show-OSDCloudDeviceInfo + @($script:lines | Where-Object { $_ -like 'Memory:*' }).Count | Should -Be 0 + ($script:warnings -join "`n") | Should -Match 'Physical memory information is unavailable' + } + + It 'falls back to reported speed and lists distinct module speeds' { + $script:inventory.PhysicalMemory = @( + [pscustomobject]@{ ConfiguredClockSpeed = 0; Speed = 3200 } + [pscustomobject]@{ ConfiguredClockSpeed = 4800; Speed = 5600 } + ) + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Memory: 8 GB Total | 2 DIMMs at 3200, 4800 MHz' + Should -Invoke Get-CimInstance -Times 0 -Exactly + } + + It 'reports unavailable speed rather than zero while retaining the module count' { + $script:inventory.PhysicalMemory = @([pscustomobject]@{ ConfiguredClockSpeed = 0; Speed = $null }) + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Memory: 8 GB Total | 1 DIMM at Speed unavailable' + } + + It 'retains total memory when module inventory is unavailable' { + $script:inventory.PhysicalMemory = @() + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Memory: 8 GB Total | DIMM count unavailable at Speed unavailable' + } + + It 'handles element inventory collections' -ForEach @( + @{ Count = 0 } + @{ Count = 1 } + ) { + $script:inventory.Processors = @($script:inventory.Processors | Select-Object -First $Count) + $script:inventory.DiskDrives = @($script:inventory.DiskDrives | Select-Object -First $Count) + $script:inventory.NetworkAdapters = @($script:inventory.NetworkAdapters | Select-Object -First $Count) + Show-OSDCloudDeviceInfo + @($script:lines | Where-Object { $_ -like 'Processor:*' }).Count | Should -Be $Count + @($script:lines | Where-Object { $_ -like 'Disk:*' }).Count | Should -Be $Count + @($script:lines | Where-Object { $_ -like 'NetAdapter *' }).Count | Should -Be $Count + } + + It 'warns on failed initialization without displaying stale global inventory' { + $global:OSDCoreDevice = $script:inventory + Mock Initialize-OSDCoreDevice { throw 'Inventory collection failed.' } + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Host: Unavailable | Unavailable Unavailable' + @($script:lines | Where-Object { $_ -match '^(Device|Processor|Disk):|^NetAdapter ' }).Count | + Should -Be 0 + ($script:warnings -join "`n") | Should -Match 'Unable to initialize.*Inventory collection failed' + Should -Invoke Get-CimInstance -Times 0 -Exactly + Should -Invoke Out-File -Times 0 -Exactly + } + + It 'warns when initialization does not publish an inventory' { + Mock Initialize-OSDCoreDevice {} + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Host: Unavailable | Unavailable Unavailable' + ($script:warnings -join "`n") | Should -Match 'did not produce a device inventory' + } + + It 'still displays inventory when module version discovery fails' { + Mock Get-OSDCloudModuleVersion { throw 'Version unavailable.' } + Show-OSDCloudDeviceInfo + $script:lines[0] | Should -Match 'OSDCloud PowerShell Module Unavailable$' + $script:lines | Should -Contain 'Disk: \\.\PHYSICALDRIVE0 | 8000GB Disk One' + ($script:warnings -join "`n") | Should -Match 'Unable to retrieve.*Version unavailable' + } + + It 'reports unavailable disk size without inventing a zero capacity' { + $script:inventory.DiskDrives[0].Size = $null + Show-OSDCloudDeviceInfo + $script:lines | Should -Contain 'Disk: \\.\PHYSICALDRIVE0 | Size unavailable Disk One' + Should -Invoke Get-CimInstance -Times 0 -Exactly + } +} diff --git a/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.ps1 b/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.ps1 index 86e5516..ca6ae52 100644 --- a/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.ps1 +++ b/OSDCloud/public/WinOS/Show-OSDCloudDeviceInfo.ps1 @@ -3,7 +3,7 @@ Displays comprehensive WinPE and device hardware information during OS deployment startup. .DESCRIPTION -Gathers and displays detailed hardware and environment information in Windows PE including system specifications, device identifiers, processor details, memory configuration, disk drives, and network adapters. Initializes the OSDCloud device environment and exports hardware WMI information to log files in the temporary directory. Validates system memory requirements and provides warnings if minimum specifications are not met. +Displays detailed hardware and environment information from the initialized OSDCoreDevice snapshot in Windows PE including system specifications, device identifiers, processor details, memory configuration, disk drives, and network adapters. Initialize-OSDCoreDevice owns hardware collection and diagnostic exports. Validates system memory requirements and provides warnings if minimum specifications are not met. If initialization fails, warns and displays unavailable inventory without querying hardware independently. .PARAMETER None This function does not accept any parameters. @@ -13,7 +13,7 @@ Show-OSDCloudDeviceInfo Runs Show-OSDCloudDeviceInfo to display comprehensive WinPE and device information including hardware specifications and device identifiers. .OUTPUTS -None. This function displays system information to the console and exports hardware data to log files but does not return objects. +None. This function displays system information to the console but does not return objects. .NOTES This function is designed for use in Windows PE startup environments and performs the following operations: @@ -24,9 +24,9 @@ Information Displayed: - Device manufacturer and model - BIOS information - Processor name and logical core count -- Total physical memory in GB -- Disk drive models and device IDs -- Network adapter names and MAC addresses +- Total physical memory in GB, configured speed (or reported speed), and DIMM count +- Disk drive device IDs, sizes in whole decimal GB, and models +- Network adapter MAC addresses followed by names and bracketed assigned IPv4 addresses Note: Serial number and UUID output are suppressed for privacy reasons. @@ -34,14 +34,20 @@ System Requirements: - Minimum 6 GB of physical memory recommended - Issues warning if memory is less than 6 GB -Log Files Created: +Log Files Created by Initialize-OSDCoreDevice: - Stores device information in $env:TEMP\osdcloud-logs directory - Win32_DiskDrive.txt: Complete disk drive information - Win32_NetworkAdapter.txt: Complete network adapter information +- Win32_PhysicalMemory.txt: Complete installed memory module information -Functions Called: -- Get-OSDCloudModuleVersion: Retrieves current OSDCloud module version -- Initialize-OSDCoreDevice: Populates $global:OSDCoreDevice with hardware details +External Functions: +- Get-OSDCloudModuleVersion: Retrieves the loaded OSDCloud module version. +- Initialize-OSDCoreDevice: Collects device hardware details and populates + $global:OSDCoreDevice. + +PowerShell cmdlets used: +- Get-Date, Write-Host, Write-Verbose, and Write-Warning: Display operation + status, device information, and recoverable errors. The function updates the window title to '[OSDCloud] - WinPE and Device Information' to indicate the current operation status. #> @@ -50,57 +56,105 @@ function Show-OSDCloudDeviceInfo { param () #================================================= $Error.Clear() - $host.ui.RawUI.WindowTitle = "[$(Get-Date -format s)] OSDCloud WinPE and Device Information" - Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Start" - #================================================= - $OSDCloudModuleVersion = (Get-OSDCloudModuleVersion).ToString() - Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format s)] OSDCloud PowerShell Module $OSDCloudModuleVersion" - Initialize-OSDCoreDevice - #================================================= - # Create the log path if it does not already exist - $logsPath = Join-Path -Path $env:TEMP -ChildPath 'osdcloud-logs' - if (-not (Test-Path -Path $logsPath)) { - New-Item -Path $logsPath -ItemType Directory -Force | Out-Null - } - #================================================= - # Gather hardware information - $computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem -Property Name, TotalPhysicalMemory - $operatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -Property Version, OSArchitecture - $processors = Get-CimInstance -ClassName Win32_Processor -Property Name, NumberOfLogicalProcessors - #================================================= - # Device Details - Write-Host -ForegroundColor DarkGray "WinPE $($operatingSystem.Version) $($operatingSystem.OSArchitecture) $($computerSystem.Name)" - Write-Host -ForegroundColor DarkGray "Manufacturer: $($global:OSDCoreDevice.OSDManufacturer)" - Write-Host -ForegroundColor DarkGray "Model: $($global:OSDCoreDevice.OSDModel)" - Write-Host -ForegroundColor DarkGray "BIOS: $($global:OSDCoreDevice.BiosVersion)" - Write-Host -ForegroundColor DarkGray "BIOS Release Date: $($global:OSDCoreDevice.BiosReleaseDate)" - - foreach ($item in $processors) { - Write-Host -ForegroundColor DarkGray "Processor: $($item.Name) [$($item.NumberOfLogicalProcessors) Logical]" - } + $commandName = $MyInvocation.MyCommand.Name + $deviceInfo = $null + $deviceInitialized = $false - $totalMemoryGB = [math]::Round($computerSystem.TotalPhysicalMemory / 1GB) - Write-Host -ForegroundColor DarkGray "Memory: $totalMemoryGB GB" - if ($totalMemoryGB -lt 6) { - Write-Warning "OSDCloud WinPE requires at least 6 GB of memory to function properly. Errors are expected." - } + Write-Verbose "[$(Get-Date -format s)] [$commandName] Start: preparing to display WinPE and device information." - # Win32_DiskDrive - $diskDrives = Get-CimInstance -ClassName Win32_DiskDrive | Select-Object -Property * - $diskDrives | Out-File -FilePath (Join-Path -Path $logsPath -ChildPath 'Win32_DiskDrive.txt') -Width 4096 -Force - foreach ($item in $diskDrives) { - Write-Host -ForegroundColor DarkGray "Disk: $($item.Model) [$($item.DeviceID)]" - } + try { + try { + $host.ui.RawUI.WindowTitle = "[$(Get-Date -format s)] OSDCloud WinPE and Device Information" + Write-Verbose "[$(Get-Date -format s)] [$commandName] Updated the console window title." + } + catch { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] Unable to update the console window title: $($_.Exception.Message)" + } + + $OSDCloudModuleVersion = 'Unavailable' + try { + Write-Verbose "[$(Get-Date -format s)] [$commandName] Retrieving the loaded OSDCloud module version." + $moduleVersion = Get-OSDCloudModuleVersion -ErrorAction Stop + if ($null -ne $moduleVersion) { + $OSDCloudModuleVersion = $moduleVersion.ToString() + Write-Verbose "[$(Get-Date -format s)] [$commandName] Retrieved OSDCloud module version $OSDCloudModuleVersion." + } + else { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] The OSDCloud module version was not available." + } + } + catch { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] Unable to retrieve the OSDCloud module version: $($_.Exception.Message)" + } + Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format s)] OSDCloud PowerShell Module $OSDCloudModuleVersion" + + try { + Write-Verbose "[$(Get-Date -format s)] [$commandName] Initializing the OSDCore device inventory." + Initialize-OSDCoreDevice -ErrorAction Stop | Out-Null + if ($null -eq $global:OSDCoreDevice) { + throw 'OSDCore device initialization did not produce a device inventory.' + } + $deviceInfo = $global:OSDCoreDevice + $deviceInitialized = $true + Write-Verbose "[$(Get-Date -format s)] [$commandName] OSDCore device inventory initialization completed." + } + catch { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] Unable to initialize the OSDCore device inventory: $($_.Exception.Message)" + } + + foreach ($item in $deviceInfo.NetworkAdapters) { + if ($null -ne $item.GUID) { + $ipv4Display = if ($item.IPv4Addresses.Count -gt 0) { " [$($item.IPv4Addresses -join ', ')]" } else { '' } + Write-Host -ForegroundColor DarkGray "NetAdapter $($item.MACAddress): $($item.Name)$ipv4Display" + } + } + + $winPEVersion = if ($null -ne $deviceInfo) { $deviceInfo.OSVersion } else { 'Unavailable' } + $osArchitecture = if ($null -ne $deviceInfo) { $deviceInfo.NativeOSArchitecture } else { 'Unavailable' } + $computerName = if ($null -ne $deviceInfo) { $deviceInfo.ComputerName } else { 'Unavailable' } + Write-Verbose "[$(Get-Date -format s)] [$commandName] Displaying operating system and device identity information." + Write-Host -ForegroundColor DarkGray "Host: $computerName | $winPEVersion $osArchitecture" + + if ($deviceInitialized -and $null -ne $deviceInfo) { + Write-Host -ForegroundColor DarkGray "Device: $($deviceInfo.OSDManufacturer) | $($deviceInfo.OSDProduct) | $($deviceInfo.OSDModel)" + Write-Host -ForegroundColor DarkGray "BIOS: $($deviceInfo.BiosVersion) | $($deviceInfo.BiosReleaseDate)" + } + else { + Write-Verbose "[$(Get-Date -format s)] [$commandName] Skipping device identity display because the OSDCore device inventory is unavailable." + } + + foreach ($item in $deviceInfo.Processors) { + Write-Host -ForegroundColor DarkGray "Processor: $($item.Name) | $($item.NumberOfLogicalProcessors) Logical" + } + + if ($null -ne $deviceInfo -and $null -ne $deviceInfo.TotalPhysicalMemory) { + $totalMemoryGB = [math]::Round($deviceInfo.TotalPhysicalMemory / 1GB) + $memoryModules = @($deviceInfo.PhysicalMemory | Where-Object { $null -ne $_ }) + $memorySpeeds = @($memoryModules | ForEach-Object { + if ($_.ConfiguredClockSpeed -gt 0) { $_.ConfiguredClockSpeed } + elseif ($_.Speed -gt 0) { $_.Speed } + } | Sort-Object -Unique) + $memorySpeedDisplay = if ($memorySpeeds.Count -gt 0) { "$($memorySpeeds -join ', ') MHz" } else { 'Speed unavailable' } + $dimmDisplay = if ($memoryModules.Count -eq 1) { '1 DIMM' } elseif ($memoryModules.Count -gt 1) { "$($memoryModules.Count) DIMMs" } else { 'DIMM count unavailable' } + Write-Host -ForegroundColor DarkGray "Memory: $totalMemoryGB GB Total | $dimmDisplay at $memorySpeedDisplay" + Write-Verbose "[$(Get-Date -format s)] [$commandName] Reported $totalMemoryGB GB of physical memory." + if ($totalMemoryGB -lt 6) { + Write-Warning -WarningAction Continue "OSDCloud WinPE requires at least 6 GB of memory to function properly. Errors are expected." + } + } + else { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] Physical memory information is unavailable." + } - # Win32_NetworkAdapter - $networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapter | Select-Object -Property * - $networkAdapters | Out-File -FilePath (Join-Path -Path $logsPath -ChildPath 'Win32_NetworkAdapter.txt') -Width 4096 -Force - foreach ($item in $networkAdapters) { - if ($null -ne $item.GUID) { - Write-Host -ForegroundColor DarkGray "NetAdapter: $($item.Name) [$($item.MACAddress)]" + foreach ($item in $deviceInfo.DiskDrives) { + $diskSizeDisplay = if ($null -ne $item.Size) { "$([math]::Round($item.Size / 1000000000))GB" } else { 'Size unavailable' } + Write-Host -ForegroundColor DarkGray "Disk: $($item.DeviceID) | $diskSizeDisplay $($item.Model)" } } - #================================================= - Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] End" - #================================================= + catch { + Write-Warning -WarningAction Continue "[$(Get-Date -format s)] [$commandName] An unexpected error interrupted device information display: $($_.Exception.Message)" + } + finally { + Write-Verbose "[$(Get-Date -format s)] [$commandName] End: device information display finished." + } } From e890b976931037369c5e8ecafeb7fb411359c542 Mon Sep 17 00:00:00 2001 From: David Segura Date: Mon, 12 Oct 2026 07:55:18 -0500 Subject: [PATCH 2/3] chore(module): update module version to 26.10.10.1 --- OSDCloud/OSDCloud.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OSDCloud/OSDCloud.psd1 b/OSDCloud/OSDCloud.psd1 index 89d246f..f3b4e3f 100644 --- a/OSDCloud/OSDCloud.psd1 +++ b/OSDCloud/OSDCloud.psd1 @@ -7,7 +7,7 @@ @{ # --- Identity --- RootModule = 'OSDCloud.psm1' - ModuleVersion = '26.10.6.1' + ModuleVersion = '26.10.10.1' CompatiblePSEditions = @('Core', 'Desktop') GUID = '2fbd5c65-79c7-4561-9a2e-c4a4eebc89c7' From b4526c57cf6bb53254e29e53b63c47b0e08bb47b Mon Sep 17 00:00:00 2001 From: David Segura Date: Mon, 12 Oct 2026 08:00:30 -0500 Subject: [PATCH 3/3] docs: update changelog and README for version 26.10.10.1 --- CHANGELOG.md | 13 +++++++++++++ README.md | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de91bf..8867697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. +## 26.10.10.1 - October 10, 2026 + +### Added + +- Expanded the OSDCore device inventory with processor, physical memory, and network adapter IPv4 details, and added a physical memory diagnostic log. +- Expanded `Show-OSDCloudDeviceInfo` to display device and BIOS identity, processor details, memory capacity and configuration, disk drives, and network adapters with IPv4 addresses. Warn when physical memory is below 6 GB; serial number and UUID remain suppressed for privacy. +- Added tests for hardware inventory collection and device information display, including initialization and unavailable-data scenarios. + +### Changed + +- Renamed time synchronization status messages from "System DateTime" to "Device DateTime" for clearer reporting. +- Module version bumped from `26.10.6.1` to `26.10.10.1`. + ## 26.10.6.1 - October 6, 2026 ### Added diff --git a/README.md b/README.md index d43c9bc..3896b69 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ OSDCloud is a PowerShell module for deploying Windows with cloud-hosted operatin ## Overview -- Current repository module version: `26.10.6.1`. +- Current repository module version: `26.10.10.1`. - Defaults to Windows 11 26H2 for amd64 and arm64 deployments. - Focused on Windows deployment workflows driven by PowerShell. - Supports WinpeStartup helpers and deployment UX options. @@ -14,13 +14,13 @@ OSDCloud is a PowerShell module for deploying Windows with cloud-hosted operatin --- -## What's New in 26.10.6.1 +## What's New in 26.10.10.1 -Released October 6, 2026. +Released October 10, 2026. -- **Improved startup configuration:** WinpeStartup supports folder-based profiles, `.env` environment defaults, and importing registry and certificate files during environment setup. -- **Startup logging:** Startup output is transcribed to `X:\Windows\Temp\winpestartup.log`. -- **Profile cleanup:** Removed the obsolete German CLI startup profile. +- **Expanded device information:** `Show-OSDCloudDeviceInfo` displays device and BIOS details, processor and memory configuration, disk drives, and network adapters with IPv4 addresses. +- **Richer hardware inventory:** OSDCore collects physical memory module details and includes IPv4 addresses in network adapter inventory; memory below 6 GB is flagged with a warning. +- **Clearer time-sync reporting:** Status messages now identify the clock as "Device DateTime." See [CHANGELOG.md](CHANGELOG.md) for the complete release history and [WinpeStartup documentation](docs/about_winpestartup.md) for startup configuration.