Testing RPC ports with PowerShell (and yes, it’s as much fun as it sounds!) New and Improved!!

Dr Scripto

Summary: Using PowerShell to identify RPC ports in use by capturing content from PowerShell

We’d like to introduce you today to one of our newest bloggers!  It’s a froopingly awesome friend of ours, Joel Vickery, PFE.  (did I mention Dr. Scripto is a big fan of books written by Douglas Adams?….oops!) Take it away Joel!

Thanks Doc!  So hey everybody! What do you do when you have to troubleshoot the dreaded “RPC Unavailable” error 1722, which rears its ugly head anywhere from Active Directory replication to Configuration Manager Distribution Point installations, and many other places in between? We have the answer for you!

Updated 10/31/2021: When I originally created this script many years back, it bugged me that I had a dependency on a tool that had to be installed on the system to help with the RPC Endpoint mapper. Things like that stick with me (for years…it’s a curse!) but I finally found a solution to this dilemma in a script published by another Microsoft Employee, Ryan Ries way back in 2014. I was able to make some changes to his script, which utilizes native RPC calls and allows this all to work without PortQry (no offense, still love Sysinternals!). The rest of the post below will be updated to reflect the changes and many thanks to Ryan for solving this issue. Hopefully it helps when you need to troubleshoot RPC and are dealing with admins that start developing new twitches and ticks when you want to install something on their systems to troubleshoot…no longer an issue after today!

To help prevent this from becoming a blame-pointing cage match between the System Admins and the Network Folks (we all know the Sys Admins would win anyway), I’m writing this post to help put some structure around troubleshooting this type of issue so that you have more facts to take to the conversation. “I think it’s a firewall issue” doesn’t get you very far in my experience.

RPC communication is one of the tougher firewall problems since most firewall folks want to know exactly which ports you need open. With RPC, they are usually given a range of ports from 49152 to 65535 to open on the firewall.  There are usually predefined rules on firewalls, WAN accelerators, and the various devices that traffic hops through to get to its destination.  They do not always work as planned.

To give you the simplest example I can think of, RPC sort of works like the concierge desk at a hotel.  You walk up and ask the person at the desk for the information about services at the hotel, like the gym or the swimming pool.  In our scenario, that person at the desk is RPC Endpoint Mapper on port 135 and they direct you to the services that are listening on the ephemeral ports. I’m just barely breaking the surface on RPC in this post.  If you want to get the full picture, take a look at what, in my opinion, is the best explanation about how RPC works in gory detail, written by Ned Pyle (and his dogs) at the link below: https://blogs.technet.microsoft.com/askds/2012/01/24/rpc-over-itpro/

I see a lot of administrators attempting to diagnose this network connectivity by looking up the RPC ports (135 & 49152-65535) and then attempting to connect to random ports in the ephemeral range, hopefully this post will help with isolating the ports that are truly listening on the server.

The way I normally troubleshoot this type of network connectivity is with the SysInternals PortQry.exe utility, which can be downloaded from the Microsoft website. As mentioned above, the dependency on PortQry.exe has been removed but I am leaving this information in the post since there is big value in using PortQry. To begin, run the following command to query the RPC Port Mapper on the remote machine, this will return the ports in the ephemeral range that the machine is actively listening on for RPC services:

Portqry.exe -n 169.254.0.10 -e 135

(PARTIAL OUTPUT BELOW)

Querying target system called:

169.254.0.10

Attempting to resolve IP address to a name...

IP address resolved to DC1.contoso.com

querying...

TCP port 135 (epmap service): LISTENING

Using ephemeral source port

Querying Endpoint Mapper Database...

Server's response:

UUID: d95afe70-a6d5-4259-822e-2c84da1ddb0d

ncacn_ip_tcp:169.254.0.10[49664]

UUID: 50abc2a4-574d-40b3-9d66-ee4fd5fba076

ncacn_ip_tcp:169.254.0.10[64555]

UUID: 897e2e5f-93f3-4376-9c9c-fd2277495c27 Frs2 Service

ncacn_ip_tcp:169.254.0.10[64528]

UUID: 367abb81-9844-35f1-ad32-98f038001003

ncacn_ip_tcp:169.254.0.10[64502]

UUID: c9ac6db5-82b7-4e55-ae8a-e464ed7b4277 Impl friendly name

ncacn_ip_tcp:169.254.0.10[49668]

UUID: 12345778-1234-abcd-ef00-0123456789ac

ncacn_ip_tcp:192.168.0.242[49668]

The output from this command will have an almost overwhelming amount of output.  Within this mountain of data will be just a handful of high-numbered ephemeral ports on which the server is listening.  You are looking for any lines that have “ip_tcp” in them and the ports are in brackets at the end of the line (highlighted in blue above).  This is where we will get a focused list of listening ports from the RPC server to query and validate connectivity. In the abbreviated example above, ports 49664 , 64555, 64502,and 49668 are listening.  Note that 49668 is listed twice.  There will be duplicates that you will have to filter out. Once you have the full (and de-duplicated) list put together, you can then feed that list of ports back into PORTQRY.EXE to validate that they are reachable over the network. About now, you are probably saying to yourself that’s a  lot of work!  Never fear, I didn’t do this too many times before I decided to automate it in PowerShell. (Editor.  Queue up Mighty Mouse and “Here I come to save the DAY!”)

RPCCheckv2.ps1 to the rescue!

# This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
# THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
# FITNESS FOR A PARTICULAR PURPOSE.
#
#
# Script queries port 135 to get the listening ephemeral ports from the remote server
#  and verifies that they are reachable.
#
#
#  Usage:  RPCCheckv2 -Computer YourServerNameHere
#
#


<#PSScriptInfo

.VERSION 1.1

.GUID 2fda37e6-9d6d-4cff-b0ae-9f924ddf4afb

.AUTHOR Ryan Ries

.COMPANYNAME 

.COPYRIGHT 

.TAGS 

.LICENSEURI 

.PROJECTURI 

.ICONURI 

.EXTERNALMODULEDEPENDENCIES 

.REQUIREDSCRIPTS 

.EXTERNALSCRIPTDEPENDENCIES 

.RELEASENOTES


#>

<# 

.DESCRIPTION 
 This script tests TCP network connectivity to not just the RPC Endpoint Mapper on port 135, but it also checks TCP network connectivity to each of the registered endpoints returned by querying the EPM.  I wrote this because many firewall teams have a difficult time with RPC, and they will end up allowing the Endpoint Mapper on port 135, but forget to also allow the ephemeral ports through the firewall.  This script uses localhost by default, but obviously you can specify a remote machine name or IP address to test a server across the network.  The script works by P/Invoking functions exported from rpcrt4.dll to get an enumeration of registered endpoints from the endpoint mapper, so it's not just a wrapper around portqry.exe.  

 Added dictionary for return since this script needs the port numbers returned - JV 10-31-2021
#> 

Param(
[string]$Computer
)
# Author: Ryan Ries [MSFT]
# Origianl date: 15 Feb. 2014
#Requires -Version 3
Function Test-RPC
{
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param([Parameter(ValueFromPipeline=$True)][String[]]$ComputerName = 'localhost')
    BEGIN
    {
        Set-StrictMode -Version Latest
        $PInvokeCode = @'
        using System;
        using System.Collections.Generic;
        using System.Runtime.InteropServices;



        public class Rpc
        {
            // I found this crud in RpcDce.h

            [DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
            public static extern int RpcBindingFromStringBinding(string StringBinding, out IntPtr Binding);

            [DllImport("Rpcrt4.dll")]
            public static extern int RpcBindingFree(ref IntPtr Binding);

            [DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
            public static extern int RpcMgmtEpEltInqBegin(IntPtr EpBinding,
                                                    int InquiryType, // 0x00000000 = RPC_C_EP_ALL_ELTS
                                                    int IfId,
                                                    int VersOption,
                                                    string ObjectUuid,
                                                    out IntPtr InquiryContext);

            [DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
            public static extern int RpcMgmtEpEltInqNext(IntPtr InquiryContext,
                                                    out RPC_IF_ID IfId,
                                                    out IntPtr Binding,
                                                    out Guid ObjectUuid,
                                                    out IntPtr Annotation);

            [DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
            public static extern int RpcBindingToStringBinding(IntPtr Binding, out IntPtr StringBinding);

            public struct RPC_IF_ID
            {
                public Guid Uuid;
                public ushort VersMajor;
                public ushort VersMinor;
            }


            // Returns a dictionary of <Uuid, port>
            public static Dictionary<int, string> QueryEPM(string host)
            {
                Dictionary<int, string> ports_and_uuids = new Dictionary<int, string>();
                int retCode = 0; // RPC_S_OK 
                               
                IntPtr bindingHandle = IntPtr.Zero;
                IntPtr inquiryContext = IntPtr.Zero;                
                IntPtr elementBindingHandle = IntPtr.Zero;
                RPC_IF_ID elementIfId;
                Guid elementUuid;
                IntPtr elementAnnotation;

                try
                {                    
                    retCode = RpcBindingFromStringBinding("ncacn_ip_tcp:" + host, out bindingHandle);
                    if (retCode != 0)
                        throw new Exception("RpcBindingFromStringBinding: " + retCode);

                    retCode = RpcMgmtEpEltInqBegin(bindingHandle, 0, 0, 0, string.Empty, out inquiryContext);
                    if (retCode != 0)
                        throw new Exception("RpcMgmtEpEltInqBegin: " + retCode);
                    
                    do
                    {
                        IntPtr bindString = IntPtr.Zero;
                        retCode = RpcMgmtEpEltInqNext (inquiryContext, out elementIfId, out elementBindingHandle, out elementUuid, out elementAnnotation);
                        if (retCode != 0)
                            if (retCode == 1772)
                                break;

                        retCode = RpcBindingToStringBinding(elementBindingHandle, out bindString);
                        if (retCode != 0)
                            throw new Exception("RpcBindingToStringBinding: " + retCode);
                            
                        string s = Marshal.PtrToStringAuto(bindString).Trim().ToLower();
                        if(s.StartsWith("ncacn_ip_tcp:"))
                            if (ports_and_uuids.ContainsKey(int.Parse(s.Split('[')[1].Split(']')[0])) == false) ports_and_uuids.Add(int.Parse(s.Split('[')[1].Split(']')[0]), elementIfId.Uuid.ToString());
                           
                        RpcBindingFree(ref elementBindingHandle);
                        
                    }
                    while (retCode != 1772); // RPC_X_NO_MORE_ENTRIES

                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex);
                    return ports_and_uuids;
                }
                finally
                {
                    RpcBindingFree(ref bindingHandle);
                }
                
                return ports_and_uuids;
            }
        }
'@
    }
    PROCESS
    {
 
        [Bool]$EPMOpen = $False
        [Bool]$bolResult = $False
        $Socket = New-Object Net.Sockets.TcpClient
                
        Try
        {                    
            $Socket.Connect($ComputerName, 135)
            If ($Socket.Connected)
            {
                $EPMOpen = $True
            }
            $Socket.Close()                    
        }
        Catch
        {
            $Socket.Dispose()
        }
                
        If ($EPMOpen)
        {
            Add-Type $PInvokeCode
                    
            # Dictionary <Uuid, Port>
            $RPC_ports_and_uuids = [Rpc]::QueryEPM($Computer)
            $PortDeDup = ($RPC_ports_and_uuids.Keys) | Sort-Object -Unique
            Foreach ($Port In $PortDeDup)
            {
                $Socket = New-Object Net.Sockets.TcpClient
                Try
                {
                    $Socket.Connect($Computer, $Port)
                    If ($Socket.Connected)
                    {
                        Write-Output "$Port Reachable"
                    }
                    $Socket.Close()
                }
                Catch
                {
                    Write-Output "$Port Unreachable"
                    $Socket.Dispose()
                }

            }

        }

  
    }

    END
    {

    }
}

Test-RPC -ComputerName $Computer

Some things to note regarding the script: In testing, I found that there were cases where the Windows Firewall did not have default rules outside of the system-created rules for say, Active Directory or DFS. In my case, a handful of ports fell outside of those bounds and ironically this turned into a troubleshooting session to figure that out. To get to the point…normal troubleshooting still applies, and you have to assess whether you want to make permanent rules in the firewall to allow traffic. In my case, put temporary rules in place to make sure that the blocks were removed for testing only and will now monitor the firewall logs to see if they need to be opened.

Finally At the end of all of this, our family lion of a house cat (rescued from the Humane Society by the), Roger, was very impressed…you can just see it on his face. Image 20160906 141752 2 300 215 300

Ned, in the off chance that you read this, I’m a dog person all the way, but the daughter wanted a cat.  If it helps, I asked for the “most dog-like” cat they had, and Roger doesn’t disappoint.

So that is all there is to pinging available RPC ports with PowerShell! 

Pop by next week as we delve into some amazing work with PowerShell and Parallel processes! I invite you to follow me on

Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Forum. See you tomorrow. Until then, Keep on Scripting! Your good friend, Doctor Scripto PowerShell, Doctor Scripto, Sysinternals, Joel Vickery, RPC, Ping

2 comments

Leave a comment