PowerShell之发现命令
从用户的角度来看,在Powershell控制台上输入一条命令,然后直接回车执行,是一件简单的事情,事实上Powershell在后台做了很多事情,其中第一步,就是查看用户输入的命令是否可用,这个步骤也被称作自动化发现命令。使用Get-Command 命令可以查看当前作用域支持的所有命令。如果你想查看关于 LS 命令的信息,请把它传递给Get-Command。
PS C:> Get-command LS CommandType Name Definition ----------- ---- ---------- Alias ls Get-ChildItem
如果你想查看更加详细的信息可以使用:
HelpUri : http://go.microsoft.com/fwlink/?LinkID=113308 ResolvedCommandName : Get-ChildItem ReferencedCommand : Get-ChildItem ResolvedCommand : Get-ChildItem Definition : Get-ChildItem Options : AllScope Description : OutputType : {System.IO.FileInfo, System.IO.DirectoryInfo, System.String} Name : ls CommandType : Alias Visibility : Public ModuleName : Module : Parameters : {[Path, System.Management.Automation.ParameterMetadata], [Literal [Include, System.Management.Automation.ParameterMetadata]...} ParameterSets
如果你想查看命令IPConfig的命令信息,可以使用:
PS C:> get-command ipconfig CommandType Name Definition ----------- ---- ---------- Application ipconfig.exe C:windowsSYSTEM32ipconfig.exe 事实上,Get-Command 返回的是一个对象CommandInfo,ApplicationInfo,FunctionInfo,或者CmdletInfo; PS C:> $info = Get-Command ping PS C:> $info.GetType().fullname System.Management.Automation.ApplicationInfo PS C:> $info = Get-Command ls PS C:> $info.GetType().fullname System.Management.Automation.AliasInfo PS C:> $info = Get-Command Get-Command PS C:> $info.GetType().fullname System.Management.Automation.CmdletInfo PS C:> $info=Get-Command more | select -First 1 PS C:> $info.GetType().fullname System.Management.Automation.FunctionInfo
如果一条命令可能指向两个实体,get-command也会返回,例如more。
PS C:> Get-Command more CommandType Name Definition ----------- ---- ---------- Function more param([string[]]$paths)... Application more.com C:windowsSYSTEM32more.com
这两条命令,前者是Powershell的自定义函数,后者是扩展的Application命令。细心的读者可能会提问,这两个会不会发生冲突。当然不会,默认会调用第一个,是不是仅仅因为它排在第一个,不是,而是在Powershell中有一个机制,就是函数永远处在最高的优先级。不信,看看下面的例子,通过函数可以重写ipconfig ,一旦删除该函数,原始的ipconfig才会重新登上历史的舞台:
PS C:> function Get-Command () {} PS C:> Get-Command PS C:> del Function:Get-Command PS C:> function ipconfig(){} PS C:> ipconfig PS C:> del Function:ipconfig PS C:> ipconfnig PS C:> ipconfig.exe Windows IP 配置 无线局域网适配器 无线网络连接 3: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 无线网络连接 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开 连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 无线网络连接: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::9c4:7e81:82a2:1a3e%15 IPv4 地址 . . . . . . . . . . . . : 192.168.1.100 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.1.1
PowerShell之调用操作符
调用操作符“&”虽然简短,但是给我们执行Powershell命令提供了很大的方便。如果你之前将Powershell命令存储在了一个字符串中,或者一个变量中。此时,调用操作符就可以将字符串直接解释成命令并执行,如果在Powershell控制台中,你只须要输入即可。具体,如下:
#将命令存储在变量中: $command = "Dir $env:windir" # 如果直接输出变量,字符串原样输出。 $command #Dir #如果使用调用操作符"&" & $command 目录: E: Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2012/5/9 0:42 Blog d---- 2012/5/23 21:45 DooMLoRD_v3_ROOT-zergRush-busybox- su d---- 2012/6/1 20:21 Haitch-TFS d---- 2012/3/25 15:32 KillProcess
但是调用操作符不能接受全部的Powershell脚本或命令,只能接受单个的一条命令,例如使用:
$command = "Dir $env:windir" & $command 就会报错 无法将“Dir C:windows”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后重试。 所在位置 E:MyScript.ps1:6 字符: 2 + & <<<< $command + CategoryInfo : ObjectNotFound: (Dir C:windows:String) [], Comm andNotFoundException + FullyQualifiedErrorId : CommandNotFoundException $command = get-command dir & $command = $env:windir
为什么会这样呢?追根溯源,Powershell中的调用符,首先会使用get-command去发现命令是否可用,而get-command的确只支持单独的一条命令,不支持命令串或者脚本串。 调用操作符执行CommandInfo对象 调用操作符初始化时会将指定的文本传递给get-command,然后有get-command去检索命令,事实上,调用操作符甚至可以直接执行一个CommandInfo对象,绕过自身的内部get-command,例如:
PS E:> $command=Get-Command tasklist PS E:> $command CommandType Name ----------- ---- Application tasklist.exe PS E:> & $command 映像名称 PID 会话名 会话# 内存使用 ========================= ======== ================ =========== ============ System Idle Process 0 Services 0 24 K System 4 Services 0 560 K smss.exe 356 Services 0 756 K csrss.exe 564 Services 0 3,744 K csrss.exe 680 Console 1 71,512 K wininit.exe 688 Services 0 2,904 K services.exe 740 Services 0 8,552 K lsass.exe 764 Services 0 10,676 K lsm.exe 776 Services 0 3,240 K
但是可能存在别名,命令,函数的的名称一样,那Powershell会不会纠结到底执行哪个呢?当然不会,因为它们之间是有一个优先级的。从高到底,依次为:
Alias(1)
Function(2)
Filter(2)
Cmdlet(3)
Application(4)
ExternalScript(5)
Script (-)
下面举个例子:
PS E:> function Ping(){"我是Ping函数"} PS E:> Set-Alias -Name Ping -Value echo CommandType Name Definition ----------- ---- ---------- Alias Ping echo Function Ping param()... Application PING.EXE C:windowsSYSTEM32PING.EXE PS E:> ping "测试我的Ping,估计执行的别名echo" 测试我的Ping,估计执行的别名echo PS E:> del Alias:Ping PS E:> ping ; #别名已经被删除,估计执行的是函数PING 我是Ping函数 PS E:> del Function:Ping PS E:> ping baidu.com ; #函数已经被删除,估计执行的是函数ping 程序 正在 Ping baidu.com [220.181.111.85] 具有 32 字节的数据: 请求超时。 请求超时。 请求超时。 请求超时。 220.181.111.85 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
那怎样突破优先级的限制执行指定的命令呢?方法都是大同小异,通过特定信息过滤到指定的CommandInfo对象,然后直接使用我们本篇的调用操作符。例如当存在如下的命令Ping命令
CommandType Name ----------- ---- Alias Ping Function Ping Application PING.EXE 我想调用第三个,可以使用: PS E:> $command=Get-Command -Name ping | where {$_.CommandType -eq "Application" } PS E:> & $command 用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS] [-r count] [-s count] [[-j host-list] | [-k host-list]] [-w timeout] [-R] [-S srcaddr] [-4] [-6] target_name 选项: -t Ping 指定的主机,直到停止。 若要查看统计信息并继续操作 - 请键入 Control-Break; 若要停止 - 请键入 Control-C。 -a 将地址解析成主机名。 -n count 要发送的回显请求数。 -l size 发送缓冲区大小。 -f 在数据包中设置“不分段”标志(仅适用于 IPv4)。 -i TTL 生存时间。 -v TOS 服务类型(仅适用于 IPv4。该设置已不赞成使用,且 对 IP 标头中的服务字段类型没有任何影响)。 -r count 记录计数跃点的路由(仅适用于 IPv4)。 -s count 计数跃点的时间戳(仅适用于 IPv4)。 -j host-list 与主机列表一起的松散源路由(仅适用于 IPv4)。 -k host-list 与主机列表一起的严格源路由(仅适用于 IPv4)。 -w timeout 等待每次回复的超时时间(毫秒)。 -R 同样使用路由标头测试反向路由(仅适用于 IPv6)。 -S srcaddr 要使用的源地址。 -4 强制使用 IPv4。 -6 强制使用 IPv6。 #或者& (Get-Command -Name ping)[2]
PowerShell之使用语句块
脚本块是一种特殊的命令模式。一个脚本块可以包含许多的 Powershell命令和语句。它通常使用大括号定义。最小最短的脚本块,可能就是一对大括号,中间什么也没有。可以使用之前的调用操作符“&”执行脚本块:
PS E:> & {"当前时间:" + (get-date) } 当前时间:08/08/2012 22:30:24
将命令行作为整体执行
PS E:> & {$files=ls;Write-Host "文件数:" $files.Count }
文件数: 29
另外还有一条Powershell命令集,Invoke-Expression,这条命令的逻辑就是将一条字符串传递给调用操作符。例如:
PS E:> Invoke-Expression 'Get-Process | Where-Object { $_.Name -like "e*"}' Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 332 29 12280 24264 154 1.40 3236 egui 386 32 78624 85508 183 1884 ekrn 284 22 8980 17048 99 1920 EvtEng 1000 89 55520 83280 355 23.24 2848 explorer
这里有一点需要注意,在传递给invoke-expression的字符串使用了单引号,单引号可以防止变量被替换。如果上面的命令使用了双引号,会先去解释$_.name,但是当前作用域中,$_.Name 为null,所以结果不是期望的。
管道中的foreach-object本身后面也会带语句块,针对数组中的每一个元素分别传递给语句块处理。 例如:
Get-Process | ForEach-Object { $_.name }
在条件语句中,如果条件满足,做一件事,条件不满足做另外一件事,这一件事或者另外一件事本身应当是一个整体,尽管本身可能包含了许多命令和语句。所以会把它们放在一个语句块中。
在循环语句中,如果条件满足循环做一件事,这一件事本身,也是一个整体。 这里就不举例了。
为什么把函数和语句块归结在一起呢?请看下面的例子。
#定义一个函数 Function SayHello([string]$people="everyone") { write-host "Hello, $people ” } #通过函数调用 SayHello "Mosser" Hello, Mosser #通过语句块调用 $scriptblocks = { param([string]$people="everyone") write-host "Hello, $people ” } & $scriptblocks Hello, everyone
既然函数只是被命令的语句块,那是不是也可以通过语句块就能实现函数的特性呢?接下来就验证一下吧。
在函数中可以传递参数:
Function SayHello([string]$people="everyone") { write-host "Hello, $people ” }
能否在语句块中也传递参数?
& { param($people="everyone") write-host "Hello, $people ” } "Mosser" Hello, Mosser
定义和传递参数一次性完成,有点匿名函数的味道。
之前讲过定义函数也可以按照Begin, Process, End的结构,这样尤其可以实时处理管道数据。那能不能也直接通过语句块定义呢?
get-process | select -last 5 | & { begin { "开始准备环境" } process { $_.Name } end { "开始清理环境" } } #输出结果为: 开始准备环境 wlcommsvc WLIDSVC WLIDSVCM WmiPrvSE XDict 开始清理环境
函数中的所有变量都是内置的,属于函数定义域。除非你指定给一个全局变量赋值。首先通过函数实现:
function Test { $value1 = 10 $global:value2 = 20 } Test $value1 结果为空: $value2 结果为空: 20
那语句块也支持吗?例如:
& { $value1 = 10; $global:value2 = 20 } $value1 结果为空: $value2 结果为空: 20
PowerShell之执行上下文
Powershell 提供了一个非常特别的自动化变量,$ExecutionContext。这个变量可能会很少碰到,但是理解它的机制,有助于我们理解Powershell执行命令和脚本的内部机制。这个对象主要包含两个属性:InvokeCommand 和 SessionState.
PS E:> $ExecutionContext Host : System.Management.Automation.Internal.Host.InternalHost Events : System.Management.Automation.PSLocalEventManager InvokeProvider : System.Management.Automation.ProviderIntrinsics SessionState : System.Management.Automation.SessionState InvokeCommand : System.Management.Automation.CommandInvocationIntrinsics
到目前为止,我们在Powershell控制台中遇到三个比较特殊的字符,字符串标识双引号,调用操作符 &,和脚本块标识花括号。
特殊字符 | 定义 | 内部方法 |
“ | 处理字符串中的变量 | ExpandString() |
& | 执行命令集 | InvokeScript() |
{} | 创建一个新的代码块 | NewScriptBlock() |
每当你在Powershell的字符串中放置一个变量,Powershell解释器会自动处理该变量,并将变量替换成变量本身的值或者内容。
1
2
3
4
|
$site
= '飞苔博客'
# 双引号中的变量会被自动解析成变量的值:
$text
= "我的个人网站 $site"
$text
|
输出:
我的个人网站 飞苔博客
既然双引号的机制是ExpandString()方法,那么也可以自己调用该方法
1
2
3
4
5
6
|
$site
= '飞苔博客'
# 双引号中的变量会被自动解析成变量的值:
$text
= '我的个人网站 $site'
$text
#通过ExpandString()自动处理字符串中的变量
$executioncontext .InvokeCommand.ExpandString( $text )
|
输出:
我的个人网站 $site
我的个人网站 飞苔博客
如果将Powershell代码放置在花括号中,这样既可以使用调用操作符&执行脚本,也可以将脚本块赋值给一个函数,因为之前的文章中说过,函数是一个命令的脚本块.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 创建新的脚本块
$block
= {
$write = Get-Process
WindowsLiveWriter
"$($write.Name) 占用内存: $($write.WorkingSet/1mb) MB"
}
$block .GetType().Name
& $block
# 使用NewScriptBlock方法创建脚本块:
$blockStr = '$write=Get-Process WindowsLiveWriter
"$($write.Name) 占用内存: $($write.WorkingSet/1mb) MB"'
$block
= $executioncontext .InvokeCommand.NewScriptBlock( $blockStr )
$block .GetType().Name
& $block
|
输出:
ScriptBlock
WindowsLiveWriter 占用内存: 150.734375 MB
ScriptBlock
WindowsLiveWriter 占用内存: 150.734375 MB
输入的命令行可以通过InvokeScript()脚本执行,也可以使用&执行,也可以使用Invoke-Expression命令执行
1
2
3
4
|
$cmd = '3*3*3.14'
& { 3*3*3.14}
$executioncontext .InvokeCommand.InvokeScript( $cmd )
Invoke-Expression
$cmd
|
输出:
28.26
28.26
28.26
SessionState是一个用来表现Powershell环境的对象,你同样可以通过自动化变量$ExecutionContext访问这些信息.
PS E:> $executioncontext.SessionState | Format-List * Drive : System.Management.Automation.DriveManagementIntrinsics Provider : System.Management.Automation.CmdletProviderManagementIntrinsics Path : System.Management.Automation.PathIntrinsics PSVariable : System.Management.Automation.PSVariableIntrinsics LanguageMode : FullLanguage UseFullLanguageModeInDebugger : False Scripts : {*} Applications : {*} Module : InvokeProvider : System.Management.Automation.ProviderIntrinsics InvokeCommand : System.Management.Automation.CommandInvocationIntrinsics
PSVariable,可以取出和更新Powershell中所有的变量.
1
2
3
4
5
6
7
8
|
$value
= "Test"
# Retrieve variable contents:
$executioncontext .SessionState.PSVariable.GetValue( "value" )
Test
# Modify variable contents:
$executioncontext .SessionState.PSVariable.Set( "value" , 100)
$value
100
|
输出:
Powershell博客 飞苔博客
Powershell博客 网站http://www.mossfly.com
查看当前驱动器信息
PS E:> $executioncontext.SessionState.Drive.Current Name Used (GB) Free (GB) Provider Root CurrentLocation ---- --------- --------- -------- ---- --------------- E 1.67 78.33 FileSystem E:
查看所有驱动器信息
PS E:> $executioncontext.SessionState.Drive.GetAll() | ft - Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- WSMan WSMan Alias Alias Env Environment C 44.48 35.52 FileSystem C: D 18.69 52.16 FileSystem D: E 1.67 78.33 FileSystem E: G 52.57 118.55 FileSystem G: I 27.31 21.52 FileSystem I: Function Function HKLM Registry HKEY_LOCAL_MACHINE HKCU Registry HKEY_CURRENT_USER Variable Variable cert Certificate F FileSystem F: H .67 FileSystem H:
如果你的只想关注特定的驱动器,可以使用下面的方法:
PS E:> $executioncontext.SessionState.Drive.GetAllForProvider("FileSystem") Name Used (GB) Free (GB) Provider Root CurrentLocation ---- --------- --------- -------- ---- --------------- C 44.48 35.52 FileSystem C: Usersbaozhen D 18.69 52.16 FileSystem D: E 1.67 78.33 FileSystem E: G 52.57 118.55 FileSystem G: I 27.31 21.52 FileSystem I: F FileSystem F: H .67 FileSystem H:
SessionState的Path包含几个特殊的方法,基本可以覆盖各种常用的路径操作了
方法 | 描述 | 对应的命令 |
CurrentLocation | 当前路径 | Get-Location |
PopLocation() | 获取存储的路径 | Pop-Location |
PushCurrentLocation() | 存储路径 | Push-Location |
SetLocation() | 定位路径 | Set-Location |
GetResolvedPSPathFromPSPath() | 相对路径转换成绝对路径 | Resolve-Location |