RunNET
Version 1.0.0 (September 21, 2011)
Sometimes I just wish I could use the power of the .NET
Framework for scripting. I use cmd files a lot for automating daily
tasks, like making backups. However convoluted their syntax,
they're actually quite powerful. And if you make an effort, you can
keep them quite readable. Using cmd files has its advantages:
they're really easy to update (i.e. if you want to add a directory
to the backup), they're kind of debuggable with Shell Runner and
they're sort of self-configuring:
Set SRC=C:\Data
Set DEST=D:\Backups\Data
For %%D in (Photos Music Stuff) Do XCOPY %SRC%\%%D bla bla
You get the drift.
But, as mentioned before: the syntax is truly horrible.
Now, of course, I could write a nice VB.NET console application
to do the same, but I'd have to make an executable file to run it,
which makes me lose two advantages: I can't inspect the program
without the source code, let alone update it. Of course, updating
should not be that necessary if I make the source and destination
directories either arguments to the application, or store them in
some configuration file. But then I'm making a real application -
parsing arguments, reporting errors, reading configuration - while
all I want to do is create, edit and run some code!
This wouldn't be the Home of Tools if we hadn't found a
solution. In this case, it's called RunNET. It's a console
application that you run like this:
RunNET test.vb
where test.vb looks lilke this:
Imports System
Public Class Program
' It can be either a Sub or a function returning Integer
Public Shared Function Main() As Integer
Console.WriteLine("Hello world from VB.NET!")
Return 1234
End Function
End Class
The result is exactly what you would expect: Hello world on the
console. The fun thing, of course, is that you have the entire .NET
Framework at your disposal: System.IO, System.Xml, System.Data,
etc, plus redirection and so forth from the command line.
The heavy lifting is done by a class called CodeRunner which, in turn, relies
excusively on the built-in code compilation abilities of the .NET
Framework. Basically, it compiles the code snippet into an
in-memory assembly, finds the Program class in that assembly,
locates its Main() method, and calls it, supplying arguments if
necessary. It then returns the value returned by Main(), which is a
nullable integer.
This approach makes the VB-code run as if it's interpreted.
That's not true, of course: an assembly is in fact generated, just
never saved to disk. The overhead necessary to accomplish this is -
again, of course - huge. The source code is actually compiled by
the .NET compiler, just like Visual Studio would make an executable
for it. But you know what? On my development machine it takes so
little time I don't notice it at all! And another advantage is that
the entire program is compiled before it's run, so there
are no nasty syntax errors that only crop up in certain run-time
situations.
So there you are: RunNET runs .NET programs like scripts.
Adding references
If you take a look at CodeRunner (all code is on CodePlex, as
usual) you'll notice first of all that it's not that much code at
all. That's because .NET's built-in compiler support does all the
hard work. That's also used to link a source file's extension to a
language (actually, a
CodeDomProvider).
Second, the whole compile process is parameterized using the
CompilerParameters class. The only thing we can't pass in
automatically is any external references the code may need. These
need to be specified in the parameters, so how do we get to them?
The simple solution would be to require arguments for RunNET, that
it can pass on to CodeRunner. But that's not a good idea at all -
how can I remember (or even determine!) which references I need to
pass? I just want to run the code snippet!
So I decided to encode the references into the code snippet
itself. The main RunNET code examines the snippet using a regular
expression that finds "url's" of the type
ref://System.Data.dll
in the code. So if you add the name of the DLL you would like to
reference somewhere in the snippet, RunNET will pick them up and
pass them to CodeRunner. Here's an example that needs a reference
to System.Net.dll:
using System;
using System.Xml; // ref://System.Xml.dll
public class Program
{
public static void Main()
{
Console.WriteLine(XmlNodeOrder.Before.ToString());
}
}
Use RunNET's /v option (see below) to resolve reference issues -
it will tell you which references were found in the code
snippet.
Usage
When you run RunNET without arguments, it will report:
RunNET: RunNET v1.0.0 (64-bit)
Copyright © MOBZystems, 2011
http://www.mobzystems.com/tools/runnet
Run .NET code as a console application.
Usage: RunNET [/v] source.ext [arg [arg ...]]
Options:
/v - verbose (show result of call to Program.Main and exit code)
/l - show supported languages and extensions on this computer
source.ext - source code to run. Must have a valid language extension.
The static/Shared method Main() in the public class Program is called,
and its result is returned as exit code for RunNET.
Main() must return an int/Integer, or return void/be a Sub.
It must accept no arguments, or an array of string, specifying arguments
arg - argument(s) passed to Program.Main()
Normally, you specify a source file to run, plus any arguments
you need for it:
RunNET AddDays.vb 2011-09-21 16
The /v option will show more information.
Language support
When the /l option is used, RunNET will display all language
providers installed on your PC. Mine reports:
Provider: CSharpCodeProvider
Supported languages: "c#", "cs", "csharp"
Supported extensions: ".cs"
Provider: VBCodeProvider
Supported languages: "vb", "vbs", "visualbasic", "vbscript"
Supported extensions: ".vb"
Provider: JScriptCodeProvider
Supported languages: "js", "jscript", "javascript"
Supported extensions: ".js"
The CodeDom provider type "(...)VJSharpCodeProvider(...)" could not be located.
Provider: CppCodeProvider
Supported languages: "c++", "mc", "cpp"
Supported extensions: ".h"
This indicates I have code providers for VB.NET, C#, JScript,
VJ#, and C++. VB.NET and C# work out of the box, but the VJ#
provider cannot be loaded (I haven't installed it, so that fits),
but I have other problems with JScript. JScript does not support
classes, so I can't create a Program class in code. Besides, there
is a jscript interpreter already, so it doesn't make much sense
using RunNET on it. C++ is yet another story: the file extension
.cpp is not supported by the CppCodeProvider - only .h. That means
RunNET will not be able to run .cpp snippets. But somehow I don't
see C++ as a scripting language anyway - weird, huh?
Example: a message box
The following 'script' will display a message box using
parameters on the command line:
Option Strict On
Option Explicit On
Imports System
Imports System.Windows.Forms ' ref://System.Windows.Forms.dll
Public Class Program
Public Shared Function Main(arguments() As String) As Integer
' We need at least 2 arguments
If arguments.Length < 2 Then
' Show usage
Console.WriteLine("Usage: MsgBox <title> <prompt> [buttons [icon]]")
Console.WriteLine()
' Show possible values for the button argument
Console.WriteLine("Possible values for buttons (default is OKCancel):")
For Each mbb As String In [Enum].GetNames(GetType(MessageBoxButtons))
Console.WriteLine("- " + mbb)
Next
' And for the icon argument
Console.WriteLine("Possible values for icon (default is Question):")
For Each mbi As String In [Enum].GetNames(GetType(MessageBoxIcon))
Console.WriteLine("- " + mbi)
Next
Console.WriteLine()
Console.WriteLine("Return value is:")
For Each dr As DialogResult In [Enum].GetValues(GetType(DialogResult))
If dr <> DialogResult.None Then
Console.WriteLine(String.Format("- {0} ({1})", dr, CInt(dr)))
End If
Next
Return -1
Else
' Get the first two arguments
Dim title As String = arguments(0)
Dim prompt As String = arguments(1)
' Get the button argument
Dim buttons As MessageBoxButtons = MessageBoxButtons.OKCancel
If arguments.Length > 2 Then
buttons = DirectCast(
[Enum].Parse(GetType(MessageBoxButtons), arguments(2), True),
MessageBoxButtons
)
End If
' Get the icon argument
Dim icon As MessageBoxIcon = MessageBoxIcon.Question
If arguments.Length > 3 Then
icon = DirectCast(
[Enum].Parse(GetType(MessageBoxIcon), arguments(3), True),
MessageBoxIcon
)
End If
' Do the work!
Return MessageBox.Show(Nothing, prompt, title, buttons, icon)
End If
End Function
End Class
The result is returned as an integer and
available to the caller in the cmd variable %ERRORLEVEL%.
Download RunNET
Download RunNET 1.0.0 (September 21, 2011) from CodePlex
32/64-bit executable
Comments? Bugs? Suggestions? Feature requests? Let us know!