private/Initialize-OSDCloudResults.Tests.ps1
|
BeforeAll { . "$PSScriptRoot\Initialize-OSDCloudResults.ps1" . "$PSScriptRoot\Update-OSDCloudResults.ps1" . "$PSScriptRoot\Export-OSDCloudResults.ps1" . "$PSScriptRoot\Send-OSDCloudResults.ps1" . "$PSScriptRoot\Set-OSDCloudAcquisitionResult.ps1" $stateNames = @('OSDCloudResults', 'OSDCloudDeploy', 'OSDCoreDevice', 'OSDCloudWorkflowInvoke') $originalState = @{} foreach ($name in $stateNames) { $originalState[$name] = Get-Variable -Name $name -Scope Global -ErrorAction SilentlyContinue } $originalTemp = $env:TEMP } AfterAll { $env:TEMP = $originalTemp foreach ($name in $stateNames) { if ($originalState[$name]) { Set-Variable -Name $name -Value $originalState[$name].Value -Scope Global } else { Remove-Variable -Name $name -Scope Global -ErrorAction SilentlyContinue } } } Describe 'Deployment results helpers' { BeforeEach { Mock Get-CimInstance { @() } $env:TEMP = Join-Path $TestDrive 'current' $targetRoot = Join-Path $TestDrive 'target' $global:OSDCoreDevice = [pscustomobject]@{ OSDManufacturer = 'Test OEM'; OSDModel = 'Model'; OSDProduct = 'Product' ProcessorArchitecture = 'arm64'; TotalPhysicalMemoryGB = 16 EndpointSHA = 'hashed-device'; UUID = 'raw-private-uuid' SerialNumber = 'private-serial'; idRegisteredLicense = 'private-license' } $global:OSDCloudDeploy = [pscustomobject]@{ WorkflowTaskName = 'OSDCloud'; OSEditionId = 'Professional'; DriverPackName = 'Test pack' DriverPackCloudObject = [pscustomobject]@{ FileName = 'drivers.cab' } OperatingSystemCloudObject = [pscustomobject]@{ OSName = 'Windows 11'; OSVersion = '26H2'; OSBuild = '26300' OSBuildVersion = '26300.9711'; OSActivation = 'Retail'; OSLanguageCode = 'en-us' FileName = 'install.esd'; FilePath = 'https://private.invalid/image.esd' } } $global:OSDCloudWorkflowInvoke = @{ ParamsExpandWindowsImage = @{ ApplyPath = $targetRoot } } $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSDownload = $true stepDriverPackDownload = $true } Initialize-OSDCloudResults -ModuleVersion '26.10.15.1' -WorkflowName default -LaunchMethod CLI Update-OSDCloudResults Mock Invoke-RestMethod {} } It 'creates a reduced fresh schema with nullable timing and no raw identifiers' { $id = $global:OSDCloudResults.DeploymentId $global:OSDCloudResults.SchemaVersion | Should -Be 1 $global:OSDCloudResults.Status | Should -Be 'NotStarted' $global:OSDCloudResults.IsComplete | Should -BeFalse $global:OSDCloudResults.StartTimeUtc | Should -BeNullOrEmpty $global:OSDCloudResults.totalDurationRAW | Should -BeNullOrEmpty $global:OSDCloudResults.ContentSource | Should -BeNullOrEmpty $global:OSDCloudResults.ContentInternet | Should -BeNullOrEmpty $global:OSDCloudResults.ContentLocal | Should -BeNullOrEmpty $propertyNames = @($global:OSDCloudResults.PSObject.Properties.Name) foreach ($name in 'sizeOS', 'sizeDriverPack', 'totalDurationRAW', 'totalDuration', 'timeDownload', 'timeCache', 'timeCopy', 'timeExpand', 'timeOther', 'timeCloud', 'totalSizeDownloadMB', 'totalSizeCopyMB', 'totalSizeMB', 'totalMBps', 'OSFileName', 'DriverPackFileName') { $propertyNames -ccontains $name | Should -BeTrue } @($propertyNames | Where-Object { $_ -cmatch '^step' }).Count | Should -Be 9 @($propertyNames | Where-Object { $_ -match '^Duration' }).Count | Should -Be 0 foreach ($name in 'stepOSCopy', 'stepOSDownload', 'stepOSExpand', 'stepDriverPackCopy', 'stepDriverPackDownload', 'stepDriverPackExpand', 'stepClearDisk', 'totalDurationRAW', 'totalDuration') { $propertyNames | Should -Contain $name } $global:OSDCloudResults.ProcessorArchitecture | Should -Be 'arm64' $global:OSDCloudResults.TotalPhysicalMemoryGB | Should -Be 16 $global:OSDCloudResults.OSBuildVersion | Should -Be '26300.9711' $global:OSDCloudResults.OSFileName | Should -BeExactly 'install.esd' $global:OSDCloudResults.DriverPackFileName | Should -BeExactly 'drivers.cab' $json = $global:OSDCloudResults | ConvertTo-Json $json | Should -Not -Match 'raw-private-uuid|private-serial|private-license|private.invalid' Initialize-OSDCloudResults -ModuleVersion '26.10.15.1' -WorkflowName default -LaunchMethod GUI $global:OSDCloudResults.DeploymentId | Should -Not -Be $id $global:OSDCloudResults.LaunchMethod | Should -Be 'GUI' } It 'hashes the UUID when needed and keeps the resolved identity for the run' { $global:OSDCoreDevice.EndpointSHA = $null $global:OSDCloudResults.DistinctId = $null Update-OSDCloudResults $sha256 = [System.Security.Cryptography.SHA256]::Create() try { $expected = [BitConverter]::ToString($sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes('raw-private-uuid'))).Replace('-', '') } finally { $sha256.Dispose() } $global:OSDCloudResults.DistinctId | Should -Be $expected $global:OSDCoreDevice.UUID = 'different' Update-OSDCloudResults $global:OSDCloudResults.DistinctId | Should -Be $expected } It 'calculates all rates from reported MB and seconds and clears unavailable rates' { foreach ($content in 'OS', 'DriverPack') { $global:OSDCloudResults."size${content}" = 5L foreach ($operation in 'Download', 'Cache', 'Copy', 'Expand') { $global:OSDCloudResults."step${content}${operation}" = 2L } } Update-OSDCloudResults foreach ($content in 'OS', 'DriverPack') { foreach ($operation in 'Download', 'Cache', 'Copy', 'Expand') { $global:OSDCloudResults."rate${content}${operation}" | Should -Be 3 $global:OSDCloudResults."rate${content}${operation}" | Should -BeOfType ([long]) $global:OSDCloudResults."step${content}${operation}" = 0L } } Update-OSDCloudResults foreach ($content in 'OS', 'DriverPack') { foreach ($operation in 'Download', 'Cache', 'Copy', 'Expand') { $global:OSDCloudResults."rate${content}${operation}" | Should -BeNullOrEmpty $global:OSDCloudResults."step${content}${operation}" = 2L } $global:OSDCloudResults."size${content}" = $null } Update-OSDCloudResults foreach ($content in 'OS', 'DriverPack') { foreach ($operation in 'Download', 'Cache', 'Copy', 'Expand') { $global:OSDCloudResults."rate${content}${operation}" | Should -BeNullOrEmpty } } } It 'calculates total size and throughput from reported content and deployment duration' { Set-OSDCloudAcquisitionResult -Content OS -Bytes 5MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 5MB $global:OSDCloudResults.totalDurationRAW = 2L Update-OSDCloudResults $global:OSDCloudResults.totalSizeMB | Should -Be 10 $global:OSDCloudResults.totalSizeMB | Should -BeOfType ([long]) $global:OSDCloudResults.totalMBps | Should -Be 5 $global:OSDCloudResults.totalMBps | Should -BeOfType ([long]) $global:OSDCloudResults.totalDurationRAW = 0L Update-OSDCloudResults $global:OSDCloudResults.totalMBps | Should -BeNullOrEmpty } It 'classifies gateway adapters as <Expected>' -TestCases @( @{ AdapterType = 0; AdapterName = 'Ethernet'; Expected = 'Wired' } @{ AdapterType = 9; AdapterName = 'Radio'; Expected = 'Wireless' } @{ AdapterType = 0; AdapterName = 'Wi-Fi'; Expected = 'Wireless' } ) { param ($AdapterType, $AdapterName, $Expected) Mock Get-CimInstance { if ($ClassName -eq 'Win32_NetworkAdapterConfiguration') { [pscustomobject]@{ Index = 4; DefaultIPGateway = @('192.0.2.1') } } else { [pscustomobject]@{ Index = 4; AdapterTypeID = $AdapterType; Name = $AdapterName; PhysicalAdapter = $true; NetConnectionStatus = 2 } } } Update-OSDCloudResults $global:OSDCloudResults.Network | Should -BeExactly $Expected } It 'keeps network null when wired and wireless gateways are both active' { Mock Get-CimInstance { if ($ClassName -eq 'Win32_NetworkAdapterConfiguration') { [pscustomobject]@{ Index = 4; DefaultIPGateway = @('192.0.2.1') } [pscustomobject]@{ Index = 5; DefaultIPGateway = @('192.0.2.2') } } else { [pscustomobject]@{ Index = 4; AdapterTypeID = 0; Name = 'Ethernet'; PhysicalAdapter = $true; NetConnectionStatus = 2 } [pscustomobject]@{ Index = 5; AdapterTypeID = 9; Name = 'Radio'; PhysicalAdapter = $true; NetConnectionStatus = 2 } } } Update-OSDCloudResults $global:OSDCloudResults.Network | Should -BeNullOrEmpty } It 'keeps network null when no gateway is available or discovery fails' { Update-OSDCloudResults $global:OSDCloudResults.Network | Should -BeNullOrEmpty Mock Get-CimInstance { throw 'Synthetic network discovery failure' } Update-OSDCloudResults -WarningVariable warnings $global:OSDCloudResults.Network | Should -BeNullOrEmpty "$warnings" | Should -Match 'Synthetic network discovery failure' $global:OSDCloudResults.DistinctId | Should -Not -BeNullOrEmpty } It 'separates USB-cache downloads from direct downloads and rounds cache bytes once' { $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSCache = $true; stepDriverPackCache = $true stepOSCopy = $true; stepDriverPackCopy = $true } $global:OSDCloudWorkflowInvoke.Durations = @{ stepOSCache = 2; stepDriverPackCache = 2 } Set-OSDCloudAcquisitionResult -Content OS -Bytes 1.49MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 1.49MB Update-OSDCloudResults $global:OSDCloudResults.totalSizeCacheMB | Should -Be 3 $global:OSDCloudResults.totalSizeDownloadMB | Should -Be 0 $global:OSDCloudResults.totalSizeCopyMB | Should -Be 3 $global:OSDCloudResults.rateOSCache | Should -Be 1 $global:OSDCloudResults.rateDriverPackCache | Should -Be 1 $global:OSDCloudResults.rateOSDownload | Should -BeNullOrEmpty $global:OSDCloudResults.rateDriverPackDownload | Should -BeNullOrEmpty Export-OSDCloudResults $loaded = Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml') $loaded.totalSizeCacheMB | Should -Be 3 $loaded.stepOSCache | Should -Be 2 $loaded.rateDriverPackCache | Should -Be 1 $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.Status = 'Completed' Mock Invoke-RestMethod { $script:capturedBody = $Body } Send-OSDCloudResults $payload = $script:capturedBody | ConvertFrom-Json $payload.properties.totalSizeCacheMB | Should -Be 3 $payload.properties.totalSizeDownloadMB | Should -Be 0 $payload.properties.stepOSCache | Should -Be 2 $payload.properties.rateDriverPackCache | Should -Be 1 } It 'classifies acquired content as <Expected> for OS=<OS> and DriverPack=<DriverPack>' -TestCases @( @{ OS = 'Download'; DriverPack = 'Download'; Expected = 'Cloud' } @{ OS = 'Cache'; DriverPack = 'Cache'; Expected = 'Cloud' } @{ OS = 'Cache'; DriverPack = 'Download'; Expected = 'Cloud' } @{ OS = 'Copy'; DriverPack = 'Copy'; Expected = 'Cache' } @{ OS = 'Copy'; DriverPack = 'Download'; Expected = 'Mixed' } @{ OS = 'Cache'; DriverPack = 'Copy'; Expected = 'Mixed' } @{ OS = 'Download'; DriverPack = ''; Expected = 'Cloud' } @{ OS = ''; DriverPack = 'Copy'; Expected = 'Cache' } @{ OS = ''; DriverPack = ''; Expected = $null } ) { param ($OS, $DriverPack, $Expected) $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{} foreach ($content in 'OS', 'DriverPack') { $operation = Get-Variable -Name $content -ValueOnly if ($operation) { $global:OSDCloudWorkflowInvoke.CompletedTransfers["step${content}${operation}"] = $true if ($operation -eq 'Cache') { $global:OSDCloudWorkflowInvoke.CompletedTransfers["step${content}Copy"] = $true } Set-OSDCloudAcquisitionResult -Content $content -Bytes 1 } } Update-OSDCloudResults $global:OSDCloudResults.ContentSource | Should -BeExactly $Expected } It 'reports copied ModelDrivers without the retained OEM archive: OS=<OS>' -TestCases @( @{ OS = 'Copy'; ExpectedSource = 'Cache' } @{ OS = 'Download'; ExpectedSource = 'Mixed' } ) { param ($OS, $ExpectedSource) $global:OSDCloudWorkflowInvoke.DriverPackName = 'ModelDrivers' $global:OSDCloudWorkflowInvoke.DriverPackCloudObject = [pscustomobject]@{ FileName = 'sp160195.exe' } $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepDriverPackCopy = $true } $global:OSDCloudWorkflowInvoke.CompletedTransfers["stepOS${OS}"] = $true Set-OSDCloudAcquisitionResult -Content OS -Bytes 5MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 3.75MB Update-OSDCloudResults $global:OSDCloudResults.DriverPackName | Should -BeExactly 'ModelDrivers' $global:OSDCloudResults.DriverPackFileName | Should -BeNullOrEmpty $global:OSDCloudResults.ContentSource | Should -BeExactly $ExpectedSource $global:OSDCloudResults.sizeDriverPack | Should -Be 4 Export-OSDCloudResults $loaded = Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml') $loaded.DriverPackFileName | Should -BeNullOrEmpty $loaded.ContentSource | Should -BeExactly $ExpectedSource $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.Status = 'Completed' Mock Invoke-RestMethod { $script:capturedBody = $Body } Send-OSDCloudResults $payload = $script:capturedBody | ConvertFrom-Json $payload.properties.DriverPackFileName | Should -BeNullOrEmpty $script:capturedBody | Should -Match '"DriverPackFileName":null' $payload.properties.ContentSource | Should -BeExactly $ExpectedSource } It 'clears the OEM archive filename when ModelDrivers is selected before workflow execution' { $global:OSDCloudDeploy.DriverPackName = 'ModelDrivers' Update-OSDCloudResults $global:OSDCloudResults.DriverPackFileName | Should -BeNullOrEmpty $global:OSDCloudWorkflowInvoke.DriverPackName = 'Resolved pack' Update-OSDCloudResults $global:OSDCloudResults.DriverPackFileName | Should -BeExactly 'drivers.cab' } It 'clears a previous classification when acquisition source is unknown' { $global:OSDCloudResults.ContentSource = 'Cloud' $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{} Set-OSDCloudAcquisitionResult -Content OS -Bytes 1MB Update-OSDCloudResults $global:OSDCloudResults.ContentSource | Should -BeNullOrEmpty } It 'generates one fallback GUID when device identity is unavailable' { $global:OSDCloudResults.DistinctId = $null $global:OSDCoreDevice.EndpointSHA = $null $global:OSDCoreDevice.UUID = $null Update-OSDCloudResults $id = $global:OSDCloudResults.DistinctId { [guid]::Parse($id) } | Should -Not -Throw Update-OSDCloudResults $global:OSDCloudResults.DistinctId | Should -Be $id } It 'refreshes metadata from the workflow execution snapshot when available' { $global:OSDCloudWorkflowInvoke.DriverPackName = 'Resolved pack' $global:OSDCloudWorkflowInvoke.DriverPackCloudObject = [pscustomobject]@{ FileName = 'resolved-drivers.cab' } $global:OSDCloudWorkflowInvoke.OSEditionId = 'Enterprise' $global:OSDCloudWorkflowInvoke.OperatingSystemCloudObject = [pscustomobject]@{ OSBuildVersion = '26300.9999' FileName = 'resolved-install.esd' } Update-OSDCloudResults $global:OSDCloudResults.DriverPackName | Should -Be 'Resolved pack' $global:OSDCloudResults.DriverPackFileName | Should -BeExactly 'resolved-drivers.cab' $global:OSDCloudResults.OSEditionId | Should -Be 'Enterprise' $global:OSDCloudResults.OSBuildVersion | Should -Be '26300.9999' $global:OSDCloudResults.OSFileName | Should -BeExactly 'resolved-install.esd' } It 'round trips typed results in current TEMP and the actual target Windows TEMP' { $null = New-Item -Path (Join-Path $targetRoot 'Windows') -ItemType Directory -Force $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.totalDurationRAW = 126L $global:OSDCloudWorkflowInvoke.CompletedTransfers.stepOSCopy = $true Set-OSDCloudAcquisitionResult -Content OS -Bytes 5.5MB $global:OSDCloudWorkflowInvoke.Durations = @{ stepOSCopy = 2.5; stepClearDisk = 10.5 } Update-OSDCloudResults $global:OSDCloudResults.IsComplete = $true $global:OSDCloudResults.Status = 'Completed' Export-OSDCloudResults -IncludeTarget foreach ($directory in @($env:TEMP, (Join-Path $targetRoot 'Windows\Temp'))) { $loaded = Import-Clixml -LiteralPath (Join-Path $directory 'OSDCloudResults.clixml') $loaded.DeploymentId | Should -Be $global:OSDCloudResults.DeploymentId $loaded.IsComplete | Should -BeOfType ([bool]) $loaded.StartTimeUtc | Should -BeOfType ([datetime]) $loaded.totalDurationRAW | Should -BeOfType ([long]) $loaded.totalDurationRAW | Should -Be 126 $loaded.sizeOS | Should -BeOfType ([long]) $loaded.sizeOS | Should -Be 6 $loaded.totalSizeCopyMB | Should -Be 6 $loaded.totalSizeDownloadMB | Should -Be 6 foreach ($name in 'sizeOS', 'sizeDriverPack', 'totalDurationRAW', 'totalDuration', 'totalSizeDownloadMB', 'totalSizeCopyMB', 'totalSizeMB', 'totalMBps') { $loaded.PSObject.Properties.Name -ccontains $name | Should -BeTrue } $loaded.stepOSCopy | Should -Be 3 $loaded.rateOSCopy | Should -Be 2 $loaded.ContentSource | Should -BeExactly 'Cloud' $loaded.Network | Should -BeNullOrEmpty $loaded.totalDuration | Should -Be 115 $loaded.stepClearDisk | Should -Be 11 $loaded.PSObject.Properties.Name | Should -Not -Contain 'DurationSeconds' $loaded.PSObject.Properties.Name | Should -Not -Contain 'OSDurationSeconds' } } It 'overwrites the running snapshot with terminal results' { $global:OSDCloudResults.Status = 'Running' Export-OSDCloudResults (Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml')).Status | Should -Be 'Running' $global:OSDCloudResults.Status = 'Failed' Export-OSDCloudResults (Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml')).Status | Should -Be 'Failed' } It 'warns about unavailable target context but still saves current TEMP: <Case>' -TestCases @( @{ Case = 'no context' } @{ Case = 'no Windows directory' } ) { param ($Case) if ($Case -eq 'no context') { $global:OSDCloudWorkflowInvoke = $null } Mock Test-Path { $false } Export-OSDCloudResults -IncludeTarget -WarningVariable warnings "$warnings" | Should -Match 'Could not resolve' Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml') | Should -Not -BeNullOrEmpty } It 'attempts target export independently when current TEMP cannot be written' { $null = New-Item -Path (Join-Path $targetRoot 'Windows') -ItemType Directory -Force Mock Export-Clixml { throw 'Synthetic write failure' } -ParameterFilter { $LiteralPath -like '*\current\*' } Export-OSDCloudResults -IncludeTarget -WarningVariable warnings "$warnings" | Should -Match 'Synthetic write failure' Import-Clixml -LiteralPath (Join-Path $targetRoot 'Windows\Temp\OSDCloudResults.clixml') | Should -Not -BeNullOrEmpty } It 'warns on directory creation failure without throwing' { Mock New-Item { throw 'Synthetic directory failure' } { Export-OSDCloudResults -WarningAction SilentlyContinue } | Should -Not -Throw Export-OSDCloudResults -WarningVariable warnings "$warnings" | Should -Match 'Synthetic directory failure' } It 'exports once when current TEMP is the target Windows TEMP' { $env:TEMP = Join-Path $targetRoot 'Windows\Temp' $null = New-Item -Path (Join-Path $targetRoot 'Windows') -ItemType Directory -Force Mock Export-Clixml {} Export-OSDCloudResults -IncludeTarget Should -Invoke Export-Clixml -Times 1 -Exactly } It 'uses a non-C image application root without touching the real volume' { $global:OSDCloudWorkflowInvoke.ParamsExpandWindowsImage.ApplyPath = 'D:\' Mock Test-Path { $true } Mock New-Item {} Mock Export-Clixml {} Mock Join-Path { "$Path\$ChildPath".Replace('\\', '\') } Export-OSDCloudResults -IncludeTarget Should -Invoke Test-Path -Times 1 -Exactly -ParameterFilter { $LiteralPath -eq 'D:\Windows' } Should -Invoke Export-Clixml -Times 1 -Exactly -ParameterFilter { $LiteralPath -eq 'D:\Windows\Temp\OSDCloudResults.clixml' } Should -Invoke Export-Clixml -Times 0 -Exactly -ParameterFilter { $LiteralPath -like 'C:\Windows\Temp\*' } } It 'sends exactly the allowlisted terminal properties as typed JSON with a bounded timeout' { $global:OSDCloudResults.Status = 'Completed' $global:OSDCloudResults.IsComplete = $true $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow.AddSeconds(-125) $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.totalDurationRAW = 126L $global:OSDCloudWorkflowInvoke.CompletedTransfers.stepOSCopy = $true Set-OSDCloudAcquisitionResult -Content OS -Bytes 2.5MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 1.25MB $global:OSDCloudWorkflowInvoke.Durations = @{ stepOSCopy = 4.5; stepOSDownload = 10.4; stepOSExpand = 11.5 stepDriverPackCopy = 0.4; stepDriverPackDownload = 12.5 stepDriverPackExpand = 13.4; stepClearDisk = 14.5 } Update-OSDCloudResults $global:OSDCloudResults | Add-Member -NotePropertyName Secret -NotePropertyValue 'private-secret' Mock Invoke-RestMethod { $script:capturedBody = $Body } Send-OSDCloudResults $payload = $script:capturedBody | ConvertFrom-Json $payload.event | Should -Be 'OSDCloudResults' $payload.properties.IsComplete | Should -BeOfType ([bool]) $payload.properties.totalDurationRAW | Should -Be 126 $payload.properties.sizeOS | Should -Be 3 $payload.properties.stepOSCopy | Should -Be 5 $payload.properties.sizeDriverPack | Should -Be 1 $payload.properties.stepDriverPackCopy | Should -Be 0 $payload.properties.stepOSDownload | Should -Be 10 $payload.properties.stepOSExpand | Should -Be 12 $payload.properties.stepDriverPackDownload | Should -Be 13 $payload.properties.stepDriverPackExpand | Should -Be 13 $payload.properties.stepClearDisk | Should -Be 15 $payload.properties.totalDuration | Should -Be 111 $payload.properties.timeDownload | Should -Be 23 $payload.properties.timeCache | Should -Be 0 $payload.properties.timeCopy | Should -Be 5 $payload.properties.timeExpand | Should -Be 25 $payload.properties.timeOther | Should -Be 58 $payload.properties.timeCloud | Should -Be 23 $payload.properties.OSFileName | Should -BeExactly 'install.esd' $payload.properties.DriverPackFileName | Should -BeExactly 'drivers.cab' $payload.properties.totalSizeDownloadMB | Should -Be 4 $payload.properties.totalSizeCopyMB | Should -Be 3 $payload.properties.totalSizeMB | Should -Be 4 $payload.properties.totalMBps | Should -Be 0 $payload.properties.ContentSource | Should -BeExactly 'Cloud' $payload.properties.rateOSCopy | Should -Be 1 $payload.properties.rateDriverPackCopy | Should -BeNullOrEmpty $payload.properties.Network | Should -BeNullOrEmpty $payload.properties.distinct_id | Should -Be 'hashed-device' $payload.properties.DeploymentId | Should -Be $global:OSDCloudResults.DeploymentId $script:capturedBody | Should -Match '"StartTimeUtc"\s*:\s*"[^"]+Z"' $script:capturedBody | Should -Match '"EndTimeUtc"\s*:\s*"[^"]+Z"' $expectedNames = @( 'SchemaVersion', 'DeploymentId', 'IsComplete', 'Status', 'StartTimeUtc', 'EndTimeUtc', 'totalDurationRAW', 'ModuleVersion', 'WorkflowName', 'WorkflowTaskName', 'LaunchMethod', 'timeDownload', 'timeCache', 'timeCopy', 'timeExpand', 'timeOther', 'timeCloud', 'totalDuration', 'stepOSDownload', 'stepOSExpand', 'stepDriverPackDownload', 'stepOSCache', 'stepDriverPackCache', 'rateOSCache', 'rateDriverPackCache', 'totalSizeCacheMB', 'totalSizeMB', 'totalMBps', 'ContentSource', 'ContentInternet', 'ContentLocal', 'rateOSDownload', 'rateOSCopy', 'rateOSExpand', 'rateDriverPackDownload', 'rateDriverPackCopy', 'rateDriverPackExpand', 'Network', 'stepDriverPackExpand', 'stepClearDisk', 'totalSizeCopyMB', 'sizeOS', 'stepOSCopy', 'sizeDriverPack', 'stepDriverPackCopy', 'totalSizeDownloadMB', 'DeploymentPhase', 'OSDManufacturer', 'OSDModel', 'OSDProduct', 'ProcessorArchitecture', 'TotalPhysicalMemoryGB', 'OSName', 'OSVersion', 'OSBuild', 'OSBuildVersion', 'OSEditionId', 'OSActivation', 'OSLanguageCode', 'OSFileName', 'DriverPackName', 'DriverPackFileName', 'distinct_id' ) @($payload.properties.PSObject.Properties.Name).Count | Should -Be $expectedNames.Count foreach ($name in $expectedNames) { $payload.properties.PSObject.Properties.Name -ccontains $name | Should -BeTrue } $script:capturedBody | Should -Not -Match 'private-secret|raw-private-uuid|private-serial|private-license|private.invalid' Should -Invoke Invoke-RestMethod -Times 1 -Exactly -ParameterFilter { $Method -eq 'Post' -and $Uri -eq 'https://us.i.posthog.com/capture/' -and $TimeoutSec -eq 2 -and $ContentType -eq 'application/json' -and $ErrorAction -eq 'Stop' } } It 'warns on telemetry failure without throwing' { $global:OSDCloudResults.Status = 'Failed' $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow Mock Invoke-RestMethod { throw 'Synthetic network failure' } Send-OSDCloudResults -WarningVariable warnings "$warnings" | Should -Match 'Synthetic network failure' } It 'does not send unstarted or unfinalized results' { Send-OSDCloudResults -WarningVariable warnings "$warnings" | Should -Match 'Only finalized' Should -Invoke Invoke-RestMethod -Times 0 -Exactly } It 'reports internet and local content in MB: <Mode>' -TestCases @( @{ Mode = 'Direct'; OSOperation = 'Download'; DriverOperation = 'Download'; Internet = 3L; Local = 0L; Total = 2L } @{ Mode = 'USB download and copy'; OSOperation = 'Cache'; DriverOperation = 'Copy'; Internet = 1L; Local = 1L; Total = 2L } @{ Mode = 'Cache only'; OSOperation = 'Copy'; DriverOperation = 'Copy'; Internet = 0L; Local = 3L; Total = 2L } @{ Mode = 'Both download destinations'; OSOperation = 'Cache'; DriverOperation = 'Download'; Internet = 2L; Local = 0L; Total = 2L } @{ Mode = 'Empty content'; OSOperation = 'Copy'; DriverOperation = 'Copy'; Internet = 0L; Local = 0L; Total = 0L; Bytes = 0L } ) { param ($Mode, $OSOperation, $DriverOperation, $Internet, $Local, $Total, $Bytes = 1.49MB) $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{} $global:OSDCloudWorkflowInvoke.CompletedTransfers["stepOS${OSOperation}"] = $true if ($OSOperation -eq 'Cache') { $global:OSDCloudWorkflowInvoke.CompletedTransfers.stepOSCopy = $true } $global:OSDCloudWorkflowInvoke.CompletedTransfers["stepDriverPack${DriverOperation}"] = $true Set-OSDCloudAcquisitionResult -Content OS -Bytes $Bytes Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes $Bytes Update-OSDCloudResults Update-OSDCloudResults $global:OSDCloudResults.ContentInternet | Should -Be $Internet $global:OSDCloudResults.ContentInternet | Should -BeOfType ([long]) $global:OSDCloudResults.ContentLocal | Should -Be $Local $global:OSDCloudResults.ContentLocal | Should -BeOfType ([long]) $global:OSDCloudResults.totalSizeMB | Should -Be $Total Export-OSDCloudResults $loaded = Import-Clixml -LiteralPath (Join-Path $env:TEMP 'OSDCloudResults.clixml') $loaded.ContentInternet | Should -Be $Internet $loaded.ContentLocal | Should -Be $Local $global:OSDCloudResults.StartTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.EndTimeUtc = [datetime]::UtcNow $global:OSDCloudResults.Status = 'Completed' Mock Invoke-RestMethod { $script:capturedBody = $Body } Send-OSDCloudResults $payload = $script:capturedBody | ConvertFrom-Json $payload.properties.ContentInternet | Should -Be $Internet $payload.properties.ContentLocal | Should -Be $Local } It 'excludes internet content from local content regardless of copy completion: Copy=<Copy>' -TestCases @( @{ Copy = $true } @{ Copy = $false } ) { param ($Copy) $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSCache = $true; stepOSCopy = $Copy stepDriverPackDownload = $true; stepDriverPackCopy = $Copy } Set-OSDCloudAcquisitionResult -Content OS -Bytes 6MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 3MB Update-OSDCloudResults $global:OSDCloudResults.ContentInternet | Should -Be 9 $global:OSDCloudResults.ContentLocal | Should -Be 0 $global:OSDCloudResults.totalSizeCopyMB | Should -Be (9 * [int]$Copy) $global:OSDCloudResults.totalSizeMB | Should -Be 9 } It 'reports only pre-existing USB content for the sample deployment sizes' { $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSCache = $true; stepOSCopy = $true; stepDriverPackCopy = $true } Set-OSDCloudAcquisitionResult -Content OS -Bytes 6045MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 2950MB Update-OSDCloudResults $global:OSDCloudResults.ContentInternet | Should -Be 6045 $global:OSDCloudResults.ContentLocal | Should -Be 2950 $global:OSDCloudResults.totalSizeCopyMB | Should -Be 8995 $global:OSDCloudResults.totalSizeMB | Should -Be 8995 } It 'rounds combined original bytes, not the sum of rounded MB sizes' { Set-OSDCloudAcquisitionResult -Content OS -Bytes 1.49MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 1.49MB $global:OSDCloudWorkflowInvoke.Durations = @{ stepOSCopy = 4.49; stepDriverPackCopy = 4.5 } Update-OSDCloudResults $global:OSDCloudResults.sizeOS | Should -Be 1 $global:OSDCloudResults.sizeDriverPack | Should -Be 1 $global:OSDCloudResults.totalSizeDownloadMB | Should -Be 3 $global:OSDCloudResults.totalSizeMB | Should -Be 2 $global:OSDCloudResults.stepOSCopy | Should -Be 4 $global:OSDCloudResults.stepDriverPackCopy | Should -Be 5 } It 'leaves missing content unknown and totals only acquired bytes without double counting' { $global:OSDCloudResults.totalSizeDownloadMB | Should -BeNullOrEmpty Set-OSDCloudAcquisitionResult -Content OS -Bytes 2.5MB Update-OSDCloudResults Update-OSDCloudResults $global:OSDCloudResults.sizeDriverPack | Should -BeNullOrEmpty $global:OSDCloudResults.stepDriverPackCopy | Should -BeNullOrEmpty $global:OSDCloudResults.totalSizeDownloadMB | Should -Be 3 $global:OSDCloudResults.totalSizeMB | Should -Be 3 } It 'uses full total duration for ZTI when no confirmation wait occurred' { $global:OSDCloudResults.totalDurationRAW = 126L Update-OSDCloudResults $global:OSDCloudResults.totalDuration | Should -Be 126 $global:OSDCloudResults.timeDownload | Should -Be 0 $global:OSDCloudResults.timeCache | Should -Be 0 $global:OSDCloudResults.timeCopy | Should -Be 0 $global:OSDCloudResults.timeExpand | Should -Be 0 $global:OSDCloudResults.timeOther | Should -Be 126 $global:OSDCloudResults.timeCloud | Should -Be 0 $global:OSDCloudResults.stepClearDisk | Should -BeNullOrEmpty } It 'does not produce a negative ZTI duration' { $global:OSDCloudResults.totalDurationRAW = 1L $global:OSDCloudWorkflowInvoke.Durations = @{ stepClearDisk = 1.5 } Update-OSDCloudResults $global:OSDCloudResults.totalDuration | Should -Be 0 } It 'separates downloaded and copied content and rounds original bytes once' { $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSDownload = $true; stepOSCopy = $true; stepDriverPackCopy = $true } Set-OSDCloudAcquisitionResult -Content OS -Bytes 1.49MB Set-OSDCloudAcquisitionResult -Content DriverPack -Bytes 1.49MB Update-OSDCloudResults $global:OSDCloudResults.sizeOS | Should -Be 1 $global:OSDCloudResults.sizeDriverPack | Should -Be 1 $global:OSDCloudResults.totalSizeDownloadMB | Should -Be 1 $global:OSDCloudResults.totalSizeCopyMB | Should -Be 3 Update-OSDCloudResults $global:OSDCloudResults.totalSizeCopyMB | Should -Be 3 } It 'reports zero download size for cache-only content' { $global:OSDCloudWorkflowInvoke.CompletedTransfers = @{ stepOSCopy = $true } Set-OSDCloudAcquisitionResult -Content OS -Bytes 2.5MB Update-OSDCloudResults $global:OSDCloudResults.totalSizeDownloadMB | Should -Be 0 $global:OSDCloudResults.totalSizeCopyMB | Should -Be 3 } } |