Skip to content

Commit

Permalink
(chocolatey#857) Load Chocolatey executable assembly
Browse files Browse the repository at this point in the history
This commit makes changes to the assembly loader to
load the installed Chocolatey executable as an
assembly instead of the statically referenced
DLL file
  • Loading branch information
AdmiringWorm committed Jun 1, 2021
1 parent 0256e2f commit 8cd59ad
Show file tree
Hide file tree
Showing 9 changed files with 481 additions and 281 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
</Reference>
<Reference Include="chocolatey, Version=0.10.15.0, Culture=neutral, PublicKeyToken=79d02ea9cad655eb, processorArchitecture=MSIL">
<HintPath>..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="ControlzEx, Version=4.0.0.0, Culture=neutral, PublicKeyToken=69f1c32f803d307e, processorArchitecture=MSIL">
<HintPath>..\packages\ControlzEx.4.4.0\lib\net462\ControlzEx.dll</HintPath>
Expand Down
6 changes: 6 additions & 0 deletions Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
</Reference>
<Reference Include="chocolatey, Version=0.10.15.0, Culture=neutral, PublicKeyToken=79d02ea9cad655eb, processorArchitecture=MSIL">
<HintPath>..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="LiteDB, Version=5.0.5.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
<HintPath>..\packages\LiteDB.5.0.5\lib\net45\LiteDB.dll</HintPath>
Expand Down Expand Up @@ -187,6 +189,7 @@
<Compile Include="Services\IVersionService.cs" />
<Compile Include="Services\LiteDBFileStorageService.cs" />
<Compile Include="Services\VersionService.cs" />
<Compile Include="Startup\AssemblyResolver.cs" />
<Compile Include="Startup\AutoFacConfiguration.cs" />
<Compile Include="Utilities\DefaultsExtensions.cs" />
<Compile Include="Utilities\LogSetup.cs" />
Expand Down Expand Up @@ -222,5 +225,8 @@
<Analyzer Include="..\packages\StyleCop.Analyzers.1.0.2\analyzers\dotnet\cs\StyleCop.Analyzers.CodeFixes.dll" />
<Analyzer Include="..\packages\StyleCop.Analyzers.1.0.2\analyzers\dotnet\cs\StyleCop.Analyzers.dll" />
</ItemGroup>
<ItemGroup>
<Folder Include="Startup\Adapters\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
121 changes: 121 additions & 0 deletions Source/ChocolateyGui.Common/Startup/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// <copyright file="AssemblyResolver.cs" company="Chocolatey">
// Copyright 2017 - Present Chocolatey Software, LLC
// Copyright 2014 - 2017 Rob Reynolds, the maintainers of Chocolatey, and RealDimensions Software, LLC
// </copyright>

using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using Serilog;

namespace ChocolateyGui.Common.Startup
{
public class AssemblyResolver
{
private const int LOCKRESOLUTIONTIMEOUTSECONDS = 5;
private static readonly object _lockObject = new object();

public static Assembly ResolveOrReloadChocolateyAssembly(string assemblyFileLocation)
{
return ResolveOrLoadAssemblyInternal(
"chocolatey",
string.Empty,
assemblyFileLocation,
(assembly) =>
{
var assemblyName = assembly.GetName();

return string.Equals(assemblyName.Name, "chocolatey", StringComparison.OrdinalIgnoreCase) &&
assemblyName.Version != new Version(0, 10, 15, 0); // TODO: Should maybe be created automatically
});
}

/// <summary>
/// Resolves or loads an assembly. If an assembly is already loaded, no need to reload it.
/// </summary>
/// <param name="assemblySimpleName">Simple Name of the assembly, such as "chocolatey"</param>
/// <param name="publicKeyToken">The public key token.</param>
/// <param name="assemblyFileLocation">The assembly file location. Typically the path to the DLL on disk.</param>
/// <returns>An assembly</returns>
/// <exception cref="Exception">Unable to enter synchronized code to determine assembly loading</exception>
public static Assembly ResolveOrLoadAssembly(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation)
{
return ResolveOrLoadAssemblyInternal(
assemblySimpleName,
publicKeyToken,
assemblyFileLocation,
(assembly) => string.Equals(assembly.GetName().Name, assemblySimpleName, StringComparison.OrdinalIgnoreCase));
}

public static bool IsPublicKeyToken(AssemblyName assemblyName, string expectedKeyToken)
{
var publicKey = GetPublicKeyToken(assemblyName);

return string.Equals(publicKey, expectedKeyToken, StringComparison.OrdinalIgnoreCase);
}

public static string GetPublicKeyToken(AssemblyName assemblyName)
{
if (assemblyName == null)
{
return string.Empty;
}

var publicKeyToken = assemblyName.GetPublicKeyToken();

if (publicKeyToken == null || publicKeyToken.Length == 0)
{
return string.Empty;
}

return publicKeyToken.Select(x => x.ToString("x2")).Aggregate((x, y) => x + y);
}

private static Assembly ResolveOrLoadAssemblyInternal(string assemblySimpleName, string publicKeyToken, string assemblyFileLocation, Func<Assembly, bool> assemblyPredicate)
{
var lockTaken = false;
try
{
Monitor.TryEnter(_lockObject, TimeSpan.FromSeconds(LOCKRESOLUTIONTIMEOUTSECONDS), ref lockTaken);
}
catch (Exception)
{
throw new Exception("Unable to enter synchronized code to determine assembly loading");
}

Assembly resolvedAssembly = null;
if (lockTaken)
{
try
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(assemblyPredicate))
{
if (string.IsNullOrWhiteSpace(publicKeyToken) || string.Equals(GetPublicKeyToken(assembly.GetName()), publicKeyToken, StringComparison.OrdinalIgnoreCase))
{
Log.Debug("Returning loaded assembly type for '{0}'", assemblySimpleName);

resolvedAssembly = assembly;
break;
}
}

if (resolvedAssembly == null)
{
Log.Debug("Loading up '{0}' assembly type from '{1}'", assemblySimpleName, assemblyFileLocation);

// Reading the raw bytes and calling 'Load' causes an exception, as such we use LoadFrom instead.
resolvedAssembly = Assembly.LoadFrom(assemblyFileLocation);
}
}
finally
{
Monitor.Pulse(_lockObject);
Monitor.Exit(_lockObject);
}
}

return resolvedAssembly;
}
}
}
49 changes: 34 additions & 15 deletions Source/ChocolateyGui/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.IO;
using System.Reflection;
using System.Windows;
using Autofac;
using chocolatey;
using chocolatey.infrastructure.registration;
using ChocolateyGui.Common.Enums;
using ChocolateyGui.Common.Services;
using ChocolateyGui.Common.Startup;
using ChocolateyGui.Common.Windows;
using ChocolateyGui.Common.Windows.Theming;

Expand All @@ -32,30 +32,49 @@ public App()
{
var requestedAssembly = new AssemblyName(args.Name);

try
{
if (string.Equals(requestedAssembly.Name, "chocolatey", StringComparison.OrdinalIgnoreCase))
{
var installDir = Environment.GetEnvironmentVariable("ChocolateyInstall");
if (string.IsNullOrEmpty(installDir))
{
var rootDrive = Path.GetPathRoot(Assembly.GetExecutingAssembly().Location);
if (string.IsNullOrEmpty(rootDrive))
{
return null; // TODO: Maybe return the chocolatey.dll file instead?
}

installDir = Path.Combine(rootDrive, "ProgramData", "chocolatey");
}

var assemblyLocation = Path.Combine(installDir, "choco.exe");

return AssemblyResolver.ResolveOrLoadAssembly("choco", string.Empty, assemblyLocation);
}

#if FORCE_CHOCOLATEY_OFFICIAL_KEY
var chocolateyGuiPublicKey = Bootstrapper.OfficialChocolateyPublicKey;
#else
var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey;
var chocolateyGuiPublicKey = Bootstrapper.UnofficialChocolateyPublicKey;
#endif

try
{
if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey)
&& requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonAssemblySimpleName))
if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey)
&& string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonAssemblySimpleName, StringComparison.OrdinalIgnoreCase))
{
return AssemblyResolution.resolve_or_load_assembly(
return AssemblyResolver.ResolveOrLoadAssembly(
Bootstrapper.ChocolateyGuiCommonAssemblySimpleName,
requestedAssembly.get_public_key_token(),
Bootstrapper.ChocolateyGuiCommonAssemblyLocation).UnderlyingType;
AssemblyResolver.GetPublicKeyToken(requestedAssembly),
Bootstrapper.ChocolateyGuiCommonAssemblyLocation);
}

if (requestedAssembly.get_public_key_token().is_equal_to(chocolateyGuiPublicKey)
&& requestedAssembly.Name.is_equal_to(Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName))
if (AssemblyResolver.IsPublicKeyToken(requestedAssembly, chocolateyGuiPublicKey)
&& string.Equals(requestedAssembly.Name, Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName, StringComparison.OrdinalIgnoreCase))
{
return AssemblyResolution.resolve_or_load_assembly(
return AssemblyResolver.ResolveOrLoadAssembly(
Bootstrapper.ChocolateyGuiCommonWindowsAssemblySimpleName,
requestedAssembly.get_public_key_token(),
Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation).UnderlyingType;
AssemblyResolver.GetPublicKeyToken(requestedAssembly),
Bootstrapper.ChocolateyGuiCommonWindowsAssemblyLocation);
}
}
catch (Exception ex)
Expand Down
5 changes: 5 additions & 0 deletions Source/ChocolateyGui/ChocolateyGui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@
<CodeAnalysisRuleSet>..\ChocolateyGuiRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=4.6.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.4.6.1\lib\net45\Autofac.dll</HintPath>
Expand All @@ -117,6 +120,8 @@
</Reference>
<Reference Include="chocolatey, Version=0.10.15.0, Culture=neutral, PublicKeyToken=79d02ea9cad655eb, processorArchitecture=MSIL">
<HintPath>..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="ControlzEx, Version=4.0.0.0, Culture=neutral, PublicKeyToken=69f1c32f803d307e, processorArchitecture=MSIL">
<HintPath>..\packages\ControlzEx.4.4.0\lib\net462\ControlzEx.dll</HintPath>
Expand Down
6 changes: 5 additions & 1 deletion Source/ChocolateyGuiCli/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// --------------------------------------------------------------------------------------------------------------------

using System.IO;
using System.Reflection;
using Autofac;
using chocolatey.infrastructure.filesystem;
using ChocolateyGui.Common;
Expand All @@ -21,7 +22,10 @@ public static class Bootstrapper
private static readonly IFileSystem _fileSystem = new DotNetFileSystem();

#pragma warning disable SA1202
public static readonly string ChocolateyGuiInstallLocation = _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path());

// Due to an unknown reason, we can not use chocolateys own get_current_assembly() function here,
// as it will be returning the path to the choco.exe executable instead.
public static readonly string ChocolateyGuiInstallLocation = _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty));
public static readonly string ChocolateyInstallEnvironmentVariableName = "ChocolateyInstall";
public static readonly string ChocolateyInstallLocation = System.Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path());
public static readonly string LicensedGuiAssemblyLocation = _fileSystem.combine_paths(ChocolateyInstallLocation, "extensions", "chocolateygui", "chocolateygui.licensed.dll");
Expand Down
5 changes: 4 additions & 1 deletion Source/ChocolateyGuiCli/ChocolateyGuiCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
</Reference>
<Reference Include="chocolatey, Version=0.10.15.0, Culture=neutral, PublicKeyToken=79d02ea9cad655eb, processorArchitecture=MSIL">
<HintPath>..\packages\chocolatey.lib.0.10.15\lib\chocolatey.dll</HintPath>
<SpecificVersion>False</SpecificVersion>
<Private>False</Private>
</Reference>
<Reference Include="LiteDB, Version=5.0.5.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
<HintPath>..\packages\LiteDB.5.0.5\lib\net45\LiteDB.dll</HintPath>
Expand Down Expand Up @@ -91,8 +93,9 @@
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Bootstrapper.cs" />
<Compile Include="Startup\ChocolateyGuiCliModule.cs" />
<Compile Include="Program.cs" />
<Compile Include="Startup\ChocolateyGuiCliModule.cs" />
<Compile Include="Runner.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading

0 comments on commit 8cd59ad

Please sign in to comment.