PowerShell脚本:快速入门

batch脚本在面对xml读写、字符串处理等需求时常常捉襟见肘,无法满足我们的需求,因此掌握powershell显得非常必要。

目录

0. 基本指令

0.1 获取帮助

下面这些命令可以帮助你了解命令作用及其用法。

  • get-command:列出所有可用命令
  • get-help command [-full] [-examples]:获取指定命令帮助信息,加full获取详细帮助
  • command -?:获取指定命令的简要介绍,相当于get-help command
  • $var.getType():获取对象的数据类型
  • $var.get-member: 获取对象的所有属性和方法,例如:Get-Process | Get-Member
  • $psversiontable:查看powershell版本信息
  • tab键的妙用:$var.然后按tab键会有对象方法提示,[type]::然后按tab键会有静态方法提示,输入cmdlet指令的一半按tab会自动将命令补充完整
  • cmdlet别名查询:ls alias: | Group-Object definition | sort -Descending Count

注意:

  • powershell与batch一样不区分大小写
  • 变量采用小驼峰命名
  • 函数采用大驼峰命名,一般格式为:动词-名词
  • powershell脚本文件后缀为ps1,文件名称按大驼峰命名或者下划线均可没有这方面的规范
  • powershell兼容cmd因此cmd的一些规则也适用于powershell
  • powershell一切皆对象,命令本身也是对象,比如:$m=或者<Get-Date>.day

0.2 模块包管理

  • find-module -name modulename:查找指定的模块包
  • install-module -name modulename -requiredversion x.y.z:安装指定名称和版本的模块
  • 创建自己的模块:PowerShell 创建迷你模块

0.3 执行策略

  • get-executionpolicy [-list]:获取当前(或全部)执行策略
  • [System.Enum]::GetNames([Microsoft.PowerShell.ExecutionPolicy]):获取系统全部执行策略
  • set-executionpolicy:设置执行策略
    默认执行策略是restricted,即非管理员用户系统不允许执行ps1脚本。我们可以通过一些方式绕开这个限制,参考:绕过PowerShell 执行策略的15种方法

总结有以下几种方式:

  1. 直接在powershell窗口输入脚本执行
  2. echo脚本内容然后pipe到powershell标准输入中
echo Write-Host "hello powershell" | powershell.exe -noprofile -
  1. 从文本读取脚本内容然后pipe到powershell标准输入中
# 原理实质上跟2是一样的
# 在powershell中可以使用get-content读取文件,在cmd中使用type
type D:\my_script.ps1 | powershell.exe -noprofile -
  1. 从URL下载脚本然后执行
# iex是Invoke-Expression命令的简写,下面会介绍
powershell -nop -c "iex(New-Object Net.WebClient).DownloadString('http://192.168.1.2/my_script.ps1)"
  1. 使用-Command命令参数
# 此方法不需要一个交互式的窗口
# 适用于简单脚本的执行,对于复杂脚本会发生解析错误
# 不会将内容写到磁盘中
# 可以将该命令写到一个bat文件中,然后放到启动目录中,来帮助提权
powershell -command "write-host 'hello powershell'"
  1. 使用-EncodedCommand命令参数
# 脚本内容为Unicode/base64 encode的字符串,可以弥补-command对于长脚本解析问题的短板
# 该工具套件还包括一个小的压缩方法来减少由于encode后字符串太长的情况
$bytes=[System.Text.Encoding]::Unicode.GetBytes("write-host 'hello powershell'")
$encodedCmd=[Convert]::ToBase64String($bytes)
powershell.exe -encodedCommand $encodedCmd
  1. 使用-ExecutionPolicy命令参数修改执行策略
    ByPass:什么都不做,什么警告也不提示
    Unrestricted:加载所有的配置文件和执行所有的脚本,如果你运行一个从网上下载的未签名的脚本,会给出权限提示
    Remote-Signed:允许执行远程已签名脚本,脚本签名方法详见签名指南
    AllSigned:允许本地和远程已签名脚本
powershell -executionpolicy bypass -file .\my_script.ps1
  1. 使用Set-ExecutionPolicy命令修改执行策略

注意:在 Windows Vista 和 Windows 的更高版本中,若要运行更改本地计算机(默认)的执行策略的命令,请使用“以管理员身份运行”选项启动 Windows PowerShell。

示例:指定临时执行策略生效范围为当前进程

# 先修改执行策略为Bypass,生效范围是当前会话进程
Set-ExecutionPolicy Bypass -Scope Process
# 再执行脚本
.\my_script.ps1
# 删除刚刚指定的执行策略
Set-ExecutionPolicy Undefined -Scope Process

示例:指定临时执行策略生效范围为当前用户

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
# 再执行脚本
.\my_script.ps1
  1. 使用Invoke-Command命令
    该方法最cool点在于对抗那些PowerShell remoting开启的Remote系统
# 执行简单脚本(脚本块可以换行)
invoke-command -scriptblock {write-host "hello powershell."}
# 从远程服务器抓取执行策略运用到本机
invoke-command -computername your-server-name -scriptblock {get-executionpolicy} | set-executionpolicy -force
  1. 使用Invoke-Expression命令
get-content .\my_script.ps1 | invoke-expression
# 上述命令可以缩写为下面
gc .\my_script.ps1 | iex

注意:

  1. 可以通过get-alias命令查看指令所有的别名,例如:get-alias -definition get-childitem
  2. 可以通过Set-Alias或New-Alias为命令设置新的别名
  1. 通过注册表设置执行策略为CurrentUser作用域
    运行regedit打开注册表页面:
    修改注册表

  2. 替换认证管理器忽略执行策略

# 定义替换AuthorizationManager的函数
function Disable-ExecutionPolicy{
	($ctx = $executioncontext.gettype().getfield("_context","nonpublic,instance").getvalue($executioncontext)).gettype().getfield("_authorizationManager","nonpublic,instance").setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager "Microsoft.PowerShell"))
}
# 调用该函数
Disable-ExecutionPolicy
# 执行脚本
.\my_script.ps1

0.4 风险控制

由于自动化操作往往伴随系统风险,因此powershell的默认设置会将自动化的操作归类为危险操作,在执行这些风险操作之前系统会要求用户进行确认。

-confirm参数

这一标准的设置被存储在全局变量$ConfirmPreference中。$ConfirmPreference可以对默认设置或者其它更严格的设置作出判断与回应。

# 查看当前的系统配置
PS> $ConfirmPreference
High
# c查看所有的可选项
PS> [ENUM]::GetNames($ConfirmPreference.getType())
None
Low
Medium
High

$ConfirmPreference的值设置为“None”时,Powershell就不会进行操作前询问确认,即使可能面临高风险的操作。
也可以在cmdlet指令后增加-confirm参数,为该指令增加用户确认步骤:

Stop-Process -Name *cm* -Confirm
 
确认
是否确实要执行此操作?
对目标“cmd (1012)”执行操作“Stop-Process”。
[Y](Y)  [A] 全是(A)  [N](N)  [L] 全否(L)  [S] 挂起(S)  [?] 帮助 (默认值为“Y”): ?
-whatif参数

通过-whatif参数你可以进行试运行许多cmdltes,Powershell不会执行任何对系统有影响的操作,只会告诉你如果没有模拟运行,可能产生什么影响和后果。
示例:

Stop-Process -Name *a* -WhatIf
WhatIf: 对目标“AcroRd32 (4544)”执行操作“Stop-Process”。

如果你想让自己的脚本和函数也支持模拟运行,只需要进行简单的整合。多增加一个switch参数:

function MapDrive([string]$driveletter, [string]$target, [switch]$whatif){
    If ($whatif) {
        Write-Host "WhatIf: creation of a network drive " + "with the letter ${driveletter}: at destination $target"
    } Else {
        New-PSDrive $driveletter FileSystem $target
    }
}
# 首先进行模拟运行:
MapDrive k 127.0.0.1c$ -whatif
WhatIf: creation of a network drive
with letter k: at destination 127.0.0.1c$
 
# 执行命令
MapDrive k 127.0.0.1c$
Name Provider Root
---- -------- ----
k FileSystem 127.0.0.1c$

0.5 脚本调试

通常调试代码有两种手段:

  • 输出调试日志:适合比较简单的脚本
  • 断点调试:适合比较复杂的脚本
输出调试日志

虽然write-host可以输出日志信息,但其实powershell有专门的指令实现这一功能:write-debug,我们可以修改$DebugPreference设置项来改变debug日志行为:

枚举值描述示例
SilentlyContinue默认,调试关闭
Stop输出调试信息,终止脚本执行
Continue输出调试信息,继续执行脚本
Inquire输出调试信息,询问用户是否继续执行
PS C:> $DebugPreference="silentlycontinue"
PS C:> Write-Debug "输入一行调试信息" ; Write-Host "伦敦奥运会女子体操决赛"
伦敦奥运会女子体操决赛

PS C:> $DebugPreference="stop"
PS C:> Write-Debug "输入一行调试信息" ; Write-Host "伦敦奥运会女子体操决赛"
调试: 输入一行调试信息
Write-Debug : 已停止执行命令,因为首选项变量“DebugPreference”或通用参数被设置为 Stop。
所在位置 行:1 字符: 12
+ Write-Debug <<<<  "输入一行调试信息" ; Write-Host "伦敦奥运会女子体操决赛"     + CategoryInfo          : OperationStopped: (:) [Write-Debug], ParentContainsErrorRecordExceptio     + FullyQualifiedErrorId : ActionPreferenceStop,Microsoft.PowerShell.Commands.WriteDebugCommand 

PS C:> $DebugPreference="continue"
PS C:> Write-Debug "输入一行调试信息" ; Write-Host "伦敦奥运会女子体操决赛"
调试: 输入一行调试信息
伦敦奥运会女子体操决赛

PS C:> $DebugPreference="inquire"
PS C:> Write-Debug "输入一行调试信息" ; Write-Host "伦敦奥运会女子体操决赛"
调试: 输入一行调试信息

确认
是否继续执行此操作?
[Y](Y)  [A] 全是(A)  [H] 终止命令(H)  [S] 挂起(S)  [?] 帮助 (默认值为“Y”): y
伦敦奥运会女子体操决赛
断点调试

可以在 Powershell ISE 中通过F9断点执行Powershell脚本。
即使没有ISE也可以单步跟踪,做法如下:
Set-PSDebug -step,Powershell会每只行一段代码,就会向用户询问是否继续执行

其他用户设置项
  • ConfirmPreference:设置提问确认的级别
  • ErrorActionPreference:设置发生错误后的执行动作
  • ErrorView:设置错误的显示模式
  • ProgressPreference:设置进度条的显示模式
  • ReportErrorShowExceptionClass:显示异常所在的类
  • ReportErrorShowInnerException:显示异常内部异常信息
  • ReportErrorShowSource:显示异常的来源
  • ReportErrorShowStackTrace:显示异常的错误跟踪栈
  • VerbosePreference:设置详细信息的显示模式
  • WarningPreference:设置警告信息的显示模式

0.6 持久化powershell配置

在Powershell控制台的许多更改只会在当前会话有效。一旦关闭当前控制台,你自定义地所有别名、函数、和其它改变将会消失,除非将更改保存在windows环境变量中。我们需要profile来保存一些基本的初始化工作。

profile脚本也是ps1脚本,Powershell在启动时会执行对应的profile脚本进行初始化。

参考文章:Powershell自动执行脚本之profile

1. 符号和运算符

1.1 符号

符号含义示例
#行注释
`换行符代码截断
|管道符$text= dir | Out-String
dir | Out-File .temp.txt
>重定向符,标准输出流:>,异常输出流:2>
重定向符的作用与Out-File 参数的功能非常接近,但是Out-File 功能丰富,出了指定文件名,可以-encoding指定字符编码
Get-Item "NoSuchDirectory" 2> Error.txt

1.2 比较运算符

比较运算符
示例:

PS C:Powershell> (3,4,5 ) -contains 2
False
PS C:Powershell> (3,4,5 ) -contains 5
True
PS C:Powershell> (3,4,5 ) -notcontains 6
True
PS C:Powershell> 1,2,3,4,3,2,1 -eq 3
3
3
PS C:Powershell> 2 -eq 10
False
PS C:Powershell> "A" -eq "a"
True
PS C:Powershell> "A" -ieq "a"
True
PS C:Powershell> "A" -ceq "a"
False
PS C:Powershell> 1gb -lt 1gb+1
True
PS C:Powershell> 1gb -lt 1gb-1
False

1.3 布尔运算符

运算符描述示例
-and$true -and $true
-or$true -or $true
-xor异或$true -xor $false
-not-not $true

1.4 转义字符

在其它编程语言中喜欢将反斜杠作为转义字符,但是在Powershell中扮演转义字符角色的不是反斜杠,而是反引号“`”字符串中的反引号。

转移字符描述示例
`n换行符
`r回车符
`t制表符
`a响铃符
`b退格符
`’单引号
`”双引号
`0Null
``反引号本身

1.5 文件路径

字符描述示例
.当前目录Ii .
#用资源浏览器打开当前目录
..父目录Cd …
#切换到父目录
\驱动器根目录Cd \
#切换到驱动器的顶级根目录
~home,PowerShell初始化的目录Cd ~
#切换到PowerShell初始化的目录

2. 变量

  1. 通过变量的$var.getType()方法可以打印变量的数据类型
  2. 通过$var -is [typename]判断是否是指定类型
  3. 通过$var -eq $null判断变量是否为null

2.0 变量的申明

弱类型变量

通过$符定义变量,例如:

$str='hello'

上述代码中,我们没有给变量指定数据类型,Powershell会给数据分配一个最佳的数据类型,规则如下:

  • 如果一个整数超出了32位整数的上限([int32]::MaxValue),它就会分配一个64位整数的数据类型;
  • 如果碰到小数,会分配一个Double类型;
  • 如果是文本,Powershell会分配一个String类型;
  • 如果是日期或者时间,会被存储为一个Datetime对象;

通过$str.getType()获取$str的数据类型,输出:

$str.getType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

Type本身也有类型和属性,比如常用的name属性:

$type=$str.getType()
$type.getType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
False    True     RuntimeType                              System.Reflection.TypeInfo
强类型变量

在变量前加[typename]来为变量指定类型,加入类型检查有助于提高程序的鲁棒性:

PS> [byte]$b=101
PS> $b
101

常用类型

typename具体类型
stringString
arrayArray
byteByte
DateTimeDateTime
XMLXML

2.1 数字

数学运算
  • 支持的算数运算符:()+,-,*,/,%,++,--
  • 自动识别计算机容量单位,包括KB,MB,GB,TB,PB
    示例1:简单的算数运算
PS C:\pstest> 1+2+3
6
PS C:\pstest> 0xABCD
43981
PS C:\pstest> 3.14*10*10
314
PS C:\pstest> 1+3-(2.4-5)*(7.899-4.444)
12.983

示例2:流量/容量计算

PS C:\pstest> 80kb*800*30/1gb
1.8310546875
PS C:pstest> 10GB/(80KB*5)/30
873.813333333333
设置数字格式

设置数值格式

2.2 字符串

字符串定义

普通字符串:

# 双引号与单引号都可以定义字符串,但是双引号支持字符串模版,而单引号会忽略所有转义字符
$str = "hello"
$str = 'hello'

Here-String字符串:

# 双引号支持字符串模版
$str = @"
hello
world
"@
# 单引号会忽略所有转义字符,显示原生字符串
$str = @'
hello
world
'@

关于字符串双引号与单引号为什么会有这种区别,可以参考这篇文章:Powershell 执行上下文

字符串方法
  • 对象方法:substring、split、contains、indexof、replace、chars(index)、compareto、startsWith、insert、padLeft(length)、remove、tolower、trim、length、toBoolean|toInt等,字符串支持“+”号拼接,单要求第一个元素必须是字符串,否则会报错
  • 静态方法:join,concat

示例:对象方法

$url="https://www.pstips.net"
# 以“:”, “.”, “/”为分隔符
$url.split(":./")
# 以“:”, “.”, “/”为分隔符,结果过滤掉空字符串
$url.split(":./",[StringSplitOptions]::RemoveEmptyEntries)

示例:静态方法

function RemoveSpace([string]$text) {
	$private:array = $text.Split(" ", `
	[StringSplitOptions]::RemoveEmptyEntries)
	[string]::Join(" ", $array)
}
字符串格式化

格式化操作符 –F 能够将一个字符串格式化为指定格式,左边是包含通配符的字符串,右边是待插入和替换的字符串。

# 单个变量
# -F 右边的表达式必选放在圆括号中,作为一个整体,先进行计算,然后在格式化。否则可能会解析错误{0} diskettes per CD” -f (720mb/1.44mb)
500 diskettes per CD

# 多个变量{0} {3} at {2}MB fit into one CD at {1}MB” -f (720mb/1.44mb), 720, 1.44, “diskettes”
500 diskettes at 1.44MB fit into one CD at 720MB
字符串模式匹配

通配符主要被使用在文件系统中,在字符串操作符-like-notlike 中也可以使用。

通配符表
通配符描述示例
*任意个任意字符,(包含零个字符)#列出当前目录中的文本文件
Dir *.txt
?一个任意字符#列出当前目录中后缀名以‘t’结尾,并且后缀名只有三个字符的文件
Dir *.??t
[xyz]一个包含在指定枚举集合中的字符#列出当前目录中以‘a’、‘b’或‘c’打头的文件
Dir [abc].
[x-z]一个包含在指定区间集合中的字符#列出当前目录中包含一个’p’到’z’之间任意字符的文件
Dir [p-z].
$ip = Read-Host "IP address"
If ($ip -like "*.*.*.*") { "valid" } Else { "invalid" }
字符串正则匹配

模式匹配不能满足复杂的需求,此时可以使用-match操作符对数据进行正则匹配。
示例:

# 文本模式包含了6个Tab字符分割的数组
$pattern = "(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)"
# 输入日志
$text = Get-Content $env:windir\windowsupdate.log
# 从日志文件中提取出任意行(这里是第21行)
$text[20] -match $pattern >null
# 每执行一次-match都可以通过$matches获取到最新的匹配结果
if($matches.length>0){
	"On {0} this took place: {1}" -f $matches[1], $matches[6]
}

On 2014-02-10 this took place:   * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}.201 to search result

示例:给正则子表达式取名

# 文本模式包含了6个Tab字符分割的数组
$pattern = "(?<Datum>.*)\t(?<time>.*)\t(?<Code1>.*)" + "\t(?<Code2>.*)\t(?<Program>.*)\t(?<Text>.*)"
# 输入日志
$text = Get-Content $env:windir\windowsupdate.log
$text[20] -match $pattern >null
if($matches.length>0){
	# 起名后,可以用名称引用
	"On {0} this took place: {1}" -f $matches.time, $matches.text
}

On 2014-02-10 this took place:   * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}.201 to search result

示例:使用switch语句实现上述日志打印

# 为结果创建一个哈希表:
result = @{Defender=0; AutoUpdate=0; SMS=0}
# 解析更新日志,并将结果保存在哈希表中:
Switch -regex -file $env:windir\wu1.log {
'START.*Agent: Install.*Defender' { $result.Defender += 1 };
'START.*Agent: Install.*AutomaticUpdates' { $result.AutoUpdate +=1 };
'START.*Agent: Install.*SMS' { $result.SMS += 1}
}

# 输出结果:
$result

Name		Value
----		-----
SMS		0
Defender	1
AutoUpdate 	8

2.3 数组

数组的定义

字面量方式定义数组:

$arr=(1,1,6,56,55,99,254,0,0,8,67)
# 可以省略()
$arr=1,1,6,56,55,99,254,0,0,8,67
# 定义空数组
$arr=@()
# 定义单元素数组
$arr=, 1
# 多态数组
$arr="hello", 1, (get-date)
# 强类型数组,指定数组类型是int[]
[int[]] $nums=@()
$nums+=2012
# 连续整数数组
$nums = 1..5

新建对象方式定义数组:

# 定义24位byte类型数组
$arr=New-Object Byte[] 24

数组的访问
PS> $books="元素1","元素2","元素3"
# 通过下标访问,下标从0开始
PS> $books[0]
元素1
PS> $books[($book.Count-1)]
元素3
PS> $books[-1]
元素3
# 获取子数组
PS> $subbooks = $books[0,2]
# 将数组逆序输出(其实也是一个子数组)
PS> $books[($books.Count)..0]
元素3
元素2
元素1
# 给数组添加元素
PS> $books+="元素4"
# 给数组删除元素(删除首个元素)
PS> $books=$books[1..($books.Count-1)]
数组的复制

数组属于引用类型,使用默认的的赋值运算符在两个变量之间赋值只是复制了一个引用,两个变量共享同一份数据。此时可以使用clone()方法创建数组的副本:

$chs=@("A","B","C")
$chsBak=$chs
$chsNew=$chs.Clone()
PS> $chs.Equals($chsBak)
True
PS> $chs.Equals($chsNew)
False
二维数组
#创建二维对称数组
$array=New-Object 'string[,]' 2,2
 
#给二维对称数组赋值
PS> $array[0,0]="moss"
PS> $array[0,1]="fly"
 
#访问二维对称数组
PS> $array
moss
fly
PS> $array[0,1]
fly

2.4 哈希表

前面使用@()创建数组,现在使用@{}创建哈希表,使用哈希表的键访问对应的值。
数组使用,作为元素分隔符,哈希表使用;作为分隔符。

创建哈希表
PS> $stu=@{ Name = "小明";Age="12";sex="男" }
PS> $stu

Name                           Value
----                           -----
Name                           小明
Age                            12
sex                            男
读写哈希表
# 读操作
PS> $stu["Name"]
小明
PS> $stu["age"]
12
# 哈希表的常用方法
PS> $stu.Count
3
PS> $stu.Keys
Name
Age
sex
PS> $stu.Values
小明
12
男
# 写操作
# 新增字段
$stu.hobby="tennies"
# 删除字段
$stu.Remove("Name")

2.5 环境变量

Windows环境变量
存储在环境变量中的Windows特殊目录
特殊目录描述示例
Application data存储在本地机器上的应用程序数据$env:localappdata
User profile用户目录$env:userprofile
Data used incommon应用程序公有数据目录$env:commonprogramfiles
Public directory所有本地用户的公有目录$env:public
Program directory具体应用程序安装的目录$env:programfiles
Roaming Profiles漫游用户的应用程序数据$env:appdata
Temporary files(private)当前用户的临时目录$env:tmp
Temporary files公有临时文件目录$env:temp
Windows directoryWindows系统安装的目录$env:windir

除了上述表格中列出的部分变量,Windows还可以通过下面方法查找全部内置目录:

[System.Environment+SpecialFolder] |
Get-Member -static -memberType Property |
ForEach-Object { "{0,-25}= {1}" -f $_.name, [Environment]::GetFolderPath($_.Name) 
}
读变量

在cmd中查看环境变量path只需输入:echo %path%或者set %path%,但是在powershell中略有不同,需要输入:

$Env:path
写变量
# 当前会话中修改path变量,如果需要全局修改需要对powershell做一些配置
$Env:path=$Env:Path+";D:/test"

2.6 自定义对象

powershell中自定义对象对应的类型是:PSCustomObject。我们可以通过以下几种方式来构建自定义对象:

  • select-object:把空字符串pipe到该指令,并通过-property来指定各个属性名称
  • New-Object+Add-Member:可以通过新建PSCustomObject对象,再为之添加属性的方式,这种方式最为繁杂,已经不推荐
  • [PSCustomObject][Ordered]@{}:ordered为可选项,可以直接在哈希表中为对象定义属性和初始化(强烈推荐)

示例:

PS> $obj = '' | select-object -property Name, Age, Sex
PS> $obj.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

示例:

PS> $obj = new-object pscustomobject
PS> $obj | add-member -membertype noteproperty -name Param -value 1
PS> $obj

Param
-----
    1

示例:

PS> $obj = [pscustomobject]@{Name='haha';Age=2}
PS> $obj

Name Age
---- ---
haha   2

2.7 变量作用域

先看一个引例,现在有一个test.ps1,其内容为:

$windows = $env:windir
“Windows Folder: $windows

然后在控制台依次执行:

PS> $windows="Hellow"
PS> .\test.ps1
Windows Folder: C:\Windows
PS> $windows
Hellow

调用脚本时,会分配一个变量$windows,在脚本调用结束后,这个变量被回收,脚本中的变量不会影响脚本外的变量,因为它们在不同的作用域中。powershell会针对每个函数和脚本给它们分配不同的作用域。

提升变量的可见性

同样是刚才的脚本,刚才的命令,只是在运行脚本时多加上一个点”.” 和一个空格:

PS> $windows="Hellow"
PS> . .\test.ps1
Windows Folder: C:\Windows
PS> $windows
C:Windows

在运行脚本时使用一个原点和空格,Powershell解释器就不会为脚本本身创建自己的变量作用域,它会共享当前控制台的作用域,这种不太灵活但却简单的方法,使用时一定要格外小心。

为变量设置作用域
作用域描述示例
$global全局变量,在所有的作用域中有效,如果你在脚本或者函数中设置了全局变量,即使脚本和函数都运行结束,这个变量也任然有效定义:
$global:var=“I am global”
访问:
$global:var
$script脚本变量,只会在脚本内部有效,包括脚本中的函数,一旦脚本运行结束,这个变量就会被回收$script:var=“I am script”
$private私有变量,只会在当前作用域有效,不能贯穿到其他作用域$private:var=“I am private”
$local默认变量,可以省略修饰符,在当前作用域有效,其它作用域只对它有只读权限$local:var=“I am local”

打开Powershell控制台后,Powershell会自动生成一个新的全局作用域。如果增加了函数和脚本,或者特殊的定义,才会生成其它作用域。
在当前控制台,只存在一个作用域,通过修饰符访问,其实访问的是同一个变量

PS> $logo="www.pstips.net"
PS> $logo
www.pstips.net
PS> $private:logo
www.pstips.net
PS> $script:logo
www.pstips.net
PS> $private:logo
www.pstips.net
PS> $global:logo
www.pstips.net

3. 函数

当我们把一个命令的执行结果保存到一个变量中,可能会认为变量存放的是纯文本。
但是,事实上Powershell会把文本按每一行作为元素存为数组。如果一个命令的返回值不止一个结果时,Powershell也会自动把结果存储为数组。

3.1 表达式

  1. 组表达式(expression)
(100+8) * 7
(Get-Process -ProcessName "WeChat").CPU
  1. 子表达式$(expression)
# 单个值
PS /> $str1 = "123"
PS /> $($str1)
123
PS /> $($str1)[0]
1
# 数组类型
PS /> $str2 = "123","456","789"
PS /> $($str2)
123
456
789
PS /> $($str2)[0]
123
  1. 数组子表达式@(expression)
PS /> $str1 = "123"
PS /> @($str1)
123
PS /> @($str1)[0]
123

3.2 if语句

语句格式:if (condition1) { block } else if(condition1) { block } else { block }

3.3 switch语句

示例:数字

# 使用 Switch
switch($value){
    1 {"Beijing"}
    2 {"Shanghai"}
    3 {"Tianjin"}
    4 {"Chongqing"}
    Default {"没有匹配条件"}
}

示例:取值范围

$value=2
# 多条件匹配,处理多次
switch($value){
    {$_ -lt 5 }  { "小于5" }
    {$_ -gt 0 }   { "大于0" }
    {$_ -lt 100}{ "小于100"}
    Default {"没有匹配条件"}
}

小于5
大于0
小于100
# 多条件匹配,处理1次
switch($value){
    {$_ -lt 5 }  { "小于5"; break }
    {$_ -gt 0 }   { "大于0"; break }
    {$_ -lt 100}{ "小于100"; break }
    Default {"没有匹配条件"; break }
}

示例:字符串

$domain="www.mossfly.com"
# 字符串默认使用-eq比较,大小写不敏感
switch($domain){
    "Www.moSSfly.com" {"Ok 1"}
    "www.MOSSFLY.com" {"Ok 2" }
    "WWW.mossfly.COM" {"Ok 3"}
}
Ok 1
Ok 2
Ok 3

# 大小写敏感
switch -case ($domain){
    "Www.moSSfly.com" {"Ok 1"}
    "www.MOSSFLY.com" {"Ok 2" }
    "www.mossfly.com" {"Ok 3"}
}
Ok 3

# 使用通配符
switch -wildcard($domain){
    "*"     {"匹配'*'"}
    "*.com" {"匹配*.com" }
    "*.*.*" {"匹配*.*.*"}
}
匹配'*'
匹配*.com
匹配*.*.*

#使用正则
switch -regex ($mail){
    "^www"     {"www打头"}
    "com$"     {"com结尾" }
    "d{1,3}.d{1,3}.d{1,3}.d{1,3}" {"IP地址"}
}
 www打头
com结尾

示例:处理集合

$value=100..999
switch($value){
# 数组元素使用$_去获取
{[Math]::Pow($_%10,3)+[Math]::Pow( [Math]::Truncate($_%100/10) ,3)+[Math]::Pow( [Math]::Truncate($_/100) , 3) -eq $_} {$_}
}
 
153
370
371
407

3.4 for/foreach语句

  • for语句格式:for ($i=1; $i -lt 100; $i++) { block }
  • foreach语句格式:foreach($var in $list){ block }
  • do-while语句参考:Powershell Do While 循环

示例1:foreach打印数组元素

$strs = @"  
a     
b  
c  
"@   
$strlist = ($strs -split "\n")  
foreach($s in $strlist) {  
    $s+"-suffix"  
}  

示例2:for循环实现进度条

for ($i=1; $i -lt 100; $i++) {
	write-progress -activity "starting..." -percentcomplete $i -currentoperation "$i Finished"` -status "loading..."
	start-sleep 1 
}

运行效果:
进度条
示例3:for循环实现倒计时

for ($p=100; $p -ge 0; $p--) {
  Write-Progress -Activity "Starting..." -SecondsRemaining $p -CurrentOperation $p% Not Finished" -Status "Loding..."
  Start-Sleep 1
}

示例4:for循环实现输入校验

for($domain="";!($domain -like "www.*.*");$domain=Read-Host "Input domain")
{
    Write-Host -ForegroundColor "Green" "Please give a valid domain name."
}
Please give a valid domain name.
Input domain: www
Please give a valid domain name.
Input domain: mossfly.com
Please give a valid domain name.

示例5:For循环的控制语句第一个和第三个可以为空

$sum=0
$i=1
for(;$i -le 100;) {
    $sum+=$i
    $i++
}
$sum

3.5 自定义函数

无参函数:

# 定义函数
function letitrun {
	代码逻辑块
}
# 调用函数
letitrun

有参函数:

# 定义函数
function letitrun($data,$data2){
	代码逻辑块引用$data,$data2
}
# 调用函数
letitrun "abc" 2
# 或者指定参数名
letitrun -data "abc" -data2 2

有参函数带默认值:

# 定义函数,这里[switch]参数就是[bool]参数,data3这个参数做了参数校验
function letitrun([string]$data="hello",[int]$data2=10,[bool] $data3=$(throw "Parameter missing: -name data3"),[switch]$data4=true){
	代码逻辑块引用$data,$data2,$data3,$data4
}
# 调用函数
letitrun "abc" 2 true false
# 或者不传参数
letitrun

命名参数可以进行参数校验,参数校验的方式除了抛出异常外,还可以通过对话框的方式提示用户或让用户选择,参考这个例子:Powershell使用Dialog设定必选参数

使用Param关键词定义入数列表:

# 定义函数
function letitrun{
	param($data,$data2)
	
	代码逻辑块引用$data,$data2
}

使用可变参数$args,增加了灵活性,但是不能使用命名参数了:

function sayHello{
	# 你可以把$args当做是长度从0到n的数组
    if($args.Count -eq 0) {
        "No argument!"
    } else {
        $args | foreach {"Hello,$($_)"}
    }
}
# 调用无参
sayHello
# 调用单个参数
sayHello LiLi
# 调用多个参数,参数之间用空格分隔
sayHello LiLi Lucy Tom

函数也可以配合管道使用,但是需要用内建关键词$input或者$_去接收,详见下节。

函数返回值:

  • 函数体中每个打印语句都是一个返回值,如何不需要将打印语句作为返回值,可以使用write-host或者Write-Debug进行打印,推荐使用后者,通过$DebugPreference="Continue"开启调试模式,$DebugPreference="SilentlyContinue"关闭调试模式。
  • 如果一个函数有多个返回值,则会包装成一个Object[]返回,如果只有一个返回值则返回实际对象。

3.6 函数执行的三个阶段begin、process、end

begin:这个关键字后的语句列表块只会运行一次,而且它必须位于函数的开头定义
process:这个关键字后的语句列表块会针对管道传递过来的每个对象运行一次,并且会自动赋予$_变量
end:在处理完所有的对象以后,才会运行一次End关键字后的语句列表块

如果函数体内没有使用任何以上三种功能关键字,那么函数体默认将管道传递来的对象视为End方式。
使用管道传参的函数无法使用参数列表去接收,是必须使用内建关键词$input或者$_去接收。

示例:

function myfun{
	end{"end: $input"}
}
# 测试
"a","b","c" | myfun
# 输出:
# end: a b c

示例2:

function myfun2{
	end {"end $_"}
}
# 测试
"a","b","c" | myfun2
# 输出
# end:
# 测试
"a" | myfun2
# 输出
# end:
# 结论:$_无法获取到管道输入

示例3:


function myfun3{
	begin {"begin $_"}
	process {"process $_"}
	end {"end $_"}
}
# 测试
"a","b","c" | myfun3
# 输出: 
# begin
# process a
# process b
# process c
# end c

3.7 Splatting传参

引例:用WMI去查询系统信息版本号

Get-WmiObject -Class Win32_OperatingSystem -Property Version

可以简写为:

Get-WmiObject Win32_OperatingSystem Version

上述简写执行成功因为实参类型与函数定义是一一对应的,但是如果把实参Win32_OperatingSystemVersion的顺序对调一下,就会报错。为此可以通过Splatting传参来解决实参与形参的对应问题,而不必严格按照函数定义的顺序进行调用:

$Param = @{Class = "Win32_OperatingSystem";  
           Property = "Version";  
           ComputerName = "LocalHost"}  

Get-WmiObject @Param  

splatting其实就是一个集合,其基本格式为:$variable=@{参数名=参数值;参数名=参数值;...},然后使用@符号进行引用。其退化格式为:$variable="参数值", "参数值", ...,不过参数的顺序就必须与函数定义顺序一致了,比如上述的语句可以改写为:

$Param = "Win32_OperatingSystem", "Version"
Get-WmiObject @Param  

除了解除入参顺序问题,splatting的另一个重要意义是实现参数复用,比如:

$Colors = @{ForegroundColor = "yellow";  
            BackgroundColor = "red"}  
Write-Host "I love PowerShell" @Colors  
Write-Host "He loves PowerShell, too." @Colors  

3.8 异常识别与处理

cmdlet指令可以通过设置-ErrorAction action来设置本条指令的异常处理策略,公有以下几种策略:

策略名称描述
Stop发生异常时抛出异常信息,并中断执行
Continue发生异常时抛出异常信息,并继续执行
SilentlyContinue发生异常时屏蔽异常信息,并继续执行

由于系统默认的ErrorActionPreference为continue,因此哪怕指定了指令的-ErrorAction Stop也不会阻止整个脚本继续执行。但是可以在trap语句块中使用Break来终止脚本继续运行。

异常相关的其他知识点:

  • 通过-ErrorAction "SilentlyContinue"抑制异常中断,-ErrorAction可以简写为-ea
  • 通过-ErrorVariable var将异常保存到一个变量中,var是一个数组;
  • 通过$?获取异常状态:发生异常该值为false,否则为true;
  • 通过$error获取错误堆栈,最新的异常保存在索引为0的位置,所以一般不需要为指令设置-ErrorVariable var$error的元素实体为System.Management.Automation.ErrorRecord,如果要获取异常,需要通过$($error[0]).Exception.Message属性;
抑制异常导致的程序中断,并获取异常状态和信息
Remove-Item "文件不存在" -ErrorAction "SilentlyContinue"
If (!$?){
	"发生异常,异常信息为$($error[0])"
} else {
	"删除文件成功!"
}

# 发生异常,异常信息为:找不到路径“E:文件不存在”,因为该路径不存在。
trap获取异常信息

trap{}语句块相当于其他语言的catch语句块,只不过trap语句块写在代码的最前面
示例:脚本多处发生异常,可以执行完,且trap只捕获到第一次异常,如果想要trap捕获每个异常,则应为每个指令设置-ErrorAction Stop

Trap {
	# 在trap语句块中,可以通过$_获取异常信息
	Write-Host $_.Exception.Message
};
Stop-Process -Name "NoSuchProcess"
Write-Host "执行下一句"
1/$null
Write-Host "执行结束"

示例:在trap中使用break使脚本在第一次捕获异常时就停止继续执行

Trap {
	Write-Host $_.Exception.Message;
	Break
};
Stop-Process -Name "NoSuchProcess" -ea Stop
Write-Host "执行下一句"
1/$null
Write-Host "执行结束"

示例:在trap中使用continue屏蔽默认的异常输出,定制自己的异常信息

Function Test-Func {
	Trap {
		"Trap到了异常: $($_.Exception.Message)";
		Continue
	}
	1/$null
	Get-Process "NoSuchProcess" -ErrorAction Stop
	Dir MossFly: -ErrorAction Stop
}
Test-Func
 
Trap到了异常: 试图除以零。
Trap到了异常: 找不到名为“NoSuchProcess”的进程。请验证该进程名称,然后再次调用 cmdlet。
Trap到了异常: 找不到驱动器。名为“MossFly”的驱动器不存在。
trap处理特定的异常
Trap [System.DivideByZeroException] {
"除数为空!";
Continue
}
Trap [System.Management.Automation.ParameterBindingException] {
"参数不正确!";
Continue
}
 
Trap [System.Net.WebException]{
"网络异常!"
Continue
}
 
1/$null
Dir -MacGuffin
$wc = new-object System.Net.WebClient
$wc.DownloadFile("http://www.mossfly.com/powershell.txt","e:ps.txt")
 
#除数为空!
#参数不正确!
#网络异常!
抛出自定义异常信息
Function Func-Test($a,$b){
	if($b -eq $null){
		throw "参数b 不能为空!"
	}
	"{0}+{1}={2}" -f $a,$b,($a+$b)
}
Func-Test -a 10

上述代码可以优化:

Function Func-Test($a,$b=$(throw "参数B 不能为空!")){
	"{0}+{1}={2}" -f $a,$b,($a+$b)
}
Func-Test -a 10

3.9 工作脚本和类库

我们希望将脚本分成两类,以最大程度实现函数复用:

  • 工作脚本:编写脚本的执行流程
  • 类库:仅含自定义函数,可在工作脚本中导入使用(相当于导包)

这在cmd中是无法实现的(cmd以bat文件为单位,无法做到类库的函数级复用),我们来看看powershell中如何实现:
假设有一个类库pslib.ps1:

# 返回输入数字的阶乘
Function Factorial([int]$n){
    $total=1
    for($i=1;$i -le $n;$i++) {
        $total*=$i
    }
    return $total
}

我们的工作脚本myscript.ps1:

# 工作脚本接收一个外部一个整数入参,否则抛出异常
param([int]$n=$(throw "请输入一个正整数"))
# 导入类库,其实就是执行了该脚本,并利用“. ”提升了类库脚本的作用域,使其执行后没有被立即销毁
. .pslib.ps1
# 调用类库中的函数
Factorial $n

执行工作脚本:

\.myscript.ps1 10
# 在本例中我们使用param()获取入参,可以使用命名参数方式调用脚本
\.myscript.ps1 -n 10

还有一个问题:目前工作脚本需要与类库放在同一个目录才能方便在工作脚本中导入类库脚本,有什么办法解决呢?
我们可以把所有的类库都放在一个用户目录中,比如:$env:appdata,然后在工作脚本中通过绝对路径引入。具体操作是:把所有的类库放到一个目录下,然后写一个install.ps1,其内容是把当前目录下的所有脚本复制到$env:appdata目录下。然后在任意工作脚本中就可以通过. $env:appdata\xxx.ps1来导入类库。

技巧:我们可以为工作脚本设置别名来简化调用方式:
set-alias ms .myscript.ps1
ms 10

4. 常用cmdlets命令

所有命令参见:Powershell 命令集 cmdlets

标准输入输出Read-Host/Write-Host

示例:接收键盘输入

# 从键盘接收输入数据
$str=read-host -prompt "Type something:"
# 从键盘接收安全字符(显示为*号)
$sstr=read-host -assecurestring
# 把密文恢复为原始输入的明文
$sstr=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sstr)
$str=[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($sstr)
# 向命令行打印字符串(等同于直接输出$str或者echo $str)
write-host $str

字符串操作

字符串编解码[System.Convert]
$str="hello powershell"
$strBytes=[System.Text.Encoding]::Unicode.GetBytes($str)
# 发现[System.Convert]可以简化为[Convert]
$encodedStr=[Convert]::ToBase64String($strBytes)
字符串加解密ConvertTo-SecureString/ConvertFrom-SecureString

示例:使用默认加密方式加解密

$password="abc"
# 没有指定加密方式,默认使用原生的DPAPI(Windows Data Protection API)算法
$securePassword=$password | ConvertTo-SecureString -AsPlainText -Force
$standardPassword=ConvertFrom-SecureString $securePassword

示例:使用AES加密算法

$password="abc"
# 定义长度为24或36的AES的常量key
# $key=(3,4,5,66,77,254,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7)
# 随机生成长度为24的AES的key
$key=New-Object Byte[] 24
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key)

$spassword=$password | ConvertTo-SecureString -AsPlainText -Force
$stdpassword=ConvertFrom-SecureString $spassword -Key $key
字符串筛选Select-String

此命令应用于字符串数组,对应于cmd的findstr命令,示例:

ipconfig | Select-String "IP"

创建对象New-Object/New-Variable

示例:使用New-Object新建PSObject对象并添加属性

PS C:Powershell> $obj=New-Object PSObject
PS C:Powershell> Add-Member -Name A -Value 1 -InputObject $obj
# NoteProperty属性为静态数据,用常量赋值
# 与之对应的ScriptProperty则是动态属性,用{}表达式赋值,可以做到动态返回值
PS C:Powershell> Add-Member -MemberType NoteProperty -Name "A" -Value "1" -InputObject $obj
PS C:Powershell> Add-Member -MemberType ScriptProperty -Name "B" -Value {get-date} -InputObject $obj
PS C:Powershell> Add-Member -MemberType NoteProperty -Name "C" -Value "3" -InputObject $obj
PS C:Powershell> Add-Member -MemberType NoteProperty -Name "D" -Value "4" -InputObject $obj
PS C:Powershell> $obj

A B C D
- - - -
1 2 3 4

MemberType表格

类型描述
AliasProperty另外一个属性的别名
CodeProperty通过静态的.Net方法返回属性的内容
Property真正的属性
NoteProperty随后增加的属性
ScriptProperty通过脚本执行返回一个属性的值
ParameterizedProperty需要传递参数的属性
示例使用New-Object新建PSCredential对象

示例:创建PSCredential对象

$credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username $password 

示例:使用New-Variable创建变量,且为常量

New-Variable a -value 1 -option Constant
$a

管道过滤

此命令需配合管道符,一般应用于对象数组。

  • Where-Object:类似于stream中的filter操作符,不会改变元素数据类型
  • Sort-Object:可以对数据进行排序,不会改变元素数据类型
  • ForEach-Object:类似于stream中的map操作符,可以实现元素类型转换
  • Select-Object:对过滤结果的属性、数量进行筛选,不改变元素类型。比如结合-first count参数,可以实现结果立即返回,这在递归查找已知数量的文件的指令中可以大大提高运行速度

示例:Where-Object基本用法

Get-Process | Where-Object{$_.ProcessName -eq "svchost"}
# 等价于
Get-Process | Where-Object{$PSItem.ProcessName -eq "svchost"}
# 等价于
Get-Process | Where-Object ProcessName -eq "svchost"

示例:综合演示利用管道筛选对结构化数据进行聚合统计
现有结构化数据a.txt:

李一 93
王二 83
王三 93
李四 60
王五 75
马六 61
孙七 75
刘八 75

实现打印出成绩相同的学生及成绩

Get-Content .\a.txt | ForEach-Object {
[PSCustomObject]@{
 Name = $_.split()[0]
 Value = $_.split()[1]
 }
} | Group-Object Value | Where-Object { $_.Count -gt 1 }|
 ForEach-Object { $_.Group | ForEach-Object { "{0} {1}" -f $_.name,$_.value } }

示例:利用Select-Object对结果的属性进行筛选

Dir | Select-Object name, length, LastWriteTime |
ConvertTo-HTML | Out-File report.htm

格式化输出

  • Format-List命令可以把对象信息以列表的形式展现出来。
  • Format-Table命令可以把对象信息以表单的形式展现,同时还支持配合一些特定参数来动态调整表单样式。
  • Format-Wide
  • Format-Custom

示例:基础用法

# 查看对象结果的所有属性
ls | Format-Table *
# 使用文本换行参数处理属性和属性的内容太多可能不会显示完全
ls | Format-Table * -Wrap
# 优化列宽度:将属性值的最大宽带作为每一列的宽度
ls | Format-Table -AutoSize
# 显示指定的属性
ls | Format-Table Name,Length,LastWriteTime
# 使用通配符(显示以pe打头,64结尾的文件)
ls | Format-Table Name,pe*64

示例:自定义表格列标题

Get-Process | Where-Object{$_.Id -gt 2000} | Format-Table ProcessName, @{Expression={$_.Id}; Name="Id>2000";FormatString="N2"}

进程操作

查进程Get-Process

示例1:获得所有名为svchost的进程信息

Get-Process | Where-Object{$_.ProcessName -eq "svchost"}

示例2:在已经罗列出来的svchost进程信息里继续筛选出Id属性值大于1000的

Get-Process | Where-Object{$_.ProcessName -eq "svchost"} | Where-Object{$_.Id -gt 1000}

示例3:查看所有当前系统里的进程,但条件是进程的Id号大于1000以内的前十个进程信息名,并且Id号需要从大到小排列

Get-Process | Where-Object {$_.Id -gt 1000} | Select-Object -Property Id, ProcessName -First 10 | Sort-Object -Property Id -Descending

示例4:将示例3的结果以表格形式呈现

Get-Process | Where-Object {$_.Id -gt 1000} | Select-Object -Property Id, ProcessName -First 10 | Sort-Object -Property Id -Descending | Format-Table -AutoSize

示例5:获取进程并将之杀掉

(Get-Process -ProcessName "Thunder").Kill()
杀进程Stop-Process

格式:Stop-Process -Name pname

Function ErrorTest(){
    #从这里开始隐藏所有的错误信息
    $ErrorActionPreference="SilentlyContinue"
    
    #关闭一个不存在的进程,默认会报错,在这里错误信息被隐藏
    Stop-Process -Name "www.mossfly.com"
     
    #恢复$ErrorActionPreference,错误开始输出
    $ErrorActionPreference="Continue"
 	
 	# 整数除以0,报错,错误信息正常输出
    2/0
}
ErrorTest
试图除以零。

文件操作

文件操作命令集
命令描述别名示例
Get-Childitem列出目录的内容,返回的是FileInfo数组Dir, ls, gci
Get-Content基于文本行来读取内容,返回的是个字符串数组type, cat, gc
Set-Content覆盖写入文件内容scSet-Content info.txt "First line"
Add-Content追加写入文件内容,在涉及到文本操作的时候建议使用Add-Content替代>>,因为后者默认使用的是控制台的字符集,容易乱码Add-Content info.txt "Third line"
Get-Item获取指定的文件或者目录gi
Get-ItemProperty获取文件或目录的属性gp
Set-ItemProperty设置文件或路径的属性sp
Get-Location获取当前路径pwd
Set-Location更改当前目录的位置Cd,chdir, sl# 从环境变量中获取系统目录 (绝对路径)
Cd $env:windir
Push-Location切换到新目录,并保存当前目录pushd
Pop-Location切回上个目录popd
Invoke-Item使用对应的默认windows程序运行文件或者目录ii
Join-Path连接两个路径为一个路径
Copy-Item复制文件或者目录cp, cpiCopy-Item -recurse $home\*.ps1 ([Environment]::GetFolderPath("Desktop"))
Move-Item移动文件或者目录mi, mv, moveMove-Item ($desktop + "\*.ps1") ($desktop + "\PS Scripts")
New-Item创建新文件或者目录ni
md和mkdir相当于ni -type directory
$file = New-Item testfile.txt -type file
Remove-Item删除空目录或者文件ri, rm, rmdir,del, erase, rddel testfile.txt
Rename-Item重命名文件或者路径rni, ren
Resolve-Path处理相对路径或者包含通配符的路径,返回的是PathInfo数组,注意与gci的区别rvpa相对路径转换成绝对路径
Resolve-Path .\a.png
Split-Path提取路径的特定部分,例如父目录,驱动器,文件名
Test-Path测试指定的路径是否存在
New-PSDrive创建一个新的驱动器(本地或网络都可)New-PSDrive -name network -psProvider FileSystem -root \\127.0.0.1\share
Remove-PSDrive删除创建的驱动器,如果该驱动器正在使用则不能删除Remove-PSDrive network

像文件查找、删除等操作都支持-recursive参数

读写文本文件Set-Content/Get-Content

示例:基本使用

Set-Content -Value $content -Path D:\tmp.txt
$cache = Get-Content -Path D:\tmp.txt
查找文件(夹)Get-ChildItem/Resolve-Path

Get-ChildItem基本用法:

# 不指定目录则是当前目录
# 仅列出子节点,非递归
Get-ChildItem C:\Windows

示例:获得C:\Windows目录下所有大小超过200 bytes的文件

Get-ChildItem C:\Windows | Where-Object -FilterScript {$_.Length -gt 200}

示例:Resolve-Path查找指定文件

function edit-file([string]$path=$(Throw "请输入相对路径!")) {
	# 处理相对路径,并抑制错误
	$files = Resolve-Path $path -ea SilentlyContinue
	# 验证是否有错误产生:
	if (!$?){
		# 如果是,没有找到符合标准的文件,给出提醒并停止:
		"没有找到符合标准的文件.";
		break
	}
	# 如果返回结果为数组,表示有多个文件:
	if ($files -is [array]){
		# 此种情况下,列出你想打开的文件:
		Write-Host -foregroundColor "Red" -backgroundColor "White" `
		"你想打开这些文件吗?"
		foreach ($file in $files){
			"- " + $file.Path
		}
 
		# 然后确认这些文件是否为用户想打开的:
		$yes = ([System.Management.Automation.Host.ChoiceDescription]"&yes")
		$no = ([System.Management.Automation.Host.ChoiceDescription]"&no")
		$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no)
		$result = $host.ui.PromptForChoice('Open files','Open these files?',$choices,1)
		# 如果用户确认,使用"&"操作符启动所有的文件
		if ($result -eq 0){
			foreach ($file in $files){ & $file }
		}
	} else {
		# 如果是单个文件,可以直接使用"&"启动:
		& $files
	}
}
新建文件(夹)New-Item
# 新建一个文件夹
New-Item test/Folder -ItemType directory
# 新建一个文本文件并写数据
New-Item test/Folder/File.txt -ItemType file -Value testcontent
删除文件(夹)Remove-Item

示例:删除创建时间最早的文件夹及其子文件

$Url="F:/123"
$File=Get-ChildItem $Url -Recurse  |Where-Object {$_.PsIsContainer -eq $true}|sort CreationTime |select FullName -First 1
$Url2=$File.FullName
Remove-Item $Url2'/*' 
Remove-Item $Url2 
Write-Host -ForegroundColor Red -BackgroundColor Yellow $File.FullName 文件已经被删除。
复制文件(夹)Copy-Item

别名: cp、cpi

# splatting写法
$ArrayArguments = "C:\PowerShell.txt", "C:\test\"  
Copy-Item @ArrayArguments
# 等同于
Copy-Item -Path "C:\test1.txt" -Destination "D:\"  
文件访问权限

参考:PowerShell文件系统(五)管理访问权限

CSV文件操作

文件导入Import-Csv
$data = Import-Csv C:\ProcessInfo.csv
$data | Get-Member

XML文件操作

假设有test.xml,内容如下:

<office>
  <staff branch="Hanover" Type="sales">
    <employee>
        <Name>Tobias Weltner</Name>
        <function>management</function>
        <age>39</age>
    </employee>
    <employee>
        <Name>Cofi Heidecke</Name>
        <function>security</function>
        <age>4</age>
    </employee>
  </staff>
  <staff branch="London" Type="Technology">
   <employee>
    <Name>XXXX</Name>
    <function>gement</function>
    <age>39</age>
   </employee>
  </staff>
</office>
读文件
$xml=new-object XML
# load方法传入xml文件的相对或绝对路径
$xml.load('.\test.xml')
# 至此,test.xml已经导入完成,我们打印一下
$xml
# 输出xml为纯文本
$xml.get_InnerXml()

也可以直接通过强转的方式:

[XML]$xml=(Get-Content .\test.xml)
# 或者
$xml = [XML](Get-Content .\test.xml)

上面代码中$xml是一个XML对象,对这个对象的修改操作只是内存对象发生改变不会同步到xml文件,可以通过下节方法使修改保存到文件

写文件
$xml.Save($env:temp\updateddata.xml”)
节点访问

示例:通过属性名访问

# 也可以这样直接获取,注意这里因为有多个节点所以返回的是数组
$staff = $xml.office.staff
# 读取staff的branch属性
foreach($s in $staff){ $s.branch }
# 如果访问的是office,就不是一个数组,而是一个节点对象了
$office = $xml.office

示例:通过SelectNodes()来访问——XPath方式

# 这里拿到的是一个数组,因此可以用foreach遍历
$staff = $xml.selectNodes('/office/staff')
# XPath支持在方括号中使用通配符访问,这里只会返回第一个员工结点
$staff1 = $xml.selectNodes('/office/staff[1]')
# 返回年龄小于18岁的员工列表
$staff = $xml.selectNodes('/office/staff[age<18]')
# 返回最后一位员工信息
$staff = $xml.selectNodes('/office/staff[last()]')
# 返回第3位及以后的员工信息
$staff = $xml.selectNodes('/office/staff[position()>1]')

示例:添加新结点

# 创建新的结点:
$newemployee = $xml.CreateElement("employee")
$newemployee.set_InnerXML( `
"<Name>Bernd Seiler</Name><function>expert</function>")
# 插入新结点:
$xml.staff.AppendChild($newemployee)
# 验证结果:
$xml.staff.employee

参考:PowerShell处理XML(二)加载和处理XML文件](https://www.pstips.net/loading-and-processing-xml-files.html)

属性访问
# 获取所有属性
$xmldata.staff.get_Attributes()
# 获取指定属性
$xmldata.staff.GetAttribute("branch")
# 指定新的属性,或者更新(重写)已有的属性
$xmldata.staff.SetAttribute("branch", "New York")

HTML文件操作

文本生成html
ConvertTo-HTML

网络操作

文件下载

我们可以使用Framework中的WebClient下载文件,但是在PowerShell 3.0 中直接提供了Invoke-WebRequest

$src = 'https://www.pstips.net/index.php'
$des = "$env:temp\index.php"
Invoke-WebRequest -uri $src -OutFile $des
Unblock-File $des
网站外链检测
Function Detect-OuterWebLinks ([string]$website){
    $siteHost=([Uri]($website)).Host
    $site = Invoke-WebRequest -UseBasicParsing -Uri $website
    $site.Links | where { ([uri]($_.href)).Host -ne $siteHost } | select -ExpandProperty outerHTML
}

5. 常用.NET Framework类库

上一节中我们使用New-Object命令可以创建对象,这些对象包括.NET Framework以及COM object。

  • .NET Framework是Windows类框架库,PowerShell底层就是构建于此,相当于Windows SDK;
  • COM全称是component,一套微软定义的与语言、平台无关的组件化架构;
    在创建对象时,两者通过参数区别,不能混用,比如:
# 创建COM对象
$app = new-object -comobject "shell.application"
# 创建.NET对象
$date = new-object -typename system.datetime 2021, 4, 2

本节主要介绍.NET Framework的一些常用类。

  1. 对于陌生的类,可以到微软官网查询其构造方法及属性、方法
  2. 完整类型通常以System开头,而实际使用中允许省略System,例如[System.DateTime]可以简写为[DateTime]

日期DateTime

# 获取当前日期
$now = get-date
# 新建日期对象
$dt = new-object -typename system.datetime -argumentlist 2020, 4,2,12,58,59

随机数Random

$rand = New-Object system.random
$rand.next(1,50)

全球唯一标示符Guid

$guid = [GUID]::NewGUID()
Foreach ($format in "N","D","B","P") { "GUID with $format : {0}" -f $GUID.ToString($format)}

GUID with N : e1a5d98f4227470b84c2b37a6a8fb894
GUID with D : e1a5d98f-4227-470b-84c2-b37a6a8fb894
GUID with B : {e1a5d98f-4227-470b-84c2-b37a6a8fb894}
GUID with P : (e1a5d98f-4227-470b-84c2-b37a6a8fb894)

HTTP服务Net.HttpListener

$Port = 8888
$Url = ""
$listener = New-Object System.Net.HttpListener
$prefix = "http://*:$Port/$Url"
$listener.Prefixes.Add($prefix)
$listener.Start()

使用 $listener.GetContext() 或 $listener.BeginGetContext 都是等待任务运行完成才能处理下一个请求,如何同时处理多个请求?

HTTP客户端Net.Client(待完善)

HTTP客户端HttpWebRequest&HttpWebResponse(待完善)

二者是微软推出的新的网络请求客户端实现,用于弥补System.Net.Client的一些缺陷。

$r = [System.Net.WebRequest]::Create("https://www.pstips.net/")
$startTime = Get-Date
$resp = $r.GetResponse()
$stopTime = Get-Date
$resp.close()
$ResponseTime = ($stopTime - $startTime).TotalMilliseconds
$ResponseTime

凭据Management.Automation.PSCredential(待完善)

包含帐号、密码两个字段,用于登录凭据的本地加密存储。
示例:

$password="password"
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "Administrator" $password

$pwd=(Get-Credential -Credential 'Administrator').password

文件读写IO.File

示例:逐行读取文件

for($file=[IO.File]::OpenText("c:autoexec.bat") ; !$file.EndOfStream);$line=$file.ReadLine() ){
    $line;
}
$file.Close()

文件路径IO.Path

path = Join-Path ([Environment]::GetFolderPath("Desktop")) "test.txt"
$path
C:\Users\mosser\Desktop\test.txt

$path = [System.IO.Path]::Combine([Environment]::GetFolderPath("Desktop"), "test.txt")
$path
C:\Users\mosser\Desktop\test.txt

6. 系统接口(待完成)

所谓系统接口是指powershell中Windows平台专属接口,这部分接口在core 6.0中仅在Windows平台有效,因此有必要做一个梳理。

window注册表:PowerShell注册表(一)操作注册表的几条重要命令
关于Windows注册表:注册表

7. 调用三方软件

7.1 调用dll

假如有Math2.dll,其中有函数:

namespace Math { 
  public class Methods   { 
    public Methods() { 
    } 
    public static int CompareI(int a, int b) { 
      if (a>b)
		return a;
      else
		return b;
    } 

    public int CompareII(int a, int b)  { 
      if (a>b)
		return a;
      else
		return b;
    } 
  } 
} 
# 加载dll库
[void][reflection.assembly]::LoadFile("G:/Math2.dll")
# 调用静态方法
[Math.methods]::CompareI(10,2)
# 调用非静态方法
$a=New-Object Math.Methods
$a.CompareII(2,3)

7.2 调用VBScript

因为VbScript是基于COM的,而Powershell是基于.NET的,所以Powershell可以使用New-Object来建立一个VBScript对象。
示例:

# ScriptControl对象只在32位系统可用可用,具有局限性
$sc = New-Object -ComObject ScriptControl
$sc.Language = "VBScript"
$sc.AddCode('
Set obj = CreateObject("WScript.Shell")
obj.popup "VBScript Form",,"hello!",0
')

改进:

# 改为用WScript.Shell就可以了
$vbs = New-Object -ComObject WScript.Shell
$vbs.popup("Popup from PowerShell",$null,"PowerShell in Practice",0)

7.3 调用exe

示例1:打开notepad

notepad.exe .\test.txt

示例2:打开Powershell_ise

# 默认启动
powershell_ise.exe
# 启动并打开多个脚本文件
powershell_ise.exe ”c:\a.ps1,c:\b.ps1”
# 不加载用户的配置文件
powershell_ise.exe –file ”c:\a.ps1” –NoProfile
# 在MTA模式下不加载用户配置
powershell_ise.exe  -MTA –NoProfile
# 显示帮助信息
powershell_ise.exe  -Help

8. 在非Windows中使用powershell

以Mac为例,使用homebrew安装PowerShell Core:

# 先安装Homebre-Cask
brew tap caskroom/cask

# 直接开始安装PowerShell
brew cask install powershell

# PowerShell的升级方法
brew update
brew cask reinstall powershell

参考资料

Powershell使用哈希表

  • 7
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值