Aracılığıyla paylaş


PowerShell modülü derleme bağımlılık çakışmalarını çözme

C# dilinde ikili bir PowerShell modülü yazarken, işlevsellik sağlamak için diğer paketlere veya kitaplıklara bağımlılık almak doğaldır. Kodun yeniden kullanılması için diğer kitaplıklara bağımlılıkları almak tercih edilir. PowerShell her zaman derlemeleri aynı bağlama yükler. Bu, bir modülün bağımlılıkları zaten yüklenmiş DLL'lerle çakıştığında ve aynı PowerShell oturumunda iki farklı şekilde ilişkisiz modülün kullanılmasını önleyebileceği durumlarda sorunları gösterir.

Bu sorunla karşılaşırsanız aşağıdaki gibi bir hata iletisiyle karşılaşırsınız:

Derleme yükü çakışması hata iletisi

Bu makalede, PowerShell'de bağımlılık çakışmalarının oluşmasının bazı yolları ve bağımlılık çakışması sorunlarını azaltma yolları ele alınıyor. Modül yazarı olmasanız bile, burada kullandığınız modüllerde oluşan bağımlılık çakışmalarında size yardımcı olabilecek bazı püf noktaları vardır.

Bağımlılık çakışmaları neden oluşur?

.NET'te, aynı derlemenin iki sürümü aynı Derleme Yükleme Bağlamıyüklendiğinde bağımlılık çakışmaları oluşur. Bu terim, bu makalenin sonraki ele alınan farklı .NET platformlarında biraz farklı şeyler anlamına gelir. Bu çakışma, sürümlenmiş bağımlılıkların kullanıldığı tüm yazılımlarda oluşan yaygın bir sorundur.

Çakışma sorunları, bir projenin neredeyse hiçbir zaman kasıtlı olarak veya doğrudan aynı bağımlılığın iki sürümüne bağımlı olmamasıyla birleştirilmiştir. Bunun yerine, projenin her biri aynı bağımlılığın farklı bir sürümünü gerektiren iki veya daha fazla bağımlılığı vardır.

Örneğin, .NET uygulamanızın DuckBuilderişlevinin bazı bölümlerini gerçekleştirmek için iki bağımlılık getirdiğini ve şöyle göründüğünü söyleyebiliriz:

DuckBuilder'ın iki bağımlılığı, Newtonsoft.Json 'nin farklı sürümlerine dayanır

hem Contoso.ZipTools hem de Fabrikam.FileHelpersNewtonsoft.Jsonfarklı sürümlerine bağlı olduğundan, her bağımlılığın nasıl yüklendiğine bağlı olarak bir bağımlılık çakışması olabilir.

PowerShell'in bağımlılıklarıyla çakışma

PowerShell'de bağımlılık çakışması sorunu, PowerShell'in kendi bağımlılıkları aynı paylaşılan bağlama yüklendiğinden büyütülmüş olur. Bu, PowerShell altyapısının ve yüklenen tüm PowerShell modüllerinin çakışan bağımlılıklara sahip olmaması gerektiği anlamına gelir. Bunun klasik bir örneği Newtonsoft.Json:

FictionalTools modülü, PowerShell daha yeni Newtonsoft.Json sürümüne bağlıdır

Bu örnekte, FictionalTools modülü Newtonsoft.Json12.0.3sürümüne bağlıdır. Bu, Newtonsoft.Json'nin örnek PowerShell'de sunulan 11.0.2 daha yeni bir sürümüdür.

Not

Bu bir örnektir. PowerShell 7.0 şu anda Newtonsoft.Json 12.0.3ile birlikte sunulur. PowerShell'in daha yeni sürümleri, Newtonsoft.Json'in daha yeni sürümlerine sahiptir.

Modül derlemenin daha yeni bir sürümüne bağlı olduğundan, PowerShell'in zaten yüklediği sürümü kabul etmez. Ancak PowerShell derlemenin bir sürümünü yüklediğinden modül, geleneksel yük mekanizmasını kullanarak kendi sürümünü yükleyemez.

Başka bir modülün bağımlılıklarıyla çakışma

PowerShell'deki bir diğer yaygın senaryo, bir derlemenin bir sürümüne bağlı olan bir modülün yüklenmesi ve daha sonra bu derlemenin farklı bir sürümüne bağlı olan başka bir modülün yüklenmesidir.

Bu genellikle aşağıdaki gibi görünür:

İki PowerShell modülü, Microsoft.Extensions.Logging bağımlılığının farklı sürümlerini gerektirir

Bu durumda, FictionalTools modülü, Microsoft.Extensions.Logging modülünden daha yeni bir FilesystemManager sürümü gerektirir.

Bu modüllerin bağımlılık derlemelerini kök modül derlemesi ile aynı dizine yerleştirerek bağımlılıklarını yükledikleri düşünün. Bu, .NET'in bunları ada göre örtük olarak yüklemesine olanak tanır. PowerShell 7.0 çalıştırıyorsak (.NET Core 3.1'in üzerinde), FictionalToolsyükleyip çalıştırabilir, ardından FilesystemManager sorunsuz bir şekilde yükleyip çalıştırabiliriz. Ancak yeni bir oturumda FilesystemManageryükleyip çalıştırırsak FictionalToolsyüklersek, yüklenenden daha yeni bir FileLoadException sürümü gerektirdiğinden FictionalTools komutundan bir Microsoft.Extensions.Logging elde ederiz. FictionalTools aynı ada sahip bir derleme zaten yüklendiğinden gereken sürümü yükleyemiyor.

PowerShell ve .NET

PowerShell, derleme bağımlılıklarını çözmek ve yüklemekle sorumlu olan .NET platformunda çalışır. Bağımlılık çakışmalarını anlamak için .NET'in burada nasıl çalıştığını anlamamız gerekir.

PowerShell'in farklı sürümlerinin farklı .NET uygulamalarında çalışması gerçeğiyle de yüzleşmemiz gerekir. Genel olarak, PowerShell 5.1 ve altı .NET Framework üzerinde, PowerShell 6 ve üzeri ise .NET Core üzerinde çalışır. Bu iki .NET uygulaması, derlemeleri farklı şekilde yükler ve işler. Bu, bağımlılık çakışmalarını çözmenin temel alınan .NET platformuna bağlı olarak değişebileceği anlamına gelir.

Derleme Yükleme Bağlamları

.NET'te Derleme Yük Bağlamı (ALC), derlemelerin yüklendiği bir çalışma zamanı ad alanıdır. Derlemelerin adları benzersiz olmalıdır. Bu kavram, derlemelerin her ALC'de ada göre benzersiz bir şekilde çözümlenmesine olanak tanır.

.NET'te derleme başvurusu yükleniyor

Derleme yükleme semantiği hem .NET uygulamasına (.NET Core ve .NET Framework) hem de belirli bir derlemeyi yüklemek için kullanılan .NET API'sine bağlıdır. Burada ayrıntılara girmek yerine, Daha fazla okuma bölümünde .NET derleme yüklemesinin her .NET uygulamasında nasıl çalıştığı hakkında ayrıntılı bilgi veren bağlantılar vardır.

Bu makalede aşağıdaki mekanizmalara başvuracağız:

  • .NET örtük olarak .NET kodundaki statik bir derleme başvurusundan ada göre bir derleme yüklemeye çalıştığında örtük derleme yükleme (etkili bir şekilde Assembly.Load(AssemblyName)).
  • Assembly.LoadFrom(), yüklenen DLL'nin bağımlılıklarını çözümlemek için işleyiciler ekleyen eklenti odaklı bir yükleme API'si. Bu yöntem bağımlılıkları istediğimiz gibi çözemeyebilir.
  • Assembly.LoadFile(), yalnızca istenen derlemeyi yüklemeyi amaçlayan ve bağımlılıkları işlemeyen temel bir yükleme API'si.

.NET Framework ile .NET Core arasındaki farklar

Bu API'lerin çalışma şekli .NET Core ile .NET Framework arasında küçük değişiklikler olduğundan,dahil edilen bağlantılarını okumaya değer. Daha da önemlisi, .NET Framework ile .NET Core arasında Derleme Yükü Bağlamları ve diğer derleme çözümleme mekanizmaları değişmiştir.

Özellikle, .NET Framework aşağıdaki özelliklere sahiptir:

  • Makine genelinde derleme çözümlemesi için Genel Bütünleştirilmiş Kod Önbelleği
  • Derleme yalıtımı için işlem içi korumalı alanlar gibi çalışan, ancak aynı zamanda ile bir serileştirme katmanı sunan Uygulama Etki Alanları
  • Her biri kendi davranışlarına sahip sabit bir derleme yükü bağlamı kümesine sahip sınırlı bir derleme yükü bağlam modeli:
    • Derlemelerin varsayılan olarak yüklendiği varsayılan yük bağlamı
    • Derlemeleri çalışma zamanında el ile yüklemek için yükleme bağlamı
    • Derlemeleri çalıştırmadan meta verilerini okumak üzere güvenli bir şekilde yüklemek için yalnızca yansıma bağlamı
    • Bütünleştirilmiş kodların Assembly.LoadFile(string path) ve Assembly.Load(byte[] asmBytes) içinde yaşadığı gizemli boşluk

Daha fazla bilgi için bkz. Derleme Yüklemeiçin En İyi Yöntemler.

.NET Core (ve .NET 5+), bu karmaşıklığın yerini daha basit bir modelle almıştır:

  • Genel Bütünleştirilmiş Kod Önbelleği yok. Uygulamalar kendi bağımlılıklarını getirir. Bu, uygulamalarda bağımlılık çözümlemesi için bir dış faktörü kaldırarak bağımlılık çözümlemesini daha yeniden üretilebilir hale getirir. Eklenti konağı olarak PowerShell, modüller için bunu biraz karmaşıklaştırır. $PSHOME bağımlılıkları tüm modüllerle paylaşılır.
  • Yalnızca bir Uygulama Etki Alanı ve yenilerini oluşturma olanağı yoktur. Uygulama Etki Alanı kavramı .NET işleminin genel durumu olması için .NET'te korunur.
  • Yeni, genişletilebilir Derleme Yük Bağlamı (ALC) modeli. Derleme çözümlemesi, yeni bir ALC'ye yerleştirilerek ad alanına eklenebilir. .NET işlemleri, tüm derlemelerin yüklendiği tek bir varsayılan ALC ile başlar (Assembly.LoadFile(string) ve Assembly.Load(byte[])ile yüklenenler hariç). Ancak işlem kendi yükleme mantığıyla kendi özel ALC'lerini oluşturabilir ve tanımlayabilir. Bir derleme yüklendiğinde, içine yüklendiği ilk ALC bağımlılıklarını çözmekle sorumludur. Bu, güçlü .NET eklentisi yükleme mekanizmaları uygulamak için fırsatlar oluşturur.

Her iki uygulamada da derlemeler lazily yüklenir. Bu, türlerini gerektiren bir yöntem ilk kez çalıştırıldığında yüklendiği anlamına gelir.

Örneğin, aynı kodun farklı zamanlarda bir bağımlılığı yükleyen iki sürümü aşağıda verilmiştır.

İlki, Program.GetRange() çağrıldığında her zaman bağımlılığını yükler çünkü bağımlılık başvurusu yönteminde sözcük temelli olarak bulunur:

using Dependency.Library;

public static class Program
{
    public static List<int> GetRange(int limit)
    {
        var list = new List<int>();
        for (int i = 0; i < limit; i++)
        {
            if (i >= 20)
            {
                // Dependency.Library will be loaded when GetRange is run
                // because the dependency call occurs directly within the method
                DependencyApi.Use();
            }

            list.Add(i);
        }
        return list;
    }
}

İkinci, bağımlılığını yalnızca limit parametresi 20 veya daha fazlaysa yükler. Bunun nedeni, bir yöntem üzerinden iç dolaylılık olmasıdır:

using Dependency.Library;

public static class Program
{
    public static List<int> GetNumbers(int limit)
    {
        var list = new List<int>();
        for (int i = 0; i < limit; i++)
        {
            if (i >= 20)
            {
                // Dependency.Library is only referenced within
                // the UseDependencyApi() method,
                // so will only be loaded when limit >= 20
                UseDependencyApi();
            }

            list.Add(i);
        }
        return list;
    }

    private static void UseDependencyApi()
    {
        // Once UseDependencyApi() is called, Dependency.Library is loaded
        DependencyApi.Use();
    }
}

Bellek ve dosya sistemi G/Ç'sini en aza indirgediğinden ve kaynakları daha verimli kullandığından bu iyi bir uygulamadır. Bunun talihsiz bir yan etkisi, derlemeyi yüklemeye çalışan kod yoluna ulaşana kadar derlemenin yüklenememesidir.

Ayrıca derleme yükü çakışmaları için bir zamanlama koşulu da oluşturabilir. Aynı programın iki bölümü aynı derlemenin farklı sürümlerini yüklemeyi denerse, yüklenen sürüm önce hangi kod yolunun çalıştırıldığına bağlıdır.

PowerShell için bu, aşağıdaki faktörlerin bir derleme yükü çakışmasını etkileyebileceği anlamına gelir:

  • İlk olarak hangi modül yüklendi?
  • Bağımlılık kitaplığını kullanan kod yolu çalıştırıldı mı?
  • PowerShell başlangıçta veya yalnızca belirli kod yolları altında çakışan bir bağımlılık mı yükler?

Hızlı düzeltmeler ve bunların sınırlamaları

Bazı durumlarda, modülünüzde küçük ayarlamalar yapmak ve işleri en az çabayla düzeltmek mümkündür. Ancak bu çözümler genellikle uyarılarla birlikte gelir. Bunlar modülünüze uygulanabilir ancak her modül için çalışmaz.

Bağımlılık sürümünüzü değiştirme

Bağımlılık çakışmalarını önlemenin en basit yolu bir bağımlılık üzerinde anlaşmaktır. Bu, aşağıdaki durumlarda mümkün olabilir:

  • Çakışmanız modülünüzün doğrudan bağımlılığıyla ve sürümü siz denetlersiniz.
  • Çakışmanız dolaylı bir bağımlılıkla ilişkilidir, ancak doğrudan bağımlılıklarınızı işlenebilir bir dolaylı bağımlılık sürümü kullanacak şekilde yapılandırabilirsiniz.
  • Çakışan sürümü biliyorsunuz ve değişmediğinden emin olabilirsiniz.

Newtonsoft.Json paketi bu son senaryoya iyi bir örnektir. Bu, PowerShell 6 ve üzeri bir bağımlılığıdır ve Windows PowerShell'de kullanılmaz. Sürüm oluşturma çakışmalarını çözmenin basit bir yolu, hedeflemek istediğiniz PowerShell sürümlerinde Newtonsoft.Json en düşük sürümünü hedeflemektir.

Örneğin, PowerShell 6.2.6 ve PowerShell 7.0.2 şu anda Newtonsoft.Json sürüm 12.0.3 kullanıyor. Windows PowerShell, PowerShell 6 ve PowerShell 7'yi hedefleyen bir modül oluşturmak için Newtonsoft.Json 12.0.3 bağımlılık olarak hedefleyip bunu yerleşik modülünüze dahil edebilirsiniz. Modül PowerShell 6 veya 7'ye yüklendiğinde, PowerShell'in kendi Newtonsoft.Json derlemesi zaten yüklenir. Modülünüz için gereken sürüm olduğundan çözüm başarılı olur. Windows PowerShell'de derleme PowerShell'de henüz mevcut olmadığından bunun yerine modül klasörünüzden yüklenir.

Genel olarak, Microsoft.PowerShell.Sdk veya System.Management.Automationgibi somut bir PowerShell paketini hedeflerken NuGet'in gereken doğru bağımlılık sürümlerini çözümleyebilmesi gerekir. Birden çok çerçeveyi hedefleme veya PowerShellStandard.Libraryarasında seçim yapmanız gerektiğinden hem Windows PowerShell'i hem de PowerShell 6+'yı hedeflemek daha zor hale gelir.

Ortak bir bağımlılık sürümüne sabitlemenin çalışmadığı durumlar şunlardır:

  • Çakışma dolaylı bir bağımlılıkla yapılır ve bağımlılıklarınızdan hiçbiri ortak bir sürümü kullanacak şekilde yapılandırılamaz.
  • Diğer bağımlılık sürümü büyük olasılıkla sık sık değişir, bu nedenle ortak bir sürüme yerleşmek yalnızca kısa vadeli bir düzeltmedir.

İşlem dışı bağımlılığı kullanma

Bu çözüm, modül kullanıcıları için modül yazarlarından daha fazladır. Bu, mevcut bağımlılık çakışması nedeniyle çalışmayacak bir modülle karşılaşıldığında kullanılacak bir çözümdür.

Bağımlılık çakışmaları, aynı derlemenin iki sürümü aynı .NET işlemine yüklendiğinden oluşur. İşlevselliği birlikte kullanmaya devam ettiğiniz sürece basit bir çözüm, bunları farklı işlemlere yüklemektir.

PowerShell'de bunu başarmanın birkaç yolu vardır:

  • PowerShell'i alt işlem olarak çağırma

    Geçerli işlemin dışında bir PowerShell komutu çalıştırmak için, komut çağrısıyla doğrudan yeni bir PowerShell işlemi başlatın:

    pwsh -c 'Invoke-ConflictingCommand'
    

    Buradaki ana sınırlama, sonucu yeniden yapılandırmanın diğer seçeneklere göre daha karmaşık veya daha fazla hataya neden olabileceğidir.

  • PowerShell iş sistemi

    PowerShell iş sistemi ayrıca komutları yeni bir PowerShell işlemine göndererek ve sonuçları döndürerek işlem dışı komutlar çalıştırır:

    $result = Start-Job { Invoke-ConflictingCommand } | Receive-Job -Wait
    

    Bu durumda, tüm değişkenlerin ve durumların doğru geçirildiğinden emin olmanız yeterlidir.

    küçük komutlar çalıştırılırken iş sistemi de biraz hantal olabilir.

  • PowerShell uzaktan iletişim

    Kullanılabilir olduğunda PowerShell uzaktan iletişim, komutları işlem dışı çalıştırmanın kullanışlı bir yolu olabilir. Uzaktan iletişim sayesinde yeni bir işlemde yeni bir PSSession oluşturabilir, PowerShell uzaktan iletişimiyle komutlarını çağırabilir ve ardından sonuçları çakışan bağımlılıkları içeren diğer modüllerle yerel olarak kullanabilirsiniz.

    Bir örnek şöyle görünebilir:

    # Create a local PowerShell session
    # where the module with conflicting assemblies will be loaded
    $s = New-PSSession
    
    # Import the module with the conflicting dependency via remoting,
    # exposing the commands locally
    Import-Module -PSSession $s -Name ConflictingModule
    
    # Run a command from the module with the conflicting dependencies
    Invoke-ConflictingCommand
    
  • Windows PowerShell'e örtük uzaktan iletişim

    PowerShell 7'deki bir diğer seçenek de -UseWindowsPowerShellüzerinde Import-Module bayrağını kullanmaktır. Bu, modülü yerel uzaktan iletişim oturumu aracılığıyla Windows PowerShell'e aktarır:

    Import-Module -Name ConflictingModule -UseWindowsPowerShell
    

    Modüllerin Windows PowerShell ile uyumlu olmayabileceğini veya farklı şekilde çalışabileceğini unutmayın.

İşlem dışı çağırma kullanılmamalıdır

Modül yazarı olarak, işlem dışı komut çağrısının modülde pişirilmesi zordur ve sorunlara neden olan uç durumlar olabilir. Özellikle uzaktan iletişim ve işler modülünüzün çalışması gereken tüm ortamlarda kullanılamayabilir. Ancak, uygulamayı işlem dışına taşıma ve PowerShell modülünün daha ince bir istemci olmasına izin verme genel ilkesi hala uygulanabilir olabilir.

Modül kullanıcısı olarak, işlem dışı çağırmanın çalışmadığı durumlar vardır:

  • PowerShell uzaktan iletişim özelliği, kullanma ayrıcalığınız olmadığı veya etkinleştirilmediği için kullanılamaz durumda olduğunda.
  • Çıkıştan bir yönteme veya başka bir komuta giriş olarak belirli bir .NET türü gerektiğinde. PowerShell uzaktan iletişim üzerinden çalışan komutlar, kesin olarak türü belirlenmiş .NET nesneleri yerine seri durumdan çıkarılmış nesneler yayar. Bu, yöntem çağrılarının ve kesin olarak yazılan API'lerin uzaktan iletişim üzerinden içeri aktarılan komutların çıkışıyla çalışmaması anlamına gelir.

Daha sağlam çözümler

Önceki çözümlerin hepsinde çalışmayan senaryolar ve modüller vardı. Ancak, doğru şekilde uygulanması nispeten basit olma özelliğine de sahiptirler. Aşağıdaki çözümler daha sağlamdır, ancak doğru şekilde uygulamak için daha fazla çaba gerektirir ve dikkatlice yazılmazsa küçük hatalar ortaya çıkar.

.NET Core Derleme Yük Bağlamları aracılığıyla yükleme

Derleme Yük Bağlamları (ALC) .NET Core 1.0'da, aynı derlemenin birden çok sürümünü aynı çalışma zamanına yükleme gereksinimini özellikle ele almak için kullanıma sunulmuştur.

.NET içinde, bir derlemenin çakışan sürümlerini yükleme sorununa en güçlü çözümü sunar. Ancak özel ALC'ler .NET Framework'te kullanılamaz. Bu, bu çözümün yalnızca PowerShell 6 ve üzerinde çalıştığı anlamına gelir.

Şu anda PowerShell'de bağımlılık yalıtımı için ALC kullanmanın en iyi örneği, Visual Studio Code için PowerShell uzantısının dil sunucusu olan PowerShell Düzenleyici Hizmetleri'dir. ALC, PowerShell Düzenleyici Hizmetleri'nin kendi bağımlılıklarının PowerShell modüllerindeki bağımlılıklarla çakışmasını önlemek için kullanılır.

Bir ALC ile modül bağımlılık yalıtımı uygulamak kavramsal olarak zordur, ancak en düşük örnekle çalışacağız. Yalnızca PowerShell 7'de çalışması amaçlanan basit bir modüle sahip olduğumuzu düşünün. Kaynak kod aşağıdaki gibi düzenlenir:

+ AlcModule.psd1
+ src/
    + TestAlcModuleCommand.cs
    + AlcModule.csproj

Cmdlet uygulaması şöyle görünür:

using Shared.Dependency;

namespace AlcModule
{
    [Cmdlet(VerbsDiagnostic.Test, "AlcModule")]
    public class TestAlcModuleCommand : Cmdlet
    {
        protected override void EndProcessing()
        {
            // Here's where our dependency gets used
            Dependency.Use();
            // Something trivial to make our cmdlet do *something*
            WriteObject("done!");
        }
    }
}

(ağır basitleştirilmiş) bildirim şöyle görünür:

@{
    Author = 'Me'
    ModuleVersion = '0.0.1'
    RootModule = 'AlcModule.dll'
    CmdletsToExport = @('Test-AlcModule')
    PowerShellVersion = '7.0'
}

csproj şöyle görünür:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Shared.Dependency" Version="1.0.0" />
    <PackageReference Include="Microsoft.PowerShell.Sdk" Version="7.0.1" PrivateAssets="all" />
  </ItemGroup>
</Project>

Bu modülü derlediğimizde, oluşturulan çıktı aşağıdaki düzene sahiptir:

AlcModule/
  + AlcModule.psd1
  + AlcModule.dll
  + Shared.Dependency.dll

Bu örnekte, problemimiz hayali çakışan bağımlılığımız olan Shared.Dependency.dll derlemesindedir. Modüle özgü sürümü kullanabilmek için bir ALC'nin arkasına koymamız gereken bağımlılık budur.

Modülü aşağıdakiler için yeniden mühendislik uygulamamız gerekir:

  • Modül bağımlılıkları yalnızca özel ALC'mize yüklenir ve PowerShell'in ALC'sine yüklenmez, bu nedenle çakışma olmaz. Ayrıca, projemize daha fazla bağımlılık eklediğimizden, yüklemeye devam etmek için sürekli olarak daha fazla kod eklemek istemiyoruz. Bunun yerine, yeniden kullanılabilir, genel bağımlılık çözümleme mantığı istiyoruz.
  • Modülü yükleme hala PowerShell'de normal şekilde çalışır. PowerShell modül sisteminin ihtiyaç duyduğu cmdlet'ler ve diğer türler PowerShell'in kendi ALC'sinde tanımlanır.

Bu iki gereksinime aracılık etmek için modülümüzü iki derlemeye ayırmamız gerekir:

  • AlcModule.Cmdlets.dll, PowerShell modül sisteminin modülümüzü doğru yüklemek için ihtiyaç duyduğu tüm türlerin tanımlarını içeren bir cmdlet derlemesi. Yani, Cmdlet temel sınıfı ve IModuleAssemblyInitializeruygulayan sınıfın tüm uygulamaları, AssemblyLoadContext.Default.Resolving için olay işleyicisini özel ALC'miz aracılığıyla AlcModule.Engine.dll düzgün bir şekilde yüklemek üzere ayarlar. PowerShell 7, diğer ALC'lere yüklenen derlemelerde tanımlanan türleri kasıtlı olarak gizlediğinden, PowerShell'e genel kullanıma açık olması amaçlanmış tüm türler de burada tanımlanmalıdır. Son olarak, özel ALC tanımımızın bu derlemede tanımlanması gerekir. Bunun ötesinde, mümkün olduğunca az kod bu derlemede yaşamalıdır.
  • Modülün gerçek uygulamasını işleyen AlcModule.Engine.dllbir altyapı derlemesi. Bunun türleri PowerShell ALC'sinde kullanılabilir, ancak başlangıçta özel ALC'miz aracılığıyla yüklenir. Bağımlılıkları yalnızca özel ALC'ye yüklenir. Bu, iki ALC arasında bir köprü haline gelir.

Bu köprü kavramını kullanarak yeni derleme durumumuz şöyle görünür:

İki ALC köprü oluşturma AlcModule.Engine.dll temsil eden Diyagramı

Varsayılan ALC'nin bağımlılık yoklama mantığının özel ALC'ye yüklenecek bağımlılıkları çözmediğinden emin olmak için modülün bu iki bölümünü farklı dizinlerde ayırmamız gerekir. Yeni modül düzeni aşağıdaki yapıya sahiptir:

AlcModule/
  AlcModule.Cmdlets.dll
  AlcModule.psd1
  Dependencies/
  | + AlcModule.Engine.dll
  | + Shared.Dependency.dll

Uygulamanın nasıl değiştiğini görmek için AlcModule.Engine.dlluygulamasıyla başlayacağız:

using Shared.Dependency;

namespace AlcModule.Engine
{
    public class AlcEngine
    {
        public static void Use()
        {
            Dependency.Use();
        }
    }
}

Bu, Shared.Dependency.dllbağımlılığı için basit bir kapsayıcıdır, ancak bunu diğer derlemedeki cmdlet'lerin PowerShell için sarmalayan işlevselliğiniz için .NET API'si olarak düşünmelisiniz.

AlcModule.Cmdlets.dll cmdlet'i şöyle görünür:

// Reference our module's Engine implementation here
using AlcModule.Engine;

namespace AlcModule.Cmdlets
{
    [Cmdlet(VerbsDiagnostic.Test, "AlcModule")]
    public class TestAlcModuleCommand : Cmdlet
    {
        protected override void EndProcessing()
        {
            AlcEngine.Use();
            WriteObject("done!");
        }
    }
}

Bu noktada, AlcModule yükleyip çalıştıracak olsaydık, varsayılan ALC çalıştırmak için yüklemeye çalıştığında bir FileNotFoundException elde ederiz. Bu iyi, çünkü varsayılan ALC gizlemek istediğimiz bağımlılıkları bulamıyor demektir.

Şimdi AlcModule.Cmdlets.dllnasıl çözümleneceğini bilmesi için AlcModule.Engine.dll kodu eklememiz gerekir. İlk olarak modülümüzün Dependencies dizininden derlemeleri çözümlemek için özel ALC'mizi tanımlamamız gerekir:

namespace AlcModule.Cmdlets
{
    internal class AlcModuleAssemblyLoadContext : AssemblyLoadContext
    {
        private readonly string _dependencyDirPath;

        public AlcModuleAssemblyLoadContext(string dependencyDirPath)
        {
            _dependencyDirPath = dependencyDirPath;
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            // We do the simple logic here of looking for an assembly of the given name
            // in the configured dependency directory.
            string assemblyPath = Path.Combine(
                _dependencyDirPath,
                $"{assemblyName.Name}.dll");

            if (File.Exists(assemblyPath))
            {
                // The ALC must use inherited methods to load assemblies.
                // Assembly.Load*() won't work here.
                return LoadFromAssemblyPath(assemblyPath);
            }

            // For other assemblies, return null to allow other resolutions to continue.
            return null;
        }
    }
}

Ardından özel ALC'mizi, Uygulama Etki Alanlarındaki Resolving olayının ALC sürümü olan varsayılan ALC'nin AssemblyResolve olayına bağlamamız gerekir. AlcModule.Engine.dll çağrıldığında EndProcessing() bulmak için bu olay tetiklenir.

namespace AlcModule.Cmdlets
{
    public class AlcModuleResolveEventHandler : IModuleAssemblyInitializer, IModuleAssemblyCleanup
    {
        // Get the path of the dependency directory.
        // In this case we find it relative to the AlcModule.Cmdlets.dll location
        private static readonly string s_dependencyDirPath = Path.GetFullPath(
            Path.Combine(
                Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                "Dependencies"));

        private static readonly AlcModuleAssemblyLoadContext s_dependencyAlc =
            new AlcModuleAssemblyLoadContext(s_dependencyDirPath);

        public void OnImport()
        {
            // Add the Resolving event handler here
            AssemblyLoadContext.Default.Resolving += ResolveAlcEngine;
        }

        public void OnRemove(PSModuleInfo psModuleInfo)
        {
            // Remove the Resolving event handler here
            AssemblyLoadContext.Default.Resolving -= ResolveAlcEngine;
        }

        private static Assembly ResolveAlcEngine(AssemblyLoadContext defaultAlc, AssemblyName assemblyToResolve)
        {
            // We only want to resolve the Alc.Engine.dll assembly here.
            // Because this will be loaded into the custom ALC,
            // all of *its* dependencies will be resolved
            // by the logic we defined for that ALC's implementation.
            //
            // Note that we are safe in our assumption that the name is enough
            // to distinguish our assembly here,
            // since it's unique to our module.
            // There should be no other AlcModule.Engine.dll on the system.
            if (!assemblyToResolve.Name.Equals("AlcModule.Engine"))
            {
                return null;
            }

            // Allow our ALC to handle the directory discovery concept
            //
            // This is where Alc.Engine.dll is loaded into our custom ALC
            // and then passed through into PowerShell's ALC,
            // becoming the bridge between both
            return s_dependencyAlc.LoadFromAssemblyName(assemblyToResolve);
        }
    }
}

Yeni uygulamayla, modül yüklendiğinde ve Test-AlcModule çalıştırıldığında gerçekleşen çağrı dizisine göz atın:

Bağımlılıkları yüklemek için özel ALC kullanan çağrıların sıralı diyagramı

Bazı ilgi çekici noktalar şunlardır:

  • modül yüklendiğinde ve IModuleAssemblyInitializer olayını ayarladığınızda ilk olarak Resolving çalıştırılır.
  • Test-AlcModule çalıştırılana ve EndProcessing() yöntemi çağrılana kadar bağımlılıkları yüklemeyiz.
  • EndProcessing() çağrıldığında, varsayılan ALC AlcModule.Engine.dll bulamaz ve Resolving olayını başlatır.
  • Olay işleyicimiz özel ALC'yi varsayılan ALC'ye bağlar ve yalnızca AlcModule.Engine.dll yükler.
  • AlcEngine.Use()içinde AlcModule.Engine.dll çağrıldığında, özel ALC Shared.Dependency.dllçözmek için yeniden devreye giriyor. Özellikle, varsayılan ALC'deki hiçbir şeyle çakışmadığı ve yalnızca dizinimize baktığı için her zaman yüklenir.

Uygulamayı bir araya getirerek yeni kaynak kodu düzenimiz şöyle görünür:

+ AlcModule.psd1
+ src/
  + AlcModule.Cmdlets/
  | + AlcModule.Cmdlets.csproj
  | + TestAlcModuleCommand.cs
  | + AlcModuleAssemblyLoadContext.cs
  | + AlcModuleInitializer.cs
  |
  + AlcModule.Engine/
  | + AlcModule.Engine.csproj
  | + AlcEngine.cs

AlcModule.Cmdlets.csproj şöyle görünür:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\AlcModule.Engine\AlcModule.Engine.csproj" />
    <PackageReference Include="Microsoft.PowerShell.Sdk" Version="7.0.1" PrivateAssets="all" />
  </ItemGroup>
</Project>

AlcModule.Engine.csproj şöyle görünür:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Shared.Dependency" Version="1.0.0" />
  </ItemGroup>
</Project>

Bu nedenle, modülü oluştururken stratejimiz şu şekildedir:

  • Derleme AlcModule.Engine
  • Derleme AlcModule.Cmdlets
  • AlcModule.Engine'den Dependencies dizinine her şeyi kopyalayın ve kopyaladığımız şeyi unutmayın
  • AlcModule.Cmdlets olmayan her şeyi AlcModule.Engine temel modül dizinine kopyalayın

Buradaki modül düzeni bağımlılık ayrımı için çok önemli olduğundan kaynak kökten kullanılacak bir derleme betiği aşağıdadır:

param(
    # The .NET build configuration
    [ValidateSet('Debug', 'Release')]
    [string]
    $Configuration = 'Debug'
)

# Convenient reusable constants
$mod = "AlcModule"
$netcore = "netcoreapp3.1"
$copyExtensions = @('.dll', '.pdb')

# Source code locations
$src = "$PSScriptRoot/src"
$engineSrc = "$src/$mod.Engine"
$cmdletsSrc = "$src/$mod.Cmdlets"

# Generated output locations
$outDir = "$PSScriptRoot/out/$mod"
$outDeps = "$outDir/Dependencies"

# Build AlcModule.Engine
Push-Location $engineSrc
dotnet publish -c $Configuration
Pop-Location

# Build AlcModule.Cmdlets
Push-Location $cmdletsSrc
dotnet publish -c $Configuration
Pop-Location

# Ensure out directory exists and is clean
Remove-Item -Path $outDir -Recurse -ErrorAction Ignore
New-Item -Path $outDir -ItemType Directory
New-Item -Path $outDeps -ItemType Directory

# Copy manifest
Copy-Item -Path "$PSScriptRoot/$mod.psd1"

# Copy each Engine asset and remember it
$deps = [System.Collections.Generic.Hashtable[string]]::new()
Get-ChildItem -Path "$engineSrc/bin/$Configuration/$netcore/publish/" |
    Where-Object { $_.Extension -in $copyExtensions } |
    ForEach-Object { [void]$deps.Add($_.Name); Copy-Item -Path $_.FullName -Destination $outDeps }

# Now copy each Cmdlets asset, not taking any found in Engine
Get-ChildItem -Path "$cmdletsSrc/bin/$Configuration/$netcore/publish/" |
    Where-Object { -not $deps.Contains($_.Name) -and $_.Extension -in $copyExtensions } |
    ForEach-Object { Copy-Item -Path $_.FullName -Destination $outDir }

Son olarak, modülümüzün bağımlılıklarını daha fazla bağımlılık eklendikçe zaman içinde sağlam kalan bir Derleme Yük Bağlamı'nda yalıtmak için genel bir yöntemimiz vardır.

Daha ayrıntılı bir örnek için bu GitHub deposugidin. Bu örnek, bir modülüN ALC kullanmak için nasıl geçirildiğini ve bu modülün .NET Framework'te çalışmasını nasıl sürdürdüğünü gösterir. Ayrıca çekirdek uygulamasını basitleştirmek için .NET Standard ve PowerShell Standard'ın nasıl kullanılacağını da gösterir.

Bu çözüm,Bicep PowerShell modülü tarafından da kullanılır ve PowerShell Modülü Çakışmalarını Çözümleme blog gönderisi de bu çözüm hakkında iyi bir okumadır.

Yan yana yükleme için derleme çözümleme işleyicisi

Sağlam olsa da, yukarıda açıklanan çözüm, modül derlemesinin doğrudan bağımlılık derlemelerine başvurmamasını, bunun yerine bağımlılık derlemelerine başvuran bir sarmalayıcı derlemesine başvurmasını gerektirir. Sarmalayıcı derlemesi, modül derlemesinden gelen çağrıları bağımlılık derlemelerine ileten bir köprü gibi davranır. Bu, genellikle bu çözümü benimsemenin önemsiz bir iş olmasını sağlar:

  • Yeni bir modül için bu, tasarıma ve uygulamaya daha fazla karmaşıklık katacaktır
  • Mevcut bir modül için bu önemli bir yeniden düzenleme gerektirir

Özel bir Resolving örneğiyle bir AssemblyLoadContext olayı bağlayarak yan yana derleme yüklemeyi başarmak için basitleştirilmiş bir çözüm vardır. Bu yöntemin kullanılması modül yazarı için daha kolaydır ancak iki sınırlaması vardır. Bu çözümle ilgili bu sınırlamaları ve ayrıntılı senaryoları açıklayan örnek kod ve belgeler için PowerShell-ALC-Samples deposuna göz atın.

Önemli

Bağımlılık yalıtımı amacıyla Assembly.LoadFile kullanmayın. Assembly.LoadFile kullanıldığında, başka bir modül aynı derlemenin farklı bir sürümünü varsayılan yüklediğinde bir AssemblyLoadContext sorunu oluşturur. Bu API ayrı bir AssemblyLoadContext örneğine bir derleme yüklerken, yüklenen derlemeler PowerShell'in tür çözümleme kodu tarafındanbulunabilir. Bu nedenle, iki farklı ALC'den aynı tam qualifed tür adına sahip yinelenen türler olabilir.

Özel Uygulama Etki Alanları

Derleme yalıtımı için son ve en uç seçenek,özel Uygulama Etki Alanları kullanmaktır. Uygulama Etki Alanları yalnızca .NET Framework'te kullanılabilir. Bir .NET uygulamasının bölümleri arasında işlem içi yalıtım sağlamak için kullanılır. Kullanımlardan biri, aynı işlem içinde derleme yüklerini birbirinden yalıtmaktır.

Ancak, Uygulama Etki Alanlarıserileştirme sınırlarıdır. Bir uygulama etki alanındaki nesnelere doğrudan başka bir uygulama etki alanındaki nesneler tarafından başvurulamıyor ve kullanılamıyor. MarshalByRefObjectuygulayarak bu sorunu geçici olarak çözümleyebilirsiniz. Ancak bağımlılıklarda olduğu gibi türleri denetlemediğinizde, burada bir uygulamayı zorlamak mümkün değildir. Tek çözüm büyük mimari değişiklikler yapmaktır. Serileştirme sınırının ciddi performans etkileri de vardır.

Uygulama Etki Alanları bu ciddi sınırlamaya sahip olduğundan, uygulanması karmaşık olduğundan ve yalnızca .NET Framework'te çalıştığından, bunları burada nasıl kullanabileceğinize yönelik bir örnek vermeyeceğiz. Olasılık olarak belirtmeye değer olsalar da, önerilmezler.

Özel bir uygulama etki alanı kullanmayı denemek istiyorsanız, aşağıdaki bağlantılar yardımcı olabilir:

  • Uygulama Etki Alanları Kavramsal belgeleri
  • Uygulama Etki Alanları kullanmaya yönelik Örnekleri

PowerShell'de çalışmayan bağımlılık çakışmalarına yönelik çözümler

Son olarak, .NET'te .NET bağımlılık çakışmalarını araştırırken ortaya çıkan ve umut verici görünebilen ancak genellikle PowerShell için çalışmayan bazı olasılıkları ele alacağız.

Bu çözümler, uygulamayı ve büyük olasılıkla tüm makineyi denetlediğiniz bir ortamın dağıtım yapılandırmalarında yapılan değişiklikler olduğu ortak temaya sahiptir. Bu çözümler, web sunucuları ve sunucu ortamlarına dağıtılan diğer uygulamalar gibi senaryolara yöneliktir; burada ortam uygulamayı barındırmak için tasarlanmıştır ve dağıtan kullanıcı tarafından yapılandırılması ücretsizdir. Ayrıca çok fazla .NET Framework odaklı olma eğilimindedirler, yani PowerShell 6 veya üzeri sürümlerle çalışmazlar.

Modülünüzün yalnızca üzerinde tam denetime sahip olduğunuz Windows PowerShell 5.1 ortamlarında kullanıldığını biliyorsanız, bunlardan bazıları seçenekler olabilir. Ancak genel olarak, modülleri bugibi genel makine durumunu değiştirmemelidir. powershell.exe, diğer modüllerde veya modülünüzün beklenmedik yollarla başarısız olmasına neden olan diğer bağımlı uygulamalarda sorunlara neden olan yapılandırmaları bozabilir.

Aynı bağımlılık sürümünü kullanmaya zorlamak için app.config ile statik bağlama yeniden yönlendirmesi

.NET Framework uygulamaları, bazı uygulama davranışlarını bildirimli olarak yapılandırmak için bir app.config dosyasından yararlanabilir. Derleme yüklemesini belirli bir sürüme yeniden yönlendirmek için derleme bağlamasını yapılandıran bir app.config girişi yazmak mümkündür.

PowerShell için bununla ilgili iki sorun şunlardır:

  • .NET Core app.configdesteklemez, bu nedenle bu çözüm yalnızca powershell.exeiçin geçerlidir.
  • powershell.exe, System32 dizininde bulunan paylaşılan bir uygulamadır. Modülünüz büyük olasılıkla birçok sistemdeki içeriğini değiştiremez. Bunu yapabilse bile, app.config değiştirmek var olan bir yapılandırmayı bozabilir veya diğer modüllerin yüklenmesini etkileyebilir.

app.config ile codebase ayarlama

Aynı nedenlerden dolayı, codebase'da app.config ayarını yapılandırmaya çalışmak PowerShell modüllerinde çalışmaz.

Genel Derleme Önbelleğine (GAC) bağımlılıkları yükleme

.NET Framework'teki bağımlılık sürümü çakışmalarını çözmenin bir diğer yolu da GAC'ye bağımlılıkları yüklemektir, böylece farklı sürümler GAC'den yan yana yüklenebilir.

PowerShell modülleri için buradaki en önemli sorunlar şunlardır:

  • GAC yalnızca .NET Framework için geçerlidir, bu nedenle PowerShell 6 ve üzeri için bu işe yaramaz.
  • Derlemelerin GAC'ye yüklenmesi, genel makine durumunun değiştirilmesidir ve diğer uygulamalarda veya diğer modüllerde yan etkilere neden olabilir. Modülünüz gerekli erişim ayrıcalıklarına sahip olsa bile doğru şekilde yapmak da zor olabilir. Yanlış olması, diğer .NET uygulamalarında ciddi, makine genelinde sorunlara neden olabilir.

Daha fazla okuma

.NET derleme sürümü bağımlılık çakışmaları hakkında daha fazla bilgi edinebilirsiniz. İşte bazı güzel atlama noktaları: