I have a C# console application, and I was trying to do some ASCII art within it. However, some of the characters I wanted to use are Unicode. So, I was searching the internet/SO and couldn't find a consolidated answer on how to set the console to be Unicode in a C# console application.
TDLR: How do I set the console in a C# console application to be Unicode?
Edit: I did find this post after searching for something not related to this question.
It turns out that there are multiple things you need to set up in order to make the console display Unicode characters.
Set the console to a Unicode supported font. To do this, run your C# console application once with Console.ReadKey(); so the window stays open. Right click on the title bar of the window and select Properties. These options will persist when debugging through Visual Studio. You might need to use the Default menu instead for persisting the options throughout the system. In the Fonts tab, you need to set the font to Lucida Console. This font supports Unicode characters. The related post can be found here.
Set the console's code page to UTF-8. This one is a bit tricky. Because, you have to execute a command in the console window to change the code page. For whatever reason, this option is not available as a console preference. To do this, you'll need to make a separate cmd.exe process, and use this instead of the normal console provided.
var cmd = new Process
{
StartInfo =
{
FileName = "cmd.exe",
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
UseShellExecute = false
}
};
cmd.Start();
cmd.StandardInput.WriteLine("chcp 65001");
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
The first part of the code above will create a new cmd.exe process. The settings given to StartInfo will make sure that Console is redirected to this new process. The second part of the code sends a command to this console window and runs it. That command, chcp 65001, sets the console's code page to UTF-8. Related posts can be found here and here.
Set the OutputEncoding to UTF-8. This is the only way that Console.WriteLine will actually output Unicode characters. Setting this is very simple.
Console.OutputEncoding = Encoding.UTF8;
Now, any output from Console will be in Unicode. The related post can be found here.
So, that's it! I hope this information helps someone. :-)
Another option is to use P/Invoke to change the code page directly:
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleOutputCP(uint wCodePageID);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCP(uint wCodePageID);
static async Task<int> Main(string[] args)
{
SetConsoleOutputCP(65001);
SetConsoleCP(65001);
Console.WriteLine("This is how you say hello in Japanese: こんにちは");
return 0;
}
}
Output:
The solution I prefer is to change the active console code page of my profile to utf-8 using region under control panel. (see picture)
Restart and select a font which can display the necessary characters.
Related
I'm currently creating a WPF application, and like to add as a small side feature the ability to clear the windows file explorer history.
If one were to manually do this operation, it is possible via the file menu within a file explorer window,
as shown here.
My goal is pretty much to programmatically execute the same action as this button does, but I've been unable to find what executable or user32.dll method is behind this operation (if it exists), and been also unsuccessful on finding the full logic behind it (namely, finding what folder and files it targets), to replicate it.
Can you help me?
As the comment by dxiv suggested, you can achieve this via the following:
enum ShellAddToRecentDocsFlags
{
Pidl = 0x001,
Path = 0x002,
PathW = 0x003
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern void SHAddToRecentDocs(ShellAddToRecentDocsFlags flag, string path);
// How To Clear Everything
SHAddToRecentDocs(ShellAddToRecentDocsFlags.Pidl, null);
I have code automatically running netsh to check firewall rules:
var process = new Process
{
StartInfo =
{
FileName = fileName,
Arguments = arguments,
LoadUserProfile = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.OutputDataReceived += (sendingProcess, outLine) => outputText.AppendLine(outLine.Data);
process.ErrorDataReceived += (sendingProcess, errorLine) => errorText.AppendLine(errorLine.Data);
output = outputText.ToString();
errorOutput = errorText.ToString();
//parse both outputs...
As a filename, I have the following command:
C:\Windows\System32\netsh.exe
And the following is the arguments:
advfirewall firewall show rule name="rulename" dir=in
It mostly works fine, its when output has non-english characters I run into issues. All of them are replaced with "?", so I thought this is encoding issue, but it looks like its not.
When I run same command manually in cmd, I get the very same result. However, if I change the font from default one to consolas, it shows non-english output perfectly fine.
How do I apply the same fix in my code and can I even do it at all? I searched for some time and found nothing, suggesting that I may be doing something wrong here.
Again, encoding doesn't seem to be an issue since both cmd and powershell show output after changing font correctly. I tried setting StartInfo.StandardOutputEncoding to the same encoding used in cmd but nothing is changed - I still get question marks in output. What am I missing?
EDIT: Tried setting StartInfo.StandardOutputEncoding to different values, including cp866, koi8-r and Windows-1251 - result stays the same, still getting question marks.
I'm trying to do this on Windows 2008 R2, with english as display language.
If the rule names contain Cyrillic characters, then you are going to need to read them with an output console or editor that will display them.
In PowerShell, use the following and edit the output in WordPad or Word.
(New-object -comObject HNetCfg.FwPolicy2).Rules | ForEach-Object { $_.Name } >rules.txt
It is sometimes desirable to have your application open the default application for a file. For example, to open a PDF file you might use:
System.Diagnostics.Process.Start("Filename.pdf");
To open an image, you'd just use the same code with a different filename:
System.Diagnostics.Process.Start("Filename.gif");
Some extensions (.gif for example) just about always have a default handler, even in a base Windows installation. However, some extensions (.pdf for example) often don't have an application installed to handle them.
In these cases, it'd be desirable to determine if an application is associated with the extension of the file you wish to open before you make the call to Process.Start(fileName).
I'm wondering how you might best implement something like this:
static bool ApplicationAssociated(string extension)
{
var extensionHasAssociatedApplication = false;
var condition = // Determine if there is an application installed that is associated with the provided file extension.;
if (condition)
{
extensionHasAssociatedApplication = true;
}
return extensionHasAssociatedApplication;
}
I would recommend following the advice in David's answer BUT since you need to detect an association:
To check whether a file has an association you can use the native function FindExecutable which is basically what Windows Explorer uses internally... it gives a nice error code (SE_ERR_NOASSOC) if there is no association. Upon success it gives a path to the respective executable.
Thee DllImport for it is
[DllImport("shell32.dll")]
static extern int FindExecutable(string lpFile, string lpDirectory, [Out] StringBuilder lpResult);
Another option would be to walk the registry for example (not recommended since complex due to several aspets like WoW64 etc.):
The real association is stored in the key that HKEY_CLASSES_ROOT\.pdf points to - in my case AcroExch.Document, so we checkoutHKEY_CLASSES_ROOT\AcroExch.Document. There you can see (and change) what command is going to be used to launch that type of file:
HKEY_CLASSES_ROOT\AcroExch.Document\shell\open\command
#Yahia gets the nod. I'm posting my quick solution for posterity so you can see what I went with. There are lots of possible improvements to this code, but this will give you the idea:
public static bool HasExecutable(string path)
{
var executable = FindExecutable(path);
return !string.IsNullOrEmpty(executable);
}
private static string FindExecutable(string path)
{
var executable = new StringBuilder(1024);
FindExecutable(path, string.Empty, executable);
return executable.ToString();
}
[DllImport("shell32.dll", EntryPoint = "FindExecutable")]
private static extern long FindExecutable(string lpFile, string lpDirectory, StringBuilder lpResult);
In a situation like this the best approach is to try to open the document and detect failure. Trying to predict whether or not a file association is in place just leads to you reimplementing the shell execute APIs. It's very hard to get that exactly right and rather needless since they already exist!
You will have too look at the registry to get that information.
You can follow from:
HKEY_CLASSES_ROOT\.extension
and it usually leads to something like HKEY_CLASSES_ROOT\extfile\Shell\Open\Command
and you will come to the command to open the file type.
Depending on what you are doing, it may be ideal to just ask for forgiveness ( that is, just open the file and see)
All of that information lives in the registry.. you could navigate to HKEY_CLASSES_ROOT, find the extension and go from there to find the default handler. But depending on the type of file and the associated handler(s) you'll need to wade into CLSIDs and whatnot... you're probably better off catching an exception instead.
This information is in the registry. For example:
# Mount the HKCR drive in powershell
ps c:\> new-psdrive hkcr registry hkey_classes_root
ps c:\> cd hkcr:\.cs
# get default key for .cs
PS hkcr:\.cs> gp . ""
(default) : VisualStudio.cs.10.0
...
# dereference the "open" verb
PS hkcr:\.cs> dir ..\VisualStudio.cs.10.0\shell\open
Hive: hkey_classes_root\VisualStudio.cs.10.0\shell\open
Name Property
---- --------
Command (default) : "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe" /dde
ddeexec (default) : Open("%1")
This one seems trivial but the answer has eluded me for a few days now.
I have a Windows batch file, that calls a C# program to do an extra verification that cannot be done in a batch file. After the verification is complete I need to return a status and a string back to the calling shell.
Now the return value is trivial and my C# console app simply sets a return value (exit code if you will). And I thought the string will also be a piece of cake. I attempted to define a new shell variable using the:
Environment.SetEnvironmentVariable("ERR", "Some text");
This call should (and does) define a shell variable within the current process - that is the very C# process that created the variable. The value is lost as soon as the C# app terminates and the shell that created the C# app knows nothing about the variable. So... A call with no particular use... At all... Unless perhaps if I created a child process from the C3 app, perhaps it would inherit my variables.
The EnvironmentVariableTarget.Machine and EnvironmentVariableTarget.User targets for the SetEnvironmentVariable call don't solve the problem either, as only a newly created process will get these new values from the registry.
So the only working solution I can think of is:
write to stdout
write to a file
encode extra meaning into the return value
The first two are a bit ugly and the last one has its limitations and problems.
Any other ideas (how to set a shell variable in the parent process)? Maybe such shell variable modifications are a security concern (think PATH)...
Thank-you for your time.
I had the same problem as Ryan and the only thing that came to my mind as a work-around was to write a batch in error out to set the variable and to call it from the batch.
ConsoleApplication1.exe:
'put some sensible code here
'put result in variable myResult
Dim myResult As String = Guid.NewGuid().ToString("D").ToUpperInvariant()
Console.WriteLine("Normal output from the consonle app")
Console.Error.WriteLine("#ECHO OFF")
Console.Error.WriteLine("SET zzzResult={0}", myResult)
Test.cmd (the calling batch):
#ECHO OFF
:Jump to folder of batch file
PUSHD %~d0%~p0
:Define a temp file
SET zzzTempFile=%TEMP%\TMP%Random%.CMD
:Call .NET console app
ConsoleApplication1.exe 2>%zzzTempFile%
:Call the generated batch file
CALL %zzzTempFile%
:Clean up temp file
DEL %zzzTempFile%
:Clean up variable
SET zzzTempFile=
:Do something with the result
ECHO Yeah, we finally got it!
ECHO:
ECHO The value is "%zzzResult%".
ECHO:
:Clean up result variable
SET zzzResult=
:Go back to original folder
POPD
That should do the trick. And yes, I do know this is an old post and Ryan is solving other issues by now, but there might be still somebody else out there having the same problem...
What you are asking is to be able to arbitrarily write to the memory space of a running process. For good reason, this is not possible without SeDebugPrivilege.
Any of the three solutions you list will work. Stdout is the standard way to communicate with a batch script.
By the way, you're writing a Windows batch file. I'm pretty sure the ship has already sailed on "a bit ugly".
If you want to put a value of some output into a variable in the batch you can use the following construct:
FOR /F "usebackq tokens=4 delims=\[\] " %i IN (`ver`) DO set VERSION=%i
ECHO %VERSION%
Output on my OS:
6.1.7601
'usebackq' means we are using back quotes which gives the ability to use a fileset in the command quoted with double quotes. You may not need this. 'tokens' means the index in the resulting string array to select (it can be a range M-N). If you need to skip lines use 'skip=X'). 'delims' are the string separators to use (like string-Split() in .Net).
You will put your console app instead of 'ver' and adapt the delimiters and tokens to match your specific output. If you have more variables to fill you will need to make the if a bit more complex but that should make a good start.
My BAT is a bit rusty, but I think it's possible to retrieve the 'exit' code from processes you've run externally, perhaps via %ERRORLEVEL%. If that's the case, make sure to exit your program via
Environment.Exit(123); // where 123 = error code
You can't add any messages, so you'll have to do that in the .bat file.
If this isn't the case, stdout is probably the best way.
After stumbling on this myself as well recently, I came up with this approach. What I did is run the bat file using the Process class, i.e.
// Spawn your process as you normally would... but also have it dump the environment varaibles
Process process = new Process();
process.StartInfo.FileName = mybatfile.bat;
process.StartInfo.Arguments = #"&&set>>envirodump.txt";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = false;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// Read the environment variable lines into a string array
string[] envirolines = File.ReadAllLines("envirodump.txt");
File.Delete("envirodump.txt");
// Now simply set the environment variables in the parent process
foreach(string line in a)
{
string var = line.Split('=')[0];
string val = line.Split('=')[1];
Environment.SetEnvironmentVariable(var, val);
}
This seems to have worked for me. It's not the cleanest approach, but will work in a bind. :)
just downloaded ActivePerl. I want to embed the perl interpreter in a C# application (or at least call the perl interpreter from C#). I need to be able to send send out data to Perl from C#, then receive the output back into C#.
I just installed ActivePerl, and added MS Script Control 1.0 as a reference. I found this code on the internet, but am having trouble getting it to work.
MSScriptControl.ScriptControlClass Interpreter = new MSScriptControl.ScriptControlClass();
Interpreter.Language = #"ActivePerl";
string Program = #"reverse 'abcde'";
string Results = (string)Interpreter.Eval(Program);
return Results;
Originally, it had 'PerlScript' instead of 'ActivePerl', but neither work for me. I'm not entirely sure what Interpreter.Language expects. Does it require the path to the interpreter?
Solved... I'm not sure how, but when I changed it back to PerlScript it works now. Still, I would like to know if MSScript Control is using ActivePerl or another interpreter.
You can run an external program as Maxwell suggests, in which case the external program can be Perl or anything else. It might be easier to use temp files to send the input data and get the output, but that depends on how the external program expects to get its data.
The alternative, which is what I think you're looking for, is to use the PerlNET compiler that comes with ActiveState's Perl Dev Kit. It lets you add a class wrapper around the Perl code so you can expose it to C# just like any C# class. It's fairly simple to use; you add POD comments to your Perl code to specify the method names and signatures to expose, including type information, then you compile your Perl module into a DLL .NET assembly. Once that's done you can reference the assembly from any .NET program, construct an object from your Perl class, and call its methods.
I am not sure about the script control but I have done a similar thing where I had to 'embed' spamassasin (which is a Perl program). I basically used the Process to do the job. Something along the lines of:
var proc = new Process
{
StartInfo =
{
FileName = "perl",
WorkingDirectory = HttpRuntime.AppDomainAppPath,
Arguments = " myscript.pl arg1 arg2",
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false
}
};
proc.Start();
proc.StandardInput.BaseStream.Write... // feed STDIN
proc.StandardOutput.Read... // Read program output
var procStdErr = proc.StandardError.ReadToEnd(); // errors
proc.StandardError.Close();
proc.StandardOutput.Close();
proc.WaitForExit(3000);
int exitCode = proc.ExitCode;
proc.Close();
This obviously not just Perl specific and it has the process creation overhead, so if you are running your script too often probably you need to think of a different solution.