Introduction
Normally I log on to my machine as a normal non-admin user. In order to debug ASP.NET applications, one needs to be a member of the administrators group. So I need to launch VS.NET as local administrator. Things work out pretty good except that there is an annoying problem that, when I start debugging of the web application, Internet Explorer does not appear, so I have to launch Internet Explorer separately.
I wrote a small macro which handled the IDE events and launched Internet Explorer when debugging started. The only problem with this solution was that, while debugging there were two ASP.NET sessions running simultaneously and single stepping through the code became confusing (as happens with debugging multithreaded applications). The fact that there were two simultaneous sessions indicated that VS did launch IE. Viewing list of processes did indicate that there was an extra IE instance.
SessView
Why was IE main window not visible? Using the SessView tool by Keith Brown indicated that the invisible IE was launched in a separate non-interactive window station. Each window created in the Windows NT based operating systems belongs to a window station. Only one window station named winsta0 is interactive. Windows that belong to this window station can interact with the user. Window stations can be allocated to a process by the parent process through the CreateProcess
function call. The 9th parameter of CreateProcess
is of type STARTUPINFO
. STARTUPINFO
has a parameter called lpDesktop
which specifies the name of the window station (and desktop) allocated to the process. Here is how it works:-
- If
lpDesktop
is NULL
the newly created process inherits the window station of the calling process.
- If
lpDestop
is an empty string, system allocates the process a unique window station based on the logon session ID of the newly created process. The string is of the form Service-0xX-XXX$.
- Otherwise system uses the name of the window station provided in
lpDesktop
.
All evidences from SessView pointed out that lpDestop
parameter passed to CreateProcess
when launching IE from VS.NET, was an empty string. So a simple fix would be to intercept CreateProcess
calls from within the context of the devenv process and modify this parameter.
Detours
Detours library from Microsoft research is my favorite for API interception. API interception is greatly simplified using Detours. I recommend everyone to have a look at the source and the documentation of this library. Detours works by changing the first few processor instructions at the start of the actual API, to invoke the interceptor function or the Detour. The interceptor function can call the real function with actual instructions if needed. The only problem remaining was to inject some code in the devnev process. The easiest way to do that was to write a VS add-in. The add-in can use the Detours library to intercept CreateProcess
calls.
The add-in
I named the add-in dbgfix. There are two solutions and two projects included with the source code. dbgfix2003.sln is for VS.NET 2003 users and dbgfix.sln is for VS.NET 2002 users.
To use the add-in directly :-
- Extract the source zip files.
- Register the add-in by calling
RegSvr32 dbgfix.dll
If you choose to build the source, the add-in gets registered in the post build step. Here is what the add-in code does in brief:-
- In the
OnConnection
event the add-in uses Detours library to hook CreateProcessW
and CreateProcessA
APIs. This is done only if VS.NET is running under the credentials of the primary interactive user - the user who logged on to the machine from the logon screen.
- In the
OnDisconnection
event the add-in cleans up by removing the hooks on CreateProcessW
and CreateProcessA
APIs.
OnConnection
Lets examine the code of the OnConnection
method of the add-in.
- Find whether VS.NET is running as primary interactive user. For each window station, system associates a user. The SID of the user can be obtained by calling
GetUserObjectInformation
on the window station handle of winsta0.
HWINSTA hwinsta = OpenWindowStation(TEXT("winsta0"),
FALSE, WINSTA_READATTRIBUTES);
DWORD dwLength = 0;
if (!GetUserObjectInformation(hwinsta, UOI_USER_SID,
NULL, 0, &dwLength))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
AtlThrowLastWin32();
}
CAutoVectorPtr<BYTE> spSid(new BYTE[dwLength]);
if (spSid == NULL)
AtlThrow(E_OUTOFMEMORY);
if (!GetUserObjectInformation(hwinsta, UOI_USER_SID,
spSid, dwLength, &dwLength))
AtlThrowLastWin32();
- The next step is to find the logon sid of the user who launched devenv. This can be obtained from the process token.
CAccessToken token;
if (!token.GetProcessToken(TOKEN_QUERY))
AtlThrowLastWin32();
CSid sidLogon;
if (!token.GetLogonSid(&sidLogon))
AtlThrowLastWin32();
m_bRunningAsPrimary = (sidLogon == *(SID*)((BYTE*)spSid));
- Finally if the logon sid of the user is different from the sid associated with the user of winsta0, we call into Detours library.
if (!m_bRunningAsPrimary)
{
DetourFunctionWithTrampoline((PBYTE)Real_CreateProcessW,
(PBYTE)CreateProcessW_Detour);
DetourFunctionWithTrampoline((PBYTE)Real_CreateProcessA,
(PBYTE)CreateProcessA_Detour);
}
The first DetourFunctionWithTrampoline
call modifies the first few instructions at the address of the CreateProcessW
and invoke the function CreateProcessW_Detour
. The actual CreateProcess
function can still be called using the Real_CreateProcessW
. For a complete discussion on how Detours work, refer the Detour web site. Here is how the CreateProcessW_Detour
function looks like.
BOOL WINAPI CreateProcessW_Detour(LPCWSTR lpApplicationName,
LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,BOOL bInheritHandles,
DWORD dwCreationFlags, LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation)
{
if (lpStartupInfo->lpDesktop != NULL)
{
if (*lpStartupInfo->lpDesktop == 0)
lpStartupInfo->lpDesktop = NULL;
}
return Real_CreateProcessW(lpApplicationName, lpCommandLine,
lpProcessAttributes, lpThreadAttributes,
bInheritHandles, dwCreationFlags,
lpEnvironment, lpCurrentDirectory,
lpStartupInfo, lpProcessInformation);
}
Basically if the lpDesktop
is an empty string, it is converted to NULL
before calling the real CreateProcessW
. This makes the invoked process to inherit the same window station as VS.NET, essentially fixing the problem.
Conclusion
This simple add-in resolves the problem with launching of IE. Thanks to Microsoft research for providing the source of Detours library. Please note that only detours.lib and detours.h is included with the source of this article. If you are interested in the full source of Detours, you can download it from the Detours web site.