最近开发的新项目里面的程序,在自己电脑上运行的好好的,打包出去之后,测试多次说程序有问题,打不开,后来好不容易试出来说需要管理员权限,原来测试把程序装在了c盘,而大家都知道c盘的读写是需要权限的,如果再没有权限的时候去读写,出异常就很正常了。那么如果非要这么干呢?这个问题在Windows上有两种解决方法,其中一种是非程序性方法,另一个是程序性的方法,也就是今天的主角,两个方法最后都获取了管理员权限。
这个就是前菜了,在Windows上,我们都经常使用快捷方式,但是很少有人去设置它,其实快捷方式是有很多设置项的。
这里右键vs快捷方式,选择属性,界面如下。可以看到可以修改的项还是很多的。
这里我们选择高级
勾选用管理员身份运行,这样我们再打开程序的时候,程序就会获取管理员权限了。
问题是,这种方法只对当前的这个快捷方式生效,意味着如果我有多个打开方式,或者我把程序拷贝给其他人的时候,这个还需要设置,否则还是要出问题。
这里我们提供一个一劳永逸的好办法,而且你还可以控制是不是提示别人你需要获取管理员权限。
Talk is cheap,show me the code.
下面就是我们完成任务的函数主体
BOOL IsRunAsAdministrator()
{
BOOL fIsRunAsAdmin = FALSE;
DWORD dwError = ERROR_SUCCESS;
PSID pAdministratorsGroup = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pAdministratorsGroup))
{
dwError = GetLastError();
goto Cleanup;
}
if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin))
{
dwError = GetLastError();
goto Cleanup;
}
Cleanup:
if (pAdministratorsGroup)
{
FreeSid(pAdministratorsGroup);
pAdministratorsGroup = NULL;
}
if (ERROR_SUCCESS != dwError)
{
throw dwError;
}
return fIsRunAsAdmin;
}
void ElevateNow()
{
BOOL bAlreadyRunningAsAdministrator = FALSE;
try
{
bAlreadyRunningAsAdministrator = IsRunAsAdministrator();
}
catch (...)
{
}
if (!bAlreadyRunningAsAdministrator)
{
WCHAR szPath[MAX_PATH];
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)))
{
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = L"runas";
sei.lpFile = szPath;
sei.hwnd = NULL;
sei.nShow = SW_SHOWDEFAULT;
if (!ShellExecuteEx(&sei))
{
DWORD dwError = GetLastError();
if (dwError == ERROR_CANCELLED)
//Annoys you to Elevate it LOL
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ElevateNow, 0, 0, 0);
}
}
}
else
{
///Code
}
}
第一个函数主要用来查看当前程序是否已经是管理员权限运行了,主要是两个函数:
我们使用GetModuleFileName获取到程序的运行路径
初始化 SHELLEXECUTEINFO sei,可以看到,我们再这个信息里面设置了软件执行的路径,runas,和SW_SHOWDEFAULT,这里SW_SHOWDEFAULT这个参数有很多其他选项,可以跳代码进去看一下,这里也贴一下,常用的就是SW_HIDE,SW_NORMAL.
#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_FORCEMINIMIZE 11
#define SW_MAX 11
最重要的就是lpVerb的值设置为runas,这里lpVerb也有很多不同的值,最常见的是"runas",“open”.“null”.更多的值可以上msdn看SHELLEXECUTEINFO,这里"runas",就是我们的提权操作。
如果提权失败,我们这里如果提权失败,会不断尝试,CreateThread,继续提权,这里其实没有太大的必要,一般一次就可以提权成功,所有最精简的代码应该可以携程这样。
void GainAdminPrivileges(CString strApp) {
SHELLEXECUTEINFO execinfo;
memset(&execinfo, 0, sizeof(execinfo));
execinfo.lpFile = strApp;
execinfo.cbSize = sizeof(execinfo);
execinfo.lpVerb = _T("runas");
execinfo.fMask = SEE_MASK_NO_CONSOLE;
execinfo.nShow = SW_SHOWDEFAULT;
ShellExecuteEx(&execinfo);
}
这里只需要传程序的路径就可以了,路径可以通过上面的GetModuleFileName获取。
然后我们可以写一个测试程序测试下
int main()
{
/* FILE* fp = fopen("administratortest.txt", "a+");
if (fp == NULL)
{
return 0;
}
fprintf(fp, "administratortest:%d.\n", 1);
fclose(fp);*/
//获取管理员权限
WCHAR path[MAX_PATH] = { 0 };
GetModuleFileName(NULL, path, MAX_PATH);
GainAdminPrivileges(path);
/*FILE* fp = fopen("administratortest.txt", "a+");
if (fp == NULL)
{
return 0;
}
fprintf(fp, "administratortest:%d.\n", 2);
fclose(fp);*/
return 0;
}
我们编译好之后将exe拷贝到c盘,打开上面的文件读写的版本,会发现,程序崩溃了。
我们只打开下面的文件读写的版本,程序正常运行,也生成了文件,写入了内容。
如果你使用vs进行项目管理,还可以通过简单的项目配置来获取管理员权限
这里应该是windows内置的一套获取管理员权限的方法,跟我们自己写的也差不多,然后选择的时候最好顺手把下main的绕过UI保护选上,这样打开程序的时候就不会弹出来烦人的提示了,如果还有的话,可能需要把windows消息提示的等级调低一点。