GetLinuxDevices.psm1

if (-not $IsLinux) {
    throw "GetLinuxDevices module requires Linux. Current OS: $($PSVersionTable.OS)"
}

function Get-UsbDevice {
    <#
    .SYNOPSIS
        Gets USB device information from lsusb.
    
    .DESCRIPTION
        Parses lsusb output into PowerShell objects with properties for Bus, Device, ID, Vendor, Product, and Name.
    
    .PARAMETER Detailed
        Show detailed information about devices (lsusb -v). Returns raw verbose output.
    
    .PARAMETER Tree
        Show USB device hierarchy as a tree (lsusb -t). Returns raw tree output.
    
    .PARAMETER Bus
        Filter by specific bus number.
    
    .PARAMETER DeviceNumber
        Filter by specific device number on a bus.
    
    .PARAMETER VendorId
        Filter by vendor ID (4-digit hex, e.g., '046d').
    
    .PARAMETER ProductId
        Filter by product ID (4-digit hex, e.g., 'c52b').
    
    .EXAMPLE
        Get-UsbDevice
        
    .EXAMPLE
        Get-UsbDevice | Where-Object { $_.Name -like '*Keyboard*' }
        
    .EXAMPLE
        Get-UsbDevice -Detailed
        
    .EXAMPLE
        Get-UsbDevice -Tree
        
    .EXAMPLE
        Get-UsbDevice -VendorId 046d -ProductId c52b
        
    .EXAMPLE
        lsusb | Format-Table
    #>

    [CmdletBinding(DefaultParameterSetName='Standard')]
    param(
        [Parameter(ParameterSetName='Detailed')]
        [switch]$Detailed,
        
        [Parameter(ParameterSetName='Tree')]
        [switch]$Tree,
        
        [Parameter(ParameterSetName='Standard')]
        [string]$Bus,
        
        [Parameter(ParameterSetName='Standard')]
        [string]$DeviceNumber,
        
        [Parameter(ParameterSetName='Standard')]
        [string]$VendorId,
        
        [Parameter(ParameterSetName='Standard')]
        [string]$ProductId
    )
    
    if (-not (Get-Command -Name lsusb -CommandType Application -ErrorAction SilentlyContinue)) {
        throw "lsusb command not found. Install usbutils package."
    }
    
    $cmdArgs = @()
    
    if ($Detailed) {
        $cmdArgs += '-v'
        $output = & /usr/bin/lsusb @cmdArgs
        return $output
    }
    
    if ($Tree) {
        $cmdArgs += '-t'
        $output = & /usr/bin/lsusb @cmdArgs
        return $output
    }
    
    if ($Bus -or $DeviceNumber) {
        $filter = ''
        if ($Bus) { $filter = $Bus }
        if ($DeviceNumber) { $filter += ":$DeviceNumber" }
        $cmdArgs += @('-s', $filter)
    }
    
    if ($VendorId -or $ProductId) {
        $filter = ''
        if ($VendorId) { $filter = $VendorId }
        $filter += ':'
        if ($ProductId) { $filter += $ProductId }
        $cmdArgs += @('-d', $filter)
    }
    
    $output = & /usr/bin/lsusb @cmdArgs
    
    foreach ($line in $output) {
        # Format: Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
        if ($line -match '^Bus (\d+) Device (\d+): ID ([0-9a-f]{4}):([0-9a-f]{4}) (.+)$') {
            [PSCustomObject]@{
                Bus = $Matches[1]
                Device = $Matches[2]
                VendorID = $Matches[3]
                ProductID = $Matches[4]
                ID = "$($Matches[3]):$($Matches[4])"
                Name = $Matches[5]
                Vendor = ($Matches[5] -split ' ', 2)[0]
                Product = if ($Matches[5] -match '.+?\s+(.+)') { $Matches[1] } else { $Matches[5] }
            }
        }
    }
}

function Get-PciDevice {
    <#
    .SYNOPSIS
        Gets PCI device information from lspci.
    
    .DESCRIPTION
        Parses lspci output into PowerShell objects with properties for Slot, Class, Vendor, and Device.
    
    .PARAMETER Numeric
        Show numeric IDs instead of looking up names.
    
    .PARAMETER ShowDrivers
        Show kernel drivers and modules handling each device (lspci -k).
    
    .PARAMETER ShowDetails
        Show detailed information (lspci -v).
    
    .PARAMETER ShowMoreDetails
        Show very detailed information (lspci -vv).
    
    .PARAMETER Tree
        Show PCI hierarchy as a tree (lspci -t). Returns raw tree output.
    
    .PARAMETER HexDump
        Include hex dump of configuration space (lspci -x).
    
    .EXAMPLE
        Get-PciDevice
        
    .EXAMPLE
        Get-PciDevice | Where-Object { $_.Class -like '*VGA*' }
        
    .EXAMPLE
        Get-PciDevice -ShowDrivers
        
    .EXAMPLE
        Get-PciDevice -ShowDetails
        
    .EXAMPLE
        Get-PciDevice -ShowMoreDetails
        
    .EXAMPLE
        lspci | Format-Table
    #>

    [CmdletBinding(DefaultParameterSetName='Standard')]
    param(
        [Parameter(ParameterSetName='Standard')]
        [switch]$Numeric,
        
        [Parameter(ParameterSetName='Drivers')]
        [switch]$ShowDrivers,
        
        [Parameter(ParameterSetName='Details')]
        [switch]$ShowDetails,
        
        [Parameter(ParameterSetName='MoreDetails')]
        [switch]$ShowMoreDetails,
        
        [Parameter(ParameterSetName='Tree')]
        [switch]$Tree,
        
        [Parameter(ParameterSetName='HexDump')]
        [switch]$HexDump
    )
    
    if (-not (Get-Command -Name lspci -CommandType Application -ErrorAction SilentlyContinue)) {
        throw "lspci command not found. Install pciutils package."
    }
    
    $cmdArgs = @()
    
    if ($Tree) {
        $cmdArgs += '-t'
        $output = & /usr/bin/lspci @cmdArgs
        return $output
    }
    
    if ($ShowDrivers) {
        $cmdArgs += '-k'
        $output = & /usr/bin/lspci @cmdArgs
        return $output
    }
    
    if ($ShowDetails) {
        $cmdArgs += '-v'
        $output = & /usr/bin/lspci @cmdArgs
        return $output
    }
    
    if ($ShowMoreDetails) {
        $cmdArgs += '-vv'
        $output = & /usr/bin/lspci @cmdArgs
        return $output
    }
    
    if ($HexDump) {
        $cmdArgs += '-x'
        $output = & /usr/bin/lspci @cmdArgs
        return $output
    }
    
    if ($Numeric) { $cmdArgs += '-n' }
    
    $output = & /usr/bin/lspci @cmdArgs
    
    foreach ($line in $output) {
        # Format: 00:00.0 Host bridge: Intel Corporation 4th Gen Core Processor DRAM Controller (rev 06)
        if ($line -match '^([0-9a-f:.]+)\s+([^:]+):\s+(.+)$') {
            [PSCustomObject]@{
                Slot = $Matches[1]
                Class = $Matches[2].Trim()
                Description = $Matches[3]
                Vendor = ($Matches[3] -split ' ', 2)[0]
            }
        }
    }
}

function Get-BlockDevice {
    <#
    .SYNOPSIS
        Gets block device information from lsblk.
    
    .DESCRIPTION
        Parses lsblk output into PowerShell objects with properties for Name, Size, Type, MountPoint, and more.
    
    .PARAMETER DisksOnly
        Show only disk devices, not partitions (lsblk -d).
    
    .PARAMETER FilesystemInfo
        Show filesystem information including UUID and LABEL (lsblk -f).
    
    .PARAMETER Topology
        Show topology information (lsblk -t).
    
    .PARAMETER FullPaths
        Print full device paths (lsblk -p).
    
    .PARAMETER Bytes
        Print sizes in bytes instead of human-readable format (lsblk -b).
    
    .PARAMETER SCSI
        Show SCSI device information (lsblk -S).
    
    .EXAMPLE
        Get-BlockDevice
        
    .EXAMPLE
        Get-BlockDevice | Where-Object { $_.Type -eq 'disk' }
        
    .EXAMPLE
        Get-BlockDevice -DisksOnly
        
    .EXAMPLE
        Get-BlockDevice -FilesystemInfo
        
    .EXAMPLE
        Get-BlockDevice -FullPaths
        
    .EXAMPLE
        lsblk | Where-Object { $_.Mountpoint -ne $null }
    #>

    [CmdletBinding(DefaultParameterSetName='Standard')]
    param(
        [Parameter(ParameterSetName='Standard')]
        [switch]$DisksOnly,
        
        [Parameter(ParameterSetName='Filesystem')]
        [switch]$FilesystemInfo,
        
        [Parameter(ParameterSetName='Topology')]
        [switch]$Topology,
        
        [Parameter(ParameterSetName='Standard')]
        [switch]$FullPaths,
        
        [Parameter(ParameterSetName='Standard')]
        [switch]$Bytes,
        
        [Parameter(ParameterSetName='SCSI')]
        [switch]$SCSI
    )
    
    if (-not (Get-Command -Name lsblk -CommandType Application -ErrorAction SilentlyContinue)) {
        throw "lsblk command not found. Install util-linux package."
    }
    
    $cmdArgs = @('-J')
    
    if ($SCSI) {
        $cmdArgs += '-S'
        $jsonText = & /usr/bin/lsblk @cmdArgs
        $json = $jsonText | ConvertFrom-Json
        
        foreach ($device in $json.blockdevices) {
            [PSCustomObject]@{
                Name = $device.name
                HostChannel = $device.'hctl'
                Type = $device.type
                Vendor = $device.vendor
                Model = $device.model
                Rev = $device.rev
                Serial = $device.serial
                Size = $device.size
                Transport = $device.tran
            }
        }
        return
    }
    
    if ($Topology) {
        $cmdArgs += '-t'
        $jsonText = & /usr/bin/lsblk @cmdArgs
        $json = $jsonText | ConvertFrom-Json
        
        function ConvertTopologyDevice {
            param($device)
            
            [PSCustomObject]@{
                Name = $device.name
                Alignment = $device.alignment
                MinIO = $device.'min-io'
                OptIO = $device.'opt-io'
                PhysicalSectorSize = $device.'phy-sec'
                LogicalSectorSize = $device.'log-sec'
                Rota = $device.rota
                Sched = $device.sched
                RQSize = $device.'rq-size'
                Type = $device.type
                DiscardAlignment = $device.'disc-aln'
                DiscardGranularity = $device.'disc-gran'
                DiscardMax = $device.'disc-max'
                DiscardZeros = $device.'disc-zero'
            }
            
            if ($device.children) {
                foreach ($child in $device.children) {
                    ConvertTopologyDevice $child
                }
            }
        }
        
        foreach ($device in $json.blockdevices) {
            ConvertTopologyDevice $device
        }
        return
    }
    
    if ($FilesystemInfo) {
        $cmdArgs += '-f'
        $jsonText = & /usr/bin/lsblk @cmdArgs
        $json = $jsonText | ConvertFrom-Json
        
        function ConvertFilesystemDevice {
            param($device)
            
            [PSCustomObject]@{
                Name = $device.name
                FileSystem = $device.fstype
                FSVersion = $device.fsver
                Label = $device.label
                UUID = $device.uuid
                FSAvailable = $device.fsavail
                FSUsed = $device.fsused
                FSUsePct = $device.'fsuse%'
                MountPoint = $device.mountpoint
                MountPoints = $device.mountpoints -join ', '
            }
            
            if ($device.children) {
                foreach ($child in $device.children) {
                    ConvertFilesystemDevice $child
                }
            }
        }
        
        foreach ($device in $json.blockdevices) {
            ConvertFilesystemDevice $device
        }
        return
    }
    
    # Standard mode
    if ($DisksOnly) { $cmdArgs += '-d' }
    if ($FullPaths) { $cmdArgs += '-p' }
    if ($Bytes) { $cmdArgs += '-b' }
    
    $cmdArgs += '-o'
    $cmdArgs += 'NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL'
    
    $jsonText = & /usr/bin/lsblk @cmdArgs
    $json = $jsonText | ConvertFrom-Json
    
    function ConvertDevice {
        param($device)
        
        [PSCustomObject]@{
            Name = $device.name
            Size = $device.size
            Type = $device.type
            MountPoint = $device.mountpoint
            FileSystem = $device.fstype
            Model = $device.model
            Serial = $device.serial
        }
        
        if ($device.children) {
            foreach ($child in $device.children) {
                ConvertDevice $child
            }
        }
    }
    
    foreach ($device in $json.blockdevices) {
        ConvertDevice $device
    }
}

# Export aliases
New-Alias -Name lsusb -Value Get-UsbDevice -Force
New-Alias -Name lspci -Value Get-PciDevice -Force
New-Alias -Name lsblk -Value Get-BlockDevice -Force

Export-ModuleMember -Function Get-UsbDevice, Get-PciDevice, Get-BlockDevice -Alias lsusb, lspci, lsblk