[TOC]

0x05 流(Flow)控制

描述: 当你从编写 PowerShell 单行命令转为编写脚本时,实际复杂程度没有想象的那么高。脚本只是在 PowerShell 控制台中以交互方式运行的相同或类似命令,只不过它们保存为 .PS1 文件;

0.条件判断

Where-Object 语句

描述: Where-Object会对集合逐个过滤,将符合条件的结果保留, 别名是 ?Where

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1.根据notepad进程名过滤所有记事本进程。
Get-Process | Where-Object {$_.Name -eq "notepad"}
# Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
# ------- ------ ----- ----- ----- ------ -- -----------
# 158 7 8800 37264 114 18.41 6204 notepad

# 2.根据company过滤所有产品发布者以”Microsoft”打头的进程:
Get-Process | Where-Object {$_.company -like '*Microsoft*' } | select Name,Description,Company


# 3.因为Where-Object的使用概率比较高,所以有一个很形象的别名 ? 可以使用:
Get-Service | ? {$_.Name -like "B*"}
Get-Service | where {$_.Name -like "B*"}


# 4.输出可以被2整除的值(满足则输出), 或者两者同时满足
1..10 | ? { $_ % 2 -eq 0 }
# 2
# 4
# 6
# 8
# 10
1..10 | where { $_ % 2 -eq 0 -and $_ % 4 -eq 0 }
# 4
# 8

Tips : 通过Get-Alias -Definition Where-Object利用cmdlet查看其别名


IF 语句

if…else 与 if…elseif…else 语句

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1.单条件判断
$week=(Get-Date).DayOfWeek
if ( $week -eq 'Thursday' ) {
Write-Output "今天是星期四(Thur),当前时间:"
Get-Date
} else {
Write-Output "今天不是星期四"
}


# 2.多条件判断
if ( $week -eq 'Monday' ) {
Write-Output "unhappy,今天是星期1"
} elseif ( $week -eq 'Thesday') {
Write-Output "unhappy,今天是星期2"
} elseif ( $week -eq 'Wednesday') {
Write-Output "unhappy,今天是星期3"
} elseif ( $week -eq 'Thursday') {
Write-Output "unhappy,今天是星期4"
} elseif ( $week -eq 'Friday') {
Write-Output "unhappy,今天是星期5"
} elseif ( $week -eq 'Saturday') {
Write-Output "Happy,今天是星期6"
} else {
Write-Output "Happy,今天是星期天"
}


Switch 判断语句

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 方式1.满足条件输出
$value=6
switch($value)
{
1 {"I come from Beijing"}
2 {"I come from Shanghai"}
3 {"I come from Tianjin"}
6 {"I come from Chongqing"}
}
# I come from Chongqing


# 方式2.测试取值范围
# 自定义将条件放在花括号中,必须保证条件表达式的返回值为布尔类型”$True”或”$False”
$value=18
switch($value)
{
{$_ -lt 10} {"小于10"}
10 {"等于10"}
{$_ -gt 10} {"大于10"}
}
#输出
#大于10


# 方式3.没有匹配条件时,我们需要使用Default关键字
$value=-7
# 使用 Switch 测试取值范围
switch($value)
{
{($_ -lt 10) -and ( $_ -gt 0) } {"小于10"}
10 {"等于10"}
{$_ -gt 10} {"大于10"}
Default {"没有匹配条件"}
}
# 没有匹配条件

# 如果case中有多个条件匹配,那么每个匹配的条件都会进行处理,所以我们需要使用Break关键字
$value=99
switch($value)
{
{$_ -lt 5 } { "小于5"; break}
{$_ -gt 0 } { "大于0"; break}
{$_ -lt 100} { "小于100"; break}
Default {"没有匹配条件"}
}
# 大于0


# 方式4.比较字符串(大小写敏感/通配符/正则匹配)
# 即大小写敏感比较字符串
$domain="Www.weiyigeek.top"
switch -case ($domain)
{
"Www.weiyigeek.top" {"Ok 1"}
"www.weiyiGeek.com" {"Ok 2" }
"WWW.weiyiGeegk.COM" {"Ok 3"}
}

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

$mail="[email protected]"
#使用通配符
switch -regex ($mail)
{
"^master" {"master"}
"top$" {"top结尾" }
"d{1,3}.d{1,3}.d{1,3}.d{1,3}" {"IP地址"}
}
#www打头
#com结尾


# 方式5.Switch支持对集合所有元素进行匹配,例如使用 Switch语句演示打印水仙花数(即该数得每个数的三次方只和=该数)
$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


1.循环执行

描述: PowerShell 优势是确定了如何为某个项执行某些操作后,就可以很容易地为数百个项执行相同的任务。 只需使用 PowerShell 中多种不同类型的循环之一循环访问这些项即可。

ForEach-Object 语句

描述: ForEach-Object 是用于循环访问管道中的项的 cmdlet,例如使用 PowerShell 单行命令然后通过管道流式处理对象。其实是ForEach-Object可以接受三个脚本块用于管道的流模式处理,分别代表begin,process和end

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 方式1.采用 ForEach-Object 循环处理
WeiyiGeek> 1..10 | ForEach-Object { Write-Host -NoNewline "当前 $_ 次序 ->"; $_ * 10}
当前 1 次序 ->10
当前 2 次序 ->20
当前 3 次序 ->30
当前 4 次序 ->40
当前 5 次序 ->50
当前 6 次序 ->60
当前 7 次序 ->70
当前 8 次序 ->80
当前 9 次序 ->90
当前 10 次序 ->100

# 方式2.在下面的情形中,如果我想通过管道将两个字符串按值传递到 Get-Command 以便与 Module 参数一起使用,则需要使用 ForEach-Object cmdlet。
'ActiveDirectory', 'SQLServer' |
ForEach-Object {Get-Command -Module $_} |
Group-Object -Property ModuleName -NoElement |
Sort-Object -Property Count -Descending
# Count Name
# ----- ----
# 147 ActiveDirectory
# 82 SqlServer

'Get-ComputerInfo', 'Test-Path' |
ForEach-Object {Get-Command -Name $_} |
Group-Object -Property ModuleName -NoElement |
Sort-Object -Property Count -Descending
# Count Name
# ----- ----
# 2 Microsoft.PowerShell.M...


# 方式3.采用 % 以及别名进行使用处理
1..5 | % { Write-Host -NoNewline "第{$_}次时间:"; Get-Date;Start-Sleep 1 }
# 第{1}次时间:
# 2021年4月1日 16:58:55
# 第{2}次时间:2021年4月1日 16:58:56
# 第{3}次时间:2021年4月1日 16:58:57
# 第{4}次时间:2021年4月1日 16:58:58
# 第{5}次时间:2021年4月1日 16:58:59


# 方式4.采用 foreach 别名进行使用处理
foreach ( $a in 1..3) { foreach ( $b in 1..3) { Write-Host -NoNewline "$a + $b = "; $a + $B } }
# 1 + 1 = 2
# 1 + 2 = 3
# 1 + 3 = 4
# 2 + 1 = 3
# 2 + 2 = 4
# 2 + 3 = 5
# 3 + 1 = 4
# 3 + 2 = 5
# 3 + 3 = 6

foreach ($Computer in 'DC01', 'WEB01') {
Get-ADComputer -Identity $Computer
}

# 方式5.逐个处理所有管道结果(个性化- 值得学习类似于print %s 占位符)
ls | ForEach-Object {"文件名:{0} 文件大小{1}KB: " -f $_.Name,($_.length/1kb).tostring()}
文件名:a.html 文件大小65.99609375KB:
文件名:a.txt 文件大小25.765625KB:
文件名:alias 文件大小11.77734375KB:


For 语句

描述: 当指定的条件为 true 时,for 循环会进行循环访问,在PS中应该使用较少。

1
2
3
4
5
6
# (1) 在前面的示例中,循环从数字 1 开始循环访问 4 次,并在计数器变量 $i 小于 5 时继续循环访问。 
# 休眠时间共计 10 秒。
for ($i = 1; $i -lt 5; $i++) {
Write-Output "Sleeping for $i seconds"
Start-Sleep -Seconds $i
}


Do 语句

描述: PowerShell 中有两个不同的 do 循环。

  • 指定的条件为 false 时, Do Until 运行。
  • 指定的条件为 True 时, Do While 运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# (1) 数字游戏,在你猜测的值等于 Get-Random cmdlet 生成的相同数字时游戏结束。
$number = Get-Random -Minimum 1 -Maximum 10
do {
$guess = Read-Host -Prompt "What's your guess?"
if ($guess -lt $number) {
Write-Output 'Too low!'
}
elseif ($guess -gt $number) {
Write-Output 'Too high!'
}
}
until ($guess -eq $number)


# (2) 只通过将测试条件反转为不等于,使用 Do While 循环可实现相同的结果。
$number = Get-Random -Minimum 1 -Maximum 10
do {
$guess = Read-Host -Prompt "What's your guess?"
if ($guess -lt $number) {
Write-Output 'Too low!'
} elseif ($guess -gt $number) {
Write-Output 'Too high!'
}
}
while ($guess -ne $number)

Tips : Do 循环始终运行至少一次,因为将在循环结束时计算条件的结果。


While 语句

描述: 与 Do While 循环类似,只要指定的条件为 true,While 循环就会运行。 但差别在于 While 循环会在运行任何代码之前,计算循环顶部条件的结果, 如果条件计算结果为 false,它就不会运行代码块中的内容。

1
2
3
4
5
6
7
8
9
10
# 1.使用案例
$week = (Get-Date).DayOfWeek
while ( $week -eq 'Thursday') {
Write-Output "今天是星期三将在此基础上加1天: "
$date = (Get-Date).AddDays(1)
break
}
Write-Output $date
# 今天是星期三将在此基础上加1天:
# 2021年4月2日 17:23:49

Tips : 从 PowerShell 版本 3.0 开始,可以使用 $PSItem 而不是 $。 但我发现,大多数经验丰富的 PowerShell 用户仍更喜欢使用 $,因为它向后兼容,需要输入的内容更少。

Tips : 使用 foreach 关键字时,必须先将所有项存储在内存中,然后才能循环访问这些项,如果不知道要处理的项数,此操作可能会很困难。


Switch 循环语句

描述: Switch 本是多路分支的关键字,但是在Powershell中由于Switch支持集合,所以也可以使用它进行循环处理。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1.使用Switch循环
Switch (10..1)
{
Default { " n = $_" }
}
# n = 10
# n = 9
# n = 8
# n = 7
# n = 6
# n = 5
# n = 4
# n = 3
# n = 2
# n = 1

# 2.有时对集合的处理,在循环中还须条件判断,使用Switch循环可以一部到位例如奇数和偶数
$nums = 10..7
Switch ($nums)
{
{($_ % 2) -eq 0} {"$_ 偶数"}
{($_ % 2) -ne 0} {"$_ 奇数"}
}
# 10 偶数
# 9 奇数
# 8 偶数
# 7 奇数


2.终止操作

描述: 如何在循环中当满足某些条件时跳出循环, 我们可以采用以下关键字 Break、Continue 和 Return

  • 1) Break 旨在中断循环。它通常与 switch 语句一起使用。
  • 2) Continue 旨在跳到循环的下一次迭代。它通常与 while 语句一起使用。
  • 3) Return 旨在退出现有作用域。

基础案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1.示例中所示的 break 语句导致循环在第一次迭代时退出。
for ($i = 1; $i -lt 5; $i++) {
Write-Output "Sleeping for $i seconds"
Start-Sleep -Seconds $i
break
}

# 2.示例中,将输出数字 1、2、4 和 5。 它跳过数字 3,并继续执行循环的下一次迭代。与 break 类似 continue 将中断除当前迭代以外的循环。
# Execution 将继续进行下一次迭代,而不是中断循环并停止。(此处 $i 默认为0)
while ($i -lt 5) {
$i += 1
if ($i -eq 3) {
continue
}
Write-Output $i
}

# 3.示例中,return 输出第一个结果,然后退出循环
$number = 1..10
foreach ($n in $number) {
if ($n -ge 4) {
Return $n
}
}


0x06 PS数组和哈希表

描述: 我们理解的数组是存储同一类型的数据集合,而Powershel中可以设置为多种数据格式。PS会将命令执行后的结果文本按每一行作为元素存为数组,在命令的返回值不止一个结果时,PS也会自动把结果存储为数组。

PS 中数组的特征:

  • 1.数组的多态性: PS中数组像变量一样如果数组中元素的类型为弱类型,默认可以存储不同类型的值。
  • 2.数组的有序性: PS数组在内存中是顺序存储的,所以数组的大小必须是确定的方便分配存储空间。
  • 3.数组的引用性: 使用默认的的赋值运算符在两个变量之间赋值只是复制了一个引用两个变量共享同一份数据,则改变一个另一个也会相应的改变;


常规数组

描述: 前面我们说明Powershell会把命令执行返回文本按每一行作为元素存为数组

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 0.判断一个变量是否为数组
"WeiyiGeek" -is [array] # 它是一个字符串类型
False
"WeiyiGeek".toCharArray() -is [array]
True

# 1.PS将命令执行后返回的结果存入到数组之中
PS > $IP=IPCONFIG
# 判断一个变量是否为数组
PS > $IP -is [array]
True

PS > $IP.getType()
# IsPublic IsSerial Name BaseType
# -------- -------- ---- --------
# True True Object[] System.Array

# 看数组的元素个数用$array.Count属性
PS > $IP.Count
32
# 访问第x个元素,使用$array[x-1],因为数组是以0开始索引的。
PS > $IP[4]
# 以太网适配器 以太网:
PS > $IP[8]
# IPv4 地址 . . . . . . . . . . . . : 10.0.0.19

思考为什么不愿把IPconfig返回的结果称为对象?
答:因为它不是真正Cmdlet命令,真正的Powershell命令返回的数组元素可不止一个字符串,它是一个内容丰富的对象。


1.数组创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
PS > $array[email protected]()        #使用@()创建一个空数组
$a=,"WeiyiGeek" #创建1个元素的数组
PS > $array=1,2,3,4,5 #常规定义数组
PS > $array=1..5 #连续的数字数组(推荐方式)
PS > $array=1,1.2,"String" #多种数据类型融合的数组
PS > $array -is [array] # 元素是否为数组 True
PS > $array.Count # 元素个数 3
PS C:\Users\WeiyiGeek> $array = @(1,'hello')
PS C:\Users\WeiyiGeek> $array
# 1
# hello
PS C:\Users\WeiyiGeek> $array.GetType()
# IsPublic IsSerial Name BaseType
# -------- -------- ---- --------
# True True Object[] System.Array

# [突发奇想]给数组添加数组会进行字符串拼接
PS > $arr=1..3
PS > $arr+=5..8
PS > $arr
1
2
3
5
6
7
8

# 所以给数组增加元素其实相当于创建一个新的数组,只不过之后会把原来的副本删除。在当前数组追加元素可以使用“+=”操作符。
PS C:Powershell> $books="元素1","元素2","元素3"
PS C:Powershell> $books+="元素4"
PS C:Powershell> $books
元素1
元素2
元素3
元素4

# 数组之间可以直接追加元素或者删除元素
PS > $arr = 1..3
PS > $arr = $arr[1..2] + 4..6
PS > $arr
2
3
4
5
6

Tips : 命令返回的数据是数组类型,数组的每一个元素存放的是一个System.IO.DirectoryInfo对象。


2.数组访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS > $arr
PS > $arr[0] # 元素都是从0下标开始索引的
1

PS > $arr[($arr.Count-1)] # 方式1.输出最后一个元素
PS > $arr[-1] # 方式2
8

PS > $arr[0,1,3,5] # 一次输出多个元素 (值得学习)
1
2
5
7

PS > $arr[($arr.Count)..0] # 将数组逆序输出 (值得学习)
8
7
6
5
3
2
1


3.复制数组
描述:复制数组最好使用Clone()方法,除非有特殊需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS > $arr=1..3
PS > $arr1=$arr
# 1.两个变量指向同一个地址的数组(表示是相同的)
PS > $arr1.Equals($arr)
True
# 2.一个新的数组不是两个变量指向同一个数组
PS > $arr2=$arr.Clone()
PS > $arr2.Equals($arr) #这里是为False
False
PS > $arr2[0]=1024
PS > $arr2
1024
2
3


数组类型

描述:数组一般具有多态性,如果你不指定元素的具体类型,解释器会自动选择合适的类型存储每个元素

  • 如果要统一限制所有元素的类型,可是使用类型名和一对方括号作为数组变量的类型。
  • 每当赋值时会自动类型检查, 如果目标数据类型不能转换成功,就会抛出一个异常。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 示例1
PS > [int[]] $num = @()
PS > $num += 1024
PS > $num += 3.1415926 #这里由于强制类型的作用(会直接忽略小数点后的数字)
PS > $num += 999
PS > $num
1024
3
999

# 示例2
[string[]] $str = @()
$str += "Name"
$str += "is"
$str += "WeiyiGeek"
$str += 1024 # 这里由于强制类型的作用(会将1024转换为字符串而非整形)
$str.GetType().fullname
System.String[]


哈希表(Hash)

描述:哈希表存放的是键值对(Key-Value),在哈希表中不再仅仅限制使用数字寻址,可以使用任意类型的数据类型寻址

Tips: 在创建哈希表时就使用数组,因为创建数组和哈希表的的元素关键字不冲突一个是逗号,一个是分号

基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 1.使用@{}创建哈希表
PS > $hash[email protected]{ Name="WeiyiGeek";Age="15" } #创建一个HASH表示
PS > $hash['Name'] #使用哈希表的键访问对应的值
WeiyiGeek
PS > $hash.Keys #HASH表的所有键
Age
Name
PS > $hash.Values #HASH表的所有值
15
WeiyiGeek
PS > $hash.Count #HASH表的键值对数量
2


# 2.在哈希表中存储数组
PS > $hash[email protected]{ Name="WeiyiGeek";Age="15"; Books="C","C++","PYTHON"}
PS > $hash.Sex="Woman"
PS > $hash
# Name Value
# ---- -----
# Books {C, C++, PYTHON}
# Name WeiyiGeek
# Age 15
# Sex Woman


# 3.在哈希表中存储数组哈希表值的更新和删除
PS > $hash.Name="weiyi"
PS > $hash.remove("Books")
PS > $hash
# Name Value
# ---- -----
# Name weiyi
# Age 15
# Sex Woman


# 4.使用哈希表格式化输出
# PS许多命令的输出结果都是以表格的形式,当然可以使用Format-Table自定义表格格式,例如:
PS C:Powershell> Dir | Format-Table FullName,Mode #命令只能限制表格输出那些列,隐藏那些列,但是对于列的宽度,列标题无能为力
# FullName Mode
# -------- ----
# C:PowershellABC d----
# C:Powershellmyscript d----
# C:Powershella.html d----

# 表格的每一个列包含四个属性,我们可以采用哈希表来修改属性以自定输出格式。
# * Expression:绑定的表达式
# * Width:列宽度
# * Label:列标题
# * Alignment:列的对齐方式
PS > $column1 = @{expression="Name"; width=30;label="filename"; alignment="left"}
PS > $column2 = @{expression="LastWriteTime"; width=40;label="last modification"; alignment="right"}
PS > ls | Format-Table $column1, $column2
# filename last modification
# -------- -----------------
# .android 2019/7/26 8:45:03
# .config 2019/8/19 17:27:45


# 5.哈希表迭代集群注意事项当直接将Hash赋值给变量时两个指向的是同一个Hash对象。( 巨坑之处 - 注意区别)
$SysPrivilege = @{
name = "weiyigeek"
result = @()
}
$demo1 = $SysPrivilege
$Hash = $SysPrivilege.Clone() # 采用 Clone() 将该Hash复制到另外一个变量之中它将是独立的不受$SysPrivilege增加与减少的影响
$SysPrivilege.age=18
$demo1 # 实际是 == $SysPrivilege 两者指向同一个对象
# Name Value
# ---- -----
# result {}
# name weiyigeek
# age 18
$Hash
# Name Value
# ---- -----
# result {}
# name weiyigeek

补充说明: 在Hash键值对的与文件中的某一行字符进行验证是否匹配时采用.Equals()时返回为False,一定要注意文件中是否带有\"\"在进行匹配时也必须带上其”单引号字符进行匹配,坑呀(一下午就荒废在这里了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$TestString = @{value='WeiyiGeek';}
"WeiyiGeek".Equals("$($TestString.value)") # True
"WeiyiGeek".Equals($TestString.value) # True
"WeiyiGeek".Equals($TestString.value.ToString()) # True
"WeiyiGeek" -eq "$($TestString.value)" # True
"WeiyiGeek" -eq "$TestString.value" # False

# 前者从文件中读取后者从Hash键值对中获得
$TestString = @{value='"WeiyiGeek_admin"';value1='"Guest"'}
"WeiyiGeek_admin" Administrator
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
"Guest" Guest
True True String System.Object
True True String System.Object


0x07 函数(Function)

描述: 函数就是将大大小小的命令组合成一个集合里面进行执行,有利于提高代码复用以及简练代码量;

函数三原则:

  • 简短:函数名简短,并且显而易见。
  • 聚合:函数可以完成多个操作。
  • 封装和扩展:将一批Powershell语句进行封装,实现全新的功能需求。


函数结构

描述: 由三部分组成:函数名,参数,函数体

1
2
3
4
Function 函数名称 (args[])
{
code; # 函数体
}


函数定义

描述: 主要针对函数的创建、调用、更新和删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# (1) 使用函数作为别名函数的创建
function Test-conn {
Test-Connection -Count 2 -ComputerName $args
}
Set-Alias tc test-conn
PS C:\PS> tc localhost


# (2) 函数调用
Function getHostName {
Write-Host -NoNewline "当前机器名称: "
HOSTNAME.EXE
}
getHostName
# 当前机器名称: WeiyiGeek


# (3) 函数更新
$function:getHostName # 直接输出函数体,所以我们可以通过先将函数定义导出到ps文件,然后在进行编辑更更新
# Write-Host -NoNewline "当前机器名称: "
# HOSTNAME.EXE
$function:getHostName | Out-File getHostName.ps1
PS C:\Users\WeiyiGeek> .\getHostName.ps1
当前机器名称: WeiyiGeek

# (4) 删除函数
# Tips: 控制台定义的函数只会在当前会话生效,一旦控制台退出会自动消失,在不关闭控制台的条件下删除一个已经定义好的函数可是使用del命令的方法:
Remove-Item function:getHostName # 或者 del function:getHostName


函数参数

描述: PS 函数可以接受参数并对参数进行处理,函数的参数有3个特性:

  • 1.任意参数:内部变量 $args 接受函数调用时接受的参数,它是一个数组类型。
  • 2.命名参数:函数的每一个参数可以分配一个名称,在调用时通过名称指定对应的参数。
  • 3.预定义参数:函数在定义参数时可以指定默认值,如果调用时没有专门指定参数的值,就会保持默认值。

(1) 万能参数

描述 : $args 变量是万能参数它可以识别任意个参数,在PS编程开发中非常有用。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1.万能参数使用演示
function say-Hello
{
if($args.Count -eq 0){
"No argument!"
}else{
# 包含在字符串中可以使用$()包含并解析变量。
$args | foreach {"Hello,$($_)"}
}
}
# 无参数调用时:
PS C:\Users\WeiyiGeek> say-Hello
No argument!
# 一个参数调用:
PS C:\Users\WeiyiGeek> say-Hello WeiyiGeek
Hello,WeiyiGeek
# 多个参数调用时:
PS C:\Users\WeiyiGeek> say-Hello WeiyiGeek Computer
Hello,WeiyiGeek
Hello,Computer


# 2.任意参数求和
function Add {
$sum=0
$args | foreach { $sum += $_ }
$sum
}
PS C:\Users\WeiyiGeek> Add 1 2 3 4 5
15


(2) 参数名称设置

描述: 我们可以设置指定名称的传入参数的名称并进行调用, 它可以有两种形式进行调用一是通过顺序传入、二是通过指定参数名称;

注意: 传入参数我们可以对其设置强弱类型的参数, 由于函数的参数解释器比较傲慢,它对你提供的参数的信息完全不关心。它只会粗略地将参数进行分割,并且最大限度的进行自动类型转换。在实际编程过程中我们常常需要限定用户输入类型(防止搞破坏)

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 1.使用弱类型参数
function StringContact($str1,$str2)
{
return $str1+$str2
}
StringContact Weiyi Geek
StringContact -str1 Weiyi -str2 Geek
# WeiyiGeek
# WeiyiGeek
StringContact 16 50
# 66

# 2.使用强类型参数(整型、浮点型、日期类型)
function subtract([int]$value1,[int]$value2)
{
return $value1-$value2
}
subtract 3 5
# -2

function subtract([double]$value1,[double]$value2)
{
return $value1-$value2
}
subtract 8.8 7.9
# 0.9

# 函数的参数解释器会自动尝试将字符串转换成日期类型,如果转换失败就是抛出异常。
function DayOfWeek([datetime]$date)
{
return $date.DayOfWeek
}
DayofWeek '1927-8-1'
# Monday
DayofWeek 2008-8-1
# Friday


(3) 参数定义默认值

描述: 我们可以对传入的参数设置默认的值,当且仅当没有传入参数时。

基础示例:

1
2
3
4
5
6
7
8
function defaultStringContact($str1="WeiyiGeek",$str2="-Computer")
{
return $str1+$str2
}
PS C:\Users\WeiyiGeek> defaultStringContact Good Luck
# GoodLuck
PS C:\Users\WeiyiGeek> defaultStringContact
# WeiyiGeek-Computer


(4) Switch 参数

描述: Powershell 函数最简单的参数类型为布尔类型,除了使用Bool类型,也可以使用Switch关键字。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 主要功能是逆转字符串但是可以通过 $try 参数进行控制,如果没有指定$try的值默认值为$false;
function tryReverse( [switch]$try , [string]$source )
{
[string]$target=""
if($try)
{
for( [int]$i = $source.length -1; $i -ge 0 ;$i--)
{
$target += $source[$i]
}
return $target
}
return $source
}
tryReverse -source www.weiyigeek.top
# www.weiyigeek.top
tryReverse -try $true -source www.weiyigeek.top
# pot.keegiyiew.www


函数返值

描述: PS 它的函数可以有多个返回值, 如果你直接调用函数,返回值会在控制台输出,当然我们通常是将结果存储在一个变量中进一步处理。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 1.例如求某值的平方返回一个值
function Square([double]$num)
{
return $num*$num
}

# 2.例如计算1gb是多少MB/KB/B返回多个值
function gbMeasure($amount)
{
# 注意引用变量的方式
"$amount GB=$($amount) GB"
"$amount GB=$($amount*1gb/1mb) MB"
"$amount GB=$($amount*1gb/1kb) KB"
"$amount GB=$($amount*1gb) B"
}
#函数返回4个值
gbMeasure 1
# 1 GB=1 GB
# 1 GB=1024 MB
# 1 GB=1048576 KB
# 1 GB=1073741824 B
#将所有的返回值存储在一个变量(自动存储在数组)中
$result=gbMeasure 1
$result # 输出结果同上
#通过索引访问每个返回值
$result=gbMeasure 1
$result[3]
# 1 GB=1073741824 B

# 3.一个函数返回了一个值还是多个值是可以验证的(值得学习)。
Function lottery([int]$number=1)
{
$rand = New-Object system.random
For ($i=1; $i -le $number; $i++) {
$rand.next(1,50)
}
}
# 参数为空时,返回值不是数组:
$result = lottery
$result -is [array]
# False
# 如果指定多个随机数是,返回值是数组类型:
$result = lottery 10
$result -is [array]
# True


# 4.函数默认会将函数中的所有输出作为函数的返回值返回,但在实际情况中可能会将不必要的输出误以为返回值,此时我们可以使用Write-Host或者Write-Debug命令只输出特定的步骤中的内容,而不作为返回值。
Function Test()
{
Write-Host "Try to calculate."
"3.1415926"
Write-Debug "Done."
}
# 测试返回值
$value
# 3.1415926

# 在变量值中保存返回值,在控制台输出注释行,注意Debug调试信息只会在调试模式下被输出
$DebugPreference="Continue"
$value=Test
# Try to calculate.
# 调试: Done.

Tips: 如果一个函数返回一个值像其它编程语言一样,这个值包括她的类型信息会直接返回
Tips: 如果遇到多个返回值,Powershell会将所有的返回值自动构造成一个Object数组。可以通过索引访问数组。

Tips: 使用Write-Debug有两个优势,首先调试信息会自动高亮显示,便于分析。其次这些调试信息只会在调试模式开启时输出,控制起来更加方便。当然最重要的是这些临时信息无论什么时候也不会混淆在返回值。

Tips: 如果你想通过显示调试信息调试函数可以开启调试模式, 如果关闭调试模式,这些调试信息自然不会输出。

1
2
3
4
# 开启调试模式
$DebugPreference="Continue"
# 关闭调试模式
$DebugPreference="SilentlyContinue"


函数支持

描述: Powershell已经提供了许多用户能够使用的预定义函数, 我们可以通过Function:进行查看有哪些预定义函数。

Powershell中的这些预定义的函数可以做很多重要的工作,例如我们常常使用的命令其实都是PS的预定义函数。

  • X: 调用Set-Location定位到指定的驱动器根目录
  • prompt 返回提示文本
  • Clear-Host 清除屏幕的缓存
  • help,man 查看命令的帮助文档
  • mkdir,md 通过new-Item创建子目录
  • more 分屏输出管道结果
  • TabExpansion Tab键的自动完成提示


基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1.预定义函数查看
> dir function: | ft -AutoSize
# CommandType Name Version Source
# ----------- ---- ------- ------
# Function A:
# Function Add-InitiatorIdToMaskingSet 2.0.0.0 Storage
...

# 2.查看预定义函数定义
$Function:A:
# Set-Location $MyInvocation.MyCommand.Name
$Function:psEdit
# param([Parameter(Mandatory=$true)]$filenames)
# foreach ($filename in $filenames)
# {
# dir $filename | where {!$_.PSIsContainer} | %{
# $psISE.CurrentPowerShellTab.Files.Add($_.FullName) > $null
# }
# }
# 如果一个函数名中包含了特殊字符就应当把它放在花括号中
${function:Clear-Host}
# $RawUI = $Host.UI.RawUI
# $RawUI.CursorPosition = @{X=0;Y=0}
# $RawUI.SetBufferContents(
# @{Top = -1; Bottom = -1; Right = -1; Left = -1},
# @{Character = ' '; ForegroundColor = $rawui.ForegroundColor; BackgroundColor = $rawui.BackgroundColor})

# 3.看当前Powershell环境中定义了多少个函数
(dir function:).count
201


自定义终端字符串
描述: 我们可以通过覆盖prompt函数来重写命令行显示,简单示例1如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 1.下面的函数会首先设置窗口标题,然后设置命令输入显示字符串行
function prompt
{
# 使用 $Host.UI.Rawui 环境变量
$host.ui.rawui.WindowTitle = "Line: " + $host.UI.RawUI.CursorPosition.Y + " " + $env:USERNAME + " " + $Host.Name + " " + $Host.Version
# 记住当前光标的位置 ,以及当前窗口的宽度
$curPos = $host.ui.rawui.CursorPosition
$widthPos = $host.ui.rawui.WindowSize.Width
$newPos = $curPos
# 然后在横坐标上增加$widthPos - 28个占位符,然后重置光标的位置至当前位置,
$newPos.X+=( $widthPos - 24 )
$host.ui.rawui.CursorPosition = $newPos
Write-Host ("{0:D} {0:T}" -f (Get-Date)) -foregroundcolor Yellow
# 最后通过prompt函数回复光标的原始位置。****
$host.ui.rawui.CursorPosition = $curPos
Write-Host ("["+$(whoami) + "] - PS " + $(get-location) +">") -nonewline -foregroundcolor Green
" "
}

# 补充说明: POWERSHELL 控制台标题修改以及函数封装`$host.ui.RawUI.WindowTitle="自定义标题"` 非常值得学习默认是没有Title的命令
title web
# title: The term 'title' is not recognized as a name of a cmdlet, function, script file, or executable program.
# Check the spelling of the name, or if a path was included, verify that the path is correct and try again.


# 创建用户自定义的ps1文件并进行创建
New-Item -path $profile -type file -force
code $profile
# 在打开的profile文件里面保存如下代码
function Set-WindowTitle {
$host.UI.RawUI.WindowTitle = [string]::Join(" ", $args)
}
Set-Alias -name "title" -value Set-WindowTitle

# 运行显示结果
[weiyigeek\weiyigeek] - PS C:\Users\WeiyiGeek> whoami 202141813:44:50
weiyigeek\weiyigeek

WeiyiGeek.命令行标题及命令行显示设置

WeiyiGeek.命令行标题及命令行显示设置


简单示例2: 使用WindowsPrincipal 辨别当前用户是否使用了管理员权限,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 采用.net对象的方式进行获取
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = new-object System.Security.principal.windowsprincipal($CurrentUser)
$global:Admin = $principal.IsInRole( [System.Security.Principal.WindowsBuiltInRole]::Administrator)
Function prompt
{
# 输出标准的提示信息:
Write-Host ("PS " + $(get-location)) -nonewline
# The rest depends on whether you have admin rights or not:
If ($admin)
{
$oldtitle = $host.ui.rawui.WindowTitle
# 将"Administrator: " 显示在标题栏
If (!$oldtitle.StartsWith("Administrator: "))
{
$host.ui.rawui.WindowTitle ="Administrator: " + $oldtitle
}
# Prompt结尾显示红色的尖括号
Write-Host ">" -nonewline -foregroundcolor Red
}
Else
{
Write-Host ">" -nonewline
}
return " "
}
# 运行显示结果
没有管理员权限时,标题栏文本:Windows Powershell
有管理员权限时,标题栏文本: Administrator :管理员 : Windows Powershell


管道函数

描述: 一个函数能够访问和进一步处理另外一条命令的结果,就是我们前面所讲的管道。

Tips: 这里在补充一哈管道的两种模式,一种是顺序处理模式,一种是流处理模式

  • (1) 低效率的顺序模式:$input 默认情况下你的函数不是真正支持管道,只能对前一个命令执行后的结果处理,即前一个命令执行的结果通过被自动保存在$input变量(数组)中,它可以包含许多元素,一个元素,甚至一个元素都没有,这取决于具体的环境。

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1.简单的顺序模式
Function output { $input }
PS > 1,2,3 | output
PS > dir | output

# 2.函数将会对管道的结果,做进一步处理例如标记指定目录中的特点格式的文件,并高亮标记后缀名为png的文件名为红色。
Function funPipeline
{
# 保存控制台当前的前景色
$oldcolor = $host.ui.rawui.ForegroundColor
# 通过循环逐条检查管道的结果
Foreach ($element in $input)
{
write-Host $element.name
# 如果后缀名为.png,设置为前景色为红色
If ($element.name.toLower().endsWith(".png"))
{
# 可用颜色: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
$host.ui.Rawui.ForegroundColor = "red"
}Else{
# 否则恢复默认的前景色
$host.ui.Rawui.ForegroundColor = $oldcolor
}
# 输出数组元素
$element
}
# 最后,重置控制台的前景色:
$host.ui.Rawui.ForegroundColor = $oldcolor
}

# 执行命令
[weiyigeek\weiyigeek] - PS C:\Users\WeiyiGeek\Pictures> ls | funPipeline 2021年4月18日 14:17:47

WeiyiGeek.管道函数的处理

WeiyiGeek.管道函数的处理

  • (2) 高效率的流模式(过滤器): 针对之前funPipeline函数,你只需要替换”function” 关键字 为 “filter”,它就会开始流模式处理,这样你再也不用过分的担心忍受程序的无休止的响应和崩溃的危险。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 1.你也可以递归处理全盘目录,甚至处理极其庞大的数据。对于过滤器filters来说$input 一直都是一个独立的元素,所以在过滤器中$input一点用也没有的道理
    filter filPipeline
    {
    # 保存控制台当前的前景色
    $oldcolor = $host.ui.rawui.ForegroundColor
    # 如果后缀名为.png,设置为前景色为红色
      # 当前的管道元素保存在 $_ 变量中
    If ($_.name.toLower().endsWith(".png")){
    $host.ui.Rawui.ForegroundColor = "Darkred"
    }Else{
    # 否则恢复默认的前景色
    $host.ui.Rawui.ForegroundColor = $oldcolor
    }
    # 输出当前元素
    $_
    # 最后,重置控制台的前景色:
    $host.ui.Rawui.ForegroundColor = $oldcolor
    }

    Dir . -recurse | filPipeline


Q: 如何开发真正的管道函数

答: 如果一个函数内部使用了管道可以定义三个基础的代码块实现函数:第一步完成函数的初始化(Begin),完成函数执行的预备步骤;第二步处理递归调用所得的结果(Process), 最后进行收尾工作(End);

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Function filterPipeline
{
begin
{
# 记录控制台的背景色
$oldcolor = $host.ui.rawui.ForegroundColor
}

process
{
# 当前管道的元素 $_ 重点
# 如果后缀名为 ".exe",
# 改变背景色为红色:
If ($_.name.toLower().endsWith(".exe"))
{
$host.ui.Rawui.ForegroundColor = "red"
} Else {
# 否则, 使用正常的背景色:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
# 输出当前的背景色
$_
}

end
{
# 最后,恢复控制台的背景色:
$host.ui.Rawui.ForegroundColor = $oldcolor
}
}

# 效果执行
ls C:\WINDOWS\System32\ | filterPipeline


Tips : 管道的低效率顺序模式在处理大容量数据时很容易出现问题,其结果是巨大的内存占用和进程等待。
Tips : 如果你的函数支持高效率的流模式,在处理管道结果时仅占用很小的内存。
Tips : 过滤器在函数中属于高级应用 (特殊的函数),因为它可以立即处理管道结果的每一个元素, 但是过滤器必须每次重复执行预定义命令的结果。