Click here to Skip to main content
Click here to Skip to main content

A Developer's Survival Guide to IE Protected Mode

, 20 May 2007
Rate this:
Please Sign up or sign in to vote.
Busted features? APIs failing? Use this guide to get your IE plugin up and running again in protected mode!

Contents

Introduction

After seeing Vista and IE 7 at PDC 2005, I remarked that protected mode was going to make life difficult for legitimate IE extensions, not just spyware. Last year while trying out Vista beta 2, I saw just how difficult - one of my products has an IE toolbar, and protected mode broke many of the major features because the toolbar needs to communicate with another process outside of IE. After a lot of trial and error, and some not-kid-sister-safe language, I eventually got the features working again. After all that work, I wanted to summarize the necessary changes into this survival guide and give folks all the relevant information in one place.

This article assumes you are familiar with C++, GUI, and COM programming. The sample code is built with Visual Studio 2005, WTL 7.5, and the Windows SDK. If you need to get up to speed with WTL, check out my series of articles on WTL. WTL is only used for the GUI; the concepts presented here apply to all applications, and are independent of any class library.

Introduction to Protected Mode

Internet Explorer's Protected Mode is a new feature in Vista, and is one of the pieces of User Account Control (UAC). Protected mode is designed to protect the computer by restricting the parts of the system that code running in the IE process can affect. If a malicious web page exploits a code-injection bug in IE or an IE plugin, that code will not be able to do damage to the system.

Before we dive into what protected mode means for IE plugin developers, we need a quick tour of the security features involved.

Integrity Levels and UIPI

Vista introduces a new attribute on securable objects called the mandatory integrity level. There are four levels:

  • System: Used by OS components, should not be used by applications.
  • High: Processes that are running elevated with full admin rights.
  • Medium: Processes launched in the normal fashion.
  • Low: Used by IE and Windows Mail to provide protected mode.

The information that Windows keeps about a process includes which integrity level it was launched with. This level can never change once the process is started, it can only be set at the time the process is created. A process's integrity level has three main effects:

  1. Any securable objects that the process creates get that same integrity level.
  2. The process cannot access a resource whose integrity level is higher than the process's own level.
  3. The process cannot send window messages to a process that has a higher integrity level.

That is not a complete list, but the three listed above have the greatest impact on plugins. The first two items prevent a low-integrity process from tampering with IPC resources, like shared memory, that contain sensitive data or data that is crucial for the proper operation of an application. The last item is called User Interface Process Isolation (UIPI), and is designed to prevent attacks like the shatter attack, where an attacker induces a process into running untrusted code by sending it messages that it was not expecting to receive.

Virtualization

Virtualization (also called redirection in some Microsoft documentation) is a feature that prevents a process from writing to protected areas of the registry and file system, while still allowing the application to function normally. For medium-integrity processes, the protected areas are system-critical areas like HKLM, the system32 and Program Files directories, and so on. A low-integrity process is even more restricted -- it can only write to special low-rights areas of the registry and file system, and any attempts to write outside those areas are prevented.

When a process tries to write to an area it doesn't have rights to, virtualization kicks in and redirects the write operation to a directory (or registry key) under the current user's profile, and the write operation actually happens there. Then later, when the app tries to read that data, the read operation is also redirected so the app will see the data that it wrote earlier.

Virtualization affects IE extensions because they can no longer do things like write settings to the registry (even under HKCU) for other processes to use. Extensions are also very restricted in where they can write data files -- only a few IE-specific directories like Favorites and Cookies are writable.

When Is Protected Mode Turned On?

In Vista's default configuration, IE always runs in protected mode. The status bar, pictured below, has an indicator that shows when protected mode is on:

You can turn protected mode off entirely by disabling UAC, or by unchecking Enabled Protected Mode on the Security tab of the IE options dialog. You can also temporarily bypass protected mode by running a new elevated instance of IE, but keep in mind that doing so will make IE run at high integrity, not at medium like a normal application.

The Sample App and Extension

The sample code for this article combines two projects. The first project, IEExtension, is a band that is docked to the bottom of the IE window:

The second project, DemoApp, is an EXE that the band communicates with. DemoApp doesn't do much by itself, the interesting parts are in IEExtension where it needs to communicate with the EXE. This communication is greatly impacted by protected mode, and both projects demonstrate how to work within protected mode's restrictions and still be able to do inter-process communication.

The band has several buttons for doing various IPC tasks. The buttons that are in pairs show both an old technique that no longer works in protected mode (button 1) and a new protected mode-aware technique that does work (button 2). The list control shows various status messages, such as the return value from Windows APIs.

The rest of the article will focus on what the extension needs to do to function properly in protected mode. I'll be introducing some APIs, then showing the code in the sample extension that uses that API. Each topic corresponds to a button (or pair of buttons) in the band, so you can refer to the corresponding code while reading through the article.

Working Within Protected Mode Restrictions

IE 7 has several new APIs that extensions can use to perform functions that are restricted in protected mode. These APIs are in ieframe.dll. You can either link directly to these APIs by using the iepmapi.lib import library, or use LoadLibrary()/GetProcAddress() to get pointers to the functions at runtime. The latter method is the one you must use if you want your extension to load on versions of Windows prior to Vista.

Many of the features that perform privileged actions use a broker process, ieuser.exe. Since the IE process is running at low integrity, it can't do higher-privileged tasks on its own; ieuser.exe fulfills that role. You'll see this broker process mentioned often in this article and in Microsoft documentation.

Detecting Protected Mode at Runtime

To determine if our extension is running in a protected mode IE process, we use IEIsProtectedModeProcess():

HRESULT IEIsProtectedModeProcess(BOOL* pbResult);

If the return value is a successful HRESULT and *pbResult is TRUE, then protected mode is enabled. Based on the value returned in *pbResult, you can take different action in your code if necessary:

HRESULT hr;
BOOL bProtectedMode = FALSE;
 
  hr = IEIsProtectedModeProcess ( &bProtectedMode );
 
  if ( SUCCEEDED(hr) && bProtectedMode )
    // IE is running in protected mode
  else
    // IE isn't running in protected mode

The sample band extension calls this API on startup, and shows a message indicating the status of protected mode.

Writing to the File System

When protected mode is enabled, an extension can only write to a few directories under the user's profile. There are special low-integrity directories under the Temp, Temporary Internet Files, Cookies, and Favorites directories that are writable. IE also has some compatibility shims, which virtualize other commonly-used directories. (I haven't seen a full list of those "common directories." This post on the IEBlog mentions the shim feature, but doesn't elaborate on which directories are covered.) Write operations to those directories will be redirected to a subdirectory of Temporary Internet Files. If an extension tries to write to a sensitive location, like the windows directory, the operation will fail.

When an extension wants to write to the file system, it should use the IEGetWriteableFolderPath() API instead of GetSpecialFolderPath(), GetFolderPath(), or SHGetKnownFolderPath(). IEGetWriteableFolderPath() is aware of protected mode, and if the extension asks for a directory that it is not allowed to write to, IEGetWriteableFolderPath() will return E_ACCESSDENIED. The prototype for IEGetWriteableFolderPath() is:

HRESULT IEGetWriteableFolderPath(GUID clsidFolderID, LPWSTR* lppwstrPath);

The GUID is one of these, defined in knownfolders.h: FOLDERID_InternetCache, FOLDERID_Cookies, FOLDERID_History. There doesn't seem to be a GUID for the Temp directory, so I recommend using FOLDERID_InternetCache when you need to write temp files.

Here's a snippet that creates a temp file in the cache:

HRESULT hr;
LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
 
  hr = IEGetWriteableFolderPath(FOLDERID_InternetCache, &pwszCacheDir);
 
  if ( SUCCEEDED(hr) )
    {
    GetTempFileName(CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile);
    CoTaskMemFree(pwszCacheDir);
 
    // szTempFile now has the full path to the temp file.
    }

If IEGetWriteableFolderPath() succeeds, it allocates a buffer and returns its address in pwszCacheDir. We pass that directory to GetTempFileName(), then free the buffer with CoTaskMemFree().

IEGetWriteableFolderPath() is not just for writing temp files. An extension will also use it when it uses the protected mode version of the save file dialog, as explained in the Prompting the User to Save Files section below. The demo project uses this API when you click the Save Log button.

Writing to the Registry

Since the registry is a critical part of the system, it's crucial that code running in the browser not be allowed to change any parts of the registry that could allow malicious code to run. To this end, there is only one key that extensions can write to. As with the file system, this key is in a special low-rights area under the current user's profile. To get a handle to this key, call IEGetWriteableHKCU():

HRESULT IEGetWriteableHKCU(HKEY* phKey);

If it succeeds, you can use the returned HKEY in other registry APIs to write whatever data is necessary. The demo project does not use the registry, but this API is quite simple and there should be no trouble in using it.

Prompting the User to Save Files

When IE is running in protected mode, there is still a way for extensions to (indirectly) write to the file system, outside of the low-rights areas. An extension can show a common save file dialog by calling IEShowSaveFileDialog(). If the user enters a file name, the extension can then have IE write the file by calling IESaveFile(). Note that this operation always results in the user seeing the save file dialog; this ensures that the user is always aware that a file is about to be written.

The steps when saving a file are:

  1. Call IEShowSaveFileDialog() to show the save file dialog.
  2. Call IEGetWriteableFolderPath() to get the IE cache directory.
  3. Write the data to a temp file in the cache directory.
  4. Call IESaveFile() to copy that data to the filename that the user selected.
  5. Clean up the temp file.

IEShowSaveFileDialog() is a wrapper around the common file save dialog:

HRESULT IEShowSaveFileDialog(
  HWND    hwnd,
  LPCWSTR lpwstrInitialFileName,
  LPCWSTR lpwstrInitialDir,
  LPCWSTR lpwstrFilter,
  LPCWSTR lpwstrDefExt,
  DWORD   dwFilterIndex,
  DWORD   dwFlags,
  LPWSTR* lppwstrDestinationFilePath,
  HANDLE* phState
);

hwnd is a window owned by the extension, IE will use the topmost owner window as the parent window of the dialog. lppwstrDestinationFilePath is a pointer to an LPWSTR that is set to the file path that the user selects. This is only informational, since the extension can't write to that path directly. phState is a pointer to a HANDLE that is filled in if the user chooses a file, this handle is used when calling other APIs. The other parameters are used like the corresponding members in the OPENFILENAME struct.

IEShowSaveFileDialog() returns S_OK if the user chooses a filename, S_FALSE if he cancels the dialog, or a failure HRESULT if the API fails.

Here is the code in the demo project that saves the log to a file. We first call IEShowSaveFileDialog() to prompt the user for a file path:

void CBandDialog::OnSaveLog(UINT uCode, int nID, HWND hwndCtrl)
{
HRESULT hr;
HANDLE hState;
LPWSTR pwszSelectedFilename = NULL;
const DWORD dwSaveFlags =
        OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST |
        OFN_OVERWRITEPROMPT;
 
  // Get a filename from the user.
  hr = IEShowSaveFileDialog (
         m_hWnd, L"Saved log.txt", NULL,
         L"Text files|*.txt|All files|*.*|",
         L"txt", 1, dwSaveFlags, &pwszSelectedFilename,
         &hState );
 
  if ( S_OK != hr )
    return;

Next, we use IEGetWriteableFolderPath() to get the location of the cache directory that we can write to.

LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
 
  // Get the path to the IE cache dir, which is a dir that we're allowed
  // to write to in protected mode.
  hr = IEGetWriteableFolderPath ( FOLDERID_InternetCache, &pwszCacheDir );
 
  if ( SUCCEEDED(hr) )
    {
    // Get a temp file name in that dir.
    GetTempFileName ( CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile );
    CoTaskMemFree ( pwszCacheDir );
 
    // Write our data to that temp file.
    hr = WriteLogFile ( szTempFile );
    }

If everything has succeeded to far, we call another protected mode API, IESaveFile(). IESaveFile() takes the state handle that IEShowSaveFileDialog() returned, and the path to our temp file. Note that this HANDLE isn't a standard handle and doesn't need to be closed; after the IESaveFile() call, the HANDLE is automatically freed.

If for some reason we don't end up calling IESaveFile() -- for example, if an error happens while writing to the temp file -- we do need to clean up the HANDLE and any internal data that IEShowSaveFileDialog() allocated. We do this by calling IECancelSaveFile().

  if ( SUCCEEDED(hr) )
    {
    // If we wrote the file successfully, have IE save that data to
    // the path that the user chose.
    hr = IESaveFile ( hState, T2CW(szTempFile) );
 
    // Clean up our temp file.
    DeleteFile ( szTempFile );
    }
  else
    {
    // We couldn't complete the save operation, so cancel it.
    IECancelSaveFile ( hState );
    }

Enabling Communication Between Your Extension and Other Applications

All the file system and registry topics we've seen so far have been dealing with code running in the IE process. With the availability of virtualization and compatibility shims, it is straightforward for IE to restrict code running in its own process and prevent it from making calls to APIs that could end up damaging the system. Now we'll see a more complex subject, which has a more complex solution: IPC with another process running at a higher integrity level. We'll cover two different forms of IPC: kernel objects and window messages.

Creating IPC Objects

When an extension and a separate process want to communicate, that communication happens between the two pieces of code, without going through IE wrappers. The NT security APIs and the mandatory integrity level come into play, and by default communication from the extension to the separate app is blocked because the app is running at a higher integrity level than IE.

If the separate app creates a kernel object (for example, an event or mutex) that the extension needs to use, the app must lower the integrity level of the object before the extension can access it. The app can use the security APIs to change the ACL on the object and lower its integrity. The code below, taken from the MSDN article "Understanding and Working in Protected Mode Internet Explorer", takes a HANDLE to a kernel object and sets its integrity level to low.

// The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity
LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
 
bool SetObjectToLowIntegrity(
  HANDLE hObject, SE_OBJECT_TYPE type = SE_KERNEL_OBJECT)
{
bool bRet = false;
DWORD dwErr = ERROR_SUCCESS;
PSECURITY_DESCRIPTOR pSD = NULL;
PACL pSacl = NULL;
BOOL fSaclPresent = FALSE;
BOOL fSaclDefaulted = FALSE;
 
  if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
         LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
    {
    if ( GetSecurityDescriptorSacl (
           pSD, &fSaclPresent, &pSacl, &fSaclDefaulted ) )
      {
      dwErr = SetSecurityInfo (
                hObject, type, LABEL_SECURITY_INFORMATION,
                NULL, NULL, NULL, pSacl );
 
      bRet = (ERROR_SUCCESS == dwErr);
      }
 
    LocalFree ( pSD );
    }
 
  return bRet;
}

The sample code uses two mutexes whose purpose is to allow the extension to tell when the app is running. The DemoApp EXE creates them when the process starts, and the extension tries to open them when you click one of the Open Mutex buttons. Mutex 1 has the default integrity level, while mutex 2 is set to low integrity using the SetObjectToLowIntegrity() function listed above. This means that when protected mode is on, the extension will only be able to access mutex 2. Here are the results you'll see when you click both of the Open Mutex buttons:

Another effect of protected mode is that an extension can't have a separate app inherit a kernel object handle. For example, when protected mode is enabled, our extension can't create a file mapping object, run the separate app (passing TRUE for the bInheritHandles parameter to CreateProcess()), and have the app inherit the handle on the file mapping object.

HANDLE hMapping;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
 
  sa.bInheritHandle = TRUE;
 
  hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, &sa,
                                PAGE_READWRITE, 0, cbyData, NULL );
 
  // Omitted: Put data in the shared memory block...
 
  // Run the EXE and pass it the shared memory handle.
CString sCommandLine;
BOOL bSuccess;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
 
  sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /h:%p"),
                       hMapping );
 
  bSuccess = CreateProcess(
               NULL, sCommandLine.GetBuffer(0), NULL, NULL,
               TRUE,  // TRUE => the new process should inherit handles
               NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );

DemoApp would then read the handle value from the /h switch, and use that value to call MapViewOfFile() and read the data. This is a standard technique to make the new process automatically receive a handle to a kernel object, but when protected mode is enabled, the new process is actually launched by the broker process. Since the IE process doesn't directly launch the new process, handle inheritance doesn't work.

To work around this restriction, the extension can use a pre-defined name for an IPC object, and the separate app will be able to access that object since the object will have low integrity. If you don't want to use a pre-defined name, you can generate a name at runtime (for example, use a GUID for the name) and pass that name to the separate app:

  // Get a GUID that we'll use as the name of the shared memory object.
GUID guid = {0};
WCHAR wszGuid[64] = {0};
HRESULT hr;

  CoCreateGuid( &guid );
  StringFromGUID2( guid, wszGuid, _countof(wszGuid) );
 
  // Create the file mapping object, we don't need a SECURITY_ATTRIBUTES
  // struct since the handle won't be inherited.
HANDLE hMapping;
 
  hMapping = CreateFileMapping( INVALID_HANDLE_VALUE, NULL,
                                PAGE_READWRITE, 0, cbyData, CW2CT(wszGuid) );
 
  // Omitted: Put data in the shared memory block...
 
  // Run the EXE and pass it the name of the shared memory object.
  // Note that the bInheritHandles param to CreateProcess() is FALSE.
CString sCommandLine;
BOOL bSuccess;
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {0};
 
  sCommandLine.Format( _T("\"C:\\path\\to\\DemoApp.exe\" /n:%ls"),
                       wszGuid );
 
  bSuccess = CreateProcess(
               NULL, sCommandLine.GetBuffer(0), NULL, NULL,
               FALSE,  // FALSE => the new process does not inherit handles
               NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi );

Using this method, the EXE receives the IPC object's name on the command line. It can then call OpenFileMapping() to access the object. There is one complication with this method, however, in that you need to be careful about object lifetime management. When using handle inheritance, the sequence of events is:

  1. The extension creates the IPC object, giving it a reference count of 1.
  2. The extension launches the new process, which inherits the handle. This increases the object's reference count to 2.
  3. The extension can immediately close its handle since it doesn't need to the object anymore. The reference count drops to 1.
  4. The new process does whatever it needs to with the IPC object. Since it still has an open handle, the object remains around until the new process closes its handle.

If we used the above steps when passing just the name of the object to the EXE, we would create a race condition where the extension could close its handle (and therefore delete the IPC object) before the EXE has a chance to open a handle on it.

  1. The extension creates the IPC object, giving it a reference count of 1.
  2. The extension launches the new process, passing it the name of the IPC object. The reference count is still 1.
  3. The extension cannot close its handle right away, it needs to wait until the new process has opened a handle to the object. Some sort of synchronization is necessary to do this.
  4. The new process opens a handle to the object and reads the data. At this point, it can signal the extension to wake the extension's thread. It's now safe for the extension to close its handle.

What I chose to do in the sample project is have DemoApp read the data from shared memory before it creates the main dialog. The extension then calls WaitForInputIdle() after CreateProcess(), which makes the thread block until DemoApp's main dialog has been created and displayed. Once the DemoApp's thread goes idle, it has finished using the shared memory, and it's safe for the extension to close its handle.

The band demonstrates both methods of passing data through shared memory. When you click the Run EXE 1 button, the band writes the current date and time to shared memory, and passes a handle to DemoApp. When protected mode is enabled, this method will fail and DemoApp will report and invalid handle error. Clicking Run EXE 2 passes the name of the file mapping object to DemoApp, which will then show the data it reads from shared memory:

Accepting Window Messages

UIPI prevents certain window messages (and all messages with a value greater than or equal to WM_USER) from being sent from a lower-integrity process to a higher-integrity one. If your application needs to receive messages from your extension, you can call ChangeWindowMessageFilter() to allow one specific message through:

BOOL ChangeWindowMessageFilter(UINT message, DWORD dwFlag);

message is the value of the message, and dwFlag indicates whether the message should be allowed or blocked. Pass MSGFLT_ADD to allow the message, or MSGFLT_REMOVE to block it. Be very careful when processing window messages from other processes - if you accept data via a message, you must treat it as untrusted and you should validate it before acting on it. (Remember that inter-process messages can be sent from anywhere, and such messages can be used for attacks, as mentioned earlier.)

The demo project shows how to communicate via registered window messages. As with the mutex example, there are two messages. DemoApp allows the second message through the filter with this code in OnInitDialog():

  m_uRegisteredMsg1 = RegisterWindowMessage( REGISTERED_MSG1_NAME );
  m_uRegisteredMsg2 = RegisterWindowMessage( REGISTERED_MSG2_NAME );
  ChangeWindowMessageFilter( m_uRegisteredMsg2, MSGFLT_ADD );

When you click the two Send Message buttons in the band, you'll see these results:

The first message is not allowed through the filter, and SendMessage() returns 0.

Other Restrictions in Protected Mode

Running Other Applications

IE has another mechanism that prevents malicious code from communicating with or launching other processes. If an extension tries to start another process, IE will ask the user for permission before starting the process. For example, using the View Source command results in this prompt:

If your extension needs to run a separate EXE, you can add a registry key that tells IE that your EXE is trusted and can be run without a prompt. The registry key that controls this behavior is HKLM\Software\Microsoft\Internet Explorer\Low Rights\ElevationPolicy. Create a new GUID, then add a key under ElevationPolicy whose name is that GUID. In that new key, create three values:

  • AppName: The filename of the executable, for example "DempApp.exe".
  • AppPath: The directory where the EXE is located.
  • Policy: A DWORD set to 3.

If your installer doesn't create a key like that, IE itself will create one if you check the Do not show me the warning for this program again checkbox.

Drag and Drop to Other Applications

A similar prompt will be shown if you try to drag content from a web page and drop it in another app:

This prompt can be suppressed with a registry key as well. The format is the same as described above, but your app's key goes under DragDrop instead of ElevationPolicy.

DemoApp registers as a drop target, and if you select text in IE and drag it into the DemoApp dialog, it will show a message indicating that it received the drag:

Further Reading

Understanding and Working in Protected Mode Internet Explorer

IEBlog: Protected Mode in Vista IE7

Copyright and License

This article is copyrighted material, ©2007 by Michael Dunn, all rights reserved. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.

The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefitting from my code) but is not required. Attribution in your own source code is also appreciated but not required.

Revision History

May 21, 2007: Article first published.
May 24, 2007: Added License section. Fixed IEExtension project in the sample code, it was missing the IDL file.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Michael Dunn
Software Developer (Senior) VMware
United States United States
Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.
 
Mike was a VC MVP from 2005 to 2009.

Comments and Discussions

 
GeneralCalling an Out-of-Proc COM server from IE (in PE) PinmemberBati93-Sep-09 8:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 21 May 2007
Article Copyright 2007 by Michael Dunn
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid