如何在.NET中从Windows服务获取当前登录的用户名?

70

我有一个需要获取当前登录用户名的Windows服务。我尝试过使用System.Environment.UserName、Windows身份验证和Windows表单身份验证,但是所有方法都返回“System”,因为我的服务在具有系统特权的用户下运行。有没有一种不需要更改我的服务帐户类型就可以获取当前登录用户名的方法?


15
你知道可能有多个已登录用户吗?你想要他们全部吗? - David Heffernan
我认为您可以通过读取Windows注册表(具有SID子键的那些)来获取已登录用户。以下是相关的编程问题链接:https://dev59.com/hFLTa4cB1Zd3GeqPcahb 和 https://dev59.com/sE3Sa4cB1Zd3GeqPvXUe。 - Jaroslav Jandek
当然,所有已登录的用户。 - Raj
9个回答

95

这是一个用于获取用户名的WMI查询:

ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem");
ManagementObjectCollection collection = searcher.Get();
string username = (string)collection.Cast<ManagementBaseObject>().First()["UserName"];

你需要手动在引用中添加 System.Management


16
人们经常忘记,在运行服务时,通常获取用户名的方法由于系统账户的存在而无法使用...因此这个回答是唯一考虑到这一点的,给它一个赞。 - amartin94
14
我知道这个问题很老了,但是我在谷歌搜索相同的问题时找到了它。虽然这个答案确实是正确的,但请记住,正如@xanblax在他的回答中所说,这个WMI查询在远程(RDP)会话中不起作用。只是想指出这一点,并使它更加明显,以防将来有人阅读此内容。 - CaptainStealthy
1
旧的答案仍然有用。我在2018年8月“现在”偶然发现了它们。 - Per Lundberg
@Tapas,您能建议如何将其用于普通用户吗? - Rupsingh
在我的服务应用程序中,以Windows 10 20H2系统帐户运行时正常工作。谢谢! - Nate

33
如果您处于用户网络中,则用户名将不同:
Environment.UserName

将显示格式更改为:“用户名”,而不是

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

将显示格式:'网络名称\用户名'

选择您想要的格式。


1
正是我所需要的! - thedrs
40
完全没有回答问题。当你的应用程序作为服务运行时,这两个调用都会返回服务账户名称。 - Aaron C. de Bruyn
4
它将返回当前服务用户,而不是普通用户。 - Mahdi

22

ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem")的解决方案对我很有效。但如果服务是通过远程桌面连接启动的,则无法正常工作。 为了解决这个问题,我们可以请求交互式进程所有者的用户名,该进程始终在PC上运行:explorer.exe。这样,我们始终可以从我们的Windows服务中获取当前Windows登录的用户名:

foreach (System.Management.ManagementObject Process in Processes.Get())
{
    if (Process["ExecutablePath"] != null && 
        System.IO.Path.GetFileName(Process["ExecutablePath"].ToString()).ToLower() == "explorer.exe" )
    {
        string[] OwnerInfo = new string[2];
        Process.InvokeMethod("GetOwner", (object[])OwnerInfo);

        Console.WriteLine(string.Format("Windows Logged-in Interactive UserName={0}", OwnerInfo[0]));

        break;
    }
}

4
我想使用这个答案,但我找不到Processes类。 - Matt Becker
如果同时有多个用户登录(例如在Windows 10中切换到另一个用户),这种方法就行不通了,因为每个用户都会运行一个explorer.exe实例。 - Vitani
应该是这样的:ManagementObjectSearcher Processes = new ManagementObjectSearcher("root\CIMV2", "SELECT * FROM Win32_Process"); - ilCosmico
为了确保我获取了正确的explorer.exe副本,我获取了当前进程并从中获取了会话ID,然后将其与我找到的explorer.exe的SessionID属性值进行比较,然后从那个进程中获取了用户信息。 - David Parvin
当然,Windows服务可能在没有UI或没有explorer.exe运行的会话中,因此您可能需要采用其他方式,但我正在使用它在Windows桌面应用程序中,并且它运作良好。 - David Parvin

6

修改自Tapas的回答的代码:

Dim searcher As New ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem")
Dim collection As ManagementObjectCollection = searcher.[Get]()
Dim username As String
For Each oReturn As ManagementObject In collection
    username = oReturn("UserName")
Next

2

如果有人像我一样在寻找用户的显示名称而不是用户名,请注意以下内容:

这是解决方法:

System.DirectoryServices.AccountManagement.UserPrincipal.Current.DisplayName.

在您的项目中添加对System.DirectoryServices.AccountManagement的引用。


2
当我尝试这样做时,我收到了错误信息:“无法将类型为 'System.DirectoryServices.AccountManagement.GroupPrincipal' 的对象转换为类型 'System.DirectoryServices.AccountManagement.UserPrincipal'。” - atjoedonahue

1
你也可以尝试:

System.Environment.GetEnvironmentVariable("UserName");

1
那会显示什么?域名\用户名还是只有用户名? - SearchForKnowledge
@寻找知识:它只返回用户名 - 这相当于使用属性 'Environment.UserName'。 - B Bhatnagar

1

尝试使用 WindowsIdentity.GetCurrent()。您需要添加对 System.Security.Principal 的引用。


13
我认为这会返回服务正在运行的用户,而不是在系统帐户下运行的服务所看到的所有交互式用户。 - Hans Kesting

0

补充来自@xanblax的答案

private static string getUserName()
{
    SelectQuery query = new SelectQuery(@"Select * from Win32_Process");
    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
    {
       foreach (System.Management.ManagementObject Process in searcher.Get())
        {
            if (Process["ExecutablePath"] != null && string.Equals(Path.GetFileName(Process["ExecutablePath"].ToString()), "explorer.exe", StringComparison.OrdinalIgnoreCase))
            {
                string[] OwnerInfo = new string[2];
                Process.InvokeMethod("GetOwner", (object[])OwnerInfo);
                 return OwnerInfo[0];
            }
        }
    }

    return "";
}

0

Windows服务的答案:

你可以通过当前登录用户的sessionId来获取它。首先,你需要在你的代码类中添加以下的P/INVOKE脚本:

[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,
}

[DllImport("Kernel32.dll", SetLastError = true)]
static extern int WTSGetActiveConsoleSessionId();

然后,添加这个方法:
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;
}

最后,像这样调用该方法:
int sessionId = WTSGetActiveConsoleSessionId();
string username = GetUsername(sessionId); // Gives 'Domain\Username'

来源链接:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接