From bb286b3cfc1c74341c246ae89ce096add01791c1 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Tue, 31 Oct 2017 17:07:43 +0100 Subject: [PATCH] PowerShell cmdlets for ILSpy --- Frontends.sln | 6 +++ .../ICSharpCode.Decompiler.Console.csproj | 2 +- ICSharpCode.Decompiler.PowerShell/Demo.ps1 | 15 ++++++ ICSharpCode.Decompiler.PowerShell/ErrorIds.cs | 12 +++++ .../GetDecompiledProjectCmdlet.cs | 46 ++++++++++++++++++ .../GetDecompiledSourceCmdlet.cs | 40 ++++++++++++++++ .../GetDecompiledTypesCmdlet.cs | 41 ++++++++++++++++ .../GetDecompilerCmdlet.cs | 32 +++++++++++++ .../ICSharpCode.Decompiler.PowerShell.csproj | 14 ++++++ ICSharpCode.Decompiler.PowerShell/README.md | 13 +++++ .../TypesParser.cs | 47 +++++++++++++++++++ 11 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler.PowerShell/Demo.ps1 create mode 100644 ICSharpCode.Decompiler.PowerShell/ErrorIds.cs create mode 100644 ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs create mode 100644 ICSharpCode.Decompiler.PowerShell/GetDecompiledSourceCmdlet.cs create mode 100644 ICSharpCode.Decompiler.PowerShell/GetDecompiledTypesCmdlet.cs create mode 100644 ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs create mode 100644 ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj create mode 100644 ICSharpCode.Decompiler.PowerShell/README.md create mode 100644 ICSharpCode.Decompiler.PowerShell/TypesParser.cs diff --git a/Frontends.sln b/Frontends.sln index 9a87d6fe3..36c395b93 100644 --- a/Frontends.sln +++ b/Frontends.sln @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler.Console", "ICSharpCode.Decompiler.Console\ICSharpCode.Decompiler.Console.csproj", "{8FDA011E-FAF8-4C1F-A695-21E2C6B5375F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler.PowerShell", "ICSharpCode.Decompiler.PowerShell\ICSharpCode.Decompiler.PowerShell.csproj", "{FF7D6041-3C52-47D1-A32A-0BFE8EE4EEEB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {8FDA011E-FAF8-4C1F-A695-21E2C6B5375F}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FDA011E-FAF8-4C1F-A695-21E2C6B5375F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FDA011E-FAF8-4C1F-A695-21E2C6B5375F}.Release|Any CPU.Build.0 = Release|Any CPU + {FF7D6041-3C52-47D1-A32A-0BFE8EE4EEEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF7D6041-3C52-47D1-A32A-0BFE8EE4EEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF7D6041-3C52-47D1-A32A-0BFE8EE4EEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF7D6041-3C52-47D1-A32A-0BFE8EE4EEEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj index b2fe5464f..90ee49432 100644 --- a/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj +++ b/ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj @@ -8,7 +8,7 @@ - + diff --git a/ICSharpCode.Decompiler.PowerShell/Demo.ps1 b/ICSharpCode.Decompiler.PowerShell/Demo.ps1 new file mode 100644 index 000000000..cd7482c52 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/Demo.ps1 @@ -0,0 +1,15 @@ +Import-Module .\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.PSCore.dll +$decompiler = Get-Decompiler .\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.PSCore.dll + +$classes = Get-DecompiledTypes $decompiler -Types class +$classes.Count + +foreach ($c in $classes) +{ + Write-Output $c.FullName +} + + +Get-DecompiledSource $decompiler -TypeName ICSharpCode.Decompiler.PSCore.GetDecompilerCmdlet + +Get-DecompiledProject $decompiler -OutputPath .\decomptest \ No newline at end of file diff --git a/ICSharpCode.Decompiler.PowerShell/ErrorIds.cs b/ICSharpCode.Decompiler.PowerShell/ErrorIds.cs new file mode 100644 index 000000000..9554e449e --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/ErrorIds.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ICSharpCode.Decompiler.PowerShell +{ + public static class ErrorIds + { + public static readonly string AssemblyLoadFailed = "1"; + public static readonly string DecompilationFailed = "2"; + } +} diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs new file mode 100644 index 000000000..bbce59a7e --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Text; +using ICSharpCode.Decompiler.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.PowerShell +{ + [Cmdlet(VerbsCommon.Get, "DecompiledProject")] + [OutputType(typeof(string))] + public class GetDecompiledProjectCmdlet : PSCmdlet + { + [Parameter(Position = 0, Mandatory = true)] + public CSharpDecompiler Decompiler { get; set; } + + [Parameter(Position = 1, Mandatory = true)] + [Alias("PSPath", "OutputPath")] + [ValidateNotNullOrEmpty] + public string LiteralPath { get; set; } + + protected override void ProcessRecord() + { + string path = GetUnresolvedProviderPathFromPSPath(LiteralPath); + if (!Directory.Exists(path)) + { + WriteObject("Destination directory must exist prior to decompilation"); + return; + } + + try + { + string assemblyFileName = Decompiler.TypeSystem.Compilation.MainAssembly.UnresolvedAssembly.Location; // just to keep the API "the same" across all cmdlets + ModuleDefinition module = UniversalAssemblyResolver.LoadMainModule(assemblyFileName); + WholeProjectDecompiler decompiler = new WholeProjectDecompiler(); + decompiler.DecompileProject(module, path); + + WriteObject("Decompilation finished"); + } catch (Exception e) { + WriteVerbose(e.ToString()); + WriteError(new ErrorRecord(e, ErrorIds.DecompilationFailed, ErrorCategory.OperationStopped, null)); + } + } + } +} diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledSourceCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledSourceCmdlet.cs new file mode 100644 index 000000000..845f69028 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledSourceCmdlet.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Text; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.PowerShell +{ + [Cmdlet(VerbsCommon.Get, "DecompiledSource")] + [OutputType(typeof(string))] + public class GetDecompiledSourceCmdlet : PSCmdlet + { + [Parameter(Position = 0, Mandatory = true)] + public CSharpDecompiler Decompiler { get; set; } + + [Parameter] + public string TypeName { get; set; } = string.Empty; + + protected override void ProcessRecord() + { + try + { + StringWriter output = new StringWriter(); + if (TypeName == null) { + output.Write(Decompiler.DecompileWholeModuleAsString()); + } else { + var name = new FullTypeName(TypeName); + output.Write(Decompiler.DecompileTypeAsString(name)); + } + + WriteObject(output.ToString()); + } catch (Exception e) { + WriteVerbose(e.ToString()); + WriteError(new ErrorRecord(e, ErrorIds.DecompilationFailed, ErrorCategory.OperationStopped, null)); + } + } + } +} diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledTypesCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledTypesCmdlet.cs new file mode 100644 index 000000000..7893527b6 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledTypesCmdlet.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Text; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.PowerShell +{ + [Cmdlet(VerbsCommon.Get, "DecompiledTypes")] + [OutputType(typeof(ITypeDefinition[]))] + public class GetDecompiledTypesCmdlet : PSCmdlet + { + [Parameter(Position = 0, Mandatory = true)] + public CSharpDecompiler Decompiler { get; set; } + + [Parameter(Mandatory = true)] + public string[] Types { get; set; } + + protected override void ProcessRecord() + { + HashSet kinds = TypesParser.ParseSelection(Types); + + try { + List output = new List(); + foreach (var type in Decompiler.TypeSystem.Compilation.MainAssembly.GetAllTypeDefinitions()) { + if (!kinds.Contains(type.Kind)) + continue; + output.Add(type); + } + + WriteObject(output.ToArray()); + } catch (Exception e) { + WriteVerbose(e.ToString()); + WriteError(new ErrorRecord(e, ErrorIds.DecompilationFailed, ErrorCategory.OperationStopped, null)); + } + } + } +} diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs new file mode 100644 index 000000000..0faab54f4 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompilerCmdlet.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Text; +using ICSharpCode.Decompiler.CSharp; + +namespace ICSharpCode.Decompiler.PowerShell +{ + [Cmdlet(VerbsCommon.Get, "Decompiler")] + [OutputType(typeof(CSharpDecompiler))] + public class GetDecompilerCmdlet : PSCmdlet + { + [Parameter(Position = 0, Mandatory = true, HelpMessage = "Path to the assembly you want to decompile")] + [Alias("PSPath")] + [ValidateNotNullOrEmpty] + public string LiteralPath { get; set; } + + protected override void ProcessRecord() + { + string path = GetUnresolvedProviderPathFromPSPath(LiteralPath); + + try { + var decompiler = new CSharpDecompiler(path, new DecompilerSettings()); + WriteObject(decompiler); + + } catch (Exception e) { + WriteVerbose(e.ToString()); + WriteError(new ErrorRecord(e, ErrorIds.AssemblyLoadFailed, ErrorCategory.OperationStopped, null)); + } + } + } +} diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj new file mode 100644 index 000000000..9865e60f5 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + true + ICSharpCode.Decompiler.PowerShell + + + + + + + + diff --git a/ICSharpCode.Decompiler.PowerShell/README.md b/ICSharpCode.Decompiler.PowerShell/README.md new file mode 100644 index 000000000..114739a72 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/README.md @@ -0,0 +1,13 @@ +# ILSpy PowerShell Module + +Built using https://github.com/PowerShell/PowerShell/blob/master/docs/cmdlet-example/command-line-simple-example.md as guideline + +Sample usage: Demo.ps1 + +Tested with: PowerShell 5.1 on Windows, PowerShell Core on Windows and Mac (Beta9) + + + +## Missing + +.psd1 for deploying to https://www.powershellgallery.com/ \ No newline at end of file diff --git a/ICSharpCode.Decompiler.PowerShell/TypesParser.cs b/ICSharpCode.Decompiler.PowerShell/TypesParser.cs new file mode 100644 index 000000000..ed157d656 --- /dev/null +++ b/ICSharpCode.Decompiler.PowerShell/TypesParser.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.PowerShell +{ + public static class TypesParser + { + public static HashSet ParseSelection(string[] values) + { + var possibleValues = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["class"] = TypeKind.Class, ["struct"] = TypeKind.Struct, ["interface"] = TypeKind.Interface, ["enum"] = TypeKind.Enum, ["delegate"] = TypeKind.Delegate }; + HashSet kinds = new HashSet(); + if (values.Length == 1 && !possibleValues.Keys.Any(v => values[0].StartsWith(v, StringComparison.OrdinalIgnoreCase))) { + foreach (char ch in values[0]) { + switch (ch) { + case 'c': + kinds.Add(TypeKind.Class); + break; + case 'i': + kinds.Add(TypeKind.Interface); + break; + case 's': + kinds.Add(TypeKind.Struct); + break; + case 'd': + kinds.Add(TypeKind.Delegate); + break; + case 'e': + kinds.Add(TypeKind.Enum); + break; + } + } + } else { + foreach (var value in values) { + string v = value; + while (v.Length > 0 && !possibleValues.ContainsKey(v)) + v = v.Remove(v.Length - 1); + if (possibleValues.TryGetValue(v, out var kind)) + kinds.Add(kind); + } + } + return kinds; + } + } +}