PowerShell 技能连载 - 更好的 PowerShell 帮助(第 1 部分)

许多 cmdlet 提供了丰富的联机帮助,您可以使用 Get-Help 并加上 -Online 参数来自动打开 cmdlet 的网页:

1
PS> Get-Help -Name Get-Service -Online

每个 cmdlet 还支持公用参数 -?。不过,它仅显示了有限的内置本地帮助:

1
2
3
4
5
6
7
8
9
PS> Get-Service -?

NAME
Get-Service

SYNTAX
Get-Service [-ComputerName <System.String[]>] [-DependentServices] -DisplayName <System.String[]> [-Exclude
<System.String[]>] [-Include <System.String[]>] [-RequiredServices] []
...

但是,您可以通过一个简单的技巧告诉 PowerShell 也对内置参数 -? 使用丰富的联机帮助。请运行这段代码:

1
PS> $PSDefaultParameterValues['Get-Help:Online'] = $true

此命令自动为 Get-Help -Online 参数设置一个新的默认值,并且因为 -? 内部使用 Get-Help,您现在只需添加 -? 到命令名称就可以获得大多数 PowerShell cmdlet 的丰富在线帮助。

1
PS> Get-Service -?

真是太棒了,因此您可能需要将该命令添加到自动启动配置文件脚本中。该脚本的路径可以在 $profile.CurrentUserAllHosts 中找到。它可能尚不存在,所以您可能必须创建它。

但是,有一个警告:如果命令没有在线帮助,那么您现在会收到一条错误消息,提示没有在线帮助。在即将发布的技能中,我们将单独解决此问题。

PowerShell 技能连载 - 检测 Wi-Fi 信号强度(第 3 部分)

在上一个技巧中,我们介绍了免费的 PowerShell 模块 Get-WLANs ,该模块可以访问 Windows Wi-Fi 框架并返回信息,例如信号强度。这是下载并安装它的命令:

1
Install-Module -Name Get-WLANs -Scope CurrentUser -Force

尽管您可以简单地使用其新命令 Get-WLANs(如上一技巧中所述),但该模块还添加了一个新的 .NET 类型,您可以在运行该命令后使用它。首先运行此命令以初始化新的 .NET 类型:

1
2
# "initialize" the new type
$null = Get-WLANs

接下来,创建一个原生的 “WlanClient” 对象:

1
$wc = [NativeWifi.WlanClient]::New()

现在,您可以转储有关系统中所有可用的 Wi-Fi 适配器的所有技术细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> $wc.Interfaces


Autoconf : True
BssType : Any
InterfaceState : Connected
Channel : 36
RSSI : 29
RadioState : NativeWifi.Wlan+WlanRadioState
CurrentOperationMode : ExtensibleStation
CurrentConnection : NativeWifi.Wlan+WlanConnectionAttributes
NetworkInterface : System.Net.NetworkInformation.SystemNetworkInterface
InterfaceGuid : 7d6c33b7-0354-4ad7-a72f-5a1a5cbb1a9b
InterfaceDescription : Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)
InterfaceName : WLAN

Interfaces“ 属性返回一个数组,因此您的第一个 Wi-Fi 适配器由以下形式表示:

1
$wc.Interfaces[0]

它提供了很多方法,例如,检索可访问的可用 Wi-Fi 网络列表。这将扫描新的网络:

1
2
$wc.Interfaces[0].Scan()
Start-Sleep -Seconds 1

以下代码转储可使用的网络列表(包括信号强度,频率和信道):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> $wc.Interfaces[0].GetAvailableNetworkList(3)


Dot11PhyTypes : {8}
profileName : internetcafe
dot11Ssid : NativeWifi.Wlan+Dot11Ssid
dot11BssType : Infrastructure
numberOfBssids : 3
networkConnectable : True
wlanNotConnectableReason : Success
morePhyTypes : False
wlanSignalQuality : 81
securityEnabled : True
dot11DefaultAuthAlgorithm : RSNA_PSK
dot11DefaultCipherAlgorithm : CCMP
flags : Connected, HasProfile

(...)

此对象模型为提供了丰富的方法和属性来控制和管理 Wi-Fi 网络适配器。例如,此代码转储可用的 SSID 列表及其信号强度:

1
2
3
4
5
6
7
8
9
10
11
$ssid = @{
N='SSID'
E={ [System.Text.Encoding]::Ascii.GetString( $_.dot11ssid.SSID,
0,
$_.dot11ssid.SSIDLength )
}
}

$wc.Interfaces[0].GetAvailableNetworkList(3) |
Select-Object -Property $ssid, wlanSignalQuality, profileName |
Where-Object SSID

结果看起来类似这样:

SSID                       wlanSignalQuality profileName
----                       ----------------- -----------
internetcafe                              81 internetcafe
internetcafe                              87 internetcafe 2
internetcafe                              87
DIRECT-fb-HP M477 LaserJet                31
Guest                                     67

PowerShell 技能连载 - 检测 Wi-Fi 信号强度(第 2 部分)

在上一个技能中,我们使用 netsh.exe 来确定 Wi-Fi 信号强度。由于 netsh.exe 返回的是原始格式的文本,因此需要大量的文本运算符技巧来提取实际的信号强度。通过调用结构化的 API 和对象成员直接访问信息始终是更优的方法。

坦白地说,没有内置的 PowerShell 方法可以以面向对象和结构化的方式访问 Wi-Fi 信息。但是,借助 PowerShell Gallery 中的免费 PowerShell 模块,您可以检索大量有用的 Wi-Fi 信息:

1
Install-Module -Name Get-WLANs -Scope CurrentUser -Force

该模块基本上随附访问任何最新 Windows 操作系统中存在的内置 Windows “Managed Wi-Fi” 框架所需的 C# 代码。

安装该模块后,新命令 Get-WLANs 将返回有关所有可访问的 Wi-Fi 网络的面向对象的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS> Get-WLANs

SSID : internetcafe
BSSID : 38:96:ED:0E:31:AD
RSSI : -63
QUALITY : 81
FREQ : 5180
CHANNEL : 36
PHY : VHT
CAPABILITY : 0x1511
IESIZE : 393

SSID : guests
BSSID : 3E:96:ED:0E:31:AD
RSSI : -69
QUALITY : 70
FREQ : 5180
CHANNEL : 36
PHY : VHT
CAPABILITY : 0x1511
IESIZE : 286
(...)

这也包括有关信号强度的信息(在 Quality 属性中)。

PowerShell 技能连载 - 检测 Wi-Fi 信号强度(第 1 部分)

如果您已连接到无线网络,则以下这行代码可提供当前信号强度:

1
2
PS> @(netsh wlan show interfaces) -match '^\s+Signal' -replace '^\s+Signal\s+:\s+',''
80%

信号强度来自 netsh.exe 提供的文本输出。将其包含在 @() 中可确保它始终返回一个数组。然后,-match 运算符使用正则表达式来标识在行首包含单词 “Signal” 的行。后面的 -replace 运算符使用正则表达式删除信号强度之前的文本。

如果您不喜欢正则表达式,则可以使用其他方法来实现,例如:

1
2
PS> (@(netsh wlan show interfaces).Trim() -like 'Signal*').Split(':')[-1].Trim()
80%

这段代码中,将首先修剪 netsh.exe 返回的每一行(删除两端的空白)。接下来,经典的 -like 运算符选择以 “Signal” 开头的行。然后用 “:” 分隔该行,并使用最后一部分(索引为 -1)。再次清除信号强度两端的空白。

如果两个命令均未返回任何内容,请检查您的计算机是否已连接到无线网络。您可能还希望仅使用 netsh.exe 命令的参数来运行它,而没有任何文本运算符来查看命令的原始输出。

PowerShell 技能连载 - Linux 如何保护安全字符串(实际未保护)

当您在非 Windows 系统上的 PowerShell 中将对象序列化为 XML 时,即通过使用 Export-CliXml,所有 SecureString 数据仅被编码,而不被加密。这是根本的区别,也是安全代码一旦移植到非 Windows 操作系统后可能变得不安全的原因。

例如,如果序列化凭据,则结果仅在 Windows 操作系统上是安全的:

1
Get-Credential | Export-Clixml -Path $env:temp\secret.xml

在 XML 文件中,您可以看到 Export-CliXml 如何更改 SecureString 的表示形式并将其转换为十六进制值的列表。在 Windows 上,SecureString 数据已安全加密,并且只有加密数据的人员(并且正在使用该计算机时)才能读取。在 Linux 和 macOS 上并非如此。由于此处缺少 Windows 加密API,因此仅对 SecureString 进行编码而不是加密。

要在非 Windows 操作系统上解码序列化 XML 内容中的纯文本内容,请尝试如下代码:

1
2
3
$secret = '53007500700065007200530065006300720065007400' $bytes = $secret -split '(?<=\G.{2})(?=.)' |ForEach-Object {
[Convert]::ToByte($_, 16)}
$plain = [Text.Encoding]::Unicode.GetString($bytes)$plain

它采用编码字符串,将其分成两对,将十六进制值转换为十进制值,并通过相应的 Unicode 解码器运行结果字节。结果就是原始的密文。

简而言之,请记住,仅在 Windows 操作系统上对序列化的 SecureString 进行安全加密。在 Linux 和 macOS 上,将敏感数据发送到 Export-CliXml 不会对其进行保护。

PowerShell 技能连载 - 快速初始化多个PowerShell控制台

假设您是许多领域的管理员,例如,Azure、SharePoint、SQL、Microsoft 365。对于每种环境,您可能需要运行一些先决条件,登录某些系统并运行一些命令,直到您的 PowerShell 环境准备好为止。

这是一个简单的策略,可以帮助您自动启动不同的PowerShell控制台。在新文件夹中,放置一个具有以下内容的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# safely serialize a credential
$credPath = "$PSScriptRoot\secret.xml"
$exists = Test-Path -Path $credPath
if (!$exists)
{
$cred = Get-Credential
$cred | Export-Clixml -Path $credPath
}

# define your different environments
$action = @{
'Azure' = "$PSScriptRoot\azure.ps1"
'Teams' = "$PSScriptRoot\teams.ps1"
'Office' = "$PSScriptRoot\office.ps1"
}

# run a new PowerShell for each environment, and run the
# associated "spin-up" script:
$action.Keys | ForEach-Object {
$path = $action[$_]
Start-Process -FilePath powershell -ArgumentList "-noexit -noprofile -executionpolicy bypass -file ""$path"""
}

该示例启动了三个 PowerShell 控制台,并为每个控制台运行一个单独的启动脚本。通过将脚本 azure.ps1,teams.ps1 和 office.ps1 添加到您的文件夹,您现在可以定义初始化和准备每个控制台所需的代码。您还可以通过从任何其他脚本中读取主凭据来使用公共凭据。这是一个例子:

1
2
3
4
# set the console title bar
$host.UI.RawUI.WindowTitle = 'Administering Office'
# read the common credential from file
$cred = Import-Clixml -Path "$PSScriptRoot\secret.xml"

PowerShell 技能连载 - 识别组成员身份

如果您的脚本需要知道当前用户是否是给定组的成员,那么最快且耗费最少资源的方法是使用如下代码:

1
2
3
4
5
6
$token = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$xy = 'S-1-5-64-36'
if ($token.Groups -contains $xy)
{
"You're in this group."
}

当当前用户是由 SID S-1-5-64-36 标识的组的直接或间接成员时,该示例将执行代码(将该 SID 替换为所需的组的 SID)。

这段代码访问用户已经存在并且始终有权访问的访问令牌。无需额外耗时进行 AD 查询,并且嵌套组成员身份也没有问题。访问令牌具有当前用户所属的直接和间接组的完整列表。

所有组均按 SID 列出,这很有意义。再次解析 SID 名称既耗时又毫无意义。如果您只想知道用户是否是组的成员,则只需找出该组的 SID(即使用 Get-AdGroup),然后在上述方法中使用此 SID 即可。

PowerShell 技能连载 - 查找系统路径

有时,脚本需要知道用户桌面或开始菜单等的路径。这些路径可能会有所不同,尤其是在用户使用 OneDrive 时。要找到当前系统路径,请使用以下代码:

1
2
PS> [Environment]::GetFolderPath('Desktop')
C:\Users\tobia\OneDrive\Desktop

您可以使用 [System.Environment+SpecialFolder] 类型中定义的所有常量:

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
PS> [Enum]::GetNames([System.Environment+SpecialFolder])
Desktop
Programs
MyDocuments
Personal
Favorites
Startup
Recent
SendTo
StartMenu
MyMusic
MyVideos
DesktopDirectory
MyComputer
NetworkShortcuts
Fonts
Templates
CommonStartMenu
CommonPrograms
CommonStartup
CommonDesktopDirectory
ApplicationData
PrinterShortcuts
LocalApplicationData
InternetCache
Cookies
History

CommonApplicationData
Windows
System
ProgramFiles
MyPictures
UserProfile
SystemX86
ProgramFilesX86
CommonProgramFiles
CommonProgramFilesX86
CommonTemplates
CommonDocuments
CommonAdminTools
AdminTools
CommonMusic
CommonPictures
CommonVideos
Resources
LocalizedResources
CommonOemLinks
CDBurning

如果您知道该路径,并且想知道是否有可以使用的已注册系统路径,请尝试相反的方法并转储所有文件夹常量及其关联的路径:

1
2
3
4
5
6
7
8
9
[Enum]::GetNames([System.Environment+SpecialFolder]) |
ForEach-Object {
# ...for each, create a new object with the constant, the associated path
# and the code required to get that path
[PSCustomObject]@{
Name = $_
Path = [Environment]::GetFolderPath($_)
}
}

结果看起来像这样:

Name                   Path
----                   ----
Desktop                C:\Users\tobia\OneDrive\Desktop
Programs               C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Start Menu\Pr...
MyDocuments            C:\Users\tobia\OneDrive\Dokumente
Personal               C:\Users\tobia\OneDrive\Dokumente
Favorites              C:\Users\tobia\Favorites
Startup                C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Start Menu\Pr...
Recent                 C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Recent
SendTo                 C:\Users\tobia\AppData\Roaming\Microsoft\Windows\SendTo
StartMenu              C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Start Menu
MyMusic                C:\Users\tobia\Music
MyVideos               C:\Users\tobia\Videos
DesktopDirectory       C:\Users\tobia\OneDrive\Desktop
MyComputer
NetworkShortcuts       C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Network Short...
Fonts                  C:\WINDOWS\Fonts
Templates              C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Templates
CommonStartMenu        C:\ProgramData\Microsoft\Windows\Start Menu
CommonPrograms         C:\ProgramData\Microsoft\Windows\Start Menu\Programs
CommonStartup          C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
CommonDesktopDirectory C:\Users\Public\Desktop
ApplicationData        C:\Users\tobia\AppData\Roaming
PrinterShortcuts       C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Printer Short...
LocalApplicationData   C:\Users\tobia\AppData\Local
InternetCache          C:\Users\tobia\AppData\Local\Microsoft\Windows\INetCache
Cookies                C:\Users\tobia\AppData\Local\Microsoft\Windows\INetCookies
History                C:\Users\tobia\AppData\Local\Microsoft\Windows\History
CommonApplicationData  C:\ProgramData
Windows                C:\WINDOWS
System                 C:\WINDOWS\system32
ProgramFiles           C:\Program Files
MyPictures             C:\Users\tobia\OneDrive\Bilder
UserProfile            C:\Users\tobia
SystemX86              C:\WINDOWS\SysWOW64
ProgramFilesX86        C:\Program Files (x86)
CommonProgramFiles     C:\Program Files\Common Files
CommonProgramFilesX86  C:\Program Files (x86)\Common Files
CommonTemplates        C:\ProgramData\Microsoft\Windows\Templates
CommonDocuments        C:\Users\Public\Documents
CommonAdminTools       C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administr...
AdminTools             C:\Users\tobia\AppData\Roaming\Microsoft\Windows\Start Menu\Pr...
CommonMusic            C:\Users\Public\Music
CommonPictures         C:\Users\Public\Pictures
CommonVideos           C:\Users\Public\Videos
Resources              C:\WINDOWS\resources
LocalizedResources
CommonOemLinks
CDBurning              C:\Users\tobia\AppData\Local\Microsoft\Windows\Burn\Burn   #

PowerShell 技能连载 - 使用 NTFS 流(第 5 部分)

在前面的技能中,我们研究了 NTFS 流,并发现 Windows 如何使用“区域信息”流标记下载的文件。您还学习了使用 Unblock-File 从文件中删除此限制。

在最后一部分中,我们做了相反的事情,查找从不受信任的来源下载的文件。例如,此行列出了所有附加了 Zone.Identifier 流的文件:

1
2
3
4
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' }

所有这些文件都来自 Windows 认为不一定可信的来源。

要了解更多信息,您必须阅读附件中的信息流。这段代码揭示了“下载”文件夹中所有“区域信息”流的全部内容:

1
2
3
4
5
6
7
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' } |
ForEach-Object {
Get-Content -Path $_.FullName -Stream Zone.Identifier
}

显然,该信息包含有关引用和来源的信息,因此您可以还原此信息并找出在“下载”文件夹中找到的所有内容的来源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' } |
ForEach-Object {
$info = Get-Content -Path $_.FullName -Stream Zone.Identifier
[PSCustomObject]@{
Name = $_.Name
Referrer = @(($info -like 'ReferrerUrl=*').Split('='))[-1]
HostUrl = @(($info -like 'HostUrl=*').Split('='))[-1]
Path = $_.FullName
}
} |
Out-GridView

这使您可以很好地了解下载的原始来源。

PowerShell 技能连载 - 使用 NTFS 流(第 4 部分)

每当您从 Internet(或其他不受信任的来源)下载文件并将其存储在 NTFS 驱动器上时,Windows 就会使用区域标识符对这些文件进行静默标记。例如,这就是为什么 PowerShell 拒绝执行从域外部下载的脚本的原因。

您实际上可以查看区域标识符。只要确保您从 Internet 下载文件并将其存储在 NTFS 驱动器上即可。接下来,使用此行查看区域标识符:

1
Get-Content -Path C:\users\tobia\Downloads\Flyer2021.rar -Stream Zone.Identifier

如果存在该流,则您会看到类似以下信息:

[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://shop.laserkino.de/
HostUrl=https://www.somecompany/Flyer2021.rar

它暴露了文件的来源以及从中检索文件的远程区域的类型。如果没有附加到文件的区域信息,则上述命令将引发异常。

若要从文件中删除区域信息(并删除所有限制),请使用 Unblock-File cmdlet。例如,要取消阻止“下载”文件夹中的所有文件,请尝试以下操作:

1
Get-ChildItem -Path C:\users\tobia\Downloads\ -File | Unblock-File -WhatIf

删除 -WhatIf 参数以实际删除该保护流。