#region Copyright 2010-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.CodeDom.Compiler; using System.IO; using System.Runtime.InteropServices; using CSharpTest.Net.IO; using CSharpTest.Net.Utils; using Microsoft.CSharp; using Microsoft.VisualBasic; namespace CSharpTest.Net.Processes { /// /// Defines the information needed to run various types of scripts on a Windows host /// public class ScriptEngine { /// /// Defines the languages we know how to run, or 'Other' when user-defined /// public enum Language { /// .JS Javascript file JScript = 1, /// .VBS VBScript file VBScript = 2, /// .CMD Shell Script Cmd = 3, /// PowerShell (v2, or v1) PowerShell = 4, /// .CS C# Program CSharp = 5, /// Visual Basic .Net Program VBNet = 6, /// The script is an executable's path Exe = 7, } /// /// Options for script execution /// [Flags] public enum Options : int { /// None = 0, /// Sends the script to the process via std::in rather than using a temp file UsesStandardInputScript = 0x0001, } internal static ScriptEngine[] Engines = new ScriptEngine[] { new ScriptEngine((Language)0, "", "", ".", Options.None), new ScriptEngine(Language.JScript, null, "//B //E:Javascript //Nologo \"{SCRIPT}\"", ".js", Options.None), new ScriptEngine(Language.VBScript, null, "//B //E:VBscript //Nologo \"{SCRIPT}\"", ".vbs", Options.None), new ScriptEngine(Language.Cmd, null, "/C \"{SCRIPT}\"", ".cmd", Options.None), new ScriptEngine(Language.PowerShell, null, "-Command -", ".psh", Options.UsesStandardInputScript), new ScriptEngine(Language.CSharp, "{SCRIPT}", "", ".exe", Options.None), new ScriptEngine(Language.VBNet, "{SCRIPT}", "", ".exe", Options.None), new ScriptEngine(Language.Exe, "{SCRIPT}", "", ".exe", Options.None), }; /// /// Returns the default execution options for the specified scripting type /// public static ScriptEngine GetDefaults(Language type) { return new Cloning.MemberwiseClone().Clone(Engines[(int)type]); } private readonly string _argumentFormat; private readonly Options _runOptions; private readonly Language _language; private readonly Converter _compile; private string _executable; private string _fileExtension; private ScriptEngine(Language type, string executable, string argumentFormat, string fileExtension, Options options) : this(type, executable, argumentFormat, fileExtension, options, null) { } private ScriptEngine(Language type, string executable, string argumentFormat, string fileExtension, Options options, Converter compiler) { _language = type; _executable = Check.NotEmpty(executable ?? FindExecutable(type)); _argumentFormat = argumentFormat ?? String.Empty; _fileExtension = fileExtension ?? "."; _runOptions = options; _compile = compiler; if(_compile == null) { if(type == Language.Exe) _compile = SetExePath; else if (type == Language.CSharp) _compile = CodeCompiler; else if (type == Language.VBNet) _compile = CodeCompiler; else _compile = ScriptWriter; } } /// Returns the type/language of the script public Language ScriptType { get { return _language; } } /// The script engine executable public string Executable { get { return _executable; } } /// The arguments to run the script public string ArgumentFormat { get { return _argumentFormat; } } /// The file extension of the script public string FileExtension { get { return _fileExtension; } } /// The run options public Options RunOptions { get { return _runOptions; } } /// Preprocessing/Compiler routine public TempFile Compile(String script) { return _compile(script); } /// /// Returns true if the script should be fed into the std::in stream of the script process /// public bool UsesStandardInputScript { get { return ((RunOptions & Options.UsesStandardInputScript) == Options.UsesStandardInputScript); } } private TempFile SetExePath(string script) { _executable = script.Trim(); _executable = FileUtils.ExpandEnvironment(_executable); if (!Path.IsPathRooted(_executable)) { string found; if (File.Exists(_executable)) _executable = Path.GetFullPath(_executable); else if(FileUtils.TrySearchPath(_executable, out found)) _executable = found; else throw new FileNotFoundException(new FileNotFoundException().Message, script.Trim()); } _fileExtension = Path.GetExtension(script.Trim()); TempFile temp = new TempFile(); temp.Delete(); return temp; } private TempFile ScriptWriter(string script) { TempFile f = TempFile.FromExtension(FileExtension); f.WriteAllBytes(System.Text.Encoding.ASCII.GetBytes(script)); return f; } private TempFile CodeCompiler(string script) where TCompiler : CodeDomProvider, new() { TempFile exe = TempFile.FromExtension(".exe"); exe.Delete(); TCompiler csc = new TCompiler(); CompilerParameters args = new CompilerParameters(); args.GenerateExecutable = true; args.IncludeDebugInformation = false; args.ReferencedAssemblies.Add("System.dll"); args.OutputAssembly = exe.TempPath; CompilerResults results = csc.CompileAssemblyFromSource(args, script); StringWriter sw = new StringWriter(); foreach (CompilerError ce in results.Errors) { if(ce.IsWarning) continue; sw.WriteLine("{0}({1},{2}: error {3}: {4}", ce.FileName, ce.Line, ce.Column, ce.ErrorNumber, ce.ErrorText); } string errorText = sw.ToString(); if (errorText.Length > 0) throw new ApplicationException(errorText); if (!exe.Exists) throw new FileNotFoundException(new FileNotFoundException().Message, exe.TempPath); _executable = Path.GetFullPath(exe.TempPath); return exe; } private static string FindExecutable(Language type) { string windir = Environment.GetFolderPath(Environment.SpecialFolder.System); switch (type) { case Language.CSharp: return Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "CSC.exe"); case Language.JScript: case Language.VBScript: return Path.Combine(windir, "CScript.exe"); case Language.Cmd: return Path.Combine(windir, "Cmd.exe"); case Language.PowerShell: { string path = null; path = Microsoft.Win32.Registry.GetValue( @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\2\PowerShellEngine", @"ApplicationBase", null) as string; if (path == null) path = Microsoft.Win32.Registry.GetValue( @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine", @"ApplicationBase", null) as string; if (path == null) path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"WindowsPowerShell\v1.0\"); return Path.Combine(path ?? String.Empty, "powershell.exe"); } default: throw new ArgumentOutOfRangeException(); } } } }