65.9K
CodeProject 正在变化。 阅读更多。
Home

PowerShell 内部机制和 PowerShell 线程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (39投票s)

2011 年 9 月 29 日

LGPL3

7分钟阅读

viewsIcon

144077

downloadIcon

1028

本文将向您展示 PowerShell 的一些功能,例如 .NET Framework 4.0 的使用以及 .NET 线程。

介绍  

PowerShell 是一种出色的脚本语言,可以访问完整的 .NET Framework。大多数人会忽略 PowerShell 并非编程语言,并且在某些方面似乎有所限制。但是,如果一个人不允许使用真正的编程语言,该怎么办?如果只能提供脚本而不是预编译为 IL、字节码、PCode 或甚至像 C、C++ 那样的已编译代码的二进制文件,该怎么办?答案是……一种选择就是使用 PowerShell。我想向您展示 PowerShell 的动态性和灵活性。

我的下一篇文章将向您介绍 PowerShell 4.0 中期望的状态配置的一些细节。

但在介绍我的想法之前,我想先提供一些 PowerShell 的内部机制。

PowerShell 1.0 和 2.0 内部机制   

PowerShell 是用 C++ 6.0 编写的。可以通过使用 Microsoft 的 dumpbin 工具并查找 "msvcrt.dll" 二进制文件来检索此信息。MSVCRT 是 Visual C++ 版本 4.2 到 6.0 的 C++ 运行时。之前的版本和之后使用的 DLL 名称不同(例如,msvcr70.dllmsvcr100.dll 等)。 

PowerShell imports

这些信息使我们知道 PowerShell.exe 本身并非用 .NET 编写。但是 PowerShell 如何与 .NET 协同工作呢? 

答案很简单。PowerShell 使用 mscoree.dllCorBindToRuntimeEx 函数。

CorBindToRuntimeEx in PowerShell

HRESULT CorBindToRuntimeEx (
    [in]  LPWSTR    pwszVersion, 
    [in]  LPWSTR    pwszBuildFlavor, 
    [in]  DWORD     flags, 
    [in]  REFCLSID  rclsid, 
    [in]  REFIID    riid, 
    [out] LPVOID*   ppv
);  

在此方面,.NET 4.0 和 2.0 之间几乎没有区别,除了

  • MSCorEE.idl 已被 MSCorEE.h 替换,
  • flags 已重命名为 startupFlags,并且
  • 该函数在 .NET 4.0 中已被标记为已弃用。 

有关更多信息,请访问: http://msdn.microsoft.com/en-us/library/99sz37yh(v=VS.100).aspx

这就是为什么在 PowerShell 中使用 .NET 4.0 代码比使用 .NET 2.0 代码更麻烦的原因。

顺便说一句,在 PowerShell 中使用 .NET 4.0 是可能的,但您必须定义自己的宿主或更改某些注册表设置或配置文件,因为 PowerShell.exe 和 ISE 已针对 .NET 2.0 编译。例如:

reg add hklm\software\microsoft\.netframework /v OnlyUseLatestCLR /t REG_DWORD /d 1
reg add hklm\software\wow6432node\microsoft\.netframework /v OnlyUseLatestCLR /t REG_DWORD /d 1

上述注册表更改将允许 PowerShell 使用 .NET 4.0。

Attention

注意!修改 .NET Framework 注册表设置可能会影响整个系统。请考虑改用修改配置文件。 

通过更改 powershell_ise.exe.config,ISE 也适用

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <startup>
  <supportedRuntime version="v4.0.30319" />
 </startup>
</configuration>

您甚至可以按如下方式更改 powershell.exe.config ($pshome\$)

<?xml version="1.0"?> 
<configuration> 
    <startup useLegacyV2RuntimeActivationPolicy="true"> 
        <supportedRuntime version="v4.0.30319"/> 
        <supportedRuntime version="v2.0.50727"/> 
    </startup> 
</configuration>

或者您可以编写自己的 PowerShell 宿主

using System;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell;
namespace PowerShell40
{
    class Program
    {
        static int Main(string[] args)
        {
            return ConsoleShell.Start
            (
                RunspaceConfiguration.Create(),
                "Windows PowerShell .NET 4.0 Enabled",
                string.Empty,
                args
            );
        }
    }
}

为了编译您的 .NET 4.0 控制台应用程序,您需要添加对 Microsoft.PowerShell.ConsoleHostSystem.Management.Automation 程序集的引用,它们位于 %programfiles%\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0 目录下。

PowerShell 3.0 和 4.0 内部机制

与下面所示的一些例外情况几乎相同,适用于 Microsoft 的 PowerShell 3.0 和 4.0。

VC++ 6.0 再次用于 PowerShell 3.0 版本的编程。细心的读者可能已经注意到,与 2.0 版本相比,PowerShell 现在包含 SHELL32.dll 导入。

HRESULT SHGetKnownFolderPath(
  _In_      REFKNOWNFOLDERID rfid,
  _In_      DWORD dwFlags,
  _In_opt_  HANDLE hToken,
  _Out_     PWSTR *ppszPath
); 

该函数在 PowerShell 内部用于检索“已知文件夹”的完整路径,有关更多信息,请访问:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx

此外,Microsoft 不再使用以下导入:

KERNEL32
    VirtualProtect
    RtlCaptureContext
    RtlLookupFunctionEntry
    RtlVirtualUnwind

而是添加了以下内容:

KERNEL32
    InterlockedDecrement
    GetFileAttributesW
    GetCurrentDirectoryW
    SetConsoleTitleW
    GetModuleHandleA
    InterlockedCompareExchange
    InterlockedExchange

SHELL32
    SHGetKnownFolderPath

ADVAPI32
    RegGetValueW

PowerShell 3.0 addresses .NET Framework 4.0

Windows PowerShell 3.0 可在此处下载(它是 Windows Management Framework 3.0 的一部分)。

http://www.microsoft.com/en-us/download/details.aspx?id=34595 

WMF 4.0 的下载链接如下:

http://www.microsoft.com/en-us/download/details.aspx?id=39347

有一件事让我感到惊讶:

powershell.exe 现在是 x86 应用程序(即使安装了 x64 版本),不再是 x64 位应用程序。

一些 PowerShell 4.0 PE 头详细信息

FILE HEADER VALUES
             14C machine (x86)
               5 number of sections
        51B89908 time date stamp Wed Jun 12 17:51:36 2013
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             102 characteristics
                   Executable
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic # (PE32)

一些 PowerShell 3.0 PE 头详细信息

FILE HEADER VALUES
             14C machine (x86)
               5 number of sections
        50338287 time date stamp Tue Aug 21 14:43:51 2012
               0 file pointer to symbol table
               0 number of symbols
              E0 size of optional header
             102 characteristics
                   Executable
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic # (PE32)

PowerShell 3.0 Property Details

与 2.0 版的 powershell 相比

FILE HEADER VALUES
            8664 machine (x64)
               5 number of sections
        4A5BC7F3 time date stamp Tue Jul 14 01:49:07 2009
               0 file pointer to symbol table
               0 number of symbols
              F0 size of optional header
              22 characteristics
                   Executable
                   Application can handle large (>2GB) addresses

OPTIONAL HEADER VALUES
             20B magic # (PE32+)

PowerShell 2.0 Property Details

PowerShell 3.0 有什么新功能? 

帮助系统得到了改进。

您现在可以使用 "Show-Command" CMDLet 来获取 PowerShell 命令的 GUI 列表。

PS 帮助系统现在可以通过调用 CMDLet "Update-Help" 或通过搜索命令(如下面的 "Get-Help" 示例)来更新帮助文件。

与 PS 2.0 时代一样,仍然可以打开在线帮助。

 

还添加了 Active Directory CMDLet。

例如,现在可以通过 PowerShell 安装 AD。

位于 ADDSDeployment 模块中的新命令是:

  • Install-ADDSDomainController 
  • Install-ADDSDomain
  • Install-ADDSForest 
  • UnInstall-ADDSDomainController
  • Test-ADDSDomainInstallation
  • Test-ADDSForestInstallation 
  • ADDSDomainControllerUnInstallation
  • Test-ADDSReadOnlyDomainControllerUnInstallation  和
  • Test-ADDSDomainControllerInstallation

AD 管理中心已扩展,包含一个 PowerShell 历史记录窗口。

 

Microsoft 的 Beta 项目(Microsoft Connect)允许您将 Microsoft Online Backup 与 PowerShell 3.0 一起使用。

http://connect.microsoft.com/onlinebackup

在 PowerShell 中要加载的模块(使用 CMDLet Import-Module)称为: MSOnlineBackup

还有更多新功能,例如“PowerShell Web Access”。为了快速了解,请参考以下不错的 Technet 文章:

http://technet.microsoft.com/en-us/library/hh831611.aspx

PowerShell 4.0 有什么新功能? 

要了解 PowerShell 4.0 的新闻和成就,请参考文章 "PowerShell 4.0 - What's New?"。

在 PS 版本之间切换

可以更改编码规则,也可以在 PowerShell 版本之间切换。使用命令: 

Set-StrictMode -Version <Version> 

可能的值是

1.0

2.0    (需要 PS 2.0)

3.0    (需要 PS 3.0)

4.0    (需要 PS 4.0) 或 

Latest (最新版本切换到可用的最高 PowerShell 版本)

深入探讨 - 线程    

如前几节所示,您在 PowerShell 中既不受运行时版本的限制,也不受线程能力的限制,我现在将向您展示。

PowerShell 2.0 和 4.0 提供某种形式的作业,使您能够运行后台作业。例如,当您有多台计算机并希望通过 PowerShell 获取每台计算机的信息时,这可能很有用。线程在数百万种场景中都很有用。

即使对于这样一个简单的命令,PowerShell 也会创建一个新进程、一个新的 runspace,并通过“宿主”进程通过 IPC 进行通信: 

start-job { gwmi win32_process }   

另一个详细示例

start-job { get-process }

将使用 WinRM 的 IPC 通道来运行,并且不需要管理员权限。

第二个

invoke-command -scriptblock {get-process} -computer localhost -asjob

同时使用 HTTP 和 WinRM,需要管理员权限。

PowerShell in the task manager

这可能被认为是过度设计。此外,您无法通过 .NET 机制控制作业。

因此,我提供了一个简单的库,可以通过 .NET 启动线程,不会创建第二个进程,所需的通信开销要低得多,并且如果您有一个复杂的任务,CPU 不会过载。

方法 #1 

function CreateThreadStart ([ScriptBlock]$scriptBlock)
{
    $ps_s=$scriptBlock.GetPowerShell()
    $smi=($ps_s.gettype().getmethods()| ? {$_.name -eq "Invoke"})[0]$fp = 
                                           $smi.methodhandle.getfunctionpointer
    $ts=new-object threading.threadstart $ps_s,$fp
    return $ts
}

function CreateThread ([ScriptBlock]$scriptBlock)
{
    return new-object threading.thread (CreateThreadStart $scriptBlock)
}

这种方法非常简单,它只是获取函数指针并创建一个 System.Threading.ThreadStart 类型的对象。

但是您会受到限制,因为 PowerShell(即 GetPowerShell() 方法)只允许在脚本块中传递 **一个** 参数。

这会起作用

[System.Threading.Thread] $thread = (CreateThread {Get-Service})
$thread.Start()

这不会起作用

# Error: "Can only convert a script block that contains exactly one pipeline 
  or command. Expressions or control structures aren't permitted. Make sure t
  he script block contains exactly one pipeline or command."
[System.Threading.Thread] $thread = (CreateThread {Get-Service;Get-Service})
$thread.Start()

方法 #2 

创建您自己的 PSHostPSHostUserInterface

这可以通过创建您自己的 .NET DLL 来完成。如果您在可能的审计等方面遇到问题,或者只能编写脚本,那么请使用 PowerShell 的 Add-Type,您将能够在 PowerShell 中完成您能想象到的一切。该脚本仅为示例(因此 **不** 完美),并且是为了演示 .NET 和 PowerShell 交互的一些功能而编写的。

如何包含 PowerShell 库

“库”这个词可能用错了;它只是一个为了简单起见可以包含的脚本。可包含的 Threading.ps1 脚本可在 PowerShell 2.0 和 3.0 下运行。只需将以下代码放在主脚本的开头,或者直接放在 param 部分之后(如果适用),以包含 Threading.ps1 脚本或库,例如:

[array]$loads = "Threading.ps1"
foreach ($load in $loads)
{
    If (!($loadedlibs -like "*$load*"))
    {
           . (Join-Path (Split-Path -parent 
             ($MyInvocation.MyCommand.Definition)) 
              -childpath "$($load)")
    }
}

[System.Guid]$guid = StartThread -scriptBlock { Write "Application"; Sleep 2; 
                         Write "Application 2"; Get-Service; }
GetThreadOutput
Start-Job {Sleep 2000}
StopThread

示例输出

Application
Application 2

Status   Name               DisplayName
------   ----               ----------- 
Stopped  aspnet_state       ASP.NET State Service
Stopped  COMSysApp          COM+ System Application
Running  CryptSvc           Cryptographic Services
Running  CscService         Offline Files
Running  DcomLaunch         DCOM Server Process Launcher
Stopped  defragsvc          Disk Defragmenter
Running  Dhcp               DHCP Client
Running  Dnscache           DNS Client
...

Id   Name   State    HasMoreData   Location    Command
--   ----   -----    -----------   --------    -------
3    Job3   Running  True          localhost   Sleep 2000
True

就是这样。

© . All rights reserved.