PowerShell 技能连载 - 读取已安装的软件(第 2 部分)

在上一个技能中,我们演示了 Get-ItemProperty 强大的功能,并且您可以通过读取多个注册表位置而仅用一行代码来创建整个软件清单。

今天,让我们在软件清单中添加两个重要的信息:范围(是为每个用户安装的软件还是为所有用户安装的软件?)和体系结构(x86或x64)。

每个信息都无法在注册表键值中找到,而是通过确定信息在注册表中的存储位置来找到。这是 Get-ItemProperty 提供的另一个巨大好处:它不仅返回给定注册表项的注册表值。它还添加了许多属性,例如 PSDrive(注册表配置单元)和 PSParentPath(正在读取注册表项的路径)。

以下是根据发现信息的地方将范围和体系结构信息添加到软件清单的解决方案:

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
# Registry locations for installed software
$paths =
# all users x64
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
# all users x86
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
# current user x64
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
# current user x86
'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

# calculated properties

# AllUsers oder CurrentUser?
$user = @{
Name = 'Scope'
Expression = {
if ($_.PSDrive -like 'HKLM')
{
'AllUsers'
}
else
{
'CurrentUser'
}
}
}

# 32- or 64-Bit?
$architecture = @{
Name = 'Architecture'
Expression = {
if ($_.PSParentPath -like '*\WOW6432Node\*')
{
'x86'
}
else
{
'x64'
}
}
}
Get-ItemProperty -ErrorAction Ignore -Path $paths |
# eliminate reg keys with empty DisplayName
Where-Object DisplayName |
# select desired properties and add calculated properties
Select-Object -Property DisplayName, DisplayVersion, $user, $architecture

PowerShell 技能连载 - 读取已安装的软件(第 1 部分)

Get-ItemProperty cmdlet 可以比大多数用户知道的功能强大得多的方式读取注册表值。该 cmdlet 支持多个注册表路径,并且支持通配符。这样,只需一行代码即可从四个注册表项读取所有已安装的软件(及其卸载字符串):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# list of registry locations where installed software is stored
$paths =
# all users x64
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
# all users x86
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
# current user x64
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
# current user x86
'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

Get-ItemProperty -ErrorAction Ignore -Path $paths |
# eliminate all entries with empty DisplayName
Where-Object DisplayName |
# select some properties (registry values)
Select-Object -Property DisplayName, DisplayVersion, UninstallString, QuietUninstallString

PowerShell 技能连载 - 保持 Windows 和 PowerShell 持续运行

根据 Windows PC 的电源设置,即使您在运行冗长的脚本,您的计算机仍可能在一段时间后进入待机或休眠状态。

确保 Windows 在脚本忙时继续运行的一种方法是使用“演示模式”。您可以使用一个工具来启用和禁用它。在 PowerShell 中,运行以下命令:

1
PS> presentationsettings

这将打开一个窗口,您可以在其中检查和控制当前的演示文稿设置。要自动启动和停止演示模式,该命令支持参数 /start/stop

为了确保 Windows 在脚本运行时不会进入休眠状态,请将其放在第一行脚本中:

1
PS> presentationsettings /start

在脚本末尾,关闭演示模式,如下所示:

1
PS> presentationsettings /stop

要验证这两个命令的效果,请运行不带参数的命令,并选中对话框中最上方的复选框。

PowerShell 技能连载 - 读取事件日志(第 4 部分)

在上一个技巧中,我们鼓励您弃用 Get-EventLog cmdlet,而开始使用 Get-WinEvent,因为后者功能更强大,并且在PowerShell 7 中不再支持前者。

如上例所示,通过 Get-WinEvent 查询事件需要一个哈希表。例如,以下命令返回已安装更新的列表:

1
2
3
4
5
Get-WinEvent -FilterHashtable @{
LogName = 'System'
ProviderName = 'Microsoft-Windows-WindowsUpdateClient'
Id = 19
}

实际上,事件数据始终使用 XML 格式存储,并且所有查询都使用 XPath 过滤器查询来为您检索数据。如果您是 XML 和XPath专家,则可以直接使用以下命令来获得相同的结果:

1
2
3
Get-WinEvent -FilterXML @'
<QueryList><Query Id="0" Path="system"><Select Path="system">*[System/Provider[@Name='microsoft-windows-windowsupdateclient'] and (System/EventID=19)]</Select></Query></QueryList>
'@

哈希表是一个很方便的快捷方式。在内部,哈希表中包含的信息将转换为上面的 XML 语句。幸运的是,将哈希表转换为 XML 一点也不困难,因为 Get-WinEvent 会为您做到这一点:只需提交一个哈希表,并要求返回 XML 语句:

1
2
3
4
5
6
7
$result = Get-WinEvent -FilterHashtable @{
LogName = 'System'
ProviderName = 'Microsoft-Windows-WindowsUpdateClient'
Id = 19
} -MaxEvents 1 -Verbose 4>&1

$result | Where-Object { $_ -is [System.Management.Automation.VerboseRecord] }

本质上,通过提交 -Verbose 参数,要求 Get-WinEvent 将计算出的XML语句返回给您。通过将管道 4 重定向到输出管道 1,您可以将详细消息捕获到 $result 并过滤详细消息。这样,您可以捕获计算出的XML:

VERBOSE: Found matching provider: Microsoft-Windows-WindowsUpdateClient
VERBOSE: The Microsoft-Windows-WindowsUpdateClient provider writes events to the System log.
VERBOSE: The Microsoft-Windows-WindowsUpdateClient provider writes events to the Microsoft-Windows-WindowsUpdateClient/Operational log.
VERBOSE: Constructed structured query:
VERBOSE: <QueryList><Query Id="0" Path="system"><Select Path="system">*[System/Provider[@Name='microsoft-windows-windowsupdateclient'] and
VERBOSE: (System/EventID=19)]</Select></Query></QueryList>.

PowerShell 技能连载 - 读取事件日志(第 3 部分)

在上一个技能中,我们鼓励您弃用 Get-EventLog cmdlet,而开始使用 Get-WinEvent——因为后者功能更强大,并且在 PowerShell 7 中不再支持前者。

Win-EventLog 相比,Get-WinEvent 的优点之一是它能够读取所有 Windows 事件日志,而不仅仅是经典事件日志。要找出这些其他事件日志的名称,请尝试以下操作:

1
2
3
4
Get-WinEvent -ListLog * -ErrorAction Ignore |
# ...that have records...
Where-Object RecordCount -gt 0 |
Sort-Object -Property RecordCount -Descending

这将返回系统上所有包含数据的事件日志的列表,并按记录的事件数进行排序。显然,诸如“系统”和“应用程序”之类的“经典”日志比较显眼,但是还有许多其他有价值的日志,例如“具有高级安全性/防火墙的 Microsoft-Windows-Windows 防火墙”。让我们检查其内容:

1
2
3
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-Windows Firewall With Advanced Security/Firewall'
} -MaxEvents 20

由于我的系统正在使用内置防火墙,因此结果将返回有关更改防火墙规则和其他配置历史记录的详细信息。

使用不推荐使用的 Get-EventLog 将无法获得此信息。

PowerShell 技能连载 - 读取事件日志(第 2 部分)

在上一个技能中,我们鼓励您弃用 Get-EventLog cmdlet,而开始使用 Get-WinEvent——因为后者功能更强大,并且在 PowerShell 7 中不再支持前者。

让我们再次练习如何将 Get-EventLog 语句转换为 Get-WinEvent。这是我想翻译的一行代码。它从过去 48 小时内发生的系统事件日志中返回所有错误和警告:

1
$twoDaysAgo = (Get-Date).AddDays(-2)Get-EventLog -LogName System -EntryType Error, Warning -After $twoDaysAgo

这将是在所有 PowerShell 版本中均可使用的 Get-WinEvent 单行代码:

1
2
$twoDaysAgo = (Get-Date).AddDays(-2)Get-WinEvent -FilterHashtable @{
LogName = 'System' Level = 2,3 StartTime = $twoDaysAgo }

它返回相同的事件,但是速度更快。以下是您可以在哈希表中使用的其余键:

col 1 col 2 col 3
Key name Data Type Wildcards Allowed
LogName <String[]> Yes
ProviderName <String[]> Yes
Path <String[]> No
Keywords <Long[]> No
ID <Int32[]> No
Level <Int32[]> No
StartTime No
EndTime No
UserID No
Data <String[]> No

PowerShell 技能连载 - 读取事件日志(第 1 部分)

在 Windows 中,有许多事件日志,例如“系统”和“应用程序”,在 Windows PowerShell 中,使用 Get-EventLog 从这些日志中检索事件条目很简单。这种单行代码从您的系统事件日志中返回最新的五个错误事件:

1
PS> Get-EventLog -LogName System -EntryType Error -Newest 5 | Out-GridView

在 PowerShell 7 和更高版本中,cmdlet Get-EventLog 不再存在。它被使用不同语法的 Get-WinEvent 代替,并且查询条件以哈希表的形式传入:

1
2
3
4
Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2
} -MaxEvents 5

Level”键是一个数字值,值越小,事件越紧急。 ID 号 2 代表“错误”条目。 ID 号 3 将代表“警告”条目。要查看错误和警告,请提交一个数组:

1
2
3
4
Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2,3
} -MaxEvents 5

即使您正在使用 Windows PowerShell,并且不打算很快过渡到 PowerShell 7,现在还是该习惯于 Get-WinEvent 和弃用 Get-EventLog 的时候,因为新的 Get-WinEvent 自 PowerShell 3 起就可用,并确保您的代码也将在将来的 PowerShell 版本中无缝运行。

此外,Get-WinEvent 不仅可以访问一些经典的 Windows 事件日志,还可以访问所有特定于应用程序的事件。另外,与从 Get-EventLog 接收到的结果相比, Get-WinEvent 传递的结果更加完整:后者偶尔返回带有诸如“找不到事件 xyz 的描述”之类的消息的结果。Get-WinEvent 始终返回完整的消息。

PowerShell 技能连载 - 读取上次登录的用户和其他注册表值

使用 PowerShell 读取一些注册表值通常很容易:只需使用 Get-ItemProperty。此代码段读取 Windows 操作系统的才详细信息,例如:

1
2
3
4
$Path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"

Get-ItemProperty -Path $Path |
Select-Object -Property ProductName, CurrentBuild, ReleaseId, UBR

结果看起来像这样:

ProductName    CurrentBuild ReleaseId UBR
-----------    ------------ --------- ---
Windows 10 Pro 19042        2009      630

不幸的是,Get-ItemProperty 实际上得与 Select-Object 结合使用才理想,因为 cmdlet 总是会添加许多属性。如果您只想读取一个注册表值,即最后一个登录的用户,则始终需要将两者结合起来:

1
2
3
4
$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"

Get-ItemProperty -Path $Path |
Select-Object -ExpandProperty LastLoggedOnUser

另外,您可以将 Get-ItemProperty 的结果存储在变量中,并使用点号访问各个注册表值:

1
2
3
4
$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"

$values = Get-ItemProperty -Path $Path
$values.LastLoggedOnUser

如果此调用不能帮助您从其他用户帐户启动 Windows Terminal,那么在即将发布的技能中,我们将说明如何将应用程序转变为不再由 Windows 管理的便携式应用程序。取而代之的是,您可以用任何用户(包括高级帐户)运行它。敬请关注。

1
2
3
4
$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"

$key = Get-Item -Path $Path
$key.GetValue('LastLoggedOnUser')

PowerShell 技能连载 - 恒定函数

在 PowerShell中,您可以对函数进行写保护。当您这样做时,将无法在运行的 PowerShell 会话期间更改、覆盖或删除函数。尽管可能没有明显的作用。该方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$code =
{
param
(
[string]
[Parameter(Mandatory)]
$SomeParameter
)

"I have received $SomeParameter and could now process it..."
}

$null = New-Item -Path function:Invoke-Something -Options Constant,AllScope -Value $code

# run the function like this
Invoke-Something -SomeParameter "My Data"

由于该函数现在是恒定的,因此尝试重新定义它将会失败:

1
2
3
4
5
6
# you can no longer overwrite the function
# the following code raises an exception now
function Invoke-Something
{
# some new code
}

取消该效果的唯一方法是重新启动 PowerShell 会话。恒定变量是一个更有用的方案:通过将重要数据存储在写保护变量中,可以确保它们不会因意外或有意更改。此行定义了一个写保护变量 $testserver1,其中包含一些内容:

1
Set-Variable -Name testserver1 -Value server1 -Option Constant, AllScope

PowerShell 技能连载 - 禁止错误提示

使用 cmdlet,禁止错误提示似乎很容易:只需添加 –ErrorAction Ignore 参数。

但是,事实证明,这并不能消除所有错误。它仅禁止 cmdlet 选择处理的错误。特别是与安全相关的异常仍然显示。

如果要禁止所有错误提示,可以通过在命令尾部 2>$null 将异常传递给 null:

PS> Get-Service foobar 2>$null

这适用于所有命令甚至原生方法的调用。只需将代码括在大括号中,然后通过 调用运算符来进行调用:

1
2
3
4
5
6
PS> [Net.DNS]::GetHostEntry('notPresent')
MethodInvocationException: Exception calling "GetHostEntry" with "1" argument(s): "No such host is known."

PS> & {[Net.DNS]::GetHostEntry('notPresent')} 2>$null

PS>