Iterating through the contents of the ROT (running objects table) using C#

Back in the day I had to do quite a bit of COM / .Net interop. For some reason (and I cannot remember what it was), I need to be able to enumerate the COM running objects table (ROT). I think it was to do with createboject/getobject and trying to synthesis a singleton for an exe.

The System.Runtime.InteropServices.Marshal.GetActiveObject method will return an existing instance of application registered in the Running Objects Table (ROT) by matching the prog id (or moniker) to an entry in this table.

Note, Office applications do not register themselves if another instance is already in the ROT because the moniker for itself is always the same, and cannot be distinguished. This means that you cannot attach to any instance except for the first instance. However, because Office applications also register their documents in the ROT, you can successfully attach to other instances by iterating the ROT looking for a specific document, and attaching to this document to get the Application object from this document.

The source code below contains a ROTHelper class, which among other things, demonstrates how to iterate through the ROT. Note, once you have the desired application object you will either need to use reflection or cast to the relevant COM interface to call any methods.

using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Collections;

#region TestROT Class

/// <summary>
/// Test ROT class showing how to use the ROTHelper.
/// </summary>
class TestROT
{
	/// <summary>
	/// The main entry point for the application.
	/// </summary>
	[STAThread]
	static void Main(string[] args)
	{
		// Iterate through all the objects in the ROT
		Hashtable runningObjects = ROTHelper.GetActiveObjectList(null);
		// Display the object ids
		foreach( DictionaryEntry de in runningObjects ) 
		{
			string progId = de.Key.ToString();
			if ( progId.IndexOf("{") != -1 ) 
			{
				// Convert a class id into a friendly prog Id
				progId = ROTHelper.ConvertClassIdToProgId( de.Key.ToString() ) ;
			}
			Console.WriteLine( progId );
			object getObj = ROTHelper.GetActiveObject(progId);
			if ( getObj != null ) 
			{
				Console.WriteLine( "Fetched: " + progId );
			}
			else
			{
				Console.WriteLine( "!!!!!FAILED TO fetch: " + progId );
			}
		}
		Console.ReadLine();
	}
}
#endregion TestROT Class

#region ROTHelper Class

/// <summary>
/// The COM running object table utility class.
/// </summary>
public class ROTHelper
{
	#region APIs

	[DllImport("ole32.dll")]  
	private static extern int GetRunningObjectTable(int reserved, 
		out UCOMIRunningObjectTable prot); 

	[DllImport("ole32.dll")]  
	private static extern int  CreateBindCtx(int reserved, 
		out UCOMIBindCtx ppbc);

	[DllImport("ole32.dll", PreserveSig=false)]
	private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] string progId, out Guid clsid);

	[DllImport("ole32.dll", PreserveSig=false)]
	private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] string progId, out Guid clsid);

	[DllImport("ole32.dll")]
	private static extern int ProgIDFromCLSID([In()]ref Guid clsid, [MarshalAs(UnmanagedType.LPWStr)]out string lplpszProgID);

	#endregion

	#region Public Methods
	
	/// <summary>
	/// Converts a COM class ID into a prog id.
	/// </summary>
	/// <param name="progID">The prog id to convert to a class id.</param>
	/// <returns>Returns the matching class id or the prog id if it wasn't found.</returns>
	public static string ConvertProgIdToClassId( string progID )
	{
		Guid testGuid;
		try
		{
			CLSIDFromProgIDEx(progID, out testGuid);
		}
		catch
		{
			try
			{
				CLSIDFromProgID(progID, out testGuid);
			}
			catch
			{
				return progID;
			}
		}
		return testGuid.ToString().ToUpper();
	}

	/// <summary>
	/// Converts a COM class ID into a prog id.
	/// </summary>
	/// <param name="classID">The class id to convert to a prog id.</param>
	/// <returns>Returns the matching class id or null if it wasn't found.</returns>
	public static string ConvertClassIdToProgId( string classID )
	{
		Guid testGuid = new Guid(classID.Replace("!",""));
		string progId = null;
		try
		{
			ProgIDFromCLSID(ref testGuid, out progId);
		}
		catch (Exception)
		{
			return null;
		}
		return progId;
	}

	/// <summary>
	/// Get a snapshot of the running object table (ROT).
	/// </summary>
	/// <returns>A hashtable mapping the name of the object in the ROT to the corresponding object
	/// <param name="filter">The filter to apply to the list (nullable).</param>
	/// <returns>A hashtable of the matching entries in the ROT</returns>
	public static Hashtable GetActiveObjectList(string filter)
	{
		Hashtable result = new Hashtable();

		int numFetched;
		UCOMIRunningObjectTable runningObjectTable;   
		UCOMIEnumMoniker monikerEnumerator;
		UCOMIMoniker[] monikers = new UCOMIMoniker[1];

		GetRunningObjectTable(0, out runningObjectTable);    
		runningObjectTable.EnumRunning(out monikerEnumerator);
		monikerEnumerator.Reset();          

		while (monikerEnumerator.Next(1, monikers, out numFetched) == 0)
		{     
			UCOMIBindCtx ctx;
			CreateBindCtx(0, out ctx);     
        
			string runningObjectName;
			monikers[0].GetDisplayName(ctx, null, out runningObjectName);

			object runningObjectVal;  
			runningObjectTable.GetObject( monikers[0], out runningObjectVal); 
			if ( filter == null || filter.Length == 0 || filter.IndexOf( filter ) != -1 ) 
			{
				result[ runningObjectName ] = runningObjectVal;
			}
		} 

		return result;
	}

	/// <summary>
	/// Returns an object from the ROT, given a prog Id.
	/// </summary>
	/// <param name="progId">The prog id of the object to return.</param>
	/// <returns>The requested object, or null if the object is not found.</returns>
	public static object GetActiveObject(string progId)
	{
		// Convert the prog id into a class id
		string classId = ConvertProgIdToClassId( progId );

		UCOMIRunningObjectTable prot = null;
		UCOMIEnumMoniker pMonkEnum = null;
		try
		{
    		int Fetched = 0;
			// Open the running objects table.
			GetRunningObjectTable(0, out prot);
			prot.EnumRunning(out pMonkEnum);
			pMonkEnum.Reset();
			UCOMIMoniker[] pmon = new UCOMIMoniker[1];

			// Iterate through the results
			while (pMonkEnum.Next(1, pmon, out Fetched) == 0)
			{
				UCOMIBindCtx pCtx;

				CreateBindCtx(0, out pCtx);

				string displayName;
				pmon[0].GetDisplayName(pCtx, null, out displayName);
				Marshal.ReleaseComObject(pCtx);
				if ( displayName.IndexOf( classId ) != -1 )
				{
					// Return the matching object
					object objReturnObject;
					prot.GetObject(pmon[0], out objReturnObject);
					return objReturnObject;
				}
			}
			return null;
		}
		finally
		{
			// Free resources
			if (prot != null )
				Marshal.ReleaseComObject( prot);
			if (pMonkEnum != null )
				Marshal.ReleaseComObject ( pMonkEnum );
		}
	}

	#endregion
}

#endregion

How to Ping a computer using .NET Sockets

///
/// The main entry point for the application.
///
[STAThread]

static void Main(string[] args)
{

    int timeToReplyMs = Ping("andrewbaker.ninja", 10000);
    Console.WriteLine("Time Taken(ms): " + timeToReplyMs);
}




#region Ping Method
///
/// Pings a host and returns the time taken for the host to respond (given the host name or IP address of the host to ping).
///



/// The host (machine name or IP address) to ping.
/// The ping timeout in ms (zero represents infinite).
/// Returns the time in milliseconds to receive a response from the host.

public static int Ping(string host, int timeout)

{

    //IPHostEntry ipHost = Dns.GetHostByName(host);
    IPHostEntry ipHost = Dns.GetHostEntry(host);

    // Ping the server
    return Ping(ipHost, timeout);

}




///
/// Pings a host and returns the time taken for the host to respond.
///




/// The host to ping.
/// The ping timeout in ms (zero represents infinite).
/// Returns the time in milliseconds to receive a response from the host.

public static int Ping(IPHostEntry host, int timeout)

{

    // Declare some Constant Variables
    const int SOCKET_ERROR = -1;
    const int ICMP_ECHO = 8;
    int timeTaken;



    // Declare the IPHostEntry
    if (host == null)
    {
        throw new ArgumentNullException("host", "The host to ping must be specified.");
    }

    Socket socket = null;

    try

    {
        // Initilize a Socket of the Type ICMP
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);


        // Convert to the first Ip address from the host to an endpoint
        IPEndPoint ipepServer = new IPEndPoint(_GetValidAddress(host.AddressList), 0);
        EndPoint epServer = (ipepServer);

        // Set the receiving endpoint to the client machine
        IPHostEntry localHost;

        localHost = Dns.GetHostEntry(Dns.GetHostName());
        IPEndPoint ipEndPointFrom = new IPEndPoint(_GetValidAddress(localHost.AddressList), 0);
        EndPoint EndPointFrom = (ipEndPointFrom);


        int packetSize;

        IcmpPacket packet = new IcmpPacket();

        // Construct the echo packet
        packet.Type = ICMP_ECHO; //8
        packet.SubCode = 0;
        packet.CheckSum = UInt16.Parse("0");
        packet.Identifier = UInt16.Parse("45");
        packet.SequenceNumber = UInt16.Parse("0");

        int pingDataSize = 32; // sizeof(IcmpPacket) - 8;

        packet.Data = new Byte[pingDataSize];

        // Initilize the Packet.Data
        for (int i = 0; i < pingDataSize; i++)
        {
            packet.Data[i] = (byte)'#';
        }

        // Variable to hold the total Packet size
        packetSize = pingDataSize + 8;
        Byte[] icmpPacketBuffer = new Byte[packetSize];
        int totalPacketSize;


        // Call Serialize, which counts the total number of Bytes in the Packet
        totalPacketSize = _SerializePacket(packet, icmpPacketBuffer, packetSize, pingDataSize);


        // Error in Packet Size
        if (totalPacketSize == -1)
        {
            // Throw serialization exception
            throw new ApplicationException("Error serializing packet buffer");
        }

        // Get the packet into a UInt16 array
        // Get the Half size of the Packet
        Double packetSizeDouble = Convert.ToDouble(totalPacketSize);
        Double halfPacketSizeDouble = Math.Ceiling(packetSizeDouble / 2);
        int checkSumBufferLength = Convert.ToInt32(halfPacketSizeDouble);

        // Create a Byte Array
        UInt16[] checkSumBuffer = new UInt16[checkSumBufferLength];

        // Code to initilize the Uint16 array
        int icmpHeaderBufferIndex = 0;

        for (int i = 0; i < checkSumBufferLength; i++)
        {
            checkSumBuffer[i] = BitConverter.ToUInt16(icmpPacketBuffer, icmpHeaderBufferIndex);
            icmpHeaderBufferIndex += 2;

        }

        // Call a method which will return a checksum
        UInt16 unsignedCheckSum = _CalcChecksum(checkSumBuffer, checkSumBufferLength);

        // Save the checksum to the Packet
        packet.CheckSum = unsignedCheckSum;

        // Have the checksum, serialize the packet again
        Byte[] sendbuf = new Byte[packetSize];

        // Check the packet size
        totalPacketSize = _SerializePacket(packet, sendbuf, packetSize, pingDataSize);


        if (totalPacketSize == -1)
        {
            // Throw serialization exception
            throw new ApplicationException("Error serializing packet buffer");

        }


        // Start timing
        int dwStart = Environment.TickCount;

        // Send the Packet
        if (socket.SendTo(sendbuf, packetSize, 0, epServer) == SOCKET_ERROR)
        {
            throw new ApplicationException("Failed to send packet to " + host.HostName + ".");

        }


        // Initialize the buffers. The receive buffer is the size of the
        // ICMP header plus the IP header (20 bytes)
        Byte[] arrayBytesReceived = new Byte[256];


        // Loop for checking the time of the server responding
        while (true)

        {

            int nBytes = socket.ReceiveFrom(arrayBytesReceived, 256, 0, ref EndPointFrom);
            timeTaken = Environment.TickCount - dwStart;



            if (nBytes == SOCKET_ERROR)
            {

                throw new ApplicationException("Failed to receive response to echo packet from " + host.HostName + ".");

            }

            else if (nBytes > 0)

            {
                // Received message
                // int dwStop = Environment.TickCount - dwStart;
                // Console.WriteLine("Reply from " + epServer.ToString() + " in " + dwStop + "MS :Bytes Received" + nBytes);
                break;
            }




            // Default to a one second timeout
            if (timeTaken > timeout && timeout > 0)
            {
                throw new ApplicationException("Ping to " + host.HostName + " timed out after " + timeout + " milliseconds.");
            }
        }

    }
    finally
    {
        if (socket != null)
        {
            // close the socket
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }

    }




    return timeTaken;

}

//
// Find a valid IP address
private static IPAddress _GetValidAddress(IPAddress[] addressList)
{
    for (int i = 0; i < addressList.Length; i++)
    {
        try
        {
            var address = addressList[i].Address;
        }
        catch (Exception)
        {
            // Invalid address
            continue;
        }
        // The address is valid
        return addressList[i];
    }
    return null;
}



#region Private Methods

///
/// This method get the Packet and calculates the total size
/// of the Pack by converting it to byte array
///
private static int _SerializePacket(IcmpPacket packet, Byte[] Buffer, int PacketSize, int PingData)

{

    int cbReturn;

    // serialize the struct into the array
    int Index = 0;


    Byte[] b_type = new Byte[1];
    b_type[0] = (packet.Type);

    Byte[] b_code = new Byte[1];
    b_code[0] = (packet.SubCode);

    Byte[] b_cksum = BitConverter.GetBytes(packet.CheckSum);
    Byte[] b_id = BitConverter.GetBytes(packet.Identifier);
    Byte[] b_seq = BitConverter.GetBytes(packet.SequenceNumber);


    // Console.WriteLine("Serialize type ");
    Array.Copy(b_type, 0, Buffer, Index, b_type.Length);
    Index += b_type.Length;

    // Console.WriteLine("Serialize code ");
    Array.Copy(b_code, 0, Buffer, Index, b_code.Length);
    Index += b_code.Length;

    // Console.WriteLine("Serialize cksum ");
    Array.Copy(b_cksum, 0, Buffer, Index, b_cksum.Length);
    Index += b_cksum.Length;

    // Console.WriteLine("Serialize id ");
    Array.Copy(b_id, 0, Buffer, Index, b_id.Length);
    Index += b_id.Length;

    Array.Copy(b_seq, 0, Buffer, Index, b_seq.Length);
    Index += b_seq.Length;

    // copy the data
    Array.Copy(packet.Data, 0, Buffer, Index, PingData);

    Index += PingData;

    if (Index != PacketSize/* sizeof(IcmpPacket) */)
    {
        cbReturn = -1;
        return cbReturn;
    }

    cbReturn = Index;
    return cbReturn;
}

///
/// This Method has the algorithm to make a checksum
///
private static UInt16 _CalcChecksum(UInt16[] buffer, int size)

{
    int cksum = 0;
    int counter;

    counter = 0;

    while (size > 0)
    {
        UInt16 val = buffer[counter];
        cksum += Convert.ToInt32(val);
        counter += 1;
        size -= 1;
    }

    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);

    return (UInt16)(~cksum);

}

#endregion Private Methods




#region IcmpPacket Class (Private)

///
/// Class that holds the Pack information.
///
private class IcmpPacket
{

    ///
    /// Type of message
    ///
    public Byte Type;

    ///
    /// Type of sub code
    ///
    public Byte SubCode;

    ///
    /// Complement checksum of struct
    ///
    public UInt16 CheckSum;

    ///
    /// Identifier
    ///
    public UInt16 Identifier;

    ///
    /// Sequence number
    ///
    public UInt16 SequenceNumber;

    ///
    /// The data
    ///
    public Byte[] Data;

}

#endregion IcmpPacket Class (Private)

}

endregion

High Performance Timer

Below is a sample class containing a high performance timer. Example usuage:

PerformanceTimer pf = new PerformanceTimer(true);

float timeMs = pf.GetTimeMs();

using System;
using System.Runtime.InteropServices;

///
/// This class can be used to gather accurate performance timings.
///
public class PerformanceTimer
{


[DllImport("kernel32.dll")]
internal static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);



[DllImport("kernel32.dll")]
internal static extern bool QueryPerformanceFrequency(ref long lpFrequency);



#region Private Variables

private bool _isRunning = false;
private long _startTime = 0;
private long _frequency = 0;
private float _elapsed = 0;

#endregion Private Variables



#region Constructors



///
/// Default constructor - does not start timer immediately.
///
public PerformanceTimer()
{

}



///
/// Constructor with option to start timer at same time.
///
///Whether to start timer immediately.

public PerformanceTimer(bool startTimer)

{

    if (startTimer)
    {

        Start();

    }

}

#endregion Constructors


#region Public Properties

///
/// Returns True if the timer is currently running.
///
/// To change the running state use the Start and Stop methods provided.

public bool IsRunning
{
    get { return _isRunning; }
}



#endregion Public Properties



#region Public Methods



///
/// Starts the performance timer running.
///

public void Start()

{
    QueryPerformanceFrequency(ref _frequency);
    QueryPerformanceCounter(ref _startTime);
    _elapsed = 0;
    _isRunning = true;

}



///
/// Stops the performance timer running.
///

public void Stop()

{

    // Call procedure that will set internal elapsed variable, then turn running flag off.

    GetTime();

    _isRunning = false;

}



///

/// Returns the time elapsed since "Start" was called (in seconds). If the timer

/// is not running the time elapsed between the last start and stop time is returned.

///

/// A float representing the time ellapsed (in seconds)

public float GetTime()

{

    if (_isRunning)
    {
        long endTime = 0;
        QueryPerformanceCounter(ref endTime);
        _elapsed = (float)(endTime - _startTime) / _frequency;
        return _elapsed;
    }
    else
    {
        return _elapsed;
    }
}



///
/// Returns the time elapsed since "Start" was called (in milliseconds). If the timer
/// is not running the time elapsed between the last start and stop time is returned.
///
/// A float representing the time ellapsed (in milliseconds)

public float GetTimeMs()

{
    if (_isRunning)
    {
        return GetTime() * 1000;
    }
    else
    {
        return _elapsed * 1000;

    }

}



///
/// Override ToString to display time nicely formatted.
///
/// Returns the time formatted as a string.

public override string ToString()

{
    return String.Format("{0:0000.000}s ", GetTime());
}
#endregion Public Methods

}

A lightweight alternative to Process.GetCurrentProcess().ProcessName

Unfortunately, the following (seemingly harmless code) requires administrator rights (and on Windows 2003 you will need to be member of “Performance Monitor Users” group):

System.Diagnostics.Process.GetCurrentProcess().ProcessName

In most cases this is not appropriate and certainly not in a Citrix environment. If you call it without the correct permissions you the following stack dump:

Unhandled exception in EntryPoint: System.InvalidOperationException: Couldn't get process information from remote machine. ---> System.ComponentModel.Win32Exception: Access is denied    
 at System.Diagnostics.PerformanceMonitor.GetData(String item)    
 at System.Diagnostics.PerformanceCounterLib.GetPerformanceData(String item)    
 at System.Diagnostics.PerformanceCounterLib.get_CategoryTable()    
 at System.Diagnostics.PerformanceCounterLib.GetPerformanceData(String[] categories, Int32[] categoryIndexes)    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(PerformanceCounterLib library)   
  --- End of inner exception stack trace ---    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(PerformanceCounterLib library)    
 at System.Diagnostics.NtProcessManager.GetProcessInfos(String machineName, Boolean isRemoteMachine)    
 at System.Diagnostics.ProcessManager.GetProcessInfos(String machineName)    
 at System.Diagnostics.Process.EnsureState(State state)    
 at System.Diagnostics.Process.get_ProcessName()    

Below is a light weight alternative to System.Diagnostics.Process.GetCurrentProcess().ProcessName and does not require any special permissions:

/// <summary>
/// Returns the starting process name (same as System.Diagnostics.Process.GetCurrentProcess().ProcessName),
/// but doesn't require any admin rights.
/// </summary>
/// <returns>Returns the starting process name.</returns>
public static string GetCurrentProcessNameLite()
{
	string procName = new System.IO.FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Name;
	if (procName.ToLower().IndexOf(".exe") > -1)
	{
		// Remove the ".exe" extension
		procName = procName.Substring(0, procName.Length - 4);
	}
	return procName;
}

Managing Terminal Services Sessions Remotely

I regularly administer Windows 2000 Server and Windows Server 2003 machine using Terminal Services. In W2K I use Terminal Services in Administration mode and in W2K3 I use Remote Desktop. Basically this is the same thing but it’s packaged a bit differently on the two OSes. In both situations there are only 2 users allowed for administration so occasionally both are used up and I can’t access the server. This may happen if two people are legitimately using the servers or if someone forgot to log off. Yes, I do have the settings set to automatically disconnect and/or log off a user after X minutes of inactivity but even with that there are times when I need to be able to manage this remotely.

You would think this would be as easy as connecting to the remote server by adding the Terminal Services Manager snap-in to a MMC console or remotely stopping and starting the service (in a pinch). Strangely, these obvious solutions aren’t available. Terminal Services Manager doesn’t exist as a snap-in to connect to a remote machine and because the TermService service is a core system service, you can’t even stop it on the local machine, let alone a remote one.

Fortunately there is an easy solution. Windows 2000+ (includes Windows XP and 2003) have two command-line tools called qwinsta and rwinsta that can query and reset a remote session.

For example, let’s say that I can’t gain access to a server using Terminal Services because both sessions are used up. I can use another server to check the status of the first one. As long as the logged in user has administrative rights on the non-accessible machine I would run this:

qwinsta /server:12.12.12.12

Where 12.12.12.12 is the IP address or name of the non-accessible machine.

This will display something like this:

   qwinsta /server:12.12.12.12
    SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
    console                                     0  Conn    wdcon
    rdp-tcp                                 65536  Listen  rdpwd
    rdp-tcp#470       Bob                       1  Active  rdpwd
    rdp-tcp#471       Jane                      3  Active  rdpwd 

Now I know that Bob and Jane are the two that are logged in. Since Jane left the office 20 minutes ago I know that she forgot to log off. I don’t know where Bob is but I only need one session so I’ll ignore him for now.

To disconnect Jane’s session I would type this:

rwinsta /server:12.12.12.12 3

Notice the 3 which is the session ID I found from using qwinsta above.

Yet another option with W2K3 is a new feature that lets you connect to the desktop directly and gain a 3rd session.

If you’re like me, you probably noticed that rwinsta and qwinsta seems like strange names. This came from the Citrix Metaframe days which Terminal Services has descended from. It stands for:

qwinsta = Query WINdows STAtion
rwinsta = Reset WINdows STAtion

One final comment. Microsoft has replaced these two tools with Query but since qwinsta and rwinsta is fully compatible with W2K WinXP and W2K3 I choose to use it instead.

Note: In Windows Server 2003, you can right-click on the root in Terminal Services Manager and Connect to another server. Also, if you are in an Active Directory domain and using Windows 2000 Server, you can Connect to All Servers and access remote servers from the GUI. But, in a Windows 2000 Server not connected to a domain, you run into the limitations that prompted this blog.