Powershell学习笔记(8):在PowerShell中使用WinRM
本文在ITECN首发,未经许可严禁转载!
不知诸位是否还记得我在笔记3中提到的和WMI相关的内容,作为系统管理员手中的利器,WMI给我们带来了莫大的帮助,然而现实中我们有时无法随心所欲的使用WMI,其中的一个原因就是防火墙。
因为经历了这几年的蠕虫病毒冲击后,WMI使用的端口很有可能被防火墙阻塞掉。那么,我们就只能这样束手无策,缴械投降了么?也许以前是这样的,但是当WinRM出现后,我们又多了一种选择。
让我先来对WinRM啰嗦几句。WinRM即Windows远程管理是“WS 管理协议的 Microsoft 实施,该协议是基于标准 SOAP、不受防火墙影响的协议,允许不同供应商的硬件和操作系统相互操作。”(这段文字引用自11月的Technet杂志,点击前往)该协议使用80或者443端口来进行通信,王希老大在
Windows Vista核心技术系列之十三:Windows Vista管理新技术
这个webcast中有介绍过WinRM,我在此就不再赘述。下面要介绍的是我的测试环境。
我的客户端采用的是Vista,服务端是Windows Server 2003 R2,都是独立的工作站,这样的选择的原因仅仅是出于我自身机器性能的限制,在Vista和Windows Server 2008中WinRM都是内置的而且有相应的组策略可以调整设置,而Windows Server 2003 R2需要单独下载安装,大家可以去Microsoft下载中心搜索winrm然后下载安装。说完这些,我们开始对WinRM进行适当的配置,首先是服务端。
我们可以在命令行下输入“winrm qc”来进行快速配置
然后是客户端,我们需要修改TrustedHosts属性,否则会因为身份验证问题而失败。(理由在错误提示中已经写的很清楚了)
如果想要向TrustedHosts中添加值,那么只需运行以下命令(在提升权限的命令行下)
|
winrm set winrm/config/client @{TrustedHosts="192.168.1.10"} |
(192.168.1.10是服务端的IP)
添加完毕后,我们再测试下服务端的WinRM是否已经运行(使用winrm id 命令,使用方法可以查阅WinRM帮助)
至此,准备工作可以说是告一段落了,下面我们就开始PowerShell脚本的编写。
当然,凭空是写不出代码的,我们需要参阅相关的文档。
一份是MSDN文档,http://msdn2.microsoft.com/en-us/library/aa384423.aspx
,里面包含了VBS代码,还有一份就是我前面提到的11月份的Technet杂志上的文章,那篇文章介绍了下使用VBS来实现WinRM。有了这两份文档,我们就可以写出PowerShell脚本了,实质上就是做一些代码转换的工作。
我们先声明代表我们要访问的那台计算机名称(或者FQDN、IP)的变量,代码如下:
然后我们创建代表WinRM的com对象,代码如下:
|
$winrm = New-Object -ComObject Wsman.Automation |
接下来我们开始准备使用CreateSession来创建会话,不过在使用这个方法之前,我们需要为这个方法准备三个参数,分别是,URI,远程连接需要的验证方法参数及为远程连接准备的用户名和密码。
URI比较方便 只要用"http://"加上$computer变量即可。而远程连接需要的验证方法参数,因为我们会使用用户名和密码所以用$winrm.SessionFlagCredUsernamePassword()方法来获得该参数
下面就剩用户名和密码了,按照文档提示,我们也许只需要这么做就行了(下面代码是经过vbs到powershell转换的)
|
$objConnectionOptions = $objWsman.CreateConnectionOptions()
$objConnectionOptions.UserName = "Username"
$objConnectionOptions.Password = "Password" |
可是事实上没有我们想得那么简单,Password给我们出了一道不大不小的难题,请注意观察下图
Username给出的Definition是string,而Password则没有给出定义,因此我们直接用$objConnectionOptions.Password = "Password" 赋值语句会遇到麻烦。
赋值语句将会造成一个类型不匹配错误然后提示你输入密码,当正确的密码输入后,也能得到相关信息,我们是设置一个$ErrorActionPreference = "SilentlyContinue"就此放任不管,还是要处理这个问题?
当然,我们要处理这个问题,因为管理员输入了两次密码(第一次是使用Get-Credential cmdlet触发的),这不是一个好的用户体验。
这里我们要再次感谢MVP MOW,他为我们写了一个function来解决这个问题,而这个function实际上使用了VBS代码。(具体代码见最后整体代码部分)
然后我们就可以使用CreateSession方法创建会话了,代码如下:
|
$session = $winrm.CreateSession("http://"+$computer,$flag,$objConnectionOptions) |
解决了这个头痛的password问题,下面我们就该对我们需要的信息进行枚举了。这里我们需要一个代表我们所需要的WMI类的资源URI,就是形如以下的内容
|
$strRes = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process" |
关于这个URI我们只需关注wmi/后的部分,而如果你只对Win32*等WMI类感兴趣,那么只需关注最后一部分即可,想要获得你需要的信息只需要修改这里即可。
接下来我们使用$session.enumerate方法来枚举我们需要的信息,代码如下:
|
$results = $session.enumerate($strRes) |
下面我们尝试用$results.ReadItem()来读取一项内容,返回结果如下:
仔细观察下,我们不能发现,这是XML格式的文本,接下来就是如何处理这些文本的问题了。
首先我们用[xml]将这些文字格式化成XMLDocument,然后我们用([xml]$results.ReadItem()) | gm看下这个对象的成员
我们不难发现整个对象只有最后一个Win32_Process是我们在意的内容,那么接下来我们看看这个属性里有什么
具体命令是这个:([xml]$results.ReadItem()).Win32_Process
这个格式很接近我们用Get-WMIObject所看到的东西了,剩下的就是目前我们只是获得一个进程,我该如何获得全部进程呢?注意看那张XML格式文本的截图的第一行,AtEndOfStream属性,这是一个布尔值,如果熟悉用VBS处理文本文件的朋友很快就能反应过来,这个属性可以用在循环上,然后我们使用脚本块(&{})将循环结果作为一个整体赋值给变量(这部分代码见完整代码示例)
那么至此,我们的脚本就算大功告成了,下面给出完整代码,最后一行的格式化输出想必不用我多解释了
|
Function Get-WinRmCredentials([Management.Automation.PSCredential]$cred = (Get-Credential)){
$vbs = new-object -com MSScriptControl.ScriptControl
$vbs.language = 'vbscript'
$VBS.ExecuteStatement('Set objWsman = CreateObject( "WSMAN.Automation" )')
$VBS.ExecuteStatement('Set objOptions = objWSMan.CreateConnectionOptions')
$VBS.ExecuteStatement("objOptions.userName = ""$($cred.GetNetworkCredential().username)""")
$VBS.ExecuteStatement("objOptions.Password = ""$($cred.GetNetworkCredential().Password)""")
$VBS.eval('objOptions')
} #Powered by MOW
$computer = "192.168.1.10"
$strRes = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process"
$winrm = New-Object -ComObject Wsman.Automation
$flag = $winrm.SessionFlagCredUsernamePassword()
$objConnectionOptions = Get-WinRmCredentials
$session = $winrm.CreateSession("http://"+$computer,$flag,$objConnectionOptions)
$results = $session.Enumerate($strRes)
$services = &{do {
([xml]$results.ReadItem()).Win32_Process
}
until ($results.AtEndOfStream)}
$services | ft CSName,ProcessID,Name,CommandLine -a |
运行结果
这样我们就完成了通过PowerShell来使用WinRM的任务,也许有点麻烦,因为在PowerShell的V1版本中远程访问功能不是很强,而在不久后的V2版本中(下周会有V2的CTP版本),这个功能会大大增强,让我们期待V2的发布吧!