Execute the C# console exe with command line arguments from VB Project - c#

How to call an exe written in C# which accepts command line arguments from VB.NET application.
For Example, Let's assume the C# exe name is "SendEmail.exe" and its 4 arguments are From ,TO ,Subject and Message and If I have placed the exe in the C drive. This is how I call from the command prompt
C:\SendEmail from#email.com,to#email.com,test subject, "Email Message " & vbTab & vbTab & "After two tabs" & vbCrLf & "I am next line"
I would like to call this "SendEmail" exe from the VB.NET application and pass the command line arguments from VB (arguments will be using vb Syntax like vbCrLf, VBTab etc). This problem may look silly but I am trying to divide the complex problem into series of smaller issues and conquer it.

Because your question has the C# tag, I'll suggest a C# solution you can re-spin in your preferred language.
/// <summary>
/// This will run the EXE for the user. If arguments are passed, then arguments will be used.
/// </summary>
/// <param name="incomingShortcutItem"></param>
/// <param name="xtraArguments"></param>
public static void RunEXE(string incomingExePath, List<string> xtraArguments = null)
{
if (File.Exists(incomingExePath))
{
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
System.Diagnostics.Process proc = new System.Diagnostics.Process();
if (xtraArguments != null)
{
info.Arguments = " " + string.Join(" ", xtraArguments);
}
info.WorkingDirectory = System.IO.Path.GetDirectoryName(incomingExePath);
info.FileName = incomingExePath;
proc.StartInfo = info;
proc.Start();
}
else
{
//do your else thing here
}
}

You might not need to call it via the console. If it's done in C# and marked public instead of internal or private, or if it relies on a public type, you may be able to add it as a reference in your VB.Net solution and call the method you want directly.
This is so much cleaner and better because you don't have to worry about things like escaping spaces or quotes in your subject or body arguments.
If you have control over the SendMail program, you make it accessible with a few simple changes. By default, a C# Console project gives you something like this:
using ....
// several using blocks at the top
// class name
class Program
{
//static Main() method
static void Main(string[] args)
{
//...
}
}
You can make it available to use from VB.Net like this:
using ....
// several using blocks at the top
//Make sure an explicit namespace is declared
namespace Foo
{
// make the class public
public class Program
{
//make the method public
static void Main(string[] args)
{
//...
}
}
}
That's it. Again, just add the reference in your project, Import it at the top of your VB.Net file, and you can call the Main() method directly, without going through the console. It doesn't matter that's it an .exe instead of a .dll. In the .Net world, they're all just assemblies you can use.

Related

How to get separate arguments from single arguments string like ProcessStartInfo.Arguments?

Let's say I have a shell command line. A single line of text, exactly as you would type that in shell. Splitting command from arguments is trivial. It's always a space after the command, in every shell in the world. I know, the command theoretically can contain spaces, but let's assume it's a normal command for a while.
When I just pass the arguments as string to the ProcessStartInfo.Arguments string - it will work. But this is not what I want.
I want the arguments to be separated.
Each one. When an argument is like #"C:\Program Files" - it should be a one argument.
Getting that manually is just tricky as (s)hell. Because it's done differently for different OSes and shells. However - .NET does it somehow internally and I need to get to it not to reinvent the wheel.
What I REALLY want to do is to build an argument list first like this:
new[] { "dir", #"'C:\Program Files'" }
And then like this:
new[] { "cmd", "/C", #"dir 'C:\Program Files'" }
So it goes both ways - I extract arguments from a string, then I build an arguments string from separate arguments. All according for the OS-specific rules. My code should work on both Linux and Windows.
The catch is, it has to work multi-platform, so instead of "cmd /C" it could be "bash -c" on Linux, you get the idea.
The main point is to make the .NET to do all the quoting / unqouting itself, and AFAIK it does it properly on both Windows and Linux.
I really did search the Google and Stack Overflow for this - it seems like there's nothing.
I tried to get ArgumentList from ProcessStartInfo after settings Arguments property. It doesn't work. Setting one property doesn't set the other one. You can actually set both and get the exception when trying to start the process.
I bet the problem is far from being trivial. Any suggestions?
UPDATE
I've done more research based on digging in .NET sources and GitHub discussions. It looks really bad (or really good depending on a point of view). It seems like there's nothing like this. So I basically need one method to quote one argument for the target OS, and one method to parse a command line into unquoted parts. For parsing I'll go standard, using FSM (finite state machine algorithm), quoting is trivial, it's just adding OS / shell specific quote symbols.
Anyway, if it's somewhere in .NET just hiding and giggling, please let me know ;)
So it seems like it was not there yet, so I made a little braindead simple hack for it:
Pre-requisite:
public class SpaceDelimitedStringParser {
public char Quote { get; set; } = '"';
public IEnumerable<string> Split(string input) {
bool isQuoting = false;
for (int i = 0, s = 0, n = input.Length; i < n; i++) {
var c = input[i];
var isWhiteSpace = c is ' ' or '\t';
var isQuote = c == Quote;
var isBreak = i == n - 1 || isWhiteSpace && !isQuoting;
if (isBreak) {
yield return input[s..i];
s = i + 1;
continue;
}
if (isWhiteSpace && !isQuoting) continue;
if (isQuote) {
if (!isQuoting) {
s = i + 1;
isQuoting = true;
}
else {
yield return input[s..i];
s = i + 1;
isQuoting = false;
}
}
}
}
public string Join(IEnumerable<string> items)
=> String.Join(' ', items.Select(i => i.Contains(' ') || i.Contains('\t') ? (Quote + i + Quote) : i));
}
It just splits and joins space delimited strings using a quoting character: double quoute. I tested that is treated similarily on Windows and Linux.
Then another nice helper class:
public class ShellStartInfo {
public ShellStartInfo(string command, bool direct = false) {
var (shell, exec) =
OS.IsLinux ? ("bash", "-c") :
OS.IsWindows ? ("cmd", "/C") :
throw new PlatformNotSupportedException();
var arguments = new[] { exec, command };
StartInfo = new ProcessStartInfo(shell, new SpaceDelimitedStringParser().Join(arguments));
if (!direct) {
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.RedirectStandardError = true;
StartInfo.StandardOutputEncoding = Encoding.UTF8;
StartInfo.StandardErrorEncoding = Encoding.UTF8;
}
}
public static implicit operator ProcessStartInfo(ShellStartInfo startInfo) => startInfo.StartInfo;
private readonly ProcessStartInfo StartInfo;
}
And another little helper:
public static class OS {
public static bool IsLinux => _IsLinux ?? (_IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)).Value;
public static bool IsWindows => _IsWindows ?? (_IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)).Value;
private static bool? _IsLinux;
private static bool? _IsWindows;
}
And we can test it:
var command =
OS.IsLinux ? #"cat 'File name with spaces.txt'" :
OS.IsWindows ? #"type ""File name with spaces.txt""" :
throw new PlatformNotSupportedException();
var process = Process.Start(new ShellStartInfo(command));
if (process is null) throw new InvalidOperationException();
await process.WaitForExitAsync();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
if (output.Length > 0) Console.WriteLine($"OUTPUT:\n{output}\nEND.");
if (error.Length > 0) Console.WriteLine($"ERROR:\n{error}\nEND.");
Works on Windows, works on Linux. Done and done. The split function is unnecessary here. I thought it can be useful to pass arguments as array somewhere, but well.
Passing arguments through ArgumentList property of ProcessStartInfo class is a little broken. It just doesn't work as inteneded, especially when I pass the arguments as one shell execute argument. Why? IDK, works when passed like in the example.
BTW, my ShellStartInfo class also sets some other properties of internal ProcessStartInfo that makes it useful to read the command's output.
In my remote shell it just works, the local terminal behaves as the remote one. It's not full blown Putty clone, it's just a tiny tool to have a little piece of "ssh" to a client device that doesn't even have external IP, open SSH port or may even not accept TCP connections. In fact - the client is a small IoT device that runs Linux.

C# passing array to array but still getting "cannot convert from 'string[]' to 'string'” error

(using VS Community 2019 v16.10.4 on Win 10 Pro 202H)
I'm working on a C# console/winforms desktop app which monitors some local drive properties and displays a status message. Status message display is executed via Task Scheduler (the task is programmatically defined and registered). The UI is executed from the console app using the Process method. My intention is to pass an argument from Scheduler to the console app, perform some conditional logic and then pass the result to the UI entry (Program.cs) for further processing.
I’m testing passing an argument from the console app to the UI entry point and I’m getting a “Argument 1: cannot convert from 'string[]' to 'string'” error.
Code is:
class Program_Console
{
public static void Main(string[] tsArgs)
{
// based on MSDN example
tsArgs = new string[] { "Test Pass0", "TP1", "TP2" };
Process p = new Process();
try
{
p.StartInfo.FileName = BURS_Dir;
p.Start(tsArgs); // error here
}
public class Program_UI
{
[STAThread]
public void Main(string[] tsArgs)
{
Isn’t "tsArgs" consistently an array?
EDIT:
For clarity I’m using .NET Framework 4.7.2. The problem was not with consistency of what I am passing but in the Process.Start(String, IEnumerable String) overload. I believed “IEnumerable String” included string[ ]; it obviously does not since I was able to pass a plain string (not a string variable -- that also failed – just a hardcoded string).
In case it’s useful to somebody, my work-around is saving the arguments to a SQLite table in the console app and loading them into a List in the UI app. I’m sure a more proficient programmer could do it more efficiently.
Start doesn' have a costractor with string array. if you look at msdn document youi will see that you can use something the closest to your example
public static Start (string fileName, IEnumerable<string> arguments);
so you can try
p.Start( filename,tsArgs );
and replace filename with yours
The only Start() method taking arguments as an array also needs the filename: Start(). You can't set the Filename via StartInfo and then omit that parameter in the method call.
The following should work for you:
p.Start(BURS_Dir, tsArgs);
In .Net 5.0+, and .Net Core and Standard 2.1+, you can use a ProcessStartInfo for multiple command-line arguments
tsArgs = new string[] { "Test Pass0", "TP1", "TP2" };
Process p = new Process();
try
{
p.StartInfo.FileName = BURS_Dir;
foreach (var arg in tsArgs)
p.StartInfo.ArgumentList.Add(arg);
p.Start();
}
catch
{ //
}
Alternatively, just add them directly
Process p = new Process
{
StartInfo =
{
FileName = BURS_Dir,
ArgumentList = { "Test Pass0", "TP1", "TP2" },
}
};
try
{
p.Start();
}
catch
{ //
}

How to use delegate to point a bunch of logs written to .txt to somewhere else? C#

I just learned about delegates and the publisher/subscriber pattern, however I have been having some problem implementing them in my current code, mainly because Im not sure what should be assign to what(I shall explain this).
I have a class, example Class A. It is a library class that contains codes that write logs into .txt file. I would like to be able to take these logs and write them somewhere else, example another .txt file/TextBox/RichTextBox.
Class A
//Just a library class for log functions
//Declare and instantiate the delegate
public void delegate myDel(string message)
public myDel customDel, customDel2
LogCategory(string category)
{
//Bunch of codes that separates the log into category Info/Warn/Error
WriteLog()
}
WriteLog()
{
StreamWriter sw = new StreamWriter(LogFilePath)
//writes logs into .txt file1
}
then in a separate class
Class B
//This is the main program where all the logs are written
public void PrintLog(string message)
{
Class A ca = new Class A();
ca.LogCategory();
}
public void delegateTheLogs()
{
//how do I use customDel to write the logs to another text file in a
//different directory
}
The idea is that delegate is suppose to:
act as a pointer
allow the program to write logs to multiple destination at the same time
The question is what do I use customDel for and how do I use it catch the logs and write them somewhere?
I think this is an interesting topic, and if anyone knows how to do this, please help me figure this out.
Oh and Im not interested in using events, I know delegate and events are pretty common to use together.
Thanks
Following on from my comment, here's an example. We have a class called FlexibleLogger that basically knows how to format stuff that it is given but it doesn't have any baked in ability to write the log data to anywhere, the idea being that the code that creates the logger also creates the routine that the logger will use to output:
public class FlexibleLogger{
Action<string> _logWriterAction;
public FlexibleLogger(Action<string> logWriterAction){
_logWriterAction = logWriterAction;
}
public Log(string message){
_logWriterAction($"{DateTime.UtcNow}: {Message}");
}
public Log(Exception ex){
Log(ex.Message);
}
}
This class doesn't know how to write a file, or console, or post the message to a web service, or email it, or put it in a rabbit queue etc.. all it knows how to do is formulate a log message provided into having a time at the start, or pull the message out of an exception(and then pass it to the method that puts a time at the start), and then call the Action (a neater way of declaring a delegate that takes arguments of various types and returns no value) passing in the message
The Action is some variable(able to be varied) method created by you
We might use it like this:
public class Program
{
public static void Main()
{
//it's a "local function", IMHO a neater way of providing a method that can be passed as an action
void consoleWriterFunc(string f){
Console.WriteLine(f);
};
//see the thing we pass as the Action parameter is a method/function,
//not a data item like a string, int, Person etc
var logger = new FlexibleLogger(consoleWriterFunc);
//log will make a string like "12-Dec-2020 12:34:56: a"
//and invoke the consoleWriterFunc, passing the string into it
//in turn it prints to the console
logger.Log("a");
//how about a logger that writes a file?
void fileWriterFunc(string f){
File.AppendAllText("c:\\temp\\some.log", f);
};
logger = new FlexibleLogger(fileWriterFunc);
logger.Log(new Exception("something bad happened"));
}
}
Doesn't have to be a local function, you can pass any method at all that takes a string and returns a void, as your Action<string>. It doesn't even have to be a method you wrote:
var sw = new System.IO.StringWriter();
var logger = new FlexibleLogger(sw.Write);
logger.Log("I'm now in the string writer" );
Microsoft wrote the method StringWriter.Write- it takes a strong, returns a void and calling logger.Log having passed the Write method of that stribgwriter instance means that the logger will Log into the stringwriter (a wrapper around a stringbuilder)
Hopefully this helps you understand that a delegate is "just a way to make a method into something you can pass as a parameter, just like anything else. They've been available for years, if you think about it, manifested as events. Microsoft have no idea what you want to do when you click a button, so they just have the button expose an event, which is really just a collection of delegates; a List of methods that the button should call when it's clicked.

Have PowerShell script use C# code, then pass arguments to Main method

I found a technet blog article the said it was possible to have PowerShell use C# code.
Article: Using CSharp (C#) code in Powershell scripts
I found the format I need to get the C# code to work in PowerShell, but if it don't pass the Main method an argument ([namespace.class]::Main(foo)) the script throws an error.
Is there a way I can pass a string of "on" or "off" to the main method, then depending on which string is passed run an if statement? If this is possible can you provide examples and/or links?
Below is the way I'm currently trying to structure my code.
$Assem = #( //assemblies go here)
$source = #"
using ...;
namespace AlertsOnOff
{
public class onOff
{
public static void Main(string[] args )
{
if(args == on)
{//post foo }
if(arge == off)
{ //post bar }
}
"#
Add-Type -TypeDefinition $Source -ReferencedAssumblies $Assem
[AlertsOnOff.onOff]::Main(off)
#PowerShell script code goes here.
[AlertsOnOff.onOff]::Main(on)
Well to start, if you are going to compile and run C# code, you need to write valid C# code. On the PowerShell side, if you invoke Main from PowerShell, you need to pass it an argument. PowerShell will automatically put a single argument into an array for you, but it won't insert an argument if you don't have one. That said, its not clear why this is in a Main method. It's not an executable. It could very well just have two static methods, TurnOn and TurnOff. The code below compiles and runs, modify as you see fit:
$source = #"
using System;
namespace AlertsOnOff
{
public class onOff
{
public static void Main(string[] args)
{
if(args[0] == `"on`")
{
Console.WriteLine(`"foo`");
}
if(args[0] == `"off`")
{
Console.WriteLine(`"bar`");
}
}
}
}
"#
Add-Type -TypeDefinition $Source
[AlertsOnOff.onOff]::Main("off")
# Other code here
[AlertsOnOff.onOff]::Main("on")

creating an extension for c# application which calls static functions defined in host program

I want to create engine for extensions, right now I have a basic class called "Module" which contains lot of virtual functions, each extension is a class that inherits "Module" and override the functions (mostly hooks) with own code.
I want to be able to put these extensions to separate binary and to load it "on the fly".
I created a simple handler for loading these plugins:
public static bool LoadMod(string path)
{
try
{
if (File.Exists(path))
{
System.Reflection.Assembly library = System.Reflection.Assembly.LoadFrom(path);
if (library == null)
{
Program.Log("Unable to load " + path + " because the file can't be read", true);
return false;
}
Type[] types = library.GetTypes();
Type type = library.GetType("wmib.RegularModule");
Type pluginInfo = null;
foreach (Type curr in types)
{
if (curr.IsAssignableFrom(type))
{
pluginInfo = curr;
break;
}
}
if (pluginInfo == null)
{
Program.Log("Unable to load " + path + " because the library contains no module", true);
return false;
}
Module _plugin = (Module)Activator.CreateInstance(pluginInfo);
return true;
}
}
catch (Exception fail)
{
core.handleException(fail);
}
return false;
}
My problem is that these modules are calling functions in static classes that I have in host application. Is it possible to create some "skeleton" of this static class in source code of extension? I tried creating the class in the second source code with empty functions of same name and parameters, but that doesn't work for some reason (every time I attempt to load it I get: Exception has been thrown by the target of an invocation.mscorlib at System.Reflection.MonoCMethod.Invoke)
Here is example of what I want to be able to do:
There is a host application that contains extension handler, and some own static functions.
The host application loads the module from binary file and insert it to some array of modules (this is what is done in constructor of every class that inherits "Module". This module contains some functions which are calling the static functions of host application. What I need to be able to do is being able to call the static function that is defined in source code of host application, even in source code of plugin, which doesn't contain the static function's code. I hope it's clear a bit :)
Ok, it's actually not that hard as I thought, I just referenced the host binary to extension, which enabled all static elements and classes of host in the extension.

Categories