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

Tagged as

Go to top

A Quick-Start Guide of Process Mandatory Level Checking and Self-elevation under UAC

, 23 Mar 2010
Rate this:
Please Sign up or sign in to vote.
This example demonstrates how to check the privilege level of the current process, and how to self-elevate the process by giving explicit consent with the Consent UI.

Introduction

User Account Control (UAC) is a new security component in Windows Vista and newer operating systems. With UAC fully enabled, interactive administrators normally run with least user privileges. This article and the attached code samples demonstrate these frequently asked coding scenarios related to UAC.

  1. How to check if the current process is running as administrator?
  2. How to know if the primary access token of the current process belongs to user account that is a member of the local Administrators group, even if it currently is not elevated?
  3. How to check if the current process is elevated? The elevation information is available to only Windows Vista and newer operating systems because there was no UAC and "elevation" before Windows Vista.
  4. How to get the integrity level of the current process (System/High/Medium/Low/Unknown)? The integrity level information is available to only Windows Vista and newer operating systems because there was no UAC and "integrity level" before Windows Vista.
  5. How to show a UAC shield icon on the UI for tasks that require elevation?
  6. How to self-elevate the current process?
  7. How to automatically elevate the process when it's started up?

We provide code samples to demonstrate the above how-to scenarios in three programming languages (native VC++, VC#, VB.NET) to meet the needs of different developers.

Language Sample
VC++ CppUACSelfElevation
VC# CSUACSelfElevation
VB.NET VBUACSelfElevation

The code samples are part of Microsoft All-In-One Code Framework, which is a centralized code sample solution from Microsoft. You can download the code from this CodeProject article and from the project's download page: http://1code.codeplex.com/releases/.

Background

Microsoft All-In-One Code Framework delineates the framework and skeleton of most Microsoft development techniques (e.g., COM, Data Access, IPC) using typical sample codes in different programming languages (e.g., Visual C#, VB.NET, Visual C++). Each sample is elaborately selected, composed, and documented to demonstrate one frequently-asked, tested or used coding scenario based on Microsoft's support experience in MSDN newsgroups and forums.

Demo

This is a quick demo of the attached UAC samples.

Step 1. After you successfully build the sample project in Visual Studio 2008, you will get an application depending on the programming language that you are using: CppUACSelfElevation.exe / CSUACSelfElevation.exe / VBUACSelfElevation.exe.

Step 2. Run the application as a protected administrator on a Windows Vista or Windows 7 system with UAC fully enabled. The application should display the following content on the main dialog. There is a UAC shield icon on the Self-elevate button.

Step 3. Click on the Self-elevate button. You will see a Consent UI.

Step 4. Click Yes to approve the elevation. The original application will then be started and display the following content on the main dialog.

The Self-elevate button on the dialog does not have the UAC shield icon this time. That is, the application is running as elevated administrator. The elevation succeeds. If you click on the Self-elevate button again, the application will tell you that it is running as administrator.

Step 5. Click OK to close the application.

Using the Code

The code introduced in this section is for VC++ developers only. You can find the VC# and VB.NET implementations in the CSUACSelfElevation and VBUACSelfElevation sample packages.

  1. How to check if the current process is running as administrator?
    /// <summary>
    /// The function checks whether the current process is run as administrator.
    /// In other words, it dictates whether the primary access token of the
    /// process belongs to user account that is a member of the local
    /// Administrators group and it is elevated.
    /// </summary>
    /// <returns>
    /// Returns true if the primary access token of the process belongs to user
    /// account that is a member of the local Administrators group and it is
    /// elevated. Returns false if the token does not.
    /// </returns>
    internal bool IsRunAsAdmin()
    {
        WindowsIdentity id = WindowsIdentity.GetCurrent();
        WindowsPrincipal principal = new WindowsPrincipal(id);
        return principal.IsInRole(WindowsBuiltInRole.Administrator);
    }
  2. How to know if the primary access token of the current process belongs to user account that is a member of the local Administrators group, even if it currently is not elevated?
    /// <summary>
    /// The function checks whether the primary access token of the process belongs
    /// to user account that is a member of the local Administrators group, even if
    /// it currently is not elevated.
    /// </summary>
    /// <returns>
    /// Returns true if the primary access token of the process belongs to user
    /// account that is a member of the local Administrators group. Returns false
    /// if the token does not.
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    internal bool IsUserInAdminGroup()
    {
        bool fInAdminGroup = false;
        SafeTokenHandle hToken = null;
        SafeTokenHandle hTokenToCheck = null;
        IntPtr pElevationType = IntPtr.Zero;
        IntPtr pLinkedToken = IntPtr.Zero;
        int cbSize = 0;
    
        try
        {
            // Open the access token of the current process for query and duplicate.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY | NativeMethod.TOKEN_DUPLICATE, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Determine whether system is running Windows Vista or later operating
            // systems (major version >= 6) because they support linked tokens, but
            // previous versions (major version < 6) do not.
            if (Environment.OSVersion.Version.Major >= 6)
            {
                // Running Windows Vista or later (major version >= 6).
                // Determine token type: limited, elevated, or default.
    
                // Allocate a buffer for the elevation type information.
                cbSize = sizeof(TOKEN_ELEVATION_TYPE);
                pElevationType = Marshal.AllocHGlobal(cbSize);
                if (pElevationType == IntPtr.Zero)
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
    
                // Retrieve token elevation type information.
                if (!NativeMethod.GetTokenInformation(hToken,
                    TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType,
                    cbSize, out cbSize))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
    
                // Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET.
                TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)
                    Marshal.ReadInt32(pElevationType);
    
                // If limited, get the linked elevated token for further check.
                if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
                {
                    // Allocate a buffer for the linked token.
                    cbSize = IntPtr.Size;
                    pLinkedToken = Marshal.AllocHGlobal(cbSize);
                    if (pLinkedToken == IntPtr.Zero)
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
    
                    // Get the linked token.
                    if (!NativeMethod.GetTokenInformation(hToken,
                        TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken,
                        cbSize, out cbSize))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
    
                    // Marshal the linked token value from native to .NET.
                    IntPtr hLinkedToken = Marshal.ReadIntPtr(pLinkedToken);
                    hTokenToCheck = new SafeTokenHandle(hLinkedToken);
                }
            }
    
            // CheckTokenMembership requires an impersonation token. If we just got
            // a linked token, it already is an impersonation token.  If we did not
            // get a linked token, duplicate the original into an impersonation
            // token for CheckTokenMembership.
            if (hTokenToCheck == null)
            {
                if (!NativeMethod.DuplicateToken(hToken,
                    SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    out hTokenToCheck))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
    
            // Check if the token to be checked contains admin SID.
            WindowsIdentity id = 
    		new WindowsIdentity(hTokenToCheck.DangerousGetHandle());
            WindowsPrincipal principal = new WindowsPrincipal(id);
            fInAdminGroup = principal.IsInRole(WindowsBuiltInRole.Administrator);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (hTokenToCheck != null)
            {
                hTokenToCheck.Close();
                hTokenToCheck = null;
            }
            if (pElevationType != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pElevationType);
                pElevationType = IntPtr.Zero;
            }
            if (pLinkedToken != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pLinkedToken);
                pLinkedToken = IntPtr.Zero;
            }
        }
    
        return fInAdminGroup;
    }
  3. How to check if the current process is elevated? The elevation information is available to only Windows Vista and newer operating systems because there was no UAC and "elevation" before Windows Vista.
    /// <summary>
    /// The function gets the elevation information of the current process. It
    /// dictates whether the process is elevated or not. Token elevation is only
    /// available on Windows Vista and newer operating systems, thus
    /// IsProcessElevated throws a C++ exception if it is called on systems prior
    /// to Windows Vista. It is not appropriate to use this function to determine
    /// whether a process is run as administrator.
    /// </summary>
    /// <returns>
    /// Returns true if the process is elevated. Returns false if it is not.
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    /// <remarks>
    /// TOKEN_INFORMATION_CLASS provides TokenElevationType to check the elevation
    /// type (TokenElevationTypeDefault / TokenElevationTypeLimited /
    /// TokenElevationTypeFull) of the process. It is different from TokenElevation
    /// in that, when UAC is turned off, elevation type always returns
    /// TokenElevationTypeDefault even though the process is elevated (Integrity
    /// Level == High). In other words, it is not safe to say if the process is
    /// elevated based on elevation type. Instead, we should use TokenElevation.
    /// </remarks>
    internal bool IsProcessElevated()
    {
        bool fIsElevated = false;
        SafeTokenHandle hToken = null;
        int cbTokenElevation = 0;
        IntPtr pTokenElevation = IntPtr.Zero;
    
        try
        {
            // Open the access token of the current process with TOKEN_QUERY.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Allocate a buffer for the elevation information.
            cbTokenElevation = Marshal.SizeOf(typeof(TOKEN_ELEVATION));
            pTokenElevation = Marshal.AllocHGlobal(cbTokenElevation);
            if (pTokenElevation == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            // Retrieve token elevation information.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenElevation, pTokenElevation,
                cbTokenElevation, out cbTokenElevation))
            {
                // When the process is run on operating systems prior to Windows
                // Vista, GetTokenInformation returns false with the error code
                // ERROR_INVALID_PARAMETER because TokenElevation is not supported
                // on those operating systems.
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Marshal the TOKEN_ELEVATION struct from native to .NET object.
            TOKEN_ELEVATION elevation = (TOKEN_ELEVATION)Marshal.PtrToStructure(
                pTokenElevation, typeof(TOKEN_ELEVATION));
    
            // TOKEN_ELEVATION.TokenIsElevated is a non-zero value if the token
            // has elevated privileges; otherwise, a zero value.
            fIsElevated = (elevation.TokenIsElevated != 0);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (pTokenElevation != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pTokenElevation);
                pTokenElevation = IntPtr.Zero;
                cbTokenElevation = 0;
            }
        }
    
        return fIsElevated;
    }
  4. How to get the integrity level of the current process (System/High/Medium/Low/Unknown)? The integrity level information is available to only Windows Vista and newer operating systems because there was no UAC and "integrity level" before Windows Vista.
    /// <summary>
    /// The function gets the integrity level of the current process. Integrity
    /// level is only available on Windows Vista and newer operating systems, thus
    /// GetProcessIntegrityLevel throws a C++ exception if it is called on systems
    /// prior to Windows Vista.
    /// </summary>
    /// <returns>
    /// Returns the integrity level of the current process. It is usually one of
    /// these values:
    ///
    ///    SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level. It is used
    ///    by processes started by the Anonymous group. Blocks most write access.
    ///    (SID: S-1-16-0x0)
    ///
    ///    SECURITY_MANDATORY_LOW_RID - means low integrity level. It is used by
    ///    Protected Mode Internet Explorer. Blocks write access to most objects
    ///    (such as files and registry keys) on the system. (SID: S-1-16-0x1000)
    ///
    ///    SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level. It is
    ///    used by normal applications being launched while UAC is enabled.
    ///    (SID: S-1-16-0x2000)
    ///
    ///    SECURITY_MANDATORY_HIGH_RID - means high integrity level. It is used
    ///    by administrative applications launched through elevation when UAC is
    ///    enabled, or normal applications if UAC is disabled and the user is an
    ///    administrator. (SID: S-1-16-0x3000)
    ///
    ///    SECURITY_MANDATORY_SYSTEM_RID - means system integrity level. It is
    ///    used by services and other system-level applications (such as Wininit,
    ///    Winlogon, Smss, etc.)  (SID: S-1-16-0x4000)
    ///
    /// </returns>
    /// <exception cref="System.ComponentModel.Win32Exception">
    /// When any native Windows API call fails, the function throws a Win32Exception
    /// with the last error code.
    /// </exception>
    internal int GetProcessIntegrityLevel()
    {
        int IL = -1;
        SafeTokenHandle hToken = null;
        int cbTokenIL = 0;
        IntPtr pTokenIL = IntPtr.Zero;
    
        try
        {
            // Open the access token of the current process with TOKEN_QUERY.
            if (!NativeMethod.OpenProcessToken(Process.GetCurrentProcess().Handle,
                NativeMethod.TOKEN_QUERY, out hToken))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Then we must query the size of the integrity level information
            // associated with the token. Note that we expect GetTokenInformation
            // to return false with the ERROR_INSUFFICIENT_BUFFER error code
            // because we've given it a null buffer. On exit cbTokenIL will tell
            // the size of the group information.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, IntPtr.Zero, 0,
                out cbTokenIL))
            {
                int error = Marshal.GetLastWin32Error();
                if (error != NativeMethod.ERROR_INSUFFICIENT_BUFFER)
                {
                    // When the process is run on operating systems prior to
                    // Windows Vista, GetTokenInformation returns false with the
                    // ERROR_INVALID_PARAMETER error code because
                    // TokenIntegrityLevel is not supported on those OS's.
                    throw new Win32Exception(error);
                }
            }
    
            // Now we allocate a buffer for the integrity level information.
            pTokenIL = Marshal.AllocHGlobal(cbTokenIL);
            if (pTokenIL == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Now we ask for the integrity level information again. This may fail
            // if an administrator has added this account to an additional group
            // between our first call to GetTokenInformation and this one.
            if (!NativeMethod.GetTokenInformation(hToken,
                TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pTokenIL, cbTokenIL,
                out cbTokenIL))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
    
            // Marshal the TOKEN_MANDATORY_LABEL struct from native to .NET object.
            TOKEN_MANDATORY_LABEL tokenIL = (TOKEN_MANDATORY_LABEL)
                Marshal.PtrToStructure(pTokenIL, typeof(TOKEN_MANDATORY_LABEL));
    
            // Integrity Level SIDs are in the form of S-1-16-0xXXXX. (e.g.
            // S-1-16-0x1000 stands for low integrity level SID). There is one
            // and only one subauthority.
            IntPtr pIL = NativeMethod.GetSidSubAuthority(tokenIL.Label.Sid, 0);
            IL = Marshal.ReadInt32(pIL);
        }
        finally
        {
            // Centralized cleanup for all allocated resources.
            if (hToken != null)
            {
                hToken.Close();
                hToken = null;
            }
            if (pTokenIL != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pTokenIL);
                pTokenIL = IntPtr.Zero;
                cbTokenIL = 0;
            }
        }
    
        return IL;
    }
  5. How to show an UAC shield icon on the UI for tasks that require elevation?
    // Get the process elevation information.
    bool fIsElevated = IsProcessElevated();
    
    // Update the Self-elevate button to show the UAC shield icon on
    // the UI if the process is not elevated.
    this.btnElevate.FlatStyle = FlatStyle.System;
    NativeMethod.SendMessage(btnElevate.Handle, NativeMethod.BCM_SETSHIELD,
        0, fIsElevated ? IntPtr.Zero : (IntPtr)1);
  6. How to self-elevate the current process?
    // Launch itself as administrator
    ProcessStartInfo proc = new ProcessStartInfo();
    proc.UseShellExecute = true;
    proc.WorkingDirectory = Environment.CurrentDirectory;
    proc.FileName = Application.ExecutablePath;
    proc.Verb = "runas";
    
    try
    {
        Process.Start(proc);
    }
    catch
    {
        // The user refused the elevation.
        // Do nothing and return directly ...
        return;
    }
    
    Application.Exit();  // Quit itself
  7. How to automatically elevate the process when it's started up?

    If your application always requires administrative privileges, such as during an installation step, the operating system can automatically prompt the user for privileges elevation each time your application is invoked.

    If a specific kind of resource (RT_MANIFEST) is found embedded within the application executable, the system looks for the <trustInfo> section and parses its contents. Here is an example of this section in the manifest file:

    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
       <security>
          <requestedPrivileges>
             <requestedExecutionLevel
                level="requireAdministrator"
             />
          </requestedPrivileges>
       </security>
    </trustInfo>

Three different values are possible for the level attribute:

  1. requireAdministrator
    The application must be started with Administrator privileges; it won't run otherwise.
  2. highestAvailable
    The application is started with the highest possible privileges.  If the user is logged on with an Administrator account, an elevation prompt appears. If the user is a Standard User, the application is started (without any elevation prompt) with these standard privileges.
  3. asInvoker
    The application is started with the same privileges as the calling application.

To configure the elevation level in this Visual C# Windows Forms project, open the project's properties, turn to the Security tab, check the checkbox "Enable ClickOnce Security Settings", check "This is a fulltrust application" and close the application Properties page. This creates an app.manifest file and configures the project to embed the manifest. You can open the "app.manifest" file from Solution Explorer by expanding the Properties folder. The file has the following content by default.

<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" 
	xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC Manifest Options
            If you want to change the Windows User Account Control level replace the
            requestedExecutionLevel node with one of the following.
        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
            If you want to utilize File and Registry Virtualization for backward
            compatibility then delete the requestedExecutionLevel node.
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
      <applicationRequestMinimum>
        <PermissionSet class="System.Security.PermissionSet" version="1"
        Unrestricted="true" ID="Custom" SameSite="site" />
        <defaultAssemblyRequest permissionSetReference="Custom" />
      </applicationRequestMinimum>
    </security>
  </trustInfo>
</asmv1:assembly>	

Here we are focusing on the line:

<requestedexecutionlevel uiaccess="false" level="asInvoker" />

You can change it to be:

<requestedexecutionlevel level="requireAdministrator" uiaccess="false" />

to require the application always be started with Administrator privileges.

Points of Interest

You may be particularly interested in how to perform the above UAC operations in native VC++ and VB.NET. You can find the answer in CppUACSelfElevation and VBUACSelfElevation sample packages.

Each sample package is accompanied with a ReadMe.txt file that documents the sample in detail.

If you have any feedback regarding the code samples or the entire All-In-One Code Framework project, please feel free to contact codefxf@microsoft.com.

History

  • 16th March, 2010: Initial post
  • 22nd March, 2010: Article updated

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Microsoft All-In-One Code Framework delineates the framework and skeleton of Microsoft development techniques through typical sample codes in three popular programming languages (Visual C#, VB.NET, Visual C++). Each sample is elaborately selected, composed, and documented to demonstrate one frequently-asked, tested or used coding scenario based on our support experience in MSDN newsgroups and forums. If you are a software developer, you can fill the skeleton with blood, muscle and soul. If you are a software tester or a support engineer like us, you may extend the sample codes a little to fit your specific test scenario or refer your customer to this project if the customer's question coincides with what we collected.
http://cfx.codeplex.com/

Comments and Discussions

 
GeneralMy vote of 5 PinmemberSimon Raffl27-May-13 5:42 
QuestionMy vote of 5 PinmemberReg Hammond21-May-13 19:39 
GeneralMy vote of 5 Pinmembergndnet12-Oct-12 5:10 
QuestionElevate without re-starting? PingroupPaul @ The Computer Station28-Mar-12 23:29 
GeneralMy vote of 5 PinmemberChintanhiremath14-Feb-12 17:54 
GeneralMy vote of 5 Pinmemberwizardess19-Dec-11 15:21 
SuggestionGood work ! Pinmemberrhermens@gomation.com23-Sep-11 3:19 
QuestionMy vote of 5 PinmemberFilip D'haene4-Sep-11 13:01 
GeneralMy vote of 5 PinmemberP.Joshi18-Aug-11 7:42 
GeneralRe: My vote of 5 Pinmemberwizardess19-Dec-11 15:25 
GeneralConvert to Delphi 7 Pinmembereglx014-Jun-11 4:53 
GeneralMy vote of 5 PinmemberRenatoK6-Apr-11 13:15 
QuestionGood Work, But can i check high itegrity from medium itegrity. Pinmemberramveers14-Nov-10 23:29 
GeneralExcellent PinmemberDotnetglobal4-May-10 16:09 
GeneralAppreciating. PinmemberAjay Vijayvargiya23-Apr-10 20:23 
GeneralVery detailed! Very instructive~! Pinmembersmwikipedia19-Apr-10 21:59 
GeneralNice PinmemberGlimmerMan23-Mar-10 2:55 
GeneralRe: Nice Pinmemberjialge23-Mar-10 20:59 
GeneralRefurbished Code PinmemberKarstenK22-Mar-10 23:10 
GeneralRe: Refurbished Code Pinmemberjialge23-Mar-10 20:56 
GeneralRe: Refurbished Code PinmemberKarstenK23-Mar-10 21:23 
GeneralMy vote of 5! PinmemberBikashG18-Mar-10 4:56 
GeneralGreat work!! Pinmemberppooddoo17-Mar-10 5:35 
GeneralMy vote of 1 Pinmembereds16-Mar-10 9:05 
GeneralRe: My vote of 1 Pinmemberhiworld198017-Mar-10 5:30 

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
Web01 | 2.8.140905.1 | Last Updated 23 Mar 2010
Article Copyright 2010 by All-In-One Code Framework
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid