Visual Studio or VB6.0 script

Here’s the background:  In Windows 2012 (in workgroup mode) the only way to shadow a user's RDP session is via command line, which is really annoying.

So I wanted to build a small utility around the command line that would be more user friendly.

I wrote “shadow.bat” that eases the pain a little.  It accepts one parameter.  That parameter is the user who you want to shadow.
i.e. If I log on to a 2012 server and want to see what user BOB is doing, I need to run/type:  shadow BOB

My code for "shadow.bat" is simple:
for /F "tokens=3 skip=1" %%i in ('query user %1') DO (
mstsc.exe /shadow:%%i /noConsentPrompt /control
)

The incoming %1 parameter is the username I want to shadow. It obtains the session number of the user from there (%%I) and runs mstsc.exe with a shadow switch. I'm good to go.

What I really want to do is take away the need to run this .bat manually from command line.

How difficult would it be to have my code inside a compiled .exe where upon running, the program presents you with a dropdown list of logged on users, and you pick who you want to shadow ?

So I’d run “shadow.exe” and it would show a list of users logged on to my server, I'd pick a user to shadow, and it basically takes that username and puts it into the %1 variable in my bat script.

I'm good at .bat, vbscript, VB6.0....but .NET not really. This is a little beyond my area of expertise.  does anybody have code suggestions? maybe something I can copy & paste then compile?
LVL 15
ZabagaRAsked:
Who is Participating?
 
Kyle AbrahamsSenior .Net DeveloperCommented:
The good news with going with .Net is you can find a lot of samples online.  I didn't test this code but it looks good:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace EnumerateRDUsers
{
  class Program
  {
    [DllImport("wtsapi32.dll")]
    static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

    [DllImport("wtsapi32.dll")]
    static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("wtsapi32.dll")]
    static extern Int32 WTSEnumerateSessions(
        IntPtr hServer,
        [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
        [MarshalAs(UnmanagedType.U4)] Int32 Version,
        ref IntPtr ppSessionInfo,
        [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);

    [DllImport("wtsapi32.dll")]
    static extern void WTSFreeMemory(IntPtr pMemory);

    [DllImport("Wtsapi32.dll")]
    static extern bool WTSQuerySessionInformation(
        System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
      public Int32 SessionID;

      [MarshalAs(UnmanagedType.LPStr)]
      public String pWinStationName;

      public WTS_CONNECTSTATE_CLASS State;
    }

    public enum WTS_INFO_CLASS
    {
      WTSInitialProgram,
      WTSApplicationName,
      WTSWorkingDirectory,
      WTSOEMId,
      WTSSessionId,
      WTSUserName,
      WTSWinStationName,
      WTSDomainName,
      WTSConnectState,
      WTSClientBuildNumber,
      WTSClientName,
      WTSClientDirectory,
      WTSClientProductId,
      WTSClientHardwareId,
      WTSClientAddress,
      WTSClientDisplay,
      WTSClientProtocolType
    }
    public enum WTS_CONNECTSTATE_CLASS
    {
      WTSActive,
      WTSConnected,
      WTSConnectQuery,
      WTSShadow,
      WTSDisconnected,
      WTSIdle,
      WTSListen,
      WTSReset,
      WTSDown,
      WTSInit
    }

    static void Main(string[] args)
    {
      ListUsers("<INSERT SERVERNAME HERE>");
    }

    public static IntPtr OpenServer(String Name)
    {
      IntPtr server = WTSOpenServer(Name);
      return server;
    }
    public static void CloseServer(IntPtr ServerHandle)
    {
      WTSCloseServer(ServerHandle);
    }
    public static void ListUsers(String ServerName)
    {
      IntPtr serverHandle = IntPtr.Zero;
      List<String> resultList = new List<string>();
      serverHandle = OpenServer(ServerName);

      try
      {
        IntPtr SessionInfoPtr = IntPtr.Zero;
        IntPtr userPtr = IntPtr.Zero;
        IntPtr domainPtr = IntPtr.Zero;
        Int32 sessionCount = 0;
        Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
        Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
        Int32 currentSession = (int)SessionInfoPtr;
        uint bytes = 0;

        if (retVal != 0)
        {
          for (int i = 0; i < sessionCount; i++)
          {
            WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)currentSession, typeof(WTS_SESSION_INFO));
            currentSession += dataSize;

            WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSUserName, out userPtr, out bytes);
            WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSDomainName, out domainPtr, out bytes);

            Console.WriteLine("Domain and User: " + Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr));

            WTSFreeMemory(userPtr); 
            WTSFreeMemory(domainPtr);
          }

          WTSFreeMemory(SessionInfoPtr);
        }
      }
      finally
      {
        CloseServer(serverHandle);
      }

    }

  }
}

Open in new window


Instead of the  

Console.WriteLine("Domain and User: " + Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr));

you would add the item to a dropdownlist.  

From there you would also need a button and a button click event to call your command line shell execute.  

In case you're looking at samples, http://www.developerfusion.com/tools/convert/csharp-to-vb/ to convert from c# to vb and vice versa.  

VB6 has been dead for years.  Never hurts to learn new technologies and the conversion isn't *that* bad.  For something like this it's the perfect project to go .Net.  Try it out, let us know if you have any questions.
0
 
ZabagaRAuthor Commented:
Thanks I've seen the sample code all over too...but unless somebody actually writes it for me in this case, I'm not going to get anywhere.
0
 
Kyle AbrahamsSenior .Net DeveloperCommented:
Maybe you're better off hiring someone to do it for you then.
0
 
käµfm³d 👽Commented:
I would agree with Kyle if you're not comfortable attempting this yourself. But honestly, you can find tons of introductory WinForms tutorials that would be simple and straight-forward. IMHO, this is a simple task, and after 1/2 with a decent tutorial, you should know how to the dropdown bit.

As for executing your batch script from .NET, you would simply need the Process class (example usage at bottom of page).
0
 
ZabagaRAuthor Commented:
Thanks, I appreciate the input.  A co-worker who does much more .net helped me out and I got it pretty quickly.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.