Can you check python code in csharp before pythonNet execution? - c#

i have recently started using pythonNet for executing scripts from Csharp, on an algorithm i was doing in csharp up until now, it works pretty well:
using (Py.GIL())
{
PythonEngine.Initialize();
using (var scope = Py.CreateScope())
{
string code = File.ReadAllText(fileName);
var scriptCompiled = PythonEngine.Compile(code, "Analyze.py");
scope.Execute(scriptCompiled);
dynamic func = scope.Get("predictFromData");
PyList Pydata = new PyList(data.ToPython());
PyTuple rettp = new PyTuple(func(Pydata));
PyList pyIndexList = new PyList(rettp[0]);
foreach (PyObject intobj in pyIndexList)
{
indexList.Add(intobj.As<int>());
}
}
}
But i'd like to know if there is a way to check if the code can be executed before actually running it, since it works with compiled code, and since PythonNet does require an external python installation to see if every modules are here ect... And then switch back to my previous csharp algorithm if it is not possible in python.
For now i'm thinking about simply executing a python unit test importing modules and testing functions with dummy values and returning exceptions and units tests values to csharp code, but i'd prefer a cleaner way if anyone has an idea.
Cheers.

There are few things you can check here:
first is to see if Python code has correct syntax, it can be done with the code like this:
public static IReadOnlyList<ScriptCompilationDiagnostic> CheckErrors(ScriptEngine engine, string script, string fileName, RunFlagType mode)
{
try
{
PythonEngine.Compile(script, fileName, mode);
}
catch (PythonException e)
{
dynamic error = e.Value;
return new[]
{
new ScriptCompilationDiagnostic
{
Kind = ScriptCompilationDiagnosticKind.Error,
Line = error.lineno - 1,
Column = error.offset - 1,
Message = error.msg,
Code = error.text,
FileName = error.filename,
},
};
}
return new ScriptCompilationDiagnostic[0];
}
second is that you can check if Python is installed on a target machine, with the code like this:
var pythonHome = TryGetFullPathFromPathEnvironmentVariable("python.exe");
private static string? TryGetFullPathFromPathEnvironmentVariable(string fileName)
{
if (fileName.Length >= MAXPATH)
throw new ArgumentException($"The executable name '{fileName}' must have less than {MAXPATH} characters.", nameof(fileName));
var sb = new StringBuilder(fileName, MAXPATH);
return PathFindOnPath(sb, null) ? sb.ToString() : null;
}
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[]? ppszOtherDirs);
If your script is using third-party modules, you may check that they're installed as well:
public bool IsModuleInstalled(string module)
{
string moduleDir = Path.Combine(PythonHome, "Lib", "site-packages", module);
return Directory.Exists(moduleDir) && File.Exists(Path.Combine(moduleDir, "__init__.py"));
}
Please note that Python.NET does not officially support the latest Python version 3.9, so alternatively you can distribute and install embedded python with your application from here:
https://www.python.org/ftp/python/3.7.3/
alongside with all required third-party modules as wheels.
We use this approach in our AlterNET Studio product to check if Python is installed for our Python debugger based on Debug Adapter Protocol, and install embedded Python with wheels for our Python.NET based scripter/debugger.

Related

LookupAccountSid() throws System.AccessViolationException on Server 2016

I use pinvoke implementations of LookupAccountSid in managed C# code for several years.
On a German Windows Server 2016 it starts throwing an System.AccessViolationException when resolving S-1-5-11 (Authenticated users) where the German name is: "NT-Authorität\Authentifizierte Benutzer".
I tested 3 different impementations to rule out an pinvoke error. They all throw at the same call.
From the github vanara project and my discussion with the author
First SO implementation
Second SO implementation where I can't find the source at the moment.
They all throw the same exception so it may be a general problem in the api. Probably because of the umlaut Ä in the name?
Similar question at SO
This question sounds similar but this is not the problem I face.
My expirience in earlier projects
I used the implementation from (2.) years ago in a Windodws 7 / Server 2008 environment without any problems, but unfortunatelly I currently have no such systems to verify my recent code.
Similar reported issue
I found this thread regarding a similar behaviour on a french system
My current workaround is
ntAccountName = realSid.Translate(typeof(NTAccount)).ToString();
AdvApi32.LookupAccountName(systemName, ntAccountName, out foundSid, out domainName, out sidNameUse)
But sid.Translate(..) throws when passing a foreign principal an I don't know how reliable it is in other cases.
Questions
Is there any known issue with this api and how to solve it?
Is there any other workaround? (The LsaLookupSids can't be uses because the do not return the SID_NAME_USE flags)
I wrote the following, using the Vanara libraries and #RbMm's comments, to mimic the LookupAccountSid functionality using LsaLookupSids.
private static NTStatus LookupAccountSid2([Optional] string lpSystemName, PSID lpSid, out string lpName,
out string lpReferencedDomainName, out SID_NAME_USE peUse)
{
lpName = lpReferencedDomainName = null;
peUse = default;
using var pol = LsaOpenPolicy(LsaPolicyRights.POLICY_LOOKUP_NAMES, lpSystemName);
var ret = LsaLookupSids2(pol, LsaLookupSidsFlags.LSA_LOOKUP_RETURN_LOCAL_NAMES, 1, new[] { lpSid }, out var refDom, out var names);
if (ret.Failed) return ret;
using (refDom)
using (names)
{
lpReferencedDomainName = refDom.ToStructure<LSA_REFERENCED_DOMAIN_LIST>().DomainList.First().Name;
var name = names.ToArray<LSA_TRANSLATED_NAME>(1)[0];
lpName = name.Name;
peUse = name.Use;
}
return ret;
}
For those who are not familiar with the vanara library and how to convert a SecurityIdentifier to a PSID pointer here is a wrapper for the accepted answer. To use the library just get the nuget package Vanara.AdvApi32
using Vanara.PInvoke;
public static bool LookupAccountSid2(string lpSystemName, SecurityIdentifier sid, out string samAccountName,
out string domainName, out AdvApi32.SID_NAME_USE useFlags)
{
using (AdvApi32.SafePSID safePSID = new AdvApi32.SafePSID(sid))
{
PSID lpSid = new PSID(safePSID);
/// call the actual implementation from: https://stackoverflow.com/a/65202841/1574221
return LookupAccountSid2(lpSystemName, lpSid, out samAccountName, out domainName, out useFlags);
}
}

How does one run code within the browser at Run-time

I have been using the documentation here https://support.microsoft.com/en-gb/help/304655/how-to-programmatically-compile-code-using-c-compiler
I am trying to learn about compilers a bit more I want to host on my own site a simple text editor that I can use to run the code of a script say something simple like
The program is required to Print out Console.WriteLine("Hello World");
If anything other than Hello World is printed out the program would be in error.
I have been looking at Microsoft code on running .net code at runtime but both these force it to create an exe I want the result to be like .net fiddle in a text box.
I presume what I have to do some how is run the exe and use the process to return the result bare in mind this is inside a mvc applicaiton.
Or is their any cool nugets that can save me the time here.
private void Compiler(string code)
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler icc = codeProvider.CreateCompiler();
string Output = "Out.exe";
System.CodeDom.Compiler.CompilerParameters parameters = new
CompilerParameters();
//Make sure we generate an EXE, not a DLL
parameters.GenerateExecutable = true;
parameters.OutputAssembly = Output;
CompilerResults results = icc.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
foreach (CompilerError CompErr in results.Errors)
{
CompilerError error = new CompilerError();
error.Line = CompErr.Line;
error.ErrorNumber = CompErr.ErrorNumber;
error.ErrorText = CompErr.ErrorText;
}
}
else
{
//Successful Compile
CodeResult result = new CodeResult();
result.Message = "Success";
}
}
So how would one capture the above and return and also how does one add support for other languages like python or vb.net
Is this something that blazor could perhaps be good at doing for me ?
I am wanting to provide an experience like .net fiddle https://dotnetfiddle.net
Suchiman / Robin Sue is has integrated the Monaco editor as well as an in-browser C# compiler in this nifty blazor project (live demo)

Regular Expression to parse command lines

I need a Regular Expression to parse commands such as:
C:\Program Files\Internet Explorer\iexplore.exe https:\www.google.com
C:\Program Files\Internet Explorer\iexplore.exe http:\www.google.com
C:\Program Files\Internet Explorer\iexplore.exe www.google.com
iexplore.exe https:\www.google.com
copy C:\test.txt D:\
The point is that I want to get the first part as command and the other parts as arguments. Commands can be anything including .bat, .vbs, .exe etc.
Found a Regular Expression which is working normally if there is no space in command.
string str = #"C:\xcopy D:\test.txt D:\Test";
string pattern = #"^(?:""([^""]*)""\s*|([^""\s]+)\s*)+";
Regex parseDir = new Regex(pattern, RegexOptions.IgnoreCase);
if(parseDir.IsMatch(str))
{
Match dir = parseDir.Match(str);
var captures = dir.Groups[1].Captures.Cast<Capture>().Concat(
dir.Groups[2].Captures.Cast<Capture>()).
OrderBy(x => x.Index).
ToArray();
string cmd = captures[0].Value;
string arguments = string.Empty;
for (int i = 1; i < captures.Length; i++)
{
arguments += captures[i].Value + " ";
}
Console.WriteLine(cmd);
Console.WriteLine(arguments);
}
From your question, I'm assuming you are looking for a way to pass batch commands in a text on the Windows OS. The only way I can think of for you to do this successfully is if you have a list of all the commands or if your program can extract all the .exe files in the system, that way you can successfully check where the first exe file which is the target program of the command is and assume the others as arguments.
This way, you can do your extraction like this (Non-regex method):
var cmd = "copy this.txt C:\t.txt"
var program = getTargetProgram(cmd);
var args = cmd.Substring(cmd.IndexOf(program) + program.length).trim().split(' ');
your getTargetProgram() could go like this:
private string getTargetProgram(string cmd)
{
//first test if it's a normal executable
if(File.Exists(cmd.Substring(0, cmd.IndexOf(".exe") + 4)) //return this extract;
foreach(string s in winProgramList)
{
if(cmd.StartsWith(s)){
//voila, we can return the target
}
}
}
If you're using a standard Console application, the main entry point args[] will already have parsed this for you. There's a caveat here, because the examples you've provided won't work because of the spaces in them (C:\Program Files) but if you surround them with quotes ("C:\Program Files\Internet ...\iexplorer.exe") you'll find this works correctly.
This link walks you through creating a console application
UPDATE:
Well then, if it's not a console application but you'd like to simulate exactly what the Console Application start-up routine provides you, may I introduce to you the one and only CommandLineToArgvW native method.
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
out int pNumArgs);
This ridiculously simple method takes any string and converts it into an array. Here's a utility class that you can use to convert your text input into a well formed array.
/// <summary>
/// Wrapper class for Win32 API calls
/// </summary>
public class NativeMethods
{
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
/// <summary>
/// Parse a string into an array, including items in quotes
/// </summary>
/// <param name="commandLine"></param>
/// <returns></returns>
public static string[] CommandLineToArgs(string commandLine)
{
if (String.IsNullOrEmpty(commandLine))
return new string[] {};
int argc;
var argv = CommandLineToArgvW(commandLine, out argc);
if (argv == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception();
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
return args;
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
}
You either need to put quotes around the program path/name (if it has spaces in it), or write some extra code to figure out the program part of it.
One approach that comes to mind is to start with the first capture (for your iexplorer.exe example, it would be C:\Program), check if it's a valid program. If not, add the next capture with a space (e.g., C:\Program + Files\Internet => C:\Program Files\Internet) and repeat the check. Repeat until you've run out of captures or have found a valid program, and treat the rest as arguments normally.
No reason to do the parsing manually, as another answer suggested. The regexes will still work.

Modify Emdeded String in C# compiled exe

I have an issue where I need to be able to have a compiled exe ( .net 3.5 c# ) that I will make copies of to distribute that will need to change a key for example before the exe is sent out.
I cannot compile each time a new exe is needed. This is a thin client that will be used as part of a registration process.
Is it possible to add a entry to a resource file with a blank value then when a request comes in have another application grab the blank default thin client, copy it, populate the blank value with the data needed.
If yes how? If no do you have any ideas? I have been scratching my head for a few days now and the limitation as due to the boundaries I am required to work in.
The other idea I has was to inject the value into a method, which I have no idea how I would even attempt that.
Thanks.
Convert the assembly to IL, do a textual search and replace, recompile the IL to an assembly again. Use the standard tools from the .NET SDK.
Instead of embedding the key in the assembly, put it in the app.config file (or another file delivered with the application) and prevent your application from running if the key is not present and valid. To protect it against modification by users, also add an RSA signature the config file.
This code could be used to generate XML containing your key.
public static void Main()
{
Console.WriteLine(GenerateKey());
}
public static Byte[] Transform(Byte[] bytes, ICryptoTransform xform)
{
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
using (CryptoStream cstream = new CryptoStream(stream, xform, CryptoStreamMode.Write))
{
cstream.Write(bytes, 0, bytes.Length);
cstream.Close();
stream.Close();
return stream.ToArray();
}
}
}
public static string GenerateKey()
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
// This is the private key and should never be shared.
// Generate your own with RSA.Create().ToXmlString(true).
String rsaPrivateKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent><P>4SMSdNcOP0qAIoT2qzODgyl5yu9RubpIU3sSqky+85ZqJHXLUDjlgqAZvT71ROexJ4tMfMOgSWezHQwKWpz3sw==</P><Q>0krr7cmorhWgwCDG8jmzLMo2jafAy6tQout+1hU0bBKAQaPTGGogPB3hTnFIr84kHcRalCksI6jk4Xx/hiw+sw==</Q><DP>DtR9mb60zIx+xkdV7E8XYaNwx2JeUsqniwA3aYpmpasJ0N8FhoJI9ALRzzp/c4uDiuRNJIbKXyt6i/ZIFFH0qw==</DP><DQ>mGCxlBwLnhkN4ind/qbQriPYY8yqZuo8A9Ggln/G/IhrZyTOUWKU+Pqtx6lOghVdFjSxbapn0W8QalNMFGz7AQ==</DQ><InverseQ>WDYfqefukDvMhPHqS8EBFJFpls/pB1gKsEmTwbJu9fBxN4fZfUFPuTnCIJsrEsnyRfeNTAUFYl3hhlRYZo5GiQ==</InverseQ><D>qB8WvAmWFMW67EM8mdlReI7L7jK4bVf+YXOtJzVwfJ2PXtoUI+wTgH0Su0IRp9sR/0v/x9HZlluj0BR2O33snQCxYI8LIo5NoWhfhkVSv0QFQiDcG5Wnbizz7w2U6pcxEC2xfcoKG4yxFkAmHCIkgs/B9T86PUPSW4ZTXcwDmqU=</D></RSAKeyValue>";
rsa.FromXmlString(rsaPrivateKey);
String signedData = "<SignedData><Key>Insert your key here</Key></SignedData>";
Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(signedData);
Byte[] sigBytes = rsa.SignData(licenseData, new SHA1CryptoServiceProvider());
String sigText = System.Text.Encoding.UTF8.GetString(Transform(sigBytes, new ToBase64Transform()));
System.Text.StringBuilder sb = new StringBuilder();
using (System.Xml.XmlWriter xw = System.Xml.XmlTextWriter.Create(sb))
{
xw.WriteStartElement("License");
xw.WriteRaw(signedData);
xw.WriteElementString("Signature", sigText);
xw.WriteEndElement();
}
return sb.ToString();
}
Example output from this code:
<?xml version="1.0" encoding="utf-16"?>
<License>
<SignedData>
<Key>Insert your key here</Key>
</SignedData>
<Signature>cgpmyqaDlHFetCZbm/zo14NEcBFZWaQpyHXViuDa3d99AQ5Dw5Ya8C9WCHbTiGfRvaP4nVGyI+ezAAKj287dhHi7l5fQAggUmh9xTfDZ0slRtvYD/wISCcHfYkEhofXUFQKFNItkM9PnOTExZvo75pYPORkvKBF2UpOIIFvEIU=</Signature>
</License>
Then you can use code like this to verify it. You never have to distribute the private key:
public static Boolean CheckLicenseSignature(String licXml)
{
try
{
System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
xd.LoadXml(licXml);
String licSig = xd.SelectSingleNode("/License/Signature").InnerText;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
String rsaPublicKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
rsa.FromXmlString(rsaPublicKey);
Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(xd.SelectSingleNode("/License/SignedData").OuterXml);
return rsa.VerifyData(licenseData, new SHA1CryptoServiceProvider(), Transform(System.Text.Encoding.UTF8.GetBytes(licSig), new FromBase64Transform()));
}
catch (System.Xml.XmlException ex)
{
return false;
}
catch (InvalidOperationException ex)
{
return false;
}
}
From within the capability of the .NET code itself, I'm not sure if this is doable. But it is possible to dynamically generate a .NET DLL which contains some key that can be referred from the main application. That is, if you wouldn't mind a second file in the distribution.
Or if you don't mind to use Ildasm to disassemble the .exe, change the key, then use Ilasm to reassemble, then you can do something to automate that.
The accepted answer is GARBAGE!
I HAVE DONE THIS SUCCESSFULLY. MUCH EASIER
Just put your base application (.net) that needs the key somewhere with a string resource FILLED WITH "XXXXXXXXXXXXXXX" (more than you'll need)
.Net resources are usually kept at the top of the code so you will find them fast skipping the first 100,000 bytes in my case.
Then you just read it in and look for those XXXXXX's. When you find them you replace them with the real API key and replace the rest of the X's with spaces you just trim off in code. This is the answer. It works and it works well.
ApiToken at = new ApiToken(UserId, SelectedCID);
at.MakeToken();
byte[] app = System.IO.File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), "notkeyedapp.exe"));
for (int i = 100000; i < app.Length; i++)
{
if (app[i] == 0x58 && app[i + 1] == 0x58 && app[i + 2] == 0x58)
{
for (int j = 0; j < 128; j++)
{
if (at.Token.Length >= j + 1)
app[i + j] = System.Text.Encoding.ASCII.GetBytes(at.Token[j].ToString())[0];
else
app[i + j] = 0x20;
}
break;
}
}
string filename = "SoftwareProduct for - " + BaseModel.CompanyName.Replace(".", "") + ".exe";
return File(app, System.Net.Mime.MediaTypeNames.Application.Octet, filename);
I don't think You can get away without recompiling Your .exe and having key embedded into said .exe. The compilation process can be automated though via use of ildasm.exe and ilasm.exe as Daniel Earwicker suggested in his response https://stackoverflow.com/a/2742902/2358659
I'd like to expand on that if anyone else stumbles across this topic in the future.
I recently was facing similar problem due to my poor source code version control habits. In a nutshell I had an executable that was supposed to write some data to a Google Spreadsheet by referencing it's ID. Long after executable was released came another request from a different team to use the tool, but it had to write same information into a different spreadsheet in order to keep data separate for two teams. At the time I did not have the original source code, hence I was not able to change the static variable holding the original spreadsheet ID. What I did was as follows:
Using CMD.exe → call "C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" "myApplication.exe" /out="myApplication.il"
Using Notepad++ → Find and replace original ID to new ID inside myApplication.il file. This action can also be automated by writing own C# application to do this, or using PowerShell, or using vb/j-script or using some other find and replace tool available off-the-shelf, like FART (using CMD.exe → call fart.exe myApplication.il "OldKey" "NewKey")
Using CMD.exe → call "C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" "myApplication.il" /res="myApplication.res" /key="myApplicationKeyFile.snk"
As You see, all of these steps can be put into one .bat file that takes "NewKey" as an input and produces new .exe with NewKey embedded.
I hope that helps.
What comes to my mind, but not tried yet: Create a default String in your program, for example as
static public string regGuid = "yourguidhere";
Then, search the compiled EXE with any decent hex editor. If you find the string, replace it with another test. If you still can execute the program, you could try to automate this process and voila! Here you are.

How can a C# Windows Console application tell if it is run interactively

How can a Windows console application written in C# determine whether it is invoked in a non-interactive environment (e.g. from a service or as a scheduled task) or from an environment capable of user-interaction (e.g. Command Prompt or PowerShell)?
[EDIT: 4/2021 - new answer...]
Due to a recent change in the Visual Studio debugger, my original answer stopped working correctly when debugging. To remedy this, I'm providing an entirely different approach. The text of the original answer is included at the bottom.
1. Just the code, please...
To determine if a .NET application is running in GUI mode:
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandleW(IntPtr _);
public static bool IsGui
{
get
{
var p = GetModuleHandleW(default);
return Marshal.ReadInt16(p, Marshal.ReadInt32(p, 0x3C) + 0x5C) == 2;
}
}
This checks the Subsystem value in the PE header. For a console application, the value will be 3 instead of 2.
2. Discussion
As noted in a related question, the most reliable indicator of GUI vs. console is the "Subsystem" field in the PE header of the executable image. The following C# enum lists the allowable (documented) values:
public enum Subsystem : ushort
{
Unknown /**/ = 0x0000,
Native /**/ = 0x0001,
WindowsGui /**/ = 0x0002,
WindowsCui /**/ = 0x0003,
OS2Cui /**/ = 0x0005,
PosixCui /**/ = 0x0007,
NativeWindows /**/ = 0x0008,
WindowsCEGui /**/ = 0x0009,
EfiApplication /**/ = 0x000A,
EfiBootServiceDriver /**/ = 0x000B,
EfiRuntimeDriver /**/ = 0x000C,
EfiRom /**/ = 0x000D,
Xbox /**/ = 0x000E,
WindowsBootApplication /**/ = 0x0010,
};
As easy as that code (in that other answer) is, our case here can be vastly simplified. Since we are only specifically interested in our own running process (which is necessarily loaded), you don't have to open any file or read from the disk to obtain the subsystem value. Our executable image is guaranteed to be already mapped into memory. And it is simple to retrieve the base address for any loaded file image by calling the GetModuleHandleW function:
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandleW(IntPtr lpModuleName);
Although we might provide a filename to this function, again things are easier and we don't have to. Passing null, or in this case, default(IntPtr.Zero) (which is the same as IntPtr.Zero), returns the base address of the virtual memory image for the current process. This eliminates the extra steps (alluded to earlier) of having to fetch the entry assembly and its Location property, etc. Without further ado, here is the new and simplified code:
static Subsystem GetSubsystem()
{
var p = GetModuleHandleW(default); // VM base address of mapped PE image
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
return (Subsystem)Marshal.ReadInt16(p + 0x5C); // PE offset to 'Subsystem' word
}
public static bool IsGui => GetSubsystem() == Subsystem.WindowsGui;
public static bool IsConsole => GetSubsystem() == Subsystem.WindowsCui;
[official end of the new answer]
3. Bonus Discussion
For the purposes of .NET, Subsystem is perhaps the most—or only—useful piece of information in the PE Header. But depending on your tolerance for minutiae, there could be other invaluable tidbits, and it's easy to use the technique just described to retrieve additional interesting data.
Obviously, by changing the final field offset (0x5C) used earlier, you can access other fields in the COFF or PE header. The next snippet illustrates this for Subsystem (as above) plus three additional fields with their respective offsets.
NOTE: To reduce clutter, the enum declarations used in the following can be found here
var p = GetModuleHandleW(default); // PE image VM mapped base address
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
var subsys = (Subsystem)Marshal.ReadInt16(p + 0x005C); // (same as before)
var machine = (ImageFileMachine)Marshal.ReadInt16(p + 0x0004); // new
var imgType = (ImageFileCharacteristics)Marshal.ReadInt16(p + 0x0016); // new
var dllFlags = (DllCharacteristics)Marshal.ReadInt16(p + 0x005E); // new
// ... etc.
To improve things when accessing multiple fields in unmanaged memory, it's essential to define an overlaying struct. This allows for direct and natural managed access using C#. For the running example, I merged the adjacent COFF and PE headers together into the following C# struct definition, and only included the four fields we deemed interesting:
[StructLayout(LayoutKind.Explicit)]
struct COFF_PE
{
[FieldOffset(0x04)] public ImageFileMachine MachineType;
[FieldOffset(0x16)] public ImageFileCharacteristics Characteristics;
[FieldOffset(0x5C)] public Subsystem Subsystem;
[FieldOffset(0x5E)] public DllCharacteristics DllCharacteristics;
};
NOTE: A fuller version of this struct, without the omitted fields, can be found here
Any interop struct such as this has to be properly setup at runtime, and there are many options for doing so. Ideally, its generally better to impose the struct overlay "in-situ" directly on the unmanaged memory, so that no memory copying needs to occur. To avoid prolonging the discussion here even further however, I will instead show an easier method that does involve copying.
var p = GetModuleHandleW(default);
var _pe = Marshal.PtrToStructure<COFF_PE>(p + Marshal.ReadInt32(p, 0x3C));
Trace.WriteLine($#"
MachineType: {_pe.MachineType}
Characteristics: {_pe.Characteristics}
Subsystem: {_pe.Subsystem}
DllCharacteristics: {_pe.DllCharacteristics}");
4. Output of the demo code
Here is the output when a console program is running...
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsCui (3)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
...compared to GUI (WPF) application:
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsGui (2)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
[OLD: original answer from 2012...]
To determine if a .NET application is running in GUI mode:
bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;
Environment.UserInteractive Property
If all you're trying to do is to determine whether the console will continue to exist after your program exits (so that you can, for example, prompt the user to hit Enter before the program exits), then all you have to do is to check if your process is the only one attached to the console. If it is, then the console will be destroyed when your process exits. If there are other processes attached to the console, then the console will continue to exist (because your program won't be the last one).
For example*:
using System;
using System.Runtime.InteropServices;
namespace CheckIfConsoleWillBeDestroyedAtTheEnd
{
internal class Program
{
private static void Main(string[] args)
{
// ...
if (ConsoleWillBeDestroyedAtTheEnd())
{
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
private static bool ConsoleWillBeDestroyedAtTheEnd()
{
var processList = new uint[1];
var processCount = GetConsoleProcessList(processList, 1);
return processCount == 1;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
}
}
(*) Adapted from code found here.
I haven't tested it, but Environment.UserInteractive looks promising.
A possible improvement of Glenn Slayden's solution:
bool isConsoleApplication = Console.In != StreamReader.Null;
To prompt for user input in an interactive console, but do nothing when run without a console or when input has been redirected:
if (Environment.UserInteractive && !Console.IsInputRedirected)
{
Console.ReadKey();
}

Categories

Resources