方法一:使用GetUserName函数。
GetUserName可以获得创建当前进程的用户名,其函数原型是:
BOOL GetUserNameA(LPSTR lpBuffer, LPDWORD pcbBuffer);
其中,lpBuffer指向存放用户名的字符数组,pcbBuffer在调用之前需要赋予lpBuffer指向的缓冲区的长度,调用完成后会返回用户名的长度。
代码示例如下:
string getLoginUsernameByApi()
{
char username[1024];
DWORD usernameLength = sizeof username;
GetUserName(username, &usernameLength);
return username;
}
这种方法有个局限:只能返回创建当前进程的用户名,并非真正的当前登录用户名。如果当前进程是服务进程,或者是由服务进程所创建,则GetUserName获得的用户名会是"SYSTEM"。
方法二:使用环境变量函数GetEnvironmentVariable。
GetEnvironmentVariable的函数原型为:
DWORD GetEnvironmentVariable(LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize);
其中,lpName告诉GetEnvironmentVariable需要查询哪个环境变量的值;lpBuffer指向用于保存查询结果的缓冲区;nSize规定了缓冲区的大小。
通过设置lpName参数为"USERNAME"可以获得当前用户名。
代码示例如下:
string getLoginUsernameByEnv()
{
char username[1024];
GetEnvironmentVariable("USERNAME", username, sizeof username);
return username;
}
这种方法是通过查询环境变量来获得当前登录用户的用户名,按理应该不会有别的结果。但是诡异的是,如果进程是服务进程或者被服务进程创建,则这个函数返回的是形如"主机名$"的样式。例如,我的电脑主机名是"familyserver",得到的结果是"familyserver$"。
方法三:使用Windows Terminal Session API。
WTS API提供了一个查询函数,名为WTSQuerySessionInformation,其原型为:
BOOL WTSQuerySessionInformation(
HANDLE hServer,
DWORD SessionId,
WTS_INFO_CLASS WTSInfoClass,
LPSTR *ppBuffer,
DWORD *pBytesReturned
);
其中,hServer指定了远程终端服务的句柄,对于本地终端来说,hServer应该设置为WTS_CURRENT_SERVER_HANDLE;sessionId指定了终端会话的ID,对于本地会话来说,应该设置为WTS_CURRENT_SESSION;WTSInfoClass指定了需要查询会话的哪个信息,对于查询当前登录用户名的需求而言,应该设置为WTS_INFO_CLASS::WTSUserName;ppBuffer为字符指针的指针,WTSQuerySessionInformation将查询结果放在一个缓冲区内,并将指向缓冲区的指针通过ppBuffer返回;pBytesReturned返回了查询结果的字节长度。
代码示例如下:
std::string getLoginUsernameBySession()
{
char *usernameBuffer = nullptr;
DWORD infoSize = 0;
WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTS_INFO_CLASS::WTSUserName, &usernameBuffer, &infoSize);
string username(usernameBuffer);
WTSFreeMemory(usernameBuffer);
return username;
}
下面是一个测试程序,分别测试三种方法获得的用户名,用户名写在一个名为username.log的文件中。
/*
* main.cpp
*
* Created on: 2021年5月2日
* Author: kingfox
*/
#include <iostream>
#include <fstream>
#include <string>
#include <windows.h>
#include <wtsapi32.h>
using namespace std;
const char *logfile = "username.log";
string getLoginUsernameByApi()
{
char username[1024];
DWORD usernameLength = sizeof username;
GetUserName(username, &usernameLength);
return username;
}
void testGetUserNameApi()
{
ofstream ofs(logfile, ios::app);
ofs << "GetUserName: " << getLoginUsernameByApi() << endl;
ofs.close();
}
string getLoginUsernameByEnv()
{
char username[1024];
GetEnvironmentVariable("USERNAME", username, sizeof username);
return username;
}
void testEnvironmentUsername()
{
ofstream ofs(logfile, ios::app);
ofs << "ENV_VAR USERNAME: " << getLoginUsernameByEnv() << endl;
ofs.close();
}
std::string getLoginUsernameBySession()
{
char *usernameBuffer = nullptr;
DWORD infoSize = 0;
WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTS_INFO_CLASS::WTSUserName, &usernameBuffer, &infoSize);
string username(usernameBuffer);
WTSFreeMemory(usernameBuffer);
return username;
}
void testGetUsernameBySession()
{
ofstream ofs(logfile, ios::app);
ofs << "session username: " << getLoginUsernameBySession() << endl;
ofs.close();
}
int main(int argc, char **argv)
{
ofstream ofs(logfile);
testGetUserNameApi();
testEnvironmentUsername();
testGetUsernameBySession();
return 0;
}
在我的环境里,编译后生成的文件名为swa.exe。
单独运行swa.exe后,username.log文件中的内容如下:
GetUserName: kingfox
ENV_VAR USERNAME: kingfox
session username: kingfox
写了个服务程序来调用swa.exe,username.log文件中的内容如下:
GetUserName: SYSTEM
ENV_VAR USERNAME: FAMILYSERVER$
session username: kingfox
可以看到三种方法中,还是调用WTS API最靠谱。
其实还有一种比较麻烦的方法:
方法四:通过获取当前Shell的创建者来间接获取当前登录的用户名。
其原理是:在Windows环境中,当前会话的Shell一定是当前登录用户创建的,所以Shell的创建者用户名一定是当前登录用户名。基本思路是首先获得当前Shell的窗口句柄,然后据悉获得Shell的PID,再通过一系列复杂的查询可以获得创建该进程的用户名。代码如下:
static const int ERROR_PHASER_OPENPROCESS = -1;
static const int ERROR_PHASER_OPENPROCESSTOKEN = -2;
static const int ERROR_PHASER_GETTOKENINFO = -3;
static const int ERROR_PHASER_LOOKUPACCOUNTSID = -4;
int errorPhase = 0;
int errorCode = NO_ERROR;
std::string getCreator(uint32_t pid)
{
string creator;
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (processHandle != NULL)
{
HANDLE tokenHandle;
BOOL success = OpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle);
if (success)
{
const int USER_TOKEN_SIZE = 1024;
TOKEN_USER *user = reinterpret_cast<TOKEN_USER *>(new char[USER_TOKEN_SIZE]);
char usernameBuffer[USER_TOKEN_SIZE];
char domainName[USER_TOKEN_SIZE];
DWORD returnLength;
success = GetTokenInformation(tokenHandle, TokenUser, user, USER_TOKEN_SIZE, &returnLength);
if (success)
{
DWORD cchName = USER_TOKEN_SIZE;
DWORD cchDomainName = sizeof domainName;
SID_NAME_USE nameUse;
success = LookupAccountSid(NULL, user->User.Sid, usernameBuffer, &cchName, domainName, &cchDomainName, &nameUse);
if (success)
{
errorPhase = 0;
creator = usernameBuffer;
}
else
{
errorPhase = ERROR_PHASER_LOOKUPACCOUNTSID; // when LookupAccountSid
errorCode = -1 * GetLastError();
// printf("error when calling LookupAccountSid: %lu\n", errorCode);
}
}
else
{
errorPhase = ERROR_PHASER_GETTOKENINFO; // when GetTokenInformation
errorCode = -1 * GetLastError();
// printf("error when calling GetTokenInformation: %lu\n", *errorCode);
}
delete user;
}
else
{
errorPhase = ERROR_PHASER_OPENPROCESSTOKEN; // when OpenProcessToken
errorCode = -1 * GetLastError();
}
CloseHandle(processHandle);
}
else
{
errorPhase = ERROR_PHASER_OPENPROCESS; // when OpenProcess
errorCode = -1 * GetLastError();
}
return creator;
}
std::string getLoginUserByShell()
{
HWND hShellWindow = GetShellWindow();
DWORD shellProcId = 0;
GetWindowThreadProcessId(hShellWindow, &shellProcId);
string username = getCreator(shellProcId);
return username;
}
这种方法也能不受影响地获得当前登录用户名,不过代码未免太复杂,一不小心就会出错,得有一大堆的出错判断和处理,还需要一定的权限,适用性差点。