Recent posts (max 10) - Browse or Archive for more

IR Beam Remote Shutter Release

I recently had a discussion with another photographer about taking photos of fruit dropped into water in a wine glass. He had tried it but ran into the problem of getting the timing right. Basically, he dropped the fruit and tried to guess the right time to take the picture. While he go some good shots, there were a lot pictures where the timing was wrong. This got me thinking, there had to be a better way. I decided to build a shutter release that would take a photo after an IR beam had been interrupted.

The first thing I needed to do was figure out how the shutter release for Sony Alpha cameras works. I bought an extra remote shutter release:

Then I took it apart. After examining how the mechanism worked and a little investigation with a multimeter, I determined that red was the common ground and that shorting yellow to red engaged the auto focus and shorting all three took a picture.

My first goal was to build a circuit that would take a picture immediately after the beam was broken. I used a paired IR LED and IR photodetector to create the beam. The output of that was inverted and used to control two NPN MOSFET transistors that would connect the yellow and white shutter release wires to ground.

This is the breadboarded circuit.


With that complete, I decided to add a variable delay into the circuit. This would allow me to choose the delay of the circuit based on how high was dropping the object from instead of forcing the height based on the delay in the circuit. To implement this I used two 555 timer chips (actually, one 556 timer) in series. The first stage implements the variable delay and the second stage ensures the release is triggered long enough for the picture to be taken. I didn't know how long the trigger spike needed to be so I decided to play it safe and simulate someone holding the trigger for a fraction of a second.

When the output of the photo detector goes low it will cause a small low pulse on the trigger pin of the first stage. This will cause the output of the first stage to go high for a variable length of time. Which we can calculate with the following:

t = 1.1 * R * C

R is between 0 and 5 MOhms. For 90 nF this gives us a range of 0 to .495 seconds.

When the output of this stage goes low again it will cause a small low pulse in the trigger pin of the second stage. This will cause the output of the second stage to go high for .517 seconds (1.1 * 1 uF * 470 kOhms). While the output of the second stage is high all of the camera wires will be shorted to ground and a picture will be taken.

This is the breadboarded circuit. Many of the capacitor and resistor values are a little fudged based on what I had lying around. It worked out in the end.


After attaching the IR LED and IR photo dector to wires and jerry rigging a platform to hold them in place, I started taking pictures. Initially the timing wasn't quite right.

A little too early.

Way too late.

But I was easily able to dial in the correct delay. Unlike the guessing method, almost every shot was correctly timed. Unless, of course, I missed the glass or hit the lip.

Now that the triggering problem is solved I will be able to quickly modify lighting, fruit, liquid, drop height, etc. to get the exact look I want.

Welcome Back!

I'd like to apologize for the downtime. Our server decided to go belly-up shortly before I disappeared for vacation. We are now hosted on a newer, more reliable FreeBSD server. There are still a few kinks to work through, but the dust should settle any day now.

Should you find something wrong, feel free to  email me!

  • Posted: 2011-04-20 22:16
  • Author: erik
  • Categories: (none)
  • Comments (0)

The Master Boot Record, Volume Boot Record and Raw Disk Access in C#

At work I have a project that involves working with the Master Boot Record (MBR) and Volume Boot Record (VBR). I needed a quick and easy way to dump the contents of these sectors and see the bits they contained. After completing it I decided it was worth a quick post.

Introduction

For those that aren't familiar with hard drives, MBRs, and VBRs, here's a quick primer. For everyone else feel free to skip ahead.

Hard Drives

Your hard drive is composed of several spinning magnetic disks. Each disk can store data on both sides and thus has two read/write heads. The data is stored in concentric rings called cylindars. These cylindars can be subdivided into sections called sectors or blocks. For hard drives, sectors are usually 512 bytes long. When data is read or written to disk the smallest unit is a sector. So, even if you only want to read or write 10 bytes the computer will actually read or write 512. In order to locate any piece of data on a hard drive it is uniquely addressable by its cylindar, head, and sector. This addressing scheme is called CHS Addressing. This form, however, is being deprecated in favor of Logical Block Addressing (LBA). LBA maps the CHS addressing scheme into a sequential set of addresses. Thus the first sector of a hard drive becomes LBA=0, the second, LBA=1, and so on. This abstracts the physical geometry from the developer and only requires that they know the block size and total number of blocks.

MBR and VBR

The Master Boot Record (MBR) and Volume Boot Record (VBR) are used to bootstrap into the operating system on a Basic Input/Output? System (BIOS) based computer. The process and technologies are different, and beyond the scope of this post, for an Extensible Firmware Interface (EFI) based computer. When your computer boots it needs to execute code to load the operating system or whatever software you want. This first set of code lives in the BIOS. This code checks what hardware is present and does a few tests to make sure everything is ok to boot. Then, according to the boot order you have specified (or your computer manufacturer has specified) it begins loading the first sector of various disks. When it finds one that is marked as an MBR it proceeds to transfer execution into it. The MBR contains a few things to help boot into the system. The first is a very small chunk of code, usually only 440 bytes, to perform the steps required in this phase, a disk signature, to uniquely identify the disk, a partition table, to list the main partitions on a drive, and the MBR signature, to identify this sector as an MBR. A partition is a part of the hard drive that has been logically seperated to act as its own volume as far as an operating system is concerned and can have an independent file system structure. The MBR's job is to look through the partition table, find the first partition marked as bootable, load the first sector of the partition, and transfer execution to it. The first sector of a partition is called the volume boot record. The job of the VBR is usually to load the operating system's bootloader but many are more complex and can load other volume boot records to provide the user with a choice of operating system to load.

The App

I knew had to make an app that dumped the MBR and the VBR so I started looking into raw disk access in Windows. I realize that there are apps out there that will do this already, such as  WinHex, but where's the fun in that?

In Windows there is a special path namespace, called the Win32 Device Namespace, that allows file like direct access to devices. It starts with "\\.\" and can be used for logical volumes, physical drives, named pipes, and even DOS devices, such as COM1. The path "\\.\PhysicalDriveN" gives raw access to physical drives where you replace N with whichever drive you want to access. The drive number can be found in the Microsoft Management Console (mmc.exe) with the disk management snap in. The path "\\.\{Drive Letter}:" gives access to the logical volumes where you would replace {Drive Letter} with C, D, etc. In my case I used it to dump the Volume Boot Sector. And, since I mentioned it, "\\.\COMN" would give access to a COM device where you would replace N with the specific number.

I made a fairly simple interface for the app. All it takes is a path, the offset (bytes in hex) and the size (bytes in hex). And because it is used to directly access drives, it must be run as an administrator. To read the output you will need a hex editor such as  HxD or  WinHex. The offset specifies the number of bytes from the beginning of the drive to start reading. The size specifies the number of bytes to read.

The code works by first creating a file handle for the path supplied using CreateFile. Then, the SetFilePointer function is used to move the specified offset. Next, the ReadFile function is used to read the specified number of bytes. And, last, the .NET FileStream class is used to write out the bytes that were read.

Here is a screenshot of the app:

Here is screenshot of HxD with a VBR:

The Code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO;

namespace RawDump
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public enum EMoveMethod : uint
        {
            Begin = 0,
            Current = 1,
            End = 2
        }

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern uint SetFilePointer(
            [In] SafeFileHandle hFile,
            [In] int lDistanceToMove,
            [Out] out int lpDistanceToMoveHigh,
            [In] EMoveMethod dwMoveMethod);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
          uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
          uint dwFlagsAndAttributes, IntPtr hTemplateFile);

        [DllImport("kernel32", SetLastError = true)]
        internal extern static int ReadFile(SafeFileHandle handle, byte[] bytes,
           int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

        private void buttonDump_Click(object sender, EventArgs e)
        {
            short FILE_ATTRIBUTE_NORMAL = 0x80;
            short INVALID_HANDLE_VALUE = -1;
            uint GENERIC_READ = 0x80000000;
            uint GENERIC_WRITE = 0x40000000;
            uint CREATE_NEW = 1;
            uint CREATE_ALWAYS = 2;
            uint OPEN_EXISTING = 3;

            SaveFileDialog mySFD = new SaveFileDialog();
            mySFD.FileName = "dump.bin";
            mySFD.InitialDirectory = Path.GetDirectoryName(Application.StartupPath);
            if(mySFD.ShowDialog() == DialogResult.OK)
            {
                SafeFileHandle handleValue = CreateFile(textBoxPath.Text, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
                if (handleValue.IsInvalid)
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }

                int offset = int.Parse(textBoxOffset.Text, System.Globalization.NumberStyles.HexNumber);
                int size = int.Parse(textBoxSize.Text, System.Globalization.NumberStyles.HexNumber);
                byte[] buf = new byte[size];
                int read = 0;
                int moveToHigh;
                SetFilePointer(handleValue, offset, out moveToHigh, EMoveMethod.Begin);
                ReadFile(handleValue, buf, size, out read, IntPtr.Zero);
                FileStream myStream = File.OpenWrite(mySFD.FileName);
                myStream.Write(buf, 0, size);
                myStream.Flush();
                myStream.Close();
                handleValue.Close();
            }
        }
    }
}

Further Reading

More First Impressions on the Nanoboard3000

I was fortunate enough to obtain another Nanoboard 3000 through Lane's initial contact with  Altium.

Background

My background is primarily in electrical engineering, especially devices, circuit design, and pcb design. I work a lot with Eagle Layout Editor and ExpressPCB. While these tools are great for small/medium-complexity projects, I find that a lot of my work would benefit from the features offered by a more robust suite like Altium designer

FPGA design for me is what I will politely call 'foreign'. I am aware of it in a high-level sense, but frankly have not had ample opportunity in the past to learn more about them. I have worked with a lot of microcontrollers, especially those in the PIC or AVR families. These are great micros but the resources presented to the designer are more or less static. This is especially problematic when applications require more and more memory when some sort of external memory bus is not available. Furthermore, I've experienced firsthand the kind of massive ramp-up time that is needed to change devices or device families.

Altium Designer coupled with the Nanoboard 3000 gives me an opportunity to perhaps 'shed' this design paradigm. I like the idea of being able to redefine what resources I need, when I need them.

Photos

I will avoid some redundancy with the earlier post by limiting the unboxing photos.

http://farm5.static.flickr.com/4139/4810770667_1e5ee531b1.jpg

http://farm5.static.flickr.com/4082/4811394402_d0bef3007c.jpg

This is the part I like: FPGA with some DRAM and ample ADC or DAC channels connected via SPI. Life is good.

http://farm5.static.flickr.com/4075/4810771151_b5ebf1d02a.jpg

http://farm5.static.flickr.com/4081/4811394760_6b5d028460.jpg

The Plan for Now

To start, I'm going to try and learn as much as I can about FPGA design and the kinds of tools available to me through Altium Designer. Next, I will probably explore some designs that closely resemble those I've worked on in grad school, including some DSP problems. It might be useful to even create a miniature oscilloscope-like design that displays the different ADC channels on the LCD screen provided.

I'll think of some more this week and get some more impressions jotted down during that time.

Unboxing The NanoBoard 3000

A Little Background

A while back, I was evaluating various electronics CAD applications for future electronics design work. A few years ago I had come across  Altium Designer and I liked what I saw. Unfortunately, at the time, the lab I was working with couldn't make the move from OrCAD to Altium Designer so it faded to the background. During my searching for a new ECAD program I found that they had updated their website with tons of new demo videos and I was blown away by the quality of the software and the great user experience it offered. I was so amazed by it that I decided to make a few comments on Twitter to share it with my electronics engineer friends and colleagues. I had no idea that  Altium would see those comments. Well, it turns out someone noticed and the next day they offered to send me a NB 3000 to try out and provide feedback, with no strings attached; I am free to blog, tweet, whatever about my experiences good and bad.

The Big Event

My NanoBoard 3000 arrived on Tuesday and I couldn't wait to open it up.

Here is the brand new box:

And the packaging:

Everything it came with:

The Top:

The Bottom:

And the fully assembled NB 3000:

The Future

Now that I have this awesome dev board I need to do something with it. My current plans are to pull out some of my old projects and reimplement them on this board. The two I have in mind are:

  • 8-bit NES: several years ago two other Computer Engineers and I reimplemented the NES's 6502 in VHDL. We tried to get it hooked up in place of the original 6502 in the NES but never quite got it to work. I'd like to go another step further and implement the graphics stack to hook into the LCD display and/or SVGA on the NB 3000 and pull ROMs off of the SD card.
  • Equalizer: Another project from the past was a 5 channel stereo equalizer. I had a lot of fun with that project and think its a good candidate for the NB 3000.
  • Handheld USB A-Mode Ultrasound Prototype: I will reimplement a prototype of a unit that could be attached to a computer via USB or directly to a monitor to display A-Mode ultrasound data. I won't actually be building the ultrasound electronics but that part can be simulated.

I have a few other ideas for projects but I'll wait to discuss those until after I work through the three I mentioned. I also want to try out the built in subversion support in Altium Designer and see how it handles conflicts and merging and other interesting source control bits.

In the mean time, I will probably do an initial impressions post in a week or two for some really simple projects just to go through the basic workflow and testing out the various hardware peripherals on the baord. This post is, hopefully, the first of many electronics oriented posts since this project has sparked my interest in doing several blog posts about my past electronics and embedded systems design work and any furture projects I work on.

New Recipes and Project Updates

I spent some time this weekend adding a ton of new recipes to the Recipe Storage Center. There are now almost 60 terrific recipes! I am going to revisit a lot of the recipes and as I make them, I will be adding pictures to illustrate the directions. Many of them are recipes I intend to review. Stay tuned for those posts!

There are some exciting new developments on the horizon.

I am working on several projects which I hope to release soon. These include:

  • Smuggly 2.0: I will be releasing a new version of Smuggly with new features, improved stability, and open source code under the MIT license.
  • Two .NET SmugMug API Wrappers: One is an automatically generated wrapper that exactly replecates the web API. The other is a library designed to simplify the interface and make it conform to basic .NET API conventions.
  • .NET UI and Utility Libraries: These are UI components and Utility classes that we use in our .NET development. These will be released with documentation under the MIT license. Over time we will add similar libraries in other languages.
  • SmugMug Logon UI Changer: An application for Windows 7 that is configured like Smuggly but changes the image you see when you log on.
  • Fort Awesome Authentication Updates: Fort Awesome will be moving from our own authentication system to Live ID authentication. This will stop the spam we're getting in the bug submission form and allow anyone to authenticate to post comments and submit bugs.

Lastly, a  NanoBoard 3000 is on its way from  Altium and should arrive soon. When I get it I will post pictures, first impressions and my plans for projects with it and future reviews.

TechTip: Use F.lux to save your eyes!

To kick off my new techtips series I am going to start off with an easy one,  F.lux.

F.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day.

I was originally introduced to f.lux by  a good friend as we were working late one night. She recommended f.lux as a way to minimize eyestrain. It took about an hour to get used to it and now I can't live without it. I highly recommend this for college students, engineers, and night owls.

F.lux is free and it runs on Apple OS X and Windows computers. You can find the download links  here.

Introducing TechTips

I find something implicitly grounding about the holidays. The combination of food, family, and friends is pretty hard to beat. The holidays have one very specific downside though: computer support. While I wish I had the foresight to  fake a learning disability, I find myself the "goto" person for technical questions.

The problem is that I am surrounded by computer scientists, software engineers, and fairly geeky people. When I think "tech tip," my initial reaction goes to a new algorithm, an elegant way of multiprocessing, or some unique insight into filesystems that optimizes storage. It turns out that far more people are interested in getting things to  sync with their phone.

I am starting a series called "TechTips" in an attempt to keep me grounded year round. I am going to point my family and friends here:

http://www.fort-awesome.net/blog/category/TechTip

While I don't expect the computer questions will go away any time soon, it is my hope that these tips will help make people's lives a little bit better.

Smuggly 1.1 Release

I am happy to announce the release of Smuggly 1.1.

This release adds some new features in addition to fixing most of the bugs users have encountered.

Added Features

  • Feeds can now be added as media sources.
  • Improved item adding dialog
    • The process of adding various media items has been simplified and should be much easier to use
  • Option to prevent stretching added
    • Based on user feedback, the option to prevent the image from being stretched beyond its original size
  • Slideshow speed is more customizeable
    • Based on user feedback, the slideshow speed is now able to be completely customized

Bug Fixes

  • Correct icon appears up in add/remove programs
  • Settings dialog title is now correct
  • Smuggly should no longer crash when run in a virtual machine
  • Prevents the "System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Xna.Framework, Version=3.0.0.0..." from being thrown despite the application already calling Application.Exit
  • Checks folder permissions when setting cache path
  • Properly handles DeviceLostException and DeviceNotResetException
  • Worker threads have been set as background threads to decrease exit time
  • No longer installs itself as logon screensaver
    • If Smuggly is installed as the logon screensaver please use the repair tool to reset the logon screensaver to the Windows default

Impersonating the Desktop User From an Elevated Application in .NET Take Two

After my previous post (Impersonating the Desktop User From an Elevated Application in .NET), I received a question asking what exactly the call to  AdjustTokenPrivileges was doing and why exactly it was needed. It turns out that, if you are willing to drop support for Windows 2000, it is not needed.

Since I love simplicity and I don't think I need to support Windows 2000. I am reposting the simplified process description and code.

Basically, the code performs the following actions:

  1. Retrieves the shell window.
  2. Determines the Process ID associated with the shell window.
  3. Gets the process token for the shell process.
  4. Duplicates the process token and turns it into a primary token.
  5. Creates the new process as the user specified by the token.

Here is the code:

using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace FortAwesomeUtil.Win32
{
    public class UAC
    {
        [DllImport("user32.dll")]
        static extern IntPtr GetShellWindow();

        const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
        const int ERROR_NOT_ALL_ASSIGNED = 1300;

        [DllImport("user32.dll", SetLastError = true)]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [Flags()]
        enum ProcessAccessFlags : int
        {
            /// <summary>Specifies all possible access flags for the process object.</summary>
            AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation
                | Terminate | VMOperation | VMRead | VMWrite | Synchronize,
            /// <summary>Enables usage of the process handle in the CreateRemoteThread 
            /// function to create a thread in the process.</summary>
            CreateThread = 0x2,
            /// <summary>Enables usage of the process handle as either the source or target process 
            /// in the DuplicateHandle function to duplicate a handle.</summary>
            DuplicateHandle = 0x40,
            /// <summary>Enables usage of the process handle in the GetExitCodeProcess and 
            /// GetPriorityClass functions to read information from the process object.</summary>
            QueryInformation = 0x400,
            /// <summary>Enables usage of the process handle in the SetPriorityClass function to 
            /// set the priority class of the process.</summary>
            SetInformation = 0x200,
            /// <summary>Enables usage of the process handle in the TerminateProcess function to 
            /// terminate the process.</summary>
            Terminate = 0x1,
            /// <summary>Enables usage of the process handle in the VirtualProtectEx and 
            /// WriteProcessMemory functions to modify the virtual memory of the process.</summary>
            VMOperation = 0x8,
            /// <summary>Enables usage of the process handle in the ReadProcessMemory function to' 
            /// read from the virtual memory of the process.</summary>
            VMRead = 0x10,
            /// <summary>Enables usage of the process handle in the WriteProcessMemory function to 
            /// write to the virtual memory of the process.</summary>
            VMWrite = 0x20,
            /// <summary>Enables usage of the process handle in any of the wait functions to wait 
            /// for the process to terminate.</summary>
            Synchronize = 0x100000
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] 
            bool bInheritHandle, uint dwProcessId);

        [Flags()]
        enum TokenAccessFlags : int
        {
            STANDARD_RIGHTS_REQUIRED = 0x000F0000,
            STANDARD_RIGHTS_READ = 0x00020000,
            TOKEN_ASSIGN_PRIMARY = 0x0001,
            TOKEN_DUPLICATE = 0x0002,
            TOKEN_IMPERSONATE = 0x0004,
            TOKEN_QUERY = 0x0008,
            TOKEN_QUERY_SOURCE = 0x0010,
            TOKEN_ADJUST_PRIVILEGES = 0x0020,
            TOKEN_ADJUST_GROUPS = 0x0040,
            TOKEN_ADJUST_DEFAULT = 0x0080,
            TOKEN_ADJUST_SESSIONID = 0x0100,
            TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY),
            TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
                TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
                TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
                TOKEN_ADJUST_SESSIONID)
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool OpenProcessToken(IntPtr ProcessHandle,
            TokenAccessFlags DesiredAccess, out IntPtr TokenHandle);

        enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        extern static bool DuplicateTokenEx(
            IntPtr hExistingToken,
            TokenAccessFlags dwDesiredAccess,
            IntPtr lpThreadAttributes,
            SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
            TOKEN_TYPE TokenType,
            out IntPtr phNewToken);

        [Flags]
        enum CreationFlags
        {
            CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
            CREATE_DEFAULT_ERROR_MODE = 0x04000000,
            CREATE_NEW_CONSOLE = 0x00000010,
            CREATE_NEW_PROCESS_GROUP = 0x00000200,
            CREATE_NO_WINDOW = 0x08000000,
            CREATE_PROTECTED_PROCESS = 0x00040000,
            CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
            CREATE_SEPARATE_WOW_VDM = 0x00001000,
            CREATE_SUSPENDED = 0x00000004,
            CREATE_UNICODE_ENVIRONMENT = 0x00000400,
            DEBUG_ONLY_THIS_PROCESS = 0x00000002,
            DEBUG_PROCESS = 0x00000001,
            DETACHED_PROCESS = 0x00000008,
            EXTENDED_STARTUPINFO_PRESENT = 0x00080000
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto,
            CallingConvention = CallingConvention.StdCall)]
        extern static bool CloseHandle(IntPtr handle);

        [StructLayout(LayoutKind.Sequential)]
        struct SECURITY_ATTRIBUTES
        {
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool CreateProcessAsUserW(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            CreationFlags dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        public static Process RunAsDesktopUser(string path, string args)
        {
            int lastError;
            IntPtr shellWindow = GetShellWindow();

            if (shellWindow == IntPtr.Zero)
            {
                throw new InvalidOperationException("Unable to get shell window.");
            }

            uint processID = 0;
            GetWindowThreadProcessId(shellWindow, out processID);
            if (processID == 0)
            {
                lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            IntPtr processHandle = OpenProcess(ProcessAccessFlags.QueryInformation, true, processID);

            if (processHandle == IntPtr.Zero)
            {
                lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            IntPtr shellProcessToken;

            TokenAccessFlags tokenAccess = TokenAccessFlags.TOKEN_QUERY | TokenAccessFlags.TOKEN_ASSIGN_PRIMARY |
                TokenAccessFlags.TOKEN_DUPLICATE | TokenAccessFlags.TOKEN_ADJUST_DEFAULT |
                TokenAccessFlags.TOKEN_ADJUST_SESSIONID;

            if (!OpenProcessToken(processHandle, tokenAccess, out shellProcessToken))
            {
                lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            IntPtr newPrimaryToken;

            if (!DuplicateTokenEx(shellProcessToken, tokenAccess, IntPtr.Zero,
                SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out newPrimaryToken))
            {
                lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.cb = System.Runtime.InteropServices.Marshal.SizeOf(startupInfo);
            startupInfo.lpDesktop = "";

            PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();

            if (!CreateProcessAsUserW(newPrimaryToken, path, path + " " + args, IntPtr.Zero, IntPtr.Zero, false, 0,
                IntPtr.Zero, null, ref startupInfo, out processInfo))
            {
                lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            CloseHandle(processInfo.hProcess);
            CloseHandle(processInfo.hThread);

            return Process.GetProcessById(processInfo.dwProcessId);
        }
    }
}

This file is in the Fort Awesome Util Project and can be downloaded here.