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>

PowerShell 技能连载 - 查找 PowerShell 宿主参数和可执行文件

PowerShell 宿主可以使用参数启动,即您可以使用 –NoProfile 之类的参数运行 powershell.exepwsh.exe,或提交要执行的脚本的路径。

在外壳中,您始终可以查看启动此外壳程序的命令,包括其他参数:

1
2
3
$exe, $parameters = [System.Environment]::GetCommandLineArgs()
"EXE: $exe"
"Args: $parameters"

当您使用 -NoProfile 启动 powershell.exe 时,结果如下所示:

EXE: C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe Args: -noprofile

当您传入多个参数时,$parameters 是一个数组。

PowerShell 技能连载 - 管理已安装的模块(第 2 部分)

每当您通过 Install-Module 安装新模块或通过 Update-Module 更新现有模块时,新模块版本就会并行地安装。

如果您始终使用最新版本的模块,而无需访问特定的旧版本,则可能需要查找过时的模块并将其删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# get all installed modules as a hash table
# each key holds all versions of a given module
$list = Get-InstalledModule |
Get-InstalledModule -AllVersions |
Group-Object -Property Name -AsHashTable -AsString

# take all module names...
$list.Keys |
ForEach-Object {
# dump all present versions...
$list[$_] |
# sort by version descending (newest first)
Sort-Object -Property Version -Descending |
# and skip newest, returning all other
Select-Object -Skip 1
} |
# remove outdated (check whether you really don't need them anymore)
Uninstall-Module -WhatIf
PowerShell 技术 QQ 群