public/Convert-USBDataToObject.ps1

function Convert-USBDataToObject {
    <#
    .SYNOPSIS
        Converts raw USB device data into structured PowerShell objects.

    .DESCRIPTION
        Parses raw USB device information (typically from lsusb output) and converts it into
        structured PowerShell objects. The function processes device headers, descriptors, and
        their properties, converting numeric values into appropriate types and organizing the
        data hierarchically.

    .PARAMETER Data
        The raw USB device data string to parse. This is typically the output from lsusb or
        similar commands. The data should contain device headers and descriptor information
        in a structured format.

    .EXAMPLE
        $usbData = Get-Content -Path "./usb-output.txt" -Raw
        $usbData | Convert-USBDataToObject

        Converts the contents of a file containing USB device information into structured objects.

    .EXAMPLE
        # Get detailed information for a specific USB device and convert it to an object
        $usbDevice = Convert-USBDataToObject -Data (Get-USBInfo -id 1 -full)
        
        # Display all descriptor types
        $usbDevice.Descriptors.Keys

        # Examine specific descriptor properties
        $usbDevice.Descriptors.Device.Properties
        $usbDevice.Descriptors.Configuration.Properties

        This example shows how to:
        1. Get detailed USB information for device ID 1
        2. Convert it to a structured object
        3. View available descriptor types
        4. Access specific descriptor properties

    .EXAMPLE
        # Get and analyze all USB devices with their descriptors
        Get-USBInfo -full | Convert-USBDataToObject | ForEach-Object {
            Write-Host "Device: $($_.Manufacturer)" -ForegroundColor Yellow
            foreach ($descriptor in $_.Descriptors.Keys) {
                Write-Host "`nDescriptor: $descriptor" -ForegroundColor Cyan
                $_.Descriptors[$descriptor].Properties | Format-Table -AutoSize
            }
        }

        This example demonstrates how to:
        1. Get information for all USB devices
        2. Convert them to objects
        3. Format and display each device's descriptors in a readable way

    .OUTPUTS
        [PSCustomObject] Objects containing structured USB device information with the following properties:
        - Bus: Integer representing the USB bus number
        - Device: Integer representing the device number
        - VendorId: String containing the vendor ID in hexadecimal
        - ProductId: String containing the product ID in hexadecimal
        - Manufacturer: String containing the manufacturer/device description
        - Descriptors: Hashtable of descriptor objects containing device properties
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Alias("Content")]
        [string]$Data
    )

    begin {
        # Define regex patterns with named capture groups
        $deviceHeaderPattern = @'
(?x) # Enable comment mode
^Bus\s+(?<bus>\d+)\s+ # Bus number
Device\s+(?<dev>\d+): # Device number
\s*ID\s+ # ID label
(?<vendorId>[\da-f]{4}): # Vendor ID (4 hex digits)
(?<productId>[\da-f]{4}) # Product ID (4 hex digits)
\s+
(?<manufacturer>[^\r\n]+) # Manufacturer/description
'@

        
        $descriptorPattern = '^(?<type>\w+)\s+Descriptor:$'
        $propertyPattern = '^\s+(?<key>\w+)\s+(?<value>.+)$'
    }

    process {
        # Split input into lines
        $lines = $Data -split '\r?\n'
        
        # Track current parsing state
        $currentDevice = $null
        $currentDescriptor = $null

        foreach ($line in $lines) {
            # Skip empty lines
            if ([string]::IsNullOrWhiteSpace($line)) { continue }

            Write-Verbose "Processing line: $line"

            switch -Regex ($line) {
                $deviceHeaderPattern {
                    Write-Verbose "Found device header"
                    # Create new device object
                    $currentDevice = [PSCustomObject]@{
                        Bus = [int]$matches.bus
                        Device = [int]$matches.dev
                        VendorId = $matches.vendorId
                        ProductId = $matches.productId
                        Manufacturer = $matches.manufacturer
                        Descriptors = @{}
                    }
                    $currentDescriptor = $null
                    continue
                }

                $descriptorPattern {
                    Write-Verbose "Found descriptor: $($matches.type)"
                    # Create new descriptor section
                    $descriptorType = $matches.type
                    $currentDevice.Descriptors[$descriptorType] = [PSCustomObject]@{
                        Type = $descriptorType
                        Properties = @{}
                    }
                    $currentDescriptor = $currentDevice.Descriptors[$descriptorType]
                    continue
                }

                $propertyPattern {
                    if ($currentDescriptor) {
                        Write-Verbose "Found property: $($matches.key)"
                        $key = $matches.key
                        $value = $matches.value.Trim()
                        
                        # Handle special cases like hex values
                        if ($value -match '^0x[\da-f]+$') {
                            $value = [Convert]::ToInt32($value.Substring(2), 16)
                        }
                        elseif ($value -match '^\d+$') {
                            $value = [int]$value
                        }
                        
                        $currentDescriptor.Properties[$key] = $value
                    }
                    continue
                }

                default {
                    # Handle continuation lines or additional information
                    if ($currentDescriptor -and $line.StartsWith(' ')) {
                        Write-Verbose "Found continuation line"
                        if (-not $currentDescriptor.Properties.ContainsKey('Notes')) {
                            $currentDescriptor.Properties['Notes'] = @()
                        }
                        $currentDescriptor.Properties['Notes'] += $line.Trim()
                    }
                }
            }
        }

        # Output the device object
        if ($currentDevice) {
            $currentDevice
        }
    }
}