本系列是一个重新学习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:WINDOWSsystem32ipconfig.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:WINDOWSsystem32ipconfig.exe
Extension : .exe
Definition : C:WINDOWSsystem32ipconfig.exe
Source : C:WINDOWSsystem32ipconfig.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:WINDOWSsystem32 asklist.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执行命令和脚本的内部机制。这个对象主要包含两个属性:InvokeCommand
和 SessionState
.
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 |