PowerShell 内部机制和 PowerShell 线程






4.90/5 (39投票s)
本文将向您展示 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.dll、msvcr100.dll 等)。
这些信息使我们知道 PowerShell.exe 本身并非用 .NET 编写。但是 PowerShell 如何与 .NET 协同工作呢?
答案很简单。PowerShell 使用 mscoree.dll 的 CorBindToRuntimeEx
函数。
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。
通过更改 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.ConsoleHost 和 System.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
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)
与 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 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,需要管理员权限。
这可能被认为是过度设计。此外,您无法通过 .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
创建您自己的 PSHost
和 PSHostUserInterface
。
这可以通过创建您自己的 .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
就是这样。