PowerShell 技能连载 - 创建快速的 Ping(第六部分)

这是我们迷你系列的最后一部分,向我们超快的 Test-OnlineFast 函数添加管道功能。您现在可以像这样将计算机名通过管道传给函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> 1..200 | ForEach-Object { "192.168.189.$_" } | Test-OnlineFast

Address Online DNSName Status
------- ------ ------- ------
192.168.189.200 True DESKTOP-7AAMJLF.fritz.box Success
192.168.189.1 True fritz.box Success
192.168.189.65 True mbecker-netbook.fritz.box Success
192.168.189.29 True fritz.repeater Success
192.168.189.64 True android-6868316cec604d25.fritz.box Success
192.168.189.112 True Galaxy-S8.fritz.box Success
192.168.189.142 True Galaxy-S8.fritz.box Success
192.168.189.129 True iPhonevMuzaffer.fritz.box Success
192.168.189.10 False Request Timed Out
192.168.189.100 False Request Timed Out
(...)

当然,您也可以传递普通参数给这个函数:

1
2
3
4
5
6
7
PS> Test-OnlineFast -ComputerName google.de, microsoft.com, 127.0.0.1

Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
microsoft.com False Request Timed Out

您甚至可以使用其它 cmdlet 的结果,假设您选择了希望传给该函数的属性。一下这行代码 ping 您 Active Directory 中的所有计算机(您最好稍微做一下限制,以免耗尽资源):

1
PS> Get-ADComputer -Filter * | Select-Object -ExpandProperty DnsHostName | Test-OnlineFast

PowerShell 技能连载 - 创建快速的 Ping(第五部分)

在前一个技能中我们创建了一个名为 Test-OnlineFast 的高速的新的 PowerShell 函数,它使用 WMI 来高速 ping 任意数量的计算机。今天我们将通过向 ping 的结果增加一些列额外的属性使它变得更有用。

先让我们检查 Test-OnlineFast 是如何工作的。以下是一些例子。我们先 ping 一系列计算机。您既可以使用计算机名也可以使用 IP 地址:

1
2
3
4
5
6
7
8
PS> Test-OnlineFast -ComputerName google.de, powershellmagazine.com, 10.10.10.200, 127.0.0.1

Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
powershellmagazine.com True powershellmagazine.com Success
10.10.10.200 False Request Timed Out

我们现在 ping 整个 IP 地址段。以下例子是从我们的公共酒店 WLAN 中执行的(请将 IP 范围调整为您所在的网络):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> $iprange = 1..200 | ForEach-Object { "192.168.189.$_" }

PS> Test-OnlineFast -ComputerName $iprange

Address Online DNSName Status
------- ------ ------- ------
192.168.189.200 True DESKTOP-7AAMJLF.fritz.box Success
192.168.189.1 True fritz.box Success
192.168.189.134 True PCSUP03.fritz.box Success
192.168.189.29 True fritz.repeater Success
192.168.189.64 True android-6868316cec604d25.fritz.box Success
192.168.189.142 True Galaxy-S8.fritz.box Success
192.168.189.65 True mbecker-netbook.fritz.box Success
192.168.189.30 True android-7f35f4eadd9e425e.fritz.box Success
192.168.189.10 False Request Timed Out
192.168.189.100 False Request Timed Out
192.168.189.101 False Request Timed Out
(...)

神奇的是它的超快速度。ping 整个子网只用了几秒。

现在,我们来看看这个函数。在前面的技能中我们解释了其中的一部分。这个版本向 ping 的结果增加了有用的属性,例如 OnlineDnsName,它返回关于 ping 状态的友好文本,而不是幻数。所有这些是通过计算属性的哈希表实现的,基于 ping 返回的原始信息:

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
function Test-OnlineFast
{
param
(
# make parameter pipeline-aware
[Parameter(Mandatory)]
[string[]]
$ComputerName,

$TimeoutMillisec = 1000
)

# hash table with error code to text translation
$StatusCode_ReturnValue =
@{
0='Success'
11001='Buffer Too Small'
11002='Destination Net Unreachable'
11003='Destination Host Unreachable'
11004='Destination Protocol Unreachable'
11005='Destination Port Unreachable'
11006='No Resources'
11007='Bad Option'
11008='Hardware Error'
11009='Packet Too Big'
11010='Request Timed Out'
11011='Bad Request'
11012='Bad Route'
11013='TimeToLive Expired Transit'
11014='TimeToLive Expired Reassembly'
11015='Parameter Problem'
11016='Source Quench'
11017='Option Too Big'
11018='Bad Destination'
11032='Negotiating IPSEC'
11050='General Failure'
}

# hash table with calculated property that translates
# numeric return value into friendly text

$statusFriendlyText = @{
# name of column
Name = 'Status'
# code to calculate content of column
Expression = {
# take status code and use it as index into
# the hash table with friendly names
# make sure the key is of same data type (int)
$StatusCode_ReturnValue[([int]$_.StatusCode)]
}
}

# calculated property that returns $true when status -eq 0
$IsOnline = @{
Name = 'Online'
Expression = { $_.StatusCode -eq 0 }
}

# do DNS resolution when system responds to ping
$DNSName = @{
Name = 'DNSName'
Expression = { if ($_.StatusCode -eq 0) {
if ($_.Address -like '*.*.*.*')
{ [Net.DNS]::GetHostByAddress($_.Address).HostName }
else
{ [Net.DNS]::GetHostByName($_.Address).HostName }
}
}
}

# convert list of computers into a WMI query string
$query = $ComputerName -join "' or Address='"

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" |
Select-Object -Property Address, $IsOnline, $DNSName, $statusFriendlyText

}

PowerShell 技能连载 - 创建快速的 Ping(第四部分)

在前一个技能中我们演示了如何用 WMI 快速 ping 多台计算机。那么今天我们将它封装为一个可复用的 PowerShell 函数。它可以快速地 ping 一台或多台计算机。

以下是函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Test-OnlineFast
{
param
(
[Parameter(Mandatory)]
[string[]]
$ComputerName,

$TimeoutMillisec = 1000
)

# convert list of computers into a WMI query string
$query = $ComputerName -join "' or Address='"

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" | Select-Object -Property Address, StatusCode
}

现在要以指定的超时值 ping 多台计算机变得非常简单:

1
2
3
4
5
6
PS> Test-OnlineFast -ComputerName microsoft.com, google.de

Address StatusCode
------- ----------
google.de 0
microsoft.com 11010

状态码 “0” 代表响应结果:主机在线。其他状态码代表失败。

默认情况下,Test-OnlineFast 的超时时间为 1000 毫秒,所以当一台计算机没有响应时,最多等待 1 秒。您可以通过 -TimeoutMillseconds 参数改变超时值。设置越长的超时值意味着命令的执行时间越长。所以您应该在系统足够响应的范围内使用尽可能短的超时时间。

另一个影响时间的变量是 DNS 解析:如果 DNS 解析速度慢,或者无法解析到名称,将增加总体时间。如果指定 IP 地址,就不会发生这种变慢现象。

以下是在几秒内 ping 200 个 IP 地址的例子:

1
2
3
4
5
6
7
8
9
10
11
12
PS> $ComputerName = 1..255 | ForEach-Object { "10.62.13.$_" }

PS> Test-OnlineFast -ComputerName $ComputerName

Address StatusCode
------- ----------
10.62.13.1 11010
10.62.13.10 0
10.62.13.100 0
10.62.13.101 11010
10.62.13.102 11010
(...)

PowerShell 技能连载 - 创建快速的 Ping(第三部分)

在前一个技能中我们演示了如何用 WMI 快速 ping 多台计算机,它的语法比较另类。那么让我们重写代码,使得指定要 ping 的计算机列表变得更容易:

1
2
3
4
5
6
7
8
# ping the specified servers with a given timeout (milliseconds)
$ComputerName = 'google.de','microsoft.com','r13-c00'
$TimeoutMillisec = 1000

# convert list of computers into a WMI query string
$query = $ComputerName -join "' or Address='"

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" | Select-Object -Property Address, StatusCode

现在要 ping 更大量的计算机变得更容易:只要将它们加入 $ComputerName 字符串数组。假如有一个文本文件,每行是一个计算机名,您也可以用 Get-Content 来写入 $ComputerName 变量。

PowerShell 技能连载 - 创建快速的 Ping(第二部分)

在前一个技能中我们演示了如何用 WMI 以指定的超时值 ping 计算机。WMI 还可以做更多的事:它可以迅速 ping 多台计算机,不过语法有一点另类。

以下是如何 ping 多台计算机:

1
2
3
4
# ping the specified servers with a given timeout (milliseconds)
$TimeoutMillisec = 1000

Get-WmiObject -Class Win32_PingStatus -Filter "(Address='microsoft.com' or Address='r13-c14' or Address='google.com') and timeout=$TimeoutMillisec" | Select-Object -Property Address, StatusCode

PowerShell 技能连载 - 创建快速的 Ping(第一部分)

Ping 是一个常见的任务。类似 Test-Connection 等 PowerShell cmdlet 可以进行 Ping 操作,但没有超时限制,所以当您尝试 ping 一台离线的主机时,可能要比较长时间才能得到结果。

WMI 支持带超时的 ping 操作。以下是使用方法:

1
2
3
4
$ComputerName = 'microsoft.com'
$TimeoutMillisec = 1000

Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' and timeout=$TimeoutMillisec" | Select-Object -Property Address, StatusCode

状态码 0 代表成功,其它代码代表失败。

PowerShell 技能连载 - 获取 PowerShell 的帮助

假设您下载了 PowerShell 的帮助文件,有一个获取各类 PowerShell 主题的快捷方法:

首先,确保您下载了帮助文件:以管理员权限启动 PowerShell,并且运行以下代码:

1
Update-Help -UICulture en-us -Force

下一步,检查 “about” 主题:

1
Get-Help about_*

在 PowerShell ISE 中,您所需要做的是点击 cmdlet 列出的主题,然后按 F1 键。这将产生一个类似这样的命令:

1
PS> Get-Help -Name 'about_If' -ShowWindow

在其它编辑器里,例如 VSCode 的 PowerShell 控制台,您需要自己键入命令。它将在 PowerShell 帮助查看器中打开帮助主题。

您也可以搜索指定的帮助主题,例如:

1
2
3
4
5
6
7
8
9
10
11
PS> help operator

Name Category Module Synopsis
---- -------- ------ --------
about_Arithmetic_Operators HelpFile Describes the operators that perform…
about_Assignment_Operators HelpFile Describes how to use operators to…
about_Comparison_Operators HelpFile Describes the operators that compare
about_Logical_Operators HelpFile Describes the operators that connect…
about_Operators HelpFile Describes the operators that are…
about_Operator_Precedence HelpFile Lists the Windows PowerShell operators…
about_Type_Operators HelpFile Describes the operators that work with…

在 PowerShell ISE 中,仍然可以点击某个列出的 about 主题,并按 F1 查看它的内容。

技术上,所有 about 主题都是文本文件,它们的位置在这里:

1
PS> explorer $pshome\en-us

PowerShell 技能连载 - 探索 Select-Object

Select-Object 是一个基础的 cmdlet,多数 PowerShell 用户都经常使用它。然而,它有一些限制,不太为大家所知。

Select-Object 最常见的形式是选择可见的属性。如果不使用 Select-Object,那么 PowerShell 将自行决定该显示哪些属性,以及它们的格式:

1
Get-ChildItem -Path c:\windows\system32 -Filter *.dll

如果加上了 Select-Object,您可以自行决定哪些属性可见。例如:

1
2
Get-ChildItem -Path c:\windows\system32 -Filter *.dll |
Select-Object -Property CreationTime, Length, Name, VersionInfo

Select-Object 还可以将属性的内容向上提升一层。在前一个例子中,VersionInfo 包含另一个对象。通过使用 -ExpandProperty 属性,您可以将它的属性向上提升一层:

1
2
Get-ChildItem -Path c:\windows\system32 -Filter *.dll |
Select-Object -Property CreationTime, Length, Name -ExpandProperty VersionInfo

要查看合并属性的实际结果,请将结果再次发送给 Select-Object,因为 PowerShell 默认情况下只显示其中的一部分结果:

1
2
3
Get-ChildItem -Path c:\windows\system32 -Filter *.dll |
Select-Object -Property CreationTime, Length, Name -ExpandProperty VersionInfo |
Select-Object -Property *

除了 “*” 之外,您还可以用逗号分隔的列表来决定要查看的属性:

1
2
3
Get-ChildItem -Path c:\windows\system32 -Filter *.dll |
Select-Object -Property CreationTime, Length, Name -ExpandProperty VersionInfo |
Select-Object -Property CreationTime, Name, FileVersionRaw, CompanyName

PowerShell 技能连载 - 读取 RunOnce 注册表键

Windows 注册表中的 RunOnce 键存储了所有的自启动。它可能是空的。要检查自启动的应用程序请试试这段代码:

1
2
3
$path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
$properties = Get-ItemProperty -Path $path
$properties

再次申明,这个键可能没有内容。如果它有内容,那么每个自启动程序都有它自己的值和名字。如果只要读取自启动程序的路径,请用 GetValueNames() 读取这个注册表键。它能够读取注册表值的名称。然后通过 GetValue() 读取实际的值:

1
2
3
$path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
$key = Get-Item -Path $path
$key.GetValueNames() | ForEach-Object { $key.GetValue($_) }

PowerShell 技能连载 - 创建随机的密码

以下是另一小段用于生成由指定数量的大小写字母、数字,和特殊字符组成的随机密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$length = 10
$length_small = $length - 3
$numbers = '2,3,4,5,6,7,8,9' -split ','
$large = 'A,B,C,D,E,F,G,H,K,L,M,N,P,R,S,T,U,V,W,X,Y,Z' -split ','
$small = 'A,B,C,D,E,F,G,H,K,L,M,N,P,R,S,T,U,V,W,X,Y,Z'.ToLower() -split ','
$special = '!,§,$,='.Split(',')

$password = @()
$password = @($numbers | Get-Random)
$password += @($large | Get-Random)
$password += @($small | Get-Random -Count $length_small)
$password += @($special | Get-Random)

$password = $password | Get-Random -Count $length

$password -join ''