Ошибка конвейера при обращении к определенной C # структуре из PowerShell

#c# #powershell

#c# #powershell

Вопрос:

У меня есть следующее struct и class как часть модуля скрипта PS. Эта часть кода написана на C # и обрабатывает код для запуска при импорте модуля в сеанс PowerShell, хотя OnImport() биты, которые я здесь не включил. Это не все, что делает код C #, достаточно, чтобы показать проблему, с которой я сталкиваюсь:

 Add-Type -IgnoreWarnings @"
  using System;
  using System.Reflection;
  using System.Management.Automation;
  using System.Net;
  using System.Security.Cryptography.X509Certificates;
  using System.Runtime.Versioning;

  namespace MyPowerShell {
    public struct State {
      public readonly static ICertificatePolicy OriginalSystemCertificatePolicy = ServicePointManager.CertificatePolicy;
      public readonly static ICertificatePolicy SkipSSLCheckPolicy;
      public readonly static Version PSVersion;
      public readonly static string Framework;

      static State() {
        PSVersion = new Version(
          $($PSVersionTable.PSVersion.Major),
          $($PSVersionTable.PSVersion.Minor),
          $($PSVersionTable.PSVersion.Build),
          $($PSVersionTable.PSVersion.Revision)
        );
        OriginalSystemCertificatePolicy = ServicePointManager.CertificatePolicy;
        SkipSSLCheckPolicy = new TrustAllCertsPolicy();
      }
    }

    public class TrustAllCertsPolicy : ICertificatePolicy {
      public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
      }
    }
  }
"@
  

Это работает нормально, и я могу ссылаться на struct свойства и class штрафовать PowerShell после Add-Type завершения:

 # Each of these returns the information expected and I can instantiate TrustAllCertsPolicy
[MyPowerShell.State]::PSVersion
[MyPowerShell.State]::OriginalSystemCertificatePolicy
[MyPowerShell.State]::SkipSSLCheckPolicy
$policy = [MyPowerShell.TrustAllCertsPolicy]::new()
  

Но я попытался добавить другой способ определения текущей версии PowerShell из кода, который не зависит от расширения $PSVersionTable.PSVersion свойств в источнике here string , немного изменив struct определение:

 public struct State {
  public readonly static ICertificatePolicy OriginalSystemCertificatePolicy = ServicePointManager.CertificatePolicy;
  public readonly static ICertificatePolicy SkipSSLCheckPolicy;
  public readonly static Version PSVersion;
  public readonly static string Framework; # Added this line

  static State() {
    PSVersion = new Version(
      $($PSVersionTable.PSVersion.Major),
      $($PSVersionTable.PSVersion.Minor),
      $($PSVersionTable.PSVersion.Build),
      $($PSVersionTable.PSVersion.Revision)
    );
    OriginalSystemCertificatePolicy = ServicePointManager.CertificatePolicy;
    SkipSSLCheckPolicy = new TrustAllCertsPolicy();

    # Added the following 2 lines
    TargetFrameworkAttribute attribute = Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(TargetFrameworkAttribute)) as TargetFrameworkAttribute;
    Framework = attribute.FrameworkName;
  }

  // TrustAllCertsPolicy Code
}
  

Как только я добавляю код для извлечения FrameworkName атрибута из сборки записи в статическом конструкторе struct , он все равно компилируется, но я больше не могу ссылаться ни на одно из свойств в struct all, что приводит к ошибке конвейера:

 [MyPowerShell.State]::PSVersion

An error occurred while creating the pipeline.
  CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
  FullyQualifiedErrorId : RuntimeException
  

Однако вместо этого я могу использовать следующие вызовы для set Framework , используя AssemblyProductAttribute вместо этого вызывающую сборку, и у меня нет проблем:

 static State() {
  PSVersion = new Version(
    $($PSVersionTable.PSVersion.Major),
    $($PSVersionTable.PSVersion.Minor),
    $($PSVersionTable.PSVersion.Build),
    $($PSVersionTable.PSVersion.Revision)
  );
  OriginalSystemCertificatePolicy = ServicePointManager.CertificatePolicy;
  SkipSSLCheckPolicy = new TrustAllCertsPolicy();

  // Replaced the next two lines with this
  AssemblyProductAttribute productAttr = Attribute.GetCustomAttribute(Assembly.GetCallingAssembly(), typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
  Framework = productAttr.Product;
}
  

Почему мой struct становится непригодным для использования из PowerShell, когда я пытаюсь получить TargetFrameworkAttribute из сборки ввода, но отлично работает при получении AssemblyProductAttribute из вызывающей сборки вместо этого?

Комментарии:

1. [System.Reflection.Assembly]::GetEntryAssembly() возвращает, null поскольку powershell.exe это собственный PE, а не управляемая сборка

2. Забавно. Прекрасно, что ошибка, которую я получил, была настолько описательной. Есть идеи, почему этот вызов отлично работает из ядра PowerShell? pwsh.exe Не является также неуправляемой сборкой?

3. Nvm — похоже, в ядре PS входная сборка является DLL, а не исполняемым файлом. Думаю, это объясняет это.

4. Если вы укажете это в качестве ответа, я приму это

Ответ №1:

Если вы проверите базовое исключение, вы обнаружите, что причиной является нулевая ссылка, передаваемая Attribute.GetCustomAttribute() в статическом конструкторе.

 PS ~> $Error[0].GetBaseException() |Format-List -Force


Message        : Value cannot be null.
                 Parameter name: element
ParamName      : element
Data           : {}
InnerException :
TargetSite     : System.Attribute[] GetCustomAttributes(System.Reflection.Assembly, System.Type,
                 Boolean)
StackTrace     :    at System.Attribute.GetCustomAttributes(Assembly element, Type attributeType,
                 Boolean inherit)
                    at System.Attribute.GetCustomAttribute(Assembly element, Type attributeType,
                 Boolean inherit)
                    at State..cctor()
HelpLink       :
Source         : mscorlib
HResult        : -2147467261
  

Вызов Assembly.GetEntryAssembly() возвращает null , потому что хостинговое приложение на самом деле не является управляемой сборкой — powershell.exe это собственный Win32 PE.

Комментарии:

1. Спасибо, не подумал попробовать проверить базовое исключение. Очень раздражает, что реальная ошибка была скрыта.

2. FWIW мне все еще неясно, почему вы пытаетесь перенести проверку версии на C # в модуле сценария — не if($PSVersionTable.PSVersion.Major -lt 6){ Add-Type $PS5VersionOfState }else{ Add-Type $PSCoreVersionOfState } было бы проще в корневом модуле или скрипте?

3. Я имею опыт работы со сценариями, но это мой первый модуль PS, который я сделал. Я понял около 30 минут назад, что неправильно подхожу к этому. Мне не нужно ничего выгружать на C #, кроме одного определения класса, которое я могу сделать с помощью класса PowerShell вместо этого.