Public/Get-KritToolInventory.ps1
|
function Get-KritToolInventoryDefaultPaths { <# .SYNOPSIS Returns the canonical per-OS list of standard tool-search paths (LSB + FHS for Linux, Apple-recommended for macOS, Windows app-install conventions for Windows). Used by Get-KritToolInventory. #> [CmdletBinding()] [OutputType([string[]])] param([Parameter(Mandatory)] [string] $Family) switch ($Family) { 'Windows' { $paths = @( "$env:WINDIR\System32", "$env:WINDIR", "$env:ProgramFiles", ${env:ProgramFiles(x86)}, "$env:LOCALAPPDATA\Microsoft\WindowsApps", "$env:LOCALAPPDATA\Programs", "$env:USERPROFILE\.dotnet\tools", 'C:\ProgramData\chocolatey\bin', "$env:USERPROFILE\scoop\shims", "$env:LOCALAPPDATA\Microsoft\WinGet\Links" ) } 'macOS' { $paths = @('/usr/local/bin','/opt/homebrew/bin','/opt/local/bin','/usr/bin','/bin','/usr/sbin','/sbin','/Library/Apple/usr/bin') $extra = "$HOME/.local/bin" if (Test-Path -LiteralPath $extra) { $paths += $extra } } 'Linux' { # FHS / LSB standard paths first, then snap / flatpak / language tool dirs $paths = @('/usr/local/sbin','/usr/local/bin','/usr/sbin','/usr/bin','/sbin','/bin','/opt','/snap/bin','/var/lib/flatpak/exports/bin') $extra = "$HOME/.local/bin" if (Test-Path -LiteralPath $extra) { $paths += $extra } } default { $paths = @() } } # De-dup + filter to existing paths $seen = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $out = @() foreach ($p in $paths) { if ([string]::IsNullOrWhiteSpace($p)) { continue } if ($seen.Add($p) -and (Test-Path -LiteralPath $p)) { $out += $p } } return $out } function Find-KritTool { <# .SYNOPSIS Finds a tool by name across the OS-standard search paths. .DESCRIPTION Returns every matching executable file (so you can spot duplicates across PATH entries). Honours Windows .exe/.cmd/.bat/.ps1 extension list. .EXAMPLE Find-KritTool -Name git #> [CmdletBinding()] [OutputType([pscustomobject[]])] param( [Parameter(Mandatory)] [string] $Name, [string[]] $ExtraPath ) $plat = Get-KritPlatform $paths = Get-KritToolInventoryDefaultPaths -Family $plat.Family if ($ExtraPath) { $paths += $ExtraPath | Where-Object { Test-Path -LiteralPath $_ } } $candExts = if ($plat.Family -eq 'Windows') { @('','.exe','.cmd','.bat','.ps1','.com') } else { @('') } $hits = [System.Collections.Generic.List[pscustomobject]]::new() foreach ($p in $paths) { foreach ($e in $candExts) { $full = Join-Path $p ($Name + $e) if (Test-Path -LiteralPath $full -PathType Leaf) { $fi = Get-Item -LiteralPath $full $hits.Add([pscustomobject]@{ Name = $Name Path = $full Directory = $p Extension = $e Size = $fi.Length LastModified = $fi.LastWriteTime IsExecutable = $true }) } } } $hits } function Test-KritToolPresent { [CmdletBinding()] [OutputType([bool])] param([Parameter(Mandatory)] [string] $Name, [string[]] $ExtraPath) @(Find-KritTool -Name $Name -ExtraPath $ExtraPath).Count -gt 0 } function Get-KritToolInventory { <# .SYNOPSIS FHS/LSB-aware multi-OS tool inventory. Reports presence + first-found path + duplicate locations for a configurable tool list, OR for the full Kritical-canonical list when none is supplied. .DESCRIPTION On first run with no -Tool list, scans for the Kritical canonical tool set: shells, package managers, archive tools, programming runtimes, security tools, container/cloud CLIs, SSH/git, etc. .EXAMPLE Get-KritToolInventory | Where-Object { $_.Present } | Format-Table Name, FirstPath .EXAMPLE Get-KritToolInventory -Tool git, kubectl, terraform -IncludeDuplicates .EXAMPLE # JSON for piping into a dashboard Get-KritToolInventory | ConvertTo-Json -Depth 5 .NOTES Author: Joshua Finley - Kritical Pty Ltd #> [CmdletBinding()] [OutputType([pscustomobject[]])] param( [string[]] $Tool, [string[]] $ExtraPath, [switch] $IncludeDuplicates ) if (-not $Tool -or $Tool.Count -eq 0) { $Tool = @( # Shells / interpreters 'pwsh','powershell','bash','zsh','sh','fish','nu', # Source control 'git','gh','svn','hg', # Build / package 'make','cmake','ninja','dotnet','msbuild','gcc','clang', # Runtimes 'node','npm','pnpm','yarn','deno','bun','python','python3','py','pip','pip3','ruby','gem','perl','php','go','rustc','cargo','java','mvn','gradle', # Containers / cloud 'docker','podman','buildah','kubectl','helm','minikube','k3d','terraform','tofu','ansible','az','aws','gcloud','oc','crictl', # SSH / net 'ssh','sshd','scp','curl','wget','rsync','nc','nmap','traceroute','dig','nslookup','ping','tcpdump','iperf3', # Editors 'code','code-insiders','vim','nvim','emacs','nano','micro', # Archive / encryption '7z','tar','zip','unzip','gpg','openssl','age', # Security / hardening 'hardeningkitty','lgpo','sigcheck','sigcheck64','autoruns','procmon','procexp','accesschk', # JSON/YAML/data 'jq','yq','xmlstarlet','xq', # System 'systemctl','journalctl','service','sc','wmic','reg','wevtutil','auditpol','secedit','dism','sfc', # File systems 'lsblk','blkid','df','du','ls','dir', # Package mgrs 'apt','apt-get','dnf','yum','zypper','pacman','apk','brew','port','winget','choco','scoop' ) } $plat = Get-KritPlatform $paths = Get-KritToolInventoryDefaultPaths -Family $plat.Family if ($ExtraPath) { $paths += $ExtraPath | Where-Object { Test-Path -LiteralPath $_ } } $rows = [System.Collections.Generic.List[pscustomobject]]::new() foreach ($t in $Tool) { $hits = @(Find-KritTool -Name $t -ExtraPath $ExtraPath) $row = [pscustomobject]@{ Name = $t Present = ($hits.Count -gt 0) FirstPath = if ($hits.Count -gt 0) { $hits[0].Path } else { $null } HitCount = $hits.Count AllPaths = if ($IncludeDuplicates) { @($hits | ForEach-Object { $_.Path }) } else { $null } } $rows.Add($row) } return @($rows) } |