public/Get-LinuxInventory.ps1
|
function Get-LinuxInventory { <# .SYNOPSIS Display Hardware and Software Inventory for local computer .DESCRIPTION Displays hardware and software inventory for the local computer. .PARAMETER ExportPath Path and filename for the inventory report. If not specified, the report is displayed to the console. .PARAMETER Category Specifies which category of inventory to return. Valid values are: "All", "OperatingSystem", "ComputerSystem", "BIOS", "Processor", "Disks", "NetworkInterfaces", "InstalledApplications", "Video", "Audio" Default is "All" which returns the complete inventory. .EXAMPLE Get-LinuxInventory Returns the complete inventory report to the console. .EXAMPLE Get-LinuxInventory -Category Video Returns only the Video inventory information. .EXAMPLE Get-LinuxInventory -Category Audio Returns only the Audio inventory information. .EXAMPLE Get-LinuxInventory -ExportPath "c:\users\dave\documents" Exports the complete inventory report to the specified path. #> [CmdletBinding(SupportsShouldProcess = $True)] param ( [parameter(Mandatory = $false)][string]$ExportPath, [parameter(Mandatory = $false)] [ValidateSet("All", "OperatingSystem", "ComputerSystem", "BIOS", "Processor", "Disks", "NetworkInterfaces", "InstalledApplications", "Video", "Audio")] [string]$Category = "All" ) $FPPlatform = "Linux" # Conditionally collect data based on Category parameter for performance if ($Category -eq "All" -or $Category -eq "OperatingSystem") { $osRaw = Invoke-Command -ScriptBlock { lsb_release -a 2>/dev/null } } else { $osRaw = @() } if ($Category -eq "All" -or $Category -eq "ComputerSystem") { $csRaw = Invoke-Command -ScriptBlock { hostnamectl 2>/dev/null } $dmiRaw = Invoke-Command -ScriptBlock { cat /sys/devices/virtual/dmi/id/sys_vendor /sys/devices/virtual/dmi/id/product_name /sys/devices/virtual/dmi/id/chassis_type 2>/dev/null } } else { $csRaw = @() $dmiRaw = @() } if ($Category -eq "All" -or $Category -eq "BIOS") { $biosRaw = Invoke-Command -ScriptBlock { timeout 5 sudo -n dmidecode -t bios 2>/dev/null || echo "BIOS info requires sudo access" } } else { $biosRaw = @() } if ($Category -eq "All" -or $Category -eq "Processor") { $procRaw = Invoke-Command -ScriptBlock { lscpu } } else { $procRaw = @() } if ($Category -eq "All" -or $Category -eq "Disks") { $disksRaw = Invoke-Command -ScriptBlock { lsblk -o NAME,SIZE,TYPE,MOUNTPOINT } $fsRaw = Invoke-Command -ScriptBlock { lsblk -f } $dfRaw = Invoke-Command -ScriptBlock { df -BM } $physicalRaw = Invoke-Command -ScriptBlock { lsblk -d -o NAME,SIZE,ROTA,TYPE } $hwRaw = Invoke-Command -ScriptBlock { timeout 15 bash -c 'for dir in /sys/block/*/device; do [ -d "$dir" ] || continue; diskname=$(basename $(dirname $dir)); echo "=== $diskname ==="; [ -f "$dir/model" ] && echo "Model: $(cat $dir/model | tr -d " ")"; [ -f "$dir/vendor" ] && echo "Vendor: $(cat $dir/vendor | tr -d " ")"; [ -f "$dir/serial" ] && echo "Serial: $(cat $dir/serial | tr -d " ")"; done' || echo "Hardware detection timed out" } } else { $disksRaw = @() $fsRaw = @() $dfRaw = @() $physicalRaw = @() $hwRaw = @() } if ($Category -eq "All" -or $Category -eq "NetworkInterfaces") { $nicRaw = Invoke-Command -ScriptBlock { ip addr show } $routeRaw = Invoke-Command -ScriptBlock { ip route show } $dnsRaw = Invoke-Command -ScriptBlock { cat /etc/resolv.conf 2>/dev/null } } else { $nicRaw = @() $routeRaw = @() $dnsRaw = @() } if ($Category -eq "All" -or $Category -eq "InstalledApplications") { $appsRaw = Invoke-Command -ScriptBlock { dpkg --list } } else { $appsRaw = @() } if ($Category -eq "All" -or $Category -eq "Video") { $videoRaw = Invoke-Command -ScriptBlock { lspci | grep -i "vga\|3d\|display" } $videoDetailRaw = Invoke-Command -ScriptBlock { lspci -v | grep -A 10 -i "vga\|3d\|display" } $nvidiaRaw = Invoke-Command -ScriptBlock { timeout 10 nvidia-smi --query-gpu=driver_version,vbios_version,name --format=csv 2>/dev/null || echo "" } $glxRaw = Invoke-Command -ScriptBlock { timeout 10 glxinfo 2>/dev/null | head -30 || echo "" } } else { $videoRaw = @() $videoDetailRaw = @() $nvidiaRaw = @() $glxRaw = @() } if ($Category -eq "All" -or $Category -eq "Audio") { $audioRaw = Invoke-Command -ScriptBlock { lspci | grep -i audio } $audioDevRaw = Invoke-Command -ScriptBlock { aplay -l 2>/dev/null } $pipewireRaw = Invoke-Command -ScriptBlock { timeout 10 pipewire --version 2>/dev/null || echo "" } $wireplumberRaw = Invoke-Command -ScriptBlock { timeout 10 wireplumber --version 2>/dev/null || echo "" } $pactlRaw = Invoke-Command -ScriptBlock { timeout 10 pactl info 2>/dev/null || echo "" } $wpctlRaw = Invoke-Command -ScriptBlock { timeout 15 wpctl status 2>/dev/null || echo "" } } else { $audioRaw = @() $audioDevRaw = @() $pipewireRaw = @() $wireplumberRaw = @() $pactlRaw = @() $wpctlRaw = @() } # Create structured inventory object $inventory = [PSCustomObject]@{ Platform = $FPPlatform OperatingSystem = [PSCustomObject]@{ RawOutput = $osRaw -join "`n" Distributor = ($osRaw | Where-Object { $_ -like "Distributor ID:*" }) -replace "Distributor ID:\s*", "" Description = ($osRaw | Where-Object { $_ -like "Description:*" }) -replace "Description:\s*", "" Release = ($osRaw | Where-Object { $_ -like "Release:*" }) -replace "Release:\s*", "" Codename = ($osRaw | Where-Object { $_ -like "Codename:*" }) -replace "Codename:\s*", "" } ComputerSystem = [PSCustomObject]@{ #RawOutput = $csRaw -join "`n" DMIRawOutput = $dmiRaw -join "`n" Name = ($csRaw | Where-Object { $_ -like "*Static hostname:*" }) -replace ".*Static hostname:\s*", "" Manufacturer = if ($csRaw | Where-Object { $_ -like "*Hardware Vendor:*" }) { ($csRaw | Where-Object { $_ -like "*Hardware Vendor:*" }) -replace ".*Hardware Vendor:\s*", "" } elseif ($dmiRaw -and $dmiRaw.Count -ge 1) { ($dmiRaw -split "`n")[0].Trim() } else { "Unknown" } Model = if ($csRaw | Where-Object { $_ -like "*Hardware Model:*" }) { ($csRaw | Where-Object { $_ -like "*Hardware Model:*" }) -replace ".*Hardware Model:\s*", "" } elseif ($dmiRaw -and $dmiRaw.Count -ge 2) { ($dmiRaw -split "`n")[1].Trim() } else { "Unknown" } ChassisType = if ($csRaw | Where-Object { $_ -like "*Chassis:*" }) { ($csRaw | Where-Object { $_ -like "*Chassis:*" }) -replace ".*Chassis:\s*", "" -replace " .*", "" } elseif ($dmiRaw -and $dmiRaw.Count -ge 3) { # Convert DMI chassis type number to descriptive text $chassisNum = ($dmiRaw -split "`n")[2].Trim() switch ($chassisNum) { "1" { "Other" } "2" { "Unknown" } "3" { "Desktop" } "4" { "Low Profile Desktop" } "5" { "Pizza Box" } "6" { "Mini Tower" } "7" { "Tower" } "8" { "Portable" } "9" { "Laptop" } "10" { "Notebook" } "11" { "Hand Held" } "12" { "Docking Station" } "13" { "All In One" } "14" { "Sub Notebook" } "15" { "Space-saving" } "16" { "Lunch Box" } "17" { "Main Server Chassis" } "18" { "Expansion Chassis" } "19" { "Sub Chassis" } "20" { "Bus Expansion Chassis" } "21" { "Peripheral Chassis" } "22" { "RAID Chassis" } "23" { "Rack Mount Chassis" } "24" { "Sealed-case PC" } default { "Unknown ($chassisNum)" } } } else { "Unknown" } # Legacy fields for backward compatibility Hostname = ($csRaw | Where-Object { $_ -like "*Static hostname:*" }) -replace ".*Static hostname:\s*", "" Kernel = ($csRaw | Where-Object { $_ -like "*Kernel:*" }) -replace ".*Kernel:\s*", "" } BIOS = [PSCustomObject]@{ RawOutput = $biosRaw -join "`n" } Processor = [PSCustomObject]@{ #RawOutput = $procRaw -join "`n" ModelName = ($procRaw | Where-Object { $_ -like "Model name:*" }) -replace "Model name:\s*", "" Cores = ($procRaw | Where-Object { $_ -like "CPU(s):*" }) -replace "CPU\(s\):\s*", "" Architecture = ($procRaw | Where-Object { $_ -like "Architecture:*" }) -replace "Architecture:\s*", "" } Disks = [PSCustomObject]@{ #RawOutput = $disksRaw -join "`n" #FilesystemInfo = $fsRaw -join "`n" #UsageInfo = $dfRaw -join "`n" LogicalDisks = @( # Parse mounted filesystems from df output $dfRaw | Where-Object { $_ -match '^/dev/' } | ForEach-Object { if ($_ -match '^(/dev/[^\s]+)\s+([0-9]+)M\s+([0-9]+)M\s+([0-9]+)M\s+[0-9]+%\s+(.+)$') { $devicePath = $matches[1] $totalMB = [int]$matches[2] $usedMB = [int]$matches[3] $mountPoint = $matches[5] # Extract device name from path $deviceName = $devicePath -replace '^/dev/', '' # Get filesystem type from lsblk -f output $fsType = "" $fsLine = $fsRaw | Where-Object { $_ -match "^$([regex]::Escape($deviceName))\s+([^\s]+)" } if ($fsLine) { if ($fsLine -match "^$([regex]::Escape($deviceName))\s+([^\s]+)") { $fsType = $matches[2] } } # Get label if available $label = "" $labelLine = $fsRaw | Where-Object { $_ -match "^$([regex]::Escape($deviceName))\s+[^\s]+\s+[^\s]*\s+([^\s]+)" } if ($labelLine) { if ($labelLine -match "^$([regex]::Escape($deviceName))\s+[^\s]+\s+[^\s]*\s+([^\s]+)") { $label = $matches[2] } } [PSCustomObject]@{ Name = if ($label -and $label -ne "") { $label } else { $deviceName } Id = $devicePath FilesystemType = $fsType SizeMB = $totalMB UsedMB = $usedMB AvailableMB = $totalMB - $usedMB UsedPercent = [math]::Round(($usedMB / $totalMB) * 100, 1) MountPoint = $mountPoint } } } ) PhysicalDisks = @( # Parse physical disk information $physicalRaw | Where-Object { $_ -match '^\w+' -and $_ -notmatch '^NAME' } | ForEach-Object { if ($_ -match '^([^\s]+)\s+([^\s]+)\s+([01])\s+(.+)$') { $diskName = $matches[1] $diskSize = $matches[2] $isRotational = $matches[3] -eq "1" $diskType = $matches[4] # Get hardware details from hwRaw $model = "" $vendor = "" $serial = "" # Find the hardware info section for this disk $currentDisk = $false $hwRaw | ForEach-Object { if ($_ -match "^=== $([regex]::Escape($diskName)) ===") { $currentDisk = $true } elseif ($_ -match "^=== .+ ===") { $currentDisk = $false } elseif ($currentDisk) { if ($_ -match '^Model:\s*(.+)') { $model = $matches[1].Trim() } elseif ($_ -match '^Vendor:\s*(.+)') { $vendor = $matches[1].Trim() } elseif ($_ -match '^Serial:\s*(.+)') { $serial = $matches[1].Trim() } } } # Determine Make (prefer vendor, fallback to extracted from model) $make = $vendor if (-not $make -and $model) { if ($model -match '^(WD|WESTERN_DIGITAL|Samsung|Intel|Crucial|Kingston|SanDisk|Seagate|Toshiba|Hitachi)') { $make = $matches[1] } } # Determine disk type based on rotation and name $type = if ($diskName -match "^nvme") { "NVMe SSD" } elseif ($isRotational) { "HDD" } else { "SSD" } [PSCustomObject]@{ Name = $diskName Make = $make Model = $model PartNumber = $model # Often model serves as part number SerialNumber = $serial Size = $diskSize Type = $type IsRotational = $isRotational } } } ) } NetworkInterfaces = [PSCustomObject]@{ #RawOutput = $nicRaw -join "`n" #RouteInfo = $routeRaw -join "`n" #DNSInfo = $dnsRaw -join "`n" Interfaces = @( # Parse network interfaces from ip addr output $currentInterface = $null $interfaces = @() $nicRaw | ForEach-Object { if ($_ -match '^(\d+):\s+([^:]+):\s+<([^>]+)>.*mtu\s+(\d+).*state\s+(\w+)') { # New interface line if ($currentInterface) { $interfaces += $currentInterface } $currentInterface = [PSCustomObject]@{ Index = [int]$matches[1] Name = $matches[2] Flags = $matches[3] -split ',' MTU = [int]$matches[4] State = $matches[5] Type = "" IPv4Addresses = @() IPv6Addresses = @() Description = "" Gateway = "" Subnet = "" } } elseif ($_ -match '^\s+link/(\w+)') { # Link type line if ($currentInterface) { $currentInterface.Type = $matches[1] } } elseif ($_ -match '^\s+inet\s+([^/]+)/(\d+).*scope\s+(\w+)') { # IPv4 address line if ($currentInterface) { $currentInterface.IPv4Addresses += [PSCustomObject]@{ Address = $matches[1] PrefixLength = [int]$matches[2] Scope = $matches[3] } } } elseif ($_ -match '^\s+inet6\s+([^/]+)/(\d+).*scope\s+(\w+)') { # IPv6 address line if ($currentInterface) { $currentInterface.IPv6Addresses += [PSCustomObject]@{ Address = $matches[1] PrefixLength = [int]$matches[2] Scope = $matches[3] } } } } # Add the last interface if ($currentInterface) { $interfaces += $currentInterface } # Add routing and gateway information $routeRaw | ForEach-Object { if ($_ -match 'default via ([^\s]+) dev ([^\s]+)') { # Default gateway $gateway = $matches[1] $device = $matches[2] $interface = $interfaces | Where-Object { $_.Name -eq $device } if ($interface) { $interface.Gateway = $gateway } } elseif ($_ -match '([^\s]+/\d+) dev ([^\s]+).*scope link') { # Subnet information $subnet = $matches[1] $device = $matches[2] $interface = $interfaces | Where-Object { $_.Name -eq $device } if ($interface) { $interface.Subnet = $subnet } } } # Add DNS information $dnsServers = @($dnsRaw | Where-Object { $_ -match '^nameserver\s+(.+)' } | ForEach-Object { $matches[1] }) # Set description based on interface type $interfaces | ForEach-Object { if ($_.Name -eq "lo") { $_.Description = "Loopback Interface" } elseif ($_.Name -like "wl*") { $_.Description = "Wireless Network Interface" } elseif ($_.Name -like "en*" -or $_.Name -like "eth*") { $_.Description = "Ethernet Network Interface" } else { $_.Description = "Network Interface" } } $interfaces ) DNSServers = @($dnsRaw | Where-Object { $_ -match '^nameserver\s+(.+)' } | ForEach-Object { $matches[1] }) } InstalledApplications = [PSCustomObject]@{ RawOutput = $appsRaw -join "`n" PackageCount = ($appsRaw | Where-Object { $_ -match '^ii\s+' }).Count Applications = @( ($appsRaw | Where-Object { $_ -match '^ii\s+' } | ForEach-Object { # Parse dpkg output format: ii name version architecture description if ($_ -match '^ii\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.+)$') { [PSCustomObject]@{ Name = $matches[1] Version = $matches[2] Architecture = $matches[3] Description = $matches[4] } } }) ) } Video = @( # Parse video cards and extract Type, Manufacturer, Model, Firmware Version $videoRaw | ForEach-Object { if ($_ -match '^([^\s]+)\s+(.+?):\s*(.+)$') { $pciId = $matches[1] $deviceType = $matches[2] $description = $matches[3] # Extract manufacturer and model from description $manufacturer = "" $model = $description if ($description -match '^(Intel|NVIDIA|AMD|ATI)\s+(.+)$') { $manufacturer = $matches[1] $model = $matches[2] } elseif ($description -match '^(.+?)\s+(Corporation|Corp\.?)\s+(.+)$') { $manufacturer = "$($matches[1]) $($matches[2])" $model = $matches[3] } # Get firmware version based on manufacturer $firmwareVersion = "Unknown" if ($manufacturer -eq "NVIDIA" -and $nvidiaRaw) { # Parse NVIDIA firmware info $nvidiaLines = $nvidiaRaw -split "`n" foreach ($line in $nvidiaLines) { if ($line -match '^([^,]+),\s*([^,]+),\s*(.+)$') { $driverVer = $matches[1].Trim() $vbiosVer = $matches[2].Trim() $gpuName = $matches[3].Trim() if ($gpuName -like "*$model*" -or $model -like "*$gpuName*") { $firmwareVersion = "Driver: $driverVer, VBIOS: $vbiosVer" break } } } } elseif ($manufacturer -eq "Intel" -and $videoDetailRaw) { # Get Intel driver version from lspci detailed output $videoDetailLines = $videoDetailRaw -split "`n" foreach ($line in $videoDetailLines) { if ($line -match "$([regex]::Escape($pciId)).*$([regex]::Escape($description))") { # Found matching device, look for driver info in next few lines $startIndex = [array]::IndexOf($videoDetailLines, $line) for ($i = $startIndex; $i -lt [math]::Min($startIndex + 10, $videoDetailLines.Count); $i++) { if ($videoDetailLines[$i] -match "Kernel driver in use:\s*(.+)") { $firmwareVersion = "Driver: $($matches[1])" break } } break } } } # Determine type based on device type and manufacturer $type = if ($deviceType -like "*VGA*") { if ($manufacturer -eq "NVIDIA") { "Discrete Graphics Card" } elseif ($manufacturer -eq "Intel") { "Integrated Graphics" } elseif ($manufacturer -eq "AMD" -or $manufacturer -eq "ATI") { "Graphics Card" } else { "Graphics Controller" } } elseif ($deviceType -like "*3D*") { "3D Graphics Controller" } else { "Display Controller" } [PSCustomObject]@{ Type = $type Manufacturer = $manufacturer Model = $model FirmwareVersion = $firmwareVersion } } } ) Audio = @( # Parse audio controllers and extract Type, Manufacturer, Model, Firmware Version $audioRaw | ForEach-Object { if ($_ -match '^([^\s]+)\s+(.+?):\s*(.+)$') { $pciId = $matches[1] $deviceType = $matches[2] $description = $matches[3] # Extract manufacturer and model from description $manufacturer = "" $model = $description if ($description -match '^(Intel|NVIDIA|AMD|ATI|Realtek|Creative|VIA|C-Media|ESS|Ensoniq)\s+(.+)$') { $manufacturer = $matches[1] $model = $matches[2] } elseif ($description -match '^(.+?)\s+(Corporation|Corp\.?|Technology|Tech)\s+(.+)$') { $manufacturer = "$($matches[1]) $($matches[2])" $model = $matches[3] } # Determine firmware/driver version (basic kernel driver info) $firmwareVersion = "Unknown" # Could be enhanced with detailed driver version extraction if needed # Determine type based on device type and manufacturer $type = if ($deviceType -like "*Audio*") { if ($manufacturer -eq "NVIDIA") { "HDMI Audio Controller" } elseif ($manufacturer -eq "Intel") { "Integrated Audio Controller" } elseif ($manufacturer -eq "Realtek") { "Audio Codec" } elseif ($manufacturer -eq "AMD" -or $manufacturer -eq "ATI") { "HD Audio Controller" } else { "Audio Controller" } } elseif ($deviceType -like "*Sound*") { "Sound Card" } else { "Audio Device" } [PSCustomObject]@{ Type = $type Manufacturer = $manufacturer Model = $model FirmwareVersion = $firmwareVersion } } } ) AudioStack = [PSCustomObject]@{ PipewireVersion = if ($pipewireRaw -and $pipewireRaw -match 'libpipewire\s+([\d\.]+)') { $matches[1] } else { "Not Available" } WirePlumberVersion = if ($wireplumberRaw -and $wireplumberRaw -match 'libwireplumber\s+([\d\.]+)') { $matches[1] } else { "Not Available" } ServerInfo = if ($pactlRaw -and $pactlRaw -match 'Server Name:\s*(.+)') { $matches[1].Trim() } else { "Unknown" } ServerVersion = if ($pactlRaw -and $pactlRaw -match 'Server Version:\s*([\d\.]+)') { $matches[1] } else { "Unknown" } DefaultSampleFormat = if ($pactlRaw -and $pactlRaw -match 'Default Sample Specification:\s*(.+)') { $matches[1].Trim() } else { "Unknown" } DefaultSink = if ($pactlRaw -and $pactlRaw -match 'Default Sink:\s*(.+)') { $matches[1].Trim() } else { "Unknown" } DefaultSource = if ($pactlRaw -and $pactlRaw -match 'Default Source:\s*(.+)') { $matches[1].Trim() } else { "Unknown" } ActiveSinks = @( # Parse wpctl status for active sinks if ($wpctlRaw) { $wpctlLines = $wpctlRaw -split "`n" $inSinksSection = $false foreach ($line in $wpctlLines) { if ($line -match '^\s*├─ Sinks:') { $inSinksSection = $true continue } elseif ($line -match '^\s*├─|^\s*└─' -and $inSinksSection) { $inSinksSection = $false } elseif ($inSinksSection -and $line -match '^\s*[\*\s]\s*(\d+)\.\s*(.+?)\s*\[vol:\s*([\d\.]+)\]') { $isDefault = $line -match '^\s*\*' $sinkId = [int]$matches[1] $sinkName = $matches[2].Trim() $volume = [decimal]$matches[3] [PSCustomObject]@{ Id = $sinkId Name = $sinkName Volume = $volume IsDefault = $isDefault } } } } ) ActiveSources = @( # Parse wpctl status for active sources if ($wpctlRaw) { $wpctlLines = $wpctlRaw -split "`n" $inSourcesSection = $false foreach ($line in $wpctlLines) { if ($line -match '^\s*├─ Sources:') { $inSourcesSection = $true continue } elseif ($line -match '^\s*├─|^\s*└─' -and $inSourcesSection) { $inSourcesSection = $false } elseif ($inSourcesSection -and $line -match '^\s*[\*\s]\s*(\d+)\.\s*(.+?)\s*\[vol:\s*([\d\.]+)\]') { $isDefault = $line -match '^\s*\*' $sourceId = [int]$matches[1] $sourceName = $matches[2].Trim() $volume = [decimal]$matches[3] [PSCustomObject]@{ Id = $sourceId Name = $sourceName Volume = $volume IsDefault = $isDefault } } } } ) } } $os = $inventory.OperatingSystem $cs = $inventory.ComputerSystem $bios = $inventory.BIOS $proc = $inventory.Processor $disks = $inventory.Disks $nic = $inventory.NetworkInterfaces $apps = $inventory.InstalledApplications $video = $inventory.Video $audio = $inventory.Audio # Return the requested category or full inventory if ($Category -eq "All") { return $inventory } else { # Return only the requested category switch ($Category) { "OperatingSystem" { return $inventory.OperatingSystem } "ComputerSystem" { return $inventory.ComputerSystem } "BIOS" { return $inventory.BIOS } "Processor" { return $inventory.Processor } "Disks" { return $inventory.Disks } "NetworkInterfaces" { return $inventory.NetworkInterfaces } "InstalledApplications" { return $inventory.InstalledApplications } "Video" { return $inventory.Video } "Audio" { # For Audio, return both hardware and stack info return [PSCustomObject]@{ Hardware = $inventory.Audio Stack = $inventory.AudioStack } } default { return $inventory } } } } |