private/core-device/Initialize-OSDCoreDevice.ps1

function Initialize-OSDCoreDevice {
    <#
    .SYNOPSIS
        Initializes the OSDCore device inventory and diagnostics context.
 
    .DESCRIPTION
        Initialize-OSDCoreDevice collects local hardware, firmware, TPM, keyboard,
        disk, USB cache, and network information from CIM, environment variables,
        and UEFI checks. It creates diagnostic artifacts in
        $env:TEMP\osdcloud-logs, clears stale OSDCoreDevice snapshot files before
        collection, normalizes key identity values such as
        manufacturer/model/product, and builds $global:OSDCoreDevice as an
        ordered property bag used by deployment and workflow orchestration
        functions.
 
        OSDCloud environment values can override the reported OSD manufacturer,
        model, product, OS architecture, and processor architecture. The device
        snapshot includes the raw SMBIOS UUID and EndpointSHA, a SHA256 hash used by
        downstream telemetry callers when a stable privacy-preserving identifier
        is needed.
 
        The function is intended for internal module initialization and is called
        before workflow execution so downstream steps can rely on a consistent
        device state snapshot.
 
    .EXAMPLE
    Initialize-OSDCoreDevice
 
        Collects current device metadata, creates or updates
        $global:OSDCoreDevice, removes stale device snapshot files, and writes
        current log artifacts for troubleshooting.
 
 
    .EXAMPLE
    Initialize-OSDCoreDevice -Verbose
 
        Runs initialization and emits additional details about discovered disks,
        network adapters, keyboard selection, and support checks.
 
    .INPUTS
    None. You cannot pipe input to this function.
 
    .OUTPUTS
    None. This function does not emit pipeline output.
 
    .NOTES
        Side effects:
        - Clears the current PowerShell error collection.
        - Attempts to sync date/time (best effort) through Sync-OSDCoreDateTime.
                - Removes stale OSDCoreDevice.xml and OSDCoreDevice.json files from
                    $env:TEMP\osdcloud-logs before collecting a new snapshot.
                - Writes diagnostic logs and current device snapshots to
                    $env:TEMP\osdcloud-logs.
        - Attempts to stage logs in an OSDCloudLogs destination when available.
        - Updates global state in $global:OSDCoreDevice.
 
        Changelog:
        - 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.
        - 2026-08-13 | pending | Add EndpointSHA and clear stale device snapshot files.
            Removed existing OSDCoreDevice output files before collecting device state
            and added EndpointSHA as a SHA256 hash of the device UUID.
        - 2026-08-12 | pending | Infer AutoOSLanguageCode from keyboard layout.
            Used Convert-KeyboardLayoutToLanguageCode with the detected KeyboardLayout
            to populate AutoOSLanguageCode in the OSDCoreDevice snapshot.
        - 2026-08-12 | pending | Move DeploymentDisk selection to deployment initialization.
            Removed deployment disk target selection from device inventory so
            Initialize-DeployOSDCloud selects the deployment disk from LocalDisk.
        - 2026-08-11 | pending | Populate local disk inventory in OSDCoreDevice.
            Assigned filtered online local disk, partition, and volume objects before
            exporting the device snapshot.
        - 2026-08-11 | pending | Populate USB disk inventory in OSDCoreDevice.
            Assigned filtered online USB disk objects to USBDisk before exporting
            the device snapshot.
        - 2026-08-06 | pending | Export OSDCoreDevice CLIXML to TEMP.
            Added Export-Clixml output to $env:TEMP\OSDCoreDevice.xml so
            callers and support workflows can consume a typed device snapshot.
        - 2026-08-05 | pending | Report unknown values for empty identity fields.
            Normalized OSDManufacturer, OSDModel, and OSDProduct values written
            to OSDCoreDevice so null/whitespace values are emitted as Unknown.
        - 2026-08-05 | pending | Resolve OS catalog provider selection overwrite.
            Replaced unconditional dual assignment of OSDCoreOperatingSystems with
            command-aware selection logic that uses the available provider function
            and throws a descriptive error when neither provider is present.
        - 2026-08-05 | ef3e4fb | Core catalog initialization wiring update.
            Integrated initializer into the broader core startup path and aligned
            device state dependencies for downstream catalog and workflow logic.
        - 2026-08-04 | 538d747 | Initial OSDCoreDevice initializer introduced.
            Added core CIM collection, log export pipeline, normalization helpers,
            and baseline global property map for workflow consumers.
 
        Maintainer guidance:
        - Add a new changelog entry in this NOTES section for every functional
            behavior change in this function.
        - Keep entries in reverse chronological order using this format:
            YYYY-MM-DD | <short-hash> | <summary>
    #>

    [CmdletBinding()]
    param ()
    #=================================================
    $Error.Clear()
    # Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] [$($MyInvocation.MyCommand.Name)]"
    #=================================================
    try {
        Sync-OSDCoreDateTime -ThresholdMinutes 5 -Force -ErrorAction Stop
    }
    catch {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to sync date/time: $($_.Exception.Message)"
    }
    #=================================================
    # Set the osdcloud-logs Path
    $LogsPath = "$env:TEMP\osdcloud-logs"
    if (-not (Test-Path -Path $LogsPath)) {
        New-Item -Path $LogsPath -ItemType Directory -Force | Out-Null
    }
    #=================================================
    # Real Architecture
    $ProcessorArchitecture = $env:PROCESSOR_ARCHITECTURE
    #=================================================
    # ipconfig
    ipconfig | Out-File (Join-Path -Path $LogsPath -ChildPath 'Network_IPConfig.txt') -Width 4096 -Force
    #=================================================
    # Win32_BaseBoard
    $classWin32BaseBoard = Get-CimInstance -ClassName Win32_BaseBoard | Select-Object -Property *
    $classWin32BaseBoard | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_BaseBoard.txt') -Width 4096 -Force
    $BaseBoardProduct = $classWin32BaseBoard.Product | ConvertTo-TrimmedString
    #=================================================
    # Win32_Battery
    try {
        $classWin32Battery = Get-CimInstance -ClassName Win32_Battery | Select-Object -Property *
        $classWin32Battery | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_Battery.txt') -Width 4096 -Force
    }
    catch {
        $classWin32Battery = $null
    }

    [System.Boolean]$IsOnBattery = $false
    if ($classWin32Battery) {
        $IsOnBattery = ($classWin32Battery.BatteryStatus -contains 1)
    }
    #=================================================
    # Win32_BIOS
    $classWin32BIOS = Get-CimInstance -ClassName Win32_BIOS | Select-Object -Property *
    $classWin32BIOS | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_BIOS.txt') -Width 4096 -Force
    #=================================================
    # Win32_ComputerSystem
    $classWin32ComputerSystem = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property *
    $classWin32ComputerSystem | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_ComputerSystem.txt') -Width 4096 -Force
    $ComputerManufacturer = $classWin32ComputerSystem.Manufacturer | ConvertTo-TrimmedString
    $ComputerModel = $classWin32ComputerSystem.Model | ConvertTo-TrimmedString
    $ComputerSystemFamily = $classWin32ComputerSystem.SystemFamily | ConvertTo-TrimmedString
    $ComputerSystemSKU = $classWin32ComputerSystem.SystemSKUNumber | ConvertTo-TrimmedString
    #=================================================
    # Win32_ComputerSystemProduct
    $classWin32ComputerSystemProduct = Get-CimInstance -ClassName Win32_ComputerSystemProduct | Select-Object -Property *
    $classWin32ComputerSystemProduct | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_ComputerSystemProduct.txt') -Width 4096 -Force
    $ComputerSystemProduct = $classWin32ComputerSystemProduct.Version | ConvertTo-TrimmedString
    #=================================================
    # Win32_DiskDrive
    $classWin32DiskDrive = Get-CimInstance -ClassName Win32_DiskDrive | Select-Object -Property *
    $classWin32DiskDrive | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_DiskDrive.txt') -Width 4096 -Force
    foreach ($Item in $classWin32DiskDrive) {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Disk: $($Item.Model) [$($Item.DeviceID)]"
    }
    #=================================================
    # Win32_Environment
    $classWin32Environment = Get-CimInstance -ClassName Win32_Environment | Select-Object -Property * | Sort-Object Name
    $classWin32Environment | Where-Object { $_.SystemVariable -eq $true } | Select-Object -Property Name, VariableValue | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_Environment-System.txt') -Width 4096 -Force
    #=================================================
    # Win32_Keyboard
    try {
        $classWin32Keyboard = Get-CimInstance -ClassName Win32_Keyboard -ErrorAction Stop | Select-Object -Property *
        $classWin32Keyboard | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_Keyboard.txt') -Width 4096 -Force

        # A device can expose multiple keyboard endpoints. Prefer entries with
        # a populated layout and stable ordering, then use the first candidate.
        $keyboardCandidates = @($classWin32Keyboard) | Where-Object {
            -not [string]::IsNullOrWhiteSpace($_.Layout) -or -not [string]::IsNullOrWhiteSpace($_.Name)
        } | Sort-Object -Property @(
            @{ Expression = { [string]::IsNullOrWhiteSpace($_.Layout) } ; Descending = $false },
            @{ Expression = { [string]$_.Name } ; Descending = $false },
            @{ Expression = { [string]$_.DeviceID } ; Descending = $false }
        )

        $primaryKeyboard = $keyboardCandidates | Select-Object -First 1
        if ($primaryKeyboard) {
            $KeyboardLayout = [System.String]$primaryKeyboard.Layout
            $KeyboardName = [System.String]$primaryKeyboard.Name

            if ($keyboardCandidates.Count -gt 1) {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Multiple keyboards detected ($($keyboardCandidates.Count)). Using '$KeyboardName' with layout '$KeyboardLayout'."
            }
        }
        else {
            $KeyboardLayout = $null
            $KeyboardName = $null
        }
    }
    catch {
        $classWin32Keyboard = $null
        $KeyboardLayout = $null
        $KeyboardName = $null
    }

    $AutoOSLanguageCode = 'en-us'
    #=================================================
    # Win32_NetworkAdapter
    $classWin32NetworkAdapter = Get-CimInstance -ClassName Win32_NetworkAdapter | Select-Object -Property *
    $classWin32NetworkAdapter | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_NetworkAdapter.txt') -Width 4096 -Force

    $NetworkAdapterGuid = $classWin32NetworkAdapter | Where-Object { $null -ne $_.GUID }
    if ($NetworkAdapterGuid) {
        foreach ($Item in $NetworkAdapterGuid) {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] NetAdapter: $($Item.Name) [$($Item.MACAddress)]"
        }
    }
    #=================================================
    # Win32_NetworkAdapterConfiguration
    $classWin32NetworkAdapterConfiguration = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true } | Select-Object -Property *
    $classWin32NetworkAdapterConfiguration | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_NetworkAdapterConfiguration.txt') -Width 4096 -Force

    foreach ($Item in $classWin32NetworkAdapterConfiguration) {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] NetAdapterConfig: $($Item.IPAddress) [$($Item.Description)]"
    }
    $NetIPAddress = @()
    $NetMacAddress = @()
    $NetGateways = @()
    $classWin32NetworkAdapterConfiguration | ForEach-Object {
        $_.IPAddress | ForEach-Object { $NetIPAddress += $_ }
        $_.MacAddress | ForEach-Object { $NetMacAddress += $_ }
        $_.DefaultIPGateway | ForEach-Object { $NetGateways += $_ }
    }
    #=================================================
    # Win32_OperatingSystem
    $classWin32OperatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property *
    $classWin32OperatingSystem | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_OperatingSystem.txt') -Width 4096 -Force
    $OSArchitecture = $classWin32OperatingSystem.OSArchitecture | ConvertTo-TrimmedString
    #=================================================
    # 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

    if ($classWin32PnPEntityError) {
        $classWin32PnPEntityError | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_PnPEntityError.txt') -Width 4096 -Force
    }

    $SystemFirmwareDevice = $classWin32PnPEntity | Where-Object ClassGuid -eq '{f2e7dd72-6468-4e36-b6f1-6488f42c1b52}' | Where-Object Caption -match 'System'
    if ($SystemFirmwareDevice) {
        $GuidPattern = '\{?(([0-9a-f]){8}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){4}-([0-9a-f]){12})\}?'
        $SystemFirmwareResource = ($SystemFirmwareDevice.PNPDeviceID | Select-String -Pattern $GuidPattern -AllMatches | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value)
        $SystemFirmwareHardwareId = $SystemFirmwareResource -replace '[{}]', ''
    }
    else {
        $SystemFirmwareDevice = $null
        $SystemFirmwareResource = $null
        $SystemFirmwareHardwareId = $null
    }
    #=================================================
    # Win32_Processor
    $classWin32Processor = Get-CimInstance -ClassName Win32_Processor | Select-Object -Property *
    $classWin32Processor | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_Processor.txt') -Width 4096 -Force

    foreach ($Item in $classWin32Processor) {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Processor: $($Item.Name) [$($Item.NumberOfLogicalProcessors) Logical]"
    }
    #=================================================
    # Win32_SystemEnclosure
    $classWin32SystemEnclosure = Get-CimInstance -ClassName Win32_SystemEnclosure | Select-Object -Property *
    $classWin32SystemEnclosure | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_SystemEnclosure.txt') -Width 4096 -Force
    #=================================================
    # Win32_SystemTimeZone
    $classWin32SystemTimeZone = Get-CimInstance -ClassName Win32_SystemTimeZone | Select-Object -Property *
    $classWin32SystemTimeZone | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_SystemTimeZone.txt') -Width 4096 -Force
    #=================================================
    # Win32_TimeZone
    $classWin32TimeZone = Get-CimInstance -ClassName Win32_TimeZone | Select-Object -Property *
    $classWin32TimeZone | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_TimeZone.txt') -Width 4096 -Force
    #=================================================
    # TPM
    $IsAutopilotSpec = $false
    $IsTpmSpec = $false
    $classWin32Tpm = @{}
    try {
        $classWin32Tpm = Get-CimInstance -Namespace 'ROOT\cimv2\Security\MicrosoftTpm' -ClassName Win32_Tpm -ErrorAction Stop
        $classWin32Tpm | Out-File (Join-Path -Path $LogsPath -ChildPath 'Win32_Tpm.txt') -Width 4096 -Force
        $DeviceTpmIsActivated = $($classWin32Tpm.IsActivated_InitialValue)
        $DeviceTpmIsEnabled = $($classWin32Tpm.IsEnabled_InitialValue)
        $DeviceTpmIsOwned = $($classWin32Tpm.IsOwned_InitialValue)
        $DeviceTpmManufacturerIdTxt = $($classWin32Tpm.ManufacturerIdTxt)
        $DeviceTpmManufacturerVersion = $($classWin32Tpm.ManufacturerVersion)
        $DeviceTpmSpecVersion = $($classWin32Tpm.SpecVersion)
    }
    catch {
        $classWin32Tpm = $null
        $DeviceTpmIsActivated = $false
        $DeviceTpmIsEnabled = $false
        $DeviceTpmIsOwned = $false
        $DeviceTpmManufacturerIdTxt = $null
        $DeviceTpmManufacturerVersion = $null
        $DeviceTpmSpecVersion = $null

        Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [NOT SUPPORTED] TPM is not supported on this device."
        Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [NOT SUPPORTED] Intune Autopilot is not supported on this device."
    }

    if ($DeviceTpmSpecVersion) {
        $majorVersion = $DeviceTpmSpecVersion.Split(',')[0] -as [int]
        if ($majorVersion -lt 2) {
            Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [NOT SUPPORTED] TPM version is lower than 2.0 on this device."
            Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [NOT SUPPORTED] Intune Autopilot is not supported on this device."
        }
        else {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] TPM 2.0 is supported on this device."
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Intune Autopilot is supported on this device."
            $IsAutopilotSpec = $true
            $IsTpmSpec = $true
        }
    }
    #=================================================
    # Secure Boot Information
    # https://github.com/richardhicks/uefi/blob/main/Get-UEFICertificate.ps1
    try {
        $SecureBootStatus = Confirm-SecureBootUEFI
    }
    catch {
        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Unable to access UEFI Secure Boot information."
        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] This system may not support UEFI or Secure Boot."
    }
    if ($SecureBootStatus -eq $true) {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Secure Boot is enabled on this device."

        if (Get-Command -Name Get-SecureBootUEFI -ErrorAction Ignore) {
            try {
                $dbVariable = Get-SecureBootUEFI -Name DB -ErrorAction Stop
                $kekVariable = Get-SecureBootUEFI -Name KEK -ErrorAction Stop

                $dbBytes = $dbVariable.Bytes
                $kekBytes = $kekVariable.Bytes

                if (-not $dbBytes -or -not $kekBytes) {
                    Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Secure Boot certificates could not be read (missing DB or KEK bytes). Skipping certificate presence checks."
                }
                else {
                    $utf8Encoding = [System.Text.Encoding]::GetEncoding(
                        'utf-8',
                        [System.Text.EncoderFallback]::ReplacementFallback,
                        [System.Text.DecoderFallback]::ReplacementFallback
                    )

                    $dbText = $utf8Encoding.GetString($dbBytes)
                    if ([string]::IsNullOrWhiteSpace($dbText)) {
                        $dbText = [System.Text.Encoding]::ASCII.GetString($dbBytes)
                    }
                    $WinUEFIca2023 = $dbText -match 'Windows UEFI CA 2023'
                    if ($WinUEFIca2023) {
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Windows UEFI CA 2023 is present."
                    }
                    else {
                        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Windows UEFI CA 2023 is not present."
                    }
                    $MsUEFIca2023 = $dbText -match 'Microsoft UEFI CA 2023'
                    if ($MsUEFIca2023) {
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Microsoft UEFI CA 2023 is present."
                    }
                    else {
                        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Microsoft UEFI CA 2023 is not present."
                    }

                    $kekText = $utf8Encoding.GetString($kekBytes)
                    if ([string]::IsNullOrWhiteSpace($kekText)) {
                        $kekText = [System.Text.Encoding]::ASCII.GetString($kekBytes)
                    }
                    $MsKEKca2023 = $kekText -match 'Microsoft Corporation KEK 2K CA 2023'
                    if ($MsKEKca2023) {
                        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Microsoft Corporation KEK 2K CA 2023 is present."
                    }
                    else {
                        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Microsoft Corporation KEK 2K CA 2023 is not present."
                    }
                }
            }
            catch {
                Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Unable to query Secure Boot certificate variables (DB/KEK): $($_.Exception.Message). Skipping certificate presence checks."
            }
        }
    }
    elseif ($SecureBootStatus -eq $false) {
        Write-Host -ForegroundColor DarkYellow "[$(Get-Date -format s)] [WARN] Secure Boot is not enabled."
    }
    #=================================================
    # Identify Serial Number with multiple fallback methods due to variability in how different manufacturers populate WMI classes
    $serialNumberCandidates = @(
        $classWin32BIOS.SerialNumber,
        $classWin32SystemEnclosure.SerialNumber,
        $classWin32ComputerSystemProduct.IdentifyingNumber,
        $classWin32BaseBoard.SerialNumber
    )
    $SerialNumber = $null
    foreach ($candidate in $serialNumberCandidates) {
        $SerialNumber = $candidate | ConvertTo-TrimmedString
        if (-not [string]::IsNullOrWhiteSpace($SerialNumber)) {
            break
        }
    }
    #=================================================
    # IsUEFI
    [System.Boolean]$IsUEFI = $false
    if ($env:firmware_type -eq 'UEFI') {
        $IsUEFI = $true
    }
    elseif ($env:firmware_type -eq 'Legacy') {
        $IsUEFI = $false
    }
    elseif ($env:SystemDrive -eq 'X:') {
        Start-Process -WindowStyle Hidden -FilePath wpeutil.exe -ArgumentList ('updatebootinfo') -Wait
        $IsUEFI = (Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control).PEFirmwareType -eq 2
    }
    else {
        if ($null -eq (Get-ItemProperty HKLM:\System\CurrentControlSet\Control\SecureBoot\State -ErrorAction SilentlyContinue)) {
            $IsUEFI = $false
        }
        else {
            $IsUEFI = $true
        }
    }
    #=================================================
    # IsVM
    [System.Boolean]$IsVM = $false
    $vmDetectionSources = @(
        $classWin32ComputerSystem.Model,
        $classWin32ComputerSystem.Manufacturer,
        $classWin32ComputerSystem.SystemFamily,
        $classWin32ComputerSystemProduct.Name,
        $classWin32ComputerSystemProduct.Vendor,
        $classWin32ComputerSystemProduct.Version
    ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }

    $vmPattern = '(?i)(virtual machine|vmware|hyper-v|hyperv|kvm|qemu|xen|virtualbox|bhyve|parallels|gce|google compute engine|amazon ec2|azure|bochs|openstack|ovirt|rhev|kubevirt|ahv|nutanix)'
    [System.Boolean]$IsVM = ($vmDetectionSources -join ' ') -match $vmPattern
    #=================================================
    # IsWinPE
    [System.Boolean]$IsWinPE = $false
    if ($env:SystemDrive -eq 'X:') {
        $IsWinPE = $true
    }
    #=================================================
    # ChassisType
    $ComputerSystemType = $classWin32SystemEnclosure | ForEach-Object {
        if ($_.ChassisTypes[0] -in "8", "9", "10", "11", "12", "14", "18", "21") { "Laptop" }
        if ($_.ChassisTypes[0] -in "3", "4", "5", "6", "7", "15", "16") { "Desktop" }
        if ($_.ChassisTypes[0] -in "23") { "Server" }
        if ($_.ChassisTypes[0] -in "34", "35", "36") { "Small Form Factor" }
        if ($_.ChassisTypes[0] -in "13", "31", "32", "30") { "Tablet" }
    }
    [System.Boolean]$IsDesktop = $ComputerSystemType -contains 'Desktop'
    [System.Boolean]$IsLaptop = $ComputerSystemType -contains 'Laptop'
    [System.Boolean]$IsServer = $ComputerSystemType -contains 'Server'
    [System.Boolean]$IsSFF = $ComputerSystemType -contains 'Small Form Factor'
    [System.Boolean]$IsTablet = $ComputerSystemType -contains 'Tablet'
    #=================================================
    # TotalPhysicalMemoryGB
    $TotalPhysicalMemoryGB = [math]::Round(
        $classWin32ComputerSystem.TotalPhysicalMemory / 1GB,
        0,
        [System.MidpointRounding]::AwayFromZero
    )
    if ($TotalPhysicalMemoryGB -lt 6) {
        Write-Warning "[$(Get-Date -format s)] OSDCloud Workflow requires at least 8 GB of memory to function properly. Errors are expected."
    }
    #=================================================
    # OA3Tool for Hardware Hash (Autopilot)
    $HardwareHash = $null
    if (Get-Command 'oa3tool.exe' -ErrorAction Ignore) {
        $oa3cfg = @"
<OA3>
    <FileBased>
        <InputKeyXMLFile>$env:TEMP\OA3_Input.xml</InputKeyXMLFile>
    </FileBased>
    <OutputData>
        <AssembledBinaryFile>$env:TEMP\OA3.bin</AssembledBinaryFile>
        <ReportedXMLFile>$env:TEMP\OA3.xml</ReportedXMLFile>
    </OutputData>
</OA3>
"@


        $oa3input = @"
<?xml version="1.0"?>
<Key>
    <ProductKey>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</ProductKey>
    <ProductKeyID>0000000000000</ProductKeyID>
    <ProductKeyState>0</ProductKeyState>
</Key>
"@


        $oa3cfg | Out-File -FilePath "$env:TEMP\OA3.cfg" -Encoding utf8 -Force
        $oa3input | Out-File -FilePath "$env:TEMP\OA3_Input.xml" -Encoding utf8 -Force
        $null = oa3tool.exe /Report /ConfigFile="$env:TEMP\OA3.cfg" /LogTrace="$env:TEMP\OA3_Report.xml" /NoKeyCheck
        if (Test-Path "$env:TEMP\OA3.xml") {
            $HardwareHash = Get-Content "$env:TEMP\OA3.xml" -Raw | Select-String -Pattern '<HardwareHash>(.*?)</HardwareHash>' -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object { $_.Groups[1].Value }

            Copy-Item -Path "$env:TEMP\OA3.xml" -Destination (Join-Path -Path $LogsPath -ChildPath 'OA3.xml') -Force -ErrorAction SilentlyContinue
            Copy-Item -Path "$env:TEMP\OA3_Report.xml" -Destination (Join-Path -Path $LogsPath -ChildPath 'OA3_Report.xml') -Force -ErrorAction SilentlyContinue
            if ($HardwareHash) {
                $null = oa3tool.exe /DecodeHwHash="$HardwareHash" /LogTrace="$env:TEMP\OA3_Decode.xml"
                $null = oa3tool.exe /ValidateHwHash="$HardwareHash" /LogTrace="$env:TEMP\OA3_Validate.xml"
                $csvContent = @()
                $csvContent += [PSCustomObject]@{
                    'Device Serial Number' = $SerialNumber
                    'Windows Product ID'   = ''
                    'Hardware Hash'        = $HardwareHash
                }
                $csvContent | ConvertTo-Csv -NoTypeInformation | ForEach-Object { $_ -replace '"', '' } | Out-File -FilePath "$env:TEMP\Autopilot.csv" -Force -Encoding utf8
                Copy-Item -Path "$env:TEMP\Autopilot.csv" -Destination (Join-Path -Path $LogsPath -ChildPath 'Autopilot.csv') -Force -ErrorAction SilentlyContinue
                Copy-Item -Path "$env:TEMP\OA3_Decode.xml" -Destination (Join-Path -Path $LogsPath -ChildPath 'OA3_Decode.xml') -Force -ErrorAction SilentlyContinue
                Copy-Item -Path "$env:TEMP\OA3_Validate.xml" -Destination (Join-Path -Path $LogsPath -ChildPath 'OA3_Validate.xml') -Force -ErrorAction SilentlyContinue
            }
        }
    }
    #=================================================
    # OSD Properties with normalization and aliasing for known manufacturers and models to ensure consistent values for OSDCloud workflows and reporting
    $OSDManufacturer = $classWin32ComputerSystem.Manufacturer | ConvertTo-TrimmedString
    $OSDModel = $classWin32ComputerSystem.Model | ConvertTo-TrimmedString
    $OSDProduct = $classWin32ComputerSystemProduct.Version | ConvertTo-TrimmedString
    #=================================================
    # Normalize Aliases for Known Manufacturers and Models
    switch -Regex ($OSDManufacturer) {
        'Acer' {
            $OSDManufacturer = 'Acer'
            $OSDProduct = $BaseBoardProduct
            break
        }
        'ASUS|ASUSTeK' {
            $OSDManufacturer = 'ASUS'
            $OSDProduct = $BaseBoardProduct
            break
        }
        'Dell' {
            $OSDManufacturer = 'Dell'
            $OSDProduct = $ComputerSystemSKU
            break
        }
        'Fujitsu' {
            $OSDManufacturer = 'Fujitsu'
            $OSDProduct = $BaseBoardProduct
            break
        }
        'Gigabyte' {
            $OSDManufacturer = 'Gigabyte'
            $OSDProduct = $BaseBoardProduct
            break
        }
        'Lenovo' {
            $OSDManufacturer = 'Lenovo'
            $OSDModel = $ComputerSystemProduct
            if (-not [string]::IsNullOrWhiteSpace($ComputerModel) -and $ComputerModel.Length -ge 4) {
                $OSDProduct = $ComputerModel.Substring(0, 4)
            }
            else {
                $OSDProduct = $ComputerModel
            }
            break
        }
        'Hewlett|Packard|^HP$' {
            $OSDManufacturer = 'HP'
            $OSDProduct = $BaseBoardProduct
            break
        }
        'Microsoft' {
            $OSDManufacturer = 'Microsoft'
            if ($OSDModel -match 'Virtual') {
                $OSDProduct = $ComputerSystemProduct
            }
            else {
                $OSDProduct = $ComputerSystemSKU
            }
            break
        }
        'Panasonic' { $OSDManufacturer = 'Panasonic'; break }
        'to be filled' { $OSDManufacturer = 'OEM'; break }
    }

    if ([string]::IsNullOrWhiteSpace($OSDManufacturer) -or $OSDManufacturer -match '^to be filled$') {
        $OSDManufacturer = 'OEM'
    }
    if ([string]::IsNullOrWhiteSpace($OSDModel) -or $OSDModel -match '^to be filled$') {
        $OSDModel = 'OEM'
    }
    if ([string]::IsNullOrWhiteSpace($OSDProduct)) {
        $OSDProduct = 'Unknown'
    }
    #=================================================
    # Disk Information
    # Include only disks that are online and available.
    $GetDisk = Get-Disk |
    Where-Object {
        $_.IsOffline -eq $false -and
        $_.OperationalStatus -eq 'Online'
    } |
    Sort-Object DiskNumber |
    Select-Object -Property *

    $USBDisk = $GetDisk | Where-Object { $_.BusType -eq 'USB' }
    $LocalDisk = $GetDisk | Where-Object { $_.BusType -notin 'File Backed Virtual', 'MAX', 'Microsoft Reserved', 'USB', 'Virtual' }

    $usbDiskNumbers = [System.Collections.Generic.HashSet[int]]::new()
    foreach ($disk in $USBDisk) {
        [void]$usbDiskNumbers.Add([int]$disk.DiskNumber)
    }
    $localDiskNumbers = [System.Collections.Generic.HashSet[int]]::new()
    foreach ($disk in $LocalDisk) {
        [void]$localDiskNumbers.Add([int]$disk.DiskNumber)
    }

    # Partition Information
    $GetPartition = Get-Partition |
    Sort-Object DiskNumber, PartitionNumber |
    Select-Object -Property *, @{
        Name       = 'IsUSB'
        Expression = { $usbDiskNumbers.Contains([int]$_.DiskNumber) }
    }, @{
        Name       = 'IsLocal'
        Expression = { $localDiskNumbers.Contains([int]$_.DiskNumber) }
    }
    # USB Partitions
    $USBPartition = $GetPartition | Where-Object { $_.IsUSB -eq $true }
    $LocalPartition = $GetPartition | Where-Object { $_.IsLocal -eq $true }

    # USBVolume
    $usbDriveLetters = $USBPartition |
    ForEach-Object { $_.AccessPaths } |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
    ForEach-Object {
        if ($_ -match '^(?<DriveLetter>[A-Z]):\\$') {
            $Matches.DriveLetter
        }
    } |
    Sort-Object -Unique

    $USBVolume = $null
    if ($usbDriveLetters) {
        $USBVolume = Get-Volume -DriveLetter $usbDriveLetters -ErrorAction SilentlyContinue |
        Sort-Object DriveLetter -Unique
    }
    $USBCache = $null
    if ($USBVolume) {
        $USBCache = $USBVolume | Where-Object { $_.FileSystem -eq 'NTFS' } | Select-Object -First 1
    }

    # LocalVolume
    $localDriveLetters = $LocalPartition |
    ForEach-Object { $_.AccessPaths } |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
    ForEach-Object {
        if ($_ -match '^(?<DriveLetter>[A-Z]):\\$') {
            $Matches.DriveLetter
        }
    } |
    Sort-Object -Unique

    $LocalVolume = $null
    if ($localDriveLetters) {
        $LocalVolume = Get-Volume -DriveLetter $localDriveLetters -ErrorAction SilentlyContinue |
        Sort-Object DriveLetter -Unique
    }
    #=================================================
    # OSDCloudEnv
    #=================================================
    # Use OSDCloudEnv to override these properties:
    # OSDManufacturer
    # OSDModel
    # OSDProduct
    # OSArchitecture
    if ($global:OSDCloudEnv) {
        if ($global:OSDCloudEnv.OSDManufacturer) {
            $OSDManufacturer = $global:OSDCloudEnv.OSDManufacturer
        }
        if ($global:OSDCloudEnv.OSDModel) {
            $OSDModel = $global:OSDCloudEnv.OSDModel
        }
        if ($global:OSDCloudEnv.OSDProduct) {
            $OSDProduct = $global:OSDCloudEnv.OSDProduct
        }
        if ($global:OSDCloudEnv.OSArchitecture) {
            $OSArchitecture = $global:OSDCloudEnv.OSArchitecture
        }
        if ($global:OSDCloudEnv.ProcessorArchitecture) {
            $ProcessorArchitecture = $global:OSDCloudEnv.ProcessorArchitecture
        }
    }
    #=================================================
    # OSDeploy and Recast Registration Information
    #=================================================
    $deviceUUID = [System.String]$classWin32ComputerSystemProduct.UUID
    $EndpointSHA = $null
    if (-not [string]::IsNullOrWhiteSpace($deviceUUID)) {
        $EndpointSHA = [System.BitConverter]::ToString([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($deviceUUID))).Replace("-", "")
    }

    $idOSDeployDevice = $null
    if (-not [string]::IsNullOrWhiteSpace($env:ID_OSDEPLOYDEVICE)) {
        $idOSDeployDevice = [System.String]$env:ID_OSDEPLOYDEVICE
    }

    $idOSDeployBoot = $null
    if (-not [string]::IsNullOrWhiteSpace($env:ID_OSDEPLOYBOOT)) {
        $idOSDeployBoot = [System.String]$env:ID_OSDEPLOYBOOT
    }

    $idRegisteredEmail = $null
    if (-not [string]::IsNullOrWhiteSpace($env:ID_REGISTEREDEMAIL)) {
        $idRegisteredEmail = [System.String]$env:ID_REGISTEREDEMAIL
    }

    $idRegisteredLicense = $null
    if (-not [string]::IsNullOrWhiteSpace($env:ID_REGISTEREDLICENSE)) {
        $idRegisteredLicense = [System.String]$env:ID_REGISTEREDLICENSE
    }

    if ([string]::IsNullOrWhiteSpace($idRegisteredEmail) -or [string]::IsNullOrWhiteSpace($idRegisteredLicense)) {
        try {
            if (-not $global:OSDCoreLicense.License) {
                Initialize-OSDCoreLicense
            }
            if ([string]::IsNullOrWhiteSpace($idRegisteredEmail) -and -not [string]::IsNullOrWhiteSpace($global:OSDCoreLicense.License.Email)) {
                $idRegisteredEmail = [System.String]$global:OSDCoreLicense.License.Email
            }
            if ([string]::IsNullOrWhiteSpace($idRegisteredLicense) -and -not [string]::IsNullOrWhiteSpace($global:OSDCoreLicense.License.LicenseGuid)) {
                $idRegisteredLicense = [System.String]$global:OSDCoreLicense.License.LicenseGuid
            }
        }
        catch {}
    }

    #=================================================
    # OSLanguage
    #=================================================
    if ($global:OSDCoreLicense.IsRegistered) {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] OSDCloud is registered to $idRegisteredEmail"
        if (Get-Command -Name 'Convert-KeyboardLayoutToLanguageCode' -ErrorAction Ignore) {
            $AutoOSLanguageCode = Convert-KeyboardLayoutToLanguageCode -KeyboardLayout $KeyboardLayout -FallbackLanguageCode 'en-US' -LowerCase
        }
    }
    else {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Skipping AutoOSLanguageCode keyboard conversion because OSDCloud is not registered."
    }

    $reportedOSDManufacturer = if ([string]::IsNullOrWhiteSpace($OSDManufacturer)) { 'Unknown' } else { [System.String]$OSDManufacturer }
    $reportedOSDModel = if ([string]::IsNullOrWhiteSpace($OSDModel)) { 'Unknown' } else { [System.String]$OSDModel }
    $reportedOSDProduct = if ([string]::IsNullOrWhiteSpace($OSDProduct)) { 'Unknown' } else { [System.String]$OSDProduct }

    $global:OSDCoreDevice = $null
    $global:OSDCoreDevice = [ordered]@{
        OSDManufacturer          = $reportedOSDManufacturer
        OSDModel                 = $reportedOSDModel
        OSDProduct               = $reportedOSDProduct
        AutoOSLanguageCode       = $AutoOSLanguageCode
        BaseBoardProduct         = [System.String]$BaseBoardProduct
        BiosReleaseDate          = [System.String]$classWin32BIOS.ReleaseDate
        BiosVersion              = [System.String]$classWin32BIOS.SMBIOSBIOSVersion
        ComputerManufacturer     = [System.String]$ComputerManufacturer
        ComputerModel            = [System.String]$ComputerModel
        ComputerName             = $classWin32ComputerSystem.Name
        ComputerSystemFamily     = [System.String]$ComputerSystemFamily
        ComputerSystemProduct    = [System.String]$ComputerSystemProduct
        ComputerSystemSKU        = [System.String]$ComputerSystemSKU
        ComputerSystemType       = [System.String]$ComputerSystemType
        HardwareHash             = [System.String]$HardwareHash
        IsAutopilotSpec          = [System.Boolean]$IsAutopilotSpec
        IsDesktop                = [System.Boolean]$IsDesktop
        IsLaptop                 = [System.Boolean]$IsLaptop
        IsOnBattery              = [System.Boolean]$IsOnBattery
        IsServer                 = [System.Boolean]$IsServer
        IsSFF                    = [System.Boolean]$IsSFF
        IsTablet                 = [System.Boolean]$IsTablet
        IsTpmSpec                = [System.Boolean]$IsTpmSpec
        IsUEFI                   = [System.Boolean]$IsUEFI
        IsVM                     = [System.Boolean]$IsVM
        IsWinPE                  = [System.Boolean]$IsWinPE
        KeyboardLayout           = $KeyboardLayout
        KeyboardName             = $KeyboardName
        LocalDisk                = $LocalDisk
        LocalPartition           = $LocalPartition
        LocalVolume              = $LocalVolume
        NetGateways              = $NetGateways
        NetIPAddress             = $NetIPAddress
        NetMacAddress            = $NetMacAddress
        OSArchitecture           = $OSArchitecture
        OSVersion                = $classWin32OperatingSystem.Version
        ProcessorArchitecture    = $ProcessorArchitecture
        SerialNumber             = $SerialNumber
        SystemFirmwareHardwareId = $SystemFirmwareHardwareId
        TimeZone                 = $classWin32TimeZone.StandardName
        TotalPhysicalMemoryGB    = $TotalPhysicalMemoryGB
        TpmIsActivated           = $DeviceTpmIsActivated
        TpmIsEnabled             = $DeviceTpmIsEnabled
        TpmIsOwned               = $DeviceTpmIsOwned
        TpmManufacturerIdTxt     = $DeviceTpmManufacturerIdTxt
        TpmManufacturerVersion   = $DeviceTpmManufacturerVersion
        TpmSpecVersion           = $DeviceTpmSpecVersion
        USBCache                 = $USBCache
        USBDisk                  = $USBDisk
        USBPartition             = $USBPartition
        USBVolume                = $USBVolume
        UUID                     = $deviceUUID
        EndpointSHA              = [System.String]$EndpointSHA #Device UUID SHA256
        idOSDeployDevice         = $idOSDeployDevice
        idOSDeployBoot           = $idOSDeployBoot
        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'
    if (Test-Path -LiteralPath $OSDCoreDeviceClixmlPath) {
        Remove-Item -LiteralPath $OSDCoreDeviceClixmlPath -Force -ErrorAction SilentlyContinue
    }
    $global:OSDCoreDevice | Export-Clixml -Path $OSDCoreDeviceClixmlPath -Force

    $OSDCoreDeviceJsonPath = Join-Path -Path $LogsPath -ChildPath 'OSDCoreDevice.json'
    if (Test-Path -LiteralPath $OSDCoreDeviceJsonPath) {
        Remove-Item -LiteralPath $OSDCoreDeviceJsonPath -Force -ErrorAction SilentlyContinue
    }
    $global:OSDCoreDevice | ConvertTo-Json -Depth 10 | Out-File $OSDCoreDeviceJsonPath -Force -Encoding utf8
    #=================================================
    # OSDCloudLogs
    # Look for available drives (USB, mapped network drives, and local drives) with at least 1 GB of free space and write permissions for the current user to copy logs.
    $OSDCloudLogsDrive = $null

    foreach ($Drive in (Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -match '^[A-Z]:\\$' })) {
        $DriveLetter = $Drive.Name
        $OSDCloudLogsCheckPath = "$DriveLetter`:\OSDCloudLogs"

        try {
            if (Test-Path -Path $OSDCloudLogsCheckPath) {
                $DriveInfo = New-Object System.IO.DriveInfo($DriveLetter)
                if ($DriveInfo.AvailableFreeSpace -ge 1GB) {
                    $OSDCloudLogsDrive = [PSCustomObject]@{
                        DriveLetter          = $DriveLetter
                        AvailableFreeSpaceGB = [math]::Round($DriveInfo.AvailableFreeSpace / 1GB, 1)
                    }
                    break
                }
            }
        }
        catch {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to access drive $DriveLetter"
        }
    }

    if ($OSDCloudLogsDrive) {
        $OSDCloudLogsPath = "$($OSDCloudLogsDrive.DriveLetter):\OSDCloudLogs\$SerialNumber"
        if (-not (Test-Path -Path $OSDCloudLogsPath)) {
            New-Item -Path $OSDCloudLogsPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
        }
        # If folder doesn't exist, then the drive likely doesn't have write permissions for the current user, so skip copying logs to avoid errors
        if (Test-Path -Path $OSDCloudLogsPath) {
            Get-ChildItem -Path $LogsPath -File | ForEach-Object {
                Copy-Item -Path $_.FullName -Destination $(Join-Path -Path $OSDCloudLogsPath -ChildPath $_.Name) -Force
            }
        }
    }
    #=================================================
    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] End"
    #=================================================
}