PowerShell笔记 - 11.命令和代码块

本系列是一个重新学习PowerShell的笔记,内容引用自PowerShell中文博客

发现命令

从用户的角度来看,在Powershell控制台上输入一条命令,然后直接回车执行,是一件简单的事情,事实上Powershell在后台做了很多事情,其中第一步,就是查看用户输入的命令是否可用,这个步骤也被称作自动化发现命令。使用Get-Command 命令可以查看当前作用域支持的所有命令。如果你想查看关于 LS 命令的信息,请把它传递给Get-Command

PS C:\PowerShell> Get-Command Ls                                                                                        
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           ls -> Get-ChildItem

#查询详细信息
PS C:\PowerShell> Get-Command Ls |Format-List *                                                                         

HelpUri             : https://go.microsoft.com/fwlink/?LinkID=113308
ResolvedCommandName : Get-ChildItem
DisplayName         : ls -> Get-ChildItem
ReferencedCommand   : Get-ChildItem
ResolvedCommand     : Get-ChildItem
Definition          : Get-ChildItem
Options             : AllScope
Description         :
OutputType          : {System.IO.FileInfo, System.IO.DirectoryInfo}
Name                : ls
CommandType         : Alias
Source              :
Version             :
Visibility          : Public
ModuleName          :
Module              :
RemotingCapability  : PowerShell
Parameters          : {[Path, System.Management.Automation.ParameterMetadata], [LiteralPath, System.Management.Automati
                      on.ParameterMetadata], [Filter, System.Management.Automation.ParameterMetadata], [Include, System
                      .Management.Automation.ParameterMetadata]...}
ParameterSets       :

如果你想查看命令IPConfig的命令信息,可以使用:

PS C:\PowerShell> Get-Command ipconfig | Format-List *                                                                  

HelpUri            :
FileVersionInfo    : File:             C:\WINDOWS\system32\ipconfig.exe
                     InternalName:     ipconfig.exe
                     OriginalFilename: ipconfig.exe.mui
                     FileVersion:      10.0.18362.1 (WinBuild.160101.0800)
                     FileDescription:  IP Configuration Utility
                     Product:          Microsoft® Windows® Operating System
                     ProductVersion:   10.0.18362.1
                     Debug:            False
                     Patched:          False
                     PreRelease:       False
                     PrivateBuild:     False
                     SpecialBuild:     False
                     Language:         English (United States)

Path               : C:\WINDOWS\system32\ipconfig.exe
Extension          : .exe
Definition         : C:\WINDOWS\system32\ipconfig.exe
Source             : C:\WINDOWS\system32\ipconfig.exe
Version            : 10.0.18362.1
Visibility         : Public
OutputType         : {System.String}
Name               : ipconfig.exe
CommandType        : Application
ModuleName         :
Module             :
RemotingCapability : PowerShell
Parameters         :
ParameterSets      :


事实上,Get-Command 返回的是一个对象CommandInfo,ApplicationInfo,FunctionInfo,或者CmdletInfo;

 $info = Get-Command ping
 $info.GetType().FullName
 $info = Get-Command ls
 $info.GetType().FullName
 $info = Get-Command Get-Command
 $info.GetType().FullName
 $info=Get-Command more | Select-Object -First 1
 $info.GetType().FullName

PS C:\PowerShell> test.ps1                                                                            System.Management.Automation.ApplicationInfo
System.Management.Automation.AliasInfo
System.Management.Automation.CmdletInfo
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才会重新登上历史的舞台:


function ipconfig() {"自定义Ipconfig Function"}
ipconfig
del Function:ipconfig
ipconfig

PS C:\PowerShell> test.ps1                                                                            自定义Ipconfig Function

Windows IP Configuration

Ethernet adapter Ethernet:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :
.
.
.

调用操作符

调用操作符“&”虽然简短,但是给我们执行Powershell命令提供了很大的方便。如果你之前将Powershell命令存储在了一个字符串中,或者一个变量中。此时,调用操作符就可以将字符串直接解释成命令并执行,如果在Powershell控制台中,你只须要输入即可。具体,如下:

PS C:\PowerShell> $command = "ls"                                                                                       PS C:\PowerShell> $command                                                                                              ls
PS C:\PowerShell> &$command                                                                                             

    Directory: C:\PowerShell


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2021/9/16     13:55            812 Error.txt
-a----        2021/9/15     15:45            397 pipeline.ps1
-a----        2021/9/15     10:46              0 test.exe
-a----        2021/9/15     14:58             36 test.ps1
-a----        2021/9/14     13:51             58 test.txt

调用操作符只能接受单个命令
调用操作符不能接受全部的Powershell脚本或命令,只能接受单个的一条命令,例如使用:

PS C:\PowerShell> $command = "dir $env:windir"                                                                          PS C:\PowerShell> $command                                                                                              dir C:\WINDOWS
PS C:\PowerShell> &$command                                                                                             & : The term 'dir C:\WINDOWS' is not recognized as the name of a cmdlet, function, script file, or operable program. Ch
eck the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:2
+ &$command
+  ~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (dir C:\WINDOWS:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

为什么会这样呢?
追根溯源,Powershell中的调用符,首先会使用get-command去发现命令是否可用,而get-command的确只支持单独的一条命令,不支持命令串或者脚本串。
调用操作符执行CommandInfo对象调用操作符初始化时会将指定的文本传递给get-command,然后有get-command去检索命令,事实上,调用操作符甚至可以直接执行一个CommandInfo对象,绕过自身的内部get-command,例如:

PS C:\PowerShell> $command=Get-Command tasklist                                                                         PS C:\PowerShell> $command                                                                                              
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Application     tasklist.exe                                       10.0.18... C:\WINDOWS\system32\tasklist.exe

通过命令名称唯一标识一条命令

但是可能存在别名,命令,函数的的名称一样,那Powershell会不会纠结到底执行哪个呢?当然不会,因为它们之间是有一个优先级的。从高到底,依次为:
Alias(1)
Function(2)
Filter(2)
Cmdlet(3)
Application(4)
ExternalScript(5)
Script (-)

function Test() {"我是个别名"}
function Ping() { "我是Ping函数" }
Set-Alias -Name Ping -Value Test
ping
del Alias:Ping
ping
del Function:Ping
ping baidu.com 

PS C:\PowerShell> test.ps1
我是个别名
我是Ping函数

Pinging baidu.com [220.181.38.251] with 32 bytes of data:
Control-C

语句块

脚本块是一种特殊的命令模式。一个脚本块可以包含许多的 Powershell命令和语句。它通常使用大括号定义。最小最短的脚本块,可能就是一对大括号,中间什么也没有。可以使用之前的调用操作符“&”执行脚本块:

PS C:\PowerShell>  & {"当前时间:" + (get-date) }
当前时间:09/16/2021 16:54:21

将命令行作为整体执行

可能你已经意识到,在Powershell中调用操作符不但可以执行一条单独的命令,还可以执行”命令行”.最方便的方式就是将你的命令行放在一个语句块中,作为整体。
在前文中说过,调用操作符只能执行一条命令,但是借助语句块的这把利器,可以让调用操作符执行,多条Powershell命令,例如:

PS C:\PowerShell> & {$files=ls;Write-Host "文件数:" $files.Count }
文件数: 5

执行表达式

另外还有一条Powershell命令集,Invoke-Expression,这条命令的逻辑就是将一条字符串传递给调用操作符。例如:

PS C:\PowerShell> Invoke-Expression 'Get-Process | Where-Object { $_.Name -like "Micro*"}'                              
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    967      61    40680       5380       3.64  14412   1 Microsoft.Notes
    541      29    43884      29996       7.59  16712   1 Microsoft.ServiceHub.Controller
    406      27    25784      14460              5712   0 MicrosoftSearchInBing
    374      51    39812      22020       4.44  20932   1 MicrosoftSqlToolsServiceLayer

这里有一点需要注意,在传递给invoke-expression的字符串使用了单引号,单引号可以防止变量被替换。如果上面的命令使用了双引号,会先去解释$_.name,但是当前作用域中,$_.Name$null,所以结果不是期望的。

函数本身是一个已命名的语句块

为什么把函数和语句块归结在一起呢?请看下面的例子。

Function SayHello([string]$people = "everyone") {
	Write-Host "Hello, $people ”
}
#赋值Funciton引用
$func = $Function:SayHello
Write-Host "--------------------"
#查看语句
$func
Write-Host "--------------------"
#通过操作符调用
&$func "Hua Hua"
Write-Host "--------------------"
#创建语句块
$func = { param([string]$people = "everyone") write-host "Hello, $people ” }
Write-Host "--------------------"
#查看语句块
$func
Write-Host "--------------------"
#执行语句块
&$func
#向语句块传递参数
Write-Host "--------------------"
&$func "Hua Hua"
#直接向语句块传递参数
& { param([string]$people = "everyone") write-host "Hello, $people ” } "Hua Hua"

PS C:\PowerShell> test.ps1                                                                            --------------------
param([string]$people = "everyone")

        Write-Host "Hello, $people ”

--------------------
Hello, Hua Hua
--------------------
--------------------
 param([string]$people = "everyone") write-host "Hello, $people ”
--------------------
Hello, everyone
--------------------
Hello, Hua Hua

上述最后一条语句定义和传递参数一次性完成,有点匿名函数的味道。
之前讲过定义函数也可以按照Begin, Process, End的结构,这样尤其可以实时处理管道数据。那能不能也直接通过语句块定义呢?

Get-Service | Select-Object -Last 5 | & {
	
	begin {
		Write-Host "初始化环境"
	}
	
	process {
		$_.Name
	}
	
	end {
		Write-Host "清理环境"
	}
}

PS C:\PowerShell> test.ps1                                                                            初始化环境
XblGameSave
XboxGipSvc
XboxNetApiSvc
ZeroConfigService
ZoomCptService
清理环境

函数中的所有变量都是内置的,属于函数定义域。除非你指定给一个全局变量赋值。首先通过函数实现:

function Test {
	$value1 = 10
	$global:value2 = 20
}
Test
$value1
$value2

PS C:\PowerShell> test.ps1                                                                            20

那语句块也支持吗?例如:

&{
	$value1 = 10
	$global:value2 = 20
}
$value1
$value2

PS C:\PowerShell> test.ps1                                                                            20

执行上下文

Powershell 提供了一个非常特别的自动化变量,$ExecutionContext。这个变量可能会很少碰到,但是理解它的机制,有助于我们理解Powershell执行命令和脚本的内部机制。这个对象主要包含两个属性:InvokeCommandSessionState.

PS C:\PowerShell> $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解释器会自动处理该变量,并将变量替换成变量本身的值或者内容。

PS C:\PowerShell> $site = '飞苔博客'                                                                                    
#双引号中的变量会被自动解析成变量的值:                                                              
PS C:\PowerShell> $text = "我的个人网站 $site"                                                                          PS C:\PowerShell> $text                                                                                                 我的个人网站 飞苔博客

既然双引号的机制是ExpandString()方法,那么也可以自己调用该方法

PS C:\PowerShell> $site = '飞苔博客'                                                                                    
#双引号中的变量会被自动解析成变量的值:                                                              
PS C:\PowerShell> $text = "我的个人网站 $site"                                                                          PS C:\PowerShell> $text                                                                                                 我的个人网站 飞苔博客
#通过ExpandString()自动处理字符串中的变量                                                            
PS C:\PowerShell> $executioncontext.InvokeCommand.ExpandString($text)                                                   我的个人网站 飞苔博客

创建脚本块

如果将Powershell代码放置在{}中,这样既可以使用调用操作符&执行脚本,也可以将脚本块赋值给一个函数,因为之前的文章中说过,函数是一个命令的脚本块.

# 创建新的脚本块
$block = {
	$write = Get-Process |Select-Object -First 1 
	"$($write.Name) 占用内存: $($write.WorkingSet/1mb) MB"
}

$block.GetType().Name

& $block
	 
# 使用NewScriptBlock方法创建脚本块:
$blockStr = '$write=Get-Process |Select-Object -First 1 
	"$($write.Name) 占用内存: $($write.WorkingSet/1mb) MB"'
$block = $executioncontext.InvokeCommand.NewScriptBlock($blockStr)
$block.GetType().Name
& $block

PS C:\PowerShell> test.ps1                                                                            ScriptBlock
1E.Client 占用内存: 6.48828125 MB
ScriptBlock
1E.Client 占用内存: 6.48828125 MB

执行命令行

输入的命令行可以通过InvokeScript()脚本执行,也可以使用&执行,也可以使用Invoke-Expression命令执行

PS C:\PowerShell> $cmd='3*3*3.14'                                                                                       PS C:\PowerShell> & { 3*3*3.14}                                                                                         28.26
PS C:\PowerShell> $executioncontext.InvokeCommand.InvokeScript($cmd)                                                    28.26
PS C:\PowerShell> Invoke-Expression $cmd                                                                                28.26

SessionState

SessionState是一个用来表现Powershell环境的对象,你同样可以通过自动化变量$ExecutionContext访问这些信息.

PS C:\PowerShell> $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中所有的变量.

PS C:\PowerShell> $value = "Test"                                                                                       PS C:\PowerShell> $executioncontext.SessionState.PSVariable.GetValue("value")                                           Test
PS C:\PowerShell> $executioncontext.SessionState.PSVariable.Set("value", 100)                                           PS C:\PowerShell> $value                                                                                                100
PS C:\PowerShell> $executioncontext.SessionState.PSVariable.GetType().FullName                                          System.Management.Automation.PSVariableIntrinsics

管理驱动器

查看当前驱动器信息

PS C:\PowerShell> $ExecutionContext.SessionState.Drive.Current                                                          
Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
C                 238.34        236.62 FileSystem    C:\                                                     PowerShell

查看所有驱动器信息

PS C:\PowerShell> $ExecutionContext.SessionState.Drive.GetAll() | Format-Table                                          
Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
HKLM                                   Registry      HKEY_LOCAL_MACHINE
HKCU                                   Registry      HKEY_CURRENT_USER
Alias                                  Alias
Env                                    Environment
C                 238.34        236.62 FileSystem    C:\                                                     PowerShell
Function                               Function
Variable                               Variable
Cert                                   Certificate   \
WSMan                                  WSMan

如果你的只想关注特定的驱动器,可以使用下面的方法:

PS C:\PowerShell> $ExecutionContext.SessionState.Drive.GetAllForProvider("FileSystem")                                  
Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
----           ---------     --------- --------      ----                                               ---------------
C                 238.34        236.61 FileSystem    C:\                                                     PowerShell

路径操作

SessionState的Path包含几个特殊的方法,基本可以覆盖各种常用的路径操作

方法 描述 对应的命令
CurrentLocation 当前路径 Get-Location
PopLocation() 获取存储的路径 Pop-Location
PushCurrentLocation() 存储路径 Push-Location
SetLocation() 定位路径 Set-Location
GetResolvedPSPathFromPSPath() 相对路径转换成绝对路径 Resolve-Location
posted @ 2021-09-17 14:05  门前有根大呲花  阅读(458)  评论(0编辑  收藏  举报