From c86c3887e6a9b7033449ea32619204c7c9188747 Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Fri, 31 May 2024 18:07:15 +0200 Subject: [PATCH 01/10] (maint) Add temporary storage of benchmark --- src/chocolatey.benchmark/App.config | 78 +++ src/chocolatey.benchmark/BenchmarkConfig.cs | 51 ++ .../ParentProcessBenchmarks.cs | 63 +++ .../ProcessTreeBenchmark.cs | 71 +++ src/chocolatey.benchmark/Program.cs | 22 + .../Properties/AssemblyInfo.cs | 36 ++ .../chocolatey.benchmark.csproj | 261 ++++++++++ .../helpers/ManagedProcessHelper.cs | 223 +++++++++ .../helpers/PinvokeProcessHelper.cs | 460 ++++++++++++++++++ .../helpers/ProcessTree.cs | 125 +++++ src/chocolatey.benchmark/packages.config | 57 +++ src/chocolatey.sln | 41 ++ 12 files changed, 1488 insertions(+) create mode 100644 src/chocolatey.benchmark/App.config create mode 100644 src/chocolatey.benchmark/BenchmarkConfig.cs create mode 100644 src/chocolatey.benchmark/ParentProcessBenchmarks.cs create mode 100644 src/chocolatey.benchmark/ProcessTreeBenchmark.cs create mode 100644 src/chocolatey.benchmark/Program.cs create mode 100644 src/chocolatey.benchmark/Properties/AssemblyInfo.cs create mode 100644 src/chocolatey.benchmark/chocolatey.benchmark.csproj create mode 100644 src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs create mode 100644 src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs create mode 100644 src/chocolatey.benchmark/helpers/ProcessTree.cs create mode 100644 src/chocolatey.benchmark/packages.config diff --git a/src/chocolatey.benchmark/App.config b/src/chocolatey.benchmark/App.config new file mode 100644 index 0000000000..f8e1309c75 --- /dev/null +++ b/src/chocolatey.benchmark/App.config @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/chocolatey.benchmark/BenchmarkConfig.cs b/src/chocolatey.benchmark/BenchmarkConfig.cs new file mode 100644 index 0000000000..7e9f243a7c --- /dev/null +++ b/src/chocolatey.benchmark/BenchmarkConfig.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Analysers; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; + +namespace chocolatey.benchmark +{ + internal static class BenchmarkConfig + { + public static IConfig Get() + { + var config = ManualConfig.CreateEmpty(); + + foreach (var platform in new[] { Platform.X64/*, Platform.X86*/ }) + { + config = config.AddJob(Job.Default.WithRuntime(ClrRuntime.Net48).WithPlatform(platform).WithJit(Jit.LegacyJit)); + } + + return config + .AddDiagnoser(MemoryDiagnoser.Default) + .AddColumnProvider(DefaultColumnProviders.Instance) + .AddLogger(ConsoleLogger.Default) + .AddExporter(CsvExporter.Default) + .AddExporter(HtmlExporter.Default) + .AddExporter(MarkdownExporter.Default) + .AddExporter(AsciiDocExporter.Default) + .AddAnalyser(GetAnalysers().ToArray()); + } + + private static IEnumerable GetAnalysers() + { + yield return EnvironmentAnalyser.Default; + yield return OutliersAnalyser.Default; + yield return MinIterationTimeAnalyser.Default; + yield return MultimodalDistributionAnalyzer.Default; + yield return RuntimeErrorAnalyser.Default; + yield return ZeroMeasurementAnalyser.Default; + yield return BaselineCustomAnalyzer.Default; + } + } +} diff --git a/src/chocolatey.benchmark/ParentProcessBenchmarks.cs b/src/chocolatey.benchmark/ParentProcessBenchmarks.cs new file mode 100644 index 0000000000..1585615a5f --- /dev/null +++ b/src/chocolatey.benchmark/ParentProcessBenchmarks.cs @@ -0,0 +1,63 @@ +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using chocolatey.benchmark.helpers; + +namespace chocolatey.benchmark +{ + public class ParentProcessBenchmarks + { + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessDocumentedPinvoke() + { + return PinvokeProcessHelper.GetDocumentedParent(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessFilteredDocumentedPinvoke() + { + return PinvokeProcessHelper.GetDocumentedParentFiltered(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessFilteredManaged() + { + return ManagedProcessHelper.GetParent(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessFilteredUndocumentedPinvoke() + { + return PinvokeProcessHelper.GetUndocumentedParentFiltered(); + } + + [Benchmark(Baseline = true), MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessManaged() + { + return ManagedProcessHelper.GetParent(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public ProcessTree GetParentProcessTreeDocumentedPinvoke() + { + return PinvokeProcessHelper.GetDocumentedProcessTree(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public ProcessTree GetParentProcessTreeManaged() + { + return ManagedProcessHelper.GetProcessTree(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public ProcessTree GetParentProcessTreeUndocumentedPinvoke() + { + return PinvokeProcessHelper.GetUndocumentedProcessTree(); + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public string GetParentProcessUndocumentedPinvoke() + { + return PinvokeProcessHelper.GetUndocumentedParent(); + } + } +} \ No newline at end of file diff --git a/src/chocolatey.benchmark/ProcessTreeBenchmark.cs b/src/chocolatey.benchmark/ProcessTreeBenchmark.cs new file mode 100644 index 0000000000..251f8fdc24 --- /dev/null +++ b/src/chocolatey.benchmark/ProcessTreeBenchmark.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using chocolatey.benchmark.helpers; + +namespace chocolatey.benchmark +{ + public class ProcessTreeBenchmark + { + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + [ArgumentsSource(nameof(GetProcessTree))] + public string GetFirstFilteredProcessName(ProcessTree tree) + { + return tree.FirstFilteredProcessName; + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + [ArgumentsSource(nameof(GetProcessTree))] + public string GetFirstProcessName(ProcessTree tree) + { + return tree.FirstProcessName; + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + [ArgumentsSource(nameof(GetProcessTree))] + public string GetLastFilteredProcessName(ProcessTree tree) + { + return tree.LastFilteredProcessName; + } + + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + [ArgumentsSource(nameof(GetProcessTree))] + public string GetLastProcessName(ProcessTree tree) + { + return tree.LastProcessName; + } + + [Benchmark(Baseline = true), MethodImpl(MethodImplOptions.NoInlining)] + [ArgumentsSource(nameof(GetProcessTree))] + public LinkedList GetProcessesList(ProcessTree tree) + { + return tree.Processes; + } + + public IEnumerable GetProcessTree() + { + var currentProcess = Process.GetCurrentProcess(); + + var tree = new ProcessTree(currentProcess.ProcessName); + tree.Processes.AddLast("devenv"); + tree.Processes.AddLast("cmd"); + tree.Processes.AddLast("Tabby"); + tree.Processes.AddLast("explorer"); + yield return tree; + + yield return new ProcessTree(currentProcess.ProcessName); + + tree = new ProcessTree(currentProcess.ProcessName); + tree.Processes.AddLast(currentProcess.ProcessName); + tree.Processes.AddLast("WindowsTerminal"); + yield return tree; + + yield return PinvokeProcessHelper.GetUndocumentedProcessTree(currentProcess); + } + } +} \ No newline at end of file diff --git a/src/chocolatey.benchmark/Program.cs b/src/chocolatey.benchmark/Program.cs new file mode 100644 index 0000000000..f6df03d30f --- /dev/null +++ b/src/chocolatey.benchmark/Program.cs @@ -0,0 +1,22 @@ +using System.Linq; +using BenchmarkDotNet.Running; + +namespace chocolatey.benchmark +{ + internal class Program + { + private static void Main(string[] args) + { + var switcher = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly); + + if (args.Length > 0) + { + switcher.Run(args, BenchmarkConfig.Get()).ToArray(); + } + else + { + switcher.RunAll(BenchmarkConfig.Get()).ToArray(); + } + } + } +} diff --git a/src/chocolatey.benchmark/Properties/AssemblyInfo.cs b/src/chocolatey.benchmark/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ceb1f4def2 --- /dev/null +++ b/src/chocolatey.benchmark/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("chocolatey.benchmark")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("chocolatey.benchmark")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2b98bb42-a7ae-4ef6-b0b8-aa7bfc1e1180")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/chocolatey.benchmark/chocolatey.benchmark.csproj b/src/chocolatey.benchmark/chocolatey.benchmark.csproj new file mode 100644 index 0000000000..63f85e31da --- /dev/null +++ b/src/chocolatey.benchmark/chocolatey.benchmark.csproj @@ -0,0 +1,261 @@ + + + + + + Debug + AnyCPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180} + Exe + chocolatey.benchmark + chocolatey.benchmark + v4.8 + 512 + true + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\BenchmarkDotNet.0.13.12\lib\netstandard2.0\BenchmarkDotNet.dll + + + ..\packages\BenchmarkDotNet.Annotations.0.13.12\lib\netstandard2.0\BenchmarkDotNet.Annotations.dll + + + ..\packages\BenchmarkDotNet.Diagnostics.Windows.0.13.12\lib\netstandard2.0\BenchmarkDotNet.Diagnostics.Windows.dll + + + ..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.1.10\lib\netstandard2.0\Dia2Lib.dll + True + + + ..\packages\Gee.External.Capstone.2.3.0\lib\netstandard2.0\Gee.External.Capstone.dll + + + ..\packages\Iced.1.21.0\lib\net45\Iced.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.4.9.2\lib\netstandard2.0\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.4.9.2\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.1.10\lib\netstandard2.0\Microsoft.Diagnostics.FastSerialization.dll + + + ..\packages\Microsoft.Diagnostics.NETCore.Client.0.2.510501\lib\netstandard2.0\Microsoft.Diagnostics.NETCore.Client.dll + + + ..\packages\Microsoft.Diagnostics.Runtime.3.1.512801\lib\netstandard2.0\Microsoft.Diagnostics.Runtime.dll + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.1.10\lib\netstandard2.0\Microsoft.Diagnostics.Tracing.TraceEvent.dll + + + ..\packages\Microsoft.DotNet.PlatformAbstractions.3.1.6\lib\net45\Microsoft.DotNet.PlatformAbstractions.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.1\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.8.0.0\lib\net462\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.1\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.8.0.2\lib\net462\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.8.0.0\lib\net462\Microsoft.Extensions.Primitives.dll + + + ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll + + + ..\packages\Perfolizer.0.2.1\lib\netstandard2.0\Perfolizer.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.CodeDom.8.0.0\lib\net462\System.CodeDom.dll + + + ..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll + + + + + + ..\packages\System.Diagnostics.DiagnosticSource.8.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Diagnostics.Process.4.3.0\lib\net461\System.Diagnostics.Process.dll + True + True + + + ..\packages\System.Diagnostics.TraceSource.4.3.0\lib\net46\System.Diagnostics.TraceSource.dll + True + True + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + True + + + ..\packages\System.IO.UnmanagedMemoryStream.4.3.0\lib\net46\System.IO.UnmanagedMemoryStream.dll + True + True + + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + ..\packages\System.Net.NameResolution.4.3.0\lib\net46\System.Net.NameResolution.dll + True + True + + + ..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Numerics.dll + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll + + + ..\packages\System.Reflection.TypeExtensions.4.7.0\lib\net461\System.Reflection.TypeExtensions.dll + + + ..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll + True + True + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + True + True + + + ..\packages\System.Security.AccessControl.6.0.1\lib\net461\System.Security.AccessControl.dll + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll + + + ..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + True + True + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + + + ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.3.1.10\lib\netstandard2.0\TraceReloggerLib.dll + True + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs b/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs new file mode 100644 index 0000000000..8fbc644631 --- /dev/null +++ b/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs @@ -0,0 +1,223 @@ +using System; +using System.Diagnostics; +using System.Linq; + +namespace chocolatey.benchmark.helpers +{ + internal static class ManagedProcessHelper + { + private static readonly string[] _filteredParents = new[] + { + "explorer", + "powershell", + "pwsh", + "cmd", + "bash" + }; + + public static ProcessTree GetProcessTree(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + var tree = new ProcessTree(process.ProcessName); + + Process nextProcess = null; + + while (true) + { + var processId = nextProcess?.Id ?? process.Id; + var processName = FindIndexedProcessName(processId); + + if (string.IsNullOrEmpty(processName)) + { + break; + } + + var foundProcess = FindPidFromIndexedProcessName(processName); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + tree.Processes.AddLast(nextProcess.ProcessName); + } + + return tree; + } + + public static string GetParent(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + + while (true) + { + var processId = nextProcess?.Id ?? process.Id; + + var processName = FindIndexedProcessName(processId); + + if (string.IsNullOrEmpty(processName)) + { + break; + } + + var foundProcess = FindPidFromIndexedProcessName(processName); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + } + + return nextProcess?.ProcessName; + + //var processName = FindIndexedProcessName(process.Id); + + //if (string.IsNullOrEmpty(processName)) + //{ + // return null; + //} + + //var parentProcess = FindPidFromIndexedProcessName(processName); + + //if (parentProcess == null) + //{ + // return null; + //} + + //var topLevelProcess = GetParent(parentProcess); + + //if (topLevelProcess == null) + //{ + // return parentProcess.ProcessName; + //} + + //return topLevelProcess; + } + + public static string GetParentFiltered(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + Process selectedProcess = null; + + while (true) + { + var processId = nextProcess?.Id ?? process.Id; + + var processName = FindIndexedProcessName(processId); + + if (string.IsNullOrEmpty(processName)) + { + break; + } + + var foundProcess = FindPidFromIndexedProcessName(processName); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + + if (!IsIgnoredParent(nextProcess.ProcessName)) + { + selectedProcess = nextProcess; + } + } + + return nextProcess?.ProcessName; + + //var processName = FindIndexedProcessName(process.Id); + + //if (string.IsNullOrEmpty(processName)) + //{ + // return null; + //} + + //var parentProcess = FindPidFromIndexedProcessName(processName); + + //if (parentProcess == null) + //{ + // return null; + //} + + //var topLevelProcess = GetParent(parentProcess); + + //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) + //{ + // if (IsIgnoredParent(parentProcess.ProcessName)) + // { + // return null; + // } + // else + // { + // return parentProcess.ProcessName; + // } + //} + + //return topLevelProcess; + } + + private static bool IsIgnoredParent(string processName) + { + return _filteredParents.Contains(processName, StringComparer.OrdinalIgnoreCase); + } + + private static Process FindPidFromIndexedProcessName(string indexedProcessName) + { + try + { + var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName); + return Process.GetProcessById((int)parentId.NextValue()); + } + catch + { + return null; + } + } + + private static string FindIndexedProcessName(int pid) + { + var processName = Process.GetProcessById(pid).ProcessName; + var processByName = Process.GetProcessesByName(processName); + string processIndexedName = null; + + for (var i = 0; i < processByName.Length; i++) + { + try + { + processIndexedName = i == 0 ? processName : processName + "#" + i; + var processId = new PerformanceCounter("Process", "ID Process", processIndexedName); + + if ((int)processId.NextValue() == pid) + { + return processIndexedName; + } + } + catch + { + // Empty on purpose + } + } + + return processIndexedName; + } + } +} diff --git a/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs new file mode 100644 index 0000000000..0da3591364 --- /dev/null +++ b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs @@ -0,0 +1,460 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +using System.Security.Permissions; +using System.Security; + +namespace chocolatey.benchmark.helpers +{ + internal class PinvokeProcessHelper + { + private static readonly string[] _filteredParents = new[] + { + "explorer", + "powershell", + "pwsh", + "cmd" + }; + + public static ProcessTree GetDocumentedProcessTree(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + var tree = new ProcessTree(process.ProcessName); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return tree; + } + + Process nextProcess = null; + + while (true) + { + var foundProcess = ParentDocumentedHelper.ParentProcess(nextProcess ?? process); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + tree.Processes.AddLast(nextProcess.ProcessName); + } + + return tree; + } + + public static string GetDocumentedParent(Process process = null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return null; + } + + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + + while (true) + { + var foundProcess = ParentDocumentedHelper.ParentProcess(nextProcess ?? process); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + } + + return nextProcess?.ProcessName; + + //var parentProcess = ParentDocumentedHelper.ParentProcess(process); + + //if (parentProcess == null) + //{ + // return null; + //} + + //var topLevelProcess = GetDocumentedParent(parentProcess); + + //if (topLevelProcess == null) + //{ + // return parentProcess.ProcessName; + //} + + //return topLevelProcess; + } + + public static string GetDocumentedParentFiltered(Process process = null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return null; + } + + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + Process selectedProcess = null; + + while (true) + { + var foundProcess = ParentDocumentedHelper.ParentProcess(nextProcess ?? process); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + + if (!IsIgnoredParent(nextProcess.ProcessName)) + { + selectedProcess = nextProcess; + } + } + + return nextProcess?.ProcessName; + + var parentProcess = ParentDocumentedHelper.ParentProcess(process); + + if (parentProcess == null) + { + return null; + } + + //var topLevelProcess = GetDocumentedParentFiltered(parentProcess); + + //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) + //{ + // if (IsIgnoredParent(parentProcess.ProcessName)) + // { + // return null; + // } + // else + // { + // return parentProcess.ProcessName; + // } + //} + + //return topLevelProcess; + } + + public static ProcessTree GetUndocumentedProcessTree(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + var tree = new ProcessTree(process.ProcessName); + + Process nextProcess = null; + + while (true) + { + var parentProcess = ParentProcessUtilities.GetParentProcess(nextProcess ?? process); + + if (parentProcess == null) + { + break; + } + + nextProcess = parentProcess; + tree.Processes.AddLast(nextProcess.ProcessName); + } + + return tree; + } + + public static string GetUndocumentedParent(Process process = null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return null; + } + + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + + while (true) + { + var foundProcess = ParentProcessUtilities.GetParentProcess(nextProcess ?? process); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + } + + return nextProcess?.ProcessName; + + //var parentProcess = ParentProcessUtilities.GetParentProcess(process); + + //if (parentProcess == null) + //{ + // return null; + //} + + //var topLevelProcess = GetUndocumentedParent(parentProcess); + + //if (topLevelProcess == null) + //{ + // return parentProcess.ProcessName; + //} + + //return topLevelProcess; + } + + public static string GetUndocumentedParentFiltered(Process process = null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return null; + } + + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + Process nextProcess = null; + Process selectedProcess = null; + + while (true) + { + var foundProcess = ParentProcessUtilities.GetParentProcess(nextProcess ?? process); + + if (foundProcess == null) + { + break; + } + + nextProcess = foundProcess; + + if (!IsIgnoredParent(nextProcess.ProcessName)) + { + selectedProcess = nextProcess; + } + } + + return selectedProcess?.ProcessName; + + //var parentProcess = ParentProcessUtilities.GetParentProcess(process); + + //if (parentProcess == null) + //{ + // return null; + //} + + //var topLevelProcess = GetUndocumentedParentFiltered(parentProcess); + + //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) + //{ + // if (IsIgnoredParent(parentProcess.ProcessName)) + // { + // return null; + // } + // else + // { + // return parentProcess.ProcessName; + // } + //} + + //return topLevelProcess; + } + + private static bool IsIgnoredParent(string processName) + { + return _filteredParents.Contains(processName, StringComparer.OrdinalIgnoreCase); + } + + private class ParentDocumentedHelper + { + public static Process ParentProcess(Process process) + { + try + { + var processId = ParentProcessId(process.Id); + + if (processId == -1) + { + return null; + } + else + { + return Process.GetProcessById(processId); + } + } + catch + { + return null; + } + } + + private static int ParentProcessId(int id) + { + var pe32 = new PROCESSENTRY32 + { + dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32)) + }; + + using (var hSnapshot = CreateToolhelp32Snapshot(SnapshotFlags.Process, (uint)id)) + { + if (hSnapshot.IsInvalid) + { + throw new Win32Exception(); + } + + if (!Process32First(hSnapshot, ref pe32)) + { + var errno = Marshal.GetLastWin32Error(); + + if (errno == ERROR_NO_MORE_FILES) + { + return -1; + } + + throw new Win32Exception(errno); + } + + do + { + if (pe32.th32ProcessID == (uint)id) + { + return (int)pe32.th32ParentProcessID; + } + } while (Process32Next(hSnapshot, ref pe32)); + } + + return -1; + } + + private const int ERROR_NO_MORE_FILES = 0x12; + [DllImport("kernel32.dll", SetLastError = true)] + private static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); + + [Flags] + private enum SnapshotFlags : uint + { + HeapList = 0x00000001, + Process = 0x00000002, + Thread = 0x00000004, + Module = 0x00000008, + Module32 = 0x00000010, + All = (HeapList | Process | Thread | Module), + Inherit = 0x80000000, + NoHeaps = 0x40000000 + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESSENTRY32 + { +#pragma warning disable IDE1006 // Naming Styles + public uint dwSize; + public uint cntUsage; + public uint th32ProcessID; + public IntPtr th32DefaultHeapID; + public uint th32ModuleID; + public uint cntThreads; + public uint th32ParentProcessID; + public int pcPriClassBase; + public uint dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szExeFile; +#pragma warning restore IDE1006 // Naming Styles + } + + [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] + internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid + { + internal SafeSnapshotHandle() + : base(true) + { + } + + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + internal SafeSnapshotHandle(IntPtr handle) + : base(true) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() + { + return CloseHandle(handle); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + private static extern bool CloseHandle(IntPtr handle); + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct ParentProcessUtilities + { + internal IntPtr Reserved1; + internal IntPtr PebBaseAddress; + internal IntPtr Reserved2_0; + internal IntPtr Reselved2_1; + internal IntPtr UniqueProcessId; + internal IntPtr InheritedFromUniqueProcessId; + + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformaiton, int processInformationLength, out int returnLength); + + public static Process GetParentProcess(Process process) + { + return GetParentProcess(process.Handle); + } + + public static Process GetParentProcess(IntPtr handle) + { + var processUtilities = new ParentProcessUtilities(); + var status = NtQueryInformationProcess(handle, 0, ref processUtilities, Marshal.SizeOf(processUtilities), out _); + + if (status != 0) + { + return null; + } + + try + { + return Process.GetProcessById(processUtilities.InheritedFromUniqueProcessId.ToInt32()); + } + catch (ArgumentException) + { + return null; + } + } + } + } +} diff --git a/src/chocolatey.benchmark/helpers/ProcessTree.cs b/src/chocolatey.benchmark/helpers/ProcessTree.cs new file mode 100644 index 0000000000..8a90fb06f9 --- /dev/null +++ b/src/chocolatey.benchmark/helpers/ProcessTree.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace chocolatey.benchmark.helpers +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public sealed class ProcessTree + { + private static readonly string[] _filteredParents = new[] + { + "explorer", + "powershell", + "pwsh", + "cmd", + "bash" + }; + + public ProcessTree(string currentProcessName) + { + CurrentProcessName = currentProcessName; + } + + public string CurrentProcessName { get; } + + public string FirstFilteredProcessName + { + get { return GetFirstProcess(includeIgnored: false); } + } + + public string FirstProcessName + { + get { return GetFirstProcess(includeIgnored: true); } + } + + public string LastFilteredProcessName + { + get { return GetLastProcess(includeIgnored: false); } + } + + public string LastProcessName + { + get { return GetLastProcess(includeIgnored: true); } + } + + public LinkedList Processes { get; } = new LinkedList(); + + private static bool IsIgnoredProcess(string value) + { + return _filteredParents.Contains(value, StringComparer.OrdinalIgnoreCase); + } + + private string GetFirstProcess(bool includeIgnored) + { + if (Processes.Count == 0) + { + return null; + } + + if (includeIgnored) + { + return Processes.First.Value; + } + + LinkedListNode currentNode = Processes.First; + + while (currentNode != null) + { + if (!IsIgnoredProcess(currentNode.Value)) + { + return currentNode.Value; + } + + currentNode = currentNode.Next; + } + + return null; + } + + private string GetLastProcess(bool includeIgnored) + { + if (Processes.Count == 0) + { + return null; + } + + if (includeIgnored) + { + return Processes.Last.Value; + } + + LinkedListNode currentNode = Processes.Last; + + while (currentNode != null) + { + if (!IsIgnoredProcess(currentNode.Value)) + { + return currentNode.Value; + } + + currentNode = currentNode.Previous; + } + + return null; + } + + public override string ToString() + { + if (Processes.Count == 0) + { + return CurrentProcessName; + } + else + { + return CurrentProcessName + " =>" + string.Join(" => ", Processes); + } + } + + private string GetDebuggerDisplay() + { + return "ProcessTree (" + ToString() + ")"; + } + } +} \ No newline at end of file diff --git a/src/chocolatey.benchmark/packages.config b/src/chocolatey.benchmark/packages.config new file mode 100644 index 0000000000..568db12c80 --- /dev/null +++ b/src/chocolatey.benchmark/packages.config @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/chocolatey.sln b/src/chocolatey.sln index a528f26a0c..dcd90fddb4 100644 --- a/src/chocolatey.sln +++ b/src/chocolatey.sln @@ -55,6 +55,10 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "chocolatey.install", "choco EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chocolatey.PowerShell", "Chocolatey.PowerShell\Chocolatey.PowerShell.csproj", "{88396C46-8089-4814-A7D1-E18777FF6083}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{46D88287-13E4-4044-89FD-B52AAE7791BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chocolatey.benchmark", "chocolatey.benchmark\chocolatey.benchmark.csproj", "{2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -273,6 +277,42 @@ Global {88396C46-8089-4814-A7D1-E18777FF6083}.WIX|Mixed Platforms.Build.0 = Debug|Any CPU {88396C46-8089-4814-A7D1-E18777FF6083}.WIX|x86.ActiveCfg = Debug|Any CPU {88396C46-8089-4814-A7D1-E18777FF6083}.WIX|x86.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|x86.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Any CPU.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Any CPU.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Mixed Platforms.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|x86.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|x86.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Any CPU.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|x86.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|x86.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Any CPU.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Any CPU.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Mixed Platforms.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Mixed Platforms.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|x86.ActiveCfg = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|x86.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Any CPU.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Any CPU.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Mixed Platforms.Build.0 = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|x86.ActiveCfg = Debug|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -285,6 +325,7 @@ Global {DD9689F3-1D2D-41AE-A672-063EE70C0C9A} = {9AF88603-3E34-4B68-9B69-B0F1967A86BC} {9AF88603-3E34-4B68-9B69-B0F1967A86BC} = {FB6236DD-17EF-4C36-943C-47792FBC3306} {4795798A-2F92-467A-88FC-772E66BF8E57} = {FB6236DD-17EF-4C36-943C-47792FBC3306} + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180} = {46D88287-13E4-4044-89FD-B52AAE7791BF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {998CAC46-A2B8-447C-B4CC-3B11DC35ED46} From 5286fba1273cd97d74cc1b7b7db9974c225f0dbe Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Fri, 7 Jun 2024 14:24:26 +0200 Subject: [PATCH 02/10] (#3526) Implement process tree in codebase --- .../ParentProcessBenchmarks.cs | 6 ++ .../ProcessTreeBenchmark.cs | 1 + .../chocolatey.benchmark.csproj | 7 +- .../helpers/ManagedProcessHelper.cs | 75 +++----------- .../helpers/PinvokeProcessHelper.cs | 99 +++---------------- src/chocolatey.console/Program.cs | 2 + src/chocolatey/chocolatey.csproj | 3 +- .../information/ProcessInformation.cs | 96 ++++++++++++++++++ .../information}/ProcessTree.cs | 52 +++++++--- 9 files changed, 183 insertions(+), 158 deletions(-) rename src/{chocolatey.benchmark/helpers => chocolatey/infrastructure/information}/ProcessTree.cs (63%) diff --git a/src/chocolatey.benchmark/ParentProcessBenchmarks.cs b/src/chocolatey.benchmark/ParentProcessBenchmarks.cs index 1585615a5f..21486b8304 100644 --- a/src/chocolatey.benchmark/ParentProcessBenchmarks.cs +++ b/src/chocolatey.benchmark/ParentProcessBenchmarks.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using chocolatey.benchmark.helpers; +using chocolatey.infrastructure.information; namespace chocolatey.benchmark { @@ -59,5 +60,10 @@ public string GetParentProcessUndocumentedPinvoke() { return PinvokeProcessHelper.GetUndocumentedParent(); } + [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] + public ProcessTree GetParentProcessTreeImplemented() + { + return ProcessInformation.GetProcessTree(); + } } } \ No newline at end of file diff --git a/src/chocolatey.benchmark/ProcessTreeBenchmark.cs b/src/chocolatey.benchmark/ProcessTreeBenchmark.cs index 251f8fdc24..b6264324e7 100644 --- a/src/chocolatey.benchmark/ProcessTreeBenchmark.cs +++ b/src/chocolatey.benchmark/ProcessTreeBenchmark.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using chocolatey.benchmark.helpers; +using chocolatey.infrastructure.information; namespace chocolatey.benchmark { diff --git a/src/chocolatey.benchmark/chocolatey.benchmark.csproj b/src/chocolatey.benchmark/chocolatey.benchmark.csproj index 63f85e31da..f937db3b9b 100644 --- a/src/chocolatey.benchmark/chocolatey.benchmark.csproj +++ b/src/chocolatey.benchmark/chocolatey.benchmark.csproj @@ -237,7 +237,6 @@ - @@ -251,6 +250,12 @@ + + + {5563dc61-35fd-4fab-b331-9ae1fdb23f80} + chocolatey + + diff --git a/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs b/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs index 8fbc644631..204728978c 100644 --- a/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs +++ b/src/chocolatey.benchmark/helpers/ManagedProcessHelper.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Linq; +using chocolatey.infrastructure.information; namespace chocolatey.benchmark.helpers { @@ -8,11 +9,20 @@ internal static class ManagedProcessHelper { private static readonly string[] _filteredParents = new[] { - "explorer", - "powershell", - "pwsh", - "cmd", - "bash" + "explorer", + "powershell", + "pwsh", + "cmd", + "bash", + // The name used to launch windows services + // in the operating system. + "services", + // Known Terminal Emulators + "Tabby", + "WindowsTerminal", + "FireCMD", + "ConEmu64", + "ConEmuC64" }; public static ProcessTree GetProcessTree(Process process = null) @@ -81,29 +91,6 @@ public static string GetParent(Process process = null) } return nextProcess?.ProcessName; - - //var processName = FindIndexedProcessName(process.Id); - - //if (string.IsNullOrEmpty(processName)) - //{ - // return null; - //} - - //var parentProcess = FindPidFromIndexedProcessName(processName); - - //if (parentProcess == null) - //{ - // return null; - //} - - //var topLevelProcess = GetParent(parentProcess); - - //if (topLevelProcess == null) - //{ - // return parentProcess.ProcessName; - //} - - //return topLevelProcess; } public static string GetParentFiltered(Process process = null) @@ -142,37 +129,7 @@ public static string GetParentFiltered(Process process = null) } } - return nextProcess?.ProcessName; - - //var processName = FindIndexedProcessName(process.Id); - - //if (string.IsNullOrEmpty(processName)) - //{ - // return null; - //} - - //var parentProcess = FindPidFromIndexedProcessName(processName); - - //if (parentProcess == null) - //{ - // return null; - //} - - //var topLevelProcess = GetParent(parentProcess); - - //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) - //{ - // if (IsIgnoredParent(parentProcess.ProcessName)) - // { - // return null; - // } - // else - // { - // return parentProcess.ProcessName; - // } - //} - - //return topLevelProcess; + return selectedProcess?.ProcessName; } private static bool IsIgnoredParent(string processName) diff --git a/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs index 0da3591364..21d0b95e77 100644 --- a/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs +++ b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs @@ -7,6 +7,7 @@ using Microsoft.Win32.SafeHandles; using System.Security.Permissions; using System.Security; +using chocolatey.infrastructure.information; namespace chocolatey.benchmark.helpers { @@ -14,10 +15,20 @@ internal class PinvokeProcessHelper { private static readonly string[] _filteredParents = new[] { - "explorer", - "powershell", - "pwsh", - "cmd" + "explorer", + "powershell", + "pwsh", + "cmd", + "bash", + // The name used to launch windows services + // in the operating system. + "services", + // Known Terminal Emulators + "Tabby", + "WindowsTerminal", + "FireCMD", + "ConEmu64", + "ConEmuC64" }; public static ProcessTree GetDocumentedProcessTree(Process process = null) @@ -79,22 +90,6 @@ public static string GetDocumentedParent(Process process = null) } return nextProcess?.ProcessName; - - //var parentProcess = ParentDocumentedHelper.ParentProcess(process); - - //if (parentProcess == null) - //{ - // return null; - //} - - //var topLevelProcess = GetDocumentedParent(parentProcess); - - //if (topLevelProcess == null) - //{ - // return parentProcess.ProcessName; - //} - - //return topLevelProcess; } public static string GetDocumentedParentFiltered(Process process = null) @@ -129,30 +124,7 @@ public static string GetDocumentedParentFiltered(Process process = null) } } - return nextProcess?.ProcessName; - - var parentProcess = ParentDocumentedHelper.ParentProcess(process); - - if (parentProcess == null) - { - return null; - } - - //var topLevelProcess = GetDocumentedParentFiltered(parentProcess); - - //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) - //{ - // if (IsIgnoredParent(parentProcess.ProcessName)) - // { - // return null; - // } - // else - // { - // return parentProcess.ProcessName; - // } - //} - - //return topLevelProcess; + return selectedProcess?.ProcessName; } public static ProcessTree GetUndocumentedProcessTree(Process process = null) @@ -209,22 +181,6 @@ public static string GetUndocumentedParent(Process process = null) } return nextProcess?.ProcessName; - - //var parentProcess = ParentProcessUtilities.GetParentProcess(process); - - //if (parentProcess == null) - //{ - // return null; - //} - - //var topLevelProcess = GetUndocumentedParent(parentProcess); - - //if (topLevelProcess == null) - //{ - // return parentProcess.ProcessName; - //} - - //return topLevelProcess; } public static string GetUndocumentedParentFiltered(Process process = null) @@ -260,29 +216,6 @@ public static string GetUndocumentedParentFiltered(Process process = null) } return selectedProcess?.ProcessName; - - //var parentProcess = ParentProcessUtilities.GetParentProcess(process); - - //if (parentProcess == null) - //{ - // return null; - //} - - //var topLevelProcess = GetUndocumentedParentFiltered(parentProcess); - - //if (topLevelProcess == null || IsIgnoredParent(topLevelProcess)) - //{ - // if (IsIgnoredParent(parentProcess.ProcessName)) - // { - // return null; - // } - // else - // { - // return parentProcess.ProcessName; - // } - //} - - //return topLevelProcess; } private static bool IsIgnoredParent(string processName) diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index 51c10e1535..830a10dc7e 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -44,6 +44,7 @@ using Console = System.Console; using Environment = System.Environment; using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; +using System.Diagnostics; namespace chocolatey.console { @@ -270,6 +271,7 @@ private static void RemoveOldChocoExe(IFileSystem fileSystem) ); } + [Conditional("DEBUG")] private static void PauseIfDebug() { #if DEBUG diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 0f243f55e1..3801fb2185 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -1,4 +1,4 @@ - + @@ -263,6 +263,7 @@ + diff --git a/src/chocolatey/infrastructure/information/ProcessInformation.cs b/src/chocolatey/infrastructure/information/ProcessInformation.cs index bb6dfb9935..f3856efbf5 100644 --- a/src/chocolatey/infrastructure/information/ProcessInformation.cs +++ b/src/chocolatey/infrastructure/information/ProcessInformation.cs @@ -15,6 +15,9 @@ // limitations under the License. using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Principal; using chocolatey.infrastructure.platforms; @@ -158,6 +161,52 @@ public static bool UserIsSystem() return isSystem; } + public static ProcessTree GetProcessTree(Process process = null) + { + if (process == null) + { + process = Process.GetCurrentProcess(); + } + + var tree = new ProcessTree(process.ProcessName); + + Process nextProcess = null; + + while (true) + { + try + { + var parentProcess = ParentProcessUtilities.GetParentProcess(nextProcess ?? process); + + if (parentProcess == null) + { + break; + } + + nextProcess = parentProcess; + tree.Processes.AddLast(nextProcess.ProcessName); + } + catch (Win32Exception ex) + { + // Native error code 5 is access denied. + // This usually happens if the parent executable + // is running as a different user, in which case + // we are not able to get the necessary handle for + // the process. + if (ex.NativeErrorCode != 5) + { + throw; + } + else + { + "chocolatey".Log().Warn(logging.ChocolateyLoggers.LogFileOnly, "Unable to get parent process for '{0}'. Ignoring...", process.ProcessName); + } + } + } + + return tree; + } + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa376402.aspx BOOL WINAPI ConvertStringSidToSid( @@ -244,6 +293,53 @@ private enum TokenElevationType TokenElevationTypeLimited } + [StructLayout(LayoutKind.Sequential)] + private struct ParentProcessUtilities + { + internal IntPtr Reserved1; + internal IntPtr PebBaseAddress; + internal IntPtr Reserved2_0; + internal IntPtr Reselved2_1; + internal IntPtr UniqueProcessId; + internal IntPtr InheritedFromUniqueProcessId; + + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformaiton, int processInformationLength, out int returnLength); + + public static Process GetParentProcess(Process process) + { + try + { + return GetParentProcess(process.Handle); + } + catch (Win32Exception) + { + return null; + } + } + + public static Process GetParentProcess(IntPtr handle) + { + try + { + var processUtilities = new ParentProcessUtilities(); + + var status = NtQueryInformationProcess(handle, 0, ref processUtilities, Marshal.SizeOf(processUtilities), out _); + + if (status != 0) + { + return null; + } + + return Process.GetProcessById(processUtilities.InheritedFromUniqueProcessId.ToInt32()); + } + catch (ArgumentException) + { + return null; + } + } + } + #pragma warning disable IDE0022, IDE1006 [Obsolete("This overload is deprecated and will be removed in v3.")] public static bool user_is_administrator() diff --git a/src/chocolatey.benchmark/helpers/ProcessTree.cs b/src/chocolatey/infrastructure/information/ProcessTree.cs similarity index 63% rename from src/chocolatey.benchmark/helpers/ProcessTree.cs rename to src/chocolatey/infrastructure/information/ProcessTree.cs index 8a90fb06f9..3e548fc970 100644 --- a/src/chocolatey.benchmark/helpers/ProcessTree.cs +++ b/src/chocolatey/infrastructure/information/ProcessTree.cs @@ -3,23 +3,32 @@ using System.Diagnostics; using System.Linq; -namespace chocolatey.benchmark.helpers +namespace chocolatey.infrastructure.information { [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] - public sealed class ProcessTree + public class ProcessTree { private static readonly string[] _filteredParents = new[] { - "explorer", - "powershell", - "pwsh", - "cmd", - "bash" + "explorer", + "powershell", + "pwsh", + "cmd", + "bash", + // The name used to launch windows services + // in the operating system. + "services", + // Known Terminal Emulators + "Tabby", + "WindowsTerminal", + "FireCMD", + "ConEmu64", + "ConEmuC64" }; public ProcessTree(string currentProcessName) { - CurrentProcessName = currentProcessName; + CurrentProcessName = ToFriendlyName(currentProcessName); } public string CurrentProcessName { get; } @@ -51,6 +60,21 @@ private static bool IsIgnoredProcess(string value) return _filteredParents.Contains(value, StringComparer.OrdinalIgnoreCase); } + protected virtual string ToFriendlyName(string value) + { + switch (value.ToLowerInvariant()) + { + case "choco": + return "Chocolatey CLI"; + + case "ChocolateyGui": + return "Chocolatey GUI"; + + default: + return value; + } + } + private string GetFirstProcess(bool includeIgnored) { if (Processes.Count == 0) @@ -67,9 +91,9 @@ private string GetFirstProcess(bool includeIgnored) while (currentNode != null) { - if (!IsIgnoredProcess(currentNode.Value)) + if (!IsIgnoredProcess(currentNode.Value) && currentNode.Value != CurrentProcessName) { - return currentNode.Value; + return ToFriendlyName(currentNode.Value); } currentNode = currentNode.Next; @@ -94,9 +118,9 @@ private string GetLastProcess(bool includeIgnored) while (currentNode != null) { - if (!IsIgnoredProcess(currentNode.Value)) + if (!IsIgnoredProcess(currentNode.Value) && currentNode.Value != CurrentProcessName) { - return currentNode.Value; + return ToFriendlyName(currentNode.Value); } currentNode = currentNode.Previous; @@ -113,7 +137,7 @@ public override string ToString() } else { - return CurrentProcessName + " =>" + string.Join(" => ", Processes); + return CurrentProcessName + " => " + string.Join(" => ", Processes.Select(ToFriendlyName)); } } @@ -122,4 +146,4 @@ private string GetDebuggerDisplay() return "ProcessTree (" + ToString() + ")"; } } -} \ No newline at end of file +} From 3497344aa883b98044807dcfe9bcbcef43265999 Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Fri, 7 Jun 2024 14:25:56 +0200 Subject: [PATCH 03/10] (#3526) Implement prototype for user agent updating Work the ProcessTree into a service that can be pulled in to the NugetCommon library in order to provide a more informative user agent when querying repositories. --- .../nuget/NugetCommonSpecs.cs | 14 +++-- src/chocolatey/chocolatey.csproj | 4 +- .../infrastructure.app/nuget/NugetCommon.cs | 57 ++++++++++++++++++- .../ChocolateyRegistrationModule.cs | 1 + .../services/IProcessCollectorService.cs | 45 +++++++++++++++ .../services/ProcessCollectorService.cs | 30 ++++++++++ .../infrastructure/information/ProcessTree.cs | 10 +++- 7 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs create mode 100644 src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs diff --git a/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs index c5c41cf74d..1f3b3bd7ee 100644 --- a/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs @@ -33,6 +33,9 @@ using NuGet.Protocol.Core.Types; using NuGet.Versioning; using FluentAssertions; +using chocolatey.infrastructure.app.services; +using chocolatey.infrastructure.registration; +using System.Diagnostics; namespace chocolatey.tests.infrastructure.app.nuget { @@ -175,14 +178,17 @@ public void Should_set_user_agent_string() Context(); var source = "https://community.chocolatey.org/api/v2/"; _configuration.Sources = source; - _configuration.Information.ChocolateyProductVersion = "vNext"; _because(); // Change this when the NuGet version is updated. - var nugetClientVersion = "6.4.1"; - var expectedUserAgentString = "{0}/{1} via NuGet Client/{2}".FormatWith(ApplicationParameters.UserAgent, _configuration.Information.ChocolateyProductVersion, nugetClientVersion); - UserAgent.UserAgentString.Should().StartWith(expectedUserAgentString); + const string nugetClientVersion = "6.4.1"; + var currentProcess = Process.GetCurrentProcess(); + var expectedUserAgentRegexString = @"^{0}\/[\d\.]+(-[A-za-z\d\.-]+)? {1}\/[\d\.]+(-[A-Za-z\d\.-]+)? (\([A-za-z\d\.-]+(, [A-Za-z\d\.-]+)?\) )?via NuGet Client\/{2}".FormatWith( + ApplicationParameters.UserAgent, + currentProcess.ProcessName, + Regex.Escape(nugetClientVersion)); + UserAgent.UserAgentString.Should().MatchRegex(expectedUserAgentRegexString); } } diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 3801fb2185..72a74ea052 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -1,4 +1,4 @@ - + @@ -229,7 +229,9 @@ + + diff --git a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs index 669a702165..1805bda8f5 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs @@ -49,6 +49,9 @@ using Console = chocolatey.infrastructure.adapters.Console; using Environment = chocolatey.infrastructure.adapters.Environment; using System.Collections.Concurrent; +using chocolatey.infrastructure.information; +using chocolatey.infrastructure.registration; +using chocolatey.infrastructure.app.services; namespace chocolatey.infrastructure.app.nuget { @@ -100,8 +103,60 @@ public static SourceRepository GetLocalRepository() public static IEnumerable GetRemoteRepositories(ChocolateyConfiguration configuration, ILogger nugetLogger, IFileSystem filesystem) #pragma warning restore IDE0060 // unused method parameter (nugetLogger) { + // As this is a static method, we need to call the global SimpleInjector container to get a registered service. + var collectorService = SimpleInjectorContainer.Container.GetInstance(); + var processTree = collectorService.GetProcessesTree(); + "chocolatey".Log().Debug("Process Tree: {0}", processTree); + + var userAgent = new StringBuilder() + .Append(ApplicationParameters.UserAgent) + .Append('/') + .Append(VersionInformation.GetCurrentInformationalVersion(Assembly.GetAssembly(typeof(NugetCommon)))); + + if (!string.IsNullOrEmpty(collectorService.UserAgentProcessName)) + { + userAgent.Append(' ').Append(collectorService.UserAgentProcessName); + var processVersion = collectorService.UserAgentProcessVersion; + + if (string.IsNullOrEmpty(processVersion)) + { + processVersion = VersionInformation.GetCurrentInformationalVersion(); + } + + if (!string.IsNullOrEmpty(processVersion)) + { + userAgent.Append('/').Append(processVersion); + } + } + else if (processTree.CurrentProcessName != "Chocolatey CLI") + { + userAgent.Append(' ').Append(processTree.CurrentProcessName); + var processVersion = VersionInformation.GetCurrentInformationalVersion(); + + if (!string.IsNullOrEmpty(processVersion)) + { + userAgent.Append('/').Append(processVersion); + } + } + + if (processTree.LastFilteredProcessName != processTree.FirstFilteredProcessName && !string.IsNullOrEmpty(processTree.LastFilteredProcessName) && !string.IsNullOrEmpty(processTree.FirstFilteredProcessName)) + { + userAgent.Append(" (").Append(processTree.LastFilteredProcessName).Append(", ").Append(processTree.FirstFilteredProcessName).Append(')'); + } + else if (!string.IsNullOrEmpty(processTree.LastFilteredProcessName)) + { + userAgent.Append(" (").Append(processTree.LastFilteredProcessName).Append(')'); + } + else if (!string.IsNullOrEmpty(processTree.FirstFilteredProcessName)) + { + userAgent.Append(" (").Append(processTree.FirstFilteredProcessName).Append(')'); + } + + userAgent.Append(" via NuGet Client"); + // Set user agent for all NuGet library calls. Should not affect any HTTP calls that Chocolatey itself would make. - UserAgent.SetUserAgentString(new UserAgentStringBuilder("{0}/{1} via NuGet Client".FormatWith(ApplicationParameters.UserAgent, configuration.Information.ChocolateyProductVersion))); + UserAgent.SetUserAgentString(new UserAgentStringBuilder(userAgent.ToString())); + "chocolatey".Log().Debug("Updating User Agent to '{0}'.", UserAgent.UserAgentString); // ensure credentials can be grabbed from configuration SetHttpHandlerCredentialService(configuration); diff --git a/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs index ab559a6af2..359a7c9ec1 100644 --- a/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs +++ b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs @@ -94,6 +94,7 @@ public void RegisterDependencies(IContainerRegistrator registrator, ChocolateyCo .ToArray(); registrator.RegisterService(availableRules); + registrator.RegisterService(); } #pragma warning disable IDE0022, IDE1006 diff --git a/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs b/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs new file mode 100644 index 0000000000..10695c505c --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs @@ -0,0 +1,45 @@ +using chocolatey.infrastructure.information; + +namespace chocolatey.infrastructure.app.services +{ + /// + /// Collector service that will get information about the processes in the current execution. + /// + /// + /// This service is used to build the correct user agent we want to send to the remote servers. + /// + public interface IProcessCollectorService + { + /// + /// Gets the friendly name of the currently running process. + /// + /// + /// If no user agent process name is specified, the current process in the process tree will be used instead. + /// + string UserAgentProcessName { get; } + + /// + /// Gets the version number of the currently running process. + /// + /// + /// + /// If no user agent process version is specified, the version number of the currently + /// running proccess will be looked up. + /// + /// + /// This property will only be used if have also been specified. + /// + /// + string UserAgentProcessVersion { get; } + + /// + /// Gets the full details of the process tree that Chocolatey CLI is part of. This includes + /// the top level parent, the closest parent, the current process name and all other + /// processes between these. + /// + /// + /// The found process tree, returning null from this will throw an exception in Chocolatey CLI. + /// + ProcessTree GetProcessesTree(); + } +} diff --git a/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs b/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs new file mode 100644 index 0000000000..11631e3c76 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs @@ -0,0 +1,30 @@ +using System; +using chocolatey.infrastructure.information; + +namespace chocolatey.infrastructure.app.services +{ + public class ProcessCollectorService : IProcessCollectorService + { + private static ProcessTree _processTree = null; + + /// + public virtual string UserAgentProcessName { get; } = string.Empty; + + /// + public virtual string UserAgentProcessVersion { get; } = string.Empty; + + /// + /// + /// This method is not overridable on purpose, as once a tree is created it should not be changed. + /// + public ProcessTree GetProcessesTree() + { + if (_processTree is null) + { + _processTree = ProcessInformation.GetProcessTree(); + } + + return _processTree; + } + } +} diff --git a/src/chocolatey/infrastructure/information/ProcessTree.cs b/src/chocolatey/infrastructure/information/ProcessTree.cs index 3e548fc970..3ca0c3f5d4 100644 --- a/src/chocolatey/infrastructure/information/ProcessTree.cs +++ b/src/chocolatey/infrastructure/information/ProcessTree.cs @@ -8,6 +8,11 @@ namespace chocolatey.infrastructure.information [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] public class ProcessTree { + // IGNORED USER AGENT PROCESSES + // Our Pester tests may need their own exclusion list in the verification + // updated when this list changes. Search the repo for the above string + // in caps if you have trouble finding the corresponding list in tests + // (should be in UserAgent.Tests.ps1). private static readonly string[] _filteredParents = new[] { "explorer", @@ -67,9 +72,12 @@ protected virtual string ToFriendlyName(string value) case "choco": return "Chocolatey CLI"; - case "ChocolateyGui": + case "chocolateygui": return "Chocolatey GUI"; + case "chocolatey-agent": + return "Chocolatey Agent"; + default: return value; } From ec86c68437b033c23d1b9b18b8b8640e6805b2fc Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Tue, 29 Oct 2024 15:31:38 -0400 Subject: [PATCH 04/10] (#3526) Implement fallback logic from benchmarks The primary method is quickest, but as it is an unstable API, we have a need to provide a fallback that will be able to be used if the DLL is missing/removed, or if the entry point is later removed. Of the stable options, this p/invoke method seems to be the next best option to work with. Crucially, these p/invokes need to be provided by different types, so that if there is a TypeLoadException from a failed p/invoke, we can still attempt the fallback. Also, added a couple more exclusions for the choco.exe shim and for winlogon, which are not useful to include. --- src/chocolatey.sln | 3 - .../information/ProcessInformation.cs | 253 ++++++++++++++++-- .../infrastructure/information/ProcessTree.cs | 5 +- 3 files changed, 229 insertions(+), 32 deletions(-) diff --git a/src/chocolatey.sln b/src/chocolatey.sln index dcd90fddb4..1d95bda6d2 100644 --- a/src/chocolatey.sln +++ b/src/chocolatey.sln @@ -302,11 +302,8 @@ Global {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Any CPU.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Any CPU.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Mixed Platforms.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Mixed Platforms.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|x86.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|x86.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Any CPU.ActiveCfg = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Any CPU.Build.0 = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.WIX|Mixed Platforms.ActiveCfg = Debug|Any CPU diff --git a/src/chocolatey/infrastructure/information/ProcessInformation.cs b/src/chocolatey/infrastructure/information/ProcessInformation.cs index f3856efbf5..eeb4fbc8f8 100644 --- a/src/chocolatey/infrastructure/information/ProcessInformation.cs +++ b/src/chocolatey/infrastructure/information/ProcessInformation.cs @@ -18,9 +18,13 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Security; using System.Security.Principal; using chocolatey.infrastructure.platforms; +using Microsoft.Win32.SafeHandles; namespace chocolatey.infrastructure.information { @@ -161,7 +165,12 @@ public static bool UserIsSystem() return isSystem; } - public static ProcessTree GetProcessTree(Process process = null) + public static ProcessTree GetProcessTree() + { + return GetProcessTree(null); + } + + public static ProcessTree GetProcessTree(Process process) { if (process == null) { @@ -170,15 +179,49 @@ public static ProcessTree GetProcessTree(Process process = null) var tree = new ProcessTree(process.ProcessName); - Process nextProcess = null; + if (Platform.GetPlatform() != PlatformType.Windows) + { + return tree; + } - while (true) + try { try { - var parentProcess = ParentProcessUtilities.GetParentProcess(nextProcess ?? process); + tree = PopulateProcessTreeInternal(tree, process); + } + catch (TypeLoadException ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException) + { + try + { + // These exceptions mean the lookup failed because the DLL is missing or the entry point is no longer present. + // Ignore these and fall back to the alternative p/invoke method if we haven't already. + tree = PopulateProcessTreeStable(tree, process); + } + catch (TypeLoadException) + { + "chocolatey".Log().Warn(logging.ChocolateyLoggers.LogFileOnly, "All available methods of querying processes from the win32 APIs are broken or critical DLLs are missing."); + } + } + } + catch (Win32Exception ex) + { + "chocolatey".Log().Warn(logging.ChocolateyLoggers.LogFileOnly, "Unhandled Win32Exception ({0}) in finding parent processes.", ex.Message); + } + + return tree; + } + + private static ProcessTree PopulateProcessTreeInternal(ProcessTree tree, Process currentProcess) + { + Process nextProcess = null; + try + { + while (true) + { + var parentProcess = ParentProcessHelperInternal.GetParentProcess(nextProcess ?? currentProcess); - if (parentProcess == null) + if (parentProcess is null) { break; } @@ -186,27 +229,66 @@ public static ProcessTree GetProcessTree(Process process = null) nextProcess = parentProcess; tree.Processes.AddLast(nextProcess.ProcessName); } - catch (Win32Exception ex) + } + catch (Win32Exception ex) + { + // Native error code 5 is access denied. + // This usually happens if the parent executable + // is running as a different user, in which case + // we are not able to get the necessary handle for + // the process. + if (ex.NativeErrorCode != 5) { - // Native error code 5 is access denied. - // This usually happens if the parent executable - // is running as a different user, in which case - // we are not able to get the necessary handle for - // the process. - if (ex.NativeErrorCode != 5) - { - throw; - } - else + throw; + } + else + { + "chocolatey".Log().Debug(logging.ChocolateyLoggers.LogFileOnly, "Unable to get parent process for '{0}'. Ignoring...", currentProcess.ProcessName); + } + } + + return tree; + } + + private static ProcessTree PopulateProcessTreeStable(ProcessTree tree, Process currentProcess) + { + Process nextProcess = null; + try + { + while (true) + { + var parentProcess = ParentProcessHelperStable.GetParentProcess(nextProcess ?? currentProcess); + + if (parentProcess is null) { - "chocolatey".Log().Warn(logging.ChocolateyLoggers.LogFileOnly, "Unable to get parent process for '{0}'. Ignoring...", process.ProcessName); + break; } + + nextProcess = parentProcess; + tree.Processes.AddLast(nextProcess.ProcessName); + } + } + catch (Win32Exception ex) + { + // Native error code 5 is access denied. + // This usually happens if the parent executable + // is running as a different user, in which case + // we are not able to get the necessary handle for + // the process. + if (ex.NativeErrorCode != 5) + { + throw; + } + else + { + "chocolatey".Log().Debug(logging.ChocolateyLoggers.LogFileOnly, "Unable to get parent process for '{0}'. Ignoring...", currentProcess.ProcessName); } } return tree; } + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa376402.aspx BOOL WINAPI ConvertStringSidToSid( @@ -294,7 +376,7 @@ private enum TokenElevationType } [StructLayout(LayoutKind.Sequential)] - private struct ParentProcessUtilities + private struct ParentProcessHelperInternal { internal IntPtr Reserved1; internal IntPtr PebBaseAddress; @@ -304,25 +386,18 @@ private struct ParentProcessUtilities internal IntPtr InheritedFromUniqueProcessId; [DllImport("ntdll.dll")] - private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformaiton, int processInformationLength, out int returnLength); + private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessHelperInternal processInformation, int processInformationLength, out int returnLength); public static Process GetParentProcess(Process process) { - try - { - return GetParentProcess(process.Handle); - } - catch (Win32Exception) - { - return null; - } + return GetParentProcess(process.Handle); } public static Process GetParentProcess(IntPtr handle) { try { - var processUtilities = new ParentProcessUtilities(); + var processUtilities = new ParentProcessHelperInternal(); var status = NtQueryInformationProcess(handle, 0, ref processUtilities, Marshal.SizeOf(processUtilities), out _); @@ -340,6 +415,128 @@ public static Process GetParentProcess(IntPtr handle) } } + + private static class ParentProcessHelperStable + { + public static Process GetParentProcess(Process process) + { + var processId = ParentProcessId(process.Id); + + if (processId == -1) + { + return null; + } + else + { + return Process.GetProcessById(processId); + } + } + + private static int ParentProcessId(int id) + { + var pe32 = new PROCESSENTRY32 + { + dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32)) + }; + + using (var hSnapshot = CreateToolhelp32Snapshot(SnapshotFlags.Process, (uint)id)) + { + if (hSnapshot.IsInvalid) + { + throw new Win32Exception(); + } + + if (!Process32First(hSnapshot, ref pe32)) + { + var errno = Marshal.GetLastWin32Error(); + + if (errno == ERROR_NO_MORE_FILES) + { + return -1; + } + + throw new Win32Exception(errno); + } + + do + { + if (pe32.th32ProcessID == (uint)id) + { + return (int)pe32.th32ParentProcessID; + } + } while (Process32Next(hSnapshot, ref pe32)); + } + + return -1; + } + + private const int ERROR_NO_MORE_FILES = 0x12; + [DllImport("kernel32.dll", SetLastError = true)] + private static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); + + [Flags] + private enum SnapshotFlags : uint + { + HeapList = 0x00000001, + Process = 0x00000002, + Thread = 0x00000004, + Module = 0x00000008, + Module32 = 0x00000010, + All = (HeapList | Process | Thread | Module), + Inherit = 0x80000000, + NoHeaps = 0x40000000 + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESSENTRY32 + { +#pragma warning disable IDE1006 // Naming Styles + public uint dwSize; + public uint cntUsage; + public uint th32ProcessID; + public IntPtr th32DefaultHeapID; + public uint th32ModuleID; + public uint cntThreads; + public uint th32ParentProcessID; + public int pcPriClassBase; + public uint dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szExeFile; +#pragma warning restore IDE1006 // Naming Styles + } + + [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] + internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid + { + internal SafeSnapshotHandle() + : base(true) + { + } + + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + internal SafeSnapshotHandle(IntPtr handle) + : base(true) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() + { + return CloseHandle(handle); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + private static extern bool CloseHandle(IntPtr handle); + } + } + #pragma warning disable IDE0022, IDE1006 [Obsolete("This overload is deprecated and will be removed in v3.")] public static bool user_is_administrator() diff --git a/src/chocolatey/infrastructure/information/ProcessTree.cs b/src/chocolatey/infrastructure/information/ProcessTree.cs index 3ca0c3f5d4..b1e9225220 100644 --- a/src/chocolatey/infrastructure/information/ProcessTree.cs +++ b/src/chocolatey/infrastructure/information/ProcessTree.cs @@ -16,6 +16,7 @@ public class ProcessTree private static readonly string[] _filteredParents = new[] { "explorer", + "winlogon", "powershell", "pwsh", "cmd", @@ -28,7 +29,9 @@ public class ProcessTree "WindowsTerminal", "FireCMD", "ConEmu64", - "ConEmuC64" + "ConEmuC64", + // Nested processes / invoked by the shim choco.exe + "Chocolatey CLI" }; public ProcessTree(string currentProcessName) From 8a8c0af664787e60cd95b4121f2beb4b7f666b4e Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Wed, 30 Oct 2024 16:42:02 -0400 Subject: [PATCH 05/10] (#3526) Add tests for the user agent --- .../pester-tests/features/UserAgent.Tests.ps1 | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/pester-tests/features/UserAgent.Tests.ps1 diff --git a/tests/pester-tests/features/UserAgent.Tests.ps1 b/tests/pester-tests/features/UserAgent.Tests.ps1 new file mode 100644 index 0000000000..0637a8be2b --- /dev/null +++ b/tests/pester-tests/features/UserAgent.Tests.ps1 @@ -0,0 +1,91 @@ +Import-Module helpers/common-helpers + +Describe "Chocolatey User Agent" -Tag Chocolatey, UserAgent { + BeforeAll { + Initialize-ChocolateyTestInstall + New-ChocolateyInstallSnapshot + + $Output = Invoke-Choco search chocolatey --debug + $ChocolateyVersion = Get-ChocolateyVersion + + $Processes = [System.Collections.Generic.List[string]]::new() + + # IGNORED USER AGENT PROCESSES + # This list should match the one in CLI code for things we don't expect to see in the user agent + # after filtering the process tree. + # The corresponding list can be found in chocolatey.infrastructure.information.ProcessTree; + # search the repo for the above string in caps if you have trouble finding it. + $ExcludedProcesses = @( + "explorer" + "winlogon" + "powershell" + "pwsh" + "cmd" + "bash" + "services" + "svchost" + "Chocolatey CLI" + "alacritty" + "code" + "ConEmu64" + "ConEmuC64" + "conhost" + "c3270" + "FireCMD" + "Hyper" + "SecureCRT" + "Tabby" + "wezterm" + "wezterm-gui" + "WindowsTerminal" + ) + } + + AfterAll { + Remove-ChocolateyTestInstall + } + + It 'Logs the full process tree to debug' { + $logLine = $Output.Lines | Where-Object { $_ -match '^Process Tree' } + + $logLine | Should -Not -BeNullOrEmpty -Because 'choco.exe should log the process tree to debug' + + Write-Host "================== PROCESS TREE ==================" + Write-Host $logLine + + $parentProcesses = [string[]]@($logLine -replace '^Process Tree: ' -split ' => ' | Select-Object -Skip 1) + if ($parentProcesses.Count -gt 0) { + $Processes.AddRange($parentProcesses) + } + } + + It 'Logs the final user agent to debug' { + $logLine = $Output.Lines | Where-Object { $_ -match '^Updating User Agent' } + + $logLine | Should -Not -BeNullOrEmpty -Because "choco.exe should log the user agent string to debug`n$($Output.Lines)" + + Write-Host "================== USER AGENT ==================" + Write-Host $logLine + + $result = $logLine -match "'(?Chocolatey Command Line/[^']+)'" + $result | Should -BeTrue -Because "the user agent string should start with Chocolatey Command Line. $logLine" + + $userAgent = $matches['UserAgent'] + + $userAgent -match 'Chocolatey Command Line/(?[a-z0-9.-]+) ([a-z ]+/([a-z0-9.-]+) )?\((?[^,)]+)(?:, (?[^)]+))?\) via NuGet Client' | + Should -BeTrue -Because "the user agent string should contain the choco.exe version, the licensed extension version if any, and any parent processes. $logLine" + + $matches['Version'] | Should -Be $ChocolateyVersion -Because "the user agent string should contain the currently running Chocolatey version. $logLine" + $filteredProcesses = @($Processes | Where-Object { $_ -notin $ExcludedProcesses }) + + if ($filteredProcesses.Count -gt 1) { + $rootProcess = $filteredProcesses[-1] + $matches['RootProcess'] | Should -Be $rootProcess -Because "the user agent string should show the root calling process '$rootProcess'. $logLine" + } + + if ($filteredProcesses.Count -gt 0) { + $callingProcess = $filtered[0] + $matches['ParentProcess'] | Should -Be $callingProcess -Because "the user agent string should show the parent process '$callingProcess'. $logLine" + } + } +} \ No newline at end of file From 72fa3a5007ce9db0cd40aacd67f0a9de5e362b1c Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Thu, 31 Oct 2024 10:52:06 -0400 Subject: [PATCH 06/10] (#3526) Fix Cake build issues for benchmarks project We need to define values for ReleaseOfficial for the benchmark project or the Cake build complains. Also added Chocolatey.PowerShell to the slnf in the repo, since that was missing. --- .../chocolatey.benchmark.csproj | 11 +++++++++++ src/chocolatey.sln | 15 ++++++--------- src/chocolatey.slnf | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/chocolatey.benchmark/chocolatey.benchmark.csproj b/src/chocolatey.benchmark/chocolatey.benchmark.csproj index f937db3b9b..f42ea66591 100644 --- a/src/chocolatey.benchmark/chocolatey.benchmark.csproj +++ b/src/chocolatey.benchmark/chocolatey.benchmark.csproj @@ -13,6 +13,7 @@ 512 true true + ..\ @@ -36,6 +37,16 @@ prompt 4 + + bin\ReleaseOfficial\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + false + ..\packages\BenchmarkDotNet.0.13.12\lib\netstandard2.0\BenchmarkDotNet.dll diff --git a/src/chocolatey.sln b/src/chocolatey.sln index 1d95bda6d2..25811d8588 100644 --- a/src/chocolatey.sln +++ b/src/chocolatey.sln @@ -284,23 +284,20 @@ Global {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|x86.ActiveCfg = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Debug|x86.Build.0 = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Any CPU.ActiveCfg = Debug|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Any CPU.Build.0 = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|Mixed Platforms.Build.0 = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|x86.ActiveCfg = Debug|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.NoResources|x86.Build.0 = Debug|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Any CPU.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|Mixed Platforms.Build.0 = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|x86.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.Release|x86.Build.0 = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.Build.0 = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.Build.0 = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.ActiveCfg = Release|Any CPU - {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.Build.0 = Release|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.ActiveCfg = ReleaseOfficial|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Any CPU.Build.0 = ReleaseOfficial|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.ActiveCfg = ReleaseOfficial|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|Mixed Platforms.Build.0 = ReleaseOfficial|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.ActiveCfg = ReleaseOfficial|Any CPU + {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficial|x86.Build.0 = ReleaseOfficial|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Any CPU.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|Mixed Platforms.ActiveCfg = Release|Any CPU {2B98BB42-A7AE-4EF6-B0B8-AA7BFC1E1180}.ReleaseOfficialNo7zip|x86.ActiveCfg = Release|Any CPU diff --git a/src/chocolatey.slnf b/src/chocolatey.slnf index 3eee04a084..a491a4615a 100644 --- a/src/chocolatey.slnf +++ b/src/chocolatey.slnf @@ -2,6 +2,7 @@ "solution": { "path": "chocolatey.sln", "projects": [ + "Chocolatey.PowerShell\\Chocolatey.PowerShell.csproj", "chocolatey.console\\chocolatey.console.csproj", "chocolatey.resources\\chocolatey.resources.csproj", "chocolatey.tests.integration\\chocolatey.tests.integration.csproj", From aee561b17cc9c01322eb9c621e568877ca82606e Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Thu, 31 Oct 2024 11:20:37 -0400 Subject: [PATCH 07/10] (#3526) Minor fixes for mono in the benchmarks and tests RuntimeInformation is shadowed by Mono which means that build breaks and because all of the namespaces are the same under Mono's version, that name is not usable in our build currently. We can reuse our existing Platform.GetPlatform() helper here instead, rather than trying to disentangle exactly why this build configuration is not particularly functional. --- .../helpers/PinvokeProcessHelper.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs index 21d0b95e77..fb4f5d063e 100644 --- a/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs +++ b/src/chocolatey.benchmark/helpers/PinvokeProcessHelper.cs @@ -8,6 +8,7 @@ using System.Security.Permissions; using System.Security; using chocolatey.infrastructure.information; +using chocolatey.infrastructure.platforms; namespace chocolatey.benchmark.helpers { @@ -40,7 +41,7 @@ public static ProcessTree GetDocumentedProcessTree(Process process = null) var tree = new ProcessTree(process.ProcessName); - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.GetPlatform() != PlatformType.Windows) { return tree; } @@ -65,7 +66,7 @@ public static ProcessTree GetDocumentedProcessTree(Process process = null) public static string GetDocumentedParent(Process process = null) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.GetPlatform() != PlatformType.Windows) { return null; } @@ -94,7 +95,7 @@ public static string GetDocumentedParent(Process process = null) public static string GetDocumentedParentFiltered(Process process = null) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.GetPlatform() != PlatformType.Windows) { return null; } @@ -156,7 +157,7 @@ public static ProcessTree GetUndocumentedProcessTree(Process process = null) public static string GetUndocumentedParent(Process process = null) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.GetPlatform() != PlatformType.Windows) { return null; } @@ -185,7 +186,7 @@ public static string GetUndocumentedParent(Process process = null) public static string GetUndocumentedParentFiltered(Process process = null) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Platform.GetPlatform() != PlatformType.Windows) { return null; } From f58a4c4561e97686a815c070a6f5d8f9495c8ea0 Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Fri, 1 Nov 2024 14:39:35 -0400 Subject: [PATCH 08/10] (#3526) Expand the list of known terminal emulators These processes are not particularly useful to note as they are common terminal emulators, so we can exclude them when looking at the process tree. --- .../infrastructure/information/ProcessTree.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/chocolatey/infrastructure/information/ProcessTree.cs b/src/chocolatey/infrastructure/information/ProcessTree.cs index b1e9225220..fa36ab54be 100644 --- a/src/chocolatey/infrastructure/information/ProcessTree.cs +++ b/src/chocolatey/infrastructure/information/ProcessTree.cs @@ -15,6 +15,7 @@ public class ProcessTree // (should be in UserAgent.Tests.ps1). private static readonly string[] _filteredParents = new[] { + // Windows processes and shells "explorer", "winlogon", "powershell", @@ -24,14 +25,23 @@ public class ProcessTree // The name used to launch windows services // in the operating system. "services", + "svchost", + // Nested processes / invoked by the shim choco.exe + "Chocolatey CLI", // Known Terminal Emulators - "Tabby", - "WindowsTerminal", - "FireCMD", + "alacritty", + "code", "ConEmu64", "ConEmuC64", - // Nested processes / invoked by the shim choco.exe - "Chocolatey CLI" + "conhost", + "c3270", + "FireCMD", + "Hyper", + "SecureCRT", + "Tabby", + "wezterm", + "wezterm-gui", + "WindowsTerminal", }; public ProcessTree(string currentProcessName) From a620b2b734ce25a4391296a2d43bfd007ce33ab0 Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Mon, 4 Nov 2024 11:54:01 -0500 Subject: [PATCH 09/10] (#3526) Revise naming of new additions Simplify some naming, also add a constant (and comment) to clarify what some of the p/invoke nonsense is doing. --- src/chocolatey/infrastructure.app/nuget/NugetCommon.cs | 2 +- .../services/IProcessCollectorService.cs | 2 +- .../services/ProcessCollectorService.cs | 2 +- .../infrastructure/information/ProcessInformation.cs | 8 +++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs index 1805bda8f5..0c177cc82d 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs @@ -105,7 +105,7 @@ public static IEnumerable GetRemoteRepositories(ChocolateyConf { // As this is a static method, we need to call the global SimpleInjector container to get a registered service. var collectorService = SimpleInjectorContainer.Container.GetInstance(); - var processTree = collectorService.GetProcessesTree(); + var processTree = collectorService.GetProcessTree(); "chocolatey".Log().Debug("Process Tree: {0}", processTree); var userAgent = new StringBuilder() diff --git a/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs b/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs index 10695c505c..d5640b4b31 100644 --- a/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs +++ b/src/chocolatey/infrastructure.app/services/IProcessCollectorService.cs @@ -40,6 +40,6 @@ public interface IProcessCollectorService /// /// The found process tree, returning null from this will throw an exception in Chocolatey CLI. /// - ProcessTree GetProcessesTree(); + ProcessTree GetProcessTree(); } } diff --git a/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs b/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs index 11631e3c76..164a4a1c30 100644 --- a/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs +++ b/src/chocolatey/infrastructure.app/services/ProcessCollectorService.cs @@ -17,7 +17,7 @@ public class ProcessCollectorService : IProcessCollectorService /// /// This method is not overridable on purpose, as once a tree is created it should not be changed. /// - public ProcessTree GetProcessesTree() + public ProcessTree GetProcessTree() { if (_processTree is null) { diff --git a/src/chocolatey/infrastructure/information/ProcessInformation.cs b/src/chocolatey/infrastructure/information/ProcessInformation.cs index eeb4fbc8f8..75c896ca8c 100644 --- a/src/chocolatey/infrastructure/information/ProcessInformation.cs +++ b/src/chocolatey/infrastructure/information/ProcessInformation.cs @@ -399,7 +399,13 @@ public static Process GetParentProcess(IntPtr handle) { var processUtilities = new ParentProcessHelperInternal(); - var status = NtQueryInformationProcess(handle, 0, ref processUtilities, Marshal.SizeOf(processUtilities), out _); + // https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess#process_basic_information + // Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, + // and a unique value used by the system to identify the specified process. + // It also includes the `InheritedFromUniqueProcessId` value which we can use to look up the parent process directly. + const int processBasicInformation = 0; + + var status = NtQueryInformationProcess(handle, processBasicInformation, ref processUtilities, Marshal.SizeOf(processUtilities), out _); if (status != 0) { From aca796c55b9f4ad82931e7ed4f179696c6c97595 Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Wed, 6 Nov 2024 10:31:10 -0500 Subject: [PATCH 10/10] (maint) Reorder / tidy using statements --- src/chocolatey.console/Program.cs | 3 +-- .../nuget/NugetCommonSpecs.cs | 12 +++++----- .../infrastructure.app/nuget/NugetCommon.cs | 22 +++++-------------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index 830a10dc7e..312673a473 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -16,9 +16,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using Microsoft.Win32; using chocolatey.infrastructure.information; using chocolatey.infrastructure.app; @@ -44,7 +44,6 @@ using Console = System.Console; using Environment = System.Environment; using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; -using System.Diagnostics; namespace chocolatey.console { diff --git a/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs b/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs index 1f3b3bd7ee..a35d6601ef 100644 --- a/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/nuget/NugetCommonSpecs.cs @@ -16,26 +16,24 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; +using System.Text.RegularExpressions; using System.Threading; -using Chocolatey.NuGet.Frameworks; +using System.Threading.Tasks; using chocolatey.infrastructure.app; using chocolatey.infrastructure.app.configuration; using chocolatey.infrastructure.app.nuget; using chocolatey.infrastructure.filesystem; +using Chocolatey.NuGet.Frameworks; +using FluentAssertions; using Moq; using NuGet.Common; using NuGet.Configuration; using NuGet.Packaging; using NuGet.Packaging.Core; -using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; -using FluentAssertions; -using chocolatey.infrastructure.app.services; -using chocolatey.infrastructure.registration; -using System.Diagnostics; namespace chocolatey.tests.infrastructure.app.nuget { diff --git a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs index 0c177cc82d..e2043fdd1d 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs @@ -15,43 +15,33 @@ // limitations under the License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; -using System.Net.Http; -using System.Net.Security; -using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using chocolatey.infrastructure.adapters; -using Alphaleonis.Win32.Filesystem; -using Chocolatey.NuGet.Frameworks; -using chocolatey.infrastructure.configuration; using chocolatey.infrastructure.app.configuration; -using chocolatey.infrastructure.app.domain; +using chocolatey.infrastructure.app.services; using chocolatey.infrastructure.filesystem; -using chocolatey.infrastructure.logging; -using NuGet; +using chocolatey.infrastructure.information; +using chocolatey.infrastructure.registration; +using chocolatey.infrastructure.results; +using Chocolatey.NuGet.Frameworks; using NuGet.Common; using NuGet.Configuration; using NuGet.Credentials; -using NuGet.PackageManagement; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.ProjectManagement; using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; -using chocolatey.infrastructure.results; using Console = chocolatey.infrastructure.adapters.Console; -using Environment = chocolatey.infrastructure.adapters.Environment; -using System.Collections.Concurrent; -using chocolatey.infrastructure.information; -using chocolatey.infrastructure.registration; -using chocolatey.infrastructure.app.services; namespace chocolatey.infrastructure.app.nuget {