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 ''

PowerShell 技能连载 - 将 PowerShell 脚本转换为批处理

以下是一个有趣的 PowerShell 脚本,名为 Convert-PowerShellToBatch。将 PowerShell 脚本的路径作为参数传给它,或者将 Get-ChildItem 的执行结果用管道传给它,来批量执行多个脚本。

该函数为每个脚本创建一个批处理文件。当您双击批处理文件时,将执行 PowerShell 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Convert-PowerShellToBatch
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]
[Alias("FullName")]
$Path
)

process
{
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Content -Path $Path -Raw -Encoding UTF8)))
$newPath = [Io.Path]::ChangeExtension($Path, ".bat")
"@echo off`npowershell.exe -NoExit -encodedCommand $encoded" | Set-Content -Path $newPath -Encoding Ascii
}
}

Get-ChildItem -Path C:\path\to\powershell\scripts -Filter *.ps1 |
Convert-PowerShellToBatch

当您查看某个生成的脚本文件时,您会发现 PowerShell 代码被转换为 BASE64 编码的字符串。所以这种转换适用于许多真实世界的需求:

  • 双击执行一个批处理文件来运行 PowerShell 代码更方便。
  • 没有经验的用户更不容易受诱惑去改动脚本,因为它经过 BASE64 编码。

申明:BASE64 并不是加密。将 BASE64 编码的文本转换为可读的明文是很简单的事。所以这里用的技术不适合用来隐藏秘密,例如密码。

PowerShell 技能连载 - 用管道传递文件和文件夹

假设您想创建一个函数,接受一个文件路径参数。文件可以进行许多操作。您可能希望拷贝文件,压缩文件,将它们设置为隐藏文件,或其它各种操作。我们在这里并不关注具体需要做什么操作。我们希望关注 PowerShell 函数如何接受文件参数。

您应该遇到过这样的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Process-File
{
param
(
$Path
)

# do something with the file
$file = Get-Item -Path $Path
'File {0} is of size {1} bytes.' -f $file.FullName, $file.Length
}

Process-File -Path C:\windows\explorer.exe

结果看起来类似这样:

1
2
3
4
5
PS> Process-File -Path C:\windows\explorer.exe

File C:\windows\explorer.exe is of size 3903784 bytes.

PS>

这个函数每次只处理一个路径。如果希望传入多个路径,您需要这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Process-File
{
param
(
[string[]]
$Path
)

foreach($SinglePath in $Path)
{
# do something with the file
$file = Get-Item -Path $SinglePath
'File {0} is of size {1} bytes.' -f $file.FullName, $file.Length
}
}

Process-File -Path C:\windows\explorer.exe, C:\windows\notepad.exe

现在,您的函数可以接受任意多个逗号分隔的路径。如果您希望也能从管道输入路径呢?需要增加这些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Process-File
{
param
(
[Parameter(ValueFromPipeline)]
[string[]]
$Path
)

process
{
foreach($SinglePath in $Path)
{
# do something with the file
$file = Get-Item -Path $SinglePath
'File {0} is of size {1} bytes.' -f $file.FullName, $file.Length
}
}
}

Process-File -Path C:\windows\explorer.exe, C:\windows\notepad.exe
'C:\windows\explorer.exe', 'C:\windows\notepad.exe' | Process-File

基本上,您现在有两个嵌套的循环:process {} 是管道对象使用的循环,而其中的 foreach 循环处理用户传入的的字符串数组。

如果您希望 Get-ChildItem 提供路径给函数呢?它并不是返回字符串。它返回的是文件系统对象,而且在对象之内,有一个名为 “FullName” 的属性,存储对象的路径。以下是您要做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Process-File
{
param
(
[Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string[]]
[Alias("FullName")]
$Path
)

process
{
foreach($SinglePath in $Path)
{
# do something with the file
$file = Get-Item -Path $SinglePath
'File {0} is of size {1} bytes.' -f $file.FullName, $file.Length
}
}
}

Process-File -Path C:\windows\explorer.exe, C:\windows\notepad.exe
'C:\windows\explorer.exe', 'C:\windows\notepad.exe' | Process-File
Get-ChildItem -Path c:\windows -Filter *.exe | Process-File

现在,这个函数不止能接受管道传来的字符串 (ValueFromPipeline),而且还能接受有某个属性名或别名与参数 (Path) 相似的对象 (ValueFromPipelineByPropertyName)。任务完成了。您的函数现在能够为用户提供最大的灵活性,这基本上也是 cmdlet 所做的事。

PowerShell 技能连载 - 简易的 WMI 浏览器

WMI 是一个丰富的信息库——如果您知道 WMI 的类名:

1
2
3
Get-CimInstance -ClassName Win32_BIOS
Get-CimInstance -ClassName Win32_Share
Get-CimInstance -ClassName Win32_OperatingSystem

如果您想探索 WMI 的内容,那么以下代码会十分便利。Find-WmiClass 接受一个简单的关键字,例如 “video”、”network”、”ipaddress”。接下来它可以获取所有类名、某个属性名或方法名包含该关键字的 WMI 类。

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
function Find-WmiClass
{
param([Parameter(Mandatory)]$Keyword)

Write-Progress -Activity "Finding WMI Classes" -Status "Searching"

# find all WMI classes...
Get-WmiObject -Class * -List |
# that contain the search keyword
Where-Object {
# is there a property or method with the keyword?
$containsMember = ((@($_.Properties.Name) -like "*$Keyword*").Count -gt 0) -or ((@($_.Methods.Name) -like "*$Keyword*").Count -gt 0)
# is the keyword in the class name, and is it an interesting type of class?
$containsClassName = $_.Name -like "*$Keyword*" -and $_.Properties.Count -gt 2 -and $_.Name -notlike 'Win32_Perf*'
$containsMember -or $containsClassName
}
Write-Progress -Activity "Find WMI Classes" -Completed
}

$classes = Find-WmiClass

$classes |
# let the user select one of the found classes
Out-GridView -Title "Select WMI Class" -OutputMode Single |
ForEach-Object {
# get all instances of the selected class
Get-CimInstance -Class $_.Name |
# show all properties
Select-Object -Property * |
Out-GridView -Title "Instances"
}

接下来用户可以选择某个找到的类,该代码将显示这个类的实际实例。

声明:有部分类有几千个实例,例如 CIM_File。当选择了一个有这么多实例的 WMI 类时,该脚本将执行很长时间才能完成。