diff --git a/ICSharpCode.ILSpyCmd/DotNetToolUpdateChecker.cs b/ICSharpCode.ILSpyCmd/DotNetToolUpdateChecker.cs new file mode 100644 index 0000000000..449cceb827 --- /dev/null +++ b/ICSharpCode.ILSpyCmd/DotNetToolUpdateChecker.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using NuGet.Common; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; + +namespace ICSharpCode.ILSpyCmd +{ + // Idea from https://github.com/ErikEJ/EFCorePowerTools/blob/master/src/GUI/efcpt/Services/PackageService.cs + internal static class DotNetToolUpdateChecker + { + static NuGetVersion CurrentPackageVersion() + { + return new NuGetVersion(Assembly.GetEntryAssembly()!.GetCustomAttribute()! + .InformationalVersion); + } + + public static async Task CheckForPackageUpdateAsync(string packageId) + { + try + { + using var cache = new SourceCacheContext(); + var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + var resource = await repository.GetResourceAsync().ConfigureAwait(false); + + var versions = await resource.GetAllVersionsAsync( + packageId, + cache, + new NullLogger(), + CancellationToken.None).ConfigureAwait(false); + + var latestVersion = versions.Where(v => v.Release == "").MaxBy(v => v); + if (latestVersion > CurrentPackageVersion()) + { + return latestVersion; + } + } +#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception. + catch (Exception) + { + } +#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception. + + return null; + } + } +} diff --git a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj index 27a0ad480e..c97d0e6505 100644 --- a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj +++ b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj @@ -49,11 +49,9 @@ - - - - - + + + diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs index 1e46531cfc..8413fdfc4d 100644 --- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs +++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs @@ -8,6 +8,7 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Threading; +using System.Threading.Tasks; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; @@ -21,6 +22,10 @@ using McMaster.Extensions.CommandLineUtils; +using Microsoft.Extensions.Hosting; + +using NuGet.Versioning; + namespace ICSharpCode.ILSpyCmd { [Command(Name = "ilspycmd", Description = "dotnet tool for decompiling .NET assemblies and generating portable PDBs", @@ -48,7 +53,9 @@ into nicely nested directories. MemberName = nameof(DecompilerVersion))] class ILSpyCmdProgram { - public static int Main(string[] args) => CommandLineApplication.Execute(args); + // https://natemcmaster.github.io/CommandLineUtils/docs/advanced/generic-host.html + // https://github.com/natemcmaster/CommandLineUtils/blob/main/docs/samples/dependency-injection/generic-host/Program.cs + public static Task Main(string[] args) => new HostBuilder().RunCommandLineApplicationAsync(args); [FilesExist] [Required] @@ -92,7 +99,7 @@ class ILSpyCmdProgram [DirectoryExists] [Option("-r|--referencepath ", "Path to a directory containing dependencies of the assembly that is being decompiled.", CommandOptionType.MultipleValue)] - public string[] ReferencePaths { get; } = new string[0]; + public string[] ReferencePaths { get; } [Option("--no-dead-code", "Remove dead code.", CommandOptionType.NoValue)] public bool RemoveDeadCode { get; } @@ -100,14 +107,29 @@ class ILSpyCmdProgram [Option("--no-dead-stores", "Remove dead stores.", CommandOptionType.NoValue)] public bool RemoveDeadStores { get; } - [Option("-d|--dump-package", "Dump package assembiles into a folder. This requires the output directory option.", CommandOptionType.NoValue)] + [Option("-d|--dump-package", "Dump package assemblies into a folder. This requires the output directory option.", CommandOptionType.NoValue)] public bool DumpPackageFlag { get; } [Option("--nested-directories", "Use nested directories for namespaces.", CommandOptionType.NoValue)] public bool NestedDirectories { get; } - private int OnExecute(CommandLineApplication app) + [Option("--disable-updatecheck", "If using ilspycmd in a tight loop or fully automated scenario, you might want to disable the automatic update check.", CommandOptionType.NoValue)] + public bool DisableUpdateCheck { get; } + + private readonly IHostEnvironment _env; + public ILSpyCmdProgram(IHostEnvironment env) { + _env = env; + } + + private async Task OnExecuteAsync(CommandLineApplication app) + { + Task updateCheckTask = null; + if (!DisableUpdateCheck) + { + updateCheckTask = DotNetToolUpdateChecker.CheckForPackageUpdateAsync("ilspycmd"); + } + TextWriter output = System.Console.Out; string outputDirectory = ResolveOutputDirectory(OutputDirectory); @@ -156,6 +178,16 @@ private int OnExecute(CommandLineApplication app) finally { output.Close(); + + if (null != updateCheckTask) + { + var latestVersion = await updateCheckTask; + if (null != latestVersion) + { + Console.WriteLine("You are not using the latest version of the tool, please update."); + Console.WriteLine($"Latest version is '{latestVersion}'"); + } + } } int PerformPerFileAction(string fileName) @@ -240,7 +272,7 @@ CSharpDecompiler GetDecompiler(string assemblyFileName) { var module = new PEFile(assemblyFileName); var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Metadata.DetectTargetFrameworkId()); - foreach (var path in ReferencePaths) + foreach (var path in (ReferencePaths ?? Array.Empty())) { resolver.AddSearchDirectory(path); } @@ -278,7 +310,7 @@ ProjectId DecompileAsProject(string assemblyFileName, string projectFileName) { var module = new PEFile(assemblyFileName); var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Metadata.DetectTargetFrameworkId()); - foreach (var path in ReferencePaths) + foreach (var path in (ReferencePaths ?? Array.Empty())) { resolver.AddSearchDirectory(path); }