0

my windows service should save the name of the user, which logon/logoff at the moment. The following code works for me but didn't save the username:

protected override void OnSessionChange(SessionChangeDescription changeDescription)
    {
        try
        {
            string user = "";

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

            switch (changeDescription.Reason)
            {
                case SessionChangeReason.SessionLogon:
                    WriteLog(Constants.LogType.CONTINUE, "Logon - Program continues: " + user);
                    OnContinue();
                    break;
                case SessionChangeReason.SessionLogoff:
                    WriteLog(Constants.LogType.PAUSE, "Logoff - Program is paused: " + user);
                    OnPause();
                    break;
            }
            base.OnSessionChange(changeDescription);
        }
        catch (Exception exp)
        {
            WriteLog(Constants.LogType.ERROR, "Error");
        }
    }

edit: The foreach loop gives me an error:

Message: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) Type: System.UnauthorizedAccessException

But in my opinion, this code is not the solution, because it saves all users, which are logged onto the server.

ZerOne
  • 1,296
  • 6
  • 20
  • 40
  • @Jamiec That's why I opened this question. Should I ask a question with the correct answer in the same text?? The code is only to show the rest of the crowd how my code looks until now – ZerOne Jun 10 '15 at 15:03
  • Thanks for downvote.. Any comments why somebody downvote?? – ZerOne Jun 10 '15 at 15:05
  • Wasn't me, but the point of SO isn't to write your code for you, it's to help you do it yourself. You haven't shown us anything that you have tried, you're completely asking for the answer. That being said, take a look into Environment.Username perhaps. – bkribbs Jun 10 '15 at 15:07
  • @bkribbs Sure I tried and I failed. Will update my question with the code, which didn't work – ZerOne Jun 10 '15 at 15:09
  • @Jamiec if you read the comments on the marked answer, you will see that the OP gave up and didn't solved the problem... – ZerOne Jun 10 '15 at 15:17
  • Right you are - I scanned the comments quickly but didnt pick that up. Have reopened this question in case you get more luck. One thing pops to mind though - more than one user can be logged in, as you pointed out. So which user do you want? The one physically sat in front of the actual machine? Im not sure there is any distinction... an interactive user is an interactive user regardless of if they're physically sat at the machine or via a remote session. – Jamiec Jun 10 '15 at 15:21
  • Also, your `UnauthorizedAccessException` simply means the user running the service does not have permission to interrogate wmi, Try running the service as an elevated (admin) user to see if you get anything like what you want. – Jamiec Jun 10 '15 at 15:23
  • @Jamiec The best would be the username, which logged in at the system. Distinction if physically or via remote is not necessary. I will give it a try, but as written in the question: this is not the solution for me, I think – ZerOne Jun 10 '15 at 15:26

3 Answers3

4

I ran into a similar problem while building a Windows Service. Just like you, I had the Session ID and needed to get the corresponding username. After several unsuccessful solution hereon SO, I ran into this particular answer and it inspired my solution:

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 by calling

string username = GetUsername(changeDescription.SessionId);
Community
  • 1
  • 1
Soma Mbadiwe
  • 1,594
  • 16
  • 15
0

Finally I got a solution. In the windows service method, there is the session id provided. So with this session id we can execute a powershell command 'quser' and get the current user, who login/logoff on the server. Seen here: How to get current windows username from windows service in multiuser environment using .NET

So this is the function, which we need to create:

private string GetUsername(int sessionID)
        {
            try
            {
                Runspace runspace = RunspaceFactory.CreateRunspace();
                runspace.Open();

                Pipeline pipeline = runspace.CreatePipeline();
                pipeline.Commands.AddScript("Quser");
                pipeline.Commands.Add("Out-String");

                Collection<PSObject> results = pipeline.Invoke();

                runspace.Close();

                StringBuilder stringBuilder = new StringBuilder();
                foreach (PSObject obj in results)
                {
                    stringBuilder.AppendLine(obj.ToString());
                }

                foreach (string User in stringBuilder.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Skip(1))
                {
                    string[] UserAttributes = User.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);

                    if (UserAttributes.Length == 6)
                    {
                        if (int.Parse(UserAttributes[1].Trim()) == sessionID)
                        {
                            return UserAttributes[0].Replace(">", string.Empty).Trim();
                        }
                    }
                    else
                    {
                        if (int.Parse(UserAttributes[2].Trim()) == sessionID)
                        {
                            return UserAttributes[0].Replace(">", string.Empty).Trim();
                        }
                    }
                }

            }
            catch (Exception exp)
            {
                // Error handling
            }

            return "Undefined";
        } 

And this is the windows service function:

protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            try
            {
                switch (changeDescription.Reason)
                {
                    case SessionChangeReason.SessionLogon:
                        string user = GetUsername(changeDescription.SessionId);

                        WriteLog("Logon - Program continue" + Environment.NewLine + 
                            "User: " + user + Environment.NewLine + "Sessionid: " + changeDescription.SessionId);

                        //.....
Community
  • 1
  • 1
ZerOne
  • 1,296
  • 6
  • 20
  • 40
-2

You could try:

System.Security.Principal.WindowsIdentity.GetCurrent();

another option, see: Getting logged-on username from a service

SteveFerg
  • 3,466
  • 7
  • 19
  • 31