This current version is to be more compatible with PoSH 6.0 (by removing work-flow) as compared to a previous version.

$computernames='SERVER1','SERVER2'
$tests=@(
    #[PSCustomObject] @{protocol='tcp';port=22},
    #[PSCustomObject] @{protocol='tcp';port=80},

    # RPC client-server communication
    [PSCustomObject] @{protocol='tcp';port=135},

    # SMB / CIFS Protocol
    [PSCustomObject] @{protocol='udp';port=137},
    [PSCustomObject] @{protocol='udp';port=138},
    [PSCustomObject] @{protocol='tcp';port=139},
    [PSCustomObject] @{protocol='tcp';port=445},
    [PSCustomObject] @{protocol='tcp';port=5985}, 
    # Dynamic port range
    [PSCustomObject] @{protocol='tcp';port=49152},
    [PSCustomObject] @{protocol='tcp';port=65535}

    # [PSCustomObject] @{protocol='tcp';port=623},
    # [PSCustomObject] @{protocol='tcp';port=1433},      
    # [PSCustomObject] @{protocol='tcp';port=5986},
    # [PSCustomObject] @{protocol='tcp';port=8530},
    # [PSCustomObject] @{protocol='tcp';port=8531},
    # [PSCustomObject] @{protocol='tcp';port=8100},
    # [PSCustomObject] @{protocol='tcp';port=8102},
    )

function checkPort($computername=$env:computername,$port=135,$protocol='tcp',$verbose=$false){
    function includePortQry{
        if (!(Get-Command portqry.exe -ErrorAction SilentlyContinue)){
            if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
            Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
            choco install portqry -y
            if (Get-Command portqry.exe -ErrorAction SilentlyContinue){return $true}else{return $false}
        }else{
            return $true
        }
    }
    
    $portQryExists=includePortQry
    if($portQryExists){        
        $ip=[System.Net.Dns]::GetHostAddresses($computername).IPAddressToString|select-object -first 1
        $reachable=if($port -ne 135){[bool](portqry -n $ip -p $protocol -e $port|find ': LISTENING')
            }else{
                [bool](portqry -n $ip -p $protocol -e $port|find 'Total endpoints found: ')
            }       
        if($verbose){write-host "$port/$protocol : $reachable"}
        $result=[PSCustomObject]@{
            source=$env:computername
            destination=$computername
            port=$port
            protocol=$protocol
            reachable=$reachable
        }
    }else{
        write-warning 'Unable to proceed without PortQry'
        return $null
    }
    return $result
}

$results=@()
foreach ($computername in $computernames){
    write-host "Checking $computername"
    foreach($test in $tests){
        $port=$test.Port
        $protocol=$test.Protocol
        $result=checkPort $computername $port $protocol
        $results+=$result
    }
}
$results|ft -wrap
$remoteComputer=testmachine.kimconnect.com

function checkPort{
    Param(
        [string]$server,
        [int]$port=135,
        [bool]$verbose=$true
        )
 
        function importPortQry{
            if (!(Get-Command portqry.exe -ErrorAction SilentlyContinue)){
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                choco install portqry -y
                if (Get-Command portqry.exe -ErrorAction SilentlyContinue){return $true}else{return $false}
            }else{
                return $true
            }
        }
         function Check-NetConnection($server, $port) {
            $session = New-Object System.Net.Sockets.TcpClient;
            $result=$false
            try {
                $session.Connect($server, $port);
                $result=$true;
            } catch {
                $result=$false;
            } finally {
                $session.Close();
                
            }
            return $result;
        }        

    function testPortArray {
        # Casting parameters is better than passing arguments as that allows for switches that can be arranged in any order
        param ([string]$targetServer,[array]$arrPorts,$verbose=$false)
        $source = $env:computername
        $destination=$targetServer.toUpper()
        $portsCount=$arrPorts.count
        $portsSuccess=0

        foreach ($port in $arrPorts){        
            $testResult = Check-NetConnection -server $targetServer -port $port;
            If ($testResult){
                if($verbose){Write-Output "$port`: reachable"}
                $portsSuccess++
                }
            Else{
                if($verbose){Write-Output "$port`: unreachable"}
                }
        }

        if($portsCount -eq $portsSuccess){
            return $true
        }else{
            return $false
        }

    }
     
    if($verbose){write-host "Checking for existence of PortQry.exe..."}
    do {
        $portQryExists=Get-Command "PortQry.exe" -ErrorAction SilentlyContinue
        if(!$portQryExists){
            importPortQry;
            sleep 1;
        }
    }while(!$portQryExists)

    $portReachable=Check-NetConnection -server $server -port $port     
    if ($portReachable){
        if($verbose){write-host "$env:computername is able to reach $server on the primary port of $port."}
        $strInvokeCommand = "PortQry.exe -e $port -n $server";
        $arrQuryResult = Invoke-Expression $strInvokeCommand;
     
        $arrPorts = @();    #Initiates and clears the collection of ports array
        ForEach ($strResult in $arrQuryResult)
        {
          If ($strResult.Contains("ip_tcp"))
          {
          $arrSplt = $strResult.Split("[");
          $strPort = $arrSplt[1];
          $strPort = $strPort.Replace("]","");
          $arrPorts += $strPort;
          }
        }
 
        #  Remove duplicate port records from within the array
        $arrPorts = $arrPorts | select -uniq
        if ($arrPorts){            
            if($verbose){write-host "These are the detected dynamic ephemeral ports being used:`r`n$arrPorts"  }      
            $result=testPortArray -targetServer $server -arrPorts $arrPorts #  Pass the ports array into the workflow as a parameter
            return $result
        }else{
            if($verbose){write-host "Ephemeral ports were NOT detected.`r`n"}
                return $portReachable
                }
    }else{
        if($verbose){write-host "$env:computername is NOT able to reach $server on port: $port`r`n"}
        return $false
        }
}

checkPort -server $remoteComputer -port 135
function importPortQry{
    if (Get-Command portqry.exe -ErrorAction SilentlyContinue){
        if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
        Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
        choco install portqry -y | out-null
        if (Get-Command portqry.exe -ErrorAction SilentlyContinue){return $true}else{return $false}
        }else{
        return $false;
        }
}

function Check-NetConnection($server, $port) {
    $session = New-Object System.Net.Sockets.TcpClient;
    $result=$false
    try {
        $session.Connect($server, $port);
        $result=$true;
    } catch {
        $result=$false;
    } finally {
        $session.Close();
       
    }
    return $result;
}

function testPort{
    Param(
        [string]$server,
        [int]$port=135
        )
    
    # using a workflow to test each ports in the array
    workflow testPortArray {
        # Casting parameters is better than passing arguments as that allows for switches that can be arranged in any order
        param ([string]$targetServer,[array]$arrPorts)
        $source = $env:computername
        $destination=$targetServer.toUpper()
                      
        # Test all ports using multi-threaded sessions to save time      
        foreach -parallel ($port in $arrPorts){        
            $testResult = Check-NetConnection -server $targetServer -port $port;
            If ($testResult){
                Write-Output "$port`: reachable"
                }
            Else{
                Write-Output "$port`: unreachable"
                }
        }
    }
    
    #  Check for existence of PortQry.exe
    $portQryExists=Get-Command "PortQry.exe" -ErrorAction SilentlyContinue
    while (!($portQryExists)){
        importPortQry;
        sleep 1;
        $portQryExists #refreshes the Get-Command query
        }
    $reachable=Check-NetConnection -server $server -port $port
    
    if ($reachable){
        write-host "$env:computername is able to reach $server on the primary port of $port.`r`n"
        $strInvokeCommand = "PortQry.exe -e $port -n $server";
        $arrQuryResult = Invoke-Expression $strInvokeCommand;
    
        $arrPorts = @();    #Initiates and clears the collection of ports array
        ForEach ($strResult in $arrQuryResult)
        {
          If ($strResult.Contains("ip_tcp"))
          {
          $arrSplt = $strResult.Split("[");
          $strPort = $arrSplt[1];
          $strPort = $strPort.Replace("]","");
          $arrPorts += $strPort;
          }
        }

        #  Remove duplicate port records from within the array
        $arrPorts = $arrPorts | select -uniq
        if ($arrPorts){            
            write-host "These are the detected dynamic ephemeral ports being used:`r`n$arrPorts"        
            testPortArray -targetServer $server -arrPorts $arrPorts #  Pass the ports array into the workflow as a parameter
            }else{
                write-host "Ephemeral ports were NOT detected.`r`n"
                }

        }else{
            write-host "$env:computername is NOT able to reach $server on port: $port`r`n"
            }
}

testPort -server honeypot.kimconnect.com -port 135

<# Sample Output:
JUMPBOX is able to reach HONEYPOT on the primary port of 135.

These are the detected dynamic ephemeral ports being used:
49664 55321 55281 49672 49670 49669 49668 49667 49673 49666 49665
49665: reachable
49667: reachable
49666: reachable
49673: reachable
49669: reachable
49668: reachable
49670: reachable
55281: reachable
49672: reachable
55321: reachable
49664: reachable
#>
In order for RPC to work at the remote server (SERVER9000), these checks must pass:

1. The display name "Remote Procedure Call (RPC)" must be running. This also has the service name of "RpcSs" and its path to execute is "C:\Windows\system32\svchost.exe -k rpcss". Its dependencies are DCOM and RPC Endpoint Mapper.
2. Inbound port TCP-135 must be allowed (in Windows firewall, endpoint firewall, and network firewalls)
3. Outbound random ports ranging from 1022-5000 (Windows 2003) and 49152-65535 (Windows 2008 and newer) must also be permitted
4. Inbound port TCP-445 for SMB (RPC dependency) must be open

Here are some tools to test:
1. Systernals:
portqry -n SERVER9000 -e 135
portqry -n SERVER9000 -e 445

2. PowerShell v3 or higher
Test-NetConnection -ComputerName $server -port 135 #This only tests 1 item

3. PowerShell older versions
# Note: The proper function is to issue Test-Connection or Test-NetConnection commands against the target server, retrieve the target's responded dynamic ports, re-test those ports, etc. The Test-Path command is a short cut to validate whether SMB is functional and the RPC's dependency (the admin$ share) is present.
$servers="SERVER9000"
$servers | foreach {
$testRPC=Test-Path "\\$_\admin$"
"$_` RPC Accessible: $testRPC"
}

4. Telnet. Must manually test dynamic ports.
telnet SERVER9000:135


Peripheral Information:

Administrators can configure RPC to use a narrow list of dynamic port range with these commands:
netsh int ipv4 set dynamicport tcp start=10000 num=10200
netsh int ipv4 set dynamicport udp start=10000 num=10200
netsh int ipv6 set dynamicport tcp start=10000 num=10200
netsh int ipv6 set dynamicport udp start=10000 num=10200
or
reg add HKLM\SOFTWARE\Microsoft\Rpc\Internet /v Ports /t REG_MULTI_SZ /f /d 10000-10200
reg add HKLM\SOFTWARE\Microsoft\Rpc\Internet /v PortsInternetAvailable /t REG_SZ /f /d Y
reg add HKLM\SOFTWARE\Microsoft\Rpc\Internet /v UseInternetPorts /t REG_SZ /f /d Y
<# Test-RPC-Service-Ports.ps1
This PowerShell script will test the connectivity of inbound default port 135 to the Destination server as well as the outbound dynamic ports being used by that specific server for RPC communication.
There are 3 functions and 1 workflow nested inside the 3rd function
1. checkInternetAccess: checks if proxy is configured; if so, ensure that the current PS session uses the correct proxy server. Afterward, try to test connectivity to download.microsoft.com.
2. importPortQry: if PortQry.exe doesn't exist in the system, install it.
3. testRPC (with testPort workflow sub-function nested inside)
#>

function checkInternetAccess{
# Change this value to reflect your proxy node
$proxy="http://proxy:80";

# Check Proxy and configure when necessary
$proxyEnabled=(Get-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings").ProxyEnable
if ($proxyEnabled){
#"Proxy Enabled: $proxyEnabled"
$PSDefaultParameterValues = @{"*:Proxy"="$proxy";}
#"Setting proxy toward: $proxy"
}
$externalNetConnection=Test-NetConnection -Computername download.microsoft.com -Port 443 -InformationLevel Quiet
if ($externalNetConnection){return $true;}
else {return $false;}
}

function importPortQry{
# This function is currently not good enough because I have not yet figured out how to extract PortQryV2.exe with pure command-line, no GUI, and no confirmation

# Change these values to reflect your desired downloads
[string]$fileSource = "https://download.microsoft.com/download/0/d/9/0d9d81cf-4ef2-4aa5-8cea-95a935ee09c9/PortQryV2.exe";
$fileName="PortQry.exe"
[string]$saveAs = "C:\Temp\$fileName";
$destination="C:\WINDOWS\System32\SysInternals\";

$fileExists=Test-Path "$destination$fileName" -PathType Leaf
if (!($fileExists)){
if (!(checkInternetAccess)){
"No Internet access detected as required to download PortQry.exe. Program will now terminate.";
"Please manually copy that file into the 'C:\Windows\System32' location and re-run this program.";
exit;
}

# Check for the BitsTransfer module and import when necessary
if(!(Get-Module BitsTransfer)){
"Importing BitsTransfer Module...";
Import-Module BitsTransfer;
sleep 10;
}

$download=Start-BitsTransfer -Source $fileSource -Destination $saveAs -Asynchronous;

While( ($download.JobState.ToString() -eq 'Transferring') -or ($download.JobState.ToString() -eq 'Connecting') )
{
$percent = [int](($download.BytesTransferred*100) / $download.BytesTotal)
Write-Progress -Activity "Downloading..." -CurrentOperation "$percent`% complete"
}
Complete-BitsTransfer -BitsJob $download

# Run the Install
C:\Temp\PortQry.exe /Q /T:C:\Temp

# Secured systems will not allow scripted extraction of files. Thus this manual workaround is used.
"Please click on the 'Unzip' button with this default location C:\PortQryV2`nPlease perform this extraction or this process will repeats until those files exist. Ctrl C to stop the loop."
Pause;

}
else{"portqry.exe already exists at $saveAs.";}

$fileExtracted="C:\PortQryV2\PortQry.exe"
if ($fileExtracted){
cp C:\PortQryV2\PortQry.exe C:\WINDOWS\System32\SysInternals;
}
else {
# recurse function
"$fileExtracted is not found. Function will now repeat the download and extraction process to ensure existence of the extracted file."
importPortQry;
}
return $fileExists
}

function testRPC{
Param([string]$server)

# Default RPC port
$rpcPort = "135"

# using a workflow to test each ports in the array
workflow testPort {
# Casting parameters is better than passing arguments as that allows for switches that can be arranged in any order
param ([string[]]$RPCServer,[string]$initPort,[array]$arrRPCPorts)
$source = (hostname).toUpper()
$destination=$RPCServer.toUpper()
"`This server '$source' is able to reach the destination server '$destination' on the primary port of $initPort.`n"
"Testing RPC Dynamic Ports on $RPCServer`:`n----------------------------------------------------"

# Test all ports using multi-threaded sessions to save time
foreach -parallel ($RPCPort in $arrRPCPorts){
#Ah, the magic of inline-scripting
$testResult = InlineScript {Test-NetConnection -ComputerName $Using:RPCServer -port $Using:RPCPort -InformationLevel Quiet;}
If ($testResult){
Write-Output "$RPCPort`: reachable"
}
Else{
Write-Output "$RPCPort`: unreachable"
}
}#End foreach
} #End testPort

# Check for existence of PortQry.exe
$portQryExists=Get-Command "PortQry.exe" -ErrorAction SilentlyContinue
while (!($portQryExists)){
importPortQry;
sleep 1;
$portQryExists #refreshes the Get-Command query
}

"PortQry function is avalailable in this system. Program now proceeds using that method...`n";
$strInvokeCommand = "PortQry.exe -e $rpcPort -n $server";
$arrQuryResult = Invoke-Expression $strInvokeCommand;

$arrPorts = @(); #Initiates and clears the collection of ports array
ForEach ($strResult in $arrQuryResult)
{
If ($strResult.Contains("ip_tcp"))
{
$arrSplt = $strResult.Split("[");
$strPort = $arrSplt[1];
$strPort = $strPort.Replace("]","");
$arrPorts += $strPort;
}
}

# Remove duplicate port records from within the array
$arrPorts = $arrPorts | select -uniq

# Pass the ports array into the workflow as a parameter
testPort -RPCServer $server -initPort $rpcPort -arrRPCPorts $arrPorts
}

$remoteServer = Read-Host -Prompt 'Input the remote server name to test'
testRPC $remoteServer
Output...

PS C:\> C:\Users\kimconnect\Desktop\check-rpc.ps1
PortQry function is avalailable in this system. Program may proceed...

Testing RPC Dynamic Ports on SERVER01.KIMCONNECT.COM:
----------------------------------------------------
5722: reachable
49159: reachable
49234: reachable
49155: reachable
49242: reachable
49240: reachable
49153: reachable
49154: reachable
49152: reachable