Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to figure out what IIS Express instance is using a port?

I want to kill a running IIS Instance programmatically that is occupying a specific port, but it seems there is no way to figure out what IIS Instance is using a specific port.

netstat.exe just shows that the process is having the PID 4, but that's the system process. "netsh http show urlacl" does not display the occupied port at all.

The IIS Express Tray program knows this somehow. When I try to start another IIS Express instance while the port is occupied I get the following error:
"Port '40000' is already being used by process 'IIS Express' (process ID '10632').

Anyone got a clue how I can get this information?

like image 763
BobJohnDoe1980 Avatar asked Nov 10 '22 06:11

BobJohnDoe1980


2 Answers

It seems like the PID is 4 (System) because the actual listening socket is under a service called http.

I looked at what iisexpresstray.exe was using to provide a list of all running IISExpress applications. Thankfully it's managed .NET code (all in iisexpresstray.dll) that's easily decompiled.

It appears to have at least three different ways of getting the port number for a process:

  1. Reading /port from the command-line arguments (unreliable as we know)
  2. Running netsh http show servicestate view=requestq and parsing the output
  3. Calling Microsoft.Web.RuntimeStatusClient.GetWorkerProcess(pid) and parsing the site URL

Unfortunately, most of the useful stuff in iisexpresstray.dll like the IisExpressHelper class is declared internal (although I imagine there're tools to generate wrappers or copy the assembly and publicize everything).

I opted to use Microsoft.Web.dll. It was in my GAC, though for some reason wasn't appearing in the list of assemblies available to add as a reference in Visual Studio, so I just copied the file out from my GAC. Once I had Microsoft.Web.dll it was just a matter of using this code:

    using (var runtimeStatusClient = new RuntimeStatusClient())
    {
      var workerProcess = runtimeStatusClient.GetWorkerProcess(process.Id);
      // Apparently an IISExpress process can run multiple sites/applications?
      var apps = workerProcess.RegisteredUrlsInfo.Select(r => r.Split('|')).Select(u => new { SiteName = u[0], PhysicalPath = u[1], Url = u[2] });
      // If we just assume one app
      return new Uri(apps.FirstOrDefault().Url).Port;
     }

You can also call RuntimeClient.GetAllWorkerProcesses to retrieve only actual worker processes.

I looked into RegisteredUrlsInfo (in Microsoft.Web.dll) as well and found that it's using two COM interfaces,

  1. IRsca2_Core (F90F62AB-EE00-4E4F-8EA6-3805B6B25CDD)
  2. IRsca2_WorkerProcess (B1341209-7F09-4ECD-AE5F-3EE40D921870)

Lastly, I read about a version of Microsoft.Web.Administration apparently being able to read IISExpress application info, but information was very scarce, and the one I found on my system wouldn't even let me instantiate ServerManager without admin privileges.

like image 54
makhdumi Avatar answered Nov 14 '22 23:11

makhdumi


Here is a C# implementation of calling netsh.exe as recommended within the answer by @makhdumi:

Usage:

static public bool TryGetCurrentProcessRegisteredHttpPort(out List<int> ports, out Exception ex)
{
    NetshInvoker netsh = new NetshInvoker();
    return netsh.TryGetHttpPortUseByProcessId(Process.GetCurrentProcess().Id, out ports, out ex);
}

Implementation:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace YourCompanyName.Server.ServerCommon.Utility
{
    /// <summary>
    /// Invoke netsh.exe and extract information from its output.
    /// Source: @crokusek, https://stackoverflow.com/questions/32196188
    ///         @GETah, https://stackoverflow.com/a/8274758/538763
    /// </summary>
    public class NetshInvoker
    {
        const string NetshHttpShowServiceStateViewRequestqArgs = "http show servicestate view=requestq";

        public NetshInvoker()
        {
        }

        /// <summary>
        /// Call netsh.exe to determine the http port number used by a given windowsPid (e.g. an IIS Express process)
        /// </summary>
        /// <param name="windowsPid">For example an IIS Express process</param>
        /// <param name="port"></param>
        /// <param name="ex"></param>
        /// <returns></returns>
        public bool TryGetHttpPortUseByProcessId(Int32 windowsPid, out List<Int32> ports, out Exception ex)
        {
            ports = null;

            try
            {
                if (!TryQueryProcessIdRegisteredUrls(out Dictionary<Int32, List<string>> pidToUrlMap, out ex))
                    return false;

                if (!pidToUrlMap.TryGetValue(windowsPid, out List<string> urls))
                {
                    throw new Exception(String.Format("Unable to locate windowsPid {0} in '{1}' output.",
                        windowsPid, "netsh " + NetshHttpShowServiceStateViewRequestqArgs));
                }

                if (!urls.Any())
                {
                    throw new Exception(String.Format("WindowsPid {0} did not reference any URLs in '{1}' output.",
                        windowsPid, "netsh " + NetshHttpShowServiceStateViewRequestqArgs));
                }

                ports = urls
                    .Select(u => new Uri(u).Port)
                    .ToList();

                return true;
            }
            catch (Exception ex_)
            {
                ex = ex_;
                return false;
            }
        }

        private bool TryQueryProcessIdRegisteredUrls(out Dictionary<Int32, List<string>> pidToUrlMap, out Exception ex)
        {
            if (!TryExecNetsh(NetshHttpShowServiceStateViewRequestqArgs, out string output, out ex))
            {
                pidToUrlMap = null;
                return false;
            }

            bool gotRequestQueueName = false;
            bool gotPidStart = false;
            int currentPid = 0;
            bool gotUrlStart = false;

            pidToUrlMap = new Dictionary<int, List<string>>();

            foreach (string line in output.Split('\n').Select(s => s.Trim()))
            {
                if (!gotRequestQueueName)
                {
                    gotRequestQueueName = line.StartsWith("Request queue name:");
                }
                else if (!gotPidStart)
                {
                    gotPidStart = line.StartsWith("Process IDs:");
                }
                else if (currentPid == 0)
                {
                    Int32.TryParse(line, out currentPid);   // just get the first Pid, ignore others.
                }
                else if (!gotUrlStart)
                {
                    gotUrlStart = line.StartsWith("Registered URLs:");
                }
                else if (line.ToLowerInvariant().StartsWith("http"))
                {
                    if (!pidToUrlMap.TryGetValue(currentPid, out List<string> urls))
                        pidToUrlMap[currentPid] = urls = new List<string>();

                    urls.Add(line);
                }
                else // reset
                {
                    gotRequestQueueName = false;
                    gotPidStart = false;
                    currentPid = 0;
                    gotUrlStart = false;
                }
            }
            return true;
        }

        private bool TryExecNetsh(string args, out string output, out Exception exception)
        {
            output = null;
            exception = null;

            try
            {
                // From @GETah, https://stackoverflow.com/a/8274758/538763

                Process p = new Process();
                p.StartInfo.FileName = "netsh.exe";
                p.StartInfo.Arguments = args;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.Start();

                output = p.StandardOutput.ReadToEnd();
                return true;
            }
            catch (Exception ex)
            {
                exception = ex;
                return false;
            }
        }
    }
}
like image 31
crokusek Avatar answered Nov 14 '22 21:11

crokusek