Vista Service Issues
最近对于Vista下的service的debug工作真是太有心得了.
Service起来的时候可能你的桌面都没有show出来, 那么这种时候怎么debug呢? 写文件? 不错的方法. 但是我们有更好的方法: 写event log. ATL中的CAtlServiceModuleT这个类中有LogEvent()这个函数, 可以很方便的调用. 瞄了一眼, 其实这是一个很light-weight的函数, 稍微改改完全可以单独使用:
voidLogEvent(LPCTSTR pszFormat, ...)
{
TCHAR chMsg[1024];
HANDLE hEventSource;
va_list pArg;
va_start(pArg, pszFormat);
_vstprintf(chMsg, pszFormat, pArg);
va_end(pArg);
/* Get a handle to use with ReportEvent(). */
hEventSource = RegisterEventSource(NULL, SERVICE_NAME);
if (hEventSource != NULL)
{
/* Write to event log. */
ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*) &chMsg, NULL);
DeregisterEventSource(hEventSource);
}
}
然后就是FAQ了, service中怎么起一个程序? 诶, 其实直接CreateProcess()的话程序是起来的, 但只是跑在当前service的那个session下了, 所以你看不见而已. 那怎么才能看见呢? 这个技术叫做impersonate -.-. 我们要拿到当前active用户的执行environment和权限token, 然后调用CreateProcessAsUser(). 这里我用的全部都是unicode, msdn的论坛上说ansi版本有bug @.- :
BOOLRunProcess(LPWSTR lpwszPath, LPWSTR lpwszArgs)
{
HANDLE hToken = NULL, hTokenDup = NULL;
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
WTSQueryUserToken(dwSessionId, &hToken);
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
LPVOID pEnv = NULL;
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE);
dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT;
WCHAR wszTemp[MAX_PATH];
ZeroMemory(wszTemp, MAX_PATH*sizeof(WCHAR));
wsprintfW(wszTemp, L"\"%s\" %s", lpwszPath, lpwszArgs);
STARTUPINFOW si = { sizeof(STARTUPINFO) };
si.lpDesktop = L"winsta0\default";
PROCESS_INFORMATION pi;
BOOL bRet = CreateProcessAsUserW(hTokenDup, NULL, wszTemp,
NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL, &si, &pi);
if (bRet)
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
DestroyEnvironmentBlock(&pEnv);
CloseHandle(hToken);
CloseHandle(hTokenDup);
return bRet;
}
恩, winsta0表示当前的windows station, 表看成winvista0. default表示当前的session. 其它应该没什么好解释了.
最后还有两个函数, 用来真正的impersonate. 由于一般service都跑在local system账号下, 所以注册表之类的HKEY_CURRENT_USER之类的键值其实访问的都不是当前active的用户, 而就是local system这个账号的. 看到一篇文章所幸就说service里不能访问HKEY_CURRENT_USER, 囧... 好了废话不多说:
voidBeginImpersonate()
{
HANDLE hToken = NULL, hTokenDup = NULL;
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
WTSQueryUserToken(dwSessionId, &hToken);
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
ImpersonateLoggedOnUser(hTokenDup);
CloseHandle(hToken);
CloseHandle(hTokenDup);
}
voidEndImpersonate()
{
RevertToSelf();
}
这两个函数分别开始和结束impersonate一个账号. 之前忘了说了, 拿到当前active的用户的API用的是WTS(Windows Terminal Service)的那套.
以上...
另外可以参考我之前写的:
Windows Service Howto (1)
Windows Service Howto (2)
...