Introduction
NScript is a tool similar to WScript except that it allows scripts to be written in C#. NScript automatically compiles the code into an assembly in memory and executes the assembly. This enables any code written in these files to be executed directly by double clicking on these files in windows explorer. I wrote this tool when I needed to write a script for automating builds. A simple batch file was not sufficient for the task. This tool came in handy as I could modify the scripts easily and execute them by double clicking on the files in windows explorer.
Using NScript
- Write some code in C# like
using System.Windows.Forms; class Test { static void Main(string[] args) { MessageBox.Show("Hello World!", "NScript"); } };
- Save the file and give it an extension of
.ncs
. The nscript executables will run these directly. - To cancel the execution when running in console mode press Ctrl+C or Ctrl+Break. When running under windows mode (NScriptW) an animated icon is shown in system tray. Double clicking on the icon cancels the execution.
The requirements for the code to be executed by NScript are:-
- The code has to have a class defined.
- The class has to have a Main method which takes single argument of type array of strings.
- You can refer to any assemblies listed in a file named
NScript.nrf
if it exists in the same directory as the executable. For example:System.Web.dll System.Web.RegularExpressions.dll System.Web.Services.dll System.Windows.Forms.Dll System.XML.dll
How it Works
The NScript solution has three projects:
- NScript - a C# console application
- NScriptW - a C# windows application
- NScriptLib - a C# class library
NScript and NScriptW are very much similar to each other; the former can be used to run scripts that can output to console. NScript shows error messages in console where as NSCriptW shows error messages using message boxes. It is NScriptW which is associated with file extensions. Since there is lot of code that is shared by the two executables the common code is compiled in NScriptLib and both the executables refer to this class library.
The code behind NScript is pretty simple :
- NScript creates an asynchronous delegate that does
the compilation and execution asynchronously.
CompileAndExecuteRoutine asyncDelegate = new CompileAndExecuteRoutine(this.CompileAndExecute); IAsyncResult result = asyncDelegate.BeginInvoke(fileName, newargs, this, null, null); //For a windows app a message loop and for a // console app a simple wait ExecutionLoop(result); asyncDelegate.EndInvoke(result);
- The compilation and execution routine creates a
separate
AppDomain
where it does the main compilation and execution. This is done because the user may require to cancel the execution of he script, in that case theAppDomain
can simply be unloaded.
//Create an AppDomain to compile and execute the code //This enables us to cancel the execution if needed executionDomain = AppDomain.CreateDomain("ExecutionDomain"); IScriptManager manager = (IScriptManager) executionDomain.CreateInstanceFromAndUnwrap( typeof(BaseApp).Assembly.Location, typeof(ScriptManager).FullName); manager.CompileAndExecuteFile(file, args, this);
IScriptManager
interface is implemented by the typeScriptManager
. Since any object has to be marshaled in order for it to be referenced from a separateAppDomain
, we need the interfaceIScriptManager
(as opposed to directly using theScriptManager
object). TheScriptManager
type extends theMarshalByRefObject
as it is marshaled by reference.public class ScriptManager : MarshalByRefObject, IScriptManager
- The main compilation and execution is carried out by the
ScriptManager
object'sCompileAndExecute
method. It uses CodeDOM to carry out the compilation. It first figures out theCodeDomProvider
to use based on the extension of the input script file://Currently only csharp scripting is supported CodeDomProvider provider; string extension = Path.GetExtension(file); switch(extension) { case ".cs": case ".ncs": provider = new Microsoft.CSharp.CSharpCodeProvider(); break; case ".vb": case ".nvb": provider = new Microsoft.VisualBasic.VBCodeProvider(); break; case ".njs": case ".js": provider = (CodeDomProvider)Activator.CreateInstance( "Microsoft.JScript", "Microsoft.JScript.JScriptCodeProvider").Unwrap(); break; default: throw new UnsupportedLanguageExecption(extension); }
- Once we have a
CodeDomProvider
we can compile the file into a temporary assembly using theICodeCompiler
interfaceSystem.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler(); System.CodeDom.Compiler.CompilerParameters compilerparams = new System.CodeDom.Compiler.CompilerParameters(); compilerparams.GenerateInMemory = true; compilerparams.GenerateExecutable = true; System.CodeDom.Compiler.CompilerResults results = compiler.CompileAssemblyFromFile(compilerparams, file);
- And finally the entry point method of the just compiled assembly is invoked.
results.CompiledAssembly.EntryPoint.Invoke( null, BindingFlags.Static, null, new object[]{args}, null);
Conclusion
This is a very simple tool and I must confess that this is in no way an original idea. Don Box wrote a similar tool
a long time back but I could not locate it. As a result I decided to write my own. In future I hope to enhance it by allowing
to compile and execute XML files similar to ".wsh" files. As usual any suggestions are welcome.