• 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语句块

管道中的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 管道语句块

之前讲过定义函数也可以按照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

InvokeCommand

到目前为止,我们在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

输入的命令行可以通过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

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