Chapter 13 远程处理:一对一及一对多

PowerShell的远程处理类似于Telnet,即命令在远程主机上运行,结果返回到本地。但是它采用的通信协议不同:WS-MAN (Web Services for Management),基于HTTP或HTTPS,其基于的后台服务为Windows远程管理组件WinRM(在有的系统中该服务处于禁用状态)。PowerShell将输出对象序列化到XML中,通过网络传输,到达本地计算机后再反序列化为PowerShell中的对象。

远程处理在域环境中实现起来较容易。如果需要跨域,参看

help About_Remote_TroubleShooting

本章使用的环境为《PowerShell实战指南 Chapter 3-7》提到的环境。第七章提到的主机为域控制器,域中有一台Win7。本章的实验为WIn7通过远程管理对域控制器进行操作。

Win7在域中的相关信息如下:

域控制器的相关信息如下:

首先要在域控制器上一个以管理员权限运行的PowerShell中执行Enable-PSRemoting,来创建一个WinRM监听器:

一对一场景

你在远程计算机执行的任何命令都依赖于你的凭据(这一点通过Kerberos实现)。另外,远程计算机的执行策略会限制某些脚本的运行。如果你使用的账号在远程计算机上没有管理员权限,那么你需要使用Enter-PSSession或者Invoke-Command命令的-Credential参数去指定一个拥有管理员权限的账号。

Enter-PSSession -ComputerName WIN-F8E9GPVN2N1 -Credential "rambo\administrator"

成功获得一个Shell。我们执行一些操作:

一对多场景

例如:

Invoke-Command -ComputerName xx1,xx2,xx3 -Command {Get-EventLog Security -Newest 200 | where {$_.EventID -eq 1212}}

默认情况下,一次最多能与32台远程计算机通信。超过32台则会形成一个队列,依次执行。

(上面我们重复了对同一台计算机的命令来模拟一对多)

当然,我们也可以把计算机名放在文本文件中,通过Get-Content提取;亦或直接从Get-ADComputer中提取,这会用到我们前面学到的Select-Object -Expand

注意:

Invoke-Command -ComputerName xxx -Command {Get-EventLog -Newest 100}

上面这条命令和

Get-EventLog Seucurity -Newest 100 -ComputerName xxx

得到的结果差不多相同,但命令执行方式存在很大不同。下面的命令不通过WinRM实现,且-ComputerName提到的计算机会被顺序串行访问,而不是并发访问。一般来说,Invoke-Command更有效率。

比较下面两条命令:

Invoke-Command -ComputerName xxx -Command {Get-Process -Name Notepad} | Stop-Process
Invoke-Command -ComputerName xxx -Command {Get-Process -Name Notepad | Stop-Process}

注意大括号的位置。很明显,第二条才是我们希望的。

另外,通过远程处理获得的反序列化对象也与本地的不同,缺少了很多方法:

练习

  • 创建针对一对一连接并打开notepad.exe,会发生什么?

如果直接notepad.exe打开,则命令行会挂在那里,直到我按了CTRL-C,也可以用Start-Process notepad.exe,这样命令行不会挂起。这两种方法的确能打开记事本,在远程计算机的任务管理器中也能看到进程,但记事本本身却并不出现在远程计算机的桌面上!

Chapter 14 Windows管理规范

本章使用第七章环境。

WMI即Windows Management Instrumentation,Windows管理规范。它可能是微软提供给管理员使用的最优秀的工具之一。典型的Windows计算机包含数万个管理信息,WMI会把这些收集并整理成尽量通俗易懂的信息。

在最顶层,WMI被组织为命名空间。在WMI控件右键点击“属性”,查看“安全”选项卡可以看到其命名空间。

由于WMI命名空间在服务器与在客户机上内容不尽相同,所以下面再展示一下Win7上的WMI命名空间:

举例来说,root/CIMv2包含了所有Windows操作系统和计算机硬件信息;而root/MicrosoftDNS包含了所有关于DNS服务器的信息。在客户机上,root/SecurityCenter包含了关于防火墙、杀毒软件和反流氓软件工具的信息(如上图所示,新版本的Windows使用root/SecurityCenter2代替)。

在命名空间中,WMI被分成一系列的类,每个类是可用于WMI查询的管理单元。比如在root/CIMv2中的Win32_LogicalDisk用于保存逻辑磁盘的信息(但是即使计算机上存在某个类,也不代表计算机实际上安装了对应组件)。

我们可以试着查询一下:

可以看到,类的一个实例代表一个现实世界的事物。比如,机器上的确有两个硬盘、一个BIOS、许多后台服务。

注意,在root/CIMv2中的类名往往是Win32_CIM_开头(CIMCommon Information Model的缩写,它是WMI建立的标准)。但是在其他命名空间中,这些类名前缀很少出现。

由于不同命名空间中的类可能重名,所以在引用类时,注意带上命名空间。

所有这些实例、类等被称为WMI Repository。

我觉得从上面的操作看,WMI和PowerShell很像。

root/CIMv2中,有些类提供了修改设置的方法(因为属性是只读的,所以你必须使用这些方法来改),但是如果对应方法不存在,你就无法通过WMI来更改。(IIS团队放弃了WMI作为管理接口,转向了PowerShell的一个PSProvider,有趣)

WMI不支持类搜索,同时许多类并没有相关文档……

在PowerShell v3及后续版本中,有大量CIM命令。它们往往都是对WMI的某部分做了封装,从而隐藏底层WMI的复杂性。

探索WMI

使用来自Sapien的WMi Explorer工具(还得注册,然后免费试用45天):

让我们来试一下!假设我们现在需要查询计算机桌面图标间距的设置。它肯定和桌面有关,而且是操作系统核心部分。最终,我们在root/CIMv2找到这个Win32_Desktop类,并找到其IconSpacing属性:

这个工具的强大之处在于,它还提供了具体的操作命令:

当然,我们也可以通过PowerShell来查找:

注:以CIM_开头的通常为基本类,不能直接使用。Win_32开头的则是Windows特有的,且仅用于特定命名空间。

在PowerShell v3及后续版本中,有两种与WMI交互的方式:

  • WMI Cmdlets

Get-WmiObjectInvoke-WmiMethod。它们是遗留命令(且它们与RPC交互,这需要防火墙的支持)。

  • CMI Cmdlets

Get-CimInstanceInvoke-CimMethod。它们是新版命令,通过WS-MAN交互:

使用Get-WmiObject

常见的用法如下:

Get-WmiObject -Namespace root\cimv2 -list
Get-WmiObject -Namespace root\cimv2 -Classname Win32_Desktop

我们还可以在上一章用过的Win7上远程查询域控制器的WMI:

可以看到,在域控制器本地执行命令给出的结果中展示的属性只有5个,而远程获取到的却是全部,这是为什么呢?这就涉及到《PowerShell实战指南 Chapter 8-12》学过的内容了。

可以在这个命令中使用-Filter,但规则很麻烦。暂不学习它。

使用Get-CimInstance

它的用法与Get-WmiObject类似,但不存在-List参数,而是又一个独立命令Get-Cimclass -Namespace来获取类列表。它也没有-Credential参数,所以也许你需要用到“Chapter 13 远程处理:一对一及一对多”中的Invoke-Command

在本章开头已经展示过一些这个命令的用法,这里不再展示。

最后请注意,WMI的筛选语法和PowerShell是有差异的。

练习

  • What class could be used to view the current IP address of a network adapter? Does the class have any methods that could be used to release a DHCP lease? (Hint: network is a good keyword here.)

也可以直接用Cmdlet去获取IP,更方便。不过操作过程中有个有意思的地方:

我需要进行两遍expand过滤才能拿到值。

  • Create a table that shows a computer name, operating system build number, operating system description (caption), and BIOS serial number. (Hint: you’ve seen this technique, but you’ll need to reverse it a bit and query the OS class first, then query the BIOS second).
Get-Ciminstance win32_operatingsystem |
Select BuildNumber,Caption,@{l='Computername';e={$_.CSName}},@{l='BIOSSerialNumber';e={(get-ciminstance win32_bios).serialnumber  }} |
ft -auto

  • Display a list of services, including their current status, their start mode, and the account they use to log on.
get-ciminstance win32_service | Select Name,State,StartMode,StartName

  • Can you find a class that will display a list of installed software products? Do you consider the resulting list to be complete?
get-wmiobject -list *product

no.

Chapter 15 多任务后台作业

同步(前台)与异步(后台)的差异:

  • 同步下可以响应输入请求,异步如果遇到输入请求则会停止执行
  • 同步下遇到错误可以立即查看信息,异步需要通过其他手段获取
  • 异步下必须等待命令结束才能获取缓存的执行结果

后台任务又被称为job。

本地作业

Start-Job -ScriptBlock {dir}

注意ID的变化,每次递增2,这是因为每个作业至少都包含一个子作业。267的子作业ID为268。

本地作业也依赖远程处理系统,如果远程处理系统未启用,则无法创建本地作业。

WMI作业

《PowerShell实战指南 Chapter 14 Windows管理规范》提到,Get-WmiObject可以与一台或多台远程计算机连接。它以串行方式实现。如果计算机名称过多,则时间会长,所以我们需要把它移至后台。

Get-WmiObject Win32_OperatingSystem -ComputerName (hostname) -AsJob

该命令会针对每个指定的计算机创建一个子作业。

通过Get-Command -ParameterName AsJob可以发现相当多的命令都支持-AsJob参数。

远程处理作业

通过Invoke-Command进行。其优点在于并行,这一点我们在之前已经讨论过。

Invoke-Command -Command {Get-Process} -ComputerName (hostname) -AsJob -JobName MyRemoteJob

获取作业执行结果

Get-Job
Get-Job -Id 263 | Format-List *

从上图可以看到ChildJobs的即Job264

关于获取执行结果:

  • 必须指定ID,名称或通过Get-Job来配合管道指定对象
  • 父作业的结果,它包含所有子作业的结果
  • 正常情况下,获取一个作业的结果后,其作业缓存会被清除,无法再次获取。除非使用参数-Keep
  • 作业返回的结果可能是反序列化的对象(即不包含可用于修改的方法)
  • 如果作业失败,其失败原因会被记录在结果中

注意作业263的HasMoreData变成了False,而作业265却没有。

另外,注意作业263,我们的pwd一直是C:\,而它却是在C:\Users\Administrator\Documents执行的。所以当初如果你希望获取pwd下的列表,就应该手动指定:

Start-Job -ScriptBlock {dir (pwd)}

作业对象通过管道传输的例子:

Receive-Job -Name MyRemoteJob | Sort-Object PsComputerName | Format-Table -GroupBy ComputerName

子作业

可以发现,子作业的ChildJobs项是空的。

你可以用

Get-Job -Id 271 | Select-Object -ExpandProperty ChildJobs

来查看所有子作业。当然,也可以用Receive-Job来获取某个子作业的结果。

其他管理命令

  • Remove-Job
  • Stop-Job
  • Wait-Job

当使用脚本开启作业时,如果你希望在作业结束后脚本继续运行,可以用Wait-Job

调度作业

这一类型的作业我们在以前提到过类似的:《PowerShell实战指南 Chapter 12 学以致用》,只不过这里变成了Register-ScheduledJob。关于其用法,可以参考后面的第二个练习。

注意

不要混用前三种作业方式!

练习

  • 后台作业,寻找C:\上所有的PowerShell脚本
Start-Job -Name FindPS1 {Get-ChildItem -Recurse -Name "*.ps1"  -Path "C:\"}

  • 如何在远程计算机上运行上一个任务中的命令?
Invoke-Command -ComputerName (hostname) -Command {Get-ChildItem -Path "C:\" -Name "*.ps1" -Recurse} -AsJob
  • 创建一个后台作业,在每周一到周五早六点运行,获取计算机系统事件日志中最近的25条错误记录
Register-ScheduledJob -Name DailyError -ScriptBlock {Get-EventLog -Newest 25 -EntryType Error -LogName * | Export-Clixml (Get-Date | Out-String) + ".xml"} -Trigger (New-JobTrigger -Daily -At 6am)

我们可以按如下方式清除前面的作业命令:

Unregister-ScheduledJob -id 3

Chapter 16 同时处理多个对象

以往,对于大量管理的自动化方式都是for each枚举。事实上,PowerShell提供了三种不同的方式。

首选:“批处理”Cmdlet

我们已经见到过,比如

Get-Service | Stop-Service

它默认会对管道内的所有对象做同样的事情。再比如,我们希望改变三个服务的启动模式,VBScript的方式如下:

For Each varService in colServices
    varService.ChangeStartMode("Automatic")
Next

而在PowerShell中:

Get-Service -Name BITS,Spooler,W32Time | Set-Service -Startuptype Automatic

你甚至可以对多台计算机同时进行操作。

你可以使用-PassThru参数来输出结果,否则可能你看不到更改会不安心(下图的第一次尝试是没有结果输出的):

调用WMI

有些操作无法通过Cmdlet完成,其中的部分可以通过WMI完成。比如,我们希望对计算机上所有的Intel网卡启用DHCP(不包含虚拟网卡或其他网卡)。

我们首先查询网卡,发现有一个

Get-WmiObject win32_networkadapterconfiguration -filter "description like '%intel%'"

我们看一下对象本身是否包含了可以启用DHCP的方法:

Get-WmiObject win32_networkadapterconfiguration -filter "description like '%intel%'" | gm

OK,存在。那么我们就需要调用该方法:

Get-WmiObject win32_networkadapterconfiguration -filter "description like '%intel%'" | Invoke-WmiMethod -Name enabledhcp

测试的计算机上只有一个Intel网卡,但事实上这种WMI处理方式是可以处理多个对象的。

“如果你可以使用Get-WmiObject获取对象,就也能够用Invoke-WmiObject调用它的方法。”

当然,你也可以使用“新命令”:

Get-CimInstance -classname win32_networkadapterconfiguration -filter "description like '%intel%'" | Invoke-CimMethod -Name enabledhcp

WMI需要难以穿透防火墙的RPC通信,但WMI能够适用于老计算机。

另外,在新机器上,Get-Command *Set-Net*系列命令已经足够简单强大。

后备计划:枚举对象

当你不得不这么做的时候,你应该知道怎么做。

其用法如下:

Get-Service -Name *vm* | ForEach-Object -Process {Set-Service -StartupType Automatic -InputObject $_ -PassThru}

当然,上面的例子并不恰当,因为并不是必须用枚举。这里只是为了展示其用法。

注意,%ForEach-Object的别名。

总结

下面几种方法其功能是完全相同的:

Get-Service -Name *B* | Stop-Service
Get-Service -Name *B* | ForEach-Object {$_.Stop()}
Get-WmiObject Win32_Service -filter "name LIKE '%B%'" | Invoke-WmiMethod -name StopService
Get-WmiObject Win32_Service -filter "name LIKE '%B%'" | ForEach-Object {$_.StopService()}
Stop-Service -name *B*

如果你获取到的对象只有方法,而没有对应的Cmdlet来完成工作,那么你可能需要ForEach-Object,对象无法被通过管道传递给一个方法。

记住,可以用Get-Member查看对象的方法。

练习

  • 写至少4个命令,结束所有notepad进程
Get-Process -Name "notepad" | Stop-Process
Get-Process -Name "notepad" | ForEach-Object -Process {$_.Kill()}
Get-WmiObject -Class Win32_process -Filter "name like '%notepad%'" | Invoke-WmiMethod -Name terminate
Get-WmiObject -Class Win32_process -Filter "name like '%notepad%'" | ForEach-Object -Process {$_.terminate()}
Stop-Process -Name *notepad*

Chapter 17 安全警报

  • PowerShell不会给处理的对象额外权限(它给的最大权限也就是你使用的当前权限)
  • PowerShell无法绕过既有的权限限制
  • 通过脚本封装技术,如SAPIEM PrimalScript可以将用户凭据一起打包进exe文件,这可以使得用户在该凭据的权限下运行某些命令
  • PoweShell中的安全措施主要是“执行策略”和“代码签名”

执行策略

默认是Restricted,阻止脚本运行。

修改的方法:

  • Set-ExecutionPolicy

  • gpedit.msc

组策略会覆盖掉Set-ExecutionPolicy的设置:

  • 手动运行PowerShell时给出-ExecutionPolicy的命令行参数

可以发现,这种方式的修改会暂时覆盖组策略的设定。

Restricted

默认选项。除部分微软提供的配置PowerShell的默认脚本(其带有微软的数字签名)外,不允许执行其他脚本。

AllSigned

经过受信任的CA设计的数字证书签名后的任意脚本可执行。

RemoteSigned

可以运行本地人和脚本,以及受信任CA签名的远程脚本(远端计算机上的脚本,往往通过UNC方式访问)。在某些版本的Windows中,会区分网络路径与UNC路径,此时,本地网络中的UNC不被认为是“远程”。

Unrestricted

所有脚本都可执行。

Bypass

针对开发人员。它会忽略已经配置好的执行策略。

微软建议在执行脚本时使用RemoteSigned

数字签名

<!-- SIG # Begin signature block -->
<!-- MIIXXAYJKoZIhvcNAQcCoIIXTTCCF0kCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB -->
<!-- gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR -->
<!-- AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUquHIxmxcLxkCFtxrNl0NLF4B -->
<!-- LhmgghIxMIIEYDCCA0ygAwIBAgIKLqsR3FD/XJ3LwDAJBgUrDgMCHQUAMHAxKzAp -->
<!-- BgNVBAsTIkNvcHlyaWdodCAoYykgMTk5NyBNaWNyb3NvZnQgQ29ycC4xHjAcBgNV -->
...

签名包含两部分:

  • 签名的公司或组织
  • 脚本的加密副本

签名需要由CA颁发的证书。应当在IE浏览器的选项中配置对CA的信任。信任一个CA意味着信任其颁发的所有证书。

使用Set-AuthenticodeSignature对脚本签名。可以通过help about_signing查看更多信息(可以查询如何获取以及使用MakeCert.exe制作自己的本地安全证书)。

处理流程如下:

其他措施

  • 双击不会运行.ps1
  • 在命令行中要执行需要输入.\test而非test