The .Net Framework ships with a C# code compiler that lets you generate in-memory assemblies. This can be used to run C# scripts without the need for the installation of a large application like PowerShell. The following code, which targets .Net 2.0, builds into a 7K executable, and is all that is needed to run C# source files from the command line:
using System;
using System.CodeDom.Compiler;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.CSharp;
using System.Collections.Generic;
static class Program
{
/// <summary>
/// Executes specified C# script file
/// </summary>
/// <param name="args">Path of C# script file</param>
static void Main(string[] args)
{
// Check parameters
if (args.Length == 0)
{
Console.WriteLine("Please specify a C# script file");
Environment.Exit(-1);
}
// First parameter is source file path
string path = args[0];
// Check file exists
if (!File.Exists(path))
{
Console.WriteLine("Specified file does not exist");
Environment.Exit(-1);
}
// Read source from file
string source = ReadFile(path);
// Initialize compiler options
CompilerParameters compilerParameters =
new CompilerParameters();
compilerParameters.GenerateExecutable = true;
compilerParameters.GenerateInMemory = true;
compilerParameters.TreatWarningsAsErrors = true;
compilerParameters.CompilerOptions = "/nowarn:1633"; // unrecognized pragmas
// Prepass source for #pragma reference statements
StringReader reader = new StringReader(source);
while (true)
{
string line = reader.ReadLine();
if (line == null) break;
string pattern =
"\\s*#pragma\\s+reference\\s+\"(?<path>[^\"]*)\"\\s*";
Match match = Regex.Match(line, pattern);
if (match.Success)
compilerParameters.ReferencedAssemblies.Add
(match.Groups["path"].Value);
}
// Specify .NET version
Dictionary<string, string> providerOptions =
new Dictionary<string, string>();
providerOptions.Add("CompilerVersion", "v3.5");
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
// Compile source
CompilerResults results =
provider.CompileAssemblyFromSource(
compilerParameters,
new string[] { source });
// Show errors
if (results.Errors.HasErrors)
{
Console.WriteLine("Errors Building " + path);
foreach (var err in results.Errors)
Console.WriteLine(err);
Environment.Exit(-1);
}
// Extract argument tail
string[] parameters = new string[args.Length - 1];
Array.Copy(args, 1, parameters, 0, args.Length-1);
// Invoke compiled assembly's entry point
results.CompiledAssembly.EntryPoint.Invoke
(null, new object[1] { parameters });
}
private static string ReadFile(string path)
{
using (StreamReader reader = File.OpenText(path))
return reader.ReadToEnd();
}
}
Script files look just like Console applications:
using System;
public class Class1
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
Finally additional assemblies can be referenced using #pragma directives:
#pragma warning disable 1633 // disable unrecognized pragma directive warning
#pragma reference "System.Windows.Forms.dll"
using System.Windows.Forms;
public class Class1
{
static void Main(string[] args)
{
MessageBox.Show("Hello World");
}
}