Main window of the application

Main window of the application

Presets list

Presets list

Shortcut Icons

Shorcut Icons Drop Down

Users list

Users list DropDown

New record entry - before validation of user done

Preset Editor and Launcher - New record entry

All needed details filled - User is valid

All needed details filled

Preset name prompt after clicking "Save Preset an..." button

Preset name prompt

Prompt for location to save the shortcut

Prompt for location to save the shortcut

Note 1

Before running the application, change Project->Setting to suit your network.

Note 2

I haven't been able to create this application functionality in non domain network environment because it is using Domain Controller - which (probably) does not exist on home network...

Introduction

As a programmer (and especially Web programmer), I've found myself in need to frequently use the RunAs; but the basic RunAs that comes with Windows is neither sufficient nor efficient...

It is always a pain using it, simply because when you need it, you have to go on to the command prompt, write its name with the command and parameters, hit enter, type the password, then hit Enter again - hoping you haven't written the password wrong...

Now, for my work, this process has to be down several times (I'm developing an application which uses different permissions for Managers, Workers and some of the between - basically four levels - as starting point).
The meaning of this was that for a single round of testing the application, I have to open the application four times using different user credentials.

This was becoming even worse when I was required to add a new level of management between the existing two...

Then was the time I started digging in the .NET Framework and windows internals to find out how to do it myself.

Background

The first incarnation of the software was a simple command line program, after a while, it has evolved - pretty much the way it is shown here - when my manager thought it can be used for some other uses (like giving worker - not a manager - that replaced a manager on leave) the ability to use a specific application - WITH THE MANAGER ACCOUNT - but without giving out the manager's password to the worker.

Using the Application

The application has two command line switches:

  1. /config - to create, edit and/or run a preset. From the UI, you can save a shortcut to wherever you want (Desktop,
    Program Files, etc.)
    Example: RunAsPreset.exe /config
  2. /profile, /p, /pr - to use a command line arguments to specific (saved) profile.
    Example: RunAsPreset.exe /profile:"My application as TheUser"

Using the Code

The two most important classes in the code are:

RegistryPreset Class

/// <summary>
/// Class representing Preset from registry
/// </summary>
public class RegistryPreset : IComparable, IEquatable<RegistryPreset>

which contains one important internal function:

/// <summary>
/// Initialize the instance from predefined RegistryKey
/// </summary>
/// <param name="rkPreset">Preset RegistryKey</param>
internal void Initialize(RegistryKey rkPreset);

Six important public functions:

/// <summary>
/// Load a preset from the specified preset name
/// </summary>
/// <param name="sPresetName">Preset name to be load</param>
/// <returns>New instance of the class for this preset and sets IsLoaded</returns>
public static RegistryPreset LoadPreset(string sPresetName);

/// <summary>
/// Save preset to registry key and sets IsSaved on success
/// </summary>
/// <param name="sPresetName">Preset name to save</param>
public void SavePreset(string sPresetName);

/// <summary>
/// Save preset to registry and sets IsSaved on success
/// </summary>
public void SavePreset()

/// <summary>
/// Run the selected preset
/// </summary>
public void Run()

/// <summary>
/// Load and run a selected preset
/// </summary>
/// <param name="sPresetName">Preset name to load</param>
/// <returns>New instance of the class for this preset 
/// (in case the preset is required for other processing later)</returns>
public static RegistryPreset LoadAndRun(string sPresetName)

/// <summary>
/// Manually reload a preset (when modified).
/// </summary>
public void Reload() 

and the properties:

/*******************************************************************
 *                  Properties for Preset                          *
 *******************************************************************/

/// <summary>
/// Preset name (Registry subkey) (REQUIRED)
/// </summary>
public string PresetName { get; set; }

/// <summary>
/// Target executable path and name (for Run() - used later) (REQUIRED)
/// </summary>
public string ExecutableTarget { get; set; }

/// <summary>
/// Credentials: Login (REQUIRED)
/// </summary>
public string Login { get; set; }

/// <summary>
/// Credentials: Password (OPTIONAL)
/// </summary>
public string Password { get; set; }

/// <summary>
/// Command line arguments to be passed to executable (OPTIONAL)
/// </summary>
public string Arguments { get; set; }

/**************************************************************************
 *     Properties for shortcut (if created) and for the Run() function     *
 **************************************************************************/

/// <summary>
/// Working Directory (OPTIONAL)
/// </summary>
public string WorkingDirectory { get; set; }

/// <summary>
/// Icon file path (OPTIONAL)
/// </summary>
public string IconFile { get; set; }

/// <summary>
/// Icon index for selected shortcut (OPTIONAL)
/// </summary>
public int? IconIndex { get; set; }

/// <summary>
/// Shortcut description (OPTIONAL)
/// </summary> public string Description { get; set; }

/// <summary>
/// Window style (OPTIONAL)
/// </summary>
public ProcessWindowStyle? WindowStyle { get; set; }

/*******************************************************************
 *                 Generic usage Properties                        *
 *******************************************************************/

/// <summary>
/// True if preset was successfully loaded, otherwise False
/// </summary>
public bool IsLoaded { get; }

/// <summary>
/// True if preset was successfully loaded, otherwise False
/// </summary> public bool IsSaved { get; }

/// <summary>
/// Logon domain
/// </summary>
public string Domain { get; } 

Note: The properties marked (REQUIRED) throw an exception if set incorrectly.

ActiveDirectoryPerson Class

/// <summary>
/// Class representing User from ActiveDirectory
/// </summary>
public class ActiveDirectoryPerson : IComparable, IEquatable<ActiveDirectoryPerson>

which contains one public enum:

/// <summary>
/// Login result
/// </summary>
public enum LoginResult
{
    /// <summary>
    /// Failed login - unknown reason
    /// </summary>
    Login_Fail = 0,
    /// <summary>
    /// Login and password OK
    /// </summary>
    Login_OK,
    /// <summary>
    /// Administrator account unavailable
    /// </summary>
    Administrator_Account_Unavailible,
    /// <summary>
    /// User account unavailable
    /// </summary>
    User_Account_Unavailible,
    /// <summary>
    /// Invalid login
    /// </summary>
    Invalid_Login,
    /// <summary>
    /// Invalid password
    /// </summary>
    Invalid_Password
}

Important internal function:

/// <summary>
/// Check if login exists on ActiveDirectory and load the user's properties
/// </summary>
/// <returns>True if login exist, otherwise False</returns>
internal bool AuthenticateAndLoad()

One important public function:

/// <summary>
/// Validate the user with the password against the ActiveDirectory Server
///     This function is the one needs an Administrator credentials to function correctly 
///     (as a way of preventing misuse of it)
/// </summary>
/// <param name="sLogin">Login</param>
/// <param name="sPassword">Plain text password</param>
/// <returns>LoginResult (mentioned above)</returns>
public LoginResult Authenticate(string sLogin, string sPassword)

and the properties:

/// <summary>
/// Logon domain
/// </summary>
public string Domain { get; }

/// <summary>
/// User Login
/// </summary>
public string Login { get; set; }

/// <summary>
/// User's full name
/// </summary>
public string Name { get; set; }

/// <summary>
/// User entry on the ActiveDirectory as LDap path ("LDAP://...")
/// </summary>
public string LDapPath { get; }

/// <summary>
/// Property Description
/// </summary>
public Hashtable Properties { get; }

/// <summary>
/// Login exists in the ActiveDirectory
/// </summary>
public bool LoginExists { get; } 

There is one - but not so important - class worth mentioning:

/// <summary>
/// Assembly information class
/// </summary>
public static class CallingAssemblyInfo

This class is used as a wrapper for the Assembly class to display some information - like assembly name and version in a convenient way which contains one public static properties:

/// <summary>
/// Return the assembly as "Assembly" object
/// </summary>
public static Assembly TheAssembly { get; }

/// <summary>
/// Return the assembly name as "AssemblyName" object
/// </summary>
public static AssemblyName TheAssemblyName { get; }

/// <summary>
/// Return assembly version as "Version" object
/// </summary>
public static Version TheAssemblyVersion { get; }

/// <summary>
/// Return assembly last written date as "DateTime" object
/// </summary> public static DateTime TheAssemblyModificationDate { get; }

/// <summary>
/// Assembly file (EXE) modification (last write) date as String
/// </summary>
public static string TheAssemblyModificationDateString { get; }

/// <summary>
/// Major version as integer
/// </summary>
public static int Major { get; }

/// <summary>
/// Minor version as integer
/// </summary> public static int Minor { get; }

/// <summary>
/// Build number as integer
/// </summary>
public static int Build { get; }

/// <summary>
/// Revision number as integer
/// </summary>
public static int Revision { get; }

/// <summary>
/// Return assembly version as formatted string (1.22.333.4444)
/// </summary>
public static string TheAssemblyVersionString { get; }

/// <summary>
/// Return assembly name and version format as "name, 
/// vVersion" (= "MyAsm, v1.22.333.4444")
/// </summary>
public static string NameAndVersion { get; } 

Points of Interest

The nicest thing I've got from this application (other than its usefulness - at least in my opinion) is the knowledge of "messing around" :-) with process information to force it to my will.

Thanks

First of all:

  • To all other contributors whose code examples and highlights I have used and not mentioned here

Second:

  • Andrew Robinson on www.SourceForge.com, for his CommandLine.OptParse library
  • Hannes du Preez on www.codeguru.com, for his ShellLinkManipulate library
  • Unknown, for his IconHelpers library
  • Unknown, for his ShellLinkManipulate library

History

  • v4.2.4332.15211 - My first (at least for now - and hopefully not the last) publicly available code contribution to the developers community
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"