码农总是有许许多多的脚本工具,但windows下的bat脚本实在承担不起太多的逻辑和功能,PowerShell又太过复杂,不想学。干脆做个简单的可以执行C#脚本的工具。
嗯,花了2小时写代码加调试,总算是完成了一个简单的版本,具备如下特征:
- 动态载入命令行参数指定的脚本
- 允许在命令行附加脚本参数
- 脚本namespace必须为Script,class必须为Program,主函数必须是public static void Main(string[] args)
- 脚本开发可用注释添加需要引入的程序集,例如//dll log4net.dll,引入的程序集必须位于脚本程序同目录下。
下附代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
namespace css {
class Program {
static void Main(string[] args) {
if (args.Length == 0) {
Usage();
return;
}
if (!File.Exists(args[0])) {
Console.WriteLine("Can't find {0}.", args[0]);
return;
}
//read script file
string lines = File.ReadAllText(args[0], Encoding.UTF8);
IList<string> dlls = ParserUseDlls(lines);
CSharpCodeProvider codePrivoder = new CSharpCodeProvider();
CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.ReferencedAssemblies.Add("mscorlib.dll");
compilerParameters.ReferencedAssemblies.Add("System.dll");
compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
string exe_path = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (!exe_path.EndsWith("/")) exe_path += "/";
for (int i = 0, n = dlls.Count; i < n; i++)
compilerParameters.ReferencedAssemblies.Add(exe_path + dlls[i]);
compilerParameters.GenerateExecutable = false;
compilerParameters.GenerateInMemory = true;
CompilerResults cr = codePrivoder.CompileAssemblyFromSource(compilerParameters, lines);
if (cr.Errors.HasErrors) {
Console.WriteLine("编译错误:");
foreach (CompilerError err in cr.Errors) {
Console.WriteLine(err.ErrorText);
}
}
else {
// 通过反射,调用脚本文件的Main函数
Assembly objAssembly = cr.CompiledAssembly;
object objProgram = objAssembly.CreateInstance("Script.Program");
MethodInfo objMain = objProgram.GetType().GetMethod("Main");
object[] sargs = new object[1];
if (args.Length > 1) {
sargs[0] = new string[args.Length - 1];
Array.Copy(args, 1, (string[])sargs[0], 0, sargs.Length);
}
else sargs[0] = new string[0];
Console.WriteLine(objMain.Invoke(objProgram, sargs));
}
}
private const string DLL_PREFIX = "//dll ";
static IList<string> ParserUseDlls(string lines) {
var ret = new List<string>();
bool line_flag = false;
for (int i = 0, n = lines.Length, start = 0; i < n; i++) {
char c = lines[i];
if (c == '\r' || c == '\n') {
if (line_flag) continue;
else line_flag = true;
string line = lines.Substring(start, i - start);
if (line.StartsWith(DLL_PREFIX)) {
ret.Add(line.Substring(DLL_PREFIX.Length));
}
else break;
}
else if (line_flag) {
start = i;
line_flag = false;
}
}
return ret;
}
static void Usage() {
Console.WriteLine("Usage: {0} <script>", Path.GetFileName(typeof(Program).Assembly.Location));
Console.WriteLine("");
Console.WriteLine("-------------Example--------------");
Console.WriteLine("//dll log4net.dll");
Console.WriteLine("using System;");
Console.WriteLine("namespace Script {");
Console.WriteLine(" public class Program {");
Console.WriteLine(" public static void Main(string[] args) {");
Console.WriteLine(" Console.WriteLine(\"Hello World!\");");
Console.WriteLine(" }");
Console.WriteLine(" }");
Console.WriteLine("}");
Console.WriteLine("");
}
}
}