Skip to content

Commit 076a8f3

Browse files
authored
Merge pull request #46 from santisq/45-assembly-compatibility-vmwarevimautomationcore
Adds ALC support to the Project
2 parents fe43c63 + 2080c26 commit 076a8f3

22 files changed

Lines changed: 223 additions & 46 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 07/02/2025
4+
5+
- Added `AssemblyLoadContext` support for PowerShell 7 (.NET 8.0) to resolve DLL hell by isolating module dependencies. No support for PowerShell 5.1 (.NET Framework) due to lack of `AssemblyLoadContext` in that runtime.
6+
37
## 06/23/2025
48

59
- Added commands supporting several algorithms to compress and decompress strings:

module/PSCompression.psd1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
@{
1010
# Script module or binary module file associated with this manifest.
11-
RootModule = 'bin/netstandard2.0/PSCompression.dll'
11+
RootModule = 'PSCompression.psm1'
1212

1313
# Version number of this module.
14-
ModuleVersion = '3.0.0'
14+
ModuleVersion = '3.0.1'
1515

1616
# Supported PSEditions
1717
# CompatiblePSEditions = @()

module/PSCompression.psm1

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using namespace System.IO
2+
using namespace System.Reflection
3+
using namespace PSCompression.Shared
4+
5+
$moduleName = [Path]::GetFileNameWithoutExtension($PSCommandPath)
6+
$frame = 'net8.0'
7+
8+
if (-not $IsCoreCLR) {
9+
$frame = 'netstandard2.0'
10+
$asm = [Path]::Combine($PSScriptRoot, 'bin', $frame, "${moduleName}.dll")
11+
Import-Module -Name $asm -ErrorAction Stop -PassThru
12+
return
13+
}
14+
15+
$context = [Path]::Combine($PSScriptRoot, 'bin', $frame, "${moduleName}.Shared.dll")
16+
$isReload = $true
17+
18+
if (-not ("${moduleName}.Shared.LoadContext" -as [type])) {
19+
$isReload = $false
20+
Add-Type -Path $context
21+
}
22+
23+
$mainModule = [LoadContext]::Initialize()
24+
$innerMod = Import-Module -Assembly $mainModule -PassThru:$isReload
25+
26+
if ($innerMod) {
27+
$addExportedCmdlet = [psmoduleinfo].GetMethod(
28+
'AddExportedCmdlet', [BindingFlags] 'Instance, NonPublic')
29+
30+
foreach ($cmd in $innerMod.ExportedCmdlets.Values) {
31+
$addExportedCmdlet.Invoke($ExecutionContext.SessionState.Module, @($cmd))
32+
}
33+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Runtime.Loader;
5+
6+
namespace PSCompression.Shared;
7+
8+
[ExcludeFromCodeCoverage]
9+
public sealed class LoadContext : AssemblyLoadContext
10+
{
11+
private static LoadContext? _instance;
12+
13+
private readonly static object s_sync = new();
14+
15+
private readonly Assembly _thisAssembly;
16+
17+
private readonly AssemblyName _thisAssemblyName;
18+
19+
private readonly Assembly _moduleAssembly;
20+
21+
private readonly string _assemblyDir;
22+
23+
private LoadContext(string mainModulePathAssemblyPath)
24+
: base(name: "PSCompression", isCollectible: false)
25+
{
26+
_assemblyDir = Path.GetDirectoryName(mainModulePathAssemblyPath) ?? "";
27+
_thisAssembly = typeof(LoadContext).Assembly;
28+
_thisAssemblyName = _thisAssembly.GetName();
29+
_moduleAssembly = LoadFromAssemblyPath(mainModulePathAssemblyPath);
30+
}
31+
32+
protected override Assembly? Load(AssemblyName assemblyName)
33+
{
34+
if (AssemblyName.ReferenceMatchesDefinition(_thisAssemblyName, assemblyName))
35+
{
36+
return _thisAssembly;
37+
}
38+
39+
string asmPath = Path.Join(_assemblyDir, $"{assemblyName.Name}.dll");
40+
if (File.Exists(asmPath))
41+
{
42+
return LoadFromAssemblyPath(asmPath);
43+
}
44+
45+
return null;
46+
}
47+
48+
public static Assembly Initialize()
49+
{
50+
LoadContext? instance = _instance;
51+
if (instance is not null)
52+
{
53+
return instance._moduleAssembly;
54+
}
55+
56+
lock (s_sync)
57+
{
58+
if (_instance is not null)
59+
{
60+
return _instance._moduleAssembly;
61+
}
62+
63+
string assemblyPath = typeof(LoadContext).Assembly.Location;
64+
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
65+
string moduleName = assemblyName[..^7];
66+
string modulePath = Path.Combine(
67+
Path.GetDirectoryName(assemblyPath)!,
68+
$"{moduleName}.dll");
69+
70+
return new LoadContext(modulePath)._moduleAssembly;
71+
}
72+
}
73+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net8.0</TargetFrameworks>
5+
<Nullable>enable</Nullable>
6+
<AssemblyName>PSCompression.Shared</AssemblyName>
7+
<LangVersion>latest</LangVersion>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<InternalsVisibleTo Include="PSCompression" />
12+
</ItemGroup>
13+
14+
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Reflection;
5+
using System.Runtime.Loader;
6+
7+
namespace PSCompression.Shared;
8+
9+
[ExcludeFromCodeCoverage]
10+
internal sealed class SharedUtil
11+
{
12+
public static void AddAssemblyInfo(Type type, Dictionary<string, object> data)
13+
{
14+
Assembly asm = type.Assembly;
15+
16+
data["Assembly"] = new Dictionary<string, object?>()
17+
{
18+
["Name"] = asm.GetName().FullName,
19+
["ALC"] = AssemblyLoadContext.GetLoadContext(asm)?.Name,
20+
["Location"] = asm.Location
21+
};
22+
}
23+
}

src/PSCompression/Abstractions/EntryBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.IO;
34

45
namespace PSCompression.Abstractions;
@@ -11,6 +12,7 @@ public abstract class EntryBase(string source)
1112

1213
internal string? FormatDirectoryPath { get => _formatDirectoryPath ??= GetFormatDirectoryPath(); }
1314

15+
[MemberNotNullWhen(true, nameof(_stream))]
1416
internal bool FromStream { get => _stream is not null; }
1517

1618
public string Source { get; } = source;

src/PSCompression/Abstractions/TarEntryBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal FileSystemInfo ExtractTo(
3636
}
3737

3838
FileInfo file = new(destination);
39-
file.Directory.Create();
39+
file.Directory?.Create();
4040

4141
using FileStream destStream = File.Open(
4242
destination,

src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ protected override void BeginProcessing()
6969

7070
try
7171
{
72-
Directory.CreateDirectory(IOPath.GetDirectoryName(Destination));
72+
Directory.CreateDirectory(IOPath.GetDirectoryName(Destination)!);
7373

7474
_destination = File.Open(Destination, FileMode);
7575
_archive = CreateCompressionStream(_destination);
@@ -116,7 +116,7 @@ private void Traverse(DirectoryInfo dir, T archive)
116116
{
117117
_queue.Enqueue(dir);
118118
IEnumerable<FileSystemInfo> enumerator;
119-
int length = dir.Parent.FullName.Length + 1;
119+
int length = dir.Parent!.FullName.Length + 1;
120120

121121
while (_queue.Count > 0)
122122
{

src/PSCompression/Abstractions/ZipEntryBase.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ internal ZipArchive OpenZip(ZipArchiveMode mode) =>
9797

9898
public FileSystemInfo ExtractTo(string destination, bool overwrite)
9999
{
100-
using ZipArchive zip = _stream is not null
100+
using ZipArchive zip = FromStream
101101
? new ZipArchive(_stream, ZipArchiveMode.Read, leaveOpen: true)
102102
: ZipFile.OpenRead(Source);
103103

@@ -120,10 +120,13 @@ internal FileSystemInfo ExtractTo(
120120
}
121121

122122
FileInfo file = new(destination);
123-
file.Directory.Create();
123+
file.Directory?.Create();
124+
125+
if (zip.TryGetEntry(RelativePath, out ZipArchiveEntry? entry))
126+
{
127+
entry.ExtractToFile(destination, overwrite);
128+
}
124129

125-
ZipArchiveEntry entry = zip.GetEntry(RelativePath);
126-
entry.ExtractToFile(destination, overwrite);
127130
return file;
128131
}
129132
}

0 commit comments

Comments
 (0)