Yes, I am still alive and coding.  Besides from blogging being so last decade, I’ve been excessively busy these past two years.

In the course of a little over two years I left my cushy job, started a new one, watched it crash and burn, and went back to my former job, and left again to work with a different former employer.  2014 was an exciting and fun for a year watching and helping the BPlusTree perform inside a from-scratch effort to build a .NET NoSQL database.

At present I am no longer working with C#, or even Microsoft Windows for that matter. I’m enjoying life on the edge working with nodejs, React, Couchbase, etc.  Since I am no longer involved, I am archiving this site and will not be posting again.

 

In my previous post “Why GUID primary keys are a database’s worst nightmare” I spoke about some of downsides to using GUID primary keys. In this post I’m going to focus more specifically on the .NET Framework’s implementation of System.Guid. I have a few complaints about this class, let’s cover them…

1. Guid.ToByteArray()

This, IMHO, is one of the worst possible things about a Guid. First this method returns a byte array in little-endian format. Not only is this directly opposite the stated standard GUID format, but it also means the bytes do not compare the same as the structured value. Thus when you combine System.Guid with a sequential guid generation you wind up with either a structure that doesn’t compare sequentially, or a byte array that doesn’t compare sequentially. Yes you can always swap bytes (1,4), (2,3), (5,6), and (7,8), but really? should I have to?

The second issue I have with this method is that it absolutely requires me to allocate the bytes on the heap. While this may not sound like such a big deal, if you are moving large volumes of data and serializing those Guids the only way you can (as byte[]) it will thrash the GC. I’m hopeful that in some future version of .NET we will find a Guid with a ToByteArray(GuidFormatType, byte[], offset) where GuidFormatType would allow little-endian or big-endian formats.

2. Guid.GetHashCode()

This one does puzzle me. They choose to completely ignore bytes 9, 10, 12, 13, 14, and 15 when computing a Guid’s hash code. Ok so maybe with random Guids this really doesn’t matter, but when you start trying to use Guid with sequential values this starts to impact hash performance. If, as in my example on the previous post, bytes 1-8 are date-time, then your entire hash value amounts to the entropy introduced in bytes 11 and 16. To be specific, byte 11 xors with the MSB and byte 16 xors with the LSB.

3. Guid.NewSequentialGuid()

Don’t you wish that .NET could just do this for us? We’ve know this to be a problem, and have know of a resolution to it, since 2002. They’ve even added “NEWSEQUENTIALGUID()” to Sql Server. Wouldn’t you think they could get this done for us? I guess until they fix the endianness of ToByteArray it’s really a moot point anyway.

Of course one option is to just PInvoke UuidCreateSequential.

static class Win32
{
    [DllImport("rpcrt4.dll", SetLastError=true)]
    public static extern int UuidCreateSequential(out Guid guid);
}

4. Guid.(Missing Properties)

Another annoyance is the lack of exposing the values of the Guid. Would it have really been that hard to expose the same integer, two shorts, and 8 bytes that we can pass to the constructor? One could then create an efficient binary serializer for the Guid type without a GC allocation and without worrying about byte-swapping little-endian values. In addition, the underlying randomization could then be reused without using the ToByteArray method. Maybe this isn’t a big deal for most people, but I’m still miffed that (AFAIK) there is not a single thread-safe means of creating a random value without allocating a byte[].

 

Sometimes it is necessary to generate a password. This might be done to create a secure account on a machine, or to reset a user’s password via email (although a one-time use security token is a much better answer). Another possible use is to generate passwords for your own use on a website. There are lots of ways to achieve this, but the method below would be my approach…

/// <summary>
/// Creates a pseudo-random password containing the number of character classes
/// defined by complexity, where 2 = alpha, 3 = alpha+num, 4 = alpha+num+special.
/// </summary>
public static string GeneratePassword(int length, int complexity)
{
    System.Security.Cryptography.RNGCryptoServiceProvider csp =
        new System.Security.Cryptography.RNGCryptoServiceProvider();
    // Define the possible character classes where complexity defines the number
    // of classes to include in the final output.
    char[][] classes =
    {
        @"abcdefghijklmnopqrstuvwxyz".ToCharArray(),
        @"ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(),
        @"0123456789".ToCharArray(),
        @" !""#$%&'()*+,./:;<>?@[\]^_{|}~".ToCharArray(),
    };

    complexity = Math.Max(1, Math.Min(classes.Length, complexity));
    if(length < complexity)
        throw new ArgumentOutOfRangeException("length");

    // Since we are taking a random number 0-255 and modulo that by the number of
    // characters, characters that appear earilier in this array will recieve a
    // heavier weight. To counter this we will then reorder the array randomly.
    // This should prevent any specific character class from recieving a priority
    // based on it's order.
    char[] allchars = classes.Take(complexity).SelectMany(c => c).ToArray();
    byte[] bytes = new byte[allchars.Length];
    csp.GetBytes(bytes);
    for (int i = 0; i < allchars.Length; i++)
    {
        char tmp = allchars[i];
        allchars[i] = allchars[bytes[i]%allchars.Length];
        allchars[bytes[i]%allchars.Length] = tmp;
    }

    // Create the random values to select the characters
    Array.Resize(ref bytes, length);
    char[] result = new char[length];

    while(true)
    {
        csp.GetBytes(bytes);
        // Obtain the character of the class for each random byte
        for (int i = 0; i < length; i++)
            result[i] = allchars[bytes[i]%allchars.Length];

        // Verify that it does not start or end with whitespace
        if (Char.IsWhiteSpace(result[0]) || Char.IsWhiteSpace(result[(length - 1) % length]))
            continue;

        string testResult = new string(result);
        // Verify that all character classes are represented
        if (0 != classes.Take(complexity).Count(c => testResult.IndexOfAny(c) < 0))
            continue;

        return testResult;
    }
}

Essentially this method starts by creating a randomly ordered set of characters to choose from. The reason this is randomly ordered is that we will offset into this array by taking a random number from 0-255 modulo the length of the array. Because of the modulo operation this can weight the first set of characters higher so we randomize it.

Once we have a randomly ordered set of characters to choose from we use the RNGCryptoServiceProvider.GetBytes to populate a byte array with random values. Each random byte will be used to select a character in the output array.

Lastly we make a few assertions about the result. Since a space ‘ ‘ can be a viable password character we make sure that the password neither starts with or ends with whitespace. Having whitespace at the beginning or end would not only be confusing, but some input forms will automatically remove the trailing whitespace. The second thing we verify is that each requested character class is represented by at least one character. This ensures that an alpha-numeric password will contain at least one number and one letter.

This brings us to the ‘how long of a password to use’ kind of question. The following is a chart to display how many password characters are required to produce an equivelent key in bit-length:

case insensitive
(26 chars)
case sensitive
(52 chars)
alpha + numeric
(62 chars)
alpha + numeric + special
(92 chars)
*56-bit 12 10 9 7
*64-bit 14 12 11 10
128-bit 28 23 22 20
256-bit 55 45 43 40

*56-bit keys are capable of being brute forced in just a few hours on current specialized hardware. Distributed systems are also capable of cracking 56-bit encryption. 128-bit keys are considered a minimum key space for today’s cryptographic algorithms.

The table is actually somewhat surprising to me in how little difference the added numeric and special characters make. To make a password as secure as the AES/128-bit algorithm you need 23 upper and lower case characters. Saving one keystroke by adding numbers to the mix just doesn’t seem all that worth while to me. When you look at most people using just 8-12 characters for what they consider a ‘good’ password it almost makes you laugh.

It seems to me that most password complexity rules requiring special characters and numbers make little sense. I guess it prevents dictionary attacks to a degree, but clearly the length of the password is much more important.

 

Keeping in line with our previous post “How to prevent users from killing your service or process” and continuing down the road of securing our service this post looks at the prevention of debugging. There are a lot of anti-debugging posts out there, most of them written in C++ and assembly and are therefor of little use to the managed world. One of the most complete I’ve seen is from Symantec “Windows Anti-Debug Reference | Symantec Connect Community” and an interesting one on stackoverflow “What is your favourite anti-debugging trick?“. These both fall short on providing any solution that is both easy to implement and built with managed code.

Let’s face it nothing is going to be bullet proof in this arena, and certainly not the solution I’m going to suggest. Yet it is easy and will prevent someone from attaching a debugger after the program is running. I’m really not concerned with trying to prevent a debug-session from startup. Why? Because you can’t. The debugger can jump past any code you have that tries to verify that a debugger is not currently active. Besides this it provides me very little value, I want to protect an actively running process (a service). If the user has the rights to stop it in the first place (i.e. they are an admin) then there isn’t anything I can do to stop them from debugging.

What I want is to prevent someone from attaching a debugger to this service to protect potentially sensitive information. How? Well as it turns out the easiest way to prevent a debug session from starting is with a debug session. So what we need is to debug ourselves! Oh wait you can’t :( but what you can do is easily spawn another process to debug this process while we debug that new process. This reciprocal or circular debug session will prevent either process from being debugged. Further, any attempt to kill either process will immediately terminate the other process thus thwarting an effort to kill one debugger so that you can attach one.

To accomplish this in managed code we have two choices, either using the managed debugger API or the native win32 debugger API. It turns out that the managed debugger is excessively complicated and requires an extraordinary amount of COM code to pull it off (see the mdbg sample) So I chose to go with a raw win32 debug session and see if we could pull that off easily with a few PInvoke calls. Sure enough, this is really easy.

So let’s jump in and take a look at the debugging API calls we are going to need…

const int DBG_CONTINUE = 0x00010002;
const int DBG_EXCEPTION_NOT_HANDLED = unchecked((int)0x80010001);

enum DebugEventType : int
{
    CREATE_PROCESS_DEBUG_EVENT = 3, //Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.
    CREATE_THREAD_DEBUG_EVENT = 2, //Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.
    EXCEPTION_DEBUG_EVENT = 1, //Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.
    EXIT_PROCESS_DEBUG_EVENT = 5, //Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.
    EXIT_THREAD_DEBUG_EVENT = 4, //Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.
    LOAD_DLL_DEBUG_EVENT = 6, //Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.
    OUTPUT_DEBUG_STRING_EVENT = 8, //Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.
    RIP_EVENT = 9, //Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.
    UNLOAD_DLL_DEBUG_EVENT = 7, //Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.
}

[StructLayout(LayoutKind.Sequential)]
struct DEBUG_EVENT
{
    [MarshalAs(UnmanagedType.I4)]
    public DebugEventType dwDebugEventCode;
    public int dwProcessId;
    public int dwThreadId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
    public byte[] bytes;
}

[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool DebugActiveProcess(int dwProcessId);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool WaitForDebugEvent([Out] out DEBUG_EVENT lpDebugEvent, int dwMilliseconds);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, int dwContinueStatus);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool IsDebuggerPresent();

The structure DEBUG_EVENT above is actually only 96 bytes in total and the bytes 12 to 96 are actually a union. I did not need any real details about the specifics so I chose to use slam a blob here and not worry about it. The byte array could be reduced in theory to 84 bytes but I really didn’t care since we are only going to create a single one of these. The rest is pretty strait-forward PInvoke junk, nothing fancy.

Using the methods above to debug a process looks something like the following code:

// Start a thread to perform the debug loop
new Thread(DebuggerThread) { IsBackground = true, Name = "DebuggerThread" }
    .Start(processId);
// Debugging thread main loop
static void DebuggerThread(object arg)
{
    DEBUG_EVENT evt = new DEBUG_EVENT();
    evt.bytes = new byte[1024];
    // Attach to the process we provided the thread as an argument
    if (!DebugActiveProcess((int)arg))
        throw new Win32Exception();

    while (true)
    {
        // wait for a debug event
        if (!WaitForDebugEvent(out evt, -1))
            throw new Win32Exception();
        // return DBG_CONTINUE for all events but the exception type
        int continueFlag = DBG_CONTINUE;
        if (evt.dwDebugEventCode == DebugEventType.EXCEPTION_DEBUG_EVENT)
            continueFlag = DBG_EXCEPTION_NOT_HANDLED;
        // continue running the debugee
        ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, continueFlag);
    }
}

Frankly I had no idea how trivial this was to do. Once I had this working I was able to quickly build a method that takes my program arguments and detects if it is a parent or child process and act appropriately. For the child we need only debug our parent process, for the parent we need to both spawn the child and debug it. Ideally this would be done as a single step since it is possible to start a process as a debugee; however, I didn’t bother to figure out how to do that. If you know and don’t mind sharing please drop a comment. Since I do know how to start a process and I know how to debug an active process I chose that approach. It doesn’t seem like it would be any more or less secure, but I could be wrong on that point. Anyway the following is what my process start-up looks like…

static void Main(string[] args)
{
    NativeDebug.DebugSelf(args);
    ... stuff ...
}
public static void DebugSelf(string[] args)
{
    Process self = Process.GetCurrentProcess();
    // Child process?
    if (args.Length == 2 && args[0] == "--debug-attach")
    {
        int owner = int.Parse(args[1]);
        Process pdbg = Process.GetProcessById(owner);
        new Thread(KillOnExit) { IsBackground = true, Name = "KillOnExit" }.Start(pdbg);
        //Wait for our parent to debug us
        WaitForDebugger();
        //Start debugging our parent process
        DebuggerThread(owner);
        //Now is a good time to die.
        Environment.Exit(1);
    }
    else // else we are the Parent process...
    {
        ProcessStartInfo psi =
            new ProcessStartInfo(Environment.GetCommandLineArgs()[0], "--debug-attach " + self.Id)
                {
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    ErrorDialog = false,
                    WindowStyle = ProcessWindowStyle.Hidden
                };
        // Start the child process
        Process pdbg = Process.Start(psi);
        if (pdbg == null)
            throw new ApplicationException("Unable to debug");
        // Monitor the child process
        new Thread(KillOnExit) {IsBackground = true, Name = "KillOnExit"}.Start(pdbg);
        // Debug the child process
        new Thread(DebuggerThread) {IsBackground = true, Name = "DebuggerThread"}.Start(pdbg.Id);
        // Wait for the child to debug us
        WaitForDebugger();
    }
}
static void WaitForDebugger()
{
    DateTime start = DateTime.Now;
    while (!IsDebuggerPresent())
    {
        if ((DateTime.Now - start).TotalMinutes > 1)
            throw new TimeoutException("Debug operation timeout.");
        Thread.Sleep(1);
    }
}
static void KillOnExit(object process)
{
    ((Process)process).WaitForExit();
    Environment.Exit(1);
}

So we’ve written about 100 lines of code or so and spent almost no time at all getting it up and running, a good ROI for this kind of stuff. It worked quite well even running under the NETWORK SERVICE account as a service. It certainly stops me from debugging it although I’m certainly not what I’d call an accomplished hacker. Given the time and effort to put this together I’d have to call it a win for active debugger prevention. The down side of course is that there are now two processes running and trying to tell them apart is difficult at first launch.

If you’re going to do something like this, I would suggest adding an ‘if (!IsDebuggerPresent())’ to the else clause of our DebugSelf method above. This would allow you to launch with a debugger but not to attach one at a later time. Have fun with it and as with any code on this site, “Don’t blame me”. I didn’t make you use it ;)

My own intentions for this are to make a reasonable effort to secure a service running in a controlled but insecure environment. I would never ship something like this to a customer and hope you would not either. Preventing a consumer from accessing software they purchased is not at all what I’m after. IMHO if information is on their machine it is theirs to debug and view all they want.

One more thing, in case you are wondering this was not my idea. I read about doing this in a security article some years ago but I can’t seem to locate it.

 

Before I say another word, I have read “The arms race between programs and users” and wholeheartedly agree. You can not, and should not, attempt to stop an Administrator from killing your process or stopping your service. That is not what we are trying to do here, we are trying to prevent Joe User from disrupting our process.

So let’s get started, to do this we want to adjust our process’ access control list. The individual rights we can grant and deny are discussed on MSDN in the articled titled “Process Security and Access Rights“. Though most of that article is about creating a process with specific rights, in this case we want to modify the rights of our current process. To do this we are going to PInvoke GetKernelObjectSecurity to obtain the DACL (Discretionary Access Control List), modify it using the RawSecurityDescriptor, and finally write it back using the SetKernelObjectSecurity API. This should be somewhat familiar to those of you that followed that part of building our service.

Step 1 – Obtaining the process DACL

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool GetKernelObjectSecurity(IntPtr Handle, int securityInformation, [Out] byte[] pSecurityDescriptor,
                                           uint nLength, out uint lpnLengthNeeded);

public static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
{
    const int DACL_SECURITY_INFORMATION = 0x00000004;
    byte[] psd = new byte[0];
    uint bufSizeNeeded;
    // Call with 0 size to obtain the actual size needed in bufSizeNeeded
    GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psd, 0, out bufSizeNeeded);
    if (bufSizeNeeded < 0 || bufSizeNeeded > short.MaxValue)
        throw new Win32Exception();
    // Allocate the required bytes and obtain the DACL
    if (!GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION,
                                        psd = new byte[bufSizeNeeded], bufSizeNeeded, out bufSizeNeeded))
        throw new Win32Exception();
    // Use the RawSecurityDescriptor class from System.Security.AccessControl to parse the bytes:
    return new RawSecurityDescriptor(psd, 0);
}

And you thought that was going to be hard? Of course we also need to be able to save the DACL. So…

Part 2 – Updating the process DACL

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetKernelObjectSecurity(IntPtr Handle, int securityInformation, [In] byte[] pSecurityDescriptor);

public static void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor dacl)
{
    const int DACL_SECURITY_INFORMATION = 0x00000004;
    byte[] rawsd = new byte[dacl.BinaryLength];
    dacl.GetBinaryForm(rawsd, 0);
    if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawsd))
        throw new Win32Exception();
}

Cool, that was easy, so we are ready now right? Not quite, we still need to get the process handle. A simple thing for our own process:

Part 3 – Getting the current process

[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();

It just gets easier and easier. Since this is the current process there (AFAIK) is not any reason to duplicate handle and request additional permissions. This handle should have access to do anything. So the only thing left before we can modify the permissions is to define what those permissions are.

Part 4 – Process access rights

[Flags]
public enum ProcessAccessRights
{
    PROCESS_CREATE_PROCESS =0x0080, //	Required to create a process.
    PROCESS_CREATE_THREAD = 0x0002, //	Required to create a thread.
    PROCESS_DUP_HANDLE = 0x0040, //	Required to duplicate a handle using DuplicateHandle.
    PROCESS_QUERY_INFORMATION = 0x0400, //	Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken, GetExitCodeProcess, GetPriorityClass, and IsProcessInJob).
    PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, //	Required to retrieve certain information about a process (see QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION. Windows Server 2003 and Windows XP/2000:  This access right is not supported.
    PROCESS_SET_INFORMATION = 0x0200, //	Required to set certain information about a process, such as its priority class (see SetPriorityClass).
    PROCESS_SET_QUOTA = 0x0100, //	Required to set memory limits using SetProcessWorkingSetSize.
    PROCESS_SUSPEND_RESUME = 0x0800, //	Required to suspend or resume a process.
    PROCESS_TERMINATE = 0x0001, //	Required to terminate a process using TerminateProcess.
    PROCESS_VM_OPERATION = 0x0008, //	Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
    PROCESS_VM_READ = 0x0010, //	Required to read memory in a process using ReadProcessMemory.
    PROCESS_VM_WRITE = 0x0020, //	Required to write to memory in a process using WriteProcessMemory.
    DELETE = 0x00010000, //	Required to delete the object.
    READ_CONTROL = 0x00020000, //	Required to read information in the security descriptor for the object, not including the information in the SACL. To read or write the SACL, you must request the ACCESS_SYSTEM_SECURITY access right. For more information, see SACL Access Right.
    SYNCHRONIZE = 0x00100000, //	The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state.
    WRITE_DAC = 0x00040000, //	Required to modify the DACL in the security descriptor for the object.
    WRITE_OWNER = 0x00080000, //	Required to change the owner in the security descriptor for the object.
    STANDARD_RIGHTS_REQUIRED = 0x000f0000,
    PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF),//	All possible access rights for a process object.
}

Part 5 – Put it all together

// Get the current process handle
IntPtr hProcess = GetCurrentProcess();
// Read the DACL
var dacl = GetProcessSecurityDescriptor(hProcess);
// Insert the new ACE
dacl.DiscretionaryAcl.InsertAce(
    0,
    new CommonAce(
        AceFlags.None,
        AceQualifier.AccessDenied,
        (int)ProcessAccessRights.PROCESS_ALL_ACCESS,
        new SecurityIdentifier(WellKnownSidType.WorldSid, null),
        false,
        null)
);
// Save the DACL
SetProcessSecurityDescriptor(hProcess, dacl);

The ace we’ve added now will try to deny access to the “Everyone” group (aka WorldSid) to do anything with our process. We need not fear that we are interrupting an administrator’s ability to perform any action they desire. They will have any access required that’s why we call them administrators :) As I said in the beginning, we are not trying to stop Administrators, we just want to make sure they are an administrator if they plan to modify or kill this process.

We could of course be more discrete about the rights we are trying to deny, that is entirely up to you.

 

Let’s face it the clever guys at Google have more bandwidth than the rest of the nation combined. What better to do with all that excess pipe ;) There are of course numerous posts about the performances of +1 buttons. If you missed them there is the ever popular Google +1 Button Performance Review, and [...]

 

If you’ve missed it there is great article entitled Keep it secret, keep it safe by Eric Lippert. Essentially it attempts to dissect the essence of typical crypto issues in plain English (i.e. crypto for dummies). He did a great job of explaining the difficulties in key management, worth a read. I found it particularly [...]

 

Background: I get a lot of traffic looking for details on running a process and collecting the process output. If you haven’t already done so, you should read “How to use System.Diagnostics.Process correctly“. It outlines the major pitfalls of using this class. Another post on “Using the ProcessRunner class” will demonstrate using the helper class [...]

 

Most people believe escaping arguments to a process is easy. Surround it with quotes, right? Nope. Escape the quotes and surround it with quotes? Nope. Escape the quotes and backslashes and then surround it with quotes? Nope. It’s actually a lot more strange than that. When do you need to escape and quote an argument? [...]

 

I’d love it if there was a nice clean way to do this; however, there isn’t. When running a process via the Process.Start(…) call one must provide a fully qualified path to an executable. The problem is that hard-coding the full path to the exe isn’t always the best option. This is why there is [...]