3

I have a service that I had to log on to the local admin to install. The pupose of this service to log when a user is logging in or out to record their username. I finally found a bit of WMI code that I thought would work, but it is still returning Administrator. Why isn't this working?

var query = new ObjectQuery("SELECT * FROM Win32_Process WHERE Name = 'explorer.exe'");
var explorerProcesses = new ManagementObjectSearcher(query).Get();

foreach (ManagementObject mo in explorerProcesses)
{
    string[] ownerInfo = new string[2];
    mo.InvokeMethod("GetOwner", (object[])ownerInfo);

    userName = String.Concat(ownerInfo[1], @"\", ownerInfo[0]);
}
Console.WriteLine(userName);
Console.ReadLine();

To clarify my question I'm tring to get the currently logged on user but its giving me back Adminstrator the account I used to install the service.

Jesse
  • 1,846
  • 2
  • 22
  • 32

4 Answers4

4

You should use Service Control Manager Notifications for this. You can configure your service to receive notification events when a user logs on and / or logs off. This allows a service to do interactive user impersonation if the service requires it, but it should give you the info you need for your logging.

Check out the section "Using Service Control Manager (SCM) Notifications" here http://technet.microsoft.com/en-us/library/cc721961(WS.10).aspx

edit

In your Service class override the OnSessionChange event handler to check for logon and logoff events.

protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
    base.OnSessionChange(changeDescription);

    switch (changeDescription.Reason)
    {
        case SessionChangeReason.SessionLogon:
            // do your logging here
            break;

        case SessionChangeReason.SessionLogoff:
            // do your logging here
            break;
    }
}

edit2:

class Class1
{
    [DllImport("Advapi32.dll")]
    static extern bool GetUserName(StringBuilder lpBuffer, ref int nSize);    
    [STAThread]
    static void Main(string[] args)
    {
        StringBuilder Buffer = new StringBuilder(64);
        int nSize=64;
        GetUserName(Buffer, ref nSize);
        Console.WriteLine(Buffer.ToString());
    }
}

edit3:

public class InteractiveUser
{
    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);

    [DllImport("kernel32.dll")]
    private static extern UInt32 WTSGetActiveConsoleSessionId();

    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin
    }

    public struct TOKEN_USER 
    { 
        public SID_AND_ATTRIBUTES User; 
    } 

    [StructLayout(LayoutKind.Sequential)]
    public struct SID_AND_ATTRIBUTES 
    { 
        public IntPtr Sid; 
        public int Attributes; 
    } 

    // Using IntPtr for pSID insted of Byte[]
    [DllImport("advapi32", CharSet=CharSet.Auto, SetLastError=true)]
    static extern bool ConvertSidToStringSid(
        IntPtr pSID, 
        out IntPtr ptrSid);

    [DllImport("kernel32.dll")]
    static extern IntPtr LocalFree(IntPtr hMem);

    [DllImport("advapi32.dll", SetLastError=true)]
    static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        TOKEN_INFORMATION_CLASS TokenInformationClass,
        IntPtr TokenInformation,
        int TokenInformationLength,
        out int ReturnLength);

    private static string GetSID(IntPtr token)
    {
        bool Result;

        int TokenInfLength = 0; 
        string sidAsString = String.Empty;

        // first call gets lenght of TokenInformation
        Result = GetTokenInformation( token , TOKEN_INFORMATION_CLASS.TokenUser , IntPtr.Zero , TokenInfLength , out TokenInfLength ); 

        IntPtr TokenInformation = Marshal.AllocHGlobal( TokenInfLength ) ; 
        Result = GetTokenInformation( token  , TOKEN_INFORMATION_CLASS.TokenUser , TokenInformation , TokenInfLength , out TokenInfLength ) ; 

        if ( Result ) 
        {
            TOKEN_USER TokenUser = ( TOKEN_USER )Marshal.PtrToStructure( TokenInformation , typeof( TOKEN_USER ) ) ; 

            IntPtr pstr = IntPtr.Zero; 
            Boolean ok = ConvertSidToStringSid( TokenUser.User.Sid  , out pstr ); 

            sidAsString = Marshal.PtrToStringAuto( pstr ); 
            LocalFree(pstr);
        }

        Marshal.FreeHGlobal( TokenInformation );

        return sidAsString;
    }

    public static string Account()
    {
        IntPtr token = IntPtr.Zero;
        String account = String.Empty;

        if (WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out token))
        {
            String sid = GetSID(token);
            account =
                new SecurityIdentifier(sid).Translate(typeof(NTAccount)).ToString();
        }
        else
        {
            int err = Marshal.GetLastWin32Error();
            switch (err)
            {
                case 5:
                    account = "ERROR_ACCESS_DENIED";
                    break;
                case 87:
                    account = "ERROR_INVALID_PARAMETER";
                    break;
                case 1008:
                    account = "ERROR_NO_TOKEN";
                    break;
                case 1314:
                    account = "ERROR_PRIVILEGE_NOT_HELD";
                    break;
                case 7022:
                    account = "ERROR_CTX_WINSTATION_NOT_FOUND";
                    break;
                default:
                    account = String.Format("ERROR_{0}", err.ToString());
                    break;
            }
        }

        return account;
    }
}
bkribbs
  • 734
  • 1
  • 6
  • 24
ScottTx
  • 1,483
  • 8
  • 12
  • Although I'm sure this would work, switching C++ is not an option where I work. Also I'm already a week in coding this service using WMI. I would prefer to find a WMI solution. – Jesse Mar 08 '11 at 21:56
  • You don't need to do this in C++. The methods to do this are also available in C#. – ScottTx Mar 08 '11 at 22:25
  • Thanks for the code. I have already done exactly that and it works great. My question is however how to get the username not log-on/log-off – Jesse Mar 09 '11 at 14:23
  • Sorry, I answered an unasked question. Edited my response above with a snippet of code from pinvoke.net. – ScottTx Mar 09 '11 at 23:44
  • According to the description on pinovoke.net this method will return name of the user associated with the current thread. If I'm understanding right it will return SYSTEM not the currently logged on user. – Jesse Mar 10 '11 at 15:21
  • Yup thats a true statement. I've put some more code in my response (edit3). You can try that if you wish. Note that your service must be logged in as LocalSystem and must be granted the SE_TCB_NAME privilege for this code to work properly. – ScottTx Mar 10 '11 at 20:20
  • Well I threw this is a Service installed it on my Machine running windows 7 and logged off and on and it worked great. Alas though I had a coworker log on my machine and log off and its still recording my credentials seeing as I installed the service. I thing I'm done trying to get this to work. Think I'm just gonna break down and use logon/logoff scripts. Thank you for taking the time to respond to my question. – Jesse Mar 11 '11 at 15:34
  • thanks, if base.OnSessionChange(changeDescription) not called at first,sessionId cause error – Mohsen.Sharify Jan 14 '17 at 12:11
2

Here's my code (all of them residing inside a class; in my case, the class inheriting ServiceBase).

    [DllImport("Wtsapi32.dll")]
    private static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WtsInfoClass wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
    [DllImport("Wtsapi32.dll")]
    private static extern void WTSFreeMemory(IntPtr pointer);

    private enum WtsInfoClass
    {
        WTSUserName = 5, 
        WTSDomainName = 7,
    }

    private static string GetUsername(int sessionId, bool prependDomain = true)
    {
        IntPtr buffer;
        int strLen;
        string username = "SYSTEM";
        if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSUserName, out buffer, out strLen) && strLen > 1)
        {
            username = Marshal.PtrToStringAnsi(buffer);
            WTSFreeMemory(buffer);
            if (prependDomain)
            {
                if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
                {
                    username = Marshal.PtrToStringAnsi(buffer) + "\\" + username;
                    WTSFreeMemory(buffer);
                }
            }
        }
        return username;
    }

With the above code in your class, you can simply get the username in the method you're overriding like this:

protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
    string username = GetUsername(changeDescription.SessionId);
    //continue with any other thing you wish to do
}
Soma Mbadiwe
  • 1,594
  • 16
  • 15
1

Try to change the Account() method inserting the sessionId parameter and passing the changeDescription.SessionId to the method WTSQueryUserToken

public static string Account(uint sessionId)
{
     IntPtr token = IntPtr.Zero;
     String account = String.Empty;
     if (WTSQueryUserToken(sessionId, out token))
     { 
     ...
     ...

p.s.: Run your service with LocalSystem account

0

I know this thread is old, but if anyone need to know how it works:

Add a reference to System.Management

Put using System.Management; at the top of your file

Create this private variable in your class:

private readonly ManagementClass _wmiComputerSystem = new ManagementClass("Win32_ComputerSystem");

Create this method in your service:

protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
   base.OnSessionChange(changeDescription);

   switch (changeDescription.Reason)
   {
      case SessionChangeReason.SessionLogon:
      {
         string user = "";

         foreach (ManagementObject currentObject in _wmiComputerSystem.GetInstances())
         {
            user = currentObject.Properties["UserName"].Value.ToString().Trim();
         }
      } break;
   }
}

Now you got the username in user. If the computer is in a domain it looks like this domain\username

Pascal
  • 2,175
  • 4
  • 39
  • 57