private/Initialize-OSDCloudDevice.ps1

function Initialize-OSDCloudDevice {
    [CmdletBinding()]
    param ()
    function ConvertTo-TrimmedString {
        param(
            [Parameter(ValueFromPipeline = $true)]
            $Value
        )

        process {
            if ($null -eq $Value) {
                return $null
            }
            return $Value.ToString().Trim()
        }
    }
    #=================================================
    $Error.Clear()
    #=================================================
    # 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
    }
    #=================================================
    # 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
    }
    catch {
        $classWin32Keyboard = $null
    }
    #=================================================
    # 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
    #=================================================
    # 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)] [$($MyInvocation.MyCommand.Name)] TPM is not supported on this device."
        Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 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)] [$($MyInvocation.MyCommand.Name)] TPM version is lower than 2.0 on this device."
            Write-Host -ForegroundColor Yellow "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Autopilot is not supported on this device."
        }
        else {
            Write-Host -ForegroundColor DarkGreen "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] TPM 2.0 is supported on this device."
            Write-Host -ForegroundColor DarkGreen "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Autopilot is supported on this device."
            $IsAutopilotSpec = $true
            $IsTpmSpec = $true
        }
    }
    #=================================================
    # 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
    #=================================================
    # ChassisType
    $IsDesktop = $false
    $IsLaptop = $false
    $IsServer = $false
    $IsSFF = $false
    $IsTablet = $false
    $ComputerSystemType = $classWin32SystemEnclosure | ForEach-Object {
        if ($_.ChassisTypes[0] -in "8", "9", "10", "11", "12", "14", "18", "21") { $IsLaptop = $true; "Laptop" }
        if ($_.ChassisTypes[0] -in "3", "4", "5", "6", "7", "15", "16") { $IsDesktop = $true; "Desktop" }
        if ($_.ChassisTypes[0] -in "23") { $IsServer = $true; "Server" }
        if ($_.ChassisTypes[0] -in "34", "35", "36") { $IsSFF = $true; "Small Form Factor" }
        if ($_.ChassisTypes[0] -in "13", "31", "32", "30") { $IsTablet = $true; "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 SilentlyContinue) {
    $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) {
        'Dell' {
            $OSDManufacturer = 'Dell'
            $OSDProduct = $ComputerSystemSKU
            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'
            $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'
    }
    #=================================================
    # Pass Variables to OSDCloudDevice
    #=================================================
    $global:OSDCloudDevice = $null
    $global:OSDCloudDevice = [ordered]@{
        OSDManufacturer           = [System.String]$OSDManufacturer
        OSDModel                  = [System.String]$OSDModel
        OSDProduct                = [System.String]$OSDProduct
        ComputerName              = $classWin32ComputerSystem.Name
        BaseBoardProduct          = [System.String]$BaseBoardProduct
        BiosReleaseDate           = [System.String]$classWin32BIOS.ReleaseDate
        BiosVersion               = [System.String]$classWin32BIOS.SMBIOSBIOSVersion
        ComputerManufacturer      = [System.String]$ComputerManufacturer
        ComputerModel             = [System.String]$ComputerModel
        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
        IsVM                      = [System.Boolean]$IsVM
        IsUEFI                    = [System.Boolean]$IsUEFI
        KeyboardLayout            = [System.String]$classWin32Keyboard.Layout
        KeyboardName              = [System.String]$classWin32Keyboard.Name
        NetGateways               = $NetGateways
        NetIPAddress              = $NetIPAddress
        NetMacAddress             = $NetMacAddress
        OSArchitecture            = $classWin32OperatingSystem.OSArchitecture
        OSVersion                 = $classWin32OperatingSystem.Version
        ProcessorArchitecture     = $env:PROCESSOR_ARCHITECTURE
        SerialNumber              = $SerialNumber
        # SystemFirmwareDevice = $SystemFirmwareDevice
        # SystemFirmwareResource = $SystemFirmwareResource
        SystemFirmwareHardwareId  = $SystemFirmwareHardwareId
        TimeZone                  = $classWin32TimeZone.StandardName
        TotalPhysicalMemoryGB     = $TotalPhysicalMemoryGB
        TpmIsActivated            = $DeviceTpmIsActivated
        TpmIsEnabled              = $DeviceTpmIsEnabled
        TpmIsOwned                = $DeviceTpmIsOwned
        TpmManufacturerIdTxt      = $DeviceTpmManufacturerIdTxt
        TpmManufacturerVersion    = $DeviceTpmManufacturerVersion
        TpmSpecVersion            = $DeviceTpmSpecVersion
        UUID                      = $classWin32ComputerSystemProduct.UUID
    }
    $global:OSDCloudDevice | ConvertTo-Json -Depth 10 | Out-File "$LogsPath\OSDCloudDevice.json" -Force -Encoding utf8
    #=================================================
    # USB Debug
    $USBDrive = Get-DeviceUSBVolume | Where-Object { ($_.FileSystemLabel -match "OSDCloud|USB-DATA") } | Where-Object { $_.SizeGB -ge 16 } | Where-Object { $_.SizeRemainingGB -ge 10 } | Select-Object -First 1
    if ($USBDrive) {
        $OSDCloudDebugPath = "$($USBDrive.DriveLetter):\osdcloud-debug"
        if (Test-Path -Path $OSDCloudDebugPath) {
            $usbLogsRoot = "$($USBDrive.DriveLetter):\osdcloud-debug\$SerialNumber"
            if (-not (Test-Path -Path $usbLogsRoot)) {
                New-Item -Path $usbLogsRoot -ItemType Directory -Force | Out-Null
            }
            if (Test-Path -Path $usbLogsRoot) {
                # Copy files from $LogsPath to $usbLogsRoot
                Get-ChildItem -Path $LogsPath -File | ForEach-Object {
                    $destination = Join-Path -Path $usbLogsRoot -ChildPath $_.Name
                    Copy-Item -Path $_.FullName -Destination $destination -Force
                }
            }
        }
    }
    #=================================================
    # End the function
    $Message = "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] End"
    Write-Verbose -Message $Message; Write-Debug -Message $Message
    #=================================================
}