Title:       C# Script (missing puzzle piece)
Author:      Oleg Shilo 
Email:       oshilo@gmail.com
Environment: .NET Framework 1.1, Microsoft Visual Studio .NET 2003 
Keywords:    C#, Script, Reflection, .NET Framework
Level:       Intermediate"
Description: An article on "scripting engine" for C# language
Section      General C#
SubSection   C# Programming

Introduction

There are very specific programming tasks that require significant effort to accomplish by traditional development scenario:

design->coding->compiling->application.

On the other hand it can be achieved quite successfully by using some scripting languages. Scripting can be very useful when:

This article presents “scripting engine” for C# language, which I have implemented to simplify such usual tasks as configuring development or testing environment, automating build procedure, automating testing and collecting test results… All the tasks that I, as an active C++/C# programmer, do not normally like to do.

Background

Microsoft has not provided any adequate scripting solution. VBScript was the language, which they expected to be used for scripting. Despite the fact that VB functionality can be extended (with medium effort) by creating COM components its native functionality is extremely limited and syntactic concept is badly designed. I believe, a lot of people know that terrible aftertaste when working with VBScript.

When .NET arrived I expected that a script engine for C# language will solve this problem however it did not happened. Microsoft did not provide a C# engine for WSH.

And here is a reason given in MSDN:

Scripting has very little to do with .NET for two good reasons. First and foremost, WSH relies completely on COM. Second, the advantages you get from .NET languages over scripting languages are substantial in terms of performance and programming ease.

Microsoft provided only three scripting engines with .NET: old VBScript and Jscript and also .NET intermediate language (IL).

I do not know why C# is not there. A lot of developers need a powerful scripting language not because it does or does not fit some existing software concept but because of all the reasons that I mentioned earlier (simple and rapid development, frequent changes, effortless maintenance and deployment). I consider the absence of a script engine for C# as Microsoft marketing mistake.

I was very excited when I saw Python for the first time. Finally there was a powerful and flexible scripting solution. Here are some of the “hard to match” Python features:

I was very excited when I saw Python for the first time. Finally there was a powerful and flexible scripting solution. Here are some of the “hard to match” Python features: Language is very rich Possibility to precompile and avoid using compiling “on fly” Ability to do GUI development Possibility to create objects (yes it is an OO language) Extensible by developing the modules in the same language Portable And the more I worked with Python the more I wanted to have something similar for C#. The idea was in the air. Browsing the WEB, I have come across a few attempts to tackle this problem. All the time I felt it was possible to use C# as a script language. One of the reasons for this is that running an application under CLR is very similar to running a script under script engine. In both cases execution happens under runtime system and in both cases language interpretation takes place. Another interesting thing about .NET framework is that the C# compiler can be hosted by an application. All this eventually allowed me to implement a C# scripting engine. This host application has nothing to do with WSH despite some interface resemblance.

C# Script Engine

I wouldn’t like to spend too much time describing script engine application. Its code is available, help yourself out. But still I have to say a few words about it.

Design of such application is straightforward. Script execution consists of two steps: compilation and execution.

Compilation part is trivial and a sample code can be found in MSDN .

The more challenging task was to implement loading of all referenced assemblies from. The general problem with referenced assemblies is that it is not possible to determine what assemblies they are by just analysing the code. Microsoft decided to handle this problem the easy way: the user explicitly nominates such assemblies by adding them into a special XML file (.csprj). I could not use such approach as I decided to use a plain vanilla .cs file as the only input file for running the script. My approach was based on the fact that in real life there is a strong correlation between the assembly name and the root namespace. For example the namespace System.Windows.Forms is implemented in the assembly System.Windows.Forms and namespace System.XML implemented in the assembly System.XML. This is also applicable to the assembly file name, which is usually a dll file with the same name as the assembly name. Thus, I resolve namespaces from the script into assemblies. And I do it for both global (GAC) and local assemblies. 

Unfortunately there is no .NET API available for navigating in the GAC only the COM one. But after some research the problem was solved. I’d like to thank atoenne (CodeProject), John Renaud (CodeProject) and Junfeng Zhang for very interesting articles regarding working with GAC. It helped me a lot.

Dealing with the local assemblies is as interesting as with global the ones. I use the predicting assembly file name on the base of the namespace as a starting point. But as I mentioned before, there is only the correlation between the namespace and the assembly name; there is no true relationship. The things can get more complicated if the assembly file name is different to the assembly name . On top of this the assembly root namespace can have no resemblance to any of those names. Thus, it is not possible to reliably predict what assembly is referenced. The only way out of this is to apply Reflection to the assemblies found in the script directory (current directory ). But still there is no garrantee that all namespaces from the script can be resolved. That is why I provided a "back door": the assembly can be explicitly specified as a command line argument. But I have never had to use it as, so far, all the namespaces were resolved. The only restriction I have to impose is thatany assembly, which is referenced in the script must reside either in GAC or in the same folder where the script is".

Basically the script execution looks like this: The user runs the script engine application and specifies a script file as an argument. The script file is just a .cs file with defined method Main(...). Engine compiles the script into assembly and executes a well-known entry point in this assembly “Main”. The advantages of such scripting system are obvious and I will illustrate them by stating some of the features of that new scripting system:

Using C# Script

I have already done some development with C# Script and found system to be stable and reliable. And here are some of the features of the script engine application itself:

The script engine application is named cscscript.exe (or cswscript.exe for WinForm mode). The simplest way to prepare any script is to execute from a command prompt: cscscript /s > Sample.cs. This will generate the Sample.cs file in the current directory. Sample.cs is a C# source file. You will need to add any valid C# code to this file to do your specific tasks. After that it can be compiled and executed by the script engine (command: cscscript.exe sample.cs).

Sample.cs:

using System;
using System.Windows.Forms;

class Script
{
        static public void Main(string[] args)
        {
                MessageBox.Show("Just a test!");

                for (int i = 0; i < args.Length; i++)
                {
                        Console.WriteLine(args[i]);
                }
        }
}

Script execution:

 

Once again script file contains an ordinary C# code. There is no even single language statement that would be understood only by script engine but not by any C# compiler (MS .NET Studio). This is the one of the strongest pints of C# Script:

C# Script is not another flavour of C#. It is C#, which is compiled and executed differently.

Any script file can also be opened, compiled and debugged with .NET Studio because it is just nothing else but C#.

I have to say that I got more than I anticipated at the start of this whole exercise. The script engine is just an application (and relatively primitive one). But its functionality can be extended with scripts. The system main purpose of which is to execute scripts can be improved by other scripts. It may sound odd but it is exactly what happening. Such scripts can:

As you can see the same script engine application is much more capable now.

C# Script Package

You can download the whole ‘C# Script’ package, which contains script engine, useful scripts and the samples from this page (at start of the article) or from my WEB page . To install you will need to extract everything from zip file and execute install.bat and it is ready. Of course .NET framework has to be installed first. After the installation you will have Environment variables updated and all shell extensions created.

Strictly speaking, no installation is required. The script engine application is self-sufficient and can be run immediately after downloading. The only action performed during the installation is an adjustment to the OS environment in order to make scripting as convenient as possible.  

Here is the list of scripts (very simple ones) that I have created and added to the package:

In the future I may want to add some new features to the script engine itself like:

But I am not sure about the second one. That feature is useful for Python as it is the only way to implement complex application even though the simplicity of single file maintenance and deployment is sacrificed. But for C# it is not of great importance because a complex application can be implemented as a traditional C# application (no need to use C# script). All these features would be interesting to implement but they are not particularly useful ones.

Points of Interest

Previously I had about a hundred of C# projects on my hard drive and in most of the cases these projects contained about 100-200 lines of code (just to test some algorithm or code sample from MSDN). Now I have only a single .cs file for any coding exercise. Right-click on it and it is in the .NET Studio and ready to go. It is so convenient!

There is another thing that I really like about scripting. Development and testing can now be done in the same programming language. Here is a simple example:

You need to test your assembly that does, for example, printing. Under runtime conditions the assembly is used by the main application. However you need to do printing thousands times to get some statistics. To do it from main application would mean contaminating the main application with testing code. What you can do is to copy code that invokes the assembly from the main application into .cs file and run it with the script engine. In this case assembly is tested under the same conditions as at run-time and design of the test is more elegant.

Therefore, the script engine combined with the scripts becomes a small Development System. 

Conclusion

C# scripting is not supposed to compete with traditional C# development. They serve completely different purposes. C# scripting is simply something that was missing in .NET family (that ‘missing puzzle piece’ in the title).

I am interested to know what other people think about it and would appreciate any feedback.

Feedback

I would like to thank all people who sent me their suggestions and ideas. Some of them I have implemented in the current version of script engine. There were a few very interesting suggestions that I have not implemented in a way as they were proposed. I will explain it by an example.

there was a proposal to have the script engine, which can take C# statement and execute it without having the actual script written. The idea was good but I did not want to put such functionality into the script engine. I see the engine as a simple, reliable and self-contained application, which is not overloaded with functionality. Only this way it can be robust and stable. I do not want to modify it each time when I decide to execute scrips differently. I see the presented scripting system extensible by other scripts and assemblies rather than by reimplementing the engine. This way script engine will be protected from any changes in the system. That is why I implemented proposed feature as a script (code.cs). Thus the purpose is achieved without any changees in the script engine. The following this is the example of how to use this script:  

cscscript code MessageBox.Show("Just a test!")

Script Samples.cs is implemented in the similar manner:

cscscript samples - prints list of available samples
cscscript samples mailto myMailto.cs - saves content of "mailto" sample into myMailto.cs file.
cscscript samples 1 myScript.cs - saves content of sample #1 into myScript.cs file.

There was another proposal to have the script running within a specified security context. This can be implemented using the same approach. For example:

cscscript runas sa:sysadmin myScript - runs myScript as a user 'sa' with password 'sysadmin'

Thanks again to all people who showed interest in this project. If you are interested in the hot fixes betwee the article upodates you will be able to download them from my WEB page.

History