Closed lucdekens closed 2 years ago
I have a very good test command for that one: netstat. It exists on all platforms with differences in parameter names and command output. I made a portable Windows/macOS/Linux wrapper:
function Get-Netstat
{
$netstat = Get-Command -Name 'netstat' -ErrorAction SilentlyContinue
if (-Not $netstat) {
Write-Warning "netstat command not available"
return ,@()
}
if ($IsLinux) {
# Linux netstat:
# Proto Recv-Q Send-Q Local Address Foreign Address State
# tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
# tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
# tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
$output = netstat -an --tcp | grep LISTEN
foreach ($line in $output) {
$line = $line.Trim()
$line = $line -Split '\s+' # split line by whitespace
$Protocol = $line[0]
$LocalAddress = $line[3]
$ForeignAddress = $line[4]
$State = $line[5]
# Linux uses ':' separator for port
$LocalPort = $($LocalAddress -Split ':')[-1] -as [int]
[PSCustomObject]@{
Protocol = $Protocol
LocalAddress = $LocalAddress
LocalPort = $LocalPort
ForeignAddress = $ForeignAddress
State = $State
}
}
}
elseif ($IsMacOS) {
# macOS netstat:
# Proto Recv-Q Send-Q Local Address Foreign Address (state)
# tcp46 0 0 *.8080 *.* LISTEN
# tcp4 0 0 127.0.0.1.631 *.* LISTEN
$output = netstat -an -p tcp | grep LISTEN
foreach ($line in $output) {
$line = $line.Trim()
$line = $line -Split '\s+' # split line by whitespace
$Protocol = $line[0]
$LocalAddress = $line[3]
$ForeignAddress = $line[4]
$State = $line[5]
# macOS uses '.' separator for port, replace it with ':'
$LocalAddress = $LocalAddress -Replace '(.+)\.(\d+)', '$1:$2'
$LocalPort = $($LocalAddress -Split ':')[-1] -as [int]
[PSCustomObject]@{
Protocol = $Protocol
LocalAddress = $LocalAddress
LocalPort = $LocalPort
ForeignAddress = $ForeignAddress
State = $State
}
}
}
else { # Windows
# Windows netstat:
# Proto Local Address Foreign Address State
# TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
# TCP 192.168.25.132:4489 0.0.0.0:0 LISTENING
$output = netstat -an -p tcp | findstr LISTEN
foreach ($line in $output) {
$line = $line.Trim()
$line = $line -Split '\s+' # split line by whitespace
$Protocol = $line[0]
$LocalAddress = $line[1]
$ForeignAddress = $line[2]
$State = $line[3]
# Windows uses ':' separator for port
$LocalPort = $($LocalAddress -Split ':')[-1] -as [int]
# Normalize TCP state names according to
# https://tools.ietf.org/html/rfc793#section-3.2
if ($State -eq 'LISTENING') {
$State = 'LISTEN'
}
[PSCustomObject]@{
Protocol = $Protocol
LocalAddress = $LocalAddress
LocalPort = $LocalPort
ForeignAddress = $ForeignAddress
State = $State
}
}
}
}
function Get-LocalTcpPorts
{
$netstat = Get-Netstat
if ($netstat) {
$netstat | Select-Object -ExpandProperty 'LocalPort'
} else {
return ,@()
}
}
# Check if a TCP port is already taken:
# $(Get-LocalTcpPorts).Contains(3389)
This is an interesting enhancement and is probably out of scope for the first release. This would increase the complexity of the runtime code such as handling parameters that would need to be different for each platform. Currently, the workaround for this is to create three different proxies, one each for Linux, Windows and macOS.
As a followup, with the release of Crescendo, there is a platform property per command that allows you to specify the intended platform (Windows, Linux, MacOS). For commands that exist cross platform but utilize different parameters per platform, you will need to create a separate command definition.
Not all commands are the same, or use the same parameters, across platforms. A feature to allow specifying platform dependencies for parameters and handlers could be handy.
That would allow generating a module that is portable across platforms.