PowerShell / Crescendo

a module for wrapping native applications in a PowerShell function and module
MIT License
399 stars 37 forks source link

Provide option to specify platform dependencies #37

Closed lucdekens closed 2 years ago

lucdekens commented 3 years ago

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.

awakecoding commented 3 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)
theJasonHelmick commented 3 years ago

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.

theJasonHelmick commented 2 years ago

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.