Wednesday, October 29, 2008

Richerino and I started our running yesterday...

...and I wanted to post our cadence (the clean one) for posterity. This is one my Dad (ex UDT/SEAL type) used to sing when running (again, this is the family oriented version but the original is close except at the end.)

C130 rollin down the strip
Airborne froggies gonna take a little trip

stand up hook up shuffle to the door
jump right out and count to four

if my chute don't open wide
i gotta another one by my side

and if that one should fail me too
I'll be on the ground before you

if i die in a combat zone
box me up and ship me home

put my fins up on my chest
and bury me in leanin' rest

Monday, September 29, 2008

Monday, July 28, 2008

Long time, no blog... New TV setup

Apologies for not having blogged in such a long time, I know Scrappy was about to remove me from her list of friends because of it, lol. Hold on Scrappy!

Anyhow, I thought I'd share some recent tribulation regarding setting up a flat panel television in your house (in my case, over the mantel of the fireplace.) Now, I wanted to have the "nothing to see here but the TV" kind of approach so that none of the components (Xbox 360, comcast HD DVR, VCR/DVR-R) would be visible as they were to reside in a cabinet built into the family room wall. This looks simple, sounds simple, but for me it was in reality a real pain in the a**. ;)

I'll avoid describing the annoying pain I went through learning how to run cable, what types of cable to run, what wall plates to get, the agonizing groan when I remembered that my remote(s) are IR and not RF and therefore do not penetrate wood cabinet doors (I know, I'm an idiot...) I'll simply put the steps I would take NOW to do this. I presume that you already have your components themselves. I also presume that you already have an electrical outlet above your mantle otherwise you'll need to hire an electrician to put one in for you Remember, if you don't feel competent performing these actions, don't blame me if you screw up your drywall/sheetrock/electrocute yourself, et cetera. America has become a land of litigation and I take no responsibility for your actions.

(1)Purchase TV, I recommend measuring the width of the area you wish to place it, in my case a Samsung PN58A550 (which fit perfectly between two areas of molding over the mantle.)

(2)Purchase installer service - you do NOT want to be doing this yourself unless your a professional handyman merely for the liability issues involved in a 120 pound television tearing out of the studs of your wall when there could be children near. I highly recommend searching your local area for home theater personnel to do this and skipping using services like Firedog (although I hear they're actually excellent) due to the expense. Companies like Firedog have package deals and won't simply "hang the mount and TV" because they'll charge you for wiring and everything else regardless of whether you have them wire it or not. BTW, they also charge an extra fee if the TV goes over a fireplace (which they don't mention but a friend mentioned this to me.) Anyhow, $400+ to properly bolt a TV mount to a wall and then hang the TV on it is just a little too much. The choice is yours.

(3)Go to radio shack or some other store which carries wall plates and buy two or 4 bull nose wall plates (depending upon how many cables you are going to run. I ran two HDMI and a set of component video and audio through a single bull nose and it barely fit - 7 cables in total.) Get an equal number of simple junction boxes as well.

(4)Depending upon the distance between your components and your TV you can use light HDMI cables (higher AWG means slimmer more flexible cables) instead of heavy ones and if the distance is short (15 feet or less) you can simply run the HDMI directly from the back of the component into the back of the TV - if longer I recommend getting two Beldin twisted pair HDMI cables (get all of your cables at www.bluejeancables.com by the way - the best quality and prices by far) and run those cables inside your walls and have them connecting to HDMI wall plates at either end. Then simply connect your component to the wall plate with a 1 foot flexible HDMI cable and connect your TV to the other wall plate via another 1 foot flexible HDMI cable. Don't try to connect your component directly to the TV with the heavier gage cables as this will affect your TV's tilt ability and possibly damage the inputs on the rear of the TV.

(5)Get an IR extender, I highly recommend the relatively inexpensive Hot Link Pro (http://www.hot-link.com) I was a little worried about it at first, but once I received it and installed it, it works great. No problems with plasma IR interference with the Samsung either (apparently this was an issue with earlier plasma televisions.) I highly recommend getting the extension cable for the IR 'eye' as well (there's a cheap one available on Amazon iirc) so that you can run that cable through the wall as well in order to put the IR extender's 'eye' just at the base of the TV itself so you only ever point your remote at the TV (not the TV and over to where the component rack/closet and 'eye' are) - the default cable that comes with the hot link pro is about 6 feet long.

(6)Beg/borrow/steal a wire guide/fishing tape. This is what you will use to run the cables through your walls. You can hack one of these together (like I did) but it's extremely unfun and it's always better to "use the right tool for the job" believe me.

(7)Beg/borrow/steal a drywall saw.

(8)Optional - Buy a Logitech Harmony One universal remote. OMG it was easy to setup, very nice, IS RECHARGABLE, and works very well (at least so far, hehe...)

Now, I would do the following once I'd accumulated all these things:

(1)If possible, test EACH cable you plan to run, I can imagine no greater disappointment than busting your butt running cables and then finding out that one or more of them don't work. This is highly unlikely to occur, but hey, running cables is not fun for me.

(2)Run your cables. Find a likely spot in your component cabinet that shares a common wall with the mantle/fireplace. Use a pencil and outline the junction box against where you'd like to place it. Use the drywall saw to cut this out. Do the same above the mantle in a spot that will be reasonably close to where the inputs on your TV will be when the TV is hung. Don't put in the junction boxes yet. Get a friend and a flashlight and start feeding your wire guide as close as you can toward the other hole in the drywall. Your friend should be looking through his/her drywall hold trying to catch a glimpse of your wire. Once they see it, they use the fishing tape to retrieve it. This is the hairiest part of the whole deal for me. Once you gotten a hold of that wire, it's all just regular old work. Once you do so, I HIGHLY recommend running some strong fishing line through with the wire guide. Use enough fishing line so that the line comes out the far drywall hole and can then be taped/tied to the other end of itself to make a circle. You want to do this so that you can attach your cables (a cable or two/three at a time) to it and simply pull on the fishing line to run these cables through the wall. I cannot stress enough the need to use strong fishing line, to securely attach your cables to it when running them, and to inspect the line every time you pull a cable, looking for abrasions and/or damage of any kind. If you see any damage, use it to run another line through before it breaks. Once you've run all the cables you want (make sure there's enough slack on each side of the drying holes), put in your junction boxes. You could do this before, but when I did I found that they interfered a bit with running the cables until I removed them again. Then put in your wall plates. You should know have a nice looking connection in your component cabinet with loose cables dangling out, and a similar connection above your mantle. I HIGHLY recommend that when you're done running cable you untie the fishing line from itself, tape one end of it securely on the mantle (where it will be covered by the TV) and roll up the slack and tape it under the top of your component cabinet so that you can run cable again in the future if you wish without having to fish for the wire guide (which seems like it would be tough if cables are already running through there.)

(3)Now you can have the installers come put up the TV mount (make sure you have purchased the correct mount for the type and size of your television, I used the x-large tilt mount.) Make sure the installers place the mount at the correct height you desire or your wife may yell at you (luckily mine expressed her displeasure before they permanently mounted it.)

(4)If the installers didn't connect the inputs, go ahead and do so now. Test the TV, get everything working from each component before moving on.

(5)Setup your universal remote (if you're going to use one), get everything working before moving on.

(6)Setup your IR extender. In my case this meant using the line I'd luckily kept to run my late arriving extension cable so that I could attach the IR extender's eye to the bottom of the TV behind a grill where you can't see it, then attaching little IR flashers to the IR sensitive spots on my components.

(7)Open a beer, sit down, and enjoy your new TV.

Wednesday, April 23, 2008

For fellow fans of BucketHead...

Here is a clip of him playing with G&R (who I personally can't stand) where he does some funny stuff like doin' the robot and then plays some serious licks. Enjoy!

Friday, March 28, 2008

Kim suggested we find out what kind of 'dog' we...

...all are (especially the men I presume.)

Here's my results :)


What dog breed are you? I'm a Golden Retriever! Find out at Dogster.com

Monday, March 17, 2008

I really like Qt but when you find something...

...as amateurish as a filename with a space in it breaking their tools (qmake) it makes you wonder.

Specifically, if you have a file name 'My Filename.cpp" and you run "qmake -project" you get a project file that has failed to wrap the filename in quotes which is required by "qmake -makefile" - ridiculous. You have to edit the *.pro file by hand.

You're probably thinking what I was thinking "there's a switch or option somewhere that he's missing" - nope.

So - If you find your attempt to produce a makefile resulting in weird errors where one filename becomes two, edit your *.pro and wrap those filenames with spaces in quotes yourself ;).

Using VS 2005 and the Open Source version of Qt for Windows

I found myself with some free time the other day and wanted to play with using Qt on Windows as I have been using it for fun on various Linux flavors for years. I even went so far, on Linux, as to write an 'intellisense' like autocompletion class that I put into my own little code editor (because I deal with a lot of APIs both in my day job and my own activities.)

This time though I wanted to use it with Visual Studio 2005 and let VS do the work for me. Some initial attempts at this (I am using the open source windows version which is available as of Qt4.x) failed miserably but I finally managed to get things going after checking a few blogs (most notably Bennet Smith's.)

The version of Qt I am using is Qt 4.3.4 (you should know this because qmake command line args change occasinally [as they have between 4.x versions already]) so this should work for you if you have installed and built Qt 4.3.4 on your Windows box and have Visual Studio installed correctly.

The general process, at least for me, is to create a new toolbar in VS and add two buttons to it for triggering "qmake -project" and "qmake -makefile", then to create a Visual C++ Makefile project and edit the nmake parameters of the project. After this you should be able to use both intellisense, debug, and build using your normal build methods without a hitch.

Let's get started!

Step 1 - create a new toolbar to contain your new Qt specific commands

We want to create 2 buttons, both run the 'qmake' command, the first one runs qmake to produce a *.pro (qmake project) file and the second one generates a makefile from that *.pro file.

(1)In VS select ToolsExternal Tools to bring up the external tools menu.

(2)Press the Add button, and fill in the following properties as below:
Title: qmake project
Command: <>qmake.exe
Arguments: -project
Initial Dir: $(ProjectDir)

(3)Press the Add button and fill in the following properties as below:
Title: qmake makefile
Command: <>qmake.exe
Arguments: -makefile -spec win32-msvc2005
Initial Dir: $(ProjectDir)

If you wish, you can also set the checkbox for "Use Output Window" for these buttons (I do in case there's a problem) You may also wish to add buttons to launch Qt Designer and Qt Assistant here as well (as I did.)

Now that you have new external tools configured, let's put them on a new toolbar to help us build Qt projects.

To add a new toolbar to VS go to ToolsCustomize and then select the Toolbars tab on the dialog and press the New button. Put in whatever you want for the toolbar name, because you may ultimately have more than one version of Qt installed I recommend putting something like "Qt 4.3.3" as the title.

To add the buttons we want to this new toolbar choose the Commands tab on the Customize dialog, then find the Tools entry in the categories listed and in that sub-category find the external commands.

You need to figure out which commands are your qmake commands because for some odd reason VS simply gives them numbers. For most people they will be the two last external tool entries by number but for others who have had other external tools installed and then uninstalled (such as myself) you may have to count the number of entries for external tools in the drop down menu on the main UI because in my case my qmake entries were #10 and #11 and I had external tool numbers all the way up to 23.

You can simply drag the entries from the sub category list onto the toolbar and the title should show up and tell you if you've selected the correct entry. If you just see 'external tool #" it's the wrong one.

Step 2 - Create a VS project file for use with Qt

(1)Choose FileNewProject
(2)Under Project Types, choose General
(3)Choose Makefile Project from the list of sub projects now showing
(4)Give it the location you desire
(5)Press the Ok button and when the application wizard pops up press the Finish button

Now that the project has been created, we need to edit some of its properties:

(1)Bring up the project's properties
(2)Choose Debug as the project type
(3)Choose Configuration Properties
(4)Choose NMake, and under the NMake properties on the right side:
(5)Set Build Command Line to: nmake debug-all
(6)Set Rebuild All Command Line to: nmake debug-clean debug-all
(7)Set Clean Command Line to: nmake debug-clean
(8)Set CLR Support to: No CLR support
(9)Add to Preprocessor Definitions: UNICODE; QT_LARGEFILE_SUPPORT; QT_DLL; QT_GUI_LIB; QT_CORE_LIB; QT_THREAD_SUPPORT
(10)Set Include Search Path to the corresponding locations of your Qt install:
(mine for example are: C:\Qt\4.3.4\include\Qt;C:\Qt\4.3.4\include\Qt3Support;C:\Qt\4.3.4\include\QtAssistant;C:\Qt\4.3.4\include\QtCore;C:\Qt\4.3.4\include\QtDBus;C:\Qt\4.3.4\include\QtDesigner;C:\Qt\4.3.4\include\QtGui;C:\Qt\4.3.4\include\QtNetwork;C:\Qt\4.3.4\include\QtOpenGL;C:\Qt\4.3.4\include\QtScript;C:\Qt\4.3.4\include\QtSql;C:\Qt\4.3.4\include\QtSvg;C:\Qt\4.3.4\include\QtTest;C:\Qt\4.3.4\include\QtUiTools;C:\Qt\4.3.4\include\QtXml)
(11)Set the project type to release and perform steps 5-10 replacing the word debug with release

Now, save everything, and before you do anything else, press the qmake project button on your toolbar (this should generate a *.pro file in the solution directory) and then press the qmake makefile button (this should result in a Makefile being placed in the project directory) and now you can simply choose "Build Solution" from the Build menu of VS and everything should be up and running.

Hope this helps someone out there :)

Wednesday, March 12, 2008

If you still get a blank screen and cursor after install...

...try configuring X to avoid using the default detected S3 driver like below (other Linux distributions use other methods but below should work for OpenSuse flavors):

(1)Boot using "3" (without quotes) as an option to initiate a command shell
(2)Enter the command: "sax2 -m 0=vesa" (without quotes)
(3)Wait for the confirmation window and choose "Ok"
(4)Reboot and start as you would normally

For those of you interested in Linux under Microsoft Virtual PC 2007...

...there's a 'quirk' with Virtual PC that can make it difficult for the installer (in my case OpenSuse 10.3) to detect your mouse/keyboard properly, to get around this try adding the installer boot option "i8042.noloop" (minus the quotes of course.)

Also, if you're mounting the install from an ISO, this can be problematic as well. If the install keeps asking you to put in CD1 (or any type of media it shouldn't be asking for), try burning the ISO to disk and asking Virtual PC to let the virtual machine grab the CD/DVD drive as this fixed some problems I was having as well.

I hear that you need to change the framebuffer's color depth to 16-bit (from the default of 24-bit) after install as well, but we'll see about that later...

Wednesday, February 6, 2008

Too good not to share...

FedEx comes up with a great commercial...


Tuesday, February 5, 2008

You Are 80% Boyish and 20% Girlish

You have a tough exterior - and usually a tough interior to match it.
You're no nonsense, logical, and very assertive.
Sometimes you can't understand women at all, even if you're a woman yourself.
You see things rationally, and don't like to let your emotions get the best of you.

Sunday, February 3, 2008

Humorous poster...


I found online. Enjoy!

Who rocks more than scrappy?

That's right... I DO! Lol... I've 5 starred the first 28 songs on Expert now (on guitar) but the last few songs have been kicking my butt. I can beat the game on Expert, but getting four stars on some of these songs is tough (for me.)



I have a feeling some of the later songs like "Green grass and high tides" are going to be murder...



I was just happy to get past "Suffragette City" unscathed :). For some reason that song kicks my butt. Anyhow, eat your heart out Scrappy! (Reptilia took me a couple of times to 5 star it, the picture above is from my first attempt.)

Wednesday, January 30, 2008

We got mudified...

Mein Rich undt mein self got muddy this past weekend. Rich got very slightly muddy. I got 'dug yourself out of your own grave' muddy. The best part was that I was fairly clean until we were coming back to the main parking area on the main trail and I went down a steep, muddy, slippery hill and at the bottom was a virtual bog o' mud. It was one of those mud bogs that makes you think "Damn, I'd sure hate to fall into that."











I make it down the slippery hill, and I need to explain that I'm tired by this point as the riding we were doing is very technical (tight trails, steep downslopes with large rocks, big slippery power hill climbs - but fun), I hit the bog knowing that I need to power through; unfortunately my front tire was in one underwater rut, and my back tire went into a different underwater rut and my back end threw out left. This had the interesting effect of catapulting me to the right and a bit over my handlebars and BLAM did I hit that mud with a spectacular splash. Rich tells me it was impressive from his vantage point.







Anyhow, here's a few pics that don't really do the mud justice (especially on the bike.) It took me hours to wash that thing off.

Tuesday, January 29, 2008

Eggbeaters

I recently tried out Eggbeaters for the first time and have to say that I was quite happy with them. I made a great omelette using the Southwestern variety.

The website is a little kitsch and oddly 80's (at least to me) but there's nothing wrong with the product itself.

The point of Eggbeaters is that Eggs are a great source of protein and relatively low in calorie; however, they are ridiculously high in cholesterol which is almost entirely found in the yolk and not the egg whites (hence all the 'egg white omelette' orders you've heard.) Of course, because God has a sense of humour, virtually all the vitamin benefits of eggs are also in that cholosteral filled yolk. Eggbeaters makes their product out of the white but adds vitamins to make up for those lost in removing the yolks. This results in cutting the calorie count in half, eliminating all the fat and cholesterol.



I had mistakenly assumed that they were some sort of facsimile or vegan analog to eggs when in fact they're just egg whites fortified with things like beta carotene. (Beta carotene gives the Eggbeaters a normal yellow color as well.)


They come in flavored varieties as well: Garden vegetable, Southwestern, Cheese & chive. The sodium content is a touch high in some of the flavoried varieties, but not bad if you don't throw salt on your food (which you shouldn't need to if you're making it anyhow.)

Can someone please explain to me why Flash requires a zero byte terminator?

I ran into this years ago when adding network TCP/IP stream support for 3rd party clients and one of the companies wanted to use Flash to consume our AI and I couldn't understand why the damn thing couldn't receive responses (they were using XMLSocket in Actionscript) and there was zero information at the time because this was a new 'feature' of Flash. I only figured it out (luckily) when I noticed that n batched commands to the AI resulted in n-1 responses showing up in Flash. I figured we were missing a terminator. Why flash wouldn't need a terminator on multiple submissions but need one on a single submission is beyond me, anyhow, anybody out there understand the reasoning behind this? I'm consolidating some networking code and I really would like to eliminate this 'special case' in some fashion as I have two socket::send methods, one for normal properly behaving (imho) clients and one for flash that adds an extra zero byte explicitly into the TCP/IP stream. *** End Rant ***

Friday, January 25, 2008

When dwarves go bad...

Ananova carried a rather amusing 'quirky' today related to the growing incidence of long distance coach robberies being carried out by dwarves... Enjoy!

Sunday, January 20, 2008

New Toy Time!

Mein Rich undt mein self now both have new off road toys! Yeah! He's a vastly more experienced motocross/trail rider than myself as I've not ridden like this in decades, so he's got the 'tear your arms out of your sockets' KDX and I've gotten myself what I think is a good beginner's bike, a 2002 KTM 400 EXC. It's a four stroke (the KDX is a two stroke monster) so it's a bit more docile but it's got good torque throughout the power band so I'm sure I can enjoy this bike for a long while to come.


We're headed off to something-town next Saturday, stay tuned for pictures of damaged bikes/riders/egos then! :)

Friday, January 18, 2008

When gacutil /u goes bad (on Vista...)

Just a note to anyone using gacutil on Vista, make sure you start the command prompt with administrative permissions prior to trying to uninstall an assembly, otherwise you get the oh so informative "The process cannot access the file because it is being used by another process" message.

Monday, January 14, 2008

A pattern developing...

I forgot to mention, when blogging about my new t-shirt from the boss, that a friend and previous co-worker had awarded me a 'Happy Bunny' to display on the door to my old office.



He mentioned that I left that out when talking about how sweet I am as a person, so in the interest of complete disclosure, there's my bunny :).

A side note on the C# class I recently posted...

You need to be aware that some processes, such as internet explorer (iexplore.exe) will detect the privilege level at which they are launched and potentially relaunch themselves with different privileges (usually when a higher privileged process launches a process into the session of a user with lower default process privileges), therefore if you are tracking the created process id from the class I provided beware of this issue as the instance of (using iexplore.exe as an example) internet explorer you have created will self-terminate after launching another instance of itself.

When I get the chance I'll add a method to the class that overloads the current one for launching processes as a user which will accept an 'integrity level' in order to alleviate this issue. It isn't much of an issue currently; however, I'm not comfortable with processes running in user space with higher than default integrity levels. You would think this is a security issue but in actuality it appears to only effect iexplore.exe (I'm sure there must be something else that has this problem as well) because the DACLs and SACLs and process tokens security levels all match the user being impersonated so there's no apparent issue with a fear of security elevation (but you never know.)

I hope my frustration can help someone else out...

I've been building (when I get the opportunity when not fixing some critical issue) a failover and replication support architecture for 3rd party applications that will allow someone to remotely monitor when a mission critical application crashes and how to handle the response (i.e. start it back up? E-mail someone about the problem and then start it back up? Reboot the machine? Restart some associated services and only then start it back up?) There are, of course, many ways to solve each of these individual problems but I wanted more of an all in one solution. I also wanted to be able to connect to the machine running these services and applications and ask them for their current state (memory usage, CPU usage, et cetera), I also wanted a plugin system (so I could later add an update system/deployment system), and finally I wanted it to run as a windows service so that I could make use of the operating system's service failover capabilities (sorry *nix, this one is just for Windoze.) I hope I'll get a chance to port the feature set to a comparable daemon on *nix because I really like using Qt (for the UI.) Anyhow, this one is written in C# using .NET 2.0. Now, all that aside, now that I've partially established why I'm using a windows service, there's only one blocking issue to doing all of this - Vista. Now, this is actually a good thing. Let me explain the problem. On previous Microsoft Operating Systems you could mark a windows service as being allowed to 'interact with the desktop' when running as the SYSTEM account. This made it very simple for people to write services which interacted with the logged in session. Services could run in multiple sessions and were not isolated from desktops (in the kernel sense), not really. This meant that you could write a service that started up applications and the UI would show up on the user's desktop. You can no longer do this in Vista. Vista has 'hardened' services in two ways (one of which is actually present in XP if you have fast user switching.) The first, and most important way, is that Vista runs ALL services in session id 0. Nothing else is allowed to run in session id 0. Services are not allowed to run in other session ids. The second way is that due issues regarding fast user switching, some new security issues, and the ability (in some XP installs and Vista) to have several people logged on under an interactive session simultaneously, the services subsystem would need to be careful about whose actual desktop to place the UI associated with a spawned application by the service. So, the Vista team decided to eliminate the 'interact with desktop' service configuration checkbox (although the UI component still exists and you can check it, it has no affect in Vista), and put services in its own dedicated session. Now, if you want your service to have a UI, that's fine and it can do so easily if you don't mind the UI only showing up on the session id 0 desktop. When that happens the Operating System will interrupt the user and announce that a 'service has a message' it wishes them to see and will let you switch to viewing the session id 0 desktop (usually a grey desktop with nothing on it but the UI from your service.) If the UI is only related to your service this is very likely an acceptable solution. Then you're in luck and don't have to do anything special; however, I can't do that. So... If you're still reading this diatribe, here's the meat and potatoes. I've encapsulated, in a C# class, all the little things you need to do in Vista (and it works on XP and 2003 Server) in order to have a windows service spawn an application's process so that its UI shows up on the currently active console's session (this means the user who is actually actively using the system, not just logged in.) I hope it saves you an enormous amount of time because I was googling my a** off and didn't find anything that actually worked on Vista.

Enjoy!

    1 using System;
    2 using System.Reflection;
    3 using System.Security.Principal;
    4 using System.Runtime.InteropServices;
    5 using System.Diagnostics;
    6 
    7 
    8 namespace Common.Utilities.Processes
    9 {
   10     public class ProcessUtilities
   11     {
   12         /*** Imports ***/
   13         #region Imports
   14 
   15         [DllImport( "advapi32.dll", EntryPoint = "AdjustTokenPrivileges", SetLastError = true )]
   16         public static extern bool AdjustTokenPrivileges( IntPtr in_hToken, [MarshalAs( UnmanagedType.Bool )]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLength, IntPtr PreviousState, IntPtr ReturnLength );
   17 
   18         [DllImport( "advapi32.dll", EntryPoint = "OpenProcessToken", SetLastError = true )]
   19         public static extern bool OpenProcessToken( IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle );
   20 
   21         [DllImport( "advapi32.dll", EntryPoint = "LookupPrivilegeValue", SetLastError = true, CharSet = CharSet.Auto )]
   22         public static extern bool LookupPrivilegeValue( string lpSystemName, string lpName, out LUID lpLuid );
   23 
   24         [DllImport( "userenv.dll", EntryPoint = "CreateEnvironmentBlock", SetLastError = true )]
   25         public static extern bool CreateEnvironmentBlock( out IntPtr out_ptrEnvironmentBlock, IntPtr in_ptrTokenHandle, bool in_bInheritProcessEnvironment );
   26 
   27         [DllImport( "kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true )]
   28         public static extern bool CloseHandle( IntPtr handle );
   29 
   30         [DllImport( "wtsapi32.dll", EntryPoint = "WTSQueryUserToken", SetLastError = true )]
   31         public static extern bool WTSQueryUserToken( UInt32 in_nSessionID, out IntPtr out_ptrTokenHandle );
   32 
   33         [DllImport( "kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId", SetLastError = true )]
   34         public static extern uint WTSGetActiveConsoleSessionId();
   35 
   36         [DllImport( "Wtsapi32.dll", EntryPoint = "WTSQuerySessionInformation", SetLastError = true )]
   37         public static extern bool WTSQuerySessionInformation( IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out uint pBytesReturned );
   38 
   39         [DllImport( "wtsapi32.dll", EntryPoint= "WTSFreeMemory", SetLastError = false )]
   40         public static extern void WTSFreeMemory( IntPtr memory );
   41 
   42         [DllImport( "userenv.dll", EntryPoint = "LoadUserProfile", SetLastError = true )]
   43         public static extern bool LoadUserProfile( IntPtr hToken, ref PROFILEINFO lpProfileInfo );
   44 
   45         [DllImport( "advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Auto )]
   46         public static extern bool CreateProcessAsUser( IntPtr in_ptrUserTokenHandle, string in_strApplicationName, string in_strCommandLine, ref SECURITY_ATTRIBUTES in_oProcessAttributes, ref SECURITY_ATTRIBUTES in_oThreadAttributes, bool in_bInheritHandles, CreationFlags in_eCreationFlags, IntPtr in_ptrEnvironmentBlock, string in_strCurrentDirectory, ref STARTUPINFO in_oStartupInfo, ref PROCESS_INFORMATION in_oProcessInformation );
   47 
   48         #endregion //Imports
   49 
   50         /*** Delegates ***/
   51 
   52         /*** Structs ***/
   53         #region Structs
   54 
   55         [StructLayout( LayoutKind.Sequential )]
   56         public struct LUID
   57         {
   58             public uint m_nLowPart;
   59             public uint m_nHighPart;
   60         }
   61 
   62         [StructLayout( LayoutKind.Sequential )]
   63         public struct TOKEN_PRIVILEGES
   64         {
   65             public int m_nPrivilegeCount;
   66             public LUID m_oLUID;
   67             public int m_nAttributes;
   68         }
   69 
   70         [StructLayout( LayoutKind.Sequential )]
   71         public struct PROFILEINFO
   72         {
   73             public int dwSize;
   74             public int dwFlags;
   75             [MarshalAs( UnmanagedType.LPTStr )]
   76             public String lpUserName;
   77             [MarshalAs( UnmanagedType.LPTStr )]
   78             public String lpProfilePath;
   79             [MarshalAs( UnmanagedType.LPTStr )]
   80             public String lpDefaultPath;
   81             [MarshalAs( UnmanagedType.LPTStr )]
   82             public String lpServerName;
   83             [MarshalAs( UnmanagedType.LPTStr )]
   84             public String lpPolicyPath;
   85             public IntPtr hProfile;
   86         }
   87 
   88         [StructLayout( LayoutKind.Sequential )]
   89         public struct STARTUPINFO
   90         {
   91             public Int32 cb;
   92             public string lpReserved;
   93             public string lpDesktop;
   94             public string lpTitle;
   95             public Int32 dwX;
   96             public Int32 dwY;
   97             public Int32 dwXSize;
   98             public Int32 dwXCountChars;
   99             public Int32 dwYCountChars;
  100             public Int32 dwFillAttribute;
  101             public Int32 dwFlags;
  102             public Int16 wShowWindow;
  103             public Int16 cbReserved2;
  104             public IntPtr lpReserved2;
  105             public IntPtr hStdInput;
  106             public IntPtr hStdOutput;
  107             public IntPtr hStdError;
  108         }
  109 
  110         [StructLayout( LayoutKind.Sequential )]
  111         public struct PROCESS_INFORMATION
  112         {
  113             public IntPtr hProcess;
  114             public IntPtr hThread;
  115             public Int32 dwProcessID;
  116             public Int32 dwThreadID;
  117         }
  118 
  119         [StructLayout( LayoutKind.Sequential )]
  120         public struct SECURITY_ATTRIBUTES
  121         {
  122             public Int32 Length;
  123             public IntPtr lpSecurityDescriptor;
  124             public bool bInheritHandle;
  125         }
  126 
  127         #endregion //Structs
  128 
  129         /*** Classes ***/
  130 
  131         /*** Enums ***/
  132         #region Enums
  133 
  134         public enum CreationFlags
  135         {
  136             CREATE_SUSPENDED = 0x00000004,
  137             CREATE_NEW_CONSOLE = 0x00000010,
  138             CREATE_NEW_PROCESS_GROUP = 0x00000200,
  139             CREATE_UNICODE_ENVIRONMENT = 0x00000400,
  140             CREATE_SEPARATE_WOW_VDM = 0x00000800,
  141             CREATE_DEFAULT_ERROR_MODE = 0x04000000,
  142         }
  143 
  144         public enum WTS_INFO_CLASS
  145         {
  146             WTSInitialProgram,
  147             WTSApplicationName,
  148             WTSWorkingDirectory,
  149             WTSOEMId,
  150             WTSSessionId,
  151             WTSUserName,
  152             WTSWinStationName,
  153             WTSDomainName,
  154             WTSConnectState,
  155             WTSClientBuildNumber,
  156             WTSClientName,
  157             WTSClientDirectory,
  158             WTSClientProductId,
  159             WTSClientHardwareId,
  160             WTSClientAddress,
  161             WTSClientDisplay,
  162             WTSClientProtocolType
  163         }
  164 
  165         #endregion //Enums
  166 
  167         /*** Defines ***/
  168         #region Defines
  169 
  170         private const int TOKEN_QUERY = 0x08;
  171         private const int TOKEN_ADJUST_PRIVILEGES = 0x20;
  172         private const int SE_PRIVILEGE_ENABLED = 0x02;
  173 
  174         public const int ERROR_NO_TOKEN                    = 1008;
  175         public const int RPC_S_INVALID_BINDING            = 1702;
  176 
  177         #endregion //Defines
  178 
  179         /*** Methods ***/
  180         #region Methods
  181 
  182         /*
  183             If you need to give yourself permissions to inspect processes for their modules,
  184             and create tokens without worrying about what account you're running under,
  185             this is the method for you :) (such as the token privilege "SeDebugPrivilege")
  186         */
  187         static public bool AdjustProcessTokenPrivileges( IntPtr in_ptrProcessHandle, string in_strTokenToEnable )
  188         {
  189             IntPtr l_hProcess = IntPtr.Zero;
  190             IntPtr l_hToken = IntPtr.Zero;
  191             LUID l_oRestoreLUID;
  192             TOKEN_PRIVILEGES l_oTokenPrivileges;
  193 
  194             Debug.Assert( in_ptrProcessHandle != IntPtr.Zero );
  195 
  196             //Get the process security token
  197             if( false == OpenProcessToken( in_ptrProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out l_hToken ) )
  198             {
  199                 return false;
  200             }
  201 
  202             //Lookup the LUID for the privilege we need
  203             if( false == LookupPrivilegeValue( String.Empty, in_strTokenToEnable, out l_oRestoreLUID ) )
  204             {
  205                 return false;
  206             }
  207 
  208             //Adjust the privileges of the current process to include the new privilege
  209             l_oTokenPrivileges.m_nPrivilegeCount = 1;
  210             l_oTokenPrivileges.m_oLUID = l_oRestoreLUID;
  211             l_oTokenPrivileges.m_nAttributes = SE_PRIVILEGE_ENABLED;
  212 
  213             if( false == AdjustTokenPrivileges( l_hToken, false, ref l_oTokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero ) )
  214             {
  215                 return false;
  216             }
  217 
  218             return true;
  219         }
  220 
  221         /*
  222             Start a process the simplest way you can imagine
  223         */
  224         static public int SimpleProcessStart( string in_strTarget, string in_strArguments )
  225         {
  226             Process l_oProcess = new Process();
  227             Debug.Assert( l_oProcess != null );
  228 
  229             l_oProcess.StartInfo.FileName = in_strTarget;
  230             l_oProcess.StartInfo.Arguments = in_strArguments;
  231 
  232             if( true == l_oProcess.Start() )
  233             {
  234                 return l_oProcess.Id;
  235             }
  236 
  237             return -1;
  238         }
  239 
  240         /*
  241             All the magic is in the call to WTSQueryUserToken, it saves you changing DACLs,
  242             process tokens, pulling the SID, manipulating the Windows Station and Desktop
  243             (and its DACLs) - if you don't know what those things are, you're lucky and should
  244             be on your knees thanking God at this moment.
  245 
  246             DEV NOTE:  This method currently ASSumes that it should impersonate the user
  247                               who is logged into session 1 (if more than one user is logged in, each
  248                               user will have a session of their own which means that if user switching
  249                               is going on, this method could start a process whose UI shows up in
  250                               the session of the user who is not actually using the machine at this
  251                               moment.)
  252 
  253             DEV NOTE 2:    If the process being started is a binary which decides, based upon
  254                                 the user whose session it is being created in, to relaunch with a
  255                                 different integrity level (such as Internet Explorer), the process
  256                                 id will change immediately and the Process Manager will think
  257                                 that the process has died (because in actuality the process it
  258                                 launched DID in fact die only that it was due to self-termination)
  259                                 This means beware of using this service to startup such applications
  260                                 although it can connect to them to alarm in case of failure, just
  261                                 make sure you don't configure it to restart it or you'll get non
  262                                 stop process creation ;)
  263         */
  264         static public int CreateUIProcessForServiceRunningAsLocalSystem( string in_strTarget, string in_strArguments )
  265         {
  266             PROCESS_INFORMATION l_oProcessInformation = new PROCESS_INFORMATION();
  267             SECURITY_ATTRIBUTES l_oSecurityAttributes = new SECURITY_ATTRIBUTES();
  268             STARTUPINFO l_oStartupInfo = new STARTUPINFO();
  269             PROFILEINFO l_oProfileInfo = new PROFILEINFO();
  270             IntPtr l_ptrUserToken = new IntPtr( 0 );
  271             uint l_nActiveUserSessionId = 0xFFFFFFFF;
  272             string l_strActiveUserName = "";
  273             int l_nProcessID = -1;
  274             IntPtr l_ptrBuffer = IntPtr.Zero;
  275             uint l_nBytes = 0;
  276 
  277             try
  278             {
  279                 //The currently active user is running what session?
  280                 l_nActiveUserSessionId = WTSGetActiveConsoleSessionId();
  281 
  282                 if( l_nActiveUserSessionId == 0xFFFFFFFF )
  283                 {
  284                     throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSGetActiveConsoleSessionId failed,  GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
  285                 }
  286 
  287                 if( false == WTSQuerySessionInformation( IntPtr.Zero, (int)l_nActiveUserSessionId, WTS_INFO_CLASS.WTSUserName, out l_ptrBuffer, out l_nBytes ) )
  288                 {
  289                     int l_nLastError = Marshal.GetLastWin32Error();
  290 
  291                     //On earlier operating systems from Vista, when no one is logged in, you get RPC_S_INVALID_BINDING which is ok, we just won't impersonate
  292                     if( l_nLastError != RPC_S_INVALID_BINDING )
  293                     {
  294                         throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQuerySessionInformation failed,  GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
  295                     }
  296 
  297                     //No one logged in so let's just do this the simple way
  298                     return SimpleProcessStart( in_strTarget, in_strArguments );
  299                 }
  300 
  301                 l_strActiveUserName = Marshal.PtrToStringAnsi( l_ptrBuffer );
  302                 WTSFreeMemory( l_ptrBuffer );
  303 
  304                 //We are supposedly running as a service so we're going to be running in session 0 so get a user token from the active user session
  305                 if( false == WTSQueryUserToken( (uint)l_nActiveUserSessionId, out l_ptrUserToken ) )                
  306                 {
  307                     int l_nLastError = Marshal.GetLastWin32Error();
  308 
  309                     //Remember, sometimes nobody is logged in (especially when we're set to Automatically startup) you should get error code 1008 (no user token available)
  310                     if( ERROR_NO_TOKEN != l_nLastError )
  311                     {
  312                         //Ensure we're running under the local system account
  313                         WindowsIdentity l_oIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
  314 
  315                         if( "NT AUTHORITY\\SYSTEM" != l_oIdentity.Name )
  316                         {
  317                             throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed and querying the process' account identity results in an identity which does not match 'NT AUTHORITY\\SYSTEM' but instead returns the name:" + l_oIdentity.Name + "  GetLastError returns: " + l_nLastError.ToString() );
  318                         }
  319 
  320                         throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed, GetLastError returns: " + l_nLastError.ToString() );
  321                     }
  322 
  323                     //No one logged in so let's just do this the simple way
  324                     return SimpleProcessStart( in_strTarget, in_strArguments );
  325                 }
  326 
  327                 //Create an appropriate environment block for this user token (if we have one)
  328                 IntPtr l_ptrEnvironment = IntPtr.Zero;
  329 
  330                 Debug.Assert( l_ptrUserToken != IntPtr.Zero );
  331 
  332                 if( false == CreateEnvironmentBlock( out l_ptrEnvironment, l_ptrUserToken, false ) )
  333                 {
  334                     throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateEnvironmentBlock failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
  335                 }
  336 
  337                 l_oSecurityAttributes.Length = Marshal.SizeOf( l_oSecurityAttributes );
  338                 l_oStartupInfo.cb = Marshal.SizeOf( l_oStartupInfo );
  339 
  340                 //DO NOT set this to "winsta0\\default" (even though many online resources say to do so)
  341                 l_oStartupInfo.lpDesktop = String.Empty;
  342                 l_oProfileInfo.dwSize = Marshal.SizeOf( l_oProfileInfo );
  343                 l_oProfileInfo.lpUserName = l_strActiveUserName;
  344 
  345                 //Remember, sometimes nobody is logged in (especially when we're set to Automatically startup)
  346                 if( false == LoadUserProfile( l_ptrUserToken, ref l_oProfileInfo ) )
  347                 {
  348                     throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to LoadUserProfile failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
  349                 }
  350 
  351                 if( false == CreateProcessAsUser( l_ptrUserToken, in_strTarget, String.Empty, ref l_oSecurityAttributes, ref l_oSecurityAttributes, false, CreationFlags.CREATE_UNICODE_ENVIRONMENT, l_ptrEnvironment, null, ref l_oStartupInfo, ref l_oProcessInformation ) )
  352                 {
  353                     //System.Diagnostics.EventLog.WriteEntry( "CreateProcessAsUser FAILED", Marshal.GetLastWin32Error().ToString() );
  354                     throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateProcessAsUser failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
  355                 }
  356 
  357                 l_nProcessID = l_oProcessInformation.dwProcessID;
  358             }
  359             catch( Exception l_oException )
  360             {
  361                 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "An unhandled exception was caught spawning the process, the exception was: " + l_oException.Message );
  362             }
  363             finally
  364             {
  365                 if( l_oProcessInformation.hProcess != IntPtr.Zero )
  366                 {
  367                     CloseHandle( l_oProcessInformation.hProcess );
  368                 }
  369                 if( l_oProcessInformation.hThread != IntPtr.Zero )
  370                 {
  371                     CloseHandle( l_oProcessInformation.hThread );
  372                 }
  373             }
  374 
  375             return l_nProcessID;
  376         }
  377 
  378         #endregion //Methods
  379     }
  380 }