From bf6601e546f5b7bbf6cbfcdaae6fe8567dcc12b7 Mon Sep 17 00:00:00 2001 From: James Forshaw Date: Tue, 16 May 2017 13:57:04 +0100 Subject: [PATCH] Added support for v4 framework only and made script generation more generic. --- DotNetToJScript/DotNetToJScript.csproj | 3 + DotNetToJScript/IScriptGenerator.cs | 38 ++++++ DotNetToJScript/JScriptGenerator.cs | 127 ++++++++++++++++++ DotNetToJScript/Program.cs | 171 +++++++------------------ DotNetToJScript/VBAGenerator.cs | 112 ++++++++++++++++ DotNetToJScript/app.config | 3 + README | 14 +- 7 files changed, 336 insertions(+), 132 deletions(-) create mode 100644 DotNetToJScript/IScriptGenerator.cs create mode 100644 DotNetToJScript/JScriptGenerator.cs create mode 100644 DotNetToJScript/VBAGenerator.cs create mode 100644 DotNetToJScript/app.config diff --git a/DotNetToJScript/DotNetToJScript.csproj b/DotNetToJScript/DotNetToJScript.csproj index 79f2009..bc38768 100644 --- a/DotNetToJScript/DotNetToJScript.csproj +++ b/DotNetToJScript/DotNetToJScript.csproj @@ -44,8 +44,11 @@ + + + diff --git a/DotNetToJScript/IScriptGenerator.cs b/DotNetToJScript/IScriptGenerator.cs new file mode 100644 index 0000000..8812a71 --- /dev/null +++ b/DotNetToJScript/IScriptGenerator.cs @@ -0,0 +1,38 @@ +// This file is part of DotNetToJScript - A tool to generate a +// JScript which bootstraps an arbitrary v2.NET Assembly and class. +// Copyright (C) James Forshaw 2017 +// +// DotNetToJScript is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// DotNetToJScript is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with DotNetToJScript. If not, see . + +namespace DotNetToJScript +{ + enum RuntimeVersion + { + None, + v2, + v4, + Auto, + } + + interface IScriptGenerator + { + string GenerateScript(byte[] serialized_object, + string entry_class_name, + string additional_script, + RuntimeVersion version, + bool enable_debug); + bool SupportsScriptlet { get; } + string ScriptName { get; } + } +} diff --git a/DotNetToJScript/JScriptGenerator.cs b/DotNetToJScript/JScriptGenerator.cs new file mode 100644 index 0000000..da15d5d --- /dev/null +++ b/DotNetToJScript/JScriptGenerator.cs @@ -0,0 +1,127 @@ +// This file is part of DotNetToJScript - A tool to generate a +// JScript which bootstraps an arbitrary v2.NET Assembly and class. +// Copyright (C) James Forshaw 2017 +// +// DotNetToJScript is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// DotNetToJScript is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with DotNetToJScript. If not, see . + +using System; +using System.Text; + +namespace DotNetToJScript +{ + class JScriptGenerator : IScriptGenerator + { + static string jscript_template = + @"var serialized_obj = [ +%SERIALIZED% +]; +var entry_class = '%CLASS%'; + +try { + setversion(); + var stm = new ActiveXObject('System.IO.MemoryStream'); + var fmt = new ActiveXObject('System.Runtime.Serialization.Formatters.Binary.BinaryFormatter'); + var al = new ActiveXObject('System.Collections.ArrayList') + + for (i in serialized_obj) { + stm.WriteByte(serialized_obj[i]); + } + + stm.Position = 0; + var n = fmt.SurrogateSelector; + var d = fmt.Deserialize_2(stm); + al.Add(n); + var o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class); + %ADDEDSCRIPT% +} catch (e) { + debug(e.message); +}"; + + static string version_detection = @"function setversion() { + var shell = new ActiveXObject('WScript.Shell'); + ver = 'v4.0.30319'; + try { + shell.RegRead('HKLM\\SOFTWARE\\Microsoft\\.NETFramework\\v4.0.30319\\'); + } catch(e) { + ver = 'v2.0.50727'; + } + shell.Environment('Process')('COMPLUS_Version') = ver; +}"; + static string version_v2 = @"function setversion() { + new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v2.0.50727'; +}"; + static string version_v4 = @"function setversion() { + new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319'; +}"; + static string version_none = @"function setversion() {}"; + + static string debug_enabled = @"function debug(s) { WScript.Echo(s); }" + Environment.NewLine; + static string debug_disabled = @"function debug(s) { }" + Environment.NewLine; + + static string GetSetVersionJScript(RuntimeVersion version) + { + string script = version_none; + switch (version) + { + case RuntimeVersion.Auto: + script = version_detection; + break; + case RuntimeVersion.v2: + script = version_v2; + break; + case RuntimeVersion.v4: + script = version_v4; + break; + } + return script + Environment.NewLine; + } + + + public string ScriptName + { + get + { + return "JScript"; + } + } + + public bool SupportsScriptlet + { + get + { + return true; + } + } + + public string GenerateScript(byte[] serialized_object, string entry_class_name, string additional_script, RuntimeVersion version, bool enable_debug) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < serialized_object.Length; ++i) + { + builder.Append(serialized_object[i]); + if (i < serialized_object.Length - 1) + { + builder.Append(","); + } + if (i > 0 && (i % 32) == 0) + { + builder.AppendLine(); + } + } + + return (enable_debug ? debug_enabled : debug_disabled) + GetSetVersionJScript(version) + + jscript_template.Replace("%SERIALIZED%", builder.ToString()).Replace("%CLASS%", entry_class_name).Replace("%ADDEDSCRIPT%", additional_script); + } + } +} diff --git a/DotNetToJScript/Program.cs b/DotNetToJScript/Program.cs index db85cc3..12db6bd 100644 --- a/DotNetToJScript/Program.cs +++ b/DotNetToJScript/Program.cs @@ -27,78 +27,10 @@ using System.Text; using System.Xml; using System.Xml.Schema; -namespace Serialize +namespace DotNetToJScript { class Program { - static string jscript_template = - @" -var serialized_obj = [ -%SERIALIZED% -]; -var entry_class = '%CLASS%'; - -try { - var stm = new ActiveXObject('System.IO.MemoryStream'); - var fmt = new ActiveXObject('System.Runtime.Serialization.Formatters.Binary.BinaryFormatter'); - var al = new ActiveXObject('System.Collections.ArrayList') - - for (i in serialized_obj) { - stm.WriteByte(serialized_obj[i]); - } - - stm.Position = 0; - var n = fmt.SurrogateSelector; - var d = fmt.Deserialize_2(stm); - al.Add(n); - var o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class); - %ADDEDSCRIPT% -} catch (e) { - WScript.Echo(e.message); -}"; - - static string vba_template = - @" -Private Function decodeHex(hex) - On Error Resume Next - Dim DM, EL - Set DM = CreateObject(""Microsoft.XMLDOM"") - Set EL = DM.createElement(""tmp"") - EL.DataType = ""bin.hex"" - EL.Text = hex - decodeHex = EL.NodeTypedValue -End Function - -Function Run() - Dim serialized_obj - %SERIALIZED% - - entry_class = ""%CLASS%"" - - Dim stm As Object, fmt As Object, al As Object - Set stm = CreateObject(""System.IO.MemoryStream"") - Set fmt = CreateObject(""System.Runtime.Serialization.Formatters.Binary.BinaryFormatter"") - Set al = CreateObject(""System.Collections.ArrayList"") - - Dim dec - dec = decodeHex(serialized_obj) - - For Each i In dec - stm.WriteByte i - Next i - - stm.Position = 0 - - Dim n As Object, d As Object, o As Object - Set n = fmt.SurrogateSelector - Set d = fmt.Deserialize_2(stm) - al.Add n - - Set o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class) - %ADDEDSCRIPT% -End Function -"; - static string scriptlet_template = @" @@ -112,6 +44,11 @@ End Function "; + enum ScriptLanguage + { + JScript, + VBA, + } static object BuildLoaderDelegate(byte[] assembly) { @@ -151,7 +88,7 @@ End Function const string DEFAULT_ENTRY_CLASS_NAME = "TestClass"; - static string CreateScriptlet(string script, bool register_script) + static string CreateScriptlet(string script, string script_name, bool register_script) { XmlDocument doc = new XmlDocument(); doc.LoadXml(scriptlet_template); @@ -162,7 +99,7 @@ End Function XmlNode root_node = doc.SelectSingleNode(register_script ? "/package/component/registration" : "/package/component"); XmlNode script_node = root_node.AppendChild(doc.CreateElement("script")); - script_node.Attributes.Append(doc.CreateAttribute("language")).Value = "JScript"; + script_node.Attributes.Append(doc.CreateAttribute("language")).Value = script_name; script_node.AppendChild(doc.CreateCDataSection(script)); using (MemoryStream stm = new MemoryStream()) @@ -204,6 +141,16 @@ End Function { WriteError(String.Format(format, args)); } + + static string GetEnumString(Type enum_type) + { + return String.Join(", ", Enum.GetNames(enum_type)); + } + + static void ParseEnum(string name, out T value) where T : struct + { + value = (T)Enum.Parse(typeof(T), name, true); + } static void Main(string[] args) { @@ -211,17 +158,19 @@ End Function { if (Environment.Version.Major != 2) { - WriteError("This tool only works on v2 of the CLR"); + WriteError("This tool should only be run on v2 of the CLR"); Environment.Exit(1); } string output_file = null; string entry_class_name = DEFAULT_ENTRY_CLASS_NAME; - string script_file = null; + string additional_script = String.Empty; bool mscorlib_only = false; bool scriptlet_moniker = false; bool scriptlet_uninstall = false; - bool vba_code = false; + bool enable_debug = false; + RuntimeVersion version = RuntimeVersion.None; + ScriptLanguage language = ScriptLanguage.JScript; bool show_help = false; @@ -229,10 +178,15 @@ End Function { "n", "Build a script which only uses mscorlib.", v => mscorlib_only = v != null }, { "m", "Build a scriptlet file in moniker format.", v => scriptlet_moniker = v != null }, { "u", "Build a scriptlet file in uninstall format.", v => scriptlet_uninstall = v != null }, - { "v", "Build a VBA file.", v => vba_code = v != null }, + { "d", "Enable debug output from script", v => enable_debug = v != null }, + { "l|lang=", String.Format("Specify script language to use ({0})", + GetEnumString(typeof(ScriptLanguage))), v => ParseEnum(v, out language) }, + { "v", "Build a VBA file (use -lang switch).", v => language = ScriptLanguage.VBA }, + { "ver=", String.Format("Specify .NET version to use ({0})", + GetEnumString(typeof(RuntimeVersion))), v => ParseEnum(v, out version) }, { "o=", "Specify output file (default is stdout).", v => output_file = v }, { "c=", String.Format("Specify entry class name (default {0})", entry_class_name), v => entry_class_name = v }, - { "s=", "Specify file with additional script. 'o' is created instance.", v => script_file = v }, + { "s=", "Specify file with additional script. 'o' is created instance.", v => additional_script = File.ReadAllText(v) }, { "h|help", "Show this message and exit", v => show_help = v != null }, }; @@ -246,10 +200,17 @@ End Function Environment.Exit(1); } - if (vba_code && (scriptlet_moniker || scriptlet_uninstall)) + IScriptGenerator generator; + switch (language) { - WriteError("Cannot use '-v' in combination with scriptlet options."); - Environment.Exit(1); + case ScriptLanguage.JScript: + generator = new JScriptGenerator(); + break; + case ScriptLanguage.VBA: + generator = new VBAGenerator(); + break; + default: + throw new ArgumentException("Invalid script language option"); } byte[] assembly = File.ReadAllBytes(assembly_path); @@ -284,54 +245,14 @@ End Function MemoryStream stm = new MemoryStream(); fmt.Serialize(stm, mscorlib_only ? BuildLoaderDelegateMscorlib(assembly) : BuildLoaderDelegate(assembly)); - byte[] ba = stm.ToArray(); - string template; - StringBuilder builder = new StringBuilder(); - - if (vba_code) - { - template = vba_template; - - string hex_encoded = BitConverter.ToString(ba).Replace("-", ""); - - for (int i = 0; i < hex_encoded.Length; i++) - { - if (i == 0) - { - builder.Append(" serialized_obj = \""); - } - else if (i % 100 == 0) - { - builder.Append("\""); - builder.AppendLine(); - builder.Append(" serialized_obj = serialized_obj & \""); - } - builder.Append(hex_encoded[i]); - } - builder.Append("\""); - - } else { - template = jscript_template; - - for (int i = 0; i < ba.Length; ++i) - { - builder.Append(ba[i]); - if (i < ba.Length - 1) - { - builder.Append(","); - } - if (i > 0 && (i % 32) == 0) - { - builder.AppendLine(); - } - } - } - - string script = template.Replace("%SERIALIZED%", builder.ToString()).Replace("%CLASS%", entry_class_name).Replace("%ADDEDSCRIPT%", File.Exists(script_file) ? File.ReadAllText(script_file) : ""); - + string script = generator.GenerateScript(stm.ToArray(), entry_class_name, additional_script, version, enable_debug); if (scriptlet_moniker || scriptlet_uninstall) { - script = CreateScriptlet(script, scriptlet_uninstall); + if (generator.SupportsScriptlet) + { + throw new ArgumentException(String.Format("{0} generator does not support Scriptlet output", generator.ScriptName)); + } + script = CreateScriptlet(script, generator.ScriptName, scriptlet_uninstall); } if (!String.IsNullOrEmpty(output_file)) diff --git a/DotNetToJScript/VBAGenerator.cs b/DotNetToJScript/VBAGenerator.cs new file mode 100644 index 0000000..9977d6d --- /dev/null +++ b/DotNetToJScript/VBAGenerator.cs @@ -0,0 +1,112 @@ +// This file is part of DotNetToJScript - A tool to generate a +// JScript which bootstraps an arbitrary v2.NET Assembly and class. +// Copyright (C) James Forshaw 2017 +// +// DotNetToJScript is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// DotNetToJScript is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with DotNetToJScript. If not, see . + +using System; +using System.Text; + +namespace DotNetToJScript +{ + class VBAGenerator : IScriptGenerator + { + static string vba_template = + @" +Private Function decodeHex(hex) + On Error Resume Next + Dim DM, EL + Set DM = CreateObject(""Microsoft.XMLDOM"") + Set EL = DM.createElement(""tmp"") + EL.DataType = ""bin.hex"" + EL.Text = hex + decodeHex = EL.NodeTypedValue +End Function + +Function Run() + Dim serialized_obj + %SERIALIZED% + + entry_class = ""%CLASS%"" + + Dim stm As Object, fmt As Object, al As Object + Set stm = CreateObject(""System.IO.MemoryStream"") + Set fmt = CreateObject(""System.Runtime.Serialization.Formatters.Binary.BinaryFormatter"") + Set al = CreateObject(""System.Collections.ArrayList"") + + Dim dec + dec = decodeHex(serialized_obj) + + For Each i In dec + stm.WriteByte i + Next i + + stm.Position = 0 + + Dim n As Object, d As Object, o As Object + Set n = fmt.SurrogateSelector + Set d = fmt.Deserialize_2(stm) + al.Add n + + Set o = d.DynamicInvoke(al.ToArray()).CreateInstance(entry_class) + %ADDEDSCRIPT% +End Function +"; + + public string ScriptName + { + get + { + return "VBA"; + } + } + + public bool SupportsScriptlet + { + get + { + return false; + } + } + + public string GenerateScript(byte[] serialized_object, string entry_class_name, string additional_script, RuntimeVersion version, bool enable_debug) + { + if (version != RuntimeVersion.None) + { + throw new ArgumentException("VBA output does not support version detection"); + } + + string hex_encoded = BitConverter.ToString(serialized_object).Replace("-", ""); + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < hex_encoded.Length; i++) + { + if (i == 0) + { + builder.Append(" serialized_obj = \""); + } + else if (i % 100 == 0) + { + builder.Append("\""); + builder.AppendLine(); + builder.Append(" serialized_obj = serialized_obj & \""); + } + builder.Append(hex_encoded[i]); + } + builder.Append("\""); + + return vba_template.Replace("%SERIALIZED%", builder.ToString()).Replace("%CLASS%", entry_class_name).Replace("%ADDEDSCRIPT%", additional_script); + } + } +} diff --git a/DotNetToJScript/app.config b/DotNetToJScript/app.config new file mode 100644 index 0000000..2fa6e95 --- /dev/null +++ b/DotNetToJScript/app.config @@ -0,0 +1,3 @@ + + + diff --git a/README b/README index 0555433..680902b 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ This file is part of DotNetToJScript - A tool to generate a -JScript which bootstraps an arbitrary v2.NET Assembly and class. +JScript which bootstraps an arbitrary .NET Assembly and class. Copyright (C) James Forshaw 2017 DotNetToJScript is free software: you can redistribute it and/or modify @@ -18,11 +18,10 @@ along with DotNetToJScript. If not, see . Usage Notes: This only works from full trust JScript(obviously), so should work in -scriptlets etc. It also only works if v2/v3/v3.5 is installed. If -only v4+ is installed then the system will prompt for installation of -.NET v2. Don't think there's any way around this, seems that while the -COM objects are registered it doesn't fall-forward to using .NET 4 even -though it should work in theory. +scriptlets etc. By default it will only works if v2/v3/v3.5 is installed. +However if you specify the '-ver auto' switch when building the output it +will also work on v4+ only, however that will introduce a dependency on +WScript.Shell which you might not want. To use this you'll need to create an assembly which targets .NET 2 (though in most cases you can also use 3.5 as you don't tend to see .NET 2 installed @@ -59,7 +58,8 @@ o.DoSomething("SomeArg"); The default mode is to output a JScript file which can be executed in Windows Scripting Host. However if you want a scriptlet pass either -m (for a scriptlet which can be used from a scriptlet moniker) or -u (for a scriptlet which can be -used from regsvr32). +used from regsvr32). You can also specify the -v switch to output a VBA file which +should work in Office Macros. Finally by default the tool will output to stdout, you can output direct to a file using the -o switch. \ No newline at end of file