Category Archives: Entertainment

Generating a type library for Visual Basic 6 ActiveX Components

When generating setup code or isolation code (for Side by Side assemblies and Registration-free COM), VB6 ActiveX Components can be a little troublesome.

ActiveX exes in particular can be difficult, with the WiX heat tool and various SxS tools not being able to harvest COM registration information from them properly.

A good way of avoiding these problems is to generate a type library (.tlb) from the VB6 component, and then pointing the code generation tools at the type library.

In VB6, you can generate a Type Library (.tlb) file for an ActiveX component:

  1. Project > <Project Name> Properties…
  2. Go to the Component tab
  3. Check Remote Server Files
  4. Click OK and build the project

image

You should now see a .tlb and .vbr file next to your binary.

The .vbr file is a text file containing registry information, which will be very helpful if the type library is still missing information you want to use for your isolation or installation authoring.

Reference

How To Make a Typelib (.TLB) File for ActiveX Components @ MSDN

When System.Diagnostics.Process creates a process, it inherits inheritable handles from the parent process.

This post covers the cause of a bug I ran into at work.

Our application would check for available updates when it started, and if they were found, it would launch the installer directly and exit the application immediately, so that the installer could run without encountering file locks.

The installer was complaining our executable was still locked, meaning it had to schedule the overwrite of the old file with the new one after a reboot.

After quite a bit of troubleshooting, it looked like while the application was launching msiexec and closing down successfully, msiexec was still grabbing the same lock handle to the exe for no good reason.

So what was happening?

System.Diagnostics.Process

When you create a new process from a .NET application, you would use the classes in the System.Diagnostics namespace. Specifically, Process and ProcessStartInfo. As we were in this case.

These wrap the Windows API function CreateProcess and its alternatives and supporting types.

If we look at the CreateProcess definition, there’s a boolean argument in there called bInheritHandles:

BOOL WINAPI CreateProcess(
__in_opt     LPCTSTR lpApplicationName,
__inout_opt  LPTSTR lpCommandLine,
__in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in         BOOL bInheritHandles,
__in         DWORD dwCreationFlags,
__in_opt     LPVOID lpEnvironment,
__in_opt     LPCTSTR lpCurrentDirectory,
__in         LPSTARTUPINFO lpStartupInfo,
__out        LPPROCESS_INFORMATION lpProcessInformation
);

What does the Windows API documentation say about this?

If this parameter is TRUE, each inheritable handle in the calling process is inherited by the new process. If the parameter is FALSE, the handles are not inherited. Note that inherited handles have the same value and access rights as the original handles.

Inheritable handles?

Handle Inheritance

A child process can inherit handles from its parent process. An inherited handle is valid only in the context of the child process. To enable a child process to inherit open handles from its parent process, use the following steps.

  1. Create the handle with the bInheritHandle member of the SECURITY_ATTRIBUTES structure set to TRUE.
  2. Create the child process using the CreateProcess function, with the bInheritHandles parameter set to TRUE.

Well if we look at the internals of .NET, and look at how the Process class is calling CreateProcess, we find that it’s passing bInheritHandles as TRUE.

So this means if we start a process using the System.Diagnostics classes, the child process will inherit our inheritable handles.

What was happening is the locked file handle to wsClient.exe was being inherited from the parent process to the Windows Installer, so the executable was remaining locked even when the parent process exited.

Solution

One solution is to avoid System.Diagnostics in this particular instance, and use CreateProcess manually when we launch our child process, to ensure it doesn’t inherit handles.

I would recommend you use the System.Diagnostics classes in any other case.

Code

using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace CreateProcessTest
{
    [StructLayout(LayoutKind.Sequential)]
    internal class ProcessInformation
    {
         public IntPtr hProcess = IntPtr.Zero;
         public IntPtr hThread = IntPtr.Zero;
         public int dwProcessId;
         public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class StartupInfo
    {
         public int cb;
         public IntPtr lpReserved = IntPtr.Zero;
         public IntPtr lpDesktop = IntPtr.Zero;
         public IntPtr lpTitle = IntPtr.Zero;
         public int dwX;
         public int dwY;
         public int dwXSize;
         public int dwYSize;
         public int dwXCountChars;
         public int dwYCountChars;
         public int dwFillAttribute;
         public int dwFlags;
         public short wShowWindow;
         public short cbReserved2;
         public IntPtr lpReserved2 = IntPtr.Zero;
         public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false);
         public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false);
         public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false);

         public StartupInfo()
         {
             dwY = 0;
             cb = Marshal.SizeOf(this);
         }

         public void Dispose()
         {
             // close the handles created for child process
             if (hStdInput != null && !hStdInput.IsInvalid)
             {
                 hStdInput.Close();
                 hStdInput = null;
             }

             if (hStdOutput != null && !hStdOutput.IsInvalid)
             {
                 hStdOutput.Close();
                 hStdOutput = null;
             }

             if (hStdError == null || hStdError.IsInvalid) return;

             hStdError.Close();
             hStdError = null;
         }
     }

     [StructLayout(LayoutKind.Sequential)]
     internal class SecurityAttributes
     {
         public int nLength = 12;
         public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false);
         public bool bInheritHandle;
     }

     [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
     internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
     {
         internal SafeLocalMemHandle() : base(true) { }

         [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
         internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle)
         {
             SetHandle(existingHandle);
         }

         [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
         internal static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string stringSecurityDescriptor,
             int stringSDRevision, out SafeLocalMemHandle pSecurityDescriptor, IntPtr securityDescriptorSize);

         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll")]
         private static extern IntPtr LocalFree(IntPtr hMem);

         protected override bool ReleaseHandle()
         {
             return (LocalFree(handle) == IntPtr.Zero);
         }
     }

     public static class Test
     {
         const int normalPriorityClass = 0x0020;

         [DllImport("Kernel32", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
         internal static extern bool CreateProcess(
             [MarshalAs(UnmanagedType.LPTStr)]string applicationName,
             StringBuilder commandLine,
             SecurityAttributes processAttributes,
             SecurityAttributes threadAttributes,
             bool inheritHandles,
             int creationFlags,
             IntPtr environment,
             [MarshalAs(UnmanagedType.LPTStr)]string currentDirectory,
             StartupInfo startupInfo,
             ProcessInformation processInformation
        );

        public static void Main(string[] args)
        {
            // We can use the string builder to build up our full command line, including arguments
            var sb = new StringBuilder("notepad.exe");
            var processInformation = new ProcessInformation();
            var startupInfo = new StartupInfo();
            var processSecurity = new SecurityAttributes();
            var threadSecurity = new SecurityAttributes();

            processSecurity.nLength = Marshal.SizeOf(processSecurity);
            threadSecurity.nLength = Marshal.SizeOf(threadSecurity);

            if (CreateProcess(null, sb, processSecurity, threadSecurity, false, normalPriorityClass,
                 IntPtr.Zero, null, startupInfo, processInformation))
            {
                // Process was created successfully
                return;
            }

            // We couldn't create the process, so raise an exception with the details.
            throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
         }
     }
}

Utilities

I used the invaluable SysInternals tools Process Monitor, Process Explorer and Handle to diagnose what was going on.

References

Handle Inheritance @ MSDN

Child process keeps parent’s socket open – Diagnostics.Process and Net.TcpListene @ social.msdn.microsoft.com

.NET Decompilers

Reflector history

The de facto .NET decompiler has been Reflector for what seems like forever.

Reflector was originally developed by Lutz Roeder, and made freely available to the community.

It was acquired by Red Gate Software in August 2008, who promised to maintain it as a free product.

They reneged on this promise in February 2011, stating that Reflector would become a commercial product.

This created a community backlash, culminating in creation and/or promotion of several free rival products through open source or offered by other vendors.

Red Gate backpedalled in April 2011 by saying that the final 6.8 version of Reflector would continue to be free, while future releases of Reflector (moving forward with version 7.x) would be paid for.

List of .NET Decompilers

Name Company License Language Support Rating Comments
ILSpy SharpDevelop Open Source C# 4 except for expression trees, dynamic, fixed fields   ILSpy development was started directly as a result of Red Gate’s announcement that Reflector would be paid for.
CodeReflect DevExtras Free (Commercially supported) MSIL, C#, VB.NET    
JustDecompile Telerik Free (Commerically supported)      
dotPeek JetBrains Free (Commercially supported) C#    
Reflector 6.8 Red Gate Free MSIL, C#, VB.NET, more through plugins   Any future versions (7.x) are paid for. You need to be an existing Reflector user, and allow your Reflector to update to version 6.8.

How to check if the current user is an Administrator (even if UAC is on)

There may be a scenario where you want to determine from code if the current user is an Administrator.

One example of this which I have had to deal with is checking for software updates.

Say your application contacts a service to see if there is a newer version of the application available; if so, you can download and run the installer.

Imagine that the installer requires admin privileges; you don’t want to run the installer if the current user does not have administrative privileges.

So how can we check if the user is an admin or not?

In VB6, C++ etc

There is a Windows API function you can use very easily to see if the current user is an admin: IsUserAnAdmin.

BOOL IsUserAnAdmin(void);

Visual Basic 6 Declaration

Private Declare Function IsUserAnAdmin Lib “Shell32″ Alias “#680″ () As Integer

While you can still use this, it is actually deprecated, and the documentation recommends you call the CheckTokenMembership function instead (which IsUserAnAdmin is a wrapper for).

.NET

C#.NET

using System;
using System.Security.Principal;

var identity = WindowsIdentity.GetCurrent();
if (identity == null) throw new InvalidOperationException("Couldn't get the current user identity");
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);

User Account Control (UAC)

A problem arises when you use any of the above code on a machine that has UAC enabled, and the process is not elevated.

While the user may be an administrator, when the process is not elevated yet, the user has a split token – which doesn’t have the administrator privileges.

A way around this is to use the GetTokenInformation API call to inspect the token to see if it’s a split token. In most cases this will mean that UAC is on and the current user is an administrator.

This is not 100% reliable (see References) but it’s probably the best we can do for now.

C#.NET

This code is slightly easier in .NET, as there’s already a fair amount of code we don’t have to write to get the current process’s token.

First, we’ll need some code to support the GetTokenInformation API call:

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool GetTokenInformation(IntPtr tokenHandle, TokenInformationClass tokenInformationClass, IntPtr tokenInformation, int tokenInformationLength, out int returnLength);

/// <summary>
/// Passed to <see cref="GetTokenInformation"/> to specify what
/// information about the token to return.
/// </summary>
enum TokenInformationClass
{
     TokenUser = 1,
     TokenGroups,
     TokenPrivileges,
     TokenOwner,
     TokenPrimaryGroup,
     TokenDefaultDacl,
     TokenSource,
     TokenType,
     TokenImpersonationLevel,
     TokenStatistics,
     TokenRestrictedSids,
     TokenSessionId,
     TokenGroupsAndPrivileges,
     TokenSessionReference,
     TokenSandBoxInert,
     TokenAuditPolicy,
     TokenOrigin,
     TokenElevationType,
     TokenLinkedToken,
     TokenElevation,
     TokenHasRestrictions,
     TokenAccessInformation,
     TokenVirtualizationAllowed,
     TokenVirtualizationEnabled,
     TokenIntegrityLevel,
     TokenUiAccess,
     TokenMandatoryPolicy,
     TokenLogonSid,
     MaxTokenInfoClass
}

/// <summary>
/// The elevation type for a user token.
/// </summary>
enum TokenElevationType
{
    TokenElevationTypeDefault = 1,
    TokenElevationTypeFull,
    TokenElevationTypeLimited
}

Then, the actual code to detect if the user is an Administrator (returning true if they are, otherwise false).

var identity = WindowsIdentity.GetCurrent();
if (identity == null) throw new InvalidOperationException("Couldn't get the current user identity");
var principal = new WindowsPrincipal(identity);

// Check if this user has the Administrator role. If they do, return immediately.
// If UAC is on, and the process is not elevated, then this will actually return false.
if (principal.IsInRole(WindowsBuiltInRole.Administrator)) return true;

// If we're not running in Vista onwards, we don't have to worry about checking for UAC.
if (Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version.Major < 6)
{
     // Operating system does not support UAC; skipping elevation check.
     return false;
}

int tokenInfLength = Marshal.SizeOf(typeof(int));
IntPtr tokenInformation = Marshal.AllocHGlobal(tokenInfLength);

try
{
    var token = identity.Token;
    var result = GetTokenInformation(token, TokenInformationClass.TokenElevationType, tokenInformation, tokenInfLength, out tokenInfLength);

    if (!result)
    {
        var exception = Marshal.GetExceptionForHR( Marshal.GetHRForLastWin32Error() );
        throw new InvalidOperationException("Couldn't get token information", exception);
    }

    var elevationType = (TokenElevationType)Marshal.ReadInt32(tokenInformation);
    
    switch (elevationType)
    {
        case TokenElevationType.TokenElevationTypeDefault:
            // TokenElevationTypeDefault - User is not using a split token, so they cannot elevate.
            return false;
        case TokenElevationType.TokenElevationTypeFull:
            // TokenElevationTypeFull - User has a split token, and the process is running elevated. Assuming they're an administrator.
            return true;
        case TokenElevationType.TokenElevationTypeLimited:
            // TokenElevationTypeLimited - User has a split token, but the process is not running elevated. Assuming they're an administrator.
            return true;
        default:
            // Unknown token elevation type.
            return false;
     }
}
finally
{     
    if (tokenInformation != IntPtr.Zero) Marshal.FreeHGlobal(tokenInformation);
}

Visual Basic 6 (VB6)

For Visual Basic 6, there’s some additional code, as we need to get the token for the current process, and use more calls to also get the operating system version.

Type OSVERSIONINFO

dwOSVersionInfoSize As Long

dwMajorVersion As Long

dwMinorVersion As Long

dwBuildNumber As Long

dwPlatformId As Long

szCSDVersion As String * 128

End Type

‘ dwPlatformId values

Public Const VER_PLATFORM_WIN32s = 0

Public Const VER_PLATFORM_WIN32_WINDOWS = 1

Public Const VER_PLATFORM_WIN32_NT = 2

Public Declare Function GetVersionEx Lib “kernel32″ Alias “GetVersionExA” (ByRef lpVersionInformation As OSVERSIONINFO) As Long

‘ These functions are for getting the process token information, which IsUserAnAdministrator uses to

‘ handle detecting an administrator that’s running in a non-elevated process under UAC.

Private Const TOKEN_READ As Long = &H20008

Private Const TOKEN_ELEVATION_TYPE As Long = 18

Private Declare Function IsUserAnAdmin Lib “Shell32″ Alias “#680″ () As Integer

Private Declare Function CloseHandle Lib “kernel32″ (ByVal hObject As Long) As Long

Private Declare Function OpenProcessToken Lib “advapi32.dll” (ByVal ProcessHandle As Long, ByVal DesiredAccess As Long, TokenHandle As Long) As Long

Private Declare Function GetTokenInformation Lib “advapi32.dll” (ByVal TokenHandle As Long, ByVal TokenInformationClass As Long, TokenInformation As Any,_ ByVal TokenInformationLength As Long, ReturnLength As Long) As Long

Public Function IsUserAnAdministrator() As Boolean

On Error GoTo IsUserAnAdministratorError

IsUserAnAdministrator = False

If IsUserAnAdmin() Then

IsUserAnAdministrator = True

Exit Function

End If

‘ If we’re on Vista onwards, check for UAC elevation token

‘ as we may be an admin but we’re not elevated yet, so the

‘ IsUserAnAdmin() function will return false

Dim osVersion As OSVERSIONINFO

osVersion.dwOSVersionInfoSize = Len(osVersion)

If GetVersionEx(osVersion) = 0 Then

Exit Function

End If

If osVersion.dwPlatformId <> VER_PLATFORM_WIN32_NT Or osVersion.dwMajorVersion < 6 Then

‘ If the user is not on Vista or greater, then there’s no UAC, so don’t bother checking.

Exit Function

End If

Dim result As Long

Dim hProcessID As Long

Dim hToken As Long

Dim lReturnLength As Long

Dim tokenElevationType As Long

‘ We need to get the token for the current process

hProcessID = GetCurrentProcess()

If hProcessID <> 0 Then

If OpenProcessToken(hProcessID, TOKEN_READ, hToken) = 1 Then

result = GetTokenInformation(hToken, TOKEN_ELEVATION_TYPE, tokenElevationType, 4, lReturnLength)

If result = 0 Then

‘ Couldn’t get token information

Exit Function

End If

If tokenElevationType <> 1 Then

IsUserAnAdministrator = True

End If

CloseHandle hToken

End If

CloseHandle hProcessID

End If

Exit Function

IsUserAnAdministratorError:

‘ Handle errors

End Function

References

Blog Post by Chris Jackson: How to Determine if a User is a Member of the Administrators Group with UAC Enabled on Windows Vista