在脚本模块导入期间,从C#类评估当前PowerShell会话版本

问题描述

首先,我要做的事情有一些背景知识。我正在编写一个PowerShell脚本模块,该模块在导入时需要设置一些变量并运行一些代码。最终需要执行的操作取决于导入模块的PowerShell版本。例如,这需要支持5.1及更高版本,但是Invoke-RestMethod在PowerShell 6之前不支持
-SkipCertificateCheck参数,这意味着我需要检查版本,创建dummy ICertificatePolicy基本上在验证时总是返回true,并将其存储在全局位置中,以便在用户指示调用某些cmdlet时希望跳过安全检查时可以引用它并设置该策略。到目前为止,我有以下代码

Add-Type @"
  using System.Management.Automation;
  using System.Net;
  using System.Security.Cryptography.X509Certificates;

  namespace MyModuleNamespace {
    public struct State {
      public static ICertificatePolicy SystemCertificatePolicy;
      public static ICertificatePolicy SkipSSLCheckPolicy;
    }

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

  namespace MyModuleNamespace.SubNamespace {
    public class ModuleAssemblyInitializer : IModuleAssemblyInitializer {
      public void OnImport() {
        // pseudocode of what I'm struggling to define in C#
        if( CurrentPSSession.Psversion.Major -lt 6 ) {
          MyModuleNamespace.State.SystemCertificatePolicy = ServicePointManager.CertificatePolicy;
          MyModuleNamespace.State.SkipSSLCheckPolicy = new MyModuleNamespace.TrustAllCertsPolicy();
        }
        // end pseudocode
      }
    }
  }
"@

但是...我正在努力评估当前PowerShell会话中的信息,例如评估$PsversionTable.Psversion。我知道如何检查安装了哪些版本的PowerShell,但是我需要能够评估将此类添加到的当前正在运行的PowerShell版本。另外,尽管从技术上讲这是一个选项,但我也不希望 entire 模块用C#编写。我们团队中的其他人都应该维护该模块,尽管他们非常了解PowerShell,但是C#不在他们的技能范围内。

如果在导入模块时有PowerShell本机方式来处理正在运行的代码,我将不知所措。这是我的第一个PowerShell模块,如果我使用错误解决方案来解决此问题,也不会感到惊讶。

解决方法

通常,我强烈建议完全不要这样做。盲目跳过证书信任验证是一个坏主意。


正如我(勉强地)试图在注释中解释的那样,如果您解析了调用程序集(即System.Management.Automation.dll),则可以使用反射来使用它来发现初始化程序中的版本信息: / p>

using System;
using System.Management.Automation;
using System.Reflection;

namespace CrossVersionModule
{
    public class ModuleAssemblyInitializer : IModuleAssemblyInitializer
    {
        public void OnImport()
        {
            // obtain reference to calling assembly
            var caller = Assembly.GetCallingAssembly();

            // locate the PSVersionInfo type
            var psversionType = caller.GetType("System.Management.Automation.PSVersionInfo");
            if (null != psversionType)
            {
                // locate the PSVersion property
                PropertyInfo versionProp = psversionType.GetProperty("PSVersion");
                if (null == versionProp) {
                    // In .NET framework (ie. Windows PowerShell 5.1) we have to explicitly pass binding flags for non-public members
                    versionProp = psversionType.GetProperty("PSVersion",BindingFlags.NonPublic | BindingFlags.Static);
                }

                // Invoke the getter to get the value
                var getter = versionProp.GetGetMethod(true);
                var version = getter.Invoke(null,new object[] { });
                Console.WriteLine(version); // here's where you'd do your version check
            }
        }
    }
}

如果我针对PowerShell Standard编译以上代码,则分别在5.1和7中导入时会得到正确的PowerShell版本。

,

除了Mathias的helpful answer之外,我还发现了几种确定.NET版本的方法。

一种方法是在字符串中扩展一个表达式,然后将以下代码添加到我的struct State中,以将$PSVersionTable.PSVersion中的值插入C#代码中,然后可以在模块代码的其他位置读取该代码:

public struct State {
  public static ICertificatePolicy SystemCertificatePolicy = ServicePointManager.CertificatePolicy;
  public static ICertificatePolicy SkipSSLCheckPolicy;
  
  // Lines below were added
  public readonly static System.Version PSVersion;

  static State() {
    PSVersion = new Version(
      $($PSVersionTable.PSVersion.Major),$($PSVersionTable.PSVersion.Minor),$($PSVersionTable.PSVersion.Build),$($PSVersionTable.PSVersion.Revision)
    );
  }
}

另一种可行的方法是检查调用程序集的product属性的值,该属性可以为调用程序集上的AssemblyProductAttribute返回以下内容之一:

  • 如果直接从PowerShell调用
    • Microsoft® .NET Framework(如果使用Windows PowerShell)
    • Microsoft® .NET Core(如果使用PowerShell Core)
  • 如果使用Add-Type从在PowerShell脚本中编译的C#进行调用
    • Microsoft (R) Windows (R) Operating System(如果在.NET Framework上)
    • PowerShell(如果使用.NET Core
  • 不确定在根据.NET Core SDK进行编译时,这将从正确编译的C#项目返回什么。

就我而言,我可以检查属性是否等于PowerShell来查看它是否是.NET Core:

// New using directives
using System;
using System.Reflection;

// Check if .NET Core
AssemblyProductAttribute attribute = Attribute.GetCustomAttribute(Assembly.GetCallingAssembly(),typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
if(attribute.Product == "PowerShell")) {
  // do .NET Core things
}

似乎第三种方法在运行时有一些问题,在我弄清楚发生了什么之前,我已将其从此答案中删除。