方法一:使用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;
}

这种方法也能不受影响地获得当前登录用户名,不过代码未免太复杂,一不小心就会出错,得有一大堆的出错判断和处理,还需要一定的权限,适用性差点。