Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Friday, May 23, 2014

Mono / Unity3D C# HTTPS HttpWebRequest Asynchronous craziness

Beware, ye crabby developers, of using HttpWebRequest asynchronously with SSL connection because there appears to be some crazy bug that causes a timeout (defaults to 100 seconds if I recall correctly) that yields a null response.

I spent the entire morning (and I ran into this previously on iOS a year ago and put it aside for a day when I felt like bashing my head against rocks) wondering why some simple asynchronous download management code I'd written work wonderfully for FTP and HTTP but then nothing seemed to happen when using a URL with HTTPS as the protocol...

I did the usual things like ASSumed it was a self-signed cert issue so I overwrote the certificate acceptance code - no luck.  I spent a bunch of time trying to understand the error messages I was catching on exception, ultimately tracing it down to a timeout on the OTHER end of the connection yielding a "connection forcibly closed" which told me that my server was actually saying "look meathead, if you're just going to sit on the line I'm going to cut you off so I can help someone else..."

So, I started Googling.  An hour later after reading about a ton of other people having this issue (but nothing mentioning 'debug') I caught a little bugzilla entry mentioning 'odd behavior' with HttpWebRequest when running in DEBUG.  So, having exhausted all rational attempts to resolve the issue I said "screw it, let's see if it works in release, guffaw, chortle..."

Bang, 8 simultaneous HTTPS downloads worked like a charm.

Un-FREAKING-believable.

So, if you too have crazy HTTPS issues that defy reason and you're using Mono/Xamarin/Unity3D, give your friendly neighborhood release mode build a try and see if it magically solves your ills.  I know mine did.

Hey Mav, do you still have the number of that truck driving school?  Truck Masters I think it was called...

Monday, December 9, 2013

Sharing C# Source Code With A Unity3D Project

I ran into some issues recently trying to share some common networking and data classes between a client application, a middleware ASP.NET server, and a configuration/server management tool.  All three of them needed to be able to serialize/de-serialize the same XML data; however, Unity3D does not currently support (natively) the ability to reference source code files outside of its project directory structure.

The easy solution to this on Windows is to create a symbolic link, and it works nicely.

I'll be revisiting this problem when I go to build on OSX (I presume that I'll be messing with my samba conf) and there are ways you can trick your source control system into accomplishing this (but it's really hacky) - so this isn't a panacea for the issue.

Hopefully the Unity team will introduce its own linking system that handles this (if Mono doesn't already have it...)

Worst case I'll be writing some file synchronization scripts that trigger the Unity editor to copy from the common direct (which is problematic if I make changes in Unity - but hopefully I can avoid that.)

Sigh...

Wednesday, December 4, 2013

Recently converted an ASP.NET Web Site project to a Web Application project...

...because I really need to use shared/common sources for 3 inter-related projects and you need a Web Application to do it (because Web Site projects do not let you link to source files, just copy.)

Anywho - I converted, built, tested, deployed, crashed.  Turned on detailed errors, zilch.  Enabled .NET error page information.  Bingo, "Default.aspx.cs could not be found."  What?

I FTP in, look at the directory structure and see that the bin folder does contain the DLL it should.  Spend a half hour wondering what exotic setting I'd forgotten to configure.  Cursed Microsoft for a little bit.

Suddenly I remembered something rather important.  When you're using a pre-compiled version of your project (as a Web Application generally is), you don't use the CodeFile attribute in your Default.aspx html code.  You use CodeBehind.

CodeFile instructs the webserver serving the page to use the JIT compiler.  CodeBehind instructs the compiler on your dev box where to find the class it needs to shove into the DLL.

So, yet again, it was all my #F$()ing fault.  Sigh...

Thursday, April 18, 2013

Passing a structure containing a float array from C# to a C++ DLL

For a struct defined in C++ as:

struct MyStruct
{
    float m_aFloatArray1[4];
    float m_aFloatArray2[4];
    float m_aFloatArray3[4];
};

And you want to simply pass the struct from C# straight into C++, you would define the struct on the C# side as:

[StructLayout( LayoutKind.Sequential )]
public struct MyStruct
{
    [MarshalAs( UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R4, SizeConst = 4 )]
    public float[] m_aFloatArray1;
    [MarshalAs( UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R4, SizeConst = 4 )]
    public float[] m_aFloatArray2;
    [MarshalAs( UnmanagedType.ByValArray, ArraySubType = UnmanagedType.R4, SizeConst = 4 )]
    public float[] m_aFloatArray3;
}

Note that this approach only works for fixed size arrays (otherwise things get hairy.)

Tuesday, March 19, 2013

Consuming Google Translate API v2 with C#

I spent far too much time today figuring this out (never having dealt with JSON or Google APIs before.)

It is very simple to use Google's translate services (provided you have an API key), but in the interest of sharing (and avoiding some little oopsies I hit today - such as unwrapping certain characters) here's a crude and simplified version of what I implemented.

BTW - Please note that I am using the JavaScriptSerializer below, which means that although I use a data contract (for other reasons) this particular deserializer maps by property name; ergo, the class member names MUST match the JSON property names.


[DataContract]
public class GoogleTranslation
{
    [DataMember( Name = "data" )]
    public Data Data { get; set; }
}

[DataContract]
public class Data
{
    [DataMember ( Name = "translations" )]
    public List Translations {get; set;}
}

[DataContract]
public class Translation
{
    [DataMember( Name = "translatedText" )]
    public string TranslatedText { get; set; }
}


The 3 classes above are going to be used to deserialize the returned JSON data in the method below.  BTW, you need to add three references to your project (at least, if you use the code I supply below you do - there are surely many alternatives.)

     System.Web
     System.Web.Extensions
     System.Runtime.Serialization

Sorry I didn't spend the time formatting this code (I just dumped it in) - I may do so later as it is late and I have miles to go before I do anything resembling sleep.



public string GoogleTranslate( string in_strFromLanguageCode, string in_strToLanguageCode, string in_strString )
{
    string l_strKey = "YOUR_API_KEY_GOES_HERE";
    string l_strURL = "https://www.googleapis.com/language/translate/v2?key=" + l_strKey + "&q=" + in_strString + "&source=" + in_strFromLanguageCode.ToLower() + "&target=" + in_strToLanguageCode.ToLower();
    string l_strTranslation = "";

    try
    {
        WebClient l_oWebClient = new WebClient();
        string l_strResult = "";

        //Notify the webclient we're expecting UTF-8
        l_oWebClient.Encoding = System.Text.Encoding.UTF8;
        l_strResult = l_oWebClient.DownloadString( l_strURL );

        //Unwrap special characters
        l_strResult = HttpUtility.HtmlDecode( l_strResult );

        //Deserialize JSON
        JavaScriptSerializer l_oSerializer = new JavaScriptSerializer();
        GoogleTranslation l_oTranslation = l_oSerializer.Deserialize( l_strResult );

        l_strTranslation = l_oTranslation.Data.Translations[ 0 ].TranslatedText;
    }
    catch( WebException )
    {
        //Evaluate status code
    }

    return l_strTranslation;
}


Now, there's lots of things missing you'd use in real production code (such as any basic error handling) - but that should be enough to get you rolling.

Enjoy!

Saturday, June 16, 2012

C# calling a C++ DLL, things NOT to forget

Whatever you do, no matter how rusty you are at doing this, remember to set the calling convention on your DllImport statements.  I spent two hours last night trying to track down a bizarre AMD ATI driver bug in a 3D rendering dll I was writing - it only occured when the platform was x86 and the requested feature level was DirectX 11, all other feature levels worked fine and everything worked fine on x64 from the get go.)

I went to bed frustrated (and tired - it was 1:45AM and I wasn't planning on that last night - lol) and woke up annoyed, and after finding very little mention of the problem on the inter-tubes figured I was doing something stupid (since it is unlikely I'm the one discovering a 'new' problem with an ATI driver and Direct3D.)  As usual, assuming I screwed up has paid off in spades.

It turns out that I was suffering some sort of stack corruption that was screwing up the creating of devices and swap chains in DirectX 11 and the root cause was my ASSumption about the default calling convention of a DllImport.  I figured it was cdecl, and my C++ dll is sprinkled with __cdecl export definitions; unfortunately, (and here's where I wasn't thinking very well) the default convention is OF COURSE WinAPI.

Once I specified the calling convention my mysterious and bizarro AMD driver issues suddenly vanished, and I have now moved on to more mysterious and problematic bugs.

Friday, January 15, 2010

'Bad image exception" running C# code on Windows 7

We just ran into this problem with one of our tools when someone ran a C# application on Windows 7 (although it isn't supported just yet on that platform.) Turns out that the issue was related to a 64-bit Windows 7 .NET installation loading and referencing 32-bit modules. If everything used in the application was 32-bit, it would be fine; but, you can't mix and match. Since this application makes use of modules hosted by the operating system, the 64-bit modules load and the 32-bit application image tries to map the 64-bit image for usage. BLAM...

This article describes the problem:

http://www.gcn.com/Articles/2009/07/09/5-Tips-for-Developing-with-64-Bit-Windows.aspx

Looks like a recompilation is due... Great, I just love maintaining two images and builds for the same tool.

Thursday, December 3, 2009

This has been driving me nutty for years in C#...

...but I finally figured out how to prevent the damn Visual Studio IDE from inserting asterisks into EVERY DAMN LINE of comments I put in a C# file (guess I should have Googled this.)

Simply disable:

Text Editor > C# > Advanced > Generate XML documentation comments for ///

I know, bizarrely unobvious considering there are a myriad of settings for comment templates where you basically specify 'do not do anything when I create a comment' which are blithely ignored by this #$)(#@$* setting.

I hope this makes someone else's day better like it did mine :).

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 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 }