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

These changes are necessary to ensure that there will
be minimal incompatibilities between running Chocolatey CLI
and Chocolatey GUI for both FOSS and Licensed users.
  • Loading branch information
AdmiringWorm committed Jun 2, 2021
1 parent f2615ab commit 232b47a
Show file tree
Hide file tree
Showing 9 changed files with 465 additions and 282 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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\net45\ControlzEx.dll</HintPath>
Expand Down
4 changes: 4 additions & 0 deletions Source/ChocolateyGui.Common/ChocolateyGui.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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 @@ -185,6 +187,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 @@ -219,6 +222,7 @@
</ItemGroup>
<ItemGroup>
<SourceRoot Include="$(MSBuildThisFileDirectory)/../" />
<Folder Include="Startup\Adapters\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
106 changes: 106 additions & 0 deletions Source/ChocolateyGui.Common/Startup/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// <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();

/// <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 DoesPublicKeyTokenMatch(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;
}
}
}
51 changes: 35 additions & 16 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;
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.DoesPublicKeyTokenMatch(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.DoesPublicKeyTokenMatch(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\net45\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 @@ -56,6 +56,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 @@ -90,8 +92,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 232b47a

Please sign in to comment.