UnmanagedPowerShell工具分析

简单介绍:从非托管进程执行PowerShell。通过一些修改,可以在将这些相同的技术注入到不同的进程时使用(例如,如果需要,可以让任何进程执行PowerShell)

下面借用网上的一张图来说明这个流程,上面说了可以让任何进程执行powershell其实也就是说使用具有注入功能的程序将一个非托管的C++DLL注入到目标进程中,然后该非托管DLL启动CLR,并加载要执行的托管DLL,最后调用CLR执行托管代码。

而我们下面的工具实现的是非托管进程启动CLR,并加载要执行的托管的程序集,最后调用CLR执行托管代码

下面就对UnmanagedPowerShell工具源码来解释下整个流程的工作运转

关于PowerShellRunner.cs的部分

相关类的定义:

CustomPSHostUserInterface:可以替换我们要输出的内容

CustomPSRHostRawUserInterface:配置用户的界面

PSHost:为了让Write-Host工作,我必须实现一个自定义PSHost。如果所有的PowerShell脚本都使用Write-Output而不是Write-Host,那么这就不是问题,但是如果使用了足够多的Write-Host,那么实现一个定制PSHost是值得的

在C#中调用PowerShell会用到这个程序集,System.Management.Automation.dll,所以下面就是在适配调用时需要产生的类

using System;  using System.Collections.Generic;  using System.Text;  using System.Threading;  using System.Management.Automation;  using System.Globalization;  using System.Management.Automation.Host;  using System.Management.Automation.Runspaces;  namespace PowerShellRunner  {  public class PowerShellRunner  {  public static string InvokePS(string command)  {  // I had to implement a custom PSHost in order to get Write-Host to work.  // This wouldn't be an issue if all PowerShell scripts used Write-Output  // instead of Write-Host, but enough use Write-Host that it's worth it  // to implement a custom PSHost  //Write-Output返回的输出被传递给管道,因此管道之后的函数/cmdlet可以读取该输出以进行进一步处理。如果您使用Write-Host,这是不可能的。  //为了让Write-Host工作,我必须实现一个自定义PSHost。如果所有的PowerShell脚本都使用Write-Output而不是Write-Host,那么这就不是问题,但是如果使用了足够多的Write-Host,那么实现一个定制PSHost是值得的  CustomPSHost host = new CustomPSHost();  //允许您定义在创建会话状态时应该出现的元素集  //使用默认的cmdlet、提供程序等创建默认的PowerShell。内置函数,别名需要通过默认的InitialSessionstate构造函数可用。还需要对包装进行讨论。  var state = InitialSessionState.CreateDefault();  //指定此会话状态实例使用的授权管理器。如果没有指定授权管理器,那么将使用PowerShell的缺省授权管理器,它在运行命令之前检查ExecutionPolicy  state.AuthorizationManager = null; // Bypass PowerShell execution policy 绕过PowerShell执行策略  //RunspaceFactory--定义用于创建Runspace对象的工厂类  //使用指定的PSHost和InitialSessionState创建运行空间  using (Runspace runspace = RunspaceFactory.CreateRunspace(host, state))  {  //同步打开运行空间。运行空间在使用之前必须打开。  runspace.Open();  //Create an empty pipeline  using (Pipeline pipeline = runspace.CreatePipeline())  {  //Commands--获取此管道的命令集合  //AddScript(String)    Adds a new script command  添加一个新的脚本命令  pipeline.Commands.AddScript(command);  //合并此命令结果  //myResult:PipelineResultTypes:要重定向的管道流  //toResult:PipelineResultTypes:将myResult合并到的管道流  pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);  //将输出发送到默认格式化程序和默认输出cmdlet。  pipeline.Commands.Add("out-default");  //同步调用管道,以对象数组的形式返回结果  pipeline.Invoke();  }  }  //获取托管应用程序的PSHostUserInterface抽象基类的实现。不希望支持用户交互的主机应该返回null。  string output = ((CustomPSHostUserInterface)host.UI).Output;  return output;  }  //定义承载MSH运行空间的应用程序提供的属性和功能  //System.Management.Automation.Runspaces到Msh运行时的公共接口。提供用于创建管道、访问会话状态等的api。  //GUID数据类型是表示类标识符(ID)的文本字符串  //托管应用程序派生自此类,并重写抽象方法和属性。托管应用程序将创建其派生类的实例,然后将其传递给RunspaceFactory CreateRunspace方法。  class CustomPSHost : PSHost  {  //初始化Guid结构的新实例  private Guid _hostId = Guid.NewGuid();  //设置PSHostUserInterface抽象基类的宿主应用程序的实现 。不想支持用户交互的主机应返回null  private CustomPSHostUserInterface _ui = new CustomPSHostUserInterface();  //获取唯一标识此主机实例的GUID。该值应在此实例的生命周期内保持不变  public override Guid InstanceId  {  get { return _hostId; }  }  //以某种用户友好的方式获取托管应用程序的标识。脚本和cmdlet可以引用这个名称来标识执行它们的主机。值的格式没有定义,但建议使用简短的字符串。  public override string Name  {  get { return "ConsoleHost"; }  }  //获取宿主应用程序的版本。对于主机的特定构建,此值应该保持不变。此值可由脚本和cmdlet引用。  public override Version Version  {  get { return new Version(1, 0); }  }  //获取托管应用程序的PSHostUserInterface抽象基类的实现。不希望支持用户交互的主机应该返回null。  public override PSHostUserInterface UI  {  get { return _ui; }  }  //获取主机的区域性:运行空间应使用该区域性在新线程上设置CurrentCulture  public override CultureInfo CurrentCulture  {  get { return Thread.CurrentThread.CurrentCulture; }  }  //获取主机的UI区域性:运行空间和cmdlet应该用来加载资源的区域性。  //每次启动管道时,运行空间都会将线程当前ui区域性设置为这个值。  public override CultureInfo CurrentUICulture  {  get { return Thread.CurrentThread.CurrentUICulture; }  }  //指示主机中断当前正在运行的管道并启动一个新的“嵌套”输入循环,其中输入循环是提示、输入和执行的循环。  public override void EnterNestedPrompt()  {  throw new NotImplementedException("EnterNestedPrompt is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //导致主机结束当前运行的输入循环。如果先前调用EnterNestedPrompt创建了输入循环,则封闭管道将恢复。如果当前输入循环是最上面的循环,那么主机将执行SetShouldExit调用。  public override void ExitNestedPrompt()  {  throw new NotImplementedException("ExitNestedPrompt is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //由引擎调用,通知主机它将执行“遗留”命令行应用程序。遗留应用程序被定义为控制台模式的可执行文件,它可以执行以下一个或多个操作:。读stdin。写信给stdout。写信给stderr。使用任何win32控制台api  public override void NotifyBeginApplication()  {  return;  }  //由引擎调用,通知主机遗留命令的执行已经完成  public override void NotifyEndApplication()  {  return;  }  //引擎请求结束当前引擎运行空间(关闭并终止主机的根运行空间)  public override void SetShouldExit(int exitCode)  {  return;  }  }  //提供一个对话框,允许用户从一组选项中选择一个选项  //定义由PSHost派生的托管应用程序提供的属性和功能,该托管应用程序 提供了面向对话框和面向行的交互功能  class CustomPSHostUserInterface : PSHostUserInterface  {  // Replace StringBuilder with whatever your preferred output method is (e.g. a socket or a named pipe)  private StringBuilder _sb;  //将StringBuilder替换为您首选的输出方法(例如,套接字或命名管道);  //获取实现该类的--PSHostRawUserInterface抽象基类的托管应用程序实现。  private CustomPSRHostRawUserInterface _rawUi = new CustomPSRHostRawUserInterface();  public CustomPSHostUserInterface()  {  _sb = new StringBuilder();  }  //将字符写入屏幕缓冲区。不附加回车。  public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)  {  _sb.Append(value);  }  //默认实现将回车写入屏幕缓冲区  public override void WriteLine()  {  _sb.Append("n");  }  //与WriteLine(String)相同,只是可以指定颜色。  public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)  {  _sb.Append(value + "n");  }  //Writes characters to the screen buffer. Does not append a carriage return.  public override void Write(string value)  {  _sb.Append(value);  }  //由WriteDebug(String)调用,向用户显示调试消息。  public override void WriteDebugLine(string message)  {  _sb.AppendLine("DEBUG: " + message);  }  //向主机的“错误显示”写入一行,而不是由and的变体写入的“输出显示”  public override void WriteErrorLine(string value)  {  _sb.AppendLine("ERROR: " + value);  }  //将字符写入屏幕缓冲区,并附加回车  public override void WriteLine(string value)  {  _sb.AppendLine(value);  }  //由WriteVerbose(String)调用,向用户显示详细的处理消息  public override void WriteVerboseLine(string message)  {  _sb.AppendLine("VERBOSE: " + message);  }  //由WriteWarning(String)调用,向用户显示警告处理消息。  public override void WriteWarningLine(string message)  {  _sb.AppendLine("WARNING: " + message);  }  //Invoked by System.Management.Automation.Cmdlet.WriteProgress(System.Int64,System.Management.Automation.ProgressRecord) to display a progress record.  public override void WriteProgress(long sourceId, ProgressRecord record)  {  return;  }  public string Output  {  get { return _sb.ToString(); }  }  //构造一个“对话框”,其中向用户显示许多要为其提供值的字段。  public override Dictionary<string, PSObject> Prompt(string caption, string message, System.Collections.ObjectModel.Collection<FieldDescription> descriptions)  {  throw new NotImplementedException("Prompt is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //提供一个对话框,允许用户从一组选项中选择一个选项  public override int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection<ChoiceDescription> choices, int defaultChoice)  {  throw new NotImplementedException("PromptForChoice is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //Prompt for credentials.  public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)  {  throw new NotImplementedException("PromptForCredential1 is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //提示输入凭证  public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)  {  throw new NotImplementedException("PromptForCredential2 is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  public override PSHostRawUserInterface RawUI  {  get { return _rawUi; }  }  //从控制台读取字符,直到遇到换行(回车)。  public override string ReadLine()  {  throw new NotImplementedException("ReadLine is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  //与ReadLine相同,只是结果是SecureString,并且在收集输入时不会回显给用户(或者以某种模糊的方式回显,比如为每个字符显示一个点)。  public override System.Security.SecureString ReadLineAsSecureString()  {  throw new NotImplementedException("ReadLineAsSecureString is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  }  //PSHostRawUserInterface 读取用户操作的界面  //可以提供Shell窗口物理操作方法的途径,包括字符标记,属性,以及文字前景和背景颜色等  class CustomPSRHostRawUserInterface : PSHostRawUserInterface  {  // Warning: Setting _outputWindowSize too high will cause OutOfMemory execeptions. I assume this will happen with other properties as well  //警告:设置_outputWindowSize过高将导致OutOfMemory执行。我想这也会发生在其他性质上  private Size _windowSize = new Size { Width = 120, Height = 100 };  private Coordinates _cursorPosition = new Coordinates { X = 0, Y = 0 };  private int _cursorSize = 1;  private ConsoleColor _foregroundColor = ConsoleColor.White;  private ConsoleColor _backgroundColor = ConsoleColor.Black;  private Size _maxPhysicalWindowSize = new Size  {  Width = int.MaxValue,  Height = int.MaxValue  };  private Size _maxWindowSize = new Size { Width = 100, Height = 100 };  private Size _bufferSize = new Size { Width = 100, Height = 1000 };  private Coordinates _windowPosition = new Coordinates { X = 0, Y = 0 };  private String _windowTitle = "";  public override ConsoleColor BackgroundColor  {  get { return _backgroundColor; }  set { _backgroundColor = value; }  }  public override Size BufferSize  {  get { return _bufferSize; }  set { _bufferSize = value; }  }  public override Coordinates CursorPosition  {  get { return _cursorPosition; }  set { _cursorPosition = value; }  }  public override int CursorSize  {  get { return _cursorSize; }  set { _cursorSize = value; }  }  //重置键盘输入缓冲区  public override void FlushInputBuffer()  {  throw new NotImplementedException("FlushInputBuffer is not implemented.");  }  //获取或设置用于在屏幕缓冲区上呈现字符的颜色。屏幕缓冲区中的每个字符单元可以具有单独的前景色  public override ConsoleColor ForegroundColor  {  get { return _foregroundColor; }  set { _foregroundColor = value; }  }  //提取屏幕缓冲区的矩形区域  public override BufferCell[,] GetBufferContents(Rectangle rectangle)  {  throw new NotImplementedException("GetBufferContents is not implemented.");  }  //一个非阻塞调用,用于检查击键是否在输入缓冲区中等待  public override bool KeyAvailable  {  get { throw new NotImplementedException("KeyAvailable is not implemented."); }  }  public override Size MaxPhysicalWindowSize  {  get { return _maxPhysicalWindowSize; }  }  public override Size MaxWindowSize  {  get { return _maxWindowSize; }  }  public override KeyInfo ReadKey(ReadKeyOptions options)  {  throw new NotImplementedException("ReadKey is not implemented. The script is asking for input, which is a problem since there's no console. Make sure the script can execute without prompting the user for input.");  }  public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)  {  throw new NotImplementedException("ScrollBufferContents is not implemented");  }  public override void SetBufferContents(Rectangle rectangle, BufferCell fill)  {  throw new NotImplementedException("SetBufferContents is not implemented.");  }  public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)  {  throw new NotImplementedException("SetBufferContents is not implemented");  }  public override Coordinates WindowPosition  {  get { return _windowPosition; }  set { _windowPosition = value; }  }  public override Size WindowSize  {  get { return _windowSize; }  set { _windowSize = value; }  }  public override string WindowTitle  {  get { return _windowTitle; }  set { _windowTitle = value; }  }  }  }  }  关于  UnmanagedPowerShell/UnmanagedPowerShell/UnmanagedPowerShell.cpp的部分  运行托管与非托管代码根本区别在于托管代码是进程首先加载CLR然后通过CLR运行托管程序,而非托管代码则是操作系统直接根据其PE Header加载程序分配内存从而运行。因此如果需要通过托管代码来扩展非托管程序,首先要加载CLR来使非托管程序获得运行托管代码的能力。所以下面代码做的就是这个事情  // UnmanagedPowerShell.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  #pragma region Includes and Imports  #include <windows.h>  #include <comdef.h>  #include <mscoree.h>  #include "PowerShellRunnerDll.h"  #include <metahost.h>  #pragma comment(lib, "mscoree.lib")  // Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).  // 微软公共语言运行时类库  //raw_interface_only:仅使用原始接口  //Foo([out, retval] long * pVal);这个函数,缺省时调用:long val = obj->Foo();  //如果用了raw_interface_only就要:  //long val;  //xx->Foo(&val);  #import "mscorlib.tlb" raw_interfaces_only   //high_property_prefixes属性  //high_property_prefixes("GetPrefix,""PutPrefix,""PutRefPrefix")  //GetPrefix  //用于propget方法的前缀  //PutPrefix  //用于propput方法的前缀  //PutRefPrefix  //用于propputref方法的前缀  //在缺省情况下,高级错误处理方法,如propget、propput和propputref,分别采用以前缀Get、Put和PutRef命名的成员函数来说明。high_property_prefixes属性用于分别说明这三种属性方法的前缀。  high_property_prefixes("_get","_put","_putref")   //rename属性  //rename("OldName,""NewName")  //OldName  //类型库中的旧名  //NewName  //用于替换旧名的名称  //rename属性用于解决名称冲突的问题。若该属性被指定,编译器将在类型库中的OldName的所有出现处用结果头文件中用户提供的NewName替换。  //此属性用于类型库中  rename("ReportEvent", "InteropServices_ReportEvent")  using namespace mscorlib;  #pragma endregion  //创建接口实例,返回接口指针  //  typedef HRESULT(WINAPI *funcCLRCreateInstance)(  REFCLSID clsid,  REFIID riid,  LPVOID * ppInterface  );  //使非托管主机可以将公共语言运行库(CLR)加载到进程中。该CorBindToRuntime和CorBindToRuntimeEx功能执行相同的操作,但该CorBindToRuntimeEx功能允许您设置标志来指定CLR的行为  typedef HRESULT (WINAPI *funcCorBindToRuntime)(  LPCWSTR pwszVersion,  LPCWSTR pwszBuildFlavor,  REFCLSID rclsid,  REFIID riid,  LPVOID* ppv);  extern const unsigned int PowerShellRunner_dll_len;  extern unsigned char PowerShellRunner_dll[];  void InvokeMethod(_TypePtr spType, wchar_t* method, wchar_t* command);  //适配.Net4  bool createDotNetFourHost(HMODULE* hMscoree, const wchar_t* version, ICorRuntimeHost** ppCorRuntimeHost)  {  HRESULT hr = NULL;  funcCLRCreateInstance pCLRCreateInstance = NULL;  ICLRMetaHost *pMetaHost = NULL;  ICLRRuntimeInfo *pRuntimeInfo = NULL;  bool hostCreated = false;  //寻找CLRCreateInstance函数的地址  pCLRCreateInstance = (funcCLRCreateInstance)GetProcAddress(*hMscoree, "CLRCreateInstance");  if (pCLRCreateInstance == NULL)  {  wprintf(L"Could not find .NET 4.0 API CLRCreateInstance");  goto Cleanup;  }  //若要获取此接口的实例的唯一方法是通过调用CLRCreateInstance函数,如下所示:  //ICLRMetaHost *pMetaHost = NULL; HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);  hr = pCLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));  if (FAILED(hr))  {  // Potentially fails on .NET 2.0/3.5 machines with E_NOTIMPL  wprintf(L"CLRCreateInstance failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  //获取与特定版本的公共语言运行时 (CLR) 相对应的ICLRRuntimeInfo接口  hr = pMetaHost->GetRuntime(L"v2.0.50727", IID_PPV_ARGS(&pRuntimeInfo));  if (FAILED(hr))  {  wprintf(L"ICLRMetaHost::GetRuntime failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  //检查指定的运行时是否可以加载到流程中  // Check if the specified runtime can be loaded into the process.  BOOL loadable;  //指示与此接口关联的运行时是否可以加载到当前进程中,考虑到可能已加载到进程的其他运行时。  hr = pRuntimeInfo->IsLoadable(&loadable);  if (FAILED(hr))  {  wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  if (!loadable)  {  wprintf(L".NET runtime v2.0.50727 cannot be loadedn");  goto Cleanup;  }  // Load the CLR into the current process and return a runtime interface  // 将CLR加载到当前进程并返回运行时接口  hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(ppCorRuntimeHost));  if (FAILED(hr))  {  wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  hostCreated = true;  Cleanup:  if (pMetaHost)  {  pMetaHost->Release();  pMetaHost = NULL;  }  if (pRuntimeInfo)  {  pRuntimeInfo->Release();  pRuntimeInfo = NULL;  }  return hostCreated;  }  //适配.Net2  HRESULT createDotNetTwoHost(HMODULE* hMscoree, const wchar_t* version, ICorRuntimeHost** ppCorRuntimeHost)  {  HRESULT hr = NULL;  bool hostCreated = false;  funcCorBindToRuntime pCorBindToRuntime = NULL;  //CorBindToRuntime--使非托管的宿主能够将公共语言运行时 (CLR) 加载到进程中,.NET Framework 4 中已弃用此函数  pCorBindToRuntime = (funcCorBindToRuntime)GetProcAddress(*hMscoree, "CorBindToRuntime");  if (!pCorBindToRuntime)  {  wprintf(L"Could not find API CorBindToRuntime");  goto Cleanup;  }  //HRESULT CorBindToRuntime (  //    [in]  LPCWSTR     pwszVersion,    想要加载的 CLR 版本描述的字符串,.NET Framework 中的版本号用句点分隔的四个部分组成:major.minor.build.revision。将字符串作为传递pwszVersion必须以字符"v"跟版本号 (例如,"v1.0.1529") 的前三个部分开头,如果调用方指定为 null pwszVersion,加载的运行时的最新版本。  //    [in]  LPCWSTR     pwszBuildFlavor,    一个字符串,指定是否加载在服务器或工作站的 clr 版本。有效值为 svr 和 wks。服务器生成经过优化,可充分利用多个处理器,用于垃圾回收和工作站生成优化的单处理器计算机上运行的客户端应用程序,如果pwszBuildFlavor设置为 null,则将加载工作站版本。在单处理器计算机上运行时,工作站生成始终处于加载状态,即使pwszBuildFlavor设置为svr。但是,如果pwszBuildFlavor设置为svr,并且指定并发垃圾回收 (请参阅的说明flags参数),将加载服务器版本。  //    [in]  REFCLSID    rclsid,   CLSID的实现的组件类ICorRuntimeHost或ICLRRuntimeHost接口。支持的值为 CLSID_CorRuntimeHost 或 CLSID_CLRRuntimeHost  //    [in]  REFIID      riid,   IID从所请求的接口的rclsid。支持的值为 IID_ICorRuntimeHost 或 IID_ICLRRuntimeHost  //    [out] LPVOID FAR  *ppv  指向返回的接口指针riid  //);  hr = pCorBindToRuntime(version, L"wks", CLSID_CorRuntimeHost, IID_PPV_ARGS(ppCorRuntimeHost));  if (FAILED(hr))  {  wprintf(L"CorBindToRuntime failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  hostCreated = true;  Cleanup:  return hostCreated;  }  HRESULT createHost(const wchar_t* version, ICorRuntimeHost** ppCorRuntimeHost)  {  bool hostCreated = false;  HMODULE hMscoree = LoadLibrary(L"mscoree.dll");  if (hMscoree)  {  if (createDotNetFourHost(&hMscoree, version, ppCorRuntimeHost) || createDotNetTwoHost(&hMscoree, version, ppCorRuntimeHost))  {  hostCreated = true;  }  }  return hostCreated;  }  int _tmain(int argc, _TCHAR* argv[])  {  HRESULT hr;  ICorRuntimeHost *pCorRuntimeHost = NULL;  IUnknownPtr spAppDomainThunk = NULL;  _AppDomainPtr spDefaultAppDomain = NULL;  // The .NET assembly to load.  bstr_t bstrAssemblyName("PowerShellRunner");  _AssemblyPtr spAssembly = NULL;  // The .NET class to instantiate.  bstr_t bstrClassName("PowerShellRunner.PowerShellRunner");  _TypePtr spType = NULL;  // Create the runtime host  if (!createHost(L"v2.0.50727", &pCorRuntimeHost))  {  wprintf(L"Failed to create the runtime hostn");  goto Cleanup;  }  // Start the CLR  hr = pCorRuntimeHost->Start();  if (FAILED(hr))  {  wprintf(L"CLR failed to start w/hr 0x%08lxn", hr);  goto Cleanup;  }  DWORD appDomainId = NULL;  //获取类型的接口指针System._AppDomain,表示当前进程的默认域  //[out]类型的接口指针System._AppDomain到AppDomain表示进程的默认应用程序域的实例  hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);  if (FAILED(hr))  {  wprintf(L"RuntimeClrHost::GetCurrentAppDomainId failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  // Get a pointer to the default AppDomain in the CLR.  //获取类型的接口指针System._AppDomain,表示当前进程的默认域  hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);  if (FAILED(hr))  {  wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lxn", hr);  goto Cleanup;  }  //此指针被类型化为IUnknown,因此调用方通常应调用QueryInterface若要获取类型的接口指针System._AppDomain。  hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));  if (FAILED(hr))  {  wprintf(L"Failed to get default AppDomain w/hr 0x%08lxn", hr);  goto Cleanup;  }  // Load the .NET assembly.  // (Option 1) Load it from disk - usefully when debugging the PowerShellRunner app (you'll have to copy the DLL into the same directory as the exe)  ////加载.net程序集。  //(选项1)从磁盘加载它—在调试PowerShellRunner应用程序时非常有用(您必须将DLL复制到与exe相同的目录中)  //参数  //assemblyString  //String  //程序集的显示名称。请参阅 FullName。  //assemblySecurity  //Evidence  //用于加载程序集的证据。  // hr = spDefaultAppDomain->Load_2(bstrAssemblyName, &spAssembly);  // (Option 2) Load the assembly from memory  SAFEARRAYBOUND bounds[1];  bounds[0].cElements = PowerShellRunner_dll_len;  bounds[0].lLbound = 0;  //创建一个新的数组描述符,分配和初始化该数组的数据,并返回一个指向新数组描述符的指针  //VT_UI1 type property MUST be a 1-byte unsigned integer  SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds);  //vt  //数组的基本类型(数组每个元素的VARTYPE)。VARTYPE仅限于变体类型的子集。VT_ARRAY和VT_BYREF标志都不能设置。VT_EMPTY和VT_NULL是该数组的无效基本类型。所有其他类型都是合法的。  //cDims  //数组中的维数。创建阵列后不能更改该数字。  //rgsabound  //为数组分配的边界向量(每个维度一个)。  //递增数组的锁计数,并将指向数组数据的指针放在数组描述符的pvData中  SafeArrayLock(arr);  //memcpy指的是C和C ++使用的内存拷贝函数,函数原型为void * memcpy(void * destin,void * source,unsigned n)  memcpy(arr->pvData, PowerShellRunner_dll, PowerShellRunner_dll_len);  SafeArrayUnlock(arr);  hr = spDefaultAppDomain->Load_3(arr, &spAssembly);  if (FAILED(hr))  {  wprintf(L"Failed to load the assembly w/hr 0x%08lxn", hr);  goto Cleanup;  }  // Get the Type of PowerShellRunner.  //spType表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。  hr = spAssembly->GetType_2(bstrClassName, &spType);  if (FAILED(hr))  {  wprintf(L"Failed to get the Type interface w/hr 0x%08lxn", hr);  goto Cleanup;  }  // Call the static method of the class  wchar_t* argument = L"Get-Processn  #This is a PowerShell Commentn  Write-Host "`n`n******* The next command is going to throw an exception. This is planned *********`n`n"n  Read-Hostn";  InvokeMethod(spType, L"InvokePS", argument);  Cleanup:  if (pCorRuntimeHost)  {  pCorRuntimeHost->Release();  pCorRuntimeHost = NULL;  }  return 0;  }  void InvokeMethod(_TypePtr spType, wchar_t* method, wchar_t* command)  {  HRESULT hr;  bstr_t bstrStaticMethodName(method);  SAFEARRAY *psaStaticMethodArgs = NULL;  variant_t vtStringArg(command);  variant_t vtPSInvokeReturnVal;  variant_t vtEmpty;  //SAFEARRAY* SafeArrayCreateVector(   //用于建立一维普通数组。  //  VARTYPE vt,                                          //数组类型  //  long lLbound,                                          //数组的最小下标(可以取负数)  //  unsigned int cElements                           //数组的长度  //);  //创建方法参数  psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);  LONG index = 0;  //放元素到数组当中  hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);  if (FAILED(hr))  {  wprintf(L"SafeArrayPutElement failed w/hr 0x%08lxn", hr);  return;  }  // Invoke the method from the Type interface.  //使用指定的绑定约束和匹配的指定参数列表及区域性来调用指定成员。  hr = spType->InvokeMember_3(  bstrStaticMethodName, //字符串,它包含要调用的构造函数、方法、属性或字段成员的名称  static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),  invokeAttr  BindingFlags  枚举值的按位组合,这些值指定如何进行搜索。 访问可以是 BindingFlags 之一,如 Public、NonPublic、Private、InvokeMethod 和 GetField 等。查找类型无需指定。如果省略查找的类型,则将使用 BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static。  NULL, 一个对象,该对象定义一组属性并启用绑定,而绑定可能涉及选择重载方法、强制参数类型和通过反射调用成员。  vtEmpty, 对其调用指定成员的对象  psaStaticMethodArgs, 包含传递给要调用的成员的参数的数组  &vtPSInvokeReturnVal);  表示要使用的全局化区域设置的对象,它对区域设置特定的转换可能是必需的,比如将数字 String 转换为 Double。  if (FAILED(hr))  {  wprintf(L"Failed to invoke InvokePS w/hr 0x%08lxn", hr);  return;  }  else  {  // Print the output of the command  wprintf(vtPSInvokeReturnVal.bstrVal);  }  SafeArrayDestroy(psaStaticMethodArgs);  psaStaticMethodArgs = NULL;  }