一身肌肉的泡面 · dmp文件如何导入sql数据库 | ...· 2 月前 · |
爱看球的签字笔 · 《听琴》 (上)-华音网· 3 月前 · |
胡子拉碴的长颈鹿 · 科普 - 国家冰川冻土沙漠科学数据中心· 4 月前 · |
谦和的跑步鞋 · Amazon.com· 5 月前 · |
才高八斗的手套 · Deployment with ...· 6 月前 · |
1 |
PS D:\> $PSVersionTable |
cmdlet, function, 工作流。均为命令,且帮助系统包含这些命令类型。
在线 https://github.com/PowerShell/PowerShell/tree/master/docs/
下载在线的文档到本地:
update-help
每个月建议来一次,确保帮助文档更新。
不能上网
save-help
完成下载,文件放 web 服务器,
update-help
指向一个内网地址。
1 |
# 相当于get-help help | more; 自动分屏显示。查看下一屏,空格即可。CTRL + C 退出分屏查看。 |
获取帮助支持 pattern,例如 需要获取 windows 事件相关的帮助
1 |
PS C:\Users\slc> help *event* |
PS C:\Windows\system32> Get-Help *
1 |
PS C:\Windows\system32> Get-Help about_* |
*event*
星号是 0 个,1 个或任意个字符。可以匹配
eventlog
, 也可以获取
Register-EngineEvent
man -k process*
1 |
Name Category Module Synopsis |
-ShowWindow 独立窗口显示帮助,方便一边调试命令,一边看帮助,和
-Online
类似,online 支持浏览器窗口查看帮助。
1 |
PS C:\Windows\system32> help Get-EventLog -ShowWindow |
-Parameter 显示命令的参数
1 |
# 所有参数 帮助 |
特定提供器的帮助
1 |
Get-Help Get-Item -Path SQLSERVER:\DataCollection |
Linux
hep <cmd>
<cmd> --help
man <cmd>
现在我们需要获取日志,明显上面的
Get-Event
就比较符合我们的要求,所以,直接
1 |
PS C:\Users\slc> help Get-EventLog |
另一个命令的帮助
1 |
Set-Location [[-Path] <System.String>] [-PassThru] [-UseTransaction] [<CommonParameters>] |
命令名称,命令名
动词-名词
格式 Set-Location
摘要,命令简要说明
语法,命令的语法
[ ]
表示可以省略
[[-Path] <System.String>]
表示整体可以省略,一旦有路径时,
-path
可以省略。
set-location <system.string>
这个即可以。
[[-Path] <System.String>]
这个整体一旦你需要给时,
<System.String>
表示必给,-path 确不需要。
CommonParameters
这个是很多命令通用的参数
1 |
PS C:\Users\slc> help *common* |
about_开头,是对命令的说明。
通用参数
help about_CommonParameters
说明,命令的详细说明
[-LogName] <System.String>
[-ComputerName <System.String[]>]
String[]
[]
表示列表值,数组,集合。 例如: a,b,c 自动识别为列表。或者文件中有以下内容, get-content 读文件之后,发送给当前参数。
1 |
Server-R2 |
1 |
PS C:\Users\slc> |
或者 如果强制给的参数,直接给命令,不给参数,自动让你填值 。
1 |
PS C:\Users\slc> get-eventlog |
parameter descriptions, examples, input and output object types, and additional notes.
1 |
PS C:\Users\slc> help Get-EventLog -full |
the help file’s NAME and SYNOPSIS sections, and all the Examples.
1 |
PS C:\Users\slc> help Get-EventLog -Examples |
parameter descriptions and examples
1 |
PS C:\Users\slc> help Get-EventLog -Detailed |
1 |
PS C:\Users\slc> help Get-EventLog -Online |
1 |
PS C:\Users\slc> help *html* |
1 |
</body></html> |
1 |
PS C:\Users\slc> help Write-EventLog |
1 |
PS C:\Users\slc> help *alias* |
1 |
PS C:\Users\slc> help *transcript* |
1 |
PS C:\Users\slc> Start-Transcript |
1 |
PS C:\Users\slc> help get-eventlog -Parameter newest |
1 |
PS C:\Users\slc> help get-service -Parameter computername |
1 |
|
1 |
PS C:\Users\slc> help out-file -Parameter Width |
1 |
PS C:\Users\slc> help out-file -Parameter NoClobber |
1 |
|
1 |
PS C:\Users\slc> New-Alias test Get-Alias |
别名只能添加命令的,不能添加命令及参数
1 |
PS C:\Users\slc> Get-Command -Noun object |
1 |
PS C:\Users\slc> help ABOUT_ARRAYS |
获取动词
get-verb
参数可以省略着写,只要惟一匹配当前选项,也可以 tab 补全选项
参数-computername 值列表 win8,server1
Linux:
1 |
ls [选项]... [文件]... |
bash-completion
原生的 PowerShell 命令行工具,读为
command-let
, .net 编写。
1 |
Get-Command -CommandType cmdlet |
函数和 Cmdlet 类似,仅 cmdlet 编写。
1 |
Get-Command -CommandType Function |
工作流是嵌入 PowerShell 的工作流执行系统的一类特殊函数。
1 |
Get-Command -CommandType Workflow |
应用程序是任意类型的外部可执行程序,包括类似 PING、Ipconfig 等命令行工具
1 |
Get-Command -CommandType Application |
对以上命令命名规则,以标准的动词开始(get-verb 获取所有动词),在动词之后紧接着一个破折号,然后是一个单数形式的名词。由于 PowerShell 允许开发人员自己命名名词,因此并没有一个“Get-Noun”的 Cmdlet 来显示所有名词。
添加 new
获取 get,别名 g
更新 set
1 |
PS C:\Windows\system32> Get-Alias -Definition Get-Service |
gsv 是 get-service 别名,可以与 get-service 一样的参数。linux 别名可以是命令+参数,windows 别名只能是命令别名。
1 |
PS C:\Windows\system32> New-Alias np .\notepad.exe |
导出别名,方便下次使用别名
1 |
# 导出 |
-compo
tab 即可
1 |
PS C:\Users\slc> (Get-Command Get-EventLog) |
参数写别名时,tab 不会生效
1 |
PS C:\Windows\system32> help Get-ChildItem |
1 |
PS C:\Windows\system32> help Get-ChildItem -Parameter path |
1 |
PS C:\Windows\system32> help Get-ChildItem -Parameter filter |
所以使用位置参数
1 |
PS C:\Windows\system32> Get-ChildItem D:\ alias.txt |
移动文件 move-item
1 |
PS C:\Windows\system32> help Move-Item -Parameter path |
将 d 盘中的 alias.txt 重命名为 alias.txt.b
1 |
PS C:\Windows\system32> Set-Location d:\ |
powershell 内部支持之外的命令,ip, ifconfig, nslookup, net … CMD 命令。仍然可用,powershell 会后台启动 cmd 来运行。
并非所有外部命令均可以工作。当参数过多时,就可能出问题 powershell v3 之后,就有一个方法
1 |
PS C:\Users\slc> C:\Windows\System32\sc.exe --% qc bits |
--%
之后的命令会不被 Powershell 解释传递给 cmd,
所以这里的参数一定不能包含变量。
ping 使用
Test-Connection
替代,ping 仍然可以工作
1 |
PS C:\Users\slc> Test-Connection www.baidu.com |
1 |
# 查看所有与进程相关的命令 方法1 |
获取进程
Get-Process
1 |
PS C:\Users\slc> help Get-Process -Online |
1 |
PS C:\Users\slc> Get-Process |
1 |
PS C:\Users\slc> Get-Verb |
1 |
PS C:\Users\slc> Get-Command -Verb get -Noun *eventlog* |
1 |
PS C:\Users\slc> *eventlog*^C |
结果,最新 100 应用程序日志
1 |
PS C:\Users\slc> Get-EventLog -Newest 100 application |
1 |
PS C:\Users\slc> Get-Command -CommandType Application |
命令类型:
Alias
:获取所有 PowerShell 命令的别名。 有关详细信息,请参阅
about_Aliases
。
All
:获取所有命令类型。 此参数值等效
Get-Command *
。
Application
:获取
路径
环境变量中列出的路径中的非 PowerShell 文件, ()
$env:path
,包括.txt、.exe 和.dll 文件。 有关
Path
环境变量的详细信息,请参阅
about_Environment_Variables
。
Cmdlet
:获取所有 cmdlet。
ExternalScript
:获取
路径
环境变量中列出的路径中的所有.ps1 文件 (
$env:path
) 。
Filter
和
Function
:获取所有 PowerShell 高级和简单函数和筛选器。
Script
:获取所有脚本块。 若要) 获取 PowerShell 脚本 (.ps1 文件,请使用
ExternalScript
该值。
Workflow
:获取所有工作流。 有关工作流的详细信息,请参阅 Introducing Windows PowerShell Workflow。
1 |
PS C:\Users\slc> Get-Alias |
新别名, np 打开记事本
1 |
PS C:\Users\slc> Get-Command *note* |
1 |
PS C:\Users\slc> New-Alias np notepad.exe |
1 |
PS C:\Users\slc> Get-Service -Name m* |
1 |
PS C:\Users\slc> help *firewallrule* |
1 |
PS C:\Users\slc> Get-NetFirewallRule -Direction inbound |
提供程序,或者说 PSProvider,可以存放数据。变量,文件系统,别名,注册表, 函数,环境。
1 |
PS D:\> Get-PSProvider |
功能 Capabilities 描述
可以使用某个提供程序创建一个 PSDrive( 驱动器 映射),由于 PSDrive 使用了提供程序,除了可以连接磁盘之外,还能连接更多的数据存储介质
1 |
# 获取已连接的驱动器 |
第 2 行表示,使用
FileSystem
提供器,创建了一个 PSDrive
C
其根是 C:\ 。
我们调用 cmdlet 命令操作 c 盘数据,C 盘就是 PSDrive 提供的,而映射的是文件系统提供器,就会读磁盘中的数据。
磁盘驱动器是最上层的对象,包含文件夹和文件。文件夹是一种容器对象,它可以包含文件以及其他文件夹。
PSDrive 同时支持多种提供者,所以对每类下的实例,均有统称。 项(item)。一个文件或者一个文件夹都叫作项。每 1 项均有属性。
对项及其属性的操作方法一般有
单独对象, item (文件,文件夹,注册表的目录,项)
项属性, ItemProperty (文件属性,注册表项的键值对)
项的中项, ChildItem
1 |
Set-Location [[-Path] <string>] [<CommonParameters>] |
目录也是项,所以 new-item, 但是项的概念可以应用于 alias, fielsystem, env, register, function. 所以这个项有类型
1 |
PS D:\> help new-item -Detailed |
1 |
PS D:\> new-item b |
-a----
第 1 个文件类型
-
表示普通文件
1 |
PS D:\> new-item -type Directory bb |
大部分项的 Cmdlet 都包含了-Path 属性,默认情况下,该属性支持通配符输入。要让路径不转义
1 |
help Get-ChildItem -full |
*
通配符代表 0 个或者多个字符,
?
通配符仅代表单个字符。你应该曾经多次使用过这两种通配符,当然你可能使用的是 Get-ChildItem 的别名 Dir。
1 |
PS D:\> Get-ChildItem *xls |
ls -lh
支持
dir
1 |
PS D:\> dir |
-LiteralPath 这个参数不可隐式赋予。如果确定需要使用该参数,必须显式申明-LiteralPath 参数
1 |
PS D:\> Get-ChildItem -LiteralPath *.xls |
1 |
PS D:\> Get-PSDrive HK* |
可以看到有 2 个提供程序映射,HKCU, HKLM
我们先进入当前用户
1 |
# 进入curent user 注册表 |
可以发现这个位置的目录,就是上面的输出
切换到 software
1 |
PS HKCU:\> Set-Location .\Software\ |
切换.\Microsoft\Windows
1 |
PS HKCU:\Software\> Set-Location .\Microsoft\Windows\ |
EnableWindowColorization : 0
修改为 1
1 |
PS HKCU:\Software\Microsoft\Windows\> help Set-ItemProperty -full |
所以更新为
1 |
PS HKCU:\Software\Microsoft\Windows\> Set-ItemProperty dwm EnableWindowColorization 1 |
1 |
# 获取属性 |
1 |
PS C:\Windows\system32> new-item -type Directory C:\Labs |
1 |
PS C:\Windows\system32> new-item C:\Labs\test.txt |
1 |
PS C:\Windows\system32> set-item C:\labs\test.txt test |
1 |
PS C:\Windows\system32> Get-PSDrive |
1 |
PS Variable:\> Set-Location env: |
1 |
PS Env:\> get-item temp |
1 |
# 过滤子项 |
管道通过传输一个命令,把其输出作为另外一个 Cmdlet 的输入,使得第二个命令可以通过第一个的结果作为输入并联合起来运行。
1 |
PS C:\Windows\system32> Set-Location d: |
ps | more, ps | less
导出: save the instance of the object, including the additional members
导入: to re-create the instance of the object from the information
1 |
PS D:\> Get-EventLog Security -Newest 10 |
运行“Get-Process”时,屏幕会显示以上结果,但是你想把内存和 CPU 的利用率整理成一些图表,需要把数据导出到 CSV 文件中,比如微软的 Excel
1 |
PS D:\> Get-Process | Export-Csv ps.csv |
Export-CSV 是一个内置的 PowerShell Cmdlet,它知道如何把通过“Get-Process”产生的常规表格转换到一个普通的 CSV 文件中。打开 csv. 进入 d 盘,双击打开。
或者记事本打开
1 |
PS D:\> notepad.exe ps.csv |
几乎可以把所有
PS D:\> Get-Command -CommandType Cmdlet get-*
即 get 方法的 cmdlet 命令,用管道传输到“Export-CSV”。
一旦信息保存到 CSV 文件,就可以轻易地以附件形式发送给同事并让其在 PowerShell 中查看。只需要用下面的命令把文件导入即可。
1 |
PS D:\> Import-Csv .\ps.csv |
Shell 会读取 CSV 文件然后展示,该输出并不是基于实时的信息,而是基于 CSV 创建时的快照。
与 csv 工作类似,xml 也可以导出 所有字段,导入方便作为附件。
Export-CliXML <FILENAME>
cmdlet,generic command-line interface (CLI) Extensible Markup Language(XML)。生成 clixml 文件。此文件是 powershell 专用的。
Import-CliXML <FILENAME>
Cmdlet
尝试导出一些信息如服务、进程或者事件日志到 CliXML 文件中。确保导出的文件可以用于导入,并且尝试使用 记事本和 IE 查看这些信息 。
1 |
PS D:\> gsv | Export-Clixml service.xml |
1 |
PS D:\> gsv | Export-Csv service.csv |
PowerShell 还提供了其他导入导出命令吗?有,可以用“Get-Command”Cmdlet 配合“-verb”参数来找到所有“Import”或“Export”的命令。
1 |
PS D:\> Get-Command -Verb export |
在展示、共享信息给别人及后续重新查看过程中,CSV 和 CliXML 文件都很有用。
Compare-Object cmdlet 用来对比,我们会用到它的别名:Diff。
获取位置参数
1 |
PS C:\Windows\system32> help Compare-Object -full | Out-String -Stream | Select-String 必需.*True -Context 5 |
额外注意一个 property 属性
1 |
PS C:\Windows\system32> help Compare-Object -Parameter property |
现在比较两次 gps 的结果
1 |
PS C:\Windows\system32> Set-Location d: |
1 |
PS D:\> Import-Clixml .\ps.clixml |
由于 compare-obect 位置参数有 2 个,第 1 是源对象,第 2 个是目标对象
相对于匹配两个完整的表格,Diff 更加关注“Name”列,所以例子中使用了“-property”这个参数。
如果我们不这样定义,结果将全部有差异,因为如“VM”“CPU”和“PM”这些列的值都不一样,结果集会被认为有差异。
源对象多的 <= 表示。目标对象多的 => 表示 。而两者均存在的,则不会出现。
现在打开记事本,再对比
1 |
PS D:\> notepad.exe |
=>
表示新出现的
现在不加 property,会看到每个单独的进程都被列出来,因为诸如 PM/VM 等的值都被更改,即使它们是相同的进程。这些输出看上去用处不大,因为它们只显示进程类型和进程名称而已。
1 |
PS D:\> Compare-Object (Import-Clixml .\ps.clixml) (Get-Process) |
Cmdlet 是直接输出到 PowerShell 所在的本地机器的屏幕上,但是你可以修改输出位置
1 |
PS D:\> Get-ChildItem > childitem.txt |
>
PowerShell 向后兼容旧版本 cmd.exe 命令的一个快捷方式。而实际上,当运行这个命令时,PowerShell 底层会以下面的方式实现。
1 |
PS D:\> help out-file -full | Out-String -Stream | Select-String '必需.*True' -Context 5 |
1 |
PS D:\> help out-file -full | Out-String -Stream | Select-String 'width' |
1 |
PS D:\> Get-Command -Verb out |
Out-Default 会自动添加给管道命令之后,自动识别命令类型,对象类型是字符就自动为
out-host
Out-Host 显示到显示器
Out-Printer, Out-GridView 也有类似功能,但需要安装.NET Framework v3.5 和 Windows PowerShell ISE 之后才有。Server 版本的 Windows 以及非 Windows 系统并不是默认就有。假设安装了,可以尝试
1 |
PS D:\> gsv | Out-GridView |
直接窗口展示
out-null 隐藏输出
1 |
PS D:\> gsv | Out-Null |
Out-String 对象转字符
1 |
PS D:\> gsv | ConvertTo-Html |
如何将转换的结果保存到磁盘?
1 |
PS D:\> gsv | ConvertTo-Html | Out-File service.html |
其他转换命令, 和上面一样的效果,不会保存到磁盘,只是显示。同意需要 out-file 来写入文件。
1 |
PS D:\> Get-Command -Verb convertto |
“Export-CSV”和“ConvertTo-CSV”,在某些高级场景中,你可能不想把结果存到磁盘文件上。比如你想把数据转换成 XML 然后传输到 Web 服务,或者其他地方。
导出和转换不是你希望连接两个命令的唯一目的,比如下面的例子,记住不要运行。
它会检索每一个进程,然后尝试逐个终止。该命令可能获取到关键进程,比如本地安全权限(Local Security Authority),你的电脑很可能进入蓝屏死机状态。如果你在虚拟机中运行 PowerShell,倒是可以尝试一下。
1 |
PS D:\> Get-Process note* | Stop-Process |
Get-Service”命令的输出结果能和其他 Cmdlets(如 Stop-Service、Start-Service、Set-Service 等)一起被管道传输。
1 |
PS D:\> gsv tzautoupdate |
1 |
PS D:\> gsv tzautoupdate | Start-Service |
1 |
PS D:\> gsv tzautoupdate | Set-Service -StartupType automatic -status running |
些 Cmdlets 以某些方式修改系统,并且有一个内部定义的影响级别(impact level),$ConfirmPreference”设置,默认为“High”.当Cmdlet的内部影响级别大于等于Shell的“$ConfirmPreference”设置时,不管 Cmdlet 正准备做什么,Shell 都会自动询问“你确定要这样做吗?(Are you sure?)”。当 Cmdlet 的内部影响级别小于 Shell 的“$ConfirmPreference”设置时,不会自动弹出这个提示。
1 |
PS D:\> $ConfirmPreference |
如果使用虚拟机,可以尝试
gps | stop-process
, 可以发现部分进程会提醒 are you sure?
如果你想每次运行均可以提醒,
-confirm
1 |
PS D:\> gps | Stop-Process -Confirm |
另一个通用参数
whatif
1 |
PS D:\> gps | Stop-Process -WhatIf |
它会告诉你哪些 Cmdlet 会被执行,但是并不真正运行。这个功能为那些可能有潜在风险的 Cmdlet 的预览提供了很好的帮助,并且可以检查是否是你想要的结果。
get-content 读 csv 是逗号分隔的,而 clixml 是 xml 格式。想要查看比较友好的格式,
export-
之后,
import-
,可以让 powershell 解析。
1 |
PS D:\> Get-EventLog Security -Newest 5 | Export-Clixml event.xml |
1 |
PS D:\> Import-Clixml .\event.xml |
当使用 export-对应的 import-时,就可以看到友好的内容。powershell 会帮你解析了。解析之后,显示的字段会变少。
1 |
# 读出所有结果 |
1 |
# 查看字段, name |
1 |
PS D:\> Compare-Object (Import-Csv .\ps.csv) (Get-Process) -Property name |
1 |
PS D:\> gsv | export-csv services.csv | out-file |
1 |
PS D:\> help Stop-Service -full | Out-String -Stream | Select-String '必需.*TRUE' -Context 5 |
stop-service <服务名>
1 |
PS D:\> Get-Service tzautoupdate |
1 |
PS D:\> help export-csv -Parameter delimiter |
export-csv <file> <分隔符>
1 |
PS D:\> gps | Export-Csv service.csv.2 '|' |
1 |
PS D:\> help export-csv -full | Out-String -Stream | Select-String 'notype' -Context 3 |
1 |
PS D:\> gps | export-csv -NoTypeInformation service.svc.3 |
1 |
PS D:\> help export-csv -Parameter noclobber |
1 |
PS D:\> help export-csv -full | Export-Csv -NoClobber help.csv |
1 |
PS D:\> help export-csv -full | Export-Csv -Confirm help.csv |
1 |
PS D:\> help export-csv -Parameter useculture |
1 |
PS D:\> gsv | export-csv -UseCulture service.csv.4 |
大多数产品也会安装自己的预配置 MMC 控制台文件,安装过程仅是加载了基本的 MMC 和预加载一个或两个管理单元。你没有必要必须使用这些预配置控制台,因为你总能打开一个空白的 MMC 控制台,并加载你需要的管理单元。例如,预配置的 Exchange Server MMC 控制台不包括活动目录站点和服务的管理单元,但你可以很容易地创建一个包括 Exchange 以及站点和服务的 MMC 控制台。
PowerShell 的工作原理几乎与 MMC 完全一致。安装一个给定产品的管理工具(安装管理工具的选项通常包含在产品的安装菜单中。如果你在 Windows 7 上安装类似 Exchange Server 的产品,该安装会仅安装管理工具)。这样做会为你提供 PowerShell 的相关扩展,它甚至可能会创建该产品特定的 Shell 管理程序。
这些管理特定产品的 Shell 程序的来源很混乱。我们必须澄清:只有一个 Windows PowerShell。并不存在 Exchange PowerShell 和活动目录 PowerShell,只有一个 Shell。以活动目录为例,在 Windows Server 2008 R2 域控制器的开始菜单、管理工具下,你会发现一个关于活动目录组件的 Windows PowerShell。如果在这一项单击右键,然后从上下文菜单中选择属性,第一眼就可以看到类似如下的目标域。
该命令运行标准的 PowerShell.exe 应用程序,并指定命令行参数运行特定命令:Import-Module ActiveDirectory。执行的效果是可以预加载活动目录。你仍然可以打开一个“标准”的 PowerShell 并执行相同的命令,从而得到相同的功能。你可以找到同样适用于几乎所有特定于产品的“管理 Shell”:Exchange、SharePoint 等。查看这些产品开始菜单快捷方式的属性,你会发现,它们都是打开标准的 PowerShell.exe,并以传递一个命令行参数的方式添加一个模块、增加一个管理单元或者加载一个预配置控制台文件(该控制台文件是一个包含需要自动加载管理单元的简单列表)。
SQL Server 2008 和 SQL Server 2008 R2 却是例外。它们“产品相关”的 Shell 叫作 Sqlps 。它是一个经过特殊编译专门运行 SQL Server 扩展的 PowerShell。通常称之为 mini-Shell。微软第一次在 SQL Server 中尝试这种方法。但这种方法已经不流行了,并且微软不会再使用这种方法了: SQL Server 2012 使用的是 PowerShell。
你不只局限于使用预先设定的扩展。当你打开 Exchange 的管理 Shell 程序,你可以运行 Import-Module ActiveDirectory 并假设该活动目录模块已经存在于你的电脑,添加活动目录功能到 Shell 中。你也可以打开标准的 PowerShell 控制台并手动添加你想要的扩展。
你可以在一个 Shell 中包含所有你想要的功能,而在开始菜单中,特定产品的快捷方式不会以任何方式限制或暗示你这些产品存在特殊版本的 PowerShell。
PowerShell 存在两种类型的扩展: 模块 和 管理单元 。首先讲述管理单元。
一个适合
管理单元PowerShell的名字是PSSnapin
,用于区别这些来自管理单元的图形 MMS。PSSnapins 在 PowerShell v1 版本的时候就已经存在。一个 PSSnapin 通常包含一个或多个 DLL 文件,同时包含配置 XML 文件和帮助文档。PSSnapins 必须先安装和注册,然后 PowerShell 才能识别它的存在。
注意:
PSSnapin的概念逐步被微软移除了
,将来可能会越来越少出现。在内部,微软的重点是提供扩展模块。
你可以通过在 PowerShell 中运行 Get-PSSnapin -registered 命令获取到一个可用的管理单元列表。由于我在域控制机器上安装了 SQL Server 2008,所以执行命令的返回结果如下。
1 |
PS D:\> Get-PSSnapin -Registered |
安装 sql server https://www.microsoft.com/en-us/sql-server/sql-server-downloads
下载 express, 安装过程, https://www.youtube.com/watch?v=gANpYPM_PTQ
运行 sql server studio
1 |
PS D:\> Get-Module -ListAvailable |
上面的信息说明我的机器上安装了两个可用的管理单元,但是并没有加载。你可以通过运行 Get-PSSnapin 命令查看已加载的列表。该列表包含所有的核心,包含 PowerShell 中的本机功能的自动加载管理单元。
通过运行 Add-PSSnapin 并指定管理单元名称的方式加载某一个管理单元。
1 |
PS C:\Users\21923> Get-PSProvider |
安装 sql server 最新之后,不需要加载模块,直接就有 drive
1 |
PS C:\Windows\system32> Get-PSDrive |
获取 sqlserver 子项
1 |
PS SQLSERVER:\> Get-ChildItem SQLSERVER:\ |
模块被设计得更加独立,因此更加容易分发,但是它的工作原理类似于 PSSnapins。但是,你需对它们有更多了解,这样才能找到和使用它们。
PowerShell 会在环境变量 PSModulePath 定义的位置查找模块。
1 |
PS C:\Windows\system32> get-item Env:\PSModulePath |
PSModulePath 并不能在 PowerShell 中修改,它是你操作系统环境变量的一部分。你可以在系统控制面板中对它进行修改,或者通过组策略修改。一些微软或第三方产品可能会修改该变量。
可以使用 Get-Module 命令检索一个远程服务器的可用模块列表,还可以使用 Import-Module 加载一个远程模块到当前 PowerShell 会话
即使在模块还没有显式加载到内存的情况下,PowerShell 依然可以自动发现模块从而使得 Shell 完成命令名称自动补全(在控制台使用 Tab 按钮,或者使用 ISE 的智能提醒)、显示帮助和运行命令。该特性使得保 PSModulePath 环境变量的完整和最新很有必要。
假设你加载的两个模块中都包含了 Get-User 这个 Cmdlet 命令。你运行 Get-User 时,执行最后一个加载模块的命令。有冲突
1 |
模块\动词-名词 |
如果你已经对冲突不厌其烦,你可以随时选择删除冲突的扩展名。你需要运行 Remove-PSSnapin 或 Remove-Module,并指定管理模块或模块命令的名称,从而卸载某个扩展。
我们的目标是 清除我们计算机上的 DNS 名称解析缓存 。我们还不知道 PowerShell 是否能做到这一点,所以我们先要在帮助系统中寻找一些线索。
1 |
PS D:\> help *dns* dnsn Alias Disconnect-PSSession |
可以发现均属于 DnsClient, NetworkTransition 2 个模块。
面的列表中显示了 Clear-DnsClientCache 命令,但是我们好奇还有其他哪个命令可用。为了找出该命令,我们手动加载该模块并列出所有命令。
1 |
PS D:\> Get-Module -ListAvailable dnsclient |
获取模块的命令
1 |
PS D:\> Get-Command -Module DnsClient |
1 |
PS D:\> help Clear-DnsClientCache -full |
看上去没有必要的参数,直接运行
1 |
PS D:\> Clear-DnsClientCache |
通常来说,没有消息就是最好的消息。尽管如此,我们更愿意看到该命令所完成的工作。尝试使用下面的命令。
1 |
PS D:\> Clear-DnsClientCache |
1 |
PS C:\Users\21923> Get-PSProvider |
接下来运行 powershell, 直接双击运行文件也可以
1 |
PS C:\Users\21923> powershell.exe -PSConsoleFile D:\console.psc1 |
不会加载模块,PowerShell 会自动加载 PSModulePath 环境变量的其中一个路径中的模块。
1 |
PS C:\Users\21923> $profile |
准备文件 profile.ps1
1 |
PS D:\文档\WindowsPowerShell> new-item profile.ps1 |
在刚刚创建的文本文件中输入 Add-PSSnapin 和 Import-Module 命令,以一行一个命令的格式加载管理单元和模块。
此处我只有模块, 加载
1 |
PS D:\文档\WindowsPowerShell> "Invoke-Sqlcmd" | Out-File -Append .\profile.ps1 |
让 powershell 允许脚本执行, 管理员身份运行
1 |
Set-ExecutionPolicy RemoteSigned |
重启 shell
1 |
PS C:\Users\21923> Get-PSProvider |
可以看到 SQL 按预期有了
1 |
PS C:\Users\21923> $profile |
现在可以发现你每次启动 powershell 会自动进入 d 盘了
微软引入了一个名称为 PowerShellGet 的模块,这使得从在线仓库中搜索、下载、安装、升级模块变得容易了。微软甚至还维护一个在线源,称为 PowerShell Gallery( http://powershellgallery.com)
微软维护并不意味着微软生产、验证与支持。PowerShell Gallery 包含社区贡献的代码,在你的环境执行别人的代码需要小心。
该命令由 powershell 5, 提供。
1 |
PS D:\> $PSVersionTable |
1 |
# 获取 |
1 |
# 查找 |
查看帮助的时候不使用-example 或者-full 开关。
内建的示例是学习使用一个命令的最好方式
你需要寻找“实例 ID”敲入回车键,运行 Web 连接测试,并且从一个指定的页面中寻求帮助。使用 http://videotraining.interfacett.com作为你的测试地址。我们希望你获取的返回信息是“没有发现问题”,这意味着你成功地运行了该检查。
获取到故障诊断包 troubleshooting kit
1 |
PS D:\> help *troubleshoot* |
看到
Get-TroubleshootingPack
1 |
PS D:\> help Get-TroubleshootingPack -Examples |
1 |
PS D:\> Get-TroubleshootingPack -Path "C:\Windows\Diagnostics\System\Audio" |
执行故障诊断包
1 |
PS D:\> help *troubleshoot* |
1 |
PS D:\> Get-TroubleshootingPack -Path "C:\Windows\Diagnostics\System\Audio" | Invoke-TroubleshootingPack |
找到这些包所处的位置和它们的名称
1 |
PS D:\> Get-Module -ListAvailable *troubleshoot* |
1 |
PS D:\> Get-Command -Module TroubleshootingPack |
帮助中,有路径
1 |
PS D:\> Get-ChildItem C:\Windows\Diagnostics\System |
1 |
PS D:\> invo-^C |
Get-Process、Get-Service、Get-EventLog 或其他命令也是一样,显示并不完整。
当转换为其它格式,就会显示完整列。每列均有操作方法,进程,关闭,杀死,刷新,等待进程退出。
1 |
PS D:\> Get-Process | ConvertTo-Html | Out-File processes.html |
属性,每一列
方法,每一行,即一个对象支持的操作
集合,多个对象的集合,叫表。
当不面向对象时
,linux 中要 kill 一个进程,需要 ps ,过滤进程 test,取出 id, 再调用 kill。或者 pkill test. UNIX 和 Linux 管理员的工作, 他们花费大量的时间学习如何更好地解析文本,使用类似 Grep、Awk 和 Sed 等工具,并必须熟练使用正则表达式。UNIX 和 Linux 从业人员喜欢
类似Perl的语言
,因为该语言
包含丰富的文本解析和文本操作
方法。
基于文本操作系统
当 windows 面向对象时
,gps test | kill-process 就直接停进程了。由于对象的工作机制类似内存中的表,因此你无须告知 PowerShell 信息所在的文本位置,而是仅仅需要输入列名。无论在屏幕或文件中如何组织输出结果,PowerShell 都知道去哪里获取数据,内存表总是同一个,因此你永远都不需要由于移动列而重写命令。你必须学习一些使得你
可以构建PowerShell属性
的语法。
基于 API 的操作系统
windows 对象,例如 get-process,显示只是对象部分,那么如何看到其他你需要使用的属性呢?get-member 别名 gm
1 |
PS D:\> Get-Alias -Definition get-member |
通过管道将对象向下一级传递, 终端上显示时才会把对象抽取部分属性显示。
linux 基于文本,管道传递的是文本。
1 |
PS D:\> Get-Process | gm |
一个对象的属性、方法以及其他附加到对象的东西都被称为成员。所以获取对象的成员, get-member。PowerShell 中的惯例是使用单数名词,所以 Cmdlet 的名称为 Get-Member,而不是“Get-Members”。
请注意 Get-Member 输出结果的第一行,这一行很容易被忽视。这一行是 TypeName,是分配给特定类型对象的唯一名称。它现在看起来好像并不重要——毕竟,谁会关心它的名称呢?但该名称将会在下一章成为关键内容。
.Net Framework 中的对象——也就是所有 PowerShell 对象的来源——只包含“属性”。PowerShell 会动态添加其他内容:ScriptProperty、NoteProperty、AliasProperty 等。如果你正好在微软的 MSDN 文档中查看某个对象类型(你可以将对象的类型名称输入 MSDN 的搜索框),你无法找到这些额外的属性。
PowerShell 有一个扩展类型系统(ETS)负责添加这些后来的属性,
这些属性,使用方法一样的。
进程对象的 ID 属性可能是 1234,对象的名称属性的值可能是 NotePad。属性用于描述关于对象的某些方面:它的状态、它的 ID、它的名称等。
在 PowerShell 中,属性通常是只读的,意味着你无法通过给 Name 属性赋一个新值来改变服务的名称。但你可以通过读取 Name 属性获取服务的名称。我们估计你在 PowerShell 中 90%的工作都需要与属性打交道。
进程对象包含一个 Kill 方法,它会终止进程。通用的方法不需要参数,极少数的方法才需要参数。
1 |
PS D:\> notepad |
1 |
PS D:\> notepad |
关于对象方法的知识。但除了属性和方法之外,对象还有一个事件。事件是以对象的方式通知你某些事情发生了。
可以在进程结束时触发 Exited 事件。你可以将你自己的命令附加到这些事件上,比如说,当进程结束时发送一封邮件。以这种方式和事件交互是高级主题,并且超出了本书的范畴。
对象集合,按默认排序。例如,服务和进程都按照字母表顺序对名称进行排序。事件日志倾向于按照事件排序。那么假如我们希望改变排序方式,该如何做?
假设我们希望显示一个进程列表,按照对虚拟内存(Vitrual Memory,VM)的消耗由高到低进行排列。我们将需要基于 VM 属性对列表进行重新排序。PowerShell 提供了一个简单的 Cmdlet、Sort-Object,就像其名称那样,可以对对象进行排序。
1 |
# 对象属性 |
基于属性排序
sort-object -property
, 帮助可以看到,这个属性参数可以省略
1 |
|
1 |
# 名称正序 |
cpu 由大到小
1 |
PS D:\> Get-Process | Sort-Object cpu -desc | more |
虚拟内存和 cpu 排序 ,内存相等时按 cpu 排序
1 |
PS D:\> Get-Process | Sort-Object vm,cpu -desc | more |
1 |
# 获取属性 |
显示对象所有字段
1 |
PS D:\> Get-Service | ConvertTo-Html | Out-File services.html |
现在我想要名称 name,status,starttype
1 |
PS D:\> Get-Service | Select-Object name,status,starttype | ConvertTo-Html | Out-File service2.html |
查看进程属性
1 |
PS D:\> Get-Process | ConvertTo-Html | Out-File ps1.html |
通过查看浏览器的 html 后,我只想要 id,name,WorkingSet,VirtualMemorySize,path,cpu,description,product, cpu/ram 逆序
1 |
PS D:\> Get-Process | select id,name,WorkingSet,VirtualMemorySize,path,cpu,description,product | Sort-Object ram,cpu -desc | ConvertTo-Html | out-file ps2.html |
1 |
PS D:\> Get-Process | select id,name,WorkingSet,VirtualMemorySize,path,cpu,description,product | Sort-Object ram,cpu -desc | Select-Object -First 10 | ConvertTo-Html | Out-File ps4.html |
1 |
# 过滤属性 |
高 cpu, 内存的进程
1 |
Get-Process | Sort-Object ram,cpu -desc | where cpu -gt 1 |
在一个命令行中,管道可以包含不同类型的对象
1 |
PS D:\> Get-Process | |
1 |
PS D:\> Get-Process | Sort-Object vm -desc | Select-Object name,id,vm |
当 PowerShell 发现光标已经到达命令行结尾时,它必须知道如何对文本输出结果进行排版。这是由于管道中包含的对象不再是进程对象,PowerShell 不会再将默认规则和配置应用于进程对象,而是通过查询 PSObject 的规则和配置,这也是当前管道中包含的配置类型。由于 PSObjects 用于自定义输出,微软并没有为 PSObjects 提供任何规则或配置。而是 PowerShell 将尽最大努力进行猜测并产生表。在理论上,产生的表可以容纳上述 3 列信息,但表并不像正常的 Get-Process 输出结果那样有美观的排版,这是由于 Shell 缺少使得表更美观的额外的配置信息。
查看管道对象
1 |
PS D:\> Get-Process | Sort-Object vm -desc | Select-Object name,id,vm | gm |
1 |
PS D:\> Get-Process | Sort-Object vm -desc | gm |
1 |
PS D:\> Get-Process | Sort-Object vm -desc | Select-Object name,id,vm | gm | gm |
PowerShell 会展示出管道中对象的类型名称作为 Gm 输出结果的一部分, 所以上面管道 2 个对象是
TypeName:Selected.System.Diagnostics.Process
TypeName:System.Diagnostics.Process
运行 gm 之后的对象,不再包含进程对象,而是
TypeName:Microsoft.PowerShell.Commands.MemberDefinition
掌握 PowerShell 的一个关键点是在任意时间点知道当前管道中的对象类型。Gm 可以帮助你实现这一点,但自己将整个命令从头到尾过一遍将会更好地帮助你理清头绪。
1 |
PS D:\> Get-Process | GM | Select-Object -First 1 |
powerShell 帮助文件不包括有关对象属性的信息。你必须将对象利用管道传输给 Gm(Get-Member)从而查看属性列表
你可以在产生结果的任意管道末尾添加 Gm 命令。类似 Get-Process-name Notepad | Stop-Process 的命令行正常情况下不产生结果,所以将|Gm 置于管道末尾不会产生任何结果。
注意输入的整洁性。请在管道操作符两边加入空格,这是由于命令行看起来更像 Get-Process | Gm,而不是 Get-Process|Gm。在这里添加空格是有原因的,请使用空格。
管道中在不同阶段可以包含不同类型的对象。请考虑当前在管道中的对象类型是什么,并 把精力集中在下一个命令对当前类型的对象所做的操作 。
1 |
# 获取帮助 |
1 |
PS D:\> Get-Random 1000 |
1 |
# 从模块找 |
1 |
# 获取命令 |
1 |
PS D:\> help get-date |
1 |
PS D:\> get-date |
1 |
PS D:\> get-date | Get-Member | Select-Object -First 1 |
TypeName:System.DateTime
1 |
|
1 |
PS D:\> get-date -DisplayHint date |
1 |
PS D:\> help get-date -Online |
1 |
PS D:\> get-date -UFormat "%A" |
或者查看时间对象的属性
1 |
PS D:\> get-date | gm |
1 |
PS D:\> Get-Command -Verb get -Noun hotfix |
1 |
PS D:\> get-hotfix |
1 |
# 属性 |
1 |
# 描述、补丁ID、安装日期列 |
1 |
# 安全事件日志中显示最新的50条列表 |
通过学习如何使用管道,你可以更高效地完成某项工作,而无须编写脚本。
本章的宗旨是在尽量键入较少命令的前提下,让 Shell 完成更多的工作。可以想象,你会很惊讶地发现,该 Shell 可以非常完美地实现这些功能。
1 |
PS D:\> notepad |
拿到进程之后,获取其对象
Process
,
所以现在我们拿到一个进程集合,里面只有 1 个进程对象
1 |
|
一般同名的名词,对应的其他操作可以管道传输数据。
1 |
PS D:\> Get-Command -Noun Process |
现在我们停止这个进程,查看帮助
[-InputObject] <System.Diagnostics.Process[]>
输入可以
1 |
PS D:\> help Stop-Process -full |
注意
是否接受管道输入? True (ByValue)
可以管道接收,而且
System.Diagnostics.Process[]
是进程对象集合。
byvalue
获取。
即依据管道传递来的对象,自动查找可以接收此对象的参数。
看另一个命令 get-service, 有 3 个命令支持管道输入,
1 |
PS D:\> help get-service -full | Out-String -Stream | Select-String 管道.*True |
服务名和计算机名均接受字符串,只有 name 可以 byvalue, 直接从管道传递的值自动获取,而计算机名就需要 ByPropertyName 了。
首先看
-Name <System.String[]>
表示管道传递给 get-service 的
字符列表
,
自动当成服务名
1 |
# 字符列表 |
虽然没有找到服务,但是提示可以看出是传递服务名
1 |
# 字符列表 |
-InputObject <System.ServiceProcess.ServiceController[]>
表示输入服务对象时自动会传递给这个
1 |
# 获取对象 |
获取管道传递来的
对象 System.ServiceProcess.ServiceController
的属性,可以
看到name属性
,
1 |
PS D:\> Get-Service | gm |
获取下一个命令 stop-process 的帮助,可以发现与属性名相同的是
-Name
1 |
PS D:\> help Stop-Process |
之后看此参数是否支持接收来自 ByPropertyName 管道的输出, 通过以下看,
stop-process的-name参数支持ByPropertyName管道的输出
1 |
PS D:\> help Stop-Process -Parameter name |
ByPropertyName 与 ByValue 管道只能使用一个参数不同,ByPropertyName 会将每个匹配的属性与参数进行关联(提供的每个参数都可以接收来自 ByPropertyName 管道的输出值)。 在这个示例中,只有 Name 属性与-Name 参数匹配
1 |
PS D:\> Get-Service xls* | Stop-Process |
通过输出可以发现管道已自动将前面服务对象的属性
name
与后面命令的参数
-name
(支持 bypropertyname)已经关联起来了。所以停止进程 XLServicePlatform ,但是进程没有这个进程,所以就报错了。
1 |
name,value |
1 |
PS D:\> Import-Csv .\my.csv |
获取对象
System.Management.Automation.PSCustomObject
自定义对象
1 |
PS D:\> Import-Csv .\my.csv | Get-Member |
可以看出 列名称是属性,每 1 行是一个对象。
现在查看 new-alias 帮助
1 |
PS D:\> help new-alias |
Name 和 Value 属性都可以关联到 New-Alias 的参数名称。当然,这里是特意实现的(因为你可以将 CSV 文件的列任意命名)。现在我们可以检查 New-Alias 的-Name 和-Value 参数是否可以接收来自 ByPropertyName 管道的输出结果
现在检查这 2 个参数支持 bypropertyname
1 |
PS D:\> help New-Alias -Parameter name |
经过查看,两个参数都可以接收管道输入,也就证明下面的语句可以正常工作
1 |
PS D:\> import-csv .\my.csv | New-Alias |
执行之后,会产生 3 个新的别名,名为 d、sel 和 go,分别对应 Get-ChildItem、Select-Object 和 Invoke-Command 命令
1 |
PS D:\> Get-Alias d,sel,go |
解决上面将字符串传递给 get-service, 让其只识别为计算机名
1 |
PS D:\> "computername, |
-name *
1 |
PS D:\> $cs = 1 | Select-Object computername |
手工绑定 name, computername 绑定不了…
我们会介绍一个之前未使用过的命令 New-ADUser。该命令属于活动目录中的一个模块,它存在于 Windows Server 2008 R2 及之后的版本操作系统的域控制器中。
另外,你也可以在安装了微软的远程服务器管理工具(Remote Server Administration Tools, RSAT)的客户端电脑上找到该组件。
1 |
PS D:\> import-csv .\computer.csv | Select-Object *,@{name='abc'; expression={$_.computername}},@{label='abc1'; expression={$_.computername}},@{n='abc3'; e={$_.computername}} |
Select-Object *
所有属性
*
之后的
,
就意味着我们还会输入其他的一些属性列
我们创建一个哈希表,哈希表的结构是以@{为起始,以}为结尾。哈希表中包含了一个或者多个成对的键-值(Key-Value)数据。
elect-Object 需要寻找的
Name、N
、
Label或者L
,该键对应的值也就是我们想创建的属性的名称。
到现在为止,已完成的步骤包括获取 CSV 文件的内容(Import-CSV 的输出结果),之后在管道中动态地修改该内容。最后新的数据输出结构能与 New-ADUser 命令期望的格式一致,这样我们就可以使用下面的命令创建新的 AD 用户了。
从语法上看有点丑,但技术本身非常强大。在 PowerShell 的其他地方也可以使用该命令,后续章节中会有类似示例。甚至你可以在 PowerShell 的帮助文件的示例中看到这种命令:运行 Help Select –Example 命令并查看。
Get-WMIObject 命令接收计算机名不接收管道的计算机名称,那么我们应该如何将其他来源的数据(比如一个文本文件,其中每行数据代表一个计算机名称)传递给该命令呢?
1 |
PS D:\> help Get-WmiObject -Parameter computername |
可以使用()
1 |
PS D:\> Get-WmiObject -class win32_bios -ComputerName (Get-Content .\computer.csv | Select-Object -Skip 1 ) |
由于-Computer Name 能够接收 String 类型的对象,所以此时,整个命令可以正常执行。
如果有大量的计算机可以用来做测试,那最好不过。将正确的机器名称和 IP 地址写入到一个 computers.txt 文件中。如果是在域环境中(在域环境中,计算机的权限变更会非常容易),那么会测试得更顺利。
1 |
PS D:\> "192.168.0.109","test" | Out-File ips.txt |
括号命令功能非常强大,因为它根本不依赖于参数管道绑定——它会将获取的对象强制匹配到正确的参数。但是如果括号中输出的对象类型和需要绑定的参数类型不一致,也会存在问题。此时,我们需要手动做一些修改。
该命令包含在一个名称为 ActiveDirectory 的模块中。正如前文提到的,在 Windows 2008 Server R2 以及之后版本操作系统的域控制器上,或者在域中某一台已经安装 RSAT 的客户端计算机上都存在该模块。
通过 Get-Member 命令,我们可以看到 Get-ADComputer 命令的输出结果是 ADComputer 类型的对象,而不是 String 类型的对象。所以-ComputerName 这个参数不知道该如何处理这部分数据。但是 ADComputer 类型的对象包含了一个-Name 的属性。接下来我们要做的是,提取出 ADComputer 类型对象中的-Name 属性值,然后将这些值(也就是计算机名称)传递给-ComputerName 参数。
我们可以通过 Get-Member 命令确认 Get-ADComputer 命令的输出结果是 ADComputer 类型的对象;但是查看帮助文档,-ComputerName 这个参数只能接收 String 类型的对象,而无法处理 ADComputer 类型对象。因此,前面那个包含括号的命令无法正常执行。
1 |
PS D:\> Get-Service (Get-Service xbl* | Select-Object name) |
我们可以使用 Select-Object 命令解决这个问题,因为它包含一个可以接收属性名称的参数-ExpandProperty。它会获取对应的属性,提取属性的值,然后返回这些值(作为 Select-Object 的输出结果)。
1 |
# 提取对象集合某列为字符数组 |
类似 Select-Object –Property Name 这种命令只会返回一个 Name 的属性(因为我们只指定了该名称), 而且还是原来的对象列表
1 |
PS D:\> Get-Service xbl* | Select-Object name |
-ComputerName 参数不期望得到任何的带有-Name 属性的对象;它更期望得到一个 String 类型的对象,因为这样会更加简单。-ExpandName 会获取 Name 属性,并且提取属性值,最终该命令会输出一些比较简单的 String 对象。
最后说明一下,这是一个非常棒的技巧,可以将多种命令相互关联。这样可以避免不必要的输入,使得 PowerShell 可以实现更多的功能。
1 |
PS D:\> Get-Content .\c.csv |
1 |
PS D:\> Import-Csv .\c.csv |
现在我们可以从列出的这部分计算机上找到正在运行的进程列表。通过查看 Get-Process 命令的帮助文件,
你会发现它的-ComputerName 参数可以接收 ByProperty Name 管道的输入。可接收的对象类型为 String,这里我们不会关注管道输入。
1 |
PS D:\> Start-Service winrm |
1 |
PS D:\> Import-Csv .\c.csv | Select-Object @{name='computername';expression={$_.hostname}} | Get-Process -name * |
1 |
PS D:\> Get-Process -ComputerName (Import-Csv .\c.csv | Select-Object -ExpandProperty hostname) |
■ Get-ADComputer 命令包含一个-Filter 参数:运行 Get-ADComputer-Filter*会返回所在域中所有的计算机对象。
■ 域中计算机对象都包含一个 Name 属性,该属性包含计算机名称信息。
■ 域中计算机对象都会返回一个名为 ADComputer 的类型名称,也就是说,Get-ADComputer 命令会返回 ADComputer 类型的对象。
9.8 章节,不能动手完成
1 |
PS D:\> get-date | Select-Object dayofweek |
本章将会介绍如何覆盖这些默认值并为命令创建自定义输出格式。
PowerShell 的确在收集计算机的信息方面是一把好手,如果有恰当的输出结果,你当然可以使用这些信息生成报表。
Get-Process 注意结果的列头部分。可以看到,报表列头部分的名称与属性名称并不完全相符。每个列头都有固定的宽度、别名等。你是否意识到这些结果来自于某些配置文件?
可以在安装 PowerShell 的路径下找到其中一个名为“.format.pslxml”的文件。其中进程对象的格式化目录在“DotNetTypes.format.pslxml”中。
下面我们先修改 PowerShell 的安装目录,并且打开“DotNetType.format.pslxml”文件。注意,不要保存对该文件的任何变更。该文件带有数字签名,即使一个简单的回车或者空格,都会影响签名并阻止 PowerShell 从中获取信息。
1 |
PS D:\> $pshome |
1 |
notepad.exe .\DotNetTypes.format.ps1xml |
文本中搜索
System.Diagnostics.Process
找出以下输出
1 |
<View> |
你手动指定“Out-Cmdlet”时候,也会发生上面的情况
运行“Get-Process | Out-File procs.txt”和“Out-File”时,PowerShell 会看到你发送了一些普通对象。它会把这些对象发给格式化系统,然后创建格式化指令后回传给“Out-File”。“Out-File”基于这些指令创建格式化后的文本文件。所以在需要把对象转换成用户可读的文本输出格式时,格式化系统就会起到作用。
PowerShell 依赖于什么格式化规则?
第一个规则是系统会检查对象类型
是否已经被预定义视图处理过
。也就是我们在“
DotNetType.format. ps1xml
”中所见到的:一个针对-process 对象的预定义视图。PowerShell 中还预装了其他的“.format.ps1xml”文件,这些文件在 Shell 启动时会被自动加载。你也可以创建自己的预定义视图,但是这部分内容超出了本书范围。
没有被处理时,就让 格式化系统对特定的对象类型查找相应的预定义视图,在本例中也就是查找处理“System.Diagnostics.Process”对象的视图。
如果没找到对应的视图会发生什么?比如运行:
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-WmiObject win32_operatingsystem |
第二个格式化规则
:格式化系统会查找
是否有人为该对象类型预定义默认显示属性集
。这些可以在另外一个配置文件“
Types.ps1xml
”中找到。
现在继续用记事本打开(记住别保存任何修改),然后使用查找功能定位关键字“Win32_OperatingSystem”。一旦找到它之后,就可以看到“DefaultDisplayPropertySet”,
1 |
<MemberSet> |
结果是不是看起来很熟悉?仅仅显示这些属性是由于它们来自于默认的“Types.ps1xml”文件。如果格式化系统找到一个默认显示属性集,那么格式化系统会使用这些属性为下一步做准备,如果没有找到,那么下一步将考虑对象全部的属性值。
接下的分支,即格式化 第三个规则——用于确定输出样式 。如果格式化系统显示 4 个或以下的属性,输出结果会以表格形式展现。如果有 5 个或以上的属性,输出结果会使用列表形式。这就是“Win32_OperatingSystem”对象的结果不以表格显示的原因。它的结果有 6 列,所以以列表形式展示。其中原理就是当属性超过 4 个时,不截断输出结果的情况下,很难将输出结果正确展示在表格中。
格式化系统也是导致为什么 PowerShell 有时显得会“撒谎”。例如,运行 Get-Process 并查看列头,看到名称为 PM(K)的列了吗?这就是一个“谎言”,这是由于并没有名称为 PM(K)的属性,但有一个名称为 PM 的属性。这里所需知道的是格式化列头仅仅是格式化列头。列头无须与底层属性名称一致。查看属性名称唯一安全的方式是使用 Get-Member。
有 4 个用于格式化的 Cmdlets。我们将介绍日常使用最多的 3 种(第 4 种会在本节结尾的“补充说明”中简要介绍)
-autoSize——通常情况下,PowerShell 会根据窗口宽度生成表格(除非存在一个预定义视图,如针对进程的预定义视图,在该视图中定义了列宽度)。一个包含较少列的表格会在列之间留下大量空白,这样的表格不会很美观。通过添加“-autoSize”参数,可以强制结果集仅保存足够的列空间,使得表格更加紧凑,但是会使得 Shell 花费额外时间生成输出结果,这是由于 Shell 会查看所有对象,并找到每列最长行的长度。尝试为下面命令添加“-autoSize”参数,并比对不加该参数产生的输出结果。
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-WmiObject win32_bios |
-property——该参数接收一个逗号分隔符列表,该列表包含期望显示的属性值。这些属性不区分大小写,但是 Shell 会使用你提供的参数作为列头。因此对于大小写格式化不美观的属性值,你可以使其更加美观(如使用“CPU”替代“cpu”)。另外,该参数也接受通配符,可以使用“_”代表的所有属性,或者使用“c_”标识所有以 c 开头的属性名称。但是需要注意的是,Shell 仍然只会显示可以被表格容纳的属性,而不是输出所有你指定的列。该参数是位置参数,所以可以不提供参数名称,只需要在第一个参数位置提供属性列表即可。尝试运行下面的语句(结果见图 10.3)
默认 ps
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> ps |
1 |
|
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> ps | ft id,name,Responding -AutoSize |
-groupBy—— 分组 该参数会导致每当指定的属性值发生变化时,生成一个新的列头集合。该参数只有第一次对某个对象的特定属性排序时才能生效。示例如
1 |
S C:\Windows\System32\WindowsPowerShell\v1.0> Get-Service | Select-Object status | Format-Table -GroupBy status |
-wrap——如果 Shell 需要把列的信息截断,会在列尾带上省略号(…)以便标识信息被截断。该参数使得 Shell 可以封装信息,这使你的表变长,但会保留你期望显示的所有信息。下面是示例。
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Service | Format-Table name,status,displayname -AutoSize -Wrap |
有时候你所需展示的信息过多无法适应表格宽度,此时使用列表就很恰当。是时候用上“Format-List”了,注意你可以使用它的别名:Fl。
Fl 是另一个用于展示对象属性的方式,和 Gm 不同。Fl 也同样显示这些属性的值,以便你可以看到每个属性包含的信息。
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Service | gm |
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Service | fl |
查阅“Format-List”的帮助文档,尝试使用该命令的参数。
1 |
PS D:\> help Format-List -full |
使用方法和 table 一样,只是 每对象一列 生成属性及值列表
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Process | fl * |
用于展示一个宽列表。它仅展示一个属性的值,所以它的“-property”参数仅接受一个属性名称,而不是接受列表,并且不接受通配符。
默认情况下,“Format-Wide”会查找对象的“Name”属性,因为“Name”是广泛使用的属性并且通常包含有用信息。该命令默认输出结果只有两列,但是“-columns”参数可以用于指定输出更多的列。
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Process | Format-Wide name -col 4 |
仔细阅读“Format-Wide”的帮助文档,并尝试使用其参数
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Service | Format-Table @{n='servicename';e={$_.name}},status,DisplayName | Select-Object -f 4 |
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Process | ft name,@{n='vm/M';e={$_.vm / 1MB -as [int]}},vm -AutoSize | select -f 10 |
重复运行以下命令,注意要折行
1 |
PS C:\Windows\System32\WindowsPowerShell\v1.0> Get-Process | |
按照上面的格式输入。你会发现,当你输入完第一行之后(也就是以管道符结尾), PowerShell 会出现一个提示符。因为你以管道符结尾,所以 Shell 知道你准备输入更多的命令,直到你以花括弧、引号和括号结尾为止。
果你不想以这种“扩展输入模式”输入,按 Ctrl+C 组合键停止,并再次运行上面的示例。在这种情况下,输入第二行文本并按回车键,然后继续输入第三行,再按回车键。在这种模式下,你必须多按一次回车,以便告知 Shell 你已经完成输入。然后 Shell 会逐行按顺序运行你的输入。
与“Select-Object”不同, 它的哈希表仅接受一个 Name 和 Expression 作为哈希键(虽然对于 Name 属性,可以用 N、L 和 Label;对于 Expression 属性,可以用 E)。“Format-”命令可以处理用于控制显示的额外的键字。这些关键字对于“Format-Table”尤其有效。
FormatString:指定一个格式化代码,使得数据按照指定格式显示,该参数主要用于数值型和日期型数据。可以到 MSDN 的“Formatting Types”( http://msdn.microsoft.com/en-us/library/fbxft59x(v=vs.95).aspx)页中查看用于标准数值与日期格式的格式化代码,以及用于自定义数值与日期格式的格式化代码。■ Width:指定列宽。■ Alignment:指定列的对齐格式,可以为左对齐或者右对齐。
1 |
PS D:\> Get-Process | ft name,@{n='vm/M';e={$_.vm / 1MB };formatstring='F2';align='left'},vm -AutoSize |
如果命令最后是 Format-开头的 Cmdlet,由 Format-开头的 Cmdlet 创建的格式化指令将会传递给“Out-Default”,然后该命令会将结果传递给“Out-Host”,最后显示结果到显示屏。
1 |
PS D:\> Get-Service | Format-Wide -Column 10 |
你可以手动在上面命令中输入“Out-Host”,并以管道符连接。实际上运行了下面的语句。
1 |
PS D:\> Get-Service | Format-Wide | Out-Host |
另外一种方式是用管道把格式化指令传递给“Out-File”或“Out-Printer”,从而将结果输出到文件或者打印机中,正如你在 10.9 小节中看到的,只有这三个以“Out-”开头的 Cmdlet 才可以跟在以“Format-”开头的 Cmdlet 后面。
“Out-Printer”和“Out-File”都有默认的输出宽度,意味着输出结果在文件或打印的结果看上去可能和显示器上看到的不一致。这些 Cmdlets 允许你使用“-width”参数控制宽度,输出你想要的表格。
1 |
PS D:\> Get-Service | Format-Wide | Out-File service.fw |
1 |
PS D:\> Get-Process | Format-Table id,name,cpu,@{n='vm(mb)'; e={$_.vm / 1MB};formatstring='F2';align='left'} | Out-GridView |
1 |
PS D:\> Get-Process | select * | Out-GridView |
1 |
# select * # 不方便查看, 可以转html, 写文件查看。或者out-gridview |
每段一个对象
1 |
# fl * |
format-开头会转换传递来的对象,所以这个 format 应该在最后一个命令。或者接 out-,(除了 out-grid)
1 |
PS D:\> Get-Process chrome | ft * | select -f 2 | convertto-html | Out-File ps.ft.html |
可以看到乱码
1 |
PS D:\> Get-Process chrome | ft * | select -f 2 | convertto-html | Out-GridView |
如果管道包含两个或以上的对象,那么结果可能与你期望的会有不同。
1 |
PS D:\> Get-Service ; gsv |
其中分号允许我们把两个命令合并在一个命令行中,而不是把第一个命令的输出并以管道形式传入第二个命令。这意味着两个命令单独运行,但是会把它们的输出结果传到相同的管道中。如果你动手运行
1 |
PS D:\> Get-Service ; gsv | gm |
如果你希望将来自不同地方的两个信息以同一种格式输出,那该怎么办?你当然可以这么做,格式化系统能够以非常优雅的方式实现这点。但这是高级主题,本书并不涉及。
技术上而言,格式化系统可以处理多种类型的对象——只要你告知它处理方式。
运行“Dir| Gm”,你可以发现管道包含 了“DirectoryInfo”和“FileInfo”对象 (Gm 可以与包含不同类型对象的管道结合使用,并显示所有对象的成员信息)。
当仅运行 Dir 时,输出结果非常清晰。
这是因为微软已经对“DirectoryInfo”和“FileInfo”对象提供了 预定义的自定义格式化实体 ,该格式化由“Format-Custom”完成。
“Format-Custom”主要用于展示多种预定义视图。
技术上,你可以自己创建预定义视图,但是所需的 XML 语法相对复杂,并且目前没有文档支持,所以当前仅能使用微软提供的定制视图。 微软的定制视图被广泛使用。例如, PowerShell 的帮助系统以对象形式储存,你所看到已格式化的帮助文件是将这些对象传递给自定义视图处理后的结果。
1 |
PS D:\> Get-ChildItem | gm |
-wrap
1 |
PS C:\Windows\system32> Get-Process | Format-Table ProcessName,Id,Responding -AutoSize |
1 |
Get-Process | Format-Table ProcessName,Id,@{L='VM(mb)';E={$_.VM / 1MB};FORMATSTRING='F2';align='left'},@{l='WorkingSet(MB)';E={$_.workingset / 1MB};format='F2'}-AutoSize |
1 |
# 可用事件日志有哪些 |
1 |
PS C:\Windows\system32> Get-Service | Sort-Object status -desc | Format-Table -GroupBy status |
1 |
PS C:\Windows\system32> Get-ChildItem c:\ -Attributes Directory | Format-Wide name -col 4 |
-Attributes Directory
可以简写 -directory
1 |
PS C:\Windows\system32> Get-ChildItem C:\Windows\*.exe | Format-List Name,VersionInfo,@{n='SIZE';e={$_.length}} |
到目前为止,我们使用 Shell 向你展示了不同类型的输出:所有进程、所有服务、所有事件日志条数、所有补丁。但是这些类型的输出并不总是你想要的结果。通常你会想要将结果范围缩小到你感兴趣的几个项。
第一种方式 :尝试指定 Cmdlet 命令只检索指定的内容。
第二种方式 :采用迭代的方法,通过第一个 Cmdlet 获得所有结果,并使用第二个 Cmdlet 过滤掉不想要的东西。
按道理,应该使用第一种方式:我们称之为尽可能提前过滤。这就像告诉 Shell 你要的是什么一样简单。例如,使用 Get-Service,你可以告诉它你想要的服务名称。
1 |
PS C:\Windows\system32> Get-Service n*,b* |
如果你想让 Get-Service 只返回正在运行的服务,而不考虑它们的服务名称,该 Cmdlet 就无法做到这一点,因为它没有提供用于设定该部分信息的相关的参数。
1 |
PS C:\Windows\system32> Get-Service | where status -eq running |
如果你使用微软的活动目录模块,所有以 Get-开始的 Cmdlets 都提供了-filter 参数。通过-filter *,你可以获取所有对象。我们不建议这样使用,因为加载这些对象将增加域控制器压力。你可以指定下面的过滤条件,就能很好表示出你所希望的是什么。
再者,上述技能的优势在于该 Cmdlet 只获取匹配的对象。我们称之为左过滤技术。
尽可能把过滤条件放置在左侧或靠近命令行的开始部分,越早过滤不需要的对象,就越能减轻其他 Cmdlets 命令的工作,并且能减少不必要的信息通过网络传输到你的电脑。
缺点是 每个 Cmdlet 都可以通过自己的方式指定过滤 ,并且每个 Cmdlet 都会有不同的过滤方式。例如 Get-Service,你只能通过 Name 属性过滤服务。但是使用 Get-ADComputer,你可以根据 computer 对象可能存在的任何活动目录属性进行过滤。在有效使用左过滤技术之前,你需要学习不同 Cmdlet 的各种操作。这可能意味着学习的道路有些崎岖, 但可以得到更好的性能 。
当 无法通过一个 Cmdlet 就可以完成你所需的所有过滤时,你可以使用一个叫作 Where-Object(它的别名为 Where)的核心 PowerShell 命令 。这是一个通用的语法。当需要检索的时候,使用它过滤任何类型的对象,并把它传入管道。
为了使用 Where-Object,需要学会告诉 Shell 如何过滤出你想要的信息,这还包括使用 Shell 的比较操作符。有趣的是,一些左过滤技术中使用了相同的比较操作符,如活动目录模块下以 Get-开头的命令的-filter 参数,这就是一箭双雕。但是有些 Cmdlet 命令(如 Get-WmiObject,我们将在后面的章节中讨论)使用了完全不同的过滤和比较方式,当我们讨论这些 Cmdlet 命令时再做介绍。
当比较文本字符串时会忽略大小写。大写字母与小写字母等价。
-eq 相等
。例如 5-eq 5(返回 true)或者”hello” -eq “help”(返回 false)。
-ne——不等于
,例如 10-ne 5(返回 true)或者”help” -ne “help”(返回 false,因为它们实际上相等的,这里测试它们是否不相等)。
-ge和-le——大于等于,小于等于
,例如 10-ge 5(返回 true)或者 Get-Date -le ‘2012-12-02’(这取决于你运行该命令的时间,这意味着可以比较日期)。
-gt和-lt——大于和小于
,例如 10-lt 10(返回 false)或者 100-gt 10(返回 true)。
对于字符串的比较,如果需要区分大小写,可以使用下面的集合:
-ceq, -cne,-cgt,-clt, -cge, -cle。
如果想一次比较多个对象,可以使用布尔运算符
-and和-or
。通常在
每个子表达式两边加上圆括号,使得表达式更容易阅读
。
布尔值-not对true和false取反
。在处理一个变量或者已经包含 true 或 false 的属性时,这可能会有用。
而你想测试相反的条件
。
例如,需要测试一个进程是否没有响应,可以这样做(使用$_作为进程对象的容器):
1 |
PS C:\Windows\system32> Get-Process git | select Responding | where responding -eq $true |
Windows PowerShell 定义了**$False和$True 表示 false 和 true 的布尔值**。另外一种书写方式如下。
1 |
PS C:\Windows\system32> Get-Process git | select @{L='notresp';E={-not $_.Responding}} |
因为 Responding 通常包含 true 和 false, -not 使得 false 取反变为 true。如果进程没有响应,意味着 Responding 返回 false。然而上面的比较却返回 true,这就暗示着该进程“没有响应”。我们更喜欢使用第二种方式,因为在英语的阅读习惯中,它更接近我们的测试内容:“我想看看这个进程是否没有响应”。有些时候,你可以看到
-not运算符简写为感叹号(!)
。
-like接受作为通配符
,所以可以比较:”Hello” -like “
ll
“(返回 true)。它的反义运算符为-notlike。它们不区分大小写。区分大小写可以使用-clike 和-cnotlike。
-match用于文本字符串与正则表达式进行比较
。-notmatch 是个逻辑上的反义词。并且正如你所想,-cmatch 和-cnotmatch 提供了区分大小写的语法。正则表达式超出了本书的讨论范围。
Shell 的好处是你可以在命令行运行上面几乎所有的测试(除了前面提到的$_占位符,它不能独立运行,但是你可以在下一节看到它是如何运行的)。
如果 Cmdlet 命令不使用 11.3 节中讨论的 PowerShell 风格的比较运算符,
可以使用高中或大学(甚至是工作中)学过的更加传统的编程语言形式的比较运算符。■ = 等于 ■ <> 不等于 ■ <= 小于或等于 ■ >= 大于或等于 ■ > 大于 ■ < 小于如果支持布尔运算符,通常关键字是 AND 和 OR。有些 Cmdlet 命令可能提供类似 LlKE 的运算符。例如,通过-filter 参数可以找到 Get-WmiObject 支持的所有运算符。当我们在第 14 章讨论该 Cmdlet 时,会重现该列表。
1 |
PS C:\Windows\system32> Get-Service | Where-Object status -eq 'running' |
-filter 参数是一个位置参数,这意味着你经常看到很多命令没有显式指定该参数
而它的别名为 Where。
1 |
PS C:\Windows\system32> Get-Alias -Definition Where-Object |
当你传递多个对象到 Where-Object 时,它会使用它的过滤器检查每个对象。一次只放置一个对象到占位符$_,接着运行比较操作从而查看返回值是 true 还是 false。如果是 false,该对象就会被管道移除。如果返回 true,该对象就会从 Where-Object 传输到下一个 Cmdlet 的管道中。在上面的示例中,下一个 Cmdlet 命令是 Out-Default,这会是管道的末尾(在第 8 章已经讨论过),接着开始使用格式化过程从而显示输出结果。
占位符$_是个特殊产物:之前已经见过(在第 10 章),你将在一个或更多的上下文看到它。该占位符只能在 PowerShell 能查找的特定位置中使用。在我们的示例中,该占位符恰好是在其中一个特定位置。正如你在第 10 章学习到的,句号用于告诉 Shell 不是比较整个对象,而是只比较对象的 Status 属性。
运行 Get-Process,可以看见一个叫作 PM(MB)的列;运行 Get-Process | Gm,发现列名称实际上是 PM。这个区别非常重要:总是使用 Gm 验证属性名称,而不要使用以 Format-开头的命令。
新语法 where property 对比 value
1 |
gsv | ? status -eq 'running' |
1 |
PS C:\Windows\system32> gsv | ? {$_.status -eq 'running'} |
有逻辑运算,必须旧语法
1 |
PS C:\Windows\system32> gsv | ? {$_.status -eq 'running' -and $_.StartType -eq 'manual'} |
PSICLM 的核心思想在于你不需要一开始就创建一个大而复杂的命令行,而是从简单的开始。
比方说,你想计算正在使用虚拟内存排名前十的进程所占用的虚拟内存总和。
(1)获取进程列表;(2)排除 PowerShell 进程;(3)按照虚拟内存进行排序;(4)只保存前 10 个或者最后 10 个,这取决于我们的排序方式;(5)把剩下进程的虚拟内存相加。
1 |
# 占用虚拟内存最多的 前10的进程集合 |
我们相信你知道如何完成前 3 个步骤,第 4 个步骤完全可以使用我们的老朋友:Select-Object。
动手实验:花几分钟时间阅读 Select-Object 的帮助文档。你是否能找到让你在一个集合中保留第一个或最后一个对象的参数?
1 |
-first |
最终,需要把所有虚拟内存相加。这里就需要寻找新的命令,或许可以通过 Get-Command 或 Help 加上通配符寻找。可以尝试 add 关键字,或者 sum 关键字,甚至是 Measure 关键字。
1 |
# help measure |
因为 PowerShell 是一个命令行 Shell,可以立即返回结果,并且如果返回的结果不是期望结果,那么可以快速、简单地修改命令。当你将已经掌握的少量的 Cmdlets 命令与刚学到的这一点结合后,你应该可以发现你所能够拥有的能力。
你会希望你的过滤条件越接近开始的命令行越好。如果能在第一个 Cmdlet 后就完成过滤,那就这么做。如果不行,尝试在第二个 Cmdlet 命令后过滤,这样将尽可能减少后面 Cmdlet 命令的工作。
另外,尝试在尽可能靠近数据源的地方完成过滤。例如,你需要从一台远程计算机查询服务并使用 Where-Object——正如本章的一个例子——考虑利用 PowerShell 的远程调用在远程计算机上进行过滤,这比把所有的对象都获取到本地之后再进行过滤要好得多。
1 |
# 准备计算机地址 |
不是只有 Where-Object 方式可以过滤,它甚至不应该是你第一个想到的命令。我们已经使得本章尽量保持简短,以便让你有更多的时间进行动手实验。所以,记住左过滤的原则,尝试完成下面的内容。
1 |
# 获取 |
1 |
PS D:\> Import-Module DnsClient |
1 |
# 文件 |
description -Match 'security update'
1 |
PS D:\> Get-HotFix |
1 |
PS D:\> Get-Service | ? {$_.StartType -Match 'Automatic' -and $_.Status -ne 'running' } |
1 |
PS D:\> Get-HotFix | ? Installedby -Match system |
1 |
PS D:\> Get-Process conhost,svchost |
尝试对你学习过的命令的输出结果进行过滤,比如 Get-Hotfix、Get-EventLog、Get-Process、Get-Service 甚至是 Get-Command。例如,可以尝试对 Get-Command 的输出过滤,只剩下部分 Cmdlet 命令。或者使用 Test-Connectionping 服务器,并且只有在没有应答的情况下显示结果。
1 |
PS D:\> Get-Command | ? name -Match 'desktop' |
1 |
PS D:\> Get-Command | ? name -Match 'test' |
1 |
PS D:\> Test-Connection www.baidu.com | ? ReplyInconsistency -eq $true |
我们不会尝试教你任何新东西,而是使用你所学到的知识完成一个完整的示例。、
本章是本书内容的一个缩影,因为除了告诉你如何完成工作之外,我们还希望你意识到:你可以自学成才。
该示例无法在 Linux 或 macOS 中工作,这是由于这些操作系统没有像 Windows 中那样的用户权限。
目标是使用 PowerShell 在本地系统中修改一些默认的用户 privileges,这并不等同于权限,而是某个用户或组能够执行的一些系统范围的任务。
第一步都是找出哪一个命令可以完成任务,基于你安装的组件,你得到的结果或许与我们不同,但重要的是解决问题的过程本身。因为我们知道我们希望修改用户权限,因此我们把“privileges”作为关键字。
1 |
PS D:\> help *privileges* |
命令中并没有任何信息看起来与 privileges 有关。好的,让我们尝试另一种方式——这次,关注命令本身而不是帮助文件,使用更加宽泛的搜索
1 |
PS D:\> Get-Command -Noun *priv* |
好的,并没有名称中包含 priv 的命令。让人失望!
现在我们不得不查看 PowerShell Gallery 中可能会有什么。我们会意识到该步骤依赖于已经安装的 PowerShell Package Manager。这是 PowerShell v5 的一部分,但也可以在老版本 PowerShell 安装获取。访问 http://powershellgallery.com获得下载链接。
1 |
PS D:\> Find-Module *privi* |
PoshPrivilege PSGallery Module designed to use allow easier access to work with User Rights (privileges)
用户权限管理,安装此模块
1 |
PS D:\> Install-Module poshprivilege |
虽然 PowerShell Gallery 由微软运营,但微软并不验证其他人发布的代码。因此我们应该停下来,查看我们刚刚安装的代码,在继续往下之前确保代码没有任何问题。该模块的作者是 MVP:Boe Prox,并且我们相信他。
获取模块的命令
1 |
PS D:\> Get-Command -Module PoshPrivilege |
1 |
PS D:\> help Add-Privilege |
现在可以添加了,但是我们并不知道权限有是什么。但模块提供了 Get 命令,让我们试一下。
1 |
PS D:\> Get-Privilege |
1 |
PS D:\> Get-Privilege | Select-Object Privilege,Description |
1 |
PS D:\> Add-Privilege slc SeDenyBatchLogonRight |
file share
1 |
PS D:\> Get-Module -ListAvailable *share* |
创建一个名称为“LABS”的目录,并共享该目录
1 |
PS D:\> New-Item -Type Directory LABS |
ChangeAccess 修改和访问
1 |
# 获取共享 |
1 |
PS D:\> Get-SmbShare my-labs | Get-SmbShareAccess |
此时使用电脑 ip 访问,
1 |
\\192.168.13.103\my-labs |
Linux 连接 windows,需要 windows 有用户
1 |
new-localuser cifs -Password $(Read-Host -AsSecureString) |
linux 连接
参考:
查看
,
挂载
1 |
root@172:~# mount -t cifs -o user=cifs,password=123456 //192.168.13.103/MY-LABS /mnt |
该系统使得你可以在一个远程计算机上运行任何 Cmdlet。甚至当本地计算机没有包含某些命令时,你也可以直接运行远程计算机上已存在的这些命令(也就是说,你不需要在本地计算机上安装任何管理性质的 Cmdlet)。远程处理系统的功能非常强大,它提供了大量有趣的管理功能。
远程处理是非常庞大和复杂的一门技术。在本章中,我们会介绍该项技术,同时会覆盖到日常工作中大概 80%~ 90%的场景。正因为我们没有覆盖到全部知识点,所以在本章最后的“进一步探讨”小节中,我们会列出一些学习远程处理全部配置选项的资源。
PowerShell 的远程处理类似于 Telnet 或者其他一些老旧的远程处理技术。 当键入该命令时,它会在远程计算机上运行。只有该命令的运行结果会返回本地计算机 。与 Telnet 和 Secure Shell(SSH)不一样的是,PowerShell 采用一种 新的通信协议 ,我们称之为针对管理的 Web 服务(Web Services for Management, WS-MAN)。
WS-MAN 协议,基于 http/https。后台服务: Windows 远程管理组件(WinRM)。
微软宣布远程处理技术除了 WS-MAN 之外,还可以基于 SSH 协议。这对那些已经熟悉 SSH 而不熟悉 WS-MAN 与 WinRM 的公司来说是一个好消息。从用户角度讲,如何使用远程处理技术并无区别,底层协议的区别对你来说应该是透明的。我们敦促你在对 SSH 协议的支持发布后阅读 Invoke-Command 与 New-PSSession Option 的文档。只需要知道本版书的本章,仅仅专注于 WS-MAN 与 WinRm 的方式实现远程处理。
当你运行一个远程命令时,它会将输出结果放入一个特定形式的包中,之后通过网络中的 HTTP(或者 HTTPS)协议传回本地计算机。XML 已经被证明是针对该问题的优秀解决方案,所以 PowerShell 会将输出对象序列化到 XML 中。下一步,XML 文件会通过网络进行传输。当到达本地计算机之后,该 XML 会反序列化为 PowerShell 可以处理的对象。序列化和反序列化仅仅是一种格式转换的形式:从对象转化为 XML 称为序列化,从 XML 转为对象则为反序列化。
要保证远程处理可以正常工作,需要满足下面两个条件
希望你可以模拟本章中的示例。为了完成这些实验,理论上,你需要第二台计算机(当然,也可以是一个虚拟机),并且这两台计算机需要在同一个域中。远程计算机可以运行在已经安装第 2 版或者更新版本 PowerShell 的任意操作系统上。当然,如果无法找到第二台计算机或者虚拟机,也可以使用 localhost 创建到当前计算机的伪远程连接,但是无法像真实远程处理远程计算机那样让人兴奋。
并非只有 PowerShell 能使用 WinRM 服务。实际上,微软在越来越多的管理程序中开始使用 WinRM 服务——甚至包含已经使用了其他协议的那些程序。基于这一思想,微软保证 WinRM 可以将流量导入至多种管理程序——不仅仅是 PowerShell。WinRM 类似一个调度器:当有新的流量进来后,WinRM 会决定由哪种程序来处理这部分流量。所有 WinRM 流量都标记了接收应用程序的名称,同时这些应用程序都必须在 WinRM 中创建各自的端点,这样 WinRM 才能侦听这些主体的流量。这也就意味着,你们不只需要 启用 WinRM 服务 ,也需要在 WinRM 中将 PowerShell 注册为一个端点 。
在你的系统中可以有几十个甚至上百个 WinRM 端点(PowerShell 称它们为会话配置选项)。每一个端点都指向一种应用程序,甚至你可以将多个端点指向同一个应用程序,但是每个端点提供不同的权限以及功能。例如,你可以在环境中创建一个 PowerShell 端点,该端点仅允许特定用户运行一个或者两个命令。在本章中不会深入讲解远程处理,但是我们会在第 23 章中再深入探讨该服务。
创建一个端点:新开一个 PowerShell 窗口——需要以管理员权限运行 PowerShell,之后运行 Enable-PSRemoting 命令。有些时候你可能会看到另外一个相关的命令 Set-WSManQuickConfig。但是根本没必要手动运行该命令,Enable-PSRemoting 命令会自动调用该命令。另外,Enable-PSRemoting 命令也会运行一些其他步骤完成开启远程处理服务。总体来说,该 Cmdlet 会启用 WinRM 服务,配置该服务为自动启动模式,然后在 WinRM 中为 PowerShell 注册一个端口,甚至会在 Windows 防火墙中针对传入的 WinRM 流量创建例外条件。
1 |
PS D:\> Set-NetConnectionProfile -NetworkCategory private |
我们设置至少一个网卡为“公用网络”类型。请记住,在 Windows Vista 以及之后版本的操作系统中,对每一个网卡都会设置一个网络类型(家庭网络、工作网络或者公用网络)。类型为“公用网络”的网卡中无法设置 Windows 防火墙例外,所以当我们运行 Enable-PSRemoting 命令尝试创建一个防火墙例外时,就会失败。唯一的解决办法是进入 Windows 中,修改该网卡的类型为“工作网络”或者“家庭网络”。但是,如果你是连接到一个公用网络(比如一个公用的网络热点),请不要这样做,因为这将关闭一些重要的安全保护功能。
1 |
PS D:\> Enable-PSRemoting -SkipNetworkProfileCheck |
解决链接: [ https://helpcenter.gsx.com/hc/en-us/articles/217979218-Failure-to-enable-Remote-PowerShell-for-Non-Domain-Joined-computers#:~:text=WSManFault%20code%202150859113%3A%20WinRM%20firewall,or%20Private%20and%20try%20again . ]
1 |
help about_Remote_Troubleshooting |
1 |
PS D:\> Enter-PSSession -ComputerName 192.168.13.103 |
1 |
PS D:\> Enter-PSSession 192.168.13.103 |
比如 Server-DC4 处于防火墙中,无法被直接访问,所以需要 Server-R2 作为中转服务器,使得可以访问到 Server-DC4。但是,一般情况下,请不要使用远程处理链。
在某些人看来,远程处理链类似于“二连跳”,同时可以算作 PowerShell 的一个缺点。简单提示一下:如果 PowerShell 的命令行窗口已经显示了一个计算机的名称,那么请到此结束。除非你退出该进程回到本地计算机命令(PowerShell 命令行窗口中不包含计算机名称)时,你才能再次运行一些远程控制的命令。我们会在第 23 章中讨论启用多层远程处理的相关问题。当你使用一对一的远程处理时,你不需要担心被序列化和反序列化的对象。就你个人而言,其实等效于直接在远程计算机的控制台中键入命令。如果你获取了一个进程,并通过管道传递给 Stop-Process 命令,正如我们期待的那样,该进程会停止运行。
1 |
PS D:\> Invoke-Command 192.168.13.103,192.168.13.103 { 1 | gm} | Sort-Object name |
1 |
PS D:\> Invoke-Command 192.168.13.103,192.168.13.103 { Get-Process | ? {$_.ProcessName -match 'chrome'}} |
Invoke-Command 和 Where(是 Where-Object 命令的别名)。Where 命令完整嵌套在最外层的大括号中。最外层的大括号中包含的命令就是我们需要传递到远程计算机上运行的命令,
远程执行的是
Get-Process | ? {$_.ProcessName -match 'chrome'}
在 Invoke-Command 的帮助信息中找不到-Command 参数,但是我们确认上面示例中的命令可以正常运行。实际上,-Command 参数是帮助文档中-ScriptBlock 参数(可以在 Invoke-Command 帮助文档中找到该参数的信息)的一个别名或者昵称。由于-Command 命令更容易记住,所以我们往往使用-Command,而不会使用-ScriptCommand。但是实际上,它们的作用相同。
1 |
PS D:\> (Get-Command Invoke-Command | select -ExpandProperty parameters).ScriptBlock.Aliases |
默认情况下,PowerShell 最多一次与 32 台远程计算机通信。如果超过 32 台,那么会将计算机信息存放到一个队列中。如果命令在一台远程计算机上运行完毕,队列中的下一台计算机会立即开始运行。当然,如果网络足够良好,并且计算机足够强劲,那么我们可以通过 Invoke-Command 的-ThrottleLimit 参数来指定更多数量的计算机(如果需要了解更多的信息,请查阅该命令的帮助文档)。
1 |
PS D:\> help Invoke-Command -Parameter filepath |
1 |
PS D:\> 'get-process | select -f 10' | Out-File my.ps1 |
1 |
PS D:\> '192.168.13.103' | Out-File computers.txt |
比如从活动目录中获取计算机的名称。我们可以使用 Get-ADComputer 命令(来自于活动目录模块;Windows 7 的远程服务器管理工具(RSAT)/Windows Server 2008 R2 或者之后版本的操作系统的域控制器服务器中均存在该模块)来获取计算机信息,但是我们无法将该命令放入圆括号中(类似上面的 Get-Content 命令)。为什么不行呢?因为 Get-Content 命令产生的对象类型为-Computer 参数可接受的简单文本 String 类型。但是 Get-ADComputer 会输出完整的计算机对象,-ComputerName 参数不知道应该如何处理这部分数据。
select -expand name
使用 Invoke-Command 命令远程运行和在本地运行相同命令之间的差异
1 |
PS D:\> Invoke-Command -ComputerName (Get-Content .\ips.txt) {Get-EventLog Security -Newest 5 | ? {$_.EventID}} |
1 |
PS D:\> Get-EventLog Security -Newest 5 -ComputerName 192.168.0.109 |
一样的执行方法
我们会得到类似的结果,但是在该命令运行的方式上存在很大的不同。
对带有-ComputerName 参数的 Cmdlet 而言都存在这些差异。一般来讲, 使用 Invoke-Command 命令比 Cmdlet 的-ComputerName 参数更有效率 ,更有用。
最后一点是使用-ComputerName 参数和 Invoke-Command 命令之间很大的一个差异点。下面会讨论到这一点。
1 |
Invoke-Command -ComputerName (Get-Content .\ips.txt) {Get-EventLog Security -Newest 5 | ? {$_.EventID}} |
1 |
PS D:\> Invoke-Command -ComputerName (Get-Content .\ips.txt) {Get-EventLog Security -Newest 5 } | ? {$_.EventID} |
唯一的不同点是,我们移动了一个大括号的位置
在第二个命令中,只有 Get-EventLog 命令被远程调用。Get-EventLog 命令产生的所有结果都被序列化,之后发送到本地计算机,最后在本机反序列化为对象,再通过管道传递给 Where 做筛选。
第二个版本的命令效率更为低下,因为会有大量不必要的数据通过网络传输,然后在本地计算机上筛选来自多台计算机的返回结果,而并不是在 3 台计算机上筛选好结果之后再发送给本地计算机。所以采用第二个版本的命令是一个非常糟糕的主意。让我们看看其他命令的两个版本,如下面的命令。
1 |
PS D:\> notepad |
1 |
PS D:\> notepad |
这里唯一的差异是一个大括号的位置不同。但是在本示例中, 第二个版本的命令根本无法运行。
本地请求,远程响应时,序列化对象为 xml, 返回本地。, 本地反序列化后,对象将没有用。
本地拿到的对象一般用途:
格式化或者导出数据
,那没问题,因为 PowerShell 可以这样处理 Invoke-Command 的输出结果。
但是如果 Invoke-Command 后面跟着操作类型的 Cmdlet(比如开启、停止、设置或者修改等其他操作),就不会生效。
返回给本地计算机的对象可能会缺失部分功能。在大部分情况中,由于它们不再需要关联到可用软件,它们都会缺少对应的方法(Method)。
1 |
PS D:\> Get-Service | gm |
本地有方法
1 |
PS D:\> Invoke-Command -ComputerName 192.168.0.109 { Get-Service } | gm |
远程返回 xml, 本地反序列化后,对象将没有用。
前面每次 invoke-command 均需要接计算机地址集合。如果你需要在很短时间内多次重复连接到相同的远程计算机,那么你可以创建可重用的持久性连接。我们会在第 20 章中讲解该技术。
不是每家公司都允许开启 PowerShell 的远程处理机制
非常严格安全策略的公司在所有的客户端和服务器计算机上都会开启防火墙 ,这将阻止 PowerShell 远程处理的连接。如果你所在的公司也是这样,那么你需要确认一下在防火墙中是否有针对远程桌面协议(RDP)设置一个例外。我们可以发现,在大部分公司总是会存在该例外,因为管理员总是需要不定时远程连接到某些服务器。 如果 RDP 是允许使用的,那么也请尝试对 PowerShell 的远程处理设置类似例外 。因为 远程处理连接可以被审核到 (它们类似于网络账号,就像访问一个共享文件会在审计日志中出现对应日志),所以它们默认情况下被限制为仅管理员可以连接。 在安全风险方面,PowerShell 的远程处理和 RDP 没多大差别,并且相对于 RDP, PowerShell 的远程处理在远程计算机上占用更少的开销。
Invoke-Command 和 Enter-PSSession 命令都有一个-SessionOption 参数(该参数能处理 PSSessionOption 类型对象)
1 |
PS D:\> help Invoke-Command -Parameter sessionoption |
1 |
PS D:\> $PSSessionOption |
我们使用 New-PSSessionOption 命令实现,修改建立远程会话的属性。打开、取消和空闲超时。取消正常数据流的压缩或者加密功能。■ 通过代理服务器传递网络流量时,也可以设置一些代理相关的选项。忽略远程机器的 SSL 证书、名称以及其他安全特性。
new-pssessionoption 不接选项生成默认的会话选项。与 $PSSessionOption 一样。一旦加上选项,就会更改会话配置。例如下
1 |
PS D:\> New-PSSessionOption -SkipCNCheck |
现在我们进入会话将不会检查 机器名称
1 |
PS D:\> Invoke-Command -ComputerName 192.168.0.109 -SessionOption (New-PSSessionOption -SkipCNCheck) {Get-Process} |
1 |
PS D:\> Enter-PSSession -ComputerName 192.168.0.109 -SessionOption (New-PSSessionOption -SkipCNCheck) |
默认情况下,只有指定远程计算机的真实名称时,远程处理才能正常工作。不能使用 DNS 的别名或者 IP 地址。在第 23 章中,我们会讨论该限制的背景,同时会给出解决该问题的方案。
远程处理功能的目的主要是解决域中自动化配置。如果涉及的计算机以及所使用的用户账号都属于同一个域或者可信任的域中,那么一切都可以很轻易地实现。如果不是这种情况,那么需要详细查看 About_Remote_TroubleShooting 的帮助文档。一个需要确认的情形是你是否跨域进行远程处理。如果确认如此,那么你必须修改一些配置选项使得 PowerShell 可以正常运行,帮助文档中详细描述了该场景。
当调用一个命令时,远程计算机会发起一个 PowerShell 会话。运行你键入的命令,之后关闭 PowerShell 会话。当你在相同计算机上运行下一条命令时,又会重复该步骤(第一次调用过程中运行的任何结果或者命令都不再有效)。如果你需要运行一系列关联的命令,那么你需要将它们放入相同的调用进程中。
确保你以管理员身份运行 PowerShell,特别是对于开启用户账户控制(UAC)功能的计算机。如果你使用的账号在远程计算机上没有管理员权限,那么你需要使用 Enter-PSSession 或者 Invoke-Command 命令的-Credential 参数去指定另外一个拥有管理员权限的账号。
1 |
help invoke-command |
1 |
help get-credential |
1 |
PS D:\> New-Object System.Management.Automation.PSCredential user,(ConvertTo-SecureString pass -AsPlainText -Force) |
1 |
Invoke-Command -ComputerName 192.168.0.109 -Credential ( New-Object System.Management.Automation.PSCredential user,(ConvertTo-SecureString pass -AsPlainText -Force) ) -command { ps } |
如果你使用的不是 Windows 防火墙,而是第三方防火墙产品,Enable-PSRemoting 不会建立特定的防火墙例外。那么你需要手动来完成该项设置。如果远程连接需要穿过一个部署在路由器或者代理服务器上的普通防火墙,那么也需要针对远程流量手动设置一个例外。
请不要忘记一点规则,在组策略对象(GPO)中的配置选项会覆盖本地配置的选项。我们经常会看到管理员会花费几小时来使得远程处理可以正常工作,最后才发现一个 GPO 对象覆盖他们设置的选项。在某些情况下,可能一些好心的同事很久之前设置了一些 GPO 对象,但是后来忘记了。所以请不要想当然以为没有 GPO 影响到你的设置,你需要去检查一下,以便确认。
1 |
PS D:\> Enter-PSSession -ComputerName 192.168.0.109 |
提示符在记事本进程结束前都不会返回结果——虽然有一个替代命令可以实现同样的功能。
1 |
Start-Process notepad.exe |
1 |
PS D:\> Invoke-Command -ComputerName 192.168.0.109,192.168.0.109 { Get-Service | ? Status -Match 'stopped' } | fw |
1 |
PS D:\> Invoke-Command -ComputerName 192.168.0.109,192.168.0.109 { Get-Service | ? Status -Match 'stopped' | Sort-Object vm -desc | Select-Object -f 10 } |
1 |
PS D:\> Get-Content .\mc.txt |
Get-ItemProperty ‘HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
1 |
PS D:\> Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\' | select ProductName,EditionID,CurrentVersion |
http://PowerShell.org的e-book资源,这里Don和MVP Tobias Weltner 博士一起写了一本全面探究 PowerShell 远程处理原理的一本迷你电子书(也是完全免费)。该电子书中会重讲本章中所学的一些基础知识,但是内容主要集中关于如何配置各种远程处理场景的 Step-by-step 详细说明(同时配有彩色截图)。该指南深入探索协议与故障排查,甚至还有一小节是关于如何说服公司的安全人员同意启用远程处理,同时,该电子书也会定期更新,所以你需要每隔几个月就检查一遍,以便确认获取的版本为当前最新的版本。当然,你也别忘了,可以通过网站 PowerShell.org 的论坛向 Don 咨询一些问题。
WMI 是一个外部技术;PowerShell 仅仅与其接口交互而已。 本章的重点将放在 PowerShell 如何与 WMI 交互,而不是 WMI 的底层实现机制。
wmi 会将 Windows 计算机数万个管理信息,整理成易于访问的形式。
WMI 被组织成命名空间(namespaces)。可以把命名空间想象为关联到特定产品或技术的一个文件夹。
“root\SecurtityCenter”的内容根据你计算机上的已安装程序的情况而有所不同,新版本的Windows使用“root\SecurityCenter2”代替它,这是WMI使人困惑的例子之一。
微软管理控制台(Microsoft Management Console, MMC)的 WMI 控制单元在我本机上产生的一些命名空间
在命名空间中,WMI 被分成一系列的类,每个类都是可用于 WMI 查询的管理单元。比如,
但是即使一个计算机上存在某个类,也不代表计算机实际上安装了对应组件。
wmimgmt.msc
属性->安全
1 |
#名称空间列表 |
从名称空间获取所有类
1 |
Get-CimClass * root/StandardCimV2 |
获取指定名称空间中指定类
1 |
# 左过滤 |
类中多个实例就表示有多个管理组件,一个实例由类代表了一个现实世界的事件。
一个 bios,就表示
Win32_BIOS
类只有一个实例。
1 |
PS D:\> Get-CimClass *_bios root/CIMV2 |
在“root\CIMv2”中的类型一般以“Win32 ”(即使在 64 位系统中亦然)或“CIM ”(Common Information Model 的缩写,是 WMI 建立的标准)开头。在其他命名空间中,这些类名前缀很少出现。不同命名空间下的类型名称重复也存在可能性,虽然这种情况很少,但在 WMI 中允许存在,因为每个命名空间实际上是一种有边界的容器。当你引用一个类时,你同时需要引用其命名空间,以便 WMI 知道从哪里找到对应的类,从而避免因为多个重名但不属于同一个命名空间的类造成混乱。
所有这些实例、类和其他不可名状的东西统称为 WMI 仓库(WMI Repository)。在旧版本的 Windows 中,WMI 仓库有时会损坏而导致不可用,必须通过重建恢复。但从 Win 7 开始,这种情况越来越少见。
表面看上去,使用 WMI 十分简单:你只需要指出哪个类包含你要的信息,然后从 WMI 中查询类的实例,最后检查实例的属性得知其管理信息。有时候需要实例执行一个方法,从而启动一个动作(action)或开始一个配置变更(configuration change)。
微软为 WMI 制定了一系列的编程标准,但是产品组或多或少把精力放在如何实现类和是否对其文档化。结果就是使得 WMI 变得混乱。
IIS 团队已经放弃了把 WMI 作为管理接口的做法,并从 v7.5 开始把注意力集中在 PowerShell Cmdlets 上,并且用一个 PSProvider 类替代 WMI。
WMI 不支持类搜索,因此查找这些类对你来说就变得费时费力
微软正在努力使 PowerShell Cmdlets 尽可能完成更多的管理任务。比如,过去 WMI 仅用于某种特定的编程方式重启远程计算机,这个方法由“Win32_OperatingSystem”类实现。而现在,PowerShell 提供了名称为“Restart-Computer”的 Cmdlet 来实现。
在某些情况下,Cmdlets 内部会通过 WMI 实现,而无须直接调用 WMI。Cmdlets 能提供更一致的接口,并且这些接口大部分都有很好的文档支持。虽然 WMI 不会消失,但你时不时还是需要在某些场景用到 WMI。
1 |
- Example 6: Restart a remote computer and wait for PowerShell - |
PowerShell v3 及后续版本中, Get-Command”输出的一部分 你会留意到大量“CIM”命令,在大部分情况下,这些命令都是对 WMI 的某些部分进行了封装,从而提供了更加以 PowerShell 为中心的方式与 WMI 交互。你可以像使用其他 Cmdlet 一样使用这些 Cmdlet,包括对这些 Cmdlet 使用 Help 命令。这使得使用这部分 Cmdlet 和使用其他 PowerShell 中的 Cmdlet 的体验变得一致,也便于隐藏一些底层的 WMI 的复杂性。
powershell v5 之后,将不存在 cim
你可以尝试在搜索引擎中搜索 WMI explorer 并查看结果。你也可以尝试访问 http://powershell.org/wp/2013/03/08/wmi-explorer/。我们可以从这类工具中得到大部分所需的关于WMI的信息。当然,这个工作要求耐心和不少的浏览量——这并不是最佳方法,但是我们最终还是选择了这个工具。
由于每台计算机上的 WMI 命名空间和类都不尽相同,所以你需要把工具直接在准备查询的机器上运行,以便看到对应机器的 WMI 仓库。
现在我们需要查询一组计算机并从中得知它们的图标间距设置。这个任务依赖于 Windows 桌面,并且是操作系统的核心部分,所以我们从“root\CIMv2”类开始,显示在 WMI 浏览器左侧的树型视图中(见图 14.3)。单击命名空间并在右侧查看对应的类,我们知道需要“Desktop”这个关键字。滚动右侧窗口的滚动条,最终锁定“
Win32_Desktop
”并单击它。
此时下方窗体将展示其对应明细,然后我们选择【Properties】(属性)选项卡并查看其内容。在距离下边大约三分之一的地方,找到“IconSpacing”,其值为整数。
1 |
PS G:\> Get-CimClass *desktop* |
1 |
PS G:\> Get-CimInstance -ClassName Win32_Desktop | select * | Out-GridView |
1 |
PS G:\> Get-CimInstance -ClassName Win32_Desktop | select IconSpacing,name |
假设我们想知道一些关于磁盘的信息,那么需要从猜测正确的命名空间开始。但是我们已经 知道“root\CIMv2”包含了所有 OS 核心和硬件设备的信息 ,所以可以使用下面的命令。
1 |
PS G:\> Get-CimClass *disk* |
1 |
PS G:\> Get-WmiObject * -Namespace root\cimv2 -list | select -f 10 |
与磁盘相关
1 |
PS G:\> Get-WmiObject * -Namespace root\cimv2 -list | ? name -Match 'disk' |
最终我们找到“Win32_LogicalDisk”。
1 |
PS G:\> Get-CimInstance Win32_LogicalDisk |
上面使用了 3 个命令 get-cimclass, get-ciminstance, get-wmiobject
在 PowerShell v3 及后续版本中,有两种与 WMI 交互的方式。
“WMI Cmdlets”,例如“Get-WmiObject”与“Invoke-WmiMethod”——这些都是遗留命令,意味着它们依旧能工作,但是微软不会对它们进行后续开发投入。它们与远程过程调用(RPC)交互,也就是说,只有在防火墙支持状态审查时才能通过防火墙(实际上很难)。
新版的“CIM Cmdlets”,例如“Get-CimInstance”与“Invoke-CimMethod”——它们或多或少等价于旧版本的“WMI Cmdlets”,但是它们通过 WS-MAN(由 Windows 远程管理服务实现)交互,替代原有的 RPCs。这是微软的主方向,执行“Get-Command–noun CIM*”可以显示很多微软提供的这类命令的功能。
1 |
PS G:\> Get-Command -Noun cim* |
支持 cim*的 cmlets, 统一使用 cim 获取信息。不支持就使用 wmiobject 获取信息。而且这 2 者统一的后端均是 WMI。
1 |
Gets instances of Windows Management Instrumentation (WMI) classes or information about the available classes. |
可以指定一个命名空间、一个类名称甚至远程计算机的名称以及备用凭据名。如果需要,还可以从指定的计算机中查询该类的所有实例。
1 |
Get-WmiObject * -Namespace root\cimv2 -list |
名称空间的哪些类
1 |
PS G:\> Get-WmiObject win32_* -Namespace root\cimv2 -list |
使用 cim
1 |
PS G:\> Get-CimClass * -Namespace root\SecurityCenter2 |
对于许多 WMI 类,PowerShell 的默认配置已经设定了需要展示的属性。“Win32_OperatingSystem”是一个很好的例子,因为它默认仅在列表中展示了 6 个属性。请记住,你总能把 WMI 对象用管道传输到“Gm”或“Format-List *”中,以便查看所有可用的属性。“Gm”总是列出所有可用的方法。
1 |
|
1 |
PS G:\> Get-WmiObject *system* -list |
另外,“-filter”参数允许你通过指定的规则查询特定实例。该参数使用起来有点棘手。下面的示例可以看出其最坏情况下的结果。
get-ciminstance
,
get-wmiobject
2 个参数均支持
-filter
1 |
PS G:\> Get-Alias -Definition Get-WmiObject |
1 |
PS D:\> Get-NetAdapter -Physical | Get-NetIPAddress -AddressFamily ipv4 | select -ExpandProperty ipaddress |
计算机名称按顺序连接,如果某一台计算机不可用,该 Cmdlet 会产生一个错误,并跳过这台计算机,并转向下一台计算机。对于不可用的计算机,Cmdlet 通常需要等待直到发生超时,意味着 Cmdlet 可能会暂停 30 ~ 45 秒之后才决定放弃这台计算机,然后产生错误并继续向后连接。
一旦你查询到一个 WMI 实例的集合后,就可以把它们用管道连接到任何以“-Object”Cmdlet、“Format-”Cmdlet 或“Out-”“Export-”“ConvertTo-”开头 Cmdlet 中。你可以使用自定义表格显示“Win32_BIOS”类的信息。
1 |
PS D:\> gwmi win32_bios -ComputerName (Get-NetAdapter -Physical | Get-NetIPAddress -AddressFamily ipv4 | select -ExpandProperty ipaddress) | Format-Table serialnumber,version -AutoSize |
此时你可以创建一个关于表的自定义列,并用列的表达式执行一个全新的 WMI 查询。
1 |
PS D:\> gwmi win32_bios -ComputerName (Get-NetAdapter -Physical | Get-NetIPAddress -AddressFamily ipv4 | select -ExpandProperty ipaddress) | Format-Table @{name='computername';expression={$_.__server}},@{label='biosserial';expression={$_.serialnumber}},@{l='osbuild';e={gwmi -class win32_operatingsystem -computer $_.__server | select -expand buildnumber }} -AutoSize |
-computername localhost,localhost
, 本例使用本机 IP,因为没有测试 windows
$_.property
引用当前列哪个属性的值。
我们已经提醒过,一些 WMI 类包含方法。你可以在第 16 章中看到如何使用这些方法。这些方法相对难懂,所以我们独立出一章来介绍。
PowerShell v3 引入的新命令,与“Get-WmiObject”有很多相似的地方,但是也有几个语法上的差异。
-credential
参数。如果你需要从远程计算机查询并被要求提供替代凭据,需要通过“Invoke-Command”(前面章节已介绍)发送“Get-CimInstance”。
例如获取 win32_logicaldisk 类的实例
1 |
PS D:\> Get-CimInstance Win32_LogicalDisk |
使用凭据查询时
1 |
Invoke-Command { Get-CimInstance Win32_LogicalDisk | ft deviceid,drivetype,ProviderName,VolumeName,@{n='size(gb)';e={$_.size / 1GB};format='F2'},@{L='freespace(gb)'; E={$_.freespace / 1GB};formatstring='F2'} } -ComputerName 192.168.13.103 -Credential slc |
你只需要把类名在 Google 或者 Bing 上搜索,通常第一条就会导航到 http://msdn.microsoft.com/。
1 |
wmi win32 disk |
一搜索就出现, https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskdrive
“help win32_service”,行不通。操作系统本身不包含任何 WMI 信息,所以 PowerShell 的帮助功能不能实现你的期望。
查询“root\SecurityCenter”。不幸的是,你不会在结果中找哪怕一个微软的文档页。
WMI 的筛选,放弃直接使用 powershell 的 where-object
虽然 PowerShell 提供了从 WMI 查询信息的简易途径,但是 WMI 并不集成在 PowerShell 中。WMI 是一个外部技术,有自己的规则和工作方式。WMI 虽然可以在 PowerShell 内部使用,但和其他 Cmdlets 不一样,因为这些 Cmdlets 完全集成在 PowerShell 内。
1 |
# 帮助 |
1 |
PS D:\> (Get-CimClass Win32_NetworkAdapterConfiguration).CimClassMethods | ? name -Match dhcp |
查找模块的另一种方法,基于标签查找
1 |
PS D:\> Find-Module -tag ip | Sort-Object name | ft name,version,description -AutoSize -Wrap |
1 |
Network 0.9.1 Module with various network functions |
公网 ip
1 |
PS D:\> Get-MyIpAddress |
1 |
Get-IPInfo 1.0.2 Quickly look up IP Information including location, ISP and Organization |
1 |
PS D:\> get-ipinfo (Get-MyIpAddress) |
1 |
PS D:\> get-ipinfo (Get-MyIpAddress) |
查找模块,基于描述查
1 |
PS D:\> Find-Module | ? description -Match ' ip' |
1 |
PS D:\> Get-CimInstance Win32_OperatingSystem | ft csname,Caption,Description,@{L='bios';E={Get-CimInstance Win32_BIOS | select -ExpandProperty SerialNumber }},BuildNumber -AutoSize |
1 |
PS D:\> Get-CimClass *fix* |
1 |
PS D:\> Get-CimInstance Win32_QuickFixEngineering |
1 |
PS D:\> Compare-Object (Get-CimInstance Win32_QuickFixEngineering) (get-hotfix) |
1 |
PS D:\> Get-CimClass *_service* |
1 |
PS D:\> Get-CimClass *product -Namespace root/SecurityCenter |
1 |
PS D:\> Get-CimInstance AntiSpywareProduct -Namespace root/SecurityCenter |
1 |
PS D:\> Get-CimInstance Win32_Process | select ProcessName,ProcessId | ft -AutoSize |
1 |
PS D:\> Get-WmiObject win32_systemdriver | fl Name,DisplayName,PathName,StartMode,Status |
1 |
PS D:\> Invoke-Command -ComputerName (Get-NetAdapter -Physical | Get-NetIPAddress -AddressFamily IPv4 | select -ExpandProperty ipaddress),192.168.0.109 { get-psprovider } |
1 |
192.168.0.109 |
1 |
PS D:\> Get-Content C:\Computers.txt |
你应该确保上述两个名称各自独占一行——总共 2 行。保存文件并关闭记事本。然后写一个命令列出正在电脑上运行的服务名称,并将其写入到 C:\Computer .txt 文件中。
1 |
Get-Service | ? status -Match running | Out-File C:\Computer.txt |
1 |
PS D:\> Get-CimInstance win32_logicaldisk | ? { $_.DriveType -Match '3' -and $_.size -and $_.FreeSpace / $_.Size * 100 -gt 50} | ft DeviceID,DriveType,ProviderName,VolumeName,@{N='size'; E={$_.freespace / $_.size * 100};formatstring='F2'} |
1 |
PS D:\> Get-CimInstance win32_logicaldisk | ? { $_.DriveType -Match '3'} | select DeviceID,DriveType,ProviderName,VolumeName,@{N='size'; E={$_.freespace / $_.size * 100}} | ? size -gt 50 |
1 |
gwmi win32_logicaldisk | ft deviceid,@{N='%free'; E={$_.freespace / $_.size * 100};formatstring='F2'},@{N='available(GB)'; E={$_.freespace / 1024 /1024/1024};FORMATSTRING="F2"},@{N='size(GB)'; E={$_.size / 1024 /1024/1024};FORMATSTRING="F2"} |
1 |
PS D:\> Get-CimClass win32_* | select -f 10 |
1 |
PS D:\> gwmi win32_service -Filter "startmode='auto' and state<>'running'" |
表达式过滤
1 |
PS D:\> Get-CimInstance Win32_Service | ? {$_.StartMode -Match 'auto' -and $_.state -notmatch 'running' } |
1 |
PS D:\> Get-Command -Noun *mail* |
1 |
PS D:\> Get-ChildItem c:\ | fl * |
1 |
PS D:\> Get-ChildItem C:\Users\ -Attributes Directory,Hidden | get-acl |
1 |
Invoke-Command Cmdlet Microsoft.PowerShell.Core Runs commands on local and remote computers. |
1 |
PS D:\> Get-Help get-credential -Examples |
1 |
PS D:\> help start-process |
1 |
Suspends the activity in a script or session for the specified period of time. |
1 |
PS D:\> Start-Sleep 10 |
1 |
PS D:\> help about_Arithmetic_Operators -ShowWindow |
1 |
|
1 |
PS D:\> 6 + 2 # 返回数字 |
1 |
PS D:\> + 123 |
1 |
PS D:\> 6*2 |
1 |
PS D:\> 6 / 2 |
1 |
$a = 0 |
数值循环,数组循环
1 |
PS D:\> (0..9).ForEach{ $array += $_ } |
1 |
Get-Process | Where-Object { ($_.ws * 2) -gt 50mb } |
1 |
PS D:\> get-winevent -listlog * | ? recordcount -gt 0 | Sort-Object recordcount -desc | select -f 10 |
1 |
PS D:\> Get-CimInstance Win32_Processor | ft NumberOfLogicalProcessors,Manufacturer,NAME,@{N='MaxSpeed';E={$_.MaxClockSpeed}} |
1 |
PS D:\> Get-CimInstance Win32_Process | ? PeakWorkingSetSize -ge 100KB | ft ProcessName,Path,Peak* -AutoSize |
1 |
PS D:\> Get-Content D:\Documents\WindowsPowerShell\profile.ps1 |
1 |
PS D:\> Find-Module -tag network | Sort-Object name | ft name,version,description -AutoSize -Wrap |
如果你发现 WMI 实在很难理解,别担心。这是正常反应。但是我们有一些好消息:在 PowerShell v3 及后续版本中,你可以在没有显式调用的情况下使用 WMI。因为微软已经开发了数百个 Cmdlets 用于封装 WMI。这些 Cmdlets 提供了帮助信息、可发现性、示例和所有其他 Cmdlets 能提供给你的好处,但是它们内部实现还是使用 WMI。这样能更好地使用 WMI 的强大功能,又避免处理一些使人困惑的元素。
你键入一条命令,然后按回车键,之后 PowerShell 就会等待该命令执行结束。除非第一条命令执行结束,否则你无法运行第二条命令。 这叫 单任务
借助于 PowerShell 的后台作业功能,它可以将一个命令移至另一个独立的后台线程(一个独立的,PowerShell 后台进程)。该功能使得命令以后台模式运行,这样你就可以使用 PowerShell 处理其他任务。但是你必须在执行该命令之前就决定是否这样处理,因为在按回车键之后,无法将一个正在运行的命令移至后台进程。
当命令处于后台模式时,PowerShell 会使用一些机制来查看这些进程的状态,获取产生的结果等。
PowerShell for Linux 早期版本对 PowerShell 后台作业支持并不成熟。我们期望 PowerShell on Linux 最终会支持 PowerShell 后台作业但我们并不能保证它能支持到 Windows 版本那样。你已经了解如何阅读 PowerShell 帮助文档,本章会阐述这些概念。
通常情况下,我们会用同步模式执行命令,以便对这些命令进行测试,并使得可以正常工作。仅当它们被全面调试并能按照预期执行后,我们才会使用后台模式。我们只有遵循这些规则来保证命令的成功执行,这才是将命令置于后台模式的最好的时机。
PowerShell 将后台执行的命令称为作业(Jobs)。你可以通过多种方法创建作业,可以使用多种命令管理它们。
1 |
PS D:\> Get-Command -Noun jobs |
严格意义上,本章中讨论的作业只是你将来会使用到的其中一种作业而已。本质上讲,作业只是 PowerShell 的一个扩展点,也就是说,对他人(不管是微软还是第三方)而言,都有可能创建其他功能(也命名为作业)。但是这些作业与本章描述的作业看起来并不一样,并且工作方式也不一样。实际上,本章末尾将讲到的调度作业(Scheduled Jobs)与本章前面提到的常规作业并不一致。当你为实现不同目的而扩展该 Shell 时,也会遇到很多其他一些作业。我们只是想让你知道这些小细节,并且理解到 本章中所学的知识仅适用于 PowerShell 原生的常规作业。
1 |
PS D:\> help start-job -Examples |
1 |
PS D:\> start-job { get-service my-nginx } |
Start-Job [-ScriptBlock] <System.Management.Automation.ScriptBlock>
命令之后只需要脚本快,
如果没有指定一个脚本块,你也可以使用-FilePath 参数来使得作业执行包含多个命令的完整脚本文件。
自动使用默认的作业名称(Job1、Job2 等)
可以使用-Name 参数指定特定的作业名称
如果你需要作业运行在其他凭据下,那么可以使用-Credential 参数接受一个域名\用户名(DOMAIN\UserName)的凭据,同时该参数也会使得提示输入密码
作业会按照顺序被赋予一个作业 ID 号
这些作业完全运行于本地计算机上,的确如此。如果你执行一个可支持-ComputerName 参数的命令,在这种情形下,作业中的命令会被允许访问远程计算机。
1 |
PS D:\> start-job { Get-Service -ComputerName 192.168.13.103 my*} -Name test |
从某种程度上说,这个作业就是一个“远程作业”。但是由于该命令实际上是在本地运行,所以我们仍然将它视为本地作业。
创建的第一个作业被命名为 Job1,同时 ID 为 1,但是创建的第二个 Job 名为 Job3,同时 ID 为 3。原因是,每个作业至少都有包含一个子作业,第一个子作业(job1 的子作业)会被命名为 job2,其 ID 为 2。在本章后面章节会讲到子作业相关的知识。
尽管本地作业是在本地运行,但是它们也会需要使用 PowerShell 远程处理系统的架构,也就是在第 13 章中所讲的知识。如果你还没启用远程处理,那么将无法创建本地作业。
如果你在测试环境中运行相同的命令,那么需要在 C:根目录下新建一个名为 allservers.txt 的文本文件(因为在这些示例中,均在该路径下执行命令),同时按照每行一个名称的格式在该文件中写入多个计算机名称。你可以将本地计算机名称,以及多个 localhost 放在该文件中,正如我们展示的这样。
1 |
PS D:\> "192.168.13.103 |
1 |
PS D:\> Get-WmiObject win32_operationsystem -ComputerName (Get-Content .\allservers.txt) -asjob |
PowerShell 会创建一个上层的父作业(Job5,如上面返回结果中所示),同时会针对指定的每个计算机创建一个子作业。
你可以看到,上面的输出表格的 Location 列中包含多个计算机名称,也就表明该作业也会在这些计算机上运行。
实际上,该实现过程等同于同步执行 Get-WMIObject 命令,唯一不同点是该命令在后台运行。
你也会发现存在除 Get-WMIObject 外的其他命令来启动一个作业。尝试执行 Help * -Parameter AsJob,看看你是否可以找到所有的这种命令。
1 |
PS D:\> help * -Parameter asjob |
在第 14 章中学到的新的 Get-CimInstance 命令,并没有包含-AsJob 参数。如果你想在作业中使用该命令,请运行 Start-Job 或者 Invoke-Command(你即将学到的命令),并且将 Get-CIMInstance(或者说,任何新的 CIM 命令)放在脚本块中。
1 |
PS D:\> Invoke-Command -ComputerName (Get-Content .\allservers.txt) { Get-CimInstance Win32_OperatingSystem} -AsJob |
最大 32 个计算机一批批的,批量执行;要更多,-ThrottleLimit。
另外,当在所有计算机上都执行结束后,上层的父作业会返回一个完整的状态。
与另外两种新建作业的方式不同,该技术要求你在每台目标计算机上安装第二版或者之后版本的 PowerShell,同时要求在每台目标计算机上 PowerShell 中均启用远程处理。
命令会真正在每台远程计算机上执行,所以可以通过分布式计算工作负载提升复杂的或者长时间运行命令的性能。执行结果会返回到你的本地计算机。在你准备查看它们之前,结果都会与作业一起被存储。
1 |
PS D:\> Invoke-Command -ComputerName (Get-Content .\allservers.txt) { Get-CimInstance Win32_OperatingSystem} -AsJob -JobName myremotejob |
1 |
PS D:\> Get-Job |
你也可以通过作业 ID 或者名称去查询特定的作业信息。我们建议你可以尝试该命令并且将返回结果通过管道传递给 Format-List *,因为你已经收集了很多有用的信息。
1 |
PS D:\> get-job | ? id -eq 1 | fl * |
Receive-Job 命令。但是在运行该 Cmdlet 之前,请先了解下面的一些知识点。
可以通过作业 ID、作业名称,或者通过 Get-Job 命令获取作业列表,之后将它们通过管道传递给 Receive-Job 命令。
1 |
# 管道 |
如果你获取了父作业的返回结果,那么该结果会包含所有子作业的输出结果。当然,你也可以获取一个或多个子作业的执行结果。
1 |
PS D:\> Get-Job |
由于上面 10 号作业,一直有-keep, 所以有
HasMoreData
1 |
PS D:\> start-job { Get-ChildItem } |
1 |
PS D:\> get-job 1 | fl * |
start-job { Get-Service -ComputerName 192.168.13.103 my* }
你可以看到,Job1 包含了一个子作业 Job2。既然你知道了它的名字,那么你就可以直接获取该作业的信息。
1 |
PS D:\> get-job job2 | fl * |
某个作业会包含多个子作业,它们都会以这种格式罗列出来。此时你可能希望采用不同的方式来罗列它们,比如下面这样。
1 |
PS D:\> get-job 13 |
你也可以使用 Receive-Job 命令指定作业名称或 ID 获取来自任意独立子作业的结果。
1 |
PS D:\> receive-job 11 |
都可以指定作业 ID、作业名称,或者先获取作业信息,然后通过管道传递给这 3 个命令: remove-job, stop-job, wait-job
Remove-Job——该命令会移除一个作业,包括从内存中移除该作业缓存的所有输出结果。
■ Stop-Job——如果某个作业看起来卡住了,你可以通过执行该命令停止它。但是仍然可以获取截止到该时刻产生的结果。■
Wait-Job——该命令在下面场景中比较有用:当使用一段脚本开启一个作业,同时希望该脚本在作业运行完毕之后继续执行。该命令会使得 PowerShell 停止并等待作业执行,在作业执行结束后,允许 PowerShell 继续执行。
1 |
PS D:\> get-job | ? { -not $_.HasMoreData } | remove-job |
1 |
PS D:\> Start-Job { start-sleep 30 } |
1 |
PS D:\> receive-job 16 |
1 |
PS D:\> Invoke-Command { nothing } -ComputerName test -AsJob -jobname thiswillfail |
1 |
PS D:\> Get-Job |
我们向根本不存在的计算机发送一条不存在的命令来开启一个作业。当然,该作业立即就会失败,正如返回的 State 列。
此时,我们根本就不需要使用 Stop-Job,因为该作业并未运行。但是我们仍然可以获取对应的子作业列表。
获取子作业
1 |
PS D:\> Get-Job |
正如你所见,该作业并没有产生任何输出,因此你并不能获取对应的结果。但是该作业的错误信息仍然保留在结果中,你可以使用 Receive-Job 命令获取这部分信息。
1 |
PS D:\> receive-job 19 |
1 |
PS D:\> Invoke-Command { nothing } -ComputerName test,192.168.13.103 -AsJob -jobname thiswillfail |
Failed
均是失败的状态
在 v3 版本的 PowerShell 中引入了针对调度作业的支持——可以在 Windows 的计划任务程序中使用 PowerShell 友好的方式创建任务。
作业是 PowerShell 中的一个扩展点,也就意味着允许存在多种通过不同方式实现的作业。调度作业正好是这些不同种类的作业中的一种。
调度作业与标准计划任务作业有一点差别,调度作业的输出结果会存到磁盘中以供 PowerShell 后续进行使用。
调度作业(scheduled jobs)与调度任务(scheduled tasks)并不同——前一种是与 PowerShell 相关的,后一种是你经常使用的传统作业。
创建一个触发器(New-JobTrigger)开启一个调度作业,该触发器主要用于定义任务的运行时间。
同时,你也可以使用 New-ScheduledTaskOption 命令设置该作业的选项。
之后你使用 Register-ScheduledJob 命令将该作业注册到计划任务程序中。该命令采用计划任务程序中的 XML 格式来创建作业的定义,之后在磁盘上新建一个层级结构的文件夹存放每次作业运行的结果。
1 |
PS D:\> Register-ScheduledJob dailyproclist { get-process } |
The Register-ScheduledJob cmdlet creates the PROCESSJOB, which runs aGet-Process command. This scheduled job has the default job options and no
job trigger.
1 |
PS D:\> Register-ScheduledJob dailyproclist2 { get-process } -Trigger (New-JobTrigger -Daily -At 2am) -ScheduledJobOption (New-ScheduledJobOption -WakeToRun -RunElevated) |
每天凌晨两点执行 Get-Process 命令,生成一个 job
1 |
PS D:\> Get-JobTrigger 3 |
1 |
$trigger=New-JobTrigger -Weekly -DaysOfWeek "Monday", "Thursday" -At "5:00 AM" |
调度作业的选项,如果有必要,会唤醒计算机,同时要求该作业运行在高级特权下。
1 |
PS D:\> Get-ScheduledJobOption 3 |
调度之后,生成 job.
不像常规的作业,从调度作业中获取结果并不会导致结果被删除,因为它们是被缓存在磁盘上,而非内存中。之后可以继续多次获取该结果。当你移除这些作业时,对应的结果也会从磁盘上被移除
你可以通过 Register-ScheduledJob 命令的-MaxResultCount 参数控制存放的结果数量。
1 |
PS D:\> Get-ScheduledJob |
磁盘的作业内容,看 fl *
1 |
Get-Service | Export-Csv process.csv.27 |
本地命令: start-job {} 代码中不要出现 invoke
远程命令: invoke-command {} 代码块中不要出现 start-job
“我们是否可以看到由其他人开启的作业呢”,这里的答案是“不能”,但是调度作业例外。常规的作业完全包含在 PowerShell 进程中。尽管你可以看到其他用户在运行 PowerShell,但是你还是没有办法看到该进程内部的一些信息。这和其他应用程序一样。例如,你可以看到他人有运行微软的 Word 软件,但是你无法看到他们正在编辑的文档,因为这些文档完全隐藏于 Word 的进程中。
调度作业是一个例外:具有权限的任何人都可以看到它们,修改它们,删除它们,以及获取它们的结果。这是因为它们存放于物理磁盘上。请注意,它们存放于你的用户配置文件下,因此它通常要求管理员从配置文件中获取文件(和结果)。
1 |
PS D:\> Get-ChildItem c:\*.ps1 -Recurse |
1 |
PS D:\> Invoke-Command -ComputerName (Get-Content .\allservers.txt) { Get-ChildItem c:\*.ps1 -Recurse } -AsJob -JobName remote_lookup_ps1 |
1 |
receive-job 1 -keep |
自动化管理,这通常意味着你将会在多个目标上同时执行任务。你或许希望重启多台计算机,重新配置多个服务,修改多个邮箱等。
在本章,你将学到 3 种技术:批处理 Cmdlet、WMI 方法以及对象枚举,用于完成这些以及其他多目标任务。同时,你需要知道本章中的大多数示例无法在 Linux 或 macOS 平台下工作;这些示例(至少是当前)仅在 Windowsk 中有效。但是无论你使用的是哪种操作系统,这里谈到的概念与技术并无不同。
1 |
for each varservice in colservices |
(1)假设变量 colServices 包含多个服务。因为获取服务的方式有很多,所以先不关心 colServices 如何被赋值。重要的是,你已经获取到服务并将其存入变量。
(2)ForEach 结构将会遍历或枚举所有服务,一次一个。每次都将服务存入变量 varService。使用该结构,varService 将会仅包含一个服务。如果 colServices 包含 50 个服务,则该循环体结构将会执行 50 次,每一次 varService 变量都会只包含这 50 个服务中的一个。
(3)在循环结构中,每次都执行一个方法——在本例中是 ChangeStartMode()方法——完成某些工作。
对于上述步骤,如果再思考一下,就会发现所采用的方法并不是一次并行执行所有服务,而是每次只执行一个。方式和使用图形用户界面(GUI)重新配置服务并无不同。唯一的区别是代码使得计算机每次只配置一个服务,而不是人为操作。
计算机擅长执行重复操作,所以上面的过程所使用的方法是可取的。但问题在于该方法需要我们给计算机提供更长、更复杂的指令。学习使用该语言编写这些指令集需要花费时间,这也是管理员会尝试避免 VBScript 和其他脚本语言的原因。
PowerShell 可以使该方法重复,因为有些时候你还是需要上述方法,我们将会在本章后面展示如何做。但利用计算机枚举对象的方式并不是使用 PowerShell 最高效的方式。实际上,PowerShell 提供了其他两种更加易于学习和减少输入的方式,并且功能更加强大。
很多 PowerShell Cmdlet 可以接受批量对象,或者称之为对象集合。比如在第 6 章,你已经学习过利用管道将一个 Cmdlet 产生的结果传输给另一个 Cmdlet,
1 |
PS D:\> Get-Service | Stop-Service |
这是一个使用批处理管理的示例。在本例中,Stop-Service 专门被设计用于从管道接受一个或多个服务对象,并停止服务。Set-Service、Stop-Service、Move-ADObject 以及 Move-Mailbox 都是接受一个或多个输入对象并执行其任务或行为的 Cmdlet 示例。你
PowerShell 知道如何使用更简单的语法规则处理批量对象。
我们希望改变 3 个服务的启动模式。我们不选择 VBScript 方式的方法,而是采用下面这种。
1 |
PS D:\> Get-Service bits,Spooler,W32Time | Set-Service -StartupType Automatic |
Get-Service 也是一种批处理 Cmdlet。这是由于该命令能够从多台计算机中获取服务。假设你需要变更同样这 3 台计算机上的服务。
1 |
PS D:\> Get-Service bits,Spooler,W32Time -ComputerName server1,server2,server3 | Set-Service -StartupType Automatic |
上述方法中一个潜在的问题在于,执行动作的 Cmdlet 通常不会返回表示作业状态的结果。这意味着上面两个命令都不会产生可视化结果,这非常令人不安。值得庆幸的是,这些命令通常会有一个-passThru 参数,该参数用于打印出该命令所接受的对象。你也可以使用 Set-Service 输出其修改的服务,并使用 Get-Service 重新获取这些服务以便查看之前的命令是否生效。
1 |
PS D:\> Get-Service bits,Spooler,W32Time -ComputerName 192.168.0.109 | Start-Service -PassThru |
该命令将会从 3 台计算机列表中获取指定的服务,然后通过管道将这些服务传递给 Start-Service。该命令不仅会启动服务,而且会将涉及的服务对象打印在屏幕上。然后这些服务对象将会通过管道传递给 Out-File,将这些被更新对象的信息存储在文本文件中。
这是我们使用 PowerShell 推荐的首选方式。如果存在可以通过 Cmdlet 完成的工作,请使用 Cmdlet。理想情况下,Cmdlet 的作者都会选择以对象批处理的方式处理对象,但并不总是这样(Cmdlet 作者也在学习为我们这些管理员写 Cmdlet 的最佳方式)。这是最理想的方式。
总有一些任务无法通过调用 Cmdlet 完成。而且有一些我们可以通过 Windows 管理规范(WMI)可以操控的条目(关于 WMI,我们将会在第 14 章讲解)。
WMI 中的 Win32_NetworkAdapterConfiguration 类。该类代表与网卡绑定的配置信息(网卡可以有多个配置,但目前我们假设它只有一个配置信息,这也是对于大多数计算机的常见配置)。假如说我们的目标是在计算机上所有的 Intel 网卡上启用 DHCP,但我们不希望启用 RAS 或其他虚拟网卡的 DHCP。
1 |
PS D:\> Get-CimInstance Win32_NetworkAdapterConfiguration | ? Description -Match vmware |
我们欢迎你跟随本章的示例执行代码。你或许需要小幅修改命令,从而获得希望的结果。比如说,你的计算机中并没有使用 Intel 制造的网卡,则需要将过滤条件做适当的修改。
我们在管道中包含这些配置对象信息后,我们希望启用 DHCP(你可以看到其中一块网卡并没有启用 DHCP)。我们或许可以找一个名称类似“Enable-DHCP”的 Cmdlet。不幸的是,我们找不到该 Cmdlet,因此不存在该 Cmdlet。没有任何 Cmdlet 可以直接在批处理中与 WMI 对象打交道。
下一步是查看对象本身是否包含可以启用 DHCP 的方法,为了找出结果,我们将配置对象通过管道传输给 Get-Member
1 |
PS D:\> (Get-WmiObject Win32_NetworkAdapterConfiguration | ? Description -Match vmware | gm ) |
1 |
PS D:\> (Get-WmiObject Win32_NetworkAdapterConfiguration | ? Description -Match vmware ).EnableDHCP() |
1 |
PS D:\> Get-WmiObject Win32_NetworkAdapterConfiguration | ? Description -Match vmware | Invoke-WmiMethod -name enabledhcp |
你需要记住如下几条。■ 方法名称后无须加括号。■ 方法名称不区分大小写。■ Invoke-WmiMethod 一次只能接收一种类型的 WMI 对象。在本例中,我们只发送给 Win32_NetworkAdapterConfiguration 一种对象,这意味着命令可以如预期产生效果。当然也可以一次发送多个对象(实际上,这是重点),但所有的对象都必须是同一类型。■ 你可以针对 Invoke-WmiMethod 方法加上-WhatIf 和-Conifrm 参数。但直接由对象调用方法时,无法使用这些参数。
当你有一个 WMI 对象包含可执行的方法时,大多可以使用 Invoke-WmiMethod。该命令对于远程计算机同样有效。我们的基本原则是“如果你可以使用 Get-WmiObject 获取对象,则也能够使用 Invoke-WmiObject 执行它的方法”。
你会发现 Get-WmiObject 与 Invoke-WmiMethod 都是“遗留”用于操作 WMI 的 Cmdlet;这两个命令的接替者为 Get-CimInstanc 和 Invoke-CimMethod。它们的工作方式或多或少有些相同。
1 |
PS D:\> Get-Cimclass Win32_NetworkAdapterConfiguration | select -ExpandProperty CimClassMethods | ? name -Match dhcp |
虽然 WMI 所需的 RPC 网络通信难以穿透防火墙,但 WMI 能够适用的计算机数量最多(当前来说); CIM 只需要更新更简单的 WS-MAN 通信,但在老版本的 Windows 默认情况下,并没有安装 WS-MAN。
请尝试在 PowerShell 中运行 Help Set-NetIPAddress。在较新版本的 Windows 中,你将会发现这个强大的 Cmdlet 掩盖了大量底层 WMI/CIM 的复杂性。我们可以使用该 Cmdlet 变更 IP 地址,而无需一大堆 WMI/CIM。这是一个真实的教训:即使你在网上阅读了关于该主题的一些资料,也并不意味着新版本的 PowerShell 没有提供更好的方式。大多数发布在网上的资料都是基于 PowerShell v1 和 v2,但 v3 和更新的版本中提供的 Cmdlet 至少比之前的好 4 ~ 5 倍。
1 |
PS D:\> help Set-NetIPAddress |
PowerShell 提供了两种方法:第一种是使用 Cmdlet,另一种是使用脚本结构。我们在本章主要关注第一种技术,并在第 21 章阐述第二种。
使用 Win32_Service 这个 WMI 类作为示例,你会发现无须为该方法的每一个参数赋值。你可以将你希望忽略的参数指定为 Null(PowerShell 中有一个特殊的内置$null变量)。我们希望变更服务的启动密码,也就是第8个参数。为了完成该工作,我们需要将前7个参数指定为$null。这意味着我们的方法执行代码看上去像下面这样。
无论是 Get-Service 还是 Set-Service,都无法显示或设置某个服务的登录密码。但 WMI 可以完成该工作,所以我们使用 WMI。
由于我们无法使用首选的 Set-Service 这个批处理 Cmdlet,让我们尝试第二种方式,也就是使用 Invoke-WmiMethod。该 Cmdlet 包含一个参数:-ArgumentList,可以利用该参数为方法指定参数。下面的示例是我们进行的尝试以及接收的结果。
1 |
PS D:\> Get-WmiObject win32_service | ? name -Match bits | Invoke-WmiMethod -name change -ArgumentList $null,$null,$null,$null,$null,$null,$null,$null,"p@ssword" |
我们这里使用的是 Get-WmiObject,但 Get-CimInstance 的语法与其几乎相同。
1 |
PS D:\> $d=@{StartPassword='password'} |
我们使用 Get-WmiObject 获取所有满足过滤条件的 Win32_Service 实例,也就是名称包含“BITS”的服务(照例,我们选择 BITS 服务是由于相比其他服务来说,该服务并没有那么重要,该服务停止运行不会导致计算机崩溃)。然后我们将 Win32_Service 对象传递给 ForEach-Object 这个 Cmdlet。
通过查看 Set-Service 的帮助文档,我们发现该命令并没有提供修改密码的方式,而 Get-WmiObject 和 Get-CimInstance 这两个命令都可以完成该功能。这使得我们可以做出总结:即使是 PowerShell v3,对于这个任务,WMI 依然是一种值得使用的方式。
将一组对象发送给 Cmdlet 并由 Cmdlet 对循环进行处理,而不是指示计算机“遍历列表中的东西,并对列表中的每一个东西执行某些行为”。
1 |
PS D:\> Get-Service *bit* | Stop-Service -PassThru |
1 |
PS D:\> Get-Service *bit* | ForEach-Object { Stop-Service -PassThru $_ } |
1 |
PS D:\> Get-Service *bit* | ForEach-Object { $_.start() } |
wmi 停服务
1 |
PS D:\> Get-WmiObject win32_service | ? name -Match bit | gm |
wmi 结合 遍历
1 |
PS D:\> Get-WmiObject win32_service | ? name -Match bit | ForEach-Object { $_.StopService() } |
ReturnValue : 0 表示成功
其实还有第六种方式——使用 PowerShell 的脚本语言完成工作。你将会发现 PowerShell 中每一项工作都可以使用多种方式完成,且没有哪一种方式是错误的。某些方式比其他方式更易于学习、记忆以及重复,这也是为什么我们按照所做的顺序关注我们可以使用的技术。
原生对象通常和 WMI 有同样的功能,但语法或许会有不同。在本例中,由 Get-Service 产生的 ServiceController 对象有 Stop()方法;而我们通过 WMI 的 Win32_Service 类访问同样的对象时,方法名称变为 StopService()。
假设你通过 Get-Something 这个 Cmdlet 获取到对象,你希望删除这些对象,但不存在 Delete-Something 或 Remove-Something 这样的 Cmdlet。但该对象包含 Delete 方法,那么你就可以这么做。
1 |
get-something | remove-something并没有 |
管道将对象传递给 Get-Member,可以查看该对象包含的方法。
1 |
get-something | get-member |
PowerShell 的内置帮助系统并未记录 WMI 方法的文档。你需要使用搜索引擎(通常搜索 WMI 类的名称)来找到 WMI 方法的指南和示例。
如果你获取一个服务对象的成员列表,你将会发现存在名称为 Stop 和 Start 的方法。
1 |
PS D:\> Get-Service | gm | ? name -Match '^st' |
如果希望找到该对象的文档,请重点关注 TypeName,在本例中也就是 System.Service Process.ServiceController。在搜索引擎中搜索完整的类型名称,你通常可以找到完整的官方开发文档,并可以根据文档找出你所需的特定方法的文档。
当我搜索
System.ServiceProcess.ServiceController
, 第 1 个结果中有方法[
https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontroller?view=dotnet-plat-ext-6.0#methods
]
多使用 ForEach-Object 的完整名称,而不是使用%或 ForEach 这样的别名。完整名称更易于阅读。如果你使用别人写的示例,请将别名替换为完整名称。
1 |
PS D:\> Get-Alias -Definition ForEach-Object |
花括号内的代码段对于每一个通过管道传入的对象执行一次。
在代码段内,$_代表通过管道传入的对象之一。
使用$_本身控制所有通过管道传入的对象;使用$_后的加“.”控制单独的方法或属性。
即使方法不需要任何参数,方法名称之后也总是跟随圆括号。当需要参数时,通过逗号将参数分隔放在括号内。
1 |
PS D:\> Get-Service | gm | ? name -Match pause |
1 |
PS D:\> Get-Process | gm | ? MemberType -Match method |
cim 相关的,类获取方法。实例只能传递给 invoke-cimmethod 调用方法。(实例没有方法)
1 |
PS D:\> Get-CimClass win32_process | select -ExpandProperty cimclassmethods |
wmi 相关的,直接对象获取方法,对象调用方法。
1 |
PS D:\> Get-WmiObject win32_process | ? name -Match notepad | Invoke-WmiMethod -Name terminate |
1 |
PS D:\> Get-Process notepad | Stop-Process |
cmdlet 遍历
1 |
PS D:\> Get-Process notepad | ForEach-Object { $_.kill() } |
cim 配置
1 |
PS D:\> Get-CimClass win32_process | select -ExpandProperty cimclassmethods |
wmi 配置
1 |
PS D:\> Get-WmiObject win32_process | ? name -Match notepad | Invoke-WmiMethod -Name terminate |
1 |
PS D:\> "hello world" | ForEach-Object { $_.toupper() } |
PowerShell 将如何影响环境的安全,同时会讲解如何配置 PowerShell 才能取得安全和强大功能上的平衡。
当 PowerShell 团队宣布他们创造了一种新的、能提供前所未有强大的功能与可编程能力的命令行 Shell 语言时,我们认为,警报来临,人们将对这种新的命令行 Shell 避之不及。
PowerShell 是在比尔·盖茨先生在微软发起的一个“可信赖计算计划”之后才进行开发的。在微软公司内部,该计划产生了很积极的效果:每个产品部门都要求配备一名资深软件安全专家,该专家会参与到设计会议、代码复审等工作中。该专家被称为产品的“安全伙伴”(并不是我们编造的)。PowerShell 产品的“安全伙伴”是经由微软出版的《编写安全代码》(Writing Secure Code)的其中一位作者,该书描写了如何编写不易受攻击者利用的软件。我们可以保证 PowerShell 与其他产品一样都是安全的 ——至少默认情况下,都是安全的。当然,你也可以修改这些默认值,但是当你进行操作时,请不要只考虑软件的功能,也要注意安全问题。这也就是本章要帮你完成的事情。
PowerShell 不会给被处理的对象任何额外的权限。PowerShell 仅仅是你使用当前权限来完成某些操作的一种实现方式而已。
PowerShell 无法绕过既有的权限。PowerShell 仅能完成这些用户凭借现有权限执行命令或者脚本可以完成的工作。
PowerShell 安全系统的目的并不是为了阻止用户在正常的权限下输入并运行某些命令。PowerShell 不会应用超过该用户当前拥有的权限之外的安全设置。从过去的经验我们知道,欺骗用户运行一段可能包含恶意代码的命令是非常简单的事。这也就是为什么 PowerShell 的大部分安全设置都被设计为阻止用户运行一些未知的脚本。“意外”这个部分是非常重要的:PowerShell 的安全并不旨在阻止一个已确定用户运行的脚本,只是为了阻止用户被欺骗运行来自不受信任来源的脚本。
下面讲到的内容超出本书范围,但是还是希望你能知道存在其他一些方法可以使得用户在其他凭据(而非自有凭据)下运行某些命令。通常称这种技术为脚本封装。它是一些商业脚本开发环境的一个特性,比如 SAPlEM PrimalScript( www.PrimalTools.com)。
创建一个脚本之后,你可以使用打包程序将这个脚本放入到一个可执行文件(.EXE)中。这并不是编码学中的编译过程:这个可执行文件并不是独立的,它需要在 PowerShell 安装之后才能执行。你也可以通过配置打包程序,将可用的凭据加密到可执行文件中。这样,如果有人运行该可执行文件,其中的脚本会在指定的凭据下被执行,而不依赖当前用户的凭据。
当然,被封装的凭据也不是百分之百安全。被封装的文件中都会包含用户名以及对应密码,尽管大部分打包程序都会进行用户及密码的加密。准确地说,针对大部分用户而言,他们都无法发现用户名以及对应的密码;但是针对一个熟练的加密专家来说,破解出用户名以及密码是很简单的一件事。
PowerShell 的安全并不是针对恶意软件的防护。一旦在你的系统上存在恶意软件,那么恶意软件可以做你权限范围内的任何事情。它可能使用 PowerShell 去执行一些恶意命令,也有可能非常轻易地使用多种其他技术损坏你的电脑。一旦在你的系统中存在恶意软件,那么你就被“挟持”了。当然,PowerShell 也并不是第二道防御系统。
,首先你需要 杀毒软件 来阻止恶意软件进入你的系统。对大部分人而言,可能忽略这样一个重要的概念:即使恶意软件可能借助 PowerShell 去完成一些危害行为,也不应该将恶意软件问题归咎于 PowerShell。杀毒软件必须阻止恶意软件运行。再次申明,PowerShell 设计出来并不是为了保护一个已经受损的系统。
PowerShell 中第一个安全措施是执行策略, 该策略主要用作防止用户被注入,从而执行一些非法脚本。
1 |
PS D:\> Get-ExecutionPolicy |
该命令会修改 Windows 注册表中的 HKEY_LOCAL_MACHINE 部分,但是需要在管理员权限下才能执行该命令,因为一般用户没有修改注册表的权限。
1 |
PS D:\> help Set-ExecutionPolicy -full |
1 |
PS D:\> Set-ExecutionPolicy RemoteSigned |
使用组策略对象(GPO)。从 Windows Server 2008 R2 开始,Windows PowerShell 相关的设置已经包含在内。如果因为某些原因你不得不继续使用老版本的域(我们为你哀悼),可以通过访问网站 http://download.microsoft.com,之后在搜索框中搜索PowerShell ADM 进行查找。
我们可以在“本地计算机策略”→ 用户配置 → 管理模板 →Windows 组件 →Windows PowerShell 中找到 PowerShell 的设置选项。图 17.2 展示了我们将该策略设置为“启用”的状态。当通过组策略对象来配置时,组策略中的设定会覆盖本地的任何设置值。实际上,如果你试图运行 Set-ExecutionPolicy,命令可以正常执行,但是会返回一个警告。该警告会告知由于组策略覆盖的原因,新修改的设定值不会生效。
通过手动运行 PowerShell.exe,并且给出-ExecutionPolicy 的命令行开关参数。如果采用这种方式,那么命令中指定的执行策略会覆盖本地任何设置和组策略中的设置值。
1 |
powershell.exe -ExecutionPolicy RemoteSigned |
■ Restricted——这是默认选项,除微软提供的一部分配置 PowerShell 的默认选项的脚本外,不允许执行其他任何脚本。这些脚本中附带微软的数字签名。如果修改这些数字签名,那么这些脚本就再也无法运行了。
■ AllSigned——经过受信任的证书颁发机构(CA)设计的数字证书签名之后的任意脚本,PowerShell 均可执行。
■ RemoteSigned——PowerShell 可以运行本地任何脚本 ,同时 也可以执行受信任的 CA 签发的数字证书签名之后的远程脚本 。“远程脚本”是指存在于远端计算机上的脚本,经常通过通用命名规则(UNC)方式访问这些脚本。我们也会将那些来自于网络上的脚本称为“远程脚本”。Internet Explorer、Firefox 和 Outlook 中提供的可下载的脚本,我们均可视为来自网络的脚本。在某些版本的 Windows 中,会区分网络路径以及 UNC 路径。在这些场景中,本地网络中的 UNC 都不会认为是“远程”。
■ Unrestricted——可以运行所有脚本。我们并不是很喜欢或不建议使用这个设置选项,因为该设置选项 无法提供足够的保护功能 。
■ Bypass——这个特殊的设定主要是 针对应用程序开发人员 ,他们会将 PowerShell 嵌入到他们的应用程序中。这个设定值会忽略已经配置好的执行策略,应当仅在主机应用程序提供了自身的脚本安全层时才使用该选项。你最终告诉 PowerShell 的是“别担心,安全问题我已经全部搞定”。
我们可以在组策略对象中设置一种执行策略,但是也可以使用 PowerShell.exe 的一个参数来覆盖该设定?通过 GPO 控制的设定能被轻易覆盖,这样有什么好处呢?这里主要是体现了执行策略被设计出来的一个目的:防止不知情的用户无意中运行一些匿名脚本。
执行策略并不是为了阻止用户去运行某个已知的脚本。如果真是这样,那么执行策略就不算是一种安全设置。
一个聪明的恶意软件开发者可以更容易直接访问.NET Framework 的函数,而不是费力去使用 PowerShell 作为媒介。或是用其他方式,如果一个未经授权的用户拥有你计算机的管理员权限执行任意代码,你已经是在劫难逃了。
微软强烈建议在执行脚本时使用 RemoteSigned 执行策略,并且仅在需要执行脚本的机器上采用该策略。根据微软的建议,其他计算机应当继续保持 Restricted 的执行策略。微软解释道:RemoteSigned 策略在安全性和功能之间取得了较好的平衡;AllSigned 相对更严格,但是它要求所有脚本都需要被数字签名。PowerShell 社区作为一个整体是更开放的,在到底哪种执行策略较优的问题上,存在大量的意见。就当前而言,我们会采纳微软的建议。当然,如果你有兴趣,你可以自己研究该主题。
多个专家,包括微软的一些开发人员,都建议使用 Unrestricted 作为执行策略。他们觉得该功能并没有提供一个安全层,并且你也不应该相信该设置可以将任何危险的行为隔离开。
数字代码签名,简称为代码签名,是指将一个密码签名应用到一个文本文件的过程。签名会显示在文件末端,并且类似下面的形式。
一旦你获取了一个三级证书(具体而言,你需要一个包装为带有验证码的证书——通常 CA 会针对不同的操作系统以及不同的编程语言提供不同的证书),之后将该证书安装到本地计算机。安装之后,你可以使用 PowerShell 的 Set-AuthenticodeSignature Cmdlet 将该数字签名应用到一段脚本。如果需要查看更详细的信息,你可以在 PowerShell 中执行 Help About_Signing 命令。许多商业的脚本开发环境(PowerShell Studio、PowerShell Plus 以及 PowerGUI 等)都可进行签名,甚至可以在你保存一段脚本时进行自动签名,这样使得签名过程更加透明。
签名不仅会提供脚本作者的身份信息,也会确保在作者对脚本签名之后,不会被他人更改。实现原理如下。
(1)脚本作者持有一个数字证书,该密钥包含两个密钥:一个公钥、一个私钥。
(2)当对脚本进行签名时,该签名会被私钥加密。私钥仅能被脚本开发者访问,同时仅有公钥能对该脚本进行解密。在签名中会包含脚本的副本。
(3)当 PowerShell 运行该脚本时,当你信任一个 CA 之后,你也会信任该 CA 签发的所有证书。通过作者的公钥(包含在签名中)去查找签发证书的 CA 是不是我们信任的 CA。
如果是信任的 CA,它会使用作者的公钥(包含在签名中)解密该签名。如果解密失败,则说明签名被篡改,那么该脚本就无法被运行。如果签名中的脚本副本与明文文本不吻合,那么该签名就会被识别为损坏,该脚本也无法被运行。
如果不是信任的 CA,脚本不会执行。
PowerShell 处理的整个流程。在该流程中,你可以看到为什么 AllSigned 执行策略在某种意义上说更加安全:在该种执行策略下,仅有包含签名的脚本才能被运行,也就意味着,你总是能识别某段脚本的作者。如果需要执行某段脚本,那么就会要求对该脚本进行签名。当然,如果你修改了该脚本,你也就需要对该脚本重新签名(可能稍显烦琐)。
PowerShell 包含另外两种总是一直有效的重要安全设置。一般情况下,它们应该保持默认值。
首先, Windows 不会将 PS1 文件扩展名(PowerShell 会将 PS1 识别为 PowerShell 的脚本)视为可执行文件类型。 双击打开 PS1 文件,默认会使用记事本打开进行编辑,而不会被执行。该配置选项会保证即使 PowerShell 的执行策略允许执行该脚本时,用户也不会在不知晓的情况下运行某段脚本。
其次, 在 Shell 中不能通过键入脚本名称执行该脚本 。Shell 不会在当前目录中搜索脚本,也就是说,如果有一个名为 test.PS1 的脚本,切换到该脚本路径下,键入 test 或者 test.PS1 都不会运行该脚本。
PowerShell 会检测该脚本,但是会给出警告信息:必须通过绝对路径或者相对路径来运行该脚本。因为 test.PS1 脚本位于 C:目录下,所以你可以键入 C:\test(绝对路径)或者运行.\test(指向当前路径的相对路径)。
该安全功能的目的是为了防止称为“命令劫持”的攻击类型。在该攻击中,它会将一个脚本文件放入到一个文件夹中,然后将它命名为某些内置的命令名,比如 Dir。在 PowerShell 中,如果你在一个命令前面没有加上其路径——比如运行 Dir 命令,那么你很明确运行的这个命令的功能;但是如果运行的是.\Dir,那么你就会运行一个名为 Dir.PS1 的脚本。
正如前面提到的,微软建议针对需要运行脚本的计算机,将 PowerShell 的执行策略设置为 RemoteSigned。当然,你也可以考虑设置为 AllSigned 或者 Unrestricted。
AllSigned 选项相对来说可能比较麻烦,但是如果采用了下面两条建议,那么该选项会变得更加方便。
商业 CA 针对一个代码签名证书,每年最多收费 900 美元。如果你没有一个内部的 PKI 可以提供免费的证书,那么你也可以自己制作。运行 Help About_Signing 可以查询如何获取以及使用 MakeCert.exe,该工具可以用来制作一个本地计算机信任的证书。如果你仅需在本地计算机运行脚本,那么这种方式是较快免费获取一个证书的方式。根据你所使用的 PowerShell 版本,你还可以使用一个名称为 New-SelfSignedCertificate 的 cmdlet,也能完成同样的工作。
1 |
PS D:\> help about_signing |
The New-SelfSignedCertificate cmdlet, introduced in the PKI module in
PowerShell 3.0, creates a self-signed certificate that is Appropriate for
testing. For more information, see the help topic for the
New-SelfSignedCertificate cmdlet.
1 |
|
使用命令 Makecert.exe
1 |
|
获取我签发的证书 Get-ChildItem cert:\CurrentUser\my -codesigning
1 |
PS D:\> Get-Content test.ps1 |
1 |
|
正如前面所讲,我们都不太建议你去修改.PS1 文件名的关联性。我们曾经看到过某些人修改了 Windows 的一些设置,将.PS1 视为一种可执行文件,也就意味着,你可以通过双击一个脚本来执行它。如果采用这种方式,那么我们就回到使用 VBScript 时的糟糕日子,所以你需要避免该问题。
,我们在本书中提供的脚本都没有经过数字签名。这些脚本可能会在不知情的情况下被修改,最后脱离本意。所以在运行这些脚本之前,你应该花费一定的时间去检查它们,理解它们实现的功能,并且确保它们与本书中对应的脚本相吻合(如果可能的话)。我们之所以不对这些脚本进行签名,就是为了让你花费这部分时间来完成这些工作:你应该养成这个习惯,不管该脚本来自于多么受信任的作者,都对那些从网上下载的脚本进行检查。
如果在生产环境中使用 PowerShell 工具,也请保证你选择的执行策略的设定值符合贵公司的安全规则与流程。我们不想你为了本书以及其动手实验而陷入某种困境。
让 powershell 可以执行脚本
一旦开始编写编程,就需要了解什么是“变量”,所以我们以此作为本章开端。你可以在其他复杂的脚本中使用变量,因此我们也会展示如何在这些地方使用变量。
同样可以把变量给别人,你操作时,别人的位置也会相应的改变。
1 |
PS D:\> $a=@{L='123'} |
powershell 的变量不是指针
PowerShell 并没有对变量有太多限制。比如,你不需要在使用变量前对其进行显式声明或定义。你也可以更改变量值的类型:某个时刻你可能只存储了一个进程在里面,下一时刻又可能存储一系列的计算机名进去。变量甚至可以存储多种不同的东西,比如服务的集合和进程的集合(虽然允许这样做,但是大部分情况下,使用变量的内容还是有讲究的)。
1 |
PS D:\> "hello" | gm | select -f 10 |
人们更倾向于把它当作一个简单的值。因为大部分情况下,我们关注的是它的值(如前面提到的“SERVER-R2”),而不会过多关注从属性中查找信息。也就是说,一个进程就算很庞大,数据结构很抽象,而你通常只需要处理一些单独的属性,如 VM、PM、Name、CPU、ID 等。一个字符串是一个对象,但是相比常见的进程,它又显得没那么复杂。
PowerShell 允许在一个变量中存储简单的值。你需要定义一个变量,然后使用等号符(=),用于赋值操作,接下来是变量所需存储的值。下面是例子。
1 |
PS D:\> $a="hello" |
可读,非数字打头
查看变量内容 $a, ${a}
字符变量,方法是字符的。
数值变量,方法是数值的。
1 |
PS D:\> $b=5 |
你可以在几乎所有地方使用变量来替代值。比如,当使用 WMI 时,你可以选择指定一个计算机名称。该命令类似:
1 |
PS D:\> Get-Service -ComputerName 192.168.0.109 |
1 |
# 强 |
关于 PowerShell 双引号的另外一个窍门是转义字符。这个字符是重音符(`),在美式键盘左上角的部分,通常在 Esc 键的下方,与波浪符(~)在同一个键上。使用重音符的问题是,在某些字体中,很难区分单引号。实际上,我们常常使用 Consolas 字体,因为它与 Lucida Console 或 Raster 字体相比更容易区分重音符。
单击 PowerShell 窗口左上角的控件,选择属性。在【字体】标签页,选择图 18.1 所示的 Consolas 字体,再单击【OK】按钮。然后输入一个单引号和重音符看是否能区分它们。图 18.1 显示了在我们系统中的样子。你能从中看出区别吗?我相信,使用足够大的字体时是可以的。区分起来有点困难,所以请你选择合适的字体和大小,以便你可以轻易地区分出它们。
1 |
PS D:\> $services = Get-Service |
这样会显示所有服务
1 |
PS D:\> "hello $($services[0].name)" |
$()中的所有内容都会被当成普通的PowerShell命令,结果也被放入字符串中,替代原有的所有内容。同样,该操作仅在双引号中有效。这种$()结构称为子表达式。
下面来看看转义字符的作用。它消除了任何在转义符之后有特殊意义字符的含义,或在某些情况下增加了字符的特殊意义。下面示例展示消除特殊含义的用法。
1 |
PS D:\> $b |
1 |
`n 换行 |
1 |
PS D:\> $f="hello`t wor`nld" |
运行“help about_escape”可以获得更多的信息,它包含了其他关于特殊转义符的列表。你可以尝试使用转义后的“t”实现 tab 功能,或者使用转义后的“a”使机器发出响声(a 是 alert,警报的意思)。
1 |
PS D:\> $array="1",2,"hello" |
1 |
PS D:\> $array = "helo","wo" |
在前面的例子中,我们使用了本章前面创建的变量$computername。你是否还记得该变量包含了一个类型为 System.String 的对象,并且在 18.2 节中已经通过与 Gm 进行管道传输后得到关于这个类型的属性和方法的完整列表。在这里,我们使用了 Length、ToUpper()、ToLower()和 Replace()方法。在每一个例子中,即使 ToUpper()和 ToLower()都不要求括号中出现任何值,但是我们也要在方法名称之后使用括号。同时可以看到这些方法都没有修改变量中的任何事物——你可以在示例的最后一行发现这一点。取而代之的是,每个方法都在原有基础上创建了一个新的字符串结果,看上去就好像方法对原始字符串进行了修改。
1 |
PS D:\> $array | ForEach-Object { $_.toupper() } |
1 |
PS D:\> $array | select length |
1 |
PS D:\> $services=Get-Service |
1 |
PS D:\> $services | ForEach-Object { write-host $_.name } |
1 |
PS D:\> $services | Select-Object -ExpandProperty name |
1 |
PS D:\> $objects=Get-WmiObject win32_service | ? name -Match bits |
把对象存入变量并让 PowerShell 指出我们正在使用对象的类型。这是由于 PowerShell 不在乎你放入变量中的对象是什么类型,但是我们在意。
假设你有一个变量希望用于存储一个数值,准备用于一些算术运算,并期待用户输入一个数值。
1 |
PS D:\> $number = read-host "enter a number" |
PowerShell 并没有把我们的输入当作数值,而是把它当作字符串。PowerShell 只是把 100 这个字符串重复了 10 次,而不是把 100 乘以 10。所以结果就是把字符串 100 在一行中列了 10 次。
通过把$number 用管道传输到 Gm 中,可以看出 Shell 把它视为 System.String,而不是 System.Int32。对于该问题有很多解决方法,我们将介绍其中最简单的一种。
1 |
PS D:\> $number | gm | select -f 3 |
告诉 Shell 知道$number 变量应该存储一个整型,强制 Shell 把值转换成一个整型。如下面的例子,通过在变量首次使用前使用[],明确定义一个数据类型“int”实现。
1 |
PS D:\> [int]$number = read-host "enter a number" |
我们使用了[int]强制$number仅包含整数❶。在你输入以后,我们把$number 用管道传输到 Gm,验证它的确已经是整型而不是字符串 ❷。最后我们可以看到,变量的值被认为是数值型并进行了实际乘法运算 ❸。
在 Shell 无法把数据的值转换成数字时,使得 Shell 可以抛出错误,因为$number 仅仅是存储数值的一个容器。
1 |
PS D:\> [int]$number = read-host "renter a number" |
这是一个防止后续问题的例子,因为你可以确保$number 能存储你希望的值。
除了[int]之外,还有很多其他的选择。下面是最常用的一些类型清单。■ [int]——整型数字。■ [single]和[double]——单精度和多精度浮点型数值(小数位部分的数值)。■ [string]——字符串。■ [char]——仅单个字符(如[char]$c=’X’)。■ [xml]——一个XML文档。不管你如何解析里面的值,都要确保它包含有效的XML标记(比如[xml]$doc=Get-Content MyXML.xml)。■ [adsi]——一个活动目录服务接口(ADSI)查询。Shell 会执行查询并把结果对象存入变量(如[adsi]$user=”WinNT:\MYDOMAIN\Administrator,user”)。
一旦你指定了对象类型,PowerShell 会强制它使用该类型,直到重新显式定义变量的类型。
1 |
PS D:\> [int]$x=5 |
1 |
New-Variable |
除了“Remove-Variable”之外,其他命令可能都用不上。该命令对需要删除的变量很有用(你也可以在变量中使用 Del 命令,使其删除该变量)。你可以使用其他功能——创建新的变量、读取变量和配置变量
需要把变量名称授予对应 Cmdlets 的-name 参数。这里仅需要变量名称——不需要包含美元符。通常只有在操作超出作用域(out-of-scope)变量时,才可能用到这些 Cmdlets。使用这种变量不是好习惯,所以本书不打算讲述该类变量,但是可以使用“help about_scope”来获取更详细的信息。
不要在变量中使用空格。虽然你可以这样做,但是这种语法相当不好。
如果变量仅包含一类对象,那么在你首次使用变量时,请定义对象类型。这样可以帮助你避免一些常见的逻辑错误,并且当你在商用脚本开发环境中工作时(PrimalScript 也许就是其中一个例子),编辑软件可以在你告诉它变量将包含的对象类型时提供一些提示功能。
美元符并不是变量名称的一部分。它只是让 Shell 知道你想访问变量的内容,而美元符后面的才是变量名称本身。
■ 如果紧随美元符后的字符是一个字母、数字或下画线,则变量名称包含美元符到下一个空白的所有字符(可能是一个空格、Tab 或回车)。
■ 如果紧随美元符后的是一个左大括号,则变量名称包含左大括号开始但不包含右大括号之间的所有内容。
1 |
PS D:\> $computers="192.168.0.109","localhost" |
1 |
PS D:\> Invoke-Command -ComputerName $computers { Get-CimInstance win32_bios } -AsJob -JobName test |
1 |
PS D:\> $receive=Receive-Job 3 |
1 |
PS D:\> $receive |
1 |
PS D:\> $receive | Export-Clixml test.xml |
在第 13 章中,你已经学到创建一个远程计算机的链接。你在本章中学到的是如何在一个步骤中创建、使用和关闭链接。它不正是在几个命令中创建连接,存入到变量中吗?
,本章的内容只有在与人进行交互时才有用。对于无人值守的脚本,本章内容介绍的技术并不合适,这是由于没有可以交互的人。
PowerShell 如何展示信息和进行对应的提示依赖于 PowerShell 运行的方式
当运行 PowerShell.exe 时,你看到的命令行控制台称为控制台主机。
图形化的 PowerShell ISE 通常被称为 ISE 主机或者图形化主机。
其他非微软的应用程序也可以调用 PowerShell 的引擎。
你与宿主应用程序进行交互,之后宿主应用程序将执行的命令传递给该引擎。宿主应用程序会展现引擎产生的结果集。
说明了 PowerShell 引擎和多种宿主应用程序之间的关系。每个宿主应用程序负责图形化展现引擎产生的任何输出结果,同时负责通过界面收集引擎需要的任何输入信息。也就意味着,PowerShell 可以通过多种方式展现执行结果和收集输入信息。实际上,控制台主机和 ISE 使用不同的方法收集输入信息:控制台主机会在命令行中展现一个文本的提示框,但是 ISE 会弹出一个会话框,该会话框中包含文本区域一个“OK”按钮。
我们希望指出这些差异点,因为有些时候可能会使得初学者非常困惑。为什么一个命令在命令行中的行为与在 ISE 中的行为大相径庭?这是因为你与 Shell 交互的方式由宿主应用程序决定,并不是由 PowerShell 本身决定。我们即将展示给你的命令会显示使用不同的行为,这些行为主要依赖于你在哪里执行这些命令。
1 |
PS D:\> read-host 'read a computer name' |
录入的信息写入变量
1 |
PS D:\> $prompt=$(read-host "enter a computer name") |
实际上,在大多数课堂中讲解了 Read-Host 命令后,总有人会问我们:“是否有其他方法可以始终展现一个图形化的输入框?”很多管理员会给用户部署一些 PowerShell 脚本,但是又不希望用户必须在命令行界面输入信息(毕竟,这并不是很“Windows 风格”)。我们想说的是可以实现,但是稍微复杂。最终的结果如图 19.3 所示。
为了创建一个图形界面的输入框,你必须借助于.Net Framework 本身。使用下面的命令。
1 |
PS D:\> [void][system.reflection.assembly]::LoadWithPartialName('microsoft.visualbasic') |
[system.reflection.assembly]::LoadWithPartialName
表示引用类型的静态方法,不需要生成实例; 该方法会接收我们希望添加的 Framework 组件名称。
1 |
PS D:\> $computername = [Microsoft.VisualBasic.Interaction]::InputBox('enter a computer name', 'computer name', 'localhost') |
1 |
PS D:\> $computername = [Microsoft.VisualBasic.Interaction]::InputBox('enter a computer name', 'computer name', 'localhost') |
我们再次使用了一个静态方法,这一次是 Microsoft .VisualBasic.Inter Action 类型。我们使用前面的命令将其载入到内存中。再次说明,如果你对“静态方法”感到很难理解,也没关系——直接照搬命令即可。
InputBox() 静态方法
1 |
PS D:\> $pwd_secure_string = Read-Host "Enter a Password" -AsSecureString |
既然你可以收集输入信息,那么也会希望了解一些展示返回结果的方法。Write-Host 命令就是其中的一种方法。这并不总是最好的一种方法,但是你可以使用它,并且重要的是,你需要了解它的工作原理。
Write-Host 会和其他 Cmdlet 一样使用管道,但是它并不会放置任何数据到管道中。相反,它会直接写到宿主应用程序的界面。正因为可以这样做,所以我们可以使用命令行中的-ForegroundColor 和-BackgroundColor 参数来将前景和背景设置为其他颜色。
1 |
PS D:\> write-host -BackgroundColor Magenta -ForegroundColor Yellow COLORFUL |
不是每个使用 PowerShell 的应用程序都支持其他颜色,也并不是每个应用程序都支持所有颜色。当你尝试在某个应用程序中设置颜色时,通常会忽略掉不喜欢或者不能显示的颜色。这也是我们需要避免依赖于特定颜色的一个原因。
-Host 命令仅仅用于与人进行直接交互, 使用其他颜色来吸引人们的注意力。
针对使用脚本或者命令或无人值守命令, 来产生常规的输出结果而言, 通过-Host 命令输出到屏幕的任何东西都无法被捕捉
你永远都不应该使用 Write-Host 命令手动格式化一个表格——你能找到更好的方法来产生输出结果,比如使用那些让 PowerShell 可以实现处理格式化功能的技巧。在本书中我们不会讲到这些技巧,因为它们更多属于较为复杂的脚本以及工具制作领域。但是,你可以通过 Learn PowerShell Toolmaking in A Month of Lunches(Manning, 2012) 来学习这些输出技巧的全部知识。针对产生错误信息、警告信息、调试信息等而言,Write-Host 命令也不是最好的方法——再次申明,你可以找到更合适的方法来实现这些功能。当然本书中也会讲到这些。如果你恰当地使用 PowerShell,那么你可能不会多次使用到 Write-Host 命令。
我们经常看到有人使用 Write-Host 命令来显示“温暖和模糊”的信息——比如“nowconnecting to Server-2”“testing for folder”等。请不要这样做,有更恰当的方法来实现这些功能,就是
Write-Verbose
。
默认不会输出,当给应用添加
-verbose
选项时,这些信息才会显示。
1 |
PS D:\> Write-Verbose 'hello world' |
1 |
PS D:\> Write-Verbose 'hello world' -Verbose |
我们将在第 22 章中深入讲解 Write-Verbose 以及其他的一些 Write Cmdlet。但是如果你现在尝试使用 Write-Verbose 命令,你可能会很沮丧地发现该命令不会返回任何的结果,准确地说是默认情况下不会返回。如果你计划使用 Write Cmdlet,诀窍就是首先打开它们。例如,设置$VerbosePreference=”Continue”将会启用Write-Verbose,$VerbosePreference=”SilentlyCon tinue”会截断其输出。你会看到针对 Write-Debug($DebugPreference)和Write-Warning($WarningPreference)命令也存在类似的“Preference”变量。
1 |
PS D:\> $VerbosePreference |
看起来使用 Write-Host 命令会更容易,如果你希望使用该命令,那么也可以。但是请记住,如果使用其他的 Cmdlet,比如 Write-Verbose 命令,你会更加贴近 PowerShell 本身的使用方式,最终得到更一致的体验。
1 |
PS D:\> Write-Host (2,4,6,8,10,12) -Separator ", +2= " |
如何将对象从管道传递给显示界面。下面就是最基本的过程。(1)Write-Output 命令将 String 类型的对象 Hello 放入到管道中。(2)因为管道中不存在其他对象,Hello 会到达管道的末端,也就是 Out-Default 命令的位置。(3)Out-Default 命令将对象传递给 Out-Host 命令。(4)Out-Host 命令要求 PowerShell 的格式化系统格式化该对象。因为该示例中为简单的 String 对象,所以格式化系统会返回该 String 对象的文本信息。(5)Out-Host 将格式化的结果集放在显示界面上。
执行的结果类似使用 Write-Host 命令的返回结果,但是该对象通过不同的路径到达最后阶段。该路径是非常重要的,因为在管道中可以包含其他的对象。例如,考虑下面的命令(欢迎你尝试执行该命令)。
1 |
PS D:\> Write-Output "hello" | gm |
“Hello”字符被放进管道, 管道中传递字符串对象。
1 |
PS D:\> Write-host "hello" | gm |
这时,“Hello”字符会直接被传递给显示界面,而不会进入管道中。、
Write-Output 命令看起来可能是新学习的命令,但是其实你一直都在使用它。它是 PowerShell 默认使用的一个 Cmdlet。当你通知 PowerShell 去完成某项功能(但是又不是使用命令)时,PowerShell 会在底层将你键入的任意信息传递给 Write-Output 命令。
1 |
PS D:\> $WarningPreference |
PowerShell v5 引入了一个新的命令:Write-Information。该命令将信息写入一个在 Shell 中唯一、结构化的流中,从而使得在 PowerShell v5 中既可以写入结构化数据也可以写入信息消息。Write-Host 底层使用 Write-Information 命令。在 https://technet.icrosoft.com/en-us/library/dn998020.aspx阅读关于该命令的更新信息。
1 |
PS D:\> $InformationPreference |
Write-Error 命令会有点不一样,因为它会将错误信息写入 PowerShell 的错误流中。另外,PowerShell 还存在一个 Cmdlet Write-Progress,该 Cmdlet 可以展示进度条,但是实现原理完全不一样。你可以阅读其帮助文档来获取更多的信息以及示例。本书中不会涉及该命令。
1 |
for ($i = 1; $i -le 100; $i++ ) { |
同步非阻塞
部分 PowerShell 的宿主应用程序会在不同的位置展现这些 Cmdlet 的输出信息。比如在来自 SAPIEN 的 PowerShell Studio 中,调试信息会写入到另外一块输出窗格中,而不是脚本的主输出窗格,这样可以更容易将调试信息独立开来进行分析。在本书中,我们不会深入讲解调试相关的知识,但是如果你感兴趣,可以阅读 PowerShell 帮助文档中该 Cmdlet 对应的部分。
write-output 默认写管道,如果后面不接管道,那就是 out-default, 就是 out-host.
1 |
PS D:\> Write-Output $(100 / 10 ) |
host 后面不可以接管道,所以直接输出到控制台,还可以给颜色
1 |
PS D:\> write-host $(100/10) -BackgroundColor White -ForegroundColor Blue |
颜色显示 write-host
1 |
PS D:\> $username=$(read-host "enter your username") |
1 |
PS D:\> read-host "enter your username" | ? Length -gt 5 |
上面的方式并无不妥,但每次不断指定计算机名称、凭据、备用端口号等是一件非常麻烦的事情。在本章中,我们将发现实现远程控制更加简单、更可重用的方式。你还可以学到使用远程控制的第三种方式。
1 |
PS D:\> New-PSSession -ComputerName 192.168.0.109,192.168.0.109 |
[-Credential <System.Management.Automation.PSCredential>]
支持此参数的
1 |
PS D:\> Get-PSSession |
1 |
$iis_servers=New-PSSession -ComputerName 192.168.0.109,192.168.0.109 -Credential slc |
有一个语法允许你使用一个命令创建多个会话,并将每个会话赋值给对应的变量(而不是像之前的示例,将其全部塞入一个变量)。
1 |
PS D:\> $session1,$session2 = New-PSSession -ComputerName 192.168.0.109,192.168.0.109 |
但是请小心使用: 我们曾见过会话的顺序和计算机名称的顺序不完全一致 ,导致$s_server1 最终包含连接到 DC01 而不是 Server-R2 的会话。你可以将变量内容显示出来,从而查看会话连接到哪一台计算机。
请永远不要忘记这些会话会消耗资源。如果关闭 Shell,那么这些会话也会随之关闭,但如果你不是频繁使用这些会话,那么即使你希望使用同一个 Shell 完成其他任务,手动关闭这些会话也是不错的主意。
1 |
PS D:\> Get-PSSession | Remove-PSSession |
一旦成功建立会话后,你该如何使用这些会话?在接下来几小节中,我们假设你已经创建了一个名称为$sessions 的变量,并至少包含两个会话。我们使用 localhost 和 Server-R2(你应该指定为符合你具体环境的计算机名称)。使用 Localhost 并不是一个语法糖:PowerShell 会开启一个真正指向本机 PowerShell 副本的远程会话。请记住,只有在所有连接到的计算机上都启用了远程控制时,远程连接才会生效。如果还未启用远程控制,请返回第 13 章。
1 |
PS D:\> $sessions=New-PSSession -ComputerName 192.168.0.109,192.168.0.109 |
或许你很难记起具体哪一个索引号对应哪一个计算机名称。如果是这种情况,你可以利用会话对象的属性进行区分
1 |
PS D:\> $sessions | ? ComputerName -Match 192.168.0.109 |
即使你将会话对象存于变量中,这些会话依然被存于 PowerShell 的一个打开会话的主列表中。这意味着你可以通过 Get-PSSession 访问这些会话。
1 |
PS D:\> help Enter-PSSession -Parameter session |
你将会发现在帮助末尾的管道输入信息非常有趣。该信息告诉我们,-Session 参数可以从管道接受一个 PSSession 对象。我们知道 Get-PSSession 命令会生成 PSSession 对象,所以下述语法也可以生效。
1 |
PS D:\> Get-PSSession | ? id -Match 4 | Enter-PSSession |
为了方便,将会话存入一个变量是可以的。但请记住,PowerShell 已经保存了所有已打开会话的列表;将这些会话存入变量,只有在你需要一次性引用多个会话时才有用,正如你将在下一小节所见的。
1 |
PS D:\> Get-PSSession |
1 |
PS D:\> help Invoke-Command -Parameter session |
注意: invoke-command 的 session 并不支持管道. 所以 session 可以使用括号
1 |
PS D:\> Invoke-Command { ps } -Session (Get-PSSession) |
我们本可以选择使用 Get-WmiObject 命令自带的-computername 参数,但是由于下面 4 个原因,我们没有这么做。
在 PowerShell v3 中,新的 CIM Cmdlet(比如说 Get-CimInstance)并不像 Get-WmiObject 那样有一个-computerName 参数。新的 Cmdlet 被设计的本意就是,如果希望在远程计算机上执行,请通过 Invoke-Command 将其发送过去。
Invoke-Command 的-Session 参数也可以通过括号命令提供,正如我们在之前章节对计算机名称所做的那样。举例来说,下面的语句会将命令发送给计算机名称以“loc”开头的已连接会话。
1 |
PS D:\> Get-PSSession -ComputerName 192.168.0.109 |
1 |
PS D:\> $session = New-PSSession -ComputerName 192.168.0.109 |
① 首先,通过与一台装有活动目录模块的远程计算机建立一个会话。我们需要该计算机装有 PowerShell v2 或更新版本(在 Windows Server 2008 R2 以及更新版本的操作系统上),我们必须启用该计算机的远程控制。② 我们告诉远程计算机导入其本地的活动目录模块。这只是一个示例。我们当然可以选择载入任意模块,甚至是在需要时添加一个 PSSnapin。由于会话处于打开状态,该模块将一直在远程计算机上处于被载入状态。③ 我们接下来告诉我们的计算机从远程会话中导入命令。我们只需要在活动目录模块中的命令,并在每个命令的名词部分加入“rem”前缀。这使得我们可以更容易跟踪远程命令。这还意味着从远程会话导入的命令不会与已经在本地 Shell 中导入的命令冲突。④ PowerShell 在本地计算机创建一个临时模块,用于代表远程命令。这些命令并不是被复制过来的;PowerShell 为其创建了指向远程计算机的快捷方式。
现在我们就可以运行活动目录模块的命令了,甚至是使用帮助命令。我们使用 New-remADUser 来代替 New-ADUser,这是由于我们在命令的名词部分添加了前缀“rem”。该命令在我们关闭 Shell 或关闭与远程连接的会话之前一直存在。当我们打开一个新的 Shell 时,我们必须重复上述过程来重新获得访问远程命令的权限。当我们运行这些命令时,它们并不是在我们本地计算机上执行,而是隐式地在远程计算机上执行。在远程计算机上执行完成后,将结果发送给本地计算机。
接下来到了坏消息时间:通过隐式远程连接获取到本地计算机的结果是反序列化的结果,这意味着对象的属性将会复制到一个 XML 文件中,以便通过网络进行传输。用这种方式收到的对象不会包含任何方法。在大多数情况下,这并不是一个问题。但你希望以编程的方式使用模块或插件时,这些模块或插件对隐式远程控制的支持就不会那么好了。我们希望该限制不会影响到你,这是由于对方法的依赖违反了一些 PowerShell 的设计实践。如果你用到了这些对象,则无法通过隐式远程控制的方式使用它们。
首先,会话不再那么脆弱,意思是在网络闪断或其他传输中断的情况下,会话不会断开。即使在没有显式使用会话对象时,你也可以用到这项提升。即使你在使用类似 Enter-PSSession 和它的-ComputerName 参数时,从技术角度,你也是在底层使用了会话。因此,你获得了更稳定的连接。
在第 3 版中,你必须显式使用的一项功能:断开会话。
比如你正在以用户 Admin1(是 Domain Admins 组成员)的身份连接到名称为 Computer1 的计算机上,并创建一个连接到名称为 COMPUTER2 的连接。
1 |
PS D:\> New-PSSession -ComputerName 192.168.0.109 |
然后你就可以关闭连接。该操作仍然是在 Computer1 上进行的。当你完成该操作后,它会将两台计算机之间的连接断开,但会在 Computer2 上保留一份 PowerShell 的副本。注意,你可以通过指定 Session 的 ID 号完成该操作,该 ID 号会在你第一次创建 Session 时显示。
1 |
|
你在 COMPUTER2 上保留一份 PowerShell 的副本处于运行状态。因此为其分配一个适用的超时时间就变得很重要。在 PowerShell 早期的版本中,断开连接的 Session 将会被丢弃,所以无须清理工作。在第 3 版中,未被回收的会话可能会导致一些问题,这意味着你必须负责起回收工作。
1 |
PS D:\> Get-PSSession |
如果你以其他用户的身份登录,就无法看到这些会话。即使该身份为管理员,你也只能看到在 COMPUTER2 上创建的会话。既然已经看到了,那么你就可以重新连接。
1 |
PS D:\> Get-PSSession -id 57 | Connect-PSSession |
PowerShell 的 WSMAN:Drive,你可以发现大量帮助你管控已断开会话的设置。你还可以通过组策略对大多数配置进行中心化管理。需要寻找的关键设置如下。
在 WSMan:\localhost\Shell 下:■ -IdleTimeout 指定当远程 Shell 中没有用户活动时,远程 Shell 将保持打开状态的最长时间。在指定的时间过后,远程 Shell 将被自动删除。默认值是 2000 小时,或 84 天。■ -MaxConcurrentUsers 指定可以在同一计算机上通过远程 Shell 同时执行远程操作的最大用户数。■ -MaxShellRunTime 指定会话可以打开的最长时间。默认值为无限。请记住,IdleTimeout 参数可以覆盖该参数。
在 WSMan:\localhost\Service 下:■ -MaxConnections 设置连接到整个远程控制架构下的连接数上限。即使你设置了每个用户可运行的 Shell 数量或上限值的用户,MaxConnections 也会限制传入连接。
作为一个管理员,很明显你需要比普通用户有更高的责任心。你需要负责跟踪会话,尤其是你需要断开连接和重新连接。设置合理的超时时间,可以确保 Shell 的会话不会长时间闲置。
在第 1 章中,我们提到了一个在 CloudShare.com 中的多计算机虚拟环境。你可以找到其他类似的基于云计算的虚拟主机。通过使用 CloudShare( www.cloudshare.com),我们无须部署Windows操作系统,这是由于该服务已经提供了供我们使用的模板。你当然需要为此服务付费,且该服务并不是对所有的国家可用。但如果你可以使用该服务,在本地没有环境时,这是获得一个实验环境的极佳方式。
1 |
|
1 |
PS D:\> $session = new-pssession -ComputerName 192.168.0.109 |
1 |
PS D:\> Enter-PSSession -Session $session |
1 |
PS D:\> Invoke-Command -Session $session { Get-WmiObject win32_service } |
1 |
PS D:\> Invoke-Command -Session (Get-PSSession) { Get-EventLog Security -Newest 20} |
1 |
PS D:\> Invoke-Command -Session $session { Import-Module ServerManager } |
1 |
PS D:\> Import-PSSession -Session $session -Module servermanager -Prefix rem |
1 |
PS D:\> Get-Module tmp_hghfgl03.u5p |
1 |
PS D:\> Get-remWindowsFeature |
1 |
PS D:\> $session | Disconnect-PSSession |
1 |
# 获取远程模块 |
1 |
PS D:\> Get-Module -ListAvailable *exchange* |
PowerShell 脚本——如果你愿意或者也可以称之为批处理文件——以类似的原理工作。仅仅是将你希望运行的命令列出来,Shell 将会以指定的顺序执行这些命令。你可以通过将命令从宿主窗口中复制到文本文件中从而创建一个脚本。当然,记事本并不是一个好用的文本编辑器。我们希望你更倾向使用
PowerShell ISE,或者诸如PowerGUI、PrimalScript或PowerShell Plus之类的第三方编辑器。
ISE 实际上使用起来和使用交互式 Shell 并无不同。当使用 ISE 的脚本编辑器窗口时,只需输入命令或希望运行的命令,并单击在工具栏中的“运行”按钮执行这些命令。单击“保存”按钮,你将可以在不复制粘贴任何命令的情况下创建一个脚本。
我们需要开始使用 PowerShell ISE 而不再使用标准的控制台窗口。这是由于通过 ISE 将我们的命令转为一个脚本变得更加容易。坦白讲,由于可以使用全屏的编辑器而不是在控制台宿主上输入单行命令,ISE 使得输入复杂命令变得更加容易。
1 |
PS D:\> Get-CimInstance Win32_LogicalDisk -ComputerName 192.168.0.109 | ? DriveType -eq 3 | Sort-Object deviceid -desc | ft DeviceID,@{L='freespace(mb)'; E={$_.FreeSpace / 1MB -as [int]}},@{L='size(gb)'; E={$_.size / 1GB -as [int]}}, @{name='%Free'; E={$_.freespace / $_.size * 100 -as [int]}} |
1 |
PS D:\> get-content .\Get-DiskInventory.ps1 |
1 |
PS D:\> .\Get-DiskInventory.ps1 |
1 |
PS D:\> get-content .\Get-DiskInventory.ps1 |
这里的反引号, 是换行作业,行尾,可以是
1 |
, |
需要将被赋予常量的$computername 变量转变为一个输入参数。
只需要在变量声明代码附近添加一个 param()块 ❶。这会将$computerName 定义为一个参数,并在未对该参数赋值时指定 本机 IP 作为默认值。你可以不提供默认值,但我们能想到一个合适的值作为默认值时,我们更倾向这么做。
1 |
param ( |
准备脚本之后,执行
1 |
PS D:\> $VerbosePreference='continue' |
假如我们还希望将过滤条件设置为参数。当前脚本仅获取类型为 3 的驱动器,也就是硬盘。我们可以将该值变为参数
1 |
param ( |
1 |
PS D:\> .\Get-DiskInventory.ps1 -drivertype 5 |
1 |
PS D:\> .\Get-DiskInventory.ps1 localhost 5 |
位置 + 选项
1 |
PS D:\> .\Get-DiskInventory.ps1 localhost -drivertype 5 |
选项+选项
1 |
PS D:\> .\Get-DiskInventory.ps1 -comp localhost -drivertype 5 |
1 |
PS D:\> .\Get-DiskInventory.ps1 -drivertype 3 |
1 |
PS D:\> .\Get-DiskInventory.ps1 -drivertype 3 -computers 192.168.0.109,192.168.0.109 |
1 |
function Send-Greeting |
1 |
<# |
正常情况下,PowerShell 都会忽略以#开头的代码行,意味着#用于标识某一行是注释。而我们使用<# #>块注释语法,这是由于我们需要注释多行,而不希望在每一行开始都使用#。
并通过运行 Help.\Get-DiskInventory 命令获取帮助。(再一次,我们需要提供路径,这是由于该脚本并不是一个内置 Cmdlet。)
1 |
PS D:\> HELP .\Get-DiskInventory.ps1 |
1 |
PS D:\> help about_comment_* |
<# and #>
symbols to create a comment block. All the lines within the comment block are interpreted as comments.
.<help keyword>
关键字需要.开头
1 |
function Get-Function |
1 |
.SYNOPSIS |
脚本中包含的任何代码和手动输入 PowerShell 的代码,或是将脚本中的代码通过剪贴板粘贴到 Shell 中的代码,运行起来并无不同。但这并不完全正确。
1 |
PS D:\> Get-Process |
两个完全分开的命令,两个独立的管道,两个格式化进程,两个不同界面的结果集。
1)运行 Get-Process。(2)该命令将 Process 对象放入管道。(3)管道以 Out-Default 结束,该命令会接收对象。(4)Out-Default 将对象传递给 Out-Host,该命令会调用格式化系统产生文本输出结果(你在第 10 章学到过这些)。(5)文本输出结果显示在屏幕上。(6)运行 Get-Service。(7)该命令将 Service 对象放入管道。(8)管道以 Out-Default 结束,该命令会接收对象。(9)Out-Default 将对象传递给 Out-Host,该命令会调用格式化系统产生文本输出结果。(10)文本输出结果显示在屏幕上。
你将这两个命令放入脚本文件,并命名为 Test.ps1 或其他简单的名称。在运行脚本之前,将这两个命令复制到剪贴板,你可以选中这两行并按 Ctrl+C 组合键将其复制到剪贴板。
1 |
PS D:\> Get-Content test.ps1 |
在 PowerShell 中,所有的命令都在一个管道中执行,在脚本中也是同样。在脚本中,任何产生管道输出结果的命令都会被写入同一个管道中:脚本自身运行的管道。请查看图 21.5。
(1)脚本运行 Get-Process。(2)该命令将 Process 对象放入管道。(3)脚本运行 Get-Service。(4)该命令将 Service 对象放入管道。(5)管道以 Out-Default 结束,该命令会接收上面两类对象。(6)Out-Default 将对象传递给 Out-Host,该命令会调用格式化系统产生文本输出结果。(7)由于 Process 对象首先被放入管道,Shell 的格式化系统会为 Process 对象选择合适的格式化方式。这也是为什么 Process 对象的输出结果看起来很正常。当 Shell 碰到 Service 对象后,它会生成一个全新的表,所以会最终生成一个列表。(8)屏幕显示文本输出结果。
两种不同的输出是由于将两种类别的对象放入一个管道中。这是将命令存入脚本和手动执行之间的重要区别:在脚本中,只能够使用一个管道。正常来讲,你的脚本应该努力保持只输出一类对象,以便 PowerShell 能产生合理的文本输出格式。
全局作用域,在 powershell 退出前有效。
脚本作用域,在脚本退出前有效。
作用域中的元素: 别名,变量,函数
如果你尝试访问一个作用域元素,PowerShell 在当前作用域内查找;如果不存在于当前作用域,PowerShell 会查找其父作用域,依此类推,直到找到树形关系的顶端——也就是全局作用域。
(1)关闭已经打开的 PowerShell 或 PowerShell ISE 窗口,这样你就可以从头开始。(2)打开一个新的 PowerShell 或 PowerShell ISE 窗口。(3)在 ISE 中,创建一个包含一行命令的脚本,该命令为 Write $x。(4)将脚本保存到 c:\scope.ps1。
1 |
PS D:\> get-content .\scope.ps1 |
5)在一个标准的 PowerShell 窗口,使用命令 C:\Scope 运行脚本。没有任何输出结果。当脚本运行时,会自动为其创建一个新的作用域。而$x变量在该作用域内并不存在,因此PowerShell转向其父作用域——也就是全局作用域检查变量$x 是否存在。该变量在父作用域也不存在,因此 PowerShell 认为$x 为空,并打印出空(也就是不输出任何结果)作为输出结果。
1 |
PS D:\> .\scope.ps1 |
(6)在一个标准的 PowerShell 窗口,运行$x=4,然后再次运行C:\Scope。这次,你会看到输出结果为4。虽然变量$x 在脚本范围内未定义,但 PowerShell 可以在全局作用域内找到该变量。因此脚本可以使用全局作用域内的值。
1 |
PS D:\> |
(7)在 ISE 中,在脚本的开始添加$x=10(也就是 write 命令之前),并保存脚本。
1 |
PS D:\> get-content .\scope.ps1 |
(8)在标准的 PowerShell 窗口中,再次运行 C:\Scope。这次,你会看到输出结果为 10。这是由于$x在脚本作用域内定义,因此Shell无须查看全局作用域。在Shell中运行$x。你将看到输出结果为 4,这意味着在脚本作用域内的变量值不会影响全局作用域内的变量值。
1 |
PS D:\> .\scope.ps1 |
当在作用域内定义一个变量、别名或函数时,当前作用域就无法访问父作用域内的任何同名变量、别名或函数。PowerShell 总会使用局部定义的元素。例如,如果你将 New-Alias Dir Get-Service 命令放入一个脚本,那么在当前脚本中,别名 Dir 总是运行 Get-Service 而不是 Get-ChildItem(实际上,Shell 很可能不允许你这么做,这是由于其需要保护内置别名不会重新被定义)。通过在脚本作用域内定义别名,你可以防止 Shell 去父作用域查找标准和默认的 Dir。当然,对于 Dir 别名的重定义只能持续到脚本执行结束之前,而全局作用域默认的 Dir 将不受影响。
这些作用域相关的理念可能会让你感到困惑。你可以通过永远不依赖除了当前作用域内的其他作用域来避免这种混淆。因此在尝试在脚本中访问一个变量时,请确保你已经在同一个作用域内对其赋值。在 Param()块内的参数可以实现这一点,还有很多其他方式可以将值或对象赋予一个变量。
1 |
PS D:\> Get-WmiObject win32_logicaldisk -com $env:computername -filter "drivetype=3" | ? { ($_.freespace / $_.size * 100 ) -gt 50} | select deviceid,freespace,size |
你至少可以发现 2 处信息需要变为参数。该命令用于列出少于给定可用空间的驱动器。显而易见,你并不只想把 localhost(在本例中,我们以 PowerShell 的方式使用%computername%环境变量)作为目标,并且你不希望 10%(也就是 1)作为阈值。你还可以选择将驱动器类型作为参数(这里也就是 3),但是对于动手实验来说,保留其值为 3 即可。
1 |
<# |
1 |
PS D:\> help .\get-disk.ps1 |
我们基于之前章节做了小幅修改:我们将输出结果输出为被选择的对象,而不是格式化之后的表格。
1 |
<# |
1 |
PS D:\> D:\get-disk.ps1 |
为什么我们使用 Select-Object 而不是 Format-Table?因为我们通常感觉写一个脚本所产生的结果是已格式化的并不是一个好主意。毕竟,如果某个用户需要 CSV 格式的文件,而脚本输出格式化后的表,该用户就无法完成工作。通过本次修改,我们可以通过下述方式获得格式化后的表。
1 |
PS D:\> .\get-disk.ps1 | ft |
获取 csv
1 |
PS D:\> .\get-disk.ps1 | ConvertTo-Csv |
关键点是输出对象(也就是 Select-Object 完成的工作),对照格式化的显示结果,将会使得我们的脚本从长远角度来说更加灵活。
1 |
<# |
在基于备注的帮助代码后面,将[CmdletBinding()]指示符置于脚本的第一行非常重要。PowerShell 只会在该位置查看该指示符。加上这个指示符之后,脚本还会正常运行。但我们已经启用了好几个功能,我们会在接下来进行探索。
我们对现有的脚本并不满意,这是由于它提供了默认的-ComputerName 参数。我们并不确定是否真正需要该参数。我们更倾向于选择提示用户输入值。幸运的是,PowerShell 中实现该功能很简单——同样,只需要添加一行代码就能完成,如代码清单 22.3 所示。
1 |
<# |
某些 PowerShell 宿主程序会将帮助信息作为提示的一部分,使得用户获得更简洁的帮助信息。但并不是所有的宿主应用程序都会使用该标签,所以你测试的时候没有看到提示的帮助信息也不用沮丧。当我们写一些给他人使用的脚本时,我们喜欢在脚本中将帮助信息包含在内。这么做永远不会有任何坏处。但是为了简便起见,我们不会在本章的示例中添加帮助信息。
1 |
PS D:\> .\get-disk.ps1 |
但假如你认为-hostname 更容易记忆的话,你可以将该名称作为备用名称添加,也就是参数别名。这只需要另外一个修饰符,
1 |
<# |
新增的标签是-computerName 参数的一部分,因此对-drivetype 参数不生效。现在-computerName 参数的定义占用了 3 行。当然,我们也能将三行连成一行。如下,只是难于阅读
1 |
[Parameter(Mandatory=$true,HelpMessage="enter a or a set computer to query")] [Alias('hostname')] [string]$computername |
1 |
PS D:\> .\get-disk.ps1 -hostname localhost |
1 |
PS D:\> .\get-disk.ps1 -host localhost |
请记住,你只需输入足够让 PowerShell 分辨出是哪个参数的部分参数名即可。在本例中,-host 足以让 PowerShell 识别出指的是-hostname 参数。当然,我们也可以输入完整的参数名称。
1 |
<# |
1 |
|
还有一系列其他可以添加到参数的验证技术。如果这样做有意义,可以将多个修饰符添加到同一个参数上。运行 help about_functions_advanced_parameters 可以获得完整列表——目前为止,我们只使用 ValidateSet。Jeffery 还写了一个关于其他可能用上的“验证”标签的系列博客——你可以在网站 http://jdhitsolutions.com/blog/上查看到该系列博客(搜索“validate”)。
1 |
help ABOUT_FUNCTIONS_ADVANCED_PARAMETERS |
将这段代码保存并再次运行——尝试指定-drivetype 参数为 5,看看 PowerShell 是如何响应的。
很多脚本使用者喜欢看到脚本输出执行的进度,我们倾向于使用 Write-Verbose 而不是 Write-Host 产生这些信息。
1 |
<# |
尝试 2 种方式运维
1 |
S D:\> .\get-disk.ps1 -host localhost -drivetype 3 |
当你想要详细输出时,就能获得详细输出——并且完全无须编写-Verbose 参数的任何实现代码。当添加[CmdletBinding()]时,就可以无成本拥有详细输出。最妙的部分是,该标签还会激活脚本中所包含命令的详细输出!所以你使用的任何被设计可以产生详细输出结果的命令都会自动输出详细结果。该技术使得启用或禁用详细输出变得非常容易,相比 Write-Host 更加灵活。而且你无须通过操作$VerbosePreference 变量就能将输出结果显示在屏幕上。
同时,注意在详细输出中我们如何使用 PowerShell 的双引号技巧:通过将变量($computername)包含在双引号中,输出内容就可以包含变量的内容,所以我们可以看到 PowerShell 输出该变量的内容。
1 |
|
将计算机名称参数化。
1 |
param ( |
将-ComputerName 参数变为强制参数。
1 |
[cmdletbinding()] |
给予计算机名称参数一个别名 hostname。
1 |
[cmdletbinding()] |
调用
.\get-network.ps1 -host localhost
至少一个基于注释的帮助,帮助内容是如何使用本脚本。
1 |
|
查看运行结果
1 |
PS D:\> help .\get-network.ps1 |
在命令运行之前和之后添加详细输出结果。
1 |
|
1 |
PS D:\> .\get-network.ps1 -hostname localhost -Verbose |
本书主要内容是关于 PowerShell v3 以及之后版本,新版本关于远程控制的功能与之前版本的远程控制并无不同,关于找出当前运行的版本的办法,请重新查看第 1 章。本书涵盖的大部分内容无法在早期版本中运行。
如果你拥有管理员权限,你可以在任何计算机上运行下述命令,获得可用的会话配置列表。
1 |
PS D:\> Enable-PSRemoting -SkipNetworkProfileCheck -Force |
1 |
PS D:\> Get-PSSessionConfiguration |
每一个端点有一个名称;
诸如 New-PSSession、Enter-PSSession、Invoke-Command 等远程控制命令默认使用其中一个名称为“Microsoft.PowerShell”的端点。
在 64 位系统中,端点是 64 位的 Shell;在 32 位系统中,“Microsoft.PowerShell”是 32 位的 Shell。
你可以注意到,我们的 64 位系统有一个运行 32 位 Shell 的备用端点:“Microsoft.PowerShell32”用于兼容性目的。如果希望连接到备用端点,只需要在远程控制命令的-ConfigurationName 参数中指定端点名称。
1 |
PS D:\> Enter-PSSession -ComputerName 192.168.13.103 -ConfigurationName 'microsoft.powershell32' |
什么时候会使用备用端点?需要显式通过 32 位的端点连接到 64 位的机器的可能原因之一是当 需要运行的命令依赖于 32 位的 PowerShell 插件时 。另外一种可能是存在 自定义端点。当需要执行一些特定任务时 ,你或许需要连接到这些端点上。
我们可以创建一个只有域中 HelpDesk 组的成员可以访问的端点。在端点内,我们启用与网络适配器相关的命令——并且只允许这些命令。我们并不打算给 HelpDesk 组运行命令的权限,仅仅是让他们可以查看命令。我们还配置端点从而可以在我们提供的备用凭据下运行命令,因此可以使得 HelpDesk 组可以在自身无须拥有执行命令的权限时执行命令。
PowerShell 提供的计算机名称满足以下两点要求。■ 名称可以被解析为 IP 地址。■ 名称必须与活动目录中的计算机名称匹配。
提供你所在域的计算机名称,而对于可信域则需要提供完全限定名(也就是计算机和域名称,比如 DC01.COMPANY.LOC),这样远程控制通常就会生效。但如果你提供的是 IP 地址,或者需要提供与 DNS 中不同的名称(比如说 CNAME 别名),那么默认的双向身份验证将无法正常工作。因此你只有如下两种选择:SSL 或是“受信任的主机”。
\w 文本(数字,字母,下划线;不包含标点符号和空格),\d 数字,\s 空格。取反时,大写。例如: \W (只包含标点符号和空格)
[abcd]集合中任意单个字符
[a-z] ,[a-f,m-z]
[^abcde] 非 abcde 单个字符
? 0 或 1 个
*
0/1,多个字符
+
1 或多个
\
正则转义
{2}
\d{2} 匹配 2 个数字
^, $ 行首尾
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}可以匹配 IPv4 地址的模式,但该表达式可以接受“432.567.875.000”这样的非法地址,也可以接受“192.169.15.12”这样的合法地址。
\w{1}.\[email protected] 可以匹配特定类型的电子邮件地址:
你可以通过在 PowerShell 运行 help about_regular_expressions,发现更多关于正则表达式的基本语法。在本章末尾,我们将为你更进一步学习提供一些额外的资源。
1 |
help about_regular_expressions |
1 |
PS D:\> "成都1A@c. \t" -match '\w' |
先在日志文件中查找 40x 错误。这类错误主要是“找不到文件”以及其他错误,我们希望为 Web 开发人员生成一个缺失文件的报表。日志文件中,每一个 HTTP 请求为一行,每行又被分为以空格分割的域。我们还有一些文件名称中包含“401”等,比如“error401.html”,我们不希望这部分结果出现在我们的结果中。我们将会指定一个类似\s40[0-9]\s 的正则表达式,因为通过在 40x 错误之前和之后匹配空格,该表达式将能够匹配从 400 到 499 的错误。
1 |
PS D:\> get-content test.log |
1 |
PS D:\> Get-Content .\test.log | Select-String '\s4[0-9]{2}\s' |
我们希望扫描所有被基于 Firefox 浏览器访问过的文件。开发人员告诉我们,使用该类浏览器访问我们的网站的用户会遇到一些问题,
1 |
PS D:\> Get-Content .\test.log | Select-String 'Firefox' |
将 IIS 日志文件变为 Windows 安全日志。事件日志实体中包含 Message 属性,该属性中包含关于事件信息的细节。遗憾的是,该信息并没有良好的格式化以便于人们阅读,也不易于计算机解析。我们希望查找所有事件 ID 为 4624 的事件,该事件代表账户登录事件(该 ID 代表的含义可能根据 Windows 版本的不同而有所不同;我们的示例是在 Windows Server 2008 R2 上)。但我们只希望查看账户名称以“WIN”开头的登录信息,这些账户都与在域中的计算机账户关联。另外,我们还要求账户结尾必须是从 TM20$到TM40$的字符,这些是我们感兴趣的特定计算机。我们需要的正则表达式大概如下:WIN[\W\w]+TM[234][0-9]$ ——注意我们需要使用转义符号将末尾的$进行转义,因此该符号不会被解释成字符串结尾标记。我们需要包含[\W\w](非字符和字符),这是由于我们的账户名称中可能包含连字符,该连字符无法与\w 字符类匹配。因此最终下面是我们的命令。
1 |
PS D:\> Get-EventLog Security | ? eventid -eq 4624 | select -ExpandProperty message | Select-String 'win.*' |
这将会输出匹配的信息文本;如果我们的目标是输出所有匹配的事件,我们需要使用另一种方式。
1 |
PS D:\> Get-EventLog Security | ? { $_.eventid -eq 4624 -and $_.Message -match 'win.*' } |
1 |
PS D:\> ls | ? Name -Match '\d{2}' |
1 |
PS D:\> ps | ? Company -Match microsoft | ft id,ProcessName,Company |
1 |
PS D:\> Get-Content C:\Windows\WindowsUpdate.log | Select-String '^proxy' |
1 |
PS D:\> Get-DnsClientCache | fl data |
我们也使用免费的在线正则表达式资源: http://RegExLib.com。该网站包含用于不同目的的大量正则表达式示例(电话号码、邮件地址、IP地址等)。我们还使用http://RegExTester.com这个网站测试我们的正则表达式,从而确保正则表达式能够满足我们的需求。
一个月的“午饭学习时间”已经接近尾声。因此我们想给你分享一些额外的提示与技巧完成这次学习之旅。
每一个 PowerShell 进程开启时都是一样的:一样的别名,一样的 PSDrives,一样的色彩等。为什么不使用自定义的 Shell 界面呢?
下面是控制台宿主尝试载入的一些文件,以及尝试载入这些文件的顺序。
(1)$PsHome/Profile.PS1——不管使用何种托管应用程序,计算机上的所有用户都会执行该脚本(请记住,PowerShell已经预定义了$PSHome,该变量包含 PowerShell 的安装文件夹的路径)。
(2)$PsHome/Microsoft.PowerShell_Profile.PS1——如果该计算机上的用户使用了控制台宿主,那么就会执行该脚本。如果他们使用的是PowerShell的ISE,那么会执行$PsHome/Microsoft.PowerShellISE_Profile.ps1 脚本。
(3)$Home/Documents/WindowsPowerShell/Profile.PS1——无论用户使用的是何种托管应用程序,只有当前用户会执行该脚本(因为该脚本存在于用户的根目录下)。
(4)$Home/Documents/WindowsPowerShell/Microsoft.PowerShell_Profile.PS1——只有当前使用PowerShell控制台的用户才会执行该脚本。如果用户使用的是PowerShell ISE,那么会执行$Home/Documents/WindowsPowerShell/Microsoft.PowerShellISE_Profile.PS1。
如果上面脚本中某一个或者几个不存在,那么也没关系。托管应用程序会跳过不存在的脚本,继续寻找下一个可用的脚本。
请注意,About_Profiles 的帮助文档与我们上面罗列的有一点不同。但是我们的经验可以证明,上面的列表是正确的。下面是针对该列表的其他一些知识点。
$PsHome 是包含 PowerShell 安装路径信息的内置变量;在大部分操作系统中,该变量的值是 C:\Windows\System32\WindowsPowerShell\V1.0(针对 64 位操作系统上 64 位版本的 PowerShell)。■ $Home 是另一个内置的变量,该变量指向当前用户的配置文件夹(比如 C:\Users\Administrator)。■ 在前面的列表中,我们使用“Documents”表示文档文件夹,但是在某些版本的 Windows 系统中可能是“My Documents”。■ 在前面的列表中写的“不管用户使用何种托管应用程序”,从技术上讲并不恰当。准确地说,针对微软发布的托管应用程序(控制台或者 ISE),该命题正确;但是针对非微软发布的托管应用程序,根本无法使用该规则
因为期望将相同的 Shell 扩展程序载入到 PowerShell,而不管使用控制台还是 ISE,所以我们选择自定义$Home\Documents\WindowsPowerShell\Profile1.PS1——因为该 Profile 脚本在微软提供的两种托管应用程序中都可以运行。
为什么你自己不尝试创建一个或者多个 Profile 脚本呢?即使在这些脚本中仅打印出一些简单的信息,比如“It Worked”,这是查看不同脚本执行的一个好办法。但是请记住,你必须选择使用 Shell(或者 ISE),并且需要重新打开该 Shell(或者 ISE)去检查 Profile 脚本是否运行。
1 |
PS D:\> "'work pshome profile.ps1'" | Out-File C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 |
请记住,Profile 脚本也仅是脚本而已,它会依赖于 PowerShell 的当前执行策略。如果设置的执行策略是 Restricted,那么 Profile 脚本就无法运行;如果设置的执行策略是 AllSigned,那么 Profile 脚本必须经过签名才能运行。在第 17 章中讲到了执行策略以及脚本签名部分。如果你忘记了该知识点,请回到第 17 章重新学习。
下面是自定义的一个提示函数。你可以直接将该函数加入到任意 Profile 脚本中,这样可以保证后续新开启的 Shell 进程都会将该提示作为一个标准提示函数使用。
1 |
function prompt { |
默认的文本前景色与后景色都可以通过单击 PowerShell 命令窗口左上角的边框来修改。选择“属性”,之后切换到“颜色”标签页,如图 25.1 所示。
错误消息的前景色修改为绿色
1 |
8:19 [SLC-PC-HOME] D:\> $(get-host).PrivateData | gm |
下面是可以选择的几种颜色。■ Red■ Yellow■ Black■ White■ Green■ Cyan■ Magenta■ Blue 同时,也存在这些颜色的对应深色颜色:DarkRed, DarkYellow, DarkGreen,DarkCyan, DarkBlue 等。
-AS 运算符会将一种已存在的对象转换为新的对象类型,从而产生一个新的对象。
如果存在一个包含小数的数字(可能来自一个除法计算),可以通过 Converting 或者 Casting 将该数字转化为一个整数。
1 |
8:23 [SLC-PC-HOME] D:\ > 7 / 3 |
-AS 运算符,最后是一个中括号,中括号中包含转化之后的类型。这些类型可以是[String]、[XML]、[INT]、[Single]、[Double]、[Datetime]等,罗列的这些类型应该是你经常使用到的类型
-IS 运算符通过类似方式实现。该运算符主要用于判断某个对象是否为特定类型,如果是,则返回 True,否则为 False。
1 |
8:24 [SLC-PC-HOME] D:\ > 10 / 4 -is [float] |
-Replace 运算符主要用于在某个字符串中寻找特定字符(串),最后将该字符(串)替换为新的字符(串)。
1 |
8:27 [SLC-PC-HOME] D:\ > "192.168.34.121" -replace "34","aa" |
1 |
$(Get-Content .\Get-DiskInventory.ps1 | Out-String ).Replace('use','abc') |
-Join 和-Split 运算符主要用作将数组转化为分隔列表和将分隔列表转化为数组。
1 |
8:35 [SLC-PC-HOME] D:\ > $array = "one","two","three","four","five" |
因为 PowerShell 会自动将使用逗号隔开的列表识别一个数组,所以上面的命令可以执行成功。假如现在需要将这个数组里的值转换为以管道符隔开的字符串,可以通过-Join 来实现。
1 |
8:36 [SLC-PC-HOME] D:\ > $array -join "|" |
可以将该执行结果存入一个变量,这样可以直接重用,或者将其导出为一个文件。
1 |
8:36 [SLC-PC-HOME] D:\ > $string=$array -join "|" |
同时,我们可以使用-Split 运算符实现相反的效果:它会从一个分隔的字符串中产生一个数组。例如,假如存在仅包含一行四列数据的一个文件,在该文件中以制表符对列进行隔离。将该文件的内容显示出来,类似下面这样。
1 |
8:38 [SLC-PC-HOME] D:\ > $array="server1 windows east managed" -split " " |
产生的数组中包含 4 个元素,可以通过它的索引编号单独查询对应元素。
其他语言,contain 可能是字符是否包含子串
-Contains 运算符主要用作在一个集合中查找是否存在特定对象。比如,创建包含多个字符串对象的一组集合,然后检查特定对象是否包含在该集合中。
-In 运算符实现相同的功能,但是它会颠倒运算对象的顺序。也就是说,集合在右边,而需要检查的对象在左边。
1
2
3
4
5
6
7
8
9
10
11
12
13 8:41 [SLC-PC-HOME] D:\ > $array
server1
windows
east
managed
8:41 [SLC-PC-HOME] D:\ > $array -contains "server"
False
8:41 [SLC-PC-HOME] D:\ > $array -contains "server1"
True
8:41 [SLC-PC-HOME] D:\ > "server" -in $array
False
8:41 [SLC-PC-HOME] D:\ > "server1" -in $array
True字符串处理
你需要将该字符串全部转化为大写,或者你可能需要取得该字符串的最后 3 个字符。那么应该如何实现?
获取字符串对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 8:42 [SLC-PC-HOME] D:\ > "hello world" | gm
TypeName:System.String
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
CompareTo Method int CompareTo(System.Object value), int CompareTo(string strB), int IComparable.CompareTo(System.Object obj), ...
Contains Method bool Contains(string value)
CopyTo Method void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
EndsWith Method bool EndsWith(string value), bool EndsWith(string value, System.StringComparison comparisonType), bool EndsWit...
Equals Method bool Equals(System.Object obj), bool Equals(string value), bool Equals(string value, System.StringComparison c...
GetEnumerator Method System.CharEnumerator GetEnumerator(), System.Collections.IEnumerator IEnumerable.GetEnumerator(), System.Coll...
GetHashCode Method int GetHashCode()
GetType Method type GetType()
GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
IndexOf Method int IndexOf(char value), int IndexOf(char value, int startIndex), int IndexOf(string value), int IndexOf(strin...
IndexOfAny Method int IndexOfAny(char[] anyOf), int IndexOfAny(char[] anyOf, int startIndex), int IndexOfAny(char[] anyOf, int s...
Insert Method string Insert(int startIndex, string value)
IsNormalized Method bool IsNormalized(), bool IsNormalized(System.Text.NormalizationForm normalizationForm)
LastIndexOf Method int LastIndexOf(char value), int LastIndexOf(char value, int startIndex), int LastIndexOf(string value), int L...
LastIndexOfAny Method int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(char[] anyOf, int startIndex), int LastIndexOfAny(char[] ...
Normalize Method string Normalize(), string Normalize(System.Text.NormalizationForm normalizationForm)
PadLeft Method string PadLeft(int totalWidth), string PadLeft(int totalWidth, char paddingChar)
PadRight Method string PadRight(int totalWidth), string PadRight(int totalWidth, char paddingChar)
Remove Method string Remove(int startIndex, int count), string Remove(int startIndex)
Replace Method string Replace(char oldChar, char newChar), string Replace(string oldValue, string newValue)
Split Method string[] Split(Params char[] separator), string[] Split(char[] separator, int count), string[] Split(char[] se...
StartsWith Method bool StartsWith(string value), bool StartsWith(string value, System.StringComparison comparisonType), bool Sta...
Substring Method string Substring(int startIndex), string Substring(int startIndex, int length)
ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar Method char IConvertible.ToChar(System.IFormatProvider provider)
ToCharArray Method char[] ToCharArray(), char[] ToCharArray(int startIndex, int length)
ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provide- IndexOf()会返回特定字符在字符串中的位置。
- Split(), Join()和 Replace()类似于上面讲到的-Split, -Join 和-Replace。但是我们更加倾向于使用 PowerShell 的运算符而不是 String 的方法。
- ToLower()和 ToUpper()可以将字符串转化为小写或大写。
- Trim()会将一个字符串的前后空格去掉;TrimStart()和 TrimEnd()会将一个字符串的前面或者后面的空格去掉。
上面这些方法都是处理或者修改 String 对象比较方便的方法。请记住,所有这些方法既可以运用于包含字符串的变量(比如前面的 ToLower()和 Trim()示例),也可以用在一个静态的字符串上(比如前面的 IndexOf()示例)。
1
2 8:42 [SLC-PC-HOME] D:\ > "hello world".toupper()
HELLO WORLD
1
2
3
4
5
6 8:46 [SLC-PC-HOME] D:\ > $string="hello world"
8:46 [SLC-PC-HOME] D:\ > $string.Substring($string.Length -3)
rld
8:49 [SLC-PC-HOME] D:\ > " aaaaaa bbb ccc ".Trim()
aaaaaa bbb ccc日期处理
和 String 类型对象一样,Date(如果你喜欢,也可以是 DateTime)对象也包含多个方法。通过这些方法,可以对日期和时间进行处理和计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 8:52 [SLC-PC-HOME] D:\ > get-date | gm
TypeName:System.DateTime
Name MemberType Definition
---- ---------- ----------
Add Method datetime Add(timespan value)
AddDays Method datetime AddDays(double value)
AddHours Method datetime AddHours(double value)
AddMilliseconds Method datetime AddMilliseconds(double value)
AddMinutes Method datetime AddMinutes(double value)
AddMonths Method datetime AddMonths(int months)
AddSeconds Method datetime AddSeconds(double value)
AddTicks Method datetime AddTicks(long value)
AddYears Method datetime AddYears(int value)
CompareTo Method int CompareTo(System.Object value), int CompareTo(datetime value), int IComparable.CompareTo(System.Object obj), int IComparable[datetime].CompareTo(datetime other)
Equals Method bool Equals(System.Object value), bool Equals(datetime value), bool IEquatable[datetime].Equals(datetime other)
GetDateTimeFormats Method string[] GetDateTimeFormats(), string[] GetDateTimeFormats(System.IFormatProvider provider), string[] GetDateTimeFormats(char format), string[] GetDateTimeFormats(char format, System.IFormatProvide...
GetHashCode Method int GetHashCode()
GetObjectData Method void ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
GetType Method type GetType()
GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
IsDaylightSavingTime Method bool IsDaylightSavingTime()
Subtract Method timespan Subtract(datetime value), datetime Subtract(timespan value)
ToBinary Method long ToBinary()
ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar Method char IConvertible.ToChar(System.IFormatProvider provider)
ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble Method double IConvertible.ToDouble(System.IFormatProvider provider)
ToFileTime Method long ToFileTime()
ToFileTimeUtc Method long ToFileTimeUtc()
ToInt16 Method int16 IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32 Method int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64 Method long IConvertible.ToInt64(System.IFormatProvider provider)
ToLocalTime Method datetime ToLocalTime()
ToLongDateString Method string ToLongDateString()
ToLongTimeString Method string ToLongTimeString()
ToOADate Method double ToOADate()
ToSByte Method sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToShortDateString Method string ToShortDateString()
ToShortTimeString Method string ToShortTimeString()
ToSingle Method float IConvertible.ToSingle(System.IFormatProvider provider)
ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provider), string ToString(string format, System.IFormatProvider provider), string IFormattable.ToString(st...
ToType Method System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider)
ToUInt16 Method uint16 IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32 Method uint32 IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64 Method uint64 IConvertible.ToUInt64(System.IFormatProvider provider)- month
- 假如需要获取 90 天之前的日期,使用 AddDays()方法和一个负数参数实现。
- 名称中以“To”开头的方法可以实现将日期以及时间转化为某种特定格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 8:52 [SLC-PC-HOME] D:\ > $(get-date).Year
2022
8:53 [SLC-PC-HOME] D:\ > $(get-date).Month
7
8:53 [SLC-PC-HOME] D:\ > $(get-date).Day
30
8:53 [SLC-PC-HOME] D:\ > $(get-date).Hour
8
8:53 [SLC-PC-HOME] D:\ > $(get-date).Minute
53
8:53 [SLC-PC-HOME] D:\ > $(get-date).Second
27
8:53 [SLC-PC-HOME] D:\ > $(get-date).Millisecond
926
8:53 [SLC-PC-HOME] D:\ > $(get-date).TimeOfDay
Days : 0
Hours : 8
Minutes : 53
Seconds : 39
Milliseconds : 604
Ticks : 320196043713
TotalDays : 0.370597272815972
TotalHours : 8.89433454758333
TotalMinutes : 533.660072855
TotalSeconds : 32019.6043713
TotalMilliseconds : 32019604.3713
8:53 [SLC-PC-HOME] D:\ > $(get-date).DayOfWeek
Saturday
8:53 [SLC-PC-HOME] D:\ > $(get-date).DayOfYear
211
8:53 [SLC-PC-HOME] D:\ > $(get-date).DateTime
2022年7月30日 8:54:04
1
2
3
4
5
6
7
8
9
10
11
12 8:54 [SLC-PC-HOME] D:\ > $(get-date).ToShortTimeString()
8:54
8:54 [SLC-PC-HOME] D:\ > $(get-date).AddDays(1)
2022年7月31日 8:54:55
8:54 [SLC-PC-HOME] D:\ > $(get-date).AddDays(-1)
2022年7月29日 8:54:58另外需要注意的是,这些方法都是依赖于当前计算机本地的区域设定——区域设定决定了日期和时间格式。
处理 WMI 日期
在 WMI 中存储的日期和时间格式都难以直接利用。例如,Win32_Operating System 类主要用来记录计算机上一次启动的时间,其日期和时间格式如下。
1
2
3
4
5
6 8:55 [SLC-PC-HOME] D:\ > Get-WmiObject win32_operatingsystem | Select-Object LastBootUpTime
LastBootUpTime
--------------
20220729200330.048610+480PowerShell 的设计者知道直接使用这些信息会比较困难,所以他们对每一个 WMI 对象添加了一组转换方法。将 WMI 对象通过管道发送给 Gm,请注意观察最后两个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 8:56 [SLC-PC-HOME] D:\ > Get-WmiObject win32_operatingsystem | Select-Object LastBootUpTime | gm
TypeName:Selected.System.Management.ManagementObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
LastBootUpTime NoteProperty string LastBootUpTime=20220729200330.048610+480
8:56 [SLC-PC-HOME] D:\ > Get-WmiObject win32_operatingsystem | gm
...
ConvertFromDateTime ScriptMethod System.Object ConvertFromDateTime();
ConvertToDateTime ScriptMethod System.Object ConvertToDateTime();将输出结果集中间的大部分信息去除,这样你能很轻易地发现后面的 ConvertFrom DateTime()和 ConvertToDateTime()方法。在该示例中,获取到的是 WMI 的日期和时间。假如需要转化为正常的日期和时间格式,请参照下面的命令。
1
2
3
4 8:57 [SLC-PC-HOME] D:\ > $os=Get-WmiObject win32_operatingsystem
8:58 [SLC-PC-HOME] D:\ > $os.ConvertToDateTime($os.LastBootUpTime)
2022年7月29日 20:03:30如果你期望将正常的日期和时间信息放入到一个正常表中,你可以通过 Select-Object 或者 Format-Table 命令创建自定义计算列以及属性。
1
2
3
4
5 8:59 [SLC-PC-HOME] D:\ > Get-WmiObject win32_operatingsystem | ft buildnumber,__server,@{L='lastboottime'; E={$_.converttodatetime($_.lastbootuptime)}}
buildnumber __SERVER lastboottime
----------- -------- ------------
22000 SLC-PC-HOME 2022-07-29 20:03:30设置参数默认值
默认值保存在名为$PSDefaultParameterValues 的特殊内置变量中。当每次新开一个 PowerShell 窗口时,该变量均置空,之后使用一个哈希表填充该变量(可以通过 Profile 脚本使得默认值始终有效)。
假如你希望创建一个包含用户名以及密码的凭据对象,然后将该对象设置为所有命令中-Credential 参数的默认值。
1
2
3 8:59 [SLC-PC-HOME] D:\ > $credential=Get-Credential -username administrator -Message 'enter admin credential'
9:11 [SLC-PC-HOME] D:\ > $PSDefaultParameterValues.add('*:credential', $credential)
9:12 [SLC-PC-HOME] D:\ >如果仅希望 Invoke-Command Cmdlet 每次运行时都提示需要凭据,此时请不要直接分配一个默认值,而是分配一段执行 Get-Credential 命令的脚本块。
1 9:12 [SLC-PC-HOME] D:\ > $PSDefaultParameterValues.add('invoke-command:credential', {Get-Credential -Message 'enter administrator credential' -UserName administrator})可以看到该 Add()方法的基本格式:第一个参数为
<Cmdlet>:<Parameter>
,该<Cmdlet>
可以接受*等通配符。Add()方法的第二个参数或者是直接给出的默认值,或者是执行其他(一个或多个)命令的脚本块。查看$PSDefaultParameterValues 包含的内容。
1
2
3
4 9:14 [SLC-PC-HOME] D:\ > $PSDefaultParameterValues | gm
TypeName:System.Management.Automation.DefaultParameterDictionaryPowerShell 的变量由作用域(Scope)控制。我们在第 21 章中简单介绍了作用域,同时作用域也会影响参数的默认值。如果在命令行中设置了$PSDefaultParameterValues,那么该参数会针对本Shell会话中的所有脚本以及命令起作用。但是如果仅在一段脚本中设置了$PSDefaultParameter Values,那么同样,也只会在该脚本作用域中有用。该技术非常有用,因为这意味着你可以在一段脚本中设置多个参数的默认值,但是并不影响其他脚本或者 Shell 会话的运行。作用域的核心思想是“无论脚本发生了什么,仅会影响该脚本”。如果你想深入研究作用域,请查阅 About_Scope 帮助文档中的详细内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 9:15 [SLC-PC-HOME] D:\ > $PSDefaultParameterValues.Remove('*:credential')
9:16 [SLC-PC-HOME] D:\ > $PSDefaultParameterValues
Name Value
---- -----
invoke-command:credential Get-Credential -Message 'enter administrator credential' -UserName administrator
9:16 [SLC-PC-HOME] D:\ > Invoke-Command { ps }
Invoke-Command : 无法使用指定的命名参数解析参数集。 此错误可能是应用默认参数绑定造成的。可通过将 $PSDefaultParameterValues["Disabled"] 设置为 $true,在
$PSDefaultParameterValues 中禁用默认参数绑定,然后重试。在出现错误时,已成功为此 cmdlet 绑定了以下默认参数: -Credential
所在位置 行:1 字符: 1
+ Invoke-Command { ps }
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command],ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.InvokeCommandCommand可以通过 PowerShell 中的 About_Parameters_Default_Values 帮助文档查看该特性更多的知识点。
学习脚本块
Where-Object 命令的-FilterScript 参数会使用脚本块。
ForEach-Object 命令的-Process 参数会使用脚本块。
使用 Select-Object 创建自定义属性的哈希表或者使用 Format-Table 创建自定义列的哈希表,都会需要一个脚本块作为 E 或者 Expression 的键值。
参数的默认值也可以是一个脚本块。
针对一些远程处理以及 Job 相关的命令,比如 Invoke-Command 和 Start-Job 命令,也需要一个脚本块作为-ScriptBlock 参数的值。
脚本块是指包含在大括号中的全部命令——哈希表除外(哈希表在大括号之前会带有@符号)。你可以在命令行中输入一个脚本块,然后将该脚本块赋值给一个变量,再使用&该调用运算符来执行该脚本块。
可以使用脚本块完成更多的工作。如果希望进一步学习脚本块,请参阅 PowerShell 中的 About_Script_Block 帮助文档。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 13:21 [SLC-PC-HOME] D:\ > $block={
Get-Process | Sort-Object vm -desc | select -First 10
}
13:21 [SLC-PC-HOME] D:\ > $block
Get-Process | Sort-Object vm -desc | select -First 10
13:21 [SLC-PC-HOME] D:\ > &$block
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
431 24 45976 79704 1.47 4336 2 chrome
312 19 81496 138736 214.08 7636 2 chrome
375 22 49452 89932 3.45 13032 2 chrome
263 17 35404 65464 2.69 8496 2 chrome
249 16 21280 41468 0.27 1908 2 chrome
251 16 16944 39404 0.27 8268 2 chrome
233 16 19460 30168 0.31 13280 2 chrome
204 14 19156 28940 0.30 1012 2 msedge
194 14 16420 25400 0.89 292 2 chrome
1124 42 28444 80356 8.00 7420 2 msedge更多的提示、技巧及技术
所以请将这些提示、技巧以及技术,包括以后会遇到的其他资源作为不断提高 PowerShell 水平的一种沉淀吧!
使用他人的脚本
尽管我们希望你能从头开始编写一些自己的 PowerShell 命令脚本,但是我们也意识到,在编写过程中你会严重依赖于互联网上的一些示例。无论是直接利用别人博客中的示例还是修改在线脚本代码库——比如 PowerShell 代码库( http://PoshCode.org)中发现的脚本,其实能利用、借鉴别人的PowerShell脚本也算一项重要的核心技能。在本章中,我们会带领你学会通过该过程理解别人的脚本,并最终将脚本修改以适合我们的需要。
感谢提供本章脚本的 Christoph Tohermes 和 Kaia Taylor。我们特意让他们提供一些带有瑕疵的脚本,这些脚本与我们通常见到最佳实践中那些完美的脚本不一样。在某些情况下,我们甚至会故意将他们提供的脚本进行破坏,使得本章中的场景更能够反应真实世界。我们非常感激他们对该学习活动所做的贡献。
我们选择这些脚本主要是因为在这些脚本中,他们使用了一些在本书中并未涉及的高阶 PowerShell 功能。另外,我们需要说明,这就是真实的世界:你总是会碰到陌生的东西。本练习的目的是让你在并未学习过某个脚本用到所有技术的前提下,也能尽快知道该脚本的功能。
脚本
该脚本主要用于调用微软 IIS Cmdlet——该 Cmdlet 存在于已安装 Web 服务角色的 Windows Server 2008 R2 以及之后版本的操作系统上。
它看起来并不像有潜在风险的代码——类似 GetFolderPath 语句并不会导致任何报警。要想知道该代码所实现的功能,需要将该代码在 Shell 中执行。
1
2
3
4
5
6
7
8 13:22 [SLC-PC-HOME] D:\ > $system = [Environment]::GetFolderPath('system')
13:28 [SLC-PC-HOME] D:\ > $system
C:\Windows\system32
13:28 [SLC-PC-HOME] D:\ > $script:hostspath=([System.IO.Path]::Combine($system,'drivers/etc/'))
13:29 [SLC-PC-HOME] D:\ > $script:hostspath=([System.IO.Path]::Combine($system,'drivers/etc/')) + 'hosts'
13:29 [SLC-PC-HOME] D:\ > $script
13:29 [SLC-PC-HOME] D:\ > $script:hostspath
C:\Windows\system32\drivers/etc/hosts$script:hostsPath代码创建了一个新的变量。这样除了$system 变量之外,又有了一个新的变量。这几行命令定义了一个文件夹路径以及文件路径。请记下这几个变量的值,在学习该脚本过程中可以随时参照。
该脚本的余下部分包含了 3 个函数:New-LocalWebsite, AddEntryToHosts 和 HostsFile ContainsEntry。一个函数类似于一个脚本中的某部分脚本:每个函数都代表已打包的脚本块,该脚本可以被调用。可以看到,尽管在上面的 Param()块中并未看到参数,但每个脚本可以定义一个或多个参数。它们采用的方式是仅在函数中才合法的参数定义方法:在函数名称后面的括号中将参数罗列出来(和 Param()块一样)。其实,这也可算作一种快捷方式。
如果查看该脚本,并不会看到上面定义的任一脚本被调用,因此如果照搬这些脚本,那么脚本根本无法运行。但是在函数 New-LocateWebSite 中,你可以看到调用了函数 HostsFileContainsEntry。
获取 if 语句的帮助
1
2 help *if*
help about_if你可能不太理解 Try 块。快速查找对应的帮助文档(Help _Try_)会显示 About_Try_Cacth_Finally 帮助文档
Try 部分中的任何命令都有可能产生一个错误信息。如果确实产生了错误信息,那么就会执行 Catch 部分的命令。
命令是 New-WebAppPool。查看帮助文档,发现该命令包含在 WebAdministration 模块中,该命令的帮助文档阐述了其作用。接下来,Set-ItemProperty 命令看起来像是对刚建立的 AppPool 对象设置某些选项。
如果你查看 Test-Path,你会发现它会检查一个给定的路径是否存在,在这个脚本中是指一个文件夹。如果不存在,那么脚本就会使用 New-Item 命令创建该文件夹。
变量$Header在创建后被用作将$SiteName 转化为一个类似“ www.sitename.local”的网址,同时$Value变量用作添加一个IP地址。之后传入多个参数调用New-WebSite命令;你可以通过阅读该命令对应的帮助文档来查看各个参数的作用。
最后执行 Start-WebSite 命令。在帮助文档中有说明,该命令会使得对应的网站上线并运行。然后调用 HostsFileContainsEntry 和 AddEntryToHosts 命令。它们会确保$Value 变量中的值对应的站点信息会以“IP 地址-名称”的格式被添加到本地 Hosts 文件中。
逐行检查
在前面的小节中,我们采用的是逐行分析该脚本,这也是我建议你采用的方式。当你逐行查阅每一行时,完成下述工作。
- 遇到变量,手工执行,记录值
- 遇到新的命令,请阅读对应的帮助文档,这样可以理解这些命令的功能。针对 Get-类型的命令,尝试运行它们——将脚本中变量的值传递给命令的参数——来查看这些命令的输出结果。
- 遇到不熟悉的部分时,比如[Environment],请考虑在虚拟机中执行简短的代码片段来查看该片段的功能(使用虚拟机有助于保护你的生产环境)。可以通过在帮助文档中搜寻(使用通配符)这些关键字来查阅更多的信息。
请不要跳过脚本中的任意一行。请不要抱有这种想法:“好吧,我不知道这一行命令的功能是什么,那么我就可以跳过它,继续看后面的命令。”请一定先停下来,找出每一行命令的作用或者你认为它们可以实现的功能。这样才能保证你知道需要修改脚本的哪些部分从而满足特定的需求。
作业
代码清单 26.2 呈现了一个完整的脚本。看看你是否能明白该脚本所实现的功能,以及实现的原理。你是否能找到该脚本中可能会出现的错误?需要如何修改该脚本才可以在你的环境中运行?
学无止境
进一步学习的思想
下一步需要完成的步骤是将多个命令结合在一起构成一个包含多个步骤的自动化流程,例如针对第三方用户打包一个只读且随时可用的工具。我们称之为工具制作(ToolMaking)。如果要详细描述该过程,需要我们自己一整本书的篇幅来介绍(Learn PowerShell Toolmaking in AMonth of Lunches(Manning,2010))。即使本书中所学的知识也足够你编写参数化的脚本,在这个脚本中你可以包含任意个命令完成所需任务——其实,这也就是工具制作的初级阶段。
如果需要完成工具制作,需要包含哪些东西呢?■ PowerShell 的简化编程语言。■ 作用域。■ 功能,以及将多个工具整合到单个脚本文件的能力。■ 错误处理。■ 帮助文档的编写。■ 调试。■ 自定义显示格式。■ 自定义类型扩展。■ 脚本与清单模块。■ 使用数据库。工作流。■ 管道排错。■ 复杂的对象层次结构。■ 全局对象与本地对象。■ 代理功能。■ 受限的远程处理与委托管理。■ .Net 的使用。
既然已经阅读了本书,那么我要从哪里开始呢
现在最应该做的就是选择一个任务。选取真实环境中一些重复性的工作,然后利用 PowerShell 工具使得该部分工作自动化。你肯定会遇到一些不知该如何实现的功能,这就是开始学习的最好的切入点。
编写一段脚本修改某服务登录账号的密码,并且将该脚本发送到运行该服务的多台计算机上(可以使用单行命令实现)。
编写一段脚本,用来实现新用户配置的自动化处理,包含新建用户账号、用户邮箱以及根目录等。通过 PowerShell 配置 NTFS 权限会稍微麻烦点,所以请考虑使用基于 PowerShell 脚本开发的 Cacls.exe 或者 Xcacls.exe,而不要使用 PowerShell 的 Get-ACL 以及 Set-ACL 命令(这两个命令使用起来都比较复杂)。
■ 编写管理 Exchange 邮箱的脚本——比如获取占据空间最多邮箱的报表或者针对邮箱大小创建一个报表。
■ 通过包含在 Windows Server 2008 R2 以及之后操作系统中的 WebAdministration 模块实现 IIS 中自动化发布新站点(如果是 Windows Server 2008 中采用 IIS7,也可实现)。
管理员花费好几个星期编写了一段 PowerShell 脚本实现了强大的文件拷贝功能,这样他就可以通过 Web Server 进行发布。Don 问道:“为什么不直接使用 XCopy 或者 RoboCopy 呢?”该管理员盯着 Don 看了一会儿,然后笑了。其实,该管理员陷入了一个误区:“仅使用 PowerShell 实现。”他忘记了“PowerShell 可以直接调用那些强大的现有组件”。
你会喜欢的其他资源
http://youtube.com/powershellorg以及http://youtube.com/powershelldon——分别是powershell.org的YouTube频道以及Don的Youtube频道,这里有大量的免费PowerShell视频,包括在PowerShell + DevOps 全球峰会的录像。
http://jdhitsolutions.com——这是Jeff的发布通用脚本以及PowerShell相关文章的博客站点。
http://donjones.com——这是Don的个人博客,包含PowerShell相关的内容。
http://devopscollective.org——这是PowerShell.org的父组织,专注于以DevOps方式实现IT管理的大局。
是否还有其他一些推荐的书籍?有两本,分别为 Learn PowerShell Toolmaking in A Month of Lunches 以及 PowerShell in Depth(这两本书都是 Manning 出版),我们是这两本书的作者或合著者,如果你喜欢本书,那上面两本也会同样适用于你。我们还推荐 PowerShell Deep Dives(Manning,2013),该书是 PowerShell MVP 撰写的深入技术文章的集合(本书的获利会捐赠给儿童慈善机构,所以请多买几本吧)。最后,如果你喜欢视频教学,我们两个在 http://Pluralsight.com都有视频教学,该网站还包含了数以千计IT相关的视频。
PowerShell 备忘清单
现在是时候将遇到的一些小问题进行整理了。当你遇到什么问题时,请记住首先翻到本章进行查找。
标点符号
~(波浪符)作为路径的一部分时,该字符表示当前用户的根目录,也就是在系统变量 UserProfile 中定义的值。
1
2
3
4 14:12 [SLC-PC-HOME] D:\ > $env:userprofile
C:\Users\21923
14:12 [SLC-PC-HOME] D:\ > cd ~
14:13 [SLC-PC-HOME] C:\Users\21923 > cd()(括号)有两种使用场景:(1)和在数学中一样,括号定义了执行的顺序。PowerShell 会优先执行括号中的命令。如果存在多重括号,则会从最里层括号向外执行。通过这种方式,可以很轻易实现:先执行一个命令,之后将该命令的输出结果传递给另外一个命令的某个参数,比如 Get-Service –ComputerName (Get-Content C:\ComputerNames.txt)。(2)括号也可以被用作包含一个方法的参数。即使该方法不要求使用任何参数,也必须带有括号,比如 Change-Start-Mode(‘Audomatic’)以及 Delete()。
[](中括号)在 PowerShell 中有两种使用方式。(1)需要访问一个数组或者集合中某个单独的对象时,可以使用中括号来指定对应的索引号:$Services[2]表示从$Services 中获取第三个对象(请记住索引编号是从 0 开始计数的)。(2)当需要将某个数据转化为特定的类型时,需要将类型包含在中括号中。例如,$My Result/3 as [INT]会将除法运算的结果转化为整数;再比如,命令[XML]$Data=Get-Content Data.XML 会读取 Data.XML 中的内容,并且尝试将该内容解析为合法的 XML 文件。
{}(花括号)也被称为大括号,有 3 种用途。(1)花括号可用作包含可执行代码或者命令块,我们称之为脚本段(Script Blocks)。该脚本段经常被作为值传递给那些可接受脚本段或者筛选块的参数:Get-Service | Where-Object{$_.Status –eq ‘Running’}。(2)花括号可用作包含构成哈希表的键-值对。左大括号前面总是一个“@”符号。在下面的示例中,我们使用花括号来包含哈希表的键-值对(在示例中,有两组键-值)。第二个花括号包含一段表达式的脚本段,该脚本段作为第二个键的值:$HashTable= @{l=’Label’; e={expression}}。(3)当变量的名称中包含空格或者其他非法字符时,必须使用花括号来包含这部分信息:${My Variable}。
‘ ‘(单引号)可用作包含字符串(String)。PowerShell 并不会对包含在单引号中的字符串查找转义字符或者变量。
■ “ “(双引号)也可用作包含字符串,但与单引号不同的是,PowerShell 会针对双引号中的字符串数据进行查找转义字符以及$字符。其中会进行针对转义字符的处理,同时$符号后面带有的字符(到下一个空格为止)会被识别为一个变量名字,并且其值会被替换掉。例如,如果变量$One的值为“World”,同时定义$Two=”Hello $One
n",那么$Two 的值就会是“Hello World”之后再加一个回车(
n 代表一个回车键)。$(美元符号)告诉PowerShell $后面的字符(截止到下一个空格处)为一个变量的名称。但是当在使用管理变量的Cmdlet时,可能容易造成误解。假如$One 变量的值为 Two,然后执行 New-Variable –Name $One –Value ‘Hello’命令,会创建一个名称为Two的变量,并且其值为“Hello”——有些人感到很奇怪,为什么变量的名称会是Two。这是因为$符号告诉 PowerShell 使用$One 的值作为新变量的名称。相对应地,New-Variable –Name One–Value ‘Hello’,该命令会创建一个名为 One 的变量。
■ %(百分号)是 ForEach-Object Cmdlet 的别名,同时它也是模运算符,返回除法运算后的余数。
■ ?(问号)是 Where-Object Cmdlet 的别名。
■ >(右尖括号)类似 Out-File Cmdlet 的一个别名。但是严格来讲,它并不是一个真正的别名,但却提供了 Cmd.exe 风格式的文件重定向功能:Dir>Files.Txt。
■ +、-、*、/、% 这些数学运算符是作为标准算术运算符使用。请注意,+也可以用作拼接字符串。
■ -(破折号或者叫连字符)可以用作连接参数名称或者其他运算符,如-Co mputerName 或者-Eq。同时破折号也可以用作分离 Cmdlet 名称中的动词与名词,比如 Get-Content。另外,破折号也作为算术中的减法运算符使用。
@(at 符号)在 PowerShell 中有四种用途。
(1)可用在哈希表的左花括号之前(请参阅上面的介绍花括号部分)。
(2)当用在括号之前时,其后会紧跟一串以逗号分隔的值,这些值组成一个数组:$Array= @(1,2,3,4)。其中的@字符与括号是可选的,这是由于 PowerShell 默认会将以逗号分隔的列表识别为数组。
(3)可以表示一个 Here-String。Here-String 是指包含在单引号或者双引号中的字符串。一个 Here-String 以“@”字符作为开始和结束的标志,结束的“@”必须位于另起一行的起始位置。如果想获取更多的信息或者示例,请执行 Help About_Quoting_Rules。另外需要说明的是,Here-String 也可通过单引号进行定义。
1
2
3
4
5
6
7
8
9
10
11
12
13 14:20 [SLC-PC-HOME] D:\ > $test=@"
>> how are your"
>> aaaa'
>> '''
>> test
>> "
>> "@
14:21 [SLC-PC-HOME] D:\ > $test
how are your"
aaaa'
'''
test
"(4) @也是 PowerShell 中的传递符(Splat Operator)。如果构建了一个哈希表,在哈希表中,键名称能匹配参数名称,并且键的值为参数的值,那么你就可以将该哈希表传递给一个 Cmdlet。Don 曾经为 TechNet Magazine 写过一篇关于传递(Splating)的文章( https://technet.microsoft.com/en-us/magazine/gg675931.aspx)。
&(与符号)是 PowerShell 中的一个调用运算符,使得 PowerShell 可以将某些字符识别为命令,并运行这些命令。例如,$a=”Dir”命令将“Dir”赋给了变量$a,然后&$a 就会执行 Dir 命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 14:21 [SLC-PC-HOME] D:\ > $test = {
>> ps | select -f 3
>> }
14:22 [SLC-PC-HOME] D:\ > &$test
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
261 19 7500 14592 2.97 9384 2 APlayer
359 21 10888 30192 0.25 5476 2 ApplicationFrameHost
222 12 7288 13228 24.98 10676 0 audiodg
14:22 [SLC-PC-HOME] D:\ > $test="ls"
14:22 [SLC-PC-HOME] D:\ > &$test
目录: D:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2021-10-30 0:06 21923
d---s- 2021-11-19 18:46 360WiFi
d----- 2021-11-16 23:04 apps
d----- 2022-07-27 19:15 BaiduNetdiskDownload
d----- 2022-07-22 21:13 bb;(分号)一般用作分隔 PowerShell 中同一行的两个命令:Dir ; Get-Process。这个命令会 先执行 Dir 命令,之后执行 Get-Process 命令。它们的执行结果会发送给一个管道 ,但是 Dir 命令的执行结果并不会通过管道发送给 Get-Process 命令。
1 dir;ps#(井号)为注释符号。跟在#之后的文字,到下一个回车之前,均会被 PowerShell 忽略掉。<>可以被用作定义一个注释块的标签,“<#”作为起始,“#>”作为结束。包含在该注释块中的所有命令均会被 PowerShell 忽略掉。
=(等号)是 PowerShell 中的赋值运算符,用来给一个变量赋值:$One=1。但是它不能用作相等性比较,相等性比较需要使用-Eq。另外需要记住,该运算符可以与数学运算符结合使用:$Var +=5。该命令会对$Var 变量的值增加 5。
1
2
3
4
5
6
7
8
9
10
11
12
13 14:24 [SLC-PC-HOME] D:\ > 5 -eq 3
False
14:24 [SLC-PC-HOME] D:\ > 5 -eq 5
True
14:24 [SLC-PC-HOME] D:\ > 5 == 6
所在位置 行:1 字符: 1
+ 5 == 6
14:24 [SLC-PC-HOME] D:\ > $num=5
14:25 [SLC-PC-HOME] D:\ > $num+=1
14:25 [SLC-PC-HOME] D:\ > $num
6|(管道符)主要用于将一个 Cmdlet 的输出结果传递给另外一个 Cmdlet。第二个 Cmdlet(接收输出结果的 Cmdlet)采用管道参数绑定方法来确定哪个参数或者哪些参数来负责接收传入的管道对象。第 9 章中对该过程进行了讲解。
■ \(反斜杠)或者/(正斜杠)可以作为数学表示中的除法运算符; 反斜杠和正斜杠也可以作为文件路径中的分隔符:C:\Windows 和 C:/Windows 路径一致 。反斜杠在 WMI 筛选场景以及正则表达式中也可作为转义字符。
1
2
3
4
5
6
7
8
9 14:26 [SLC-PC-HOME] D:\ > ls d:/
目录: D:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2021-10-30 0:06 21923.(句号)有三种用途:(1)句号可以被用作表示希望访问某个成员,比如一个属性或方法;再或者一个对象:$_.Status表示访问$_占位符中对象的 Status 属性。(2)它可以通过“.”引用源码从而执行一段脚本,意味着该脚本运行在当前作用域下,并且该脚本定义的任何对象在脚本运行完毕之后均存在,比如.C:\myscript.ps1。(3)两个“.”(..)会形成一个范围运算符,该运算符在本章后面会讲到。你也会发现,“..”也可用作表示文件系统中的当前路径的父文件夹,比如..\。
1
2
3
4
5
6
7
8
9
10 14:26 [SLC-PC-HOME] D:\ > . .\Get-DiskInventory.ps1
DeviceID freespace(mb) size(gb) %Free
-------- ------------- -------- -----
J: 303670 745 40
I: 672326 745 88
G: 29446 98 29
F: 0 0
D: 12980 124 10
C: 5080 111 4,(逗号)用在引号之外时,可以用作分隔数组或者列表中的项:”One”, 2,”Three”,4。另外,它也可用作将多个静态值传递给可接收这些值的参数:Get-Process –ComputerName Server1, Server2, Server3。
:(冒号,严格来说应该是两个冒号)可用作访问类的静态成员。这里采用了.Net Framework 编程语言的概念,比如[-DateTime]::Now(其实也可以使用 Get-Date 来获取相同的结果)。
1
2
3 14:26 [SLC-PC-HOME] D:\ > [datetime]::now
2022年7月30日 14:27:50!(感叹号)是“非”(Not)布尔运算符的别名。
1
2
3
4
5
6 14:28 [SLC-PC-HOME] D:\ > 5 -eq 3
False
14:28 [SLC-PC-HOME] D:\ > ! (5 -eq 3)
True
14:28 [SLC-PC-HOME] D:\ > -not (5 -eq 3)
True在美国键盘格式中没有被 PowerShell 使用到的应该是脱字符“^”,毕竟该符号常用于正则表达式运算。
帮助文档
[](中括号)连在一起的中括号表示一个参数可接受多个值(
<String>[],而非<String>
)。<>(尖括号)可用来包含数据类型,表示值的类型或者参数匹配的对象:
<String>, <int>, <Process>
等。运算符
-eq:等于(-ceq 用作字符串比较,包括大小写是否一致)。
■ -ne:不等于(-cne 用作字符串比较,包括大小写是否一致)。
■ -ge:大于或等于(-cge 用作字符串比较,包括大小写是否一致)。
■ -le:小于或等于(-cle 用作字符串比较,包括大小写是否一致)。
■ -gt:大于(-cgt 用作字符串比较,包括大小写是否一致)。
■ -lt:小于(-clt 用作字符串比较,包括大小写是否一致)。
■ -contains:若数据集包含特定对象,则返回真(True)。($Collection–Contains $Object。)-nocontains 表示相反含义。
■ -in:若特定对象包含在数据集中,则返回真(True)。($Object –in$Collection。)-noin 表示相反含义。
逻辑运算符可用于组合运算。
■ -not:将真假值取反(!是该运算符的别名)。
■ -and:如果整个表达式要为真,则所有子表达式均需要为真。
■ -or:如果整个表达式要为真,则其中一个子表达式需要为真。
还存在执行特定操作的运算符。
■ -join:将一个数组的元素连接为分隔的字符串。
■ -split:将一个分隔的字符串分离为一个数组。
■ -replace:将一个字符串中特定字符(串)替换为另外的字符(串)。
■ -is:若一个对象为指定类型,返回为真(True)。($ID –Is [INT])
■ -as:将对象转化为特定类型。($ID –As [INT])
■ ..:一个范围运算符,1..10 会返回 1 到 10 的十个对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 14:28 [SLC-PC-HOME] D:\ > 1..9
1
2
3
4
5
6
7
8
9
14:32 [SLC-PC-HOME] D:\ > &{1..9}
1
2
3
4
5
6
7
8
9■ -f:格式化运算符,会使用后面提供的值替换对应的占位符。(”{0}, {1}” –F”Hello”, “World”)
1
2 14:33 [SLC-PC-HOME] D:\ > "{0} {1}" -f "hello","world"
hello world自定义属性与自定义列的语法
使用 Select-Object 来定义自定义属性,或者分别使用 Format-Table 以及 Format-List 自定义列或列表条目。下面是对应的哈希表语法。
1
2
3
4 @{LABEL=''column_or_property_name"; expression={value_expression}}
@{name=''column_or_property_name"; expression={value_expression}}这里的两个键“Label”和“Expression”,可以分别缩写为“l”和“e”(请注意,这里是小写的字母 l,不是数字 1)。当然,你也可以使用 n 作为键的名称。
1
2 @{l=''column_or_property_name"; e={value_expression}}
@{n=''column_or_property_name"; e={value_expression}}在表达式中,可以使用$_占位符关联到当前对象(比如当前表中的行或者期望添加自定义属性的对象)。
1 @{L='test'; E={$_.name}}Select-Object 和 Format-的 Cmdlet 均会查找 n(或者 name 或者 label 或者 l)键和 e 键;Format- Cmdlet 也支持 Width 和 Align(仅支持 Format-Table)和 FormatString 操作。请阅读 Format-Table 命令的帮助文档,获取对应的示例。
1 ft @{L='test'; E={$_.name}; formatstring='f2'; align='right'}管道参数输入
对 ByValue 方法而言,PowerShell 会查看放入管道中对象的类型。当然,你也可以通过 gm 命令自行查看该对象的类型名称。之后 PowerShell 会检查该 Cmdlet 中是否有参数可以接收传入的对象类型,并且检查是否有参数可以使用 ByValue 方法来接收管道输入。对一个 Cmdlet 而言,如果采用这种方式,则不可能有两个参数绑定到相同的数据类型。换句话说,你无法看到一个 Cmdlet 中有两个参数均满足如下两个条件:均可接收
类型的输入,均可使用 ByValue 方法实现参数绑定。 如果无法使用 ByValue 方法,那么 PowerShell 就会尝试使用 ByPropertyName 方法。在该方法中,PowerShell 仅简单查看放入管道中对象的属性,之后尝试找到某个可接收通过 ByPropertyName 方法传入对象的参数,并且要求该参数的名称与属性名称一致。例如,如果放入管道中的对象包含 Name、Status 和 ID 属性,PowerShell 会查看 Cmdlet 中是否有参数名为 Name、Status 和 ID。同时要求这些参数被标记为“可接收 ByPropertyName 管道输入”。至于如何查看是否满足条件,请阅读对应的详细帮助文档(记住,在使用 Help 命令时加上-Full 参数)。
让我们看看 PowerShell 如何实现这些功能。比如本例,假如有一个命令为 Get-Service |Stop-Service 或者是 Get-Service | Stop-Process,将其中第一个 Cmdlet 称为第一个命令,类似地,第二个 Cmdlet 称为第二个命令。PowerShell 采用下面的步骤进行工作。
(1)第一个命令产生的对象类型 .
ServiceController
1
2 14:39 [SLC-PC-HOME] D:\ > get-service | gm | select -f 10
TypeName:System.ServiceProcess.ServiceController(2)第二个命令中是否有参数可以接收第一个命令产生的对象类型(
1
2
3
4
5
6
7
8 # stop-service满足,就停止
help Stop-Service
[-InputObject] <System.ServiceProcess.ServiceController[]>
# 找不到
4:40 [SLC-PC-HOME] D:\ > help stop-process
[-InputObject] <System.Diagnostics.Process[]>(3)如果步骤(2)的答案是 Yes,那么第一个命令产生的完整对象就会被关联到步骤(2)中满足条件的参数。此时,所有步骤就结束了——不需要再到步骤(4)。但是如果步骤(2)的答案是“否”,那么就需要继续步骤(4)。
(4)此时需要检查第一个命令产生的对象。查看产生的对象包含什么属性。再次说明,你可以通过将第一个命令产生的对象通过管道传递给 Get-Member 来查看该信息。、
1
2
3
4
5
6
7
8
9
10 4:41 [SLC-PC-HOME] D:\ > get-service | gm | fw name -col 5
Name RequiredServices Disposed Close Continue
CreateObjRef Dispose Equals ExecuteCommand GetHashCode
GetLifetimeService GetType InitializeLifetimeSe... Pause Refresh
Start Stop WaitForStatus CanPauseAndContinue CanShutdown
CanStop Container DependentServices DisplayName MachineName
ServiceHandle ServiceName ServicesDependedOn ServiceType Site
StartType Status ToString(5)此时检查第二个命令的参数(此时需要重新查看详细帮助文档)。是否有参数的名称与步骤(4)中找到的属性名称一致(条件 a),并且该参数是否能接收通过 ByPropertyName 方式传入的对象(条件 b)?
1
2
3
4
5
6
7
8
9
10
11
12
13
14 Get-Process [[-Name] <System.String[]>] [-ComputerName <System.String[]>] [-FileVersionInfo] [-Module] [<CommonPara
meters>]
Get-Process [-ComputerName <System.String[]>] [-FileVersionInfo] -Id <System.Int32[]> [-Module] [<CommonParameters>
]
Get-Process [-ComputerName <System.String[]>] [-FileVersionInfo] -InputObject <System.Diagnostics.Process[]> [-Modu
le] [<CommonParameters>]
Get-Process -Id <System.Int32[]> -IncludeUserName [<CommonParameters>]
Get-Process [[-Name] <System.String[]>] -IncludeUserName [<CommonParameters>]
Get-Process -IncludeUserName -InputObject <System.Diagnostics.Process[]> [<CommonParameters>]name 一样,查看是否支持 ByPropertyName
1
2
3
4
5
6
7
8
9
10 15:02 [SLC-PC-HOME] D:\ > help get-process -Parameter name
-Name <System.String[]>
Specifies one or more processes by process name. You can type multiple process names (separated by commas) and use
wildcard characters. The parameter name (`Name`) is optional.
是否必需? False
位置? 0
默认值 None
是否接受管道输入? True (ByPropertyName)(6)如果有任一参数满足步骤(5)中的条件 a 和 b,那么第一个命令产生对象的属性就会关联到对应的第二个命令的同名参数,第二个命令就会运行。如果第一个命令产生对象的属性名称与第二个命令中可接收 ByPropertyName 方式传入对象的参数名称不一致,那么第二个命令也会运行,但是此时第二个命令并没有管道输入。
另外需要注意的是,你可以针对任意命令手动输入参数以及其值。但是此时,将会导致参数无法接收管道输入对象,即使正常情况下可以使用某种管道输入方法(不管是 ByValue 还是 ByPropertyName)。
何时使用$_
这或许是 PowerShell 中最让人费解的问题之一:使用$_占位符的最佳时机是什么?
当 PowerShell 显式查找$_,并且准备使用其他数据填充该占位符时,可以使用$*占位符。一般来讲,这只会发生在处理管道输入的脚本段中——在这种情况下,$*占位符一次只能包含一个管道输入对象。在下面几个地方会用到该占位符。
在 Where-Object 的筛选脚本段中:
1
2
3
4
5
6 15:05 [SLC-PC-HOME] D:\ > gsv | ? { $_.name -match 'box'}
Status Name DisplayName
------ ---- -----------
Stopped XboxGipSvc Xbox Accessory Management Service
Stopped XboxNetApiSvc Xbox Live 网络服务在传递给 ForEach-Object 命令的脚本段中,比如通常与该 cmdlet 一起使用获取进程的脚本段:
1
2
3 15:06 [SLC-PC-HOME] D:\ > gwmi win32_service | ? name -Match 'box' | ForEach-Object { $_.changestartmode('automatic')}针对有关进程的过滤功能和高级功能的脚本段。我们编写的另外一本书中讨论到该部分知识——Learn PowerShell Toolmaking in A Month of Lunches。
用来创建自定义属性或者表列的哈希表表达式中,请参考 28.4 小节查看更多细节,或者阅读第 8 ~ 10 章中更完整的讨论。
在上面所有场景中,$_占位符仅会出现在脚本段的花括号中。那么这也是一个判断什么时候可以使用$_占位符的比较好的规则。
powershell定时任务
打开定时任务 (要么统一使用脚本,要么纯窗口。打开窗口执行脚本会出现时而执行,时而不执行)
sheduledJob (后台)
1
2
3
4
5 # 生成一个定时任务 脚本可以任意更新
$script = "D:\zabbix\zabbix_scripts\start-computer.ps1"
# 手工测试运行脚本 (OK)
powershell $script先生成一个不执行的任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 生成一个定时任务 (定时脚本更新,必须重新添加。已测试)
$script = "D:\zabbix\zabbix_scripts\start-computer.ps1"
$jobName="run script on logon"
# To submit a hash table, use the following keys:
$O = @{
# 唤醒执行
WakeToRun = $true
# 不空闲电脑也执行
StartIfNotIdle = $true
MultipleInstancePolicy = "Queue"
# 管理员执行
RunElevated = $true
}
Register-ScheduledJob -ScheduledJobOption $O -Name $jobName -FilePath $script -RunNow
# runnow 添加后立即运行再为任务添加执行时间,指定任务触发,可以多次指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # 每分钟执行
Add-JobTrigger -Name $jobName -Trigger $(New-JobTrigger -Once -At $(get-date) -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([Timespan]::MaxValue))
# 启动电脑时 ( Starts the scheduled job when Windows starts. 用户还未登陆
Add-JobTrigger -Name $jobName -Trigger $(New-JobTrigger -AtStartup )
# 登陆时 Starts the scheduled job when the specified users log on to the computer. To specify a user, use the User parameter. * 表示任务用户,或指定 用户名
Add-JobTrigger -Name $jobName -Trigger $(New-JobTrigger -AtLogOn -User "$env:username") #########
# 每天 12:30
Add-JobTrigger -Name $jobName -Trigger $(New-JobTrigger -Daily -DaysInterval 1 -At 12:30 )
# 每周3,19:00
Add-JobTrigger -Name $jobName -Trigger $(New-JobTrigger -Weekly -WeeksInterval 1 -DaysOfWeek 3 -At 19:00 )
1
2
3
4
5 # 查看所有trigger
Get-JobTrigger -Name $jobName
Remove-JobTrigger -Name $jobName -TriggerId 1
Unregister-ScheduledJob $jobName -ForcescheduledTask (前台或后台)
前台
1
2
3
4
5
6
7
8
9
10 $script = "D:\zabbix\zabbix_scripts\start-computer.ps1"
$jobName="run script on logon"
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-WindowStyle hidden -file $script"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "$env:username" -RunLevel Highest -LogonType Interactive # interactive前端
$settings = New-ScheduledTaskSettingsSet
$task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settings
Register-ScheduledTask $jobName -InputObject $task-RunLevel Limited 有限用户权限
-RunLevel Highest 管理员
1 Unregister-ScheduledTask -TaskName $jobName后台(屏幕会闪)
1
2
3
4
5
6
7
8
9 $script = "D:\zabbix\zabbix_scripts\every-1-write.ps1"
$jobName="every 1 minutes run, backend"
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoLogo -NonInteractive -WindowStyle Hidden -file $script"
$trigger = New-ScheduledTaskTrigger -Once -At $(get-date) -RepetitionInterval (New-TimeSpan -Minutes 1) # 每分钟
$principal = New-ScheduledTaskPrincipal -UserId "$env:username" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet
$task = New-ScheduledTask -Action $action -Principal $principadd al -Trigger $trigger -Settings $settings
Register-ScheduledTask $jobName -InputObject $task-RunLevel Limited 有限用户权限
-RunLevel Highest 管理员
-Execute "PowerShell.exe" -Argument "-NonInteractive -NoLogo -WindowStyle hidden -file $script"
后台
1 Unregister-ScheduledTask -TaskName $jobName调试脚本
1 New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoExit -file $script"同时脚本末尾添加一行
read-host
,需要标准输入附录
1~6 章
1
2
3
4
5
6
7
8
9
10
11
12
13
14 15:12 [SLC-PC-HOME] D:\ > Get-Alias -Definition get-process,sort-object,select-object,export-csv,help,get-childitem,get-service
CommandType Name Version Source
----------- ---- ------- ------
Alias gps -> Get-Process
Alias ps -> Get-Process
Alias sort -> Sort-Object
Alias select -> Select-Object
Alias epcsv -> Export-Csv
Alias man -> help
Alias dir -> Get-ChildItem
Alias gci -> Get-ChildItem
Alias ls -> Get-ChildItem
Alias gsv -> Get-Service运行一个命令,从而显示应用程序事件日志中最新的 100 个条目,不要使用 Get-WinEvent。
1 15:08 [SLC-PC-HOME] D:\ > get-eventlog Application -Newest 100写一个仅显示前五个最消耗虚拟内存(VM)进程的命令。
1
2
3
4
5
6
7
8
9 15:10 [SLC-PC-HOME] D:\ > ps | sort vm -desc | select -f 5
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
397 23 33900 45668 1.91 4336 2 chrome
324 20 1129928 1170664 838.03 7636 2 chrome
347 20 41436 51732 3.09 12224 2 chrome
375 21 49572 59220 3.50 13032 2 chrome
276 17 31396 45024 1.84 11524 2 chrome创建一个包含所有服务的 CSV 文件,只需要列出服务名称和状态。所有处于运行状态的服务位于停止状态的服务之前。
1 15:13 [SLC-PC-HOME] D:\ > gsv | select name,status | sort status -desc | epcsv service.csv写一个命令行,将 BITS 服务的启动类型变更为手动。
1 15:27 [SLC-PC-HOME] D:\ > gwmi win32_service | ? name -Match bits | % { $_.ChangeStartMode('manual') }
1 set-service bits -starttype manual显示你计算机中所有文件名称为 Win*.*的文件,以 C:\开始。注意:为了完成该实验,你可能需要去实验和使用一个 Cmdlet 命令的新参数。
1 15:46 [SLC-PC-HOME] D:\ > ls -LiteralPath c:\win*.* -r获取一个 C:\Program Files 的目录列表。包含所有的子文件夹,把这些目录列表放到位于 C:\Dir.txt 的文本文件内(记住使用“>”,或者 Out-File Cmdlet)。
1 15:47 [SLC-PC-HOME] D:\ > ls 'C:\Program Files' -r > c:\dir.txt获取最近 20 条安全事件日志的列表,将这些信息转化成 XML 格式。不要在硬盘上创建文件,而是把 XML 在控制台窗口直接显示出来。注意:该 XML 可以作为一个单独的原生对象显示,而不是以一个原始的 XML 数据。这没问题。那也是 PowerShell 展示 XML 的方式。如果你喜欢,你可以将 XML 对象通过管道传递给 Format-Custom 命令,从而查看 XML 展开为对象层级的形式。
1 15:49 [SLC-PC-HOME] D:\ > Get-EventLog Security -Newest 20 | ConvertTo-Xml | format-custom获取一个服务列表,并将其导出到以 C:\services.csv 命名的 CSV 文件内。
1 gsv | export-csv c:\services.csv获取一个服务列表,仅保留服务名称、显示名称和状态,然后将这些信息发送到一个 HTML 文件中。在 HTML 文件中的服务信息表格之前显示“Installed Services”。如果可以,将安装服务显示在
<H1>
这个 html 标签中。在 Web 浏览器中验证该文件是否正确。
1 15:58 [SLC-PC-HOME] D:\ > gsv | select name,DisplayName,status | ConvertTo-Html -Title 'Installed Services' | % { $_.replace('td','h1')} > gsv.html
1
2 # 答案
gsv | select name,DisplayName,status -f 10 | ConvertTo-Html -PreContent '<h1>installed services</h1>' -Title 'service report' > gsv.html为 Get-ChildItem 创建一个新的别名 D。仅将别名导出到一个文件里。关闭这个 Shell,然后打开一个新的控制台窗口。把别名导入到新的 Shell 中。确认能够通过运行 D 并且获得一个目录列表。
1
2
3
4
5
6
7
8
9
10
11
12 15:59 [SLC-PC-HOME] D:\ > New-Alias d Get-ChildItem
16:00 [SLC-PC-HOME] D:\ > Export-Alias -Name d test.alias
16:00 [SLC-PC-HOME] D:\ > Get-Content .\test.alias
# 别名文件
# 导出者 : 21923
# 日期/时间 : 2022年7月30日 16:00:39
# 计算机: SLC-PC-HOME
"d","Get-ChildItem","","None"
16:01 [SLC-PC-HOME] D:\ > Import-Alias .\test.alias显示类别为“Hotfix”或“Update”的补丁,结果中不包含安全更新。
1
2
3
4
5
6
7 16:03 [SLC-PC-HOME] D:\ > Get-HotFix | ? description -NotMatch security
Source Description HotFixID InstalledBy InstalledOn
------ ----------- -------- ----------- -----------
SLC-PC-HOME Update KB5013889 NT AUTHORITY\SYSTEM 2022-06-27 0:00:00
SLC-PC-HOME Update KB5007040 NT AUTHORITY\SYSTEM 2021-10-31 0:00:00
SLC-PC-HOME Update KB5008295 NT AUTHORITY\SYSTEM 2021-11-07 0:00:00运行一个用于展示 Shell 所在的当前目录的命令。
1 pwd运行一个命令,展示最近你在 Shell 中运行过的命令。从中查找你在任务 11 中所运行的命令。将这两个命令通过管道传输符进行连接,重新运行任务 11 的命令。
1
2
3
4
5
6
7
8 16:08 [SLC-PC-HOME] D:\ > get-history 8 | Invoke-History
Get-HotFix | ? description -NotMatch security
Source Description HotFixID InstalledBy InstalledOn
------ ----------- -------- ----------- -----------
SLC-PC-HOME Update KB5013889 NT AUTHORITY\SYSTEM 2022-06-27 0:00:00
SLC-PC-HOME Update KB5007040 NT AUTHORITY\SYSTEM 2021-10-31 0:00:00
SLC-PC-HOME Update KB5008295 NT AUTHORITY\SYSTEM 2021-11-07 0:00:00运行命令修改安全事件日志,使得在需要时可以通过覆盖旧日志的方式新增日志。
1 16:31 [SLC-PC-HOME] D:\ > Limit-EventLog Security -OverflowAction OverwriteAsNeeded通过使用 New-Item Cmdlet 来创建一个名称为 C:\Review 的新目录。这与运行 Mkdir 并不同;New-Item 命令需要知道你所想要创建的新项目是什么类型。请阅读该命令的帮助信息。
1 16:14 [SLC-PC-HOME] D:\ > ni c:\review -type Directory显示该注册码的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 16:17 [SLC-PC-HOME] D:\ > Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders\'
AppData : C:\Users\21923\AppData\Roaming
Cache : C:\Users\21923\AppData\Local\Microsoft\Windows\INetCache
Cookies : C:\Users\21923\AppData\Local\Microsoft\Windows\INetCookies
Desktop : C:\Users\21923\Desktop
Favorites : C:\Users\21923\Favorites
History : C:\Users\21923\AppData\Local\Microsoft\Windows\History
Local AppData : C:\Users\21923\AppData\Local
My Music : C:\Users\21923\Music
My Pictures : C:\Users\21923\Pictures
My Video : C:\Users\21923\Videos
NetHood : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Network Shortcuts
Personal : D:\文档
PrintHood : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Printer Shortcuts
Programs : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
Recent : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Recent
SendTo : C:\Users\21923\AppData\Roaming\Microsoft\Windows\SendTo
Start Menu : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Start Menu
Startup : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Templates : C:\Users\21923\AppData\Roaming\Microsoft\Windows\Templates
{374DE290-123F-4565-9164-39C4925E467B} : D:\download
{24D89E24-2F19-4534-9DDE-6A6671FBB8FE} : C:\Users\21923\OneDrive\文档
{339719B5-8C47-4894-94C2-D8F77ADD44A6} : C:\Users\21923\OneDrive\图片
{7D83EE9B-2244-4E70-B1F5-5393042AF1E4} : D:\download
{F42EE2D3-909F-4907-8871-4C22FC0BF756} : D:\文档
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\Windo
ws\CurrentVersion\Explorer\User Shell Folders\
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\Windo
ws\CurrentVersion\Explorer
PSChildName : User Shell Folders
PSDrive : HKCU
PSProvider : Microsoft.PowerShell.Core\Registry找出(但是请不要运行)能完成如下功能的命令。■ 重启电脑。■ 关闭电脑。■ 从一个工作组或者域内移除一个电脑。■ 恢复一个电脑系统,并重建检查点。
1
2
3
4
5
6
7
8
9
10
11
12 16:18 [SLC-PC-HOME] D:\ > help *shutdown*
Name Category Module Synopsis
---- -------- ------ --------
Get-Member Cmdlet Microsoft.PowerShell.U... Gets the properties and methods of objects.
New-Object Cmdlet Microsoft.PowerShell.U... Creates an instance of a Microsoft .NET Framew...
Restart-Computer Cmdlet Microsoft.PowerShell.M... Restarts the operating system on local and rem...
Stop-Computer Cmdlet Microsoft.PowerShell.M... Stops (shuts down) local and remote computers.
Restart-Computer -ComputerName Server01 -Wait -For PowerShell -Timeout 300 -Delay 2
Restart-Computer -ComputerName Server01 -Protocol WSMan -WsmanAuthentication Kerberos
1
2
3
4
5
6
7 16:21 [SLC-PC-HOME] D:\ > get-command -ver remove -Noun *computer*
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Remove-ADComputer 1.0.1.0 ActiveDirectory
Cmdlet Remove-ADComputerServiceAccount 1.0.1.0 ActiveDirectory
Cmdlet Remove-Computer 3.1.0.0 Microsoft.PowerShell.Management
1
2
3 Get-ComputerRestorePoint
Restore-Computer -RestorePoint 255
Get-ComputerRestorePoint -LastStatus
1 Checkpoint-Computer你认为什么命令可以改变一个注册表值?提示:该命令与任务 16 中的命令有相同的名词部分。
1 16:26 [SLC-PC-HOME] D:\ > get-command -Noun ItemProperty复习 2
1
2
3
4
5
6
7
8
9 16:34 [SLC-PC-HOME] D:\ > Get-Alias -Definition Format-Table,Invoke-Command,get-content,where-object
CommandType Name Version Source
----------- ---- ------- ------
Alias ft -> Format-Table
Alias icm -> Invoke-Command
Alias cat -> Get-Content
Alias ? -> Where-Object在一个表格中展示一个正在运行的进程的列表,其中只包含进程名称与 ID 号。不要让该表格在两列之间有较大的空白区域。
1 ps | ft processname,id -auto但将输出结果格式化为一个列表,该列表包含驱动短名称、驱动的显示名称、驱动文件路径、启动模式以及当前状态。将路径属性的列标题显示为 Path,而不是其原本的名称。
1 16:36 [SLC-PC-HOME] D:\ > gwmi win32_systemdriver | fl name,@{L='Path';E={$_.Pathname}},StartMode,status2 个电脑 get-psdriver 使用远程处理去做,确保输出结果包含计算机名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 16:37 [SLC-PC-HOME] D:\ > Invoke-Command -ComputerName 192.168.0.109,192.168.0.109 { Get-PSProvider }
Name Capabilities Drives PSComputerName
---- ------------ ------ --------------
Registry ShouldProcess, Transactions {HKLM, HKCU} 192.168.0.109
Alias ShouldProcess {Alias} 192.168.0.109
Environment ShouldProcess {Env} 192.168.0.109
FileSystem Filter, ShouldProcess, Creden... {C, D, G, I...} 192.168.0.109
Function ShouldProcess {Function} 192.168.0.109
Variable ShouldProcess {Variable} 192.168.0.109
Registry ShouldProcess, Transactions {HKLM, HKCU} 192.168.0.109
Alias ShouldProcess {Alias} 192.168.0.109
Environment ShouldProcess {Env} 192.168.0.109
FileSystem Filter, ShouldProcess, Creden... {C, D, G, I...} 192.168.0.109
Function ShouldProcess {Function} 192.168.0.109
Variable ShouldProcess {Variable} 192.168.0.109写一个命令列出 文件中所有电脑,正在电脑上运行的服务名称,并将其写入到 C:\Computer .txt 文件中。
1
2
3
4
5 16:38 [SLC-PC-HOME] D:\ > '192.168.0.109,192.168.0.109'.Split(',') > allservers.txt
16:38 [SLC-PC-HOME] D:\ > Get-Content .\allservers.txt
192.168.0.109
192.168.0.109
16:38 [SLC-PC-HOME] D:\ > Invoke-Command -ComputerName $(get-content .\allservers.txt) { gsv } > computers.txt查询 Win32_LogicalDisk 的所有实例。仅显示 DriveType 属性中包含 3 且有百分之五十以上的可用磁盘空间的实例。你可能需要调整可用空间百分比参数从而在你的计算机上能够得到输出结果。
1
2
3
4
5
6
16:45 [SLC-PC-HOME] D:\ > gwmi win32_logicaldisk | ? { $_.drivetype -eq 3 -and $_.size -and $_.freespace / $_.size -gt .5 } | ft deviceid,drivetype,providername,@{L='%free';E={$_.freespace / $_.size * 100};format='F2'},volumename
deviceid drivetype providername %free volumename
-------- --------- ------------ ----- ----------
I: 3 88.11 娱乐
1
2
3
4
5
6
7
8 17:27 [SLC-PC-HOME] D:\ > gwmi win32_logicaldisk | ? { $_.drivetype -eq 3 -and $_.size } | select deviceid,drivetype,providername,@{L='%free';E={$_.freespace / $_.size * 100}},volumename | ? '%free' -gt 50
deviceid : I:
drivetype : 3
providername :
%free : 88.1057259183556
volumename : 娱乐显示在 root\CIMv2 的命名空间下的所有的 WMI 类列表,仅显示以“win32”开头的 WMI 类名称。
1 16:47 [SLC-PC-HOME] D:\ > gwmi -Namespace root\cimv2 -List win32*
1 16:47 [SLC-PC-HOME] D:\ > Get-Cimclass win32* -Namespace root\cimv2在列表中显示所有 StartMode 是 Auto 且 State 属性不是 Running 的 Win32_Service 实例。
1 16:49 [SLC-PC-HOME] D:\ > gwmi win32_service | ? { $_.state -notmatch 'running' -and $_.startmode -match 'auto' }找到一个能发送 E-mail 信息的命令。这个命令的必要参数都是什么?
1
2
3
4
5 Send-MailMessage [-To] <System.String[]> [-Subject] <System.String> [[-Body] <System.String>] [[-SmtpServer] <Syste
m.String>] [-Attachments <System.String[]>] [-Bcc <System.String[]>] [-BodyAsHtml] [-Cc <System.String[]>] [-Creden
tial <System.Management.Automation.PSCredential>] [-DeliveryNotificationOption {None | OnSuccess | OnFailure | Dela
y | Never}] [-Encoding {ASCII | BigEndianUnicode | Default | OEM | Unicode | UTF7 | UTF8 | UTF32}] -From <System.St
ring> [-Port <System.Int32>] [-Priority {Normal | High | Low}] [-UseSsl] [<CommonParameters>]to, subject -from
运行一个显示 C:\下目录权限的命令。你会发现以列表的形式显示输出结果会更易于阅读。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 16:51 [SLC-PC-HOME] D:\ > get-acl c:\ | fl *
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\
PSParentPath :
PSChildName : C:\
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
CentralAccessPolicyId :
CentralAccessPolicyName :
Path : Microsoft.PowerShell.Core\FileSystem::C:\
Owner : NT SERVICE\TrustedInstaller
Group : NT SERVICE\TrustedInstaller
Access : {System.Security.AccessControl.FileSystemAccessRule, System.Security.AccessControl.FileSystem
AccessRule, System.Security.AccessControl.FileSystemAccessRule, System.Security.AccessControl
.FileSystemAccessRule...}
Sddl : O:S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464G:S-1-5-80-956008885-34185226
49-1831038044-1853292631-2271478464D:PAI(A;;LC;;;AU)(A;OICIIO;SDGXGWGR;;;AU)(A;OICI;FA;;;SY)(
A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;BU)
AccessToString : NT AUTHORITY\Authenticated Users Allow AppendData
NT AUTHORITY\Authenticated Users Allow -536805376
NT AUTHORITY\SYSTEM Allow FullControl
BUILTIN\Administrators Allow FullControl
BUILTIN\Users Allow ReadAndExecute, Synchronize
AuditToString :
AccessRightType : System.Security.AccessControl.FileSystemRights
AccessRuleType : System.Security.AccessControl.FileSystemAccessRule
AuditRuleType : System.Security.AccessControl.FileSystemAuditRule
AreAccessRulesProtected : True
AreAuditRulesProtected : False
AreAccessRulesCanonical : True
AreAuditRulesCanonical : True运行一个可以显示所有 C:\Users 下子文件夹权限的目录,仅包含直接子文件夹,不需要去递归所有的文件和文件夹。你只需要把一个命令的结果通过管道传输给另一个命令即可实现。然后重复该过程从而显示隐藏文件夹的权限目录。
1
2
3
4
5
6
7
8
9
10 16:52 [SLC-PC-HOME] D:\ > ls c:\users | get-acl
目录: C:\users
Path Owner Access
---- ----- ------
21923 BUILTIN\Administrators NT AUTHORITY\SYSTEM Allow FullControl...
Public NT AUTHORITY\SYSTEM CREATOR OWNER Allow FullControl...
1
2
3
4
5
6
7
8
9
10
11
12 17:28 [SLC-PC-HOME] D:\ > ls c:\users -Hidden | get-acl
目录: C:\users
Path Owner Access
---- ----- ------
All Users NT AUTHORITY\SYSTEM Everyone Deny ReadData...
Default NT AUTHORITY\SYSTEM Everyone Allow -1610612736...
Default User NT AUTHORITY\SYSTEM Everyone Deny ReadData...
desktop.ini BUILTIN\Administrators NT AUTHORITY\SYSTEM Allow FullControl...找到一个可以使用其他凭据而不是当前登录用户的凭据启动记事本的命令。任务 12
1
2
3
4
5 start-job
invoke-command
start-process运行一个命令,使 Shell 暂停或者闲置 10 秒。
1
2
3
4
5
6
7 16:52 [SLC-PC-HOME] D:\ > Start-Sleep 10
16:53 [SLC-PC-HOME] D:\ > Get-Alias -Definition start-sleep
CommandType Name Version Source
----------- ---- ------- ------
Alias sleep -> Start-Sleep你能找到解释 Shell 的各种运算符的帮助文件吗?
1
2
3 help operat
about_Logical_Operators HelpFile
about_Operators HelpFile使用 Get-Winevent,显示所有拥有条目的日志文件列表,并根据所包含日志文件的多少,按照降序排序输出结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 16:55 [SLC-PC-HOME] D:\ > Get-WinEvent -ListLog * | sort -desc RecordCount | select -f 10
LogMode MaximumSizeInBytes RecordCount LogName
------- ------------------ ----------- -------
Circular 20971520 30330 System
Circular 20971520 29329 Security
Circular 20000000 27051 Microsoft-Windows-Store/Operational
Circular 134217728 26802 Microsoft-Windows-Hyper-V-Compute-Operational
Circular 20971520 24578 Application
Circular 33554432 21624 Microsoft-Windows-Ntfs/Operational
Circular 8388608 18799 Microsoft-Windows-SmbClient/Connectivity
Circular 15728640 13410 Windows PowerShell
Circular 15728640 12250 Microsoft-Windows-PowerShell/Operational
Circular 4194304 9733 Microsoft-Windows-GroupPolicy/Operational了解该命令的默认输出结果。现在修改这个命令,使得输出结果在表格中显示。表格内容应该包含每个处理器的核心数、制造商和名称,也包括一个列名为“MaxSpeed”的列,该列表示处理器的最大时钟频率。
1
2
3
4
5
6 16:56 [SLC-PC-HOME] D:\ > Get-CimInstance Win32_Processor | ft NumberOfLogicalProcessors,Caption,name,@{L='maxspeed'; E={$_.MaxClockSpeed}}
NumberOfLogicalProcessors Caption name maxspeed
------------------------- ------- ---- --------
8 Intel64 Family 6 Model 60 Stepping 3 Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz 3301然后将该输出结果通过管道传递给 Get-Member 命令。现在,将该命令修改为仅显示在峰值情况下工作集超过“100000”的进程,仅显示进程名称、路径以及所有峰值属性。
1 Get-CimInstance Win32_Process | ? PeakWorkingSetSize -gt 100KB | ft ProcessName,Path,Peak*使用 Find-Module 命令发现带有 Network 标签的包。显示模块的名称、版本以及描述。
1 16:59 [SLC-PC-HOME] D:\ > Find-Module -Tag network | fl name,version,description
1 17:31 [SLC-PC-HOME] D:\ > Find-Module -Tag network | sort name | fl name,version,description复习 3
你会使用哪一个命令启动一个完全在你本地计算机运行的作业?
1 start-job你会使用哪一个命令启动一个作业的内容被远程计算机处理但由本地计算机调整的作业?
1 invoke-command${computer name}是一个合法的变量名称吗?
你会如何展示由当前 Shell 定义的变量列表?
1 17:01 [SLC-PC-HOME] D:\ > ls env:哪一个命令可以被用来提示用户输入?
1 read-host哪一个命令可以被通常用于生成显示在屏幕上的输出结果,但也可以被重新转为多种其他输出格式?
1 write-output创建一个处于运行状态的进程列表,该列表应该仅包含进程名称、ID、VM 和 PM。VM 与 PM 的值显示单位为 MB。把这个列表放入一个名称为 C:\Procs.html 的 HTML 文件中。确保 HTML 文件有一个标题为“Current Processes”。在浏览器中显示文件,并把标题显示在浏览器窗口的标题栏中。为 VM 属性计算 MB 并以整数显示结果的公式是类似$_.VM / 1MB as [int]。然后尝试将该 HTML 文件重新导入回 PowerShell。(复习)
1
2
3
4
5
6 ps | fl processname,id,@{l='vm'; e={$_.vm / 1MB}},@{l='pm'; e={$_.pm / 1MB}}
ps | select processname,id,@{l='vm'; e={$_.vm / 1MB -as [int]}},@{l='pm'; e={$_.pm / 1MB -as [int]}} | ConvertTo-Html -Title 'current processes' > ps.html使用 WMI 或 CIM 命令创建一个包含所有你的电脑上的服务的制表符分隔文件,命名为 C:\Services.tdf。”`t”(在双引号之间的反撇号 t)是 PowerShell 为水平制表符使用的转义字符。文件中仅包含服务的名称、显示名称和状态。
1 17:07 [SLC-PC-HOME] D:\ > gwmi win32_service | select name,displayname,status | convertto-csv -Delimiter `t > services.tdf首先,提示用户输入一个计算机名称并将结果存入一个变量。然后使用 CIM 命令查询一个计算机(使用变量)的操作系统名称、版本号、上次启动时间以及运行时间。在结果中包含计算机名称。你可以通过当前时间与上次启动时间计算出计算机的运行时间。
1
2
3
4
5
6
7
8
9
10
11 17:08 [SLC-PC-HOME] D:\ > $computer=$(read-host 'enter a compueter name')
enter a compueter name: localhost
17:09 [SLC-PC-HOME] D:\ > Get-CimInstance Win32_OperatingSystem -ComputerName $computer
17:17 [SLC-PC-HOME] D:\ > Get-CimInstance Win32_OperatingSystem -ComputerName $computer | ft name,version,LastBootUpTime,@{L='runtime(s)'; E={((get-date) - (get-date $_.LastBootUpTime)).Totalseconds -as [int]}},CSNAME
name version LastBootUpTime runtime(s) CSNAME
---- ------- -------------- ---------- ------
Microsoft Windows 11 专业版|C:\Windows|\Device\Harddisk0\Partition3 10.0.22000 2022-07-29 20:03:30 76426 SLC-PC...
1
2
3
4
5 17:36 [SLC-PC-HOME] D:\ > Get-CimInstance Win32_OperatingSystem -ComputerName $computer | ft Caption,version,LastBootUpTime,@{L='uptime'; E={(get-date) - $_.LastBootUpTime }},CSNAME
Caption version LastBootUpTime uptime CSNAME
------- ------- -------------- ------ ------
Microsoft Windows 11 专业版 10.0.22000 2022-07-29 20:03:30 21:33:23.9843026 SLC-PC-HOME将任务 3 的命令转换为参数化脚本。显而易见,computername 是一个不错的候选参数。包含一个名称为 CN 的别名,并将其设置为必要参数。输出结果应该显示和任务 3 相同的属性,但你或许希望操作系统名称的显示名称更加优雅
1
2
3
4
5
6
7
8
9
10
11 [cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[alias('cn')]
$computer = 'localhost'
)
write-verbose "获取计算机 $computer"
Get-CimInstance Win32_OperatingSystem -ComputerName $computer | ft name,version,LastBootUpTime,@{L='runtime(s)'; E={((get-date) - (get-date $_.LastBootUpTime)).Totalseconds -as [int]}},CSNAME
write-verobse "done"使用 WMI 的 Win32_Product 类找出所有已安装的产品。该命令可能会花费较长时间,因此请将其设置为一个后台作业。当该命令完成后,获取结果,并在 gridview 中显示产品名称、公司、版本号、安装日期以及安装区域。
1
2
3
4
5
6
7
8 17:21 [SLC-PC-HOME] D:\ > Get-WmiObject win32_product -AsJob
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 WmiJob Running True localhost Get-WmiObject win32_pr...
17:24 [SLC-PC-HOME] D:\ > Receive-Job 1 -Keep | select name,vendor,version,InstallDate,InstallLocation | Out-GridView结语