PowerShell 技能连载 - 评估事件日志数据(第 3 部分)

在上一个技能中,我们了解了 Get-WinEvent 以及如何使用计算属性直接访问附加到每个事件的“属性”,而不必对事件消息进行文本解析。

例如,下面的代码通过从“属性”中找到的数组中提取已安装更新的名称来生成已安装更新列表:

1
2
3
4
$software = @{
Name = 'Software' Expression = { $_.Properties[0].Value }
}Get-WinEvent -FilterHashTable @{
Logname='System' ID=19 ProviderName='Microsoft-Windows-WindowsUpdateClient'} | Select-Object -Property TimeCreated, $software

这个概念一般适用于所有事件类型,您唯一的工作就是找出哪些信息包含在哪个数组索引中。让我们来看一个更复杂的事件类型,它包含的不仅仅是一条信息:

1
2
3
4
5
6
7
8
9
10
11
12
$LogonType = @{
Name = 'LogonType' Expression = { $_.Properties[8].Value }
}$Process = @{
Name = 'Process' Expression = { $_.Properties[9].Value }
}$Domain = @{
Name = 'Domain' Expression = { $_.Properties[5].Value }
}$User = @{
Name = 'User' Expression = { $_.Properties[6].Value }
}$Method = @{
Name = 'Method' Expression = { $_.Properties[10].Value }
}Get-WinEvent -FilterHashtable @{
LogName = 'Security' Id = 4624 } | Select-Object -Property TimeCreated, $LogonType, $Process, $Domain, $User, $Method

在这里,Get-WinEvent 从安全日志中读取 ID 为 4624 的所有事件。这些事件代表登录。由于事件位于安全日志中,因此您需要本地管理员权限才能运行代码。

Select-Object 仅返回 TimeCreated 属性。所有剩余的属性都被计算出来,基本上都是一样的:它们从所有事件日志条目对象中找到的“属性”数组中提取一些信息。

事实证明,登录的用户名可以在该数组的索引 6 中找到,登录类型可以在数组索引 8 中找到。

将代码包装到一个函数中后,现在可以很容易地对记录的登录事件进行复杂的查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Get-LogonInfo{
$LogonType = @{
Name = 'LogonType' Expression = { $_.Properties[8].Value }
}

$Process = @{
Name = 'Process' Expression = { $_.Properties[9].Value }
}

$Domain = @{
Name = 'Domain' Expression = { $_.Properties[5].Value }
}

$User = @{
Name = 'User' Expression = { $_.Properties[6].Value }
}

$Method = @{
Name = 'Method' Expression = { $_.Properties[10].Value }
}

Get-WinEvent -FilterHashtable @{
LogName = 'Security' Id = 4624 } | Select-Object -Property TimeCreated, $LogonType, $Process, $Domain, $User, $Method}Get-LogonInfo | Where-Object Domain -ne System | Where-Object User -ne 'Window Manager' | Select-Object -Property TimeCreated, Domain, User, Method

结果类似于:

TimeCreated         Domain                  User             Method
-----------         ------                  ----             ------
06.05.2021 11:46:04 RemotingUser2           DELL7390         Negotiate
05.05.2021 19:20:16 tobi.weltner@-------.de MicrosoftAccount Negotiate
05.05.2021 19:20:06 UMFD-1                  Font Driver Host Negotiate
05.05.2021 19:20:05 UMFD-0                  Font Driver Host Negotiate

PowerShell 技能连载 - 评估事件日志数据(第 2 部分)

在上一个技能中,我们查看了 Get-WinEvent 以及如何使用哈希表来指定查询。上一个提示使用以下代码列出了 Windows 更新客户端使用事件 ID 19 写入的所有事件日志文件中的所有事件:

1
2
3
4
Get-WinEvent -FilterHashTable @{
ID=19
ProviderName='Microsoft-Windows-WindowsUpdateClient'
} | Select-Object -Property TimeCreated, Message

结果是已安装更新的列表:

TimeCreated         Message
-----------         -------
05.05.2021 18:13:34 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.679.0)
05.05.2021 00:11:33 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.615.0)
04.05.2021 12:07:03 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.572.0)
03.05.2021 23:54:58 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.528.0)
...

通常,您只需要一个实际安装软件的列表,当您查看 “Message” 列时,需要删除大量无用的文本。

分析:事件日志消息由带有占位符的静态文本模板和插入到模板中的实际数据组成。实际数据可以在名为 “Properties” 的属性中找到,您需要做的就是找出这些属性中的哪些是您需要的信息。

这是上述代码的改进版本,它使用一个名为 “Software” 的计算属性读取属性(索引为 0)中的第一个数组元素,它恰好是已安装软件的实际名称:

1
2
3
4
5
6
7
8
9
10
11
$software = @{
Name = 'Software'
Expression = { $_.Properties[0].Value }
}


Get-WinEvent -FilterHashTable @{
Logname='System'
ID=19
ProviderName='Microsoft-Windows-WindowsUpdateClient'
} | Select-Object -Property TimeCreated, $software

所以现在代码返回一个更新列表以及它们的安装时间——不需要文本解析:

TimeCreated         Software
-----------         --------
05.05.2021 18:13:34 Security Intelligence-Update für Microsoft Defender Antivirus - KB2267602 (Version 1.337.679.0)
05.05.2021 00:11:33 Security Intelligence-Update für Microsoft Defender Antivirus - KB2267602 (Version 1.337.615.0)
04.05.2021 12:07:03 Security Intelligence-Update für Microsoft Defender Antivirus - KB2267602 (Version 1.337.572.0)
03.05.2021 23:54:58 Security Intelligence-Update für Microsoft Defender Antivirus - KB2267602 (Version 1.337.528.0)
03.05.2021 00:57:52 9WZDNCRFJ3Q2-Microsoft.BingWeather
03.05.2021 00:57:25 9NCBCSZSJRSB-SpotifyAB.SpotifyMusic
03.05.2021 00:57:06 9PG2DK419DRG-Microsoft.WebpImageExtension

PowerShell 技能连载 - 评估事件日志数据(第 1 部分)

事件日志包含有关 Windows 系统几乎所有方面的非常有用的信息。但是,在使用已弃用的 Get-EventLog cmdlet 时,只能访问此信息的一小部分,因为此 cmdlet 只能访问较旧的经典日志。这就是该 cmdlet 从 PowerShell 7 中完全删除的原因。

在 PowerShell 3 中,添加了一个更快、更强大的替代 cmdlet:et-WinEvent。此 cmdlet 可以根据哈希表中提供的查询项过滤任何日志文件。

例如,此行代码转储所有事件日志文件中由 Windows Update Client 使用事件 ID 19 写入的所有事件:

1
2
3
4
Get-WinEvent -FilterHashTable @{
ID=19
ProviderName='Microsoft-Windows-WindowsUpdateClient'
} | Select-Object -Property TimeCreated, Message

结果是已安装更新的列表:

TimeCreated         Message
-----------         -------
05.05.2021 18:13:34 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.679.0)
05.05.2021 00:11:33 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.615.0)
04.05.2021 12:07:03 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.572.0)
03.05.2021 23:54:58 Installation erfolgreich: Das folgende Update wurde installiert. Security Intelligence-Update für
                    Microsoft Defender Antivirus - KB2267602 (Version 1.337.528.0)
...

PowerShell 技能连载 - 解析原始数据和日志文件(第 2 部分)

在上一个技能中,我们解释了大多数日志文件可以被视为 CSV 文件并由 Import-Csv 读取。您需要做的就是告诉 Import-Csv 您的日志文件与标准 CSV 的不同之处,例如定义不同的分隔符或提供缺少的标题。

然而,一种日志文件格式很难解析:固定宽度的列。在这种情况下,没有可使用的单个分隔符。相反,数据使用固定宽度的字符串。

为了说明这种类型的数据,在 Windows 上运行实用程序 qprocess.exe。它返回固定宽度的数据(列出正在运行的进程、它们的所有者和它们的连接会话)。下面的示例取自德语操作系统,但本地化的列标题在这里并不重要。更重要的是每列使用固定的字符串宽度而不是单个分隔符,因此 ConvertFrom-Csv 无法读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> qprocess
BENUTZERNAME SITZUNGSNAME ID PID ABBILD
>tobia console 1 9332 dptf_helper.exe
>tobia console 1 9352 mbamtray.exe
>tobia console 1 9440 sihost.exe
>tobia console 1 9472 svchost.exe
...

PS> qprocess | ConvertTo-Csv
#TYPE System.String
"Length"
"60"
...

不过,对于固定宽度数据,您可以使用简单的正则表达式将可变空白替换为固定宽度分隔符:

1
2
3
4
5
6
PS> (qprocess) -replace '\s{1,}',','
,BENUTZERNAME,SITZUNGSNAME,ID,PID,ABBILD
>tobia,console,1,9332,dptf_helper.exe
>tobia,console,1,9352,mbamtray.exe
>tobia,console,1,9440,sihost.exe
...

现在您获得了有效的 CSV。由于 qprocess 返回一个字符串数组,您可以稍微微调数据,例如从每一行中删除不需要的字符:

1
2
3
4
5
6
PS> (qprocess).TrimStart(' >') -replace '\s{1,}',','
BENUTZERNAME,SITZUNGSNAME,ID,PID,ABBILD
tobia,console,1,9332,dptf_helper.exe
tobia,console,1,9352,mbamtray.exe
tobia,console,1,9440,sihost.exe
...

PowerShell 技能连载 - 解析原始数据和日志文件(第 1 部分)

大多数原始日志文件以表格形式出现:尽管它们可能不是功能齐全的 CSV 格式,但它们通常具有列和某种分隔符,有时甚至包含标题。

这是从 IIS 日志中获取的示例。当您查看它时时,会发现许多日志文件从根本上以表格方式组织它们的数据,如下所示:

#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2018-02-02 00:03:04
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2021-05-02 00:00:04 10.10.12.5 GET /Content/anonymousCheckFile.txt - 8530 - 10.22.121.248 - - 200 0 0 0
2021-05-02 00:00:04 10.10.12.5 GET /Content/anonymousCheckFile.txt - 8531 - 10.22.121.248 - - 200 0 0 2

与编写复杂的代码来读取和解析日志文件的数据相比,将日志文件与标准 CSV 格式进行比较并查看是否可以这样处理会非常有价值。

在上面的 IIS 日志示例中,结果将是这样:

  • 分隔符是一个空格(不是逗号)
  • 字段(列名)记录在注释行(而不是标题行)中

知道这一点后,请使用 Import-Csv(用于 CSV 的快速内置 PowerShell 解析器)来快速解析日志文件并将其转换为对象。您需要做的就是告诉 Import-Csv 您的日志文件与标准 CSV 格式功能的不同之处:

1
2
3
$Path = "c:\logs\l190202.log"

Import-Csv -Path $Path -Delimiter ' ' -Header date, time, s-ip, cs-method, cs-uri-stem, cs-uri-query, s-port, cs-username, c-ip, csUser-Agent, csReferer, sc-status ,sc-substatus, sc-win32-status, time-taken

在此示例中,使用 -Delimiter 告诉 Import-Csv 分隔符是一个空格,并且由于没有定义标题,请使用 -Header 并粘贴在开头的日志文件注释中找到的标题名称。

如果您不知道日志的标题名称,只需提供一个字符串数组,或使用此参数:

1
Import-Csv -Header (1..50)

这将为日志文件的列分配数字。

PowerShell 技能连载 - 修复 CSV 导出(第 2 部分)

在上一个技能中,我们指出了将对象转换为 CSV 时的一个普遍问题:任何包含数组的属性都将显示数组数据类型而不是数组内容。下面是一个例子:

1
2
3
4
5
6
PS> Get-Service | Select-Object -Property Name, DependentServices, RequiredServices | ConvertTo-Csv

#TYPE Selected.System.ServiceProcess.ServiceController
"Name","DependentServices","RequiredServices"
"AarSvc_e1277","System.ServiceProcess.ServiceController[]","System.ServiceProcess.ServiceController[]"
"AdobeARMservice","System.ServiceProcess.ServiceController[]","System.ServiceProcess.ServiceController[]"

在上一个技能中,我们还展示了该问题的手动解决方案:您总是可以使用 -join 运算符手动将任何数组属性的内容转换为字符串:

1
2
3
4
5
6
7
8
9
10
Get-Service |
Select-Object -Property Name, DependentServices, RequiredServices |

ForEach-Object {
$_.DependentServices = $_.DependentServices -join ','
$_.RequiredServices = $_.RequiredServices -join ','
return $_
} |

ConvertTo-Csv

数据现已“修复”,数组属性内容显示正确:

"Name","DependentServices","RequiredServices"
"AppIDSvc","applockerfltr","RpcSs,CryptSvc,AppID"
"Appinfo","","RpcSs,ProfSvc"
"AppVClient","","AppvVfs,RpcSS,AppvStrm,netprofm"
"AppXSvc","","rpcss,staterepository"
"AssignedAccessManagerSvc","",""
...

但是,将数组属性转换为扁平字符串可能需要大量手动工作,因此这里有一个名为 Convert-ArrayPropertyToString 的新函数,它会自动完成所有转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Convert-ArrayPropertyToString
{
process
{
$original = $_
Foreach ($prop in $_.PSObject.Properties)
{
if ($Prop.Value -is [Array] -and $prop.MemberType -ne 'AliasProperty')
{
Add-Member -InputObject $original -MemberType NoteProperty -Name $prop.Name -Value ($prop.Value -join ',') -Force
}
}
$original
}
}

要将对象转换为 CSV 而不丢失数组信息,只需将它通过管道传给新函数:

1
2
3
4
5
6
7
8
9
10
PS> Get-Service | Select-Object -Property Name, DependentServices, RequiredServices | Convert-ArrayPropertyToString | ConvertTo-Csv

#TYPE Selected.System.ServiceProcess.ServiceController
"Name","DependentServices","RequiredServices"
"AarSvc_e1277","",""
"AppIDSvc","applockerfltr","RpcSs,CryptSvc,AppID"
"Appinfo","","RpcSs,ProfSvc"
"AppMgmt","",""
"AppReadiness","",""
"AppVClient","","AppvVfs,RpcSS,AppvStrm,netprofm"

厉害吧?新函数能为您完成所有工作,它适用于任何对象:

1
2
3
4
5
6
7
8
9
PS> [PSCustomObject]@{
Name = 'Tobias'
Array = 1,2,3,4
Date = Get-Date
} | Convert-ArrayPropertyToString

Name Date Array
---- ---- -----
Tobias 06.05.2021 11:30:58 1,2,3,4

Convert-ArrayPropertyToString 使用在任何 PowerShell 对象都包含的 PSObject 隐藏属性来获取属性名称。接下来,它检查数组内容的所有属性。如果找到,它会自动将数组转换为逗号分隔的字符串。

为了能够使用新的扁平字符串内容覆盖现有属性——即使属性被写保护——它使用 Add-Member 并使用 -Force 隐藏属性。新的扁平字符串内容并没有真正覆盖属性。相反,它们被添加并优先使用。实际上,任何对象——即使其属性被写保护——都可以调整。

现在,每当您需要创建 Excel 报告或将数据导出到 CSV 时,您都可以轻松保留数组内容。

PowerShell 技能连载 - 修复 CSV 导出(第 1 部分)

当您将数据转换为 CSV 时,您可能会遇到一个很烦恼的情况:某些属性不是显示原始数据。下面是一个例子:

1
2
3
4
5
6
PS> Get-Service | Select-Object -Property Name, DependentServices, RequiredServices | ConvertTo-Csv

#TYPE Selected.System.ServiceProcess.ServiceController
"Name","DependentServices","RequiredServices"
"AarSvc_e1277","System.ServiceProcess.ServiceController[]","System.ServiceProcess.ServiceController[]"
"AdobeARMservice","System.ServiceProcess.ServiceController[]","System.ServiceProcess.ServiceController[]"

如您所见,DependentServicesRequiredServices 属性和所有服务显示的内容是一样的。

当属性包含数组时会发生这种情况。扁平化二维导出格式(例如 CSV)无法显示数组,因此将显示数组数据类型。这对用户来说当然根本没有帮助。

在我们解决它之前先总结一下:您在这里看到的是许多场景中的严重问题。它不仅影响CSV导出,还影响导出为 Excel 或其他二维表格格式。

要解决这个问题,您必须将所有数组转换为字符串。您可以手动或自动执行此操作。在这个技能中,我们首先展示手动方法来关注效果。在未来的技能中,我们会自动执行相同的操作。

这是从上方正确导出所选数据的手动方法:

1
2
3
4
5
6
7
8
9
10
Get-Service |
Select-Object -Property Name, DependentServices, RequiredServices |

ForEach-Object {
$_.DependentServices = $_.DependentServices -join ','
$_.RequiredServices = $_.RequiredServices -join ','
return $_
} |

ConvertTo-Csv

数据现在显示所有数组内容,因为 ForEach-Object 循环已使用 -join 运算符将数组内容转换为逗号分隔的字符串。

"Name","DependentServices","RequiredServices"
"AppIDSvc","applockerfltr","RpcSs,CryptSvc,AppID"
"Appinfo","","RpcSs,ProfSvc"
"AppVClient","","AppvVfs,RpcSS,AppvStrm,netprofm"
"AppXSvc","","rpcss,staterepository"
"AssignedAccessManagerSvc","",""
...

只要您通过 Select-Object 操作原始数据,就可以进行这种“调整”:Select-Object 始终复制(克隆)信息,因此一旦 Select-Object 处理了数据,您就拥有这些对象并可以以任何方式更改其属性。

PowerShell 技能连载 - 导出不带引号的CSV(和其他转换技巧)

PowerShell 附带了一堆 Export-ConvertTo- cmdlet,因此您可以将对象数据序列化为 CSV、JSON、XML和其他格式。很好,但是创建自己的导出功能并不难。

例如,Windows PowerShell 中的 Export-Csv 始终对数值添加双引号。如果你不喜欢带双引号的 CSV,就不好办了。 PowerShell 7 已解决此问题,其中包含额外的参数,但创建自己的 Export-Csv 函数根本并不难。

这是一个简单的例子:

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
function ConvertTo-MyCsv
{
param
(
[char]
$Delimiter = ','
)
begin
{
$init = $false
}
process
{
# write the headers
if ($init -eq $false)
{
$_.PSObject.Properties.Name -join $Delimiter
$init = $true
}

# write the items
$_.PSObject.Properties.Value -join $Delimiter
}
}

$a = Get-Service | ConvertTo-MyCsv

这的确是所有的内容了:ConvertTo-MyCsv 将对象转换为没有双引号的 CSV,如果需要,甚至可以选择分隔符(默认为 “,“)。

要快速检查生成的CSV的完整性,请将其转换回对象:

1
PS> $a | ConvertFrom-CSV | Out-GridView

一切都很好,转换生效了。它不比 ConvertTo-Csv 慢。

显然,如果任何数据值包含分隔符(这就是为什么 ConvertTo-Csv 为它们添加双引号的原因),则转换将失败。但这不是这里的重点。您可以轻松微调函数。更重要的是,PowerShell 中将对象自动转换为文本的艺术。

要将对象转换为 CSV(或任何其他格式),只需访问隐藏的 PSObject 属性(可用于任何 PowerShell 对象)。它描述了对象,并提供所有属性名称,值和数据类型。

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

在上一个技能中,我们使用自定义代理功能对原始的 Get-Help cmdlet 进行了“影子”处理。该功能检查命令是否存在联机帮助,如果存在,则默认情况下会打开丰富的联机帮助。这极大地改善了 PowerShell 中的帮助体验。

但是,通过简单的调整,您可以使该功能更进一步:为什么不同时检查另一种方式呢?如果用户使用 -Online 参数,但是没有在线帮助,则 Get-Help 会抛出一个丑陋的异常。显示内置的本地帮助而不是抛出异常,不是更好吗?

当用户将 Get-Help 的默认值设置为 -Online 时,也可使该函数能适应 $PSDefaultParameterValue 的调整。

这是更新的函数:

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
function Get-Help{
\# clone the original param block taken from Get-Help [CmdletBinding(DefaultParameterSetName='AllUsersView', HelpUri='https://go.microsoft.com/fwlink/?LinkID=113316')]
param(
[Parameter(Position=0, ValueFromPipelineByPropertyName)]
[string]
$Name, [Parameter(ParameterSetName='Online', Mandatory)]
[switch]
$Online, [ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','Workflow','DscResource','Class','Configuration')]
[string[]]
$Category, [string]
$Path, [string[]]
$Component, [string[]]
$Functionality, [string[]]
$Role, [Parameter(ParameterSetName='DetailedView', Mandatory)]
[switch]
$Detailed, [Parameter(ParameterSetName='AllUsersView')]
[switch]
$Full, [Parameter(ParameterSetName='Examples', Mandatory)]
[switch]
$Examples, [Parameter(ParameterSetName='Parameters', Mandatory)]
[string]
$Parameter, [Parameter(ParameterSetName='ShowWindow', Mandatory)]
[switch]
$ShowWindow )

begin {
\# we do the adjustments only when the user has submitted \# the -Name, -Category, and -Online parameters
if ( (@($PSBoundParameters.Keys) -ne 'Name' -ne 'Category' -ne 'Online'). Count -eq 0 )
{
\# check whether there IS online help available at all
\# retrieve the help URI $help = Microsoft.PowerShell.Core\Get-Command -Name $Name
\# reset the parameter -Online based on availability of online help $PSBoundParameters['Online']= [string]::IsNullOrWhiteSpace($help.HelpUri) -eq $false }

\# once the parameter adjustment has been processed, call the original \# Get-Help cmdlet with the parameters found in $PSBoundParameters
\# turn the original Get-Help cmdlet into a proxy command receiving the \# adjusted parameters \# with a proxy command, you can invoke its begin, process, and end \# logic separately. That's required to preserve pipeline functionality $cmd = Get-Command -Name 'Get-Help' -CommandType Cmdlet $proxy = {& $cmd @PSBoundParameters}. GetSteppablePipeline($myInvocation.CommandOrigin)

\# now, call its default begin, process, and end blocks in the appropriate \# script blocks so it integrates in real-time pipelines $proxy.Begin($PSCmdlet)
}

process { $proxy.Process($_) }

end { $proxy.End() }

\# use the original help taken from Get-Help for this function <#
.ForwardHelpTargetName Microsoft.PowerShell.Core\Get-Help .ForwardHelpCategory Cmdlet \#>}

运行这段代码之后,无论使用 Get-Helphelp 还是公共参数 -?,您的帮助系统现在都变得更加智能。

如果有可用于命令的联机帮助,则默认情况下显示:

1
2
3
PS> Get-Help Get-Service

PS> Get-Service -?

如果没有可用的联机帮助,它将始终显示本地帮助(即使您不小心指定了 -Online 或使用 $PSDefaultParameterValue 来显式使用 -Online):

1
2
3
PS> Connect-IscsiTarget -?

PS> Get-Help Connect-IscsiTarget -Online

并且,如果您指定其他参数,它们仍然可以按预期继续工作:

1
PS> Get-Help Get-Service -ShowWindow

本质上,该调整仅包含同时提供丰富在线帮助内容,并在可用时显示它。

如果您喜欢此功能,请将其添加到配置文件脚本中,以便在启动 PowerShell 时对其进行定义。路径可以在这里找到:$profile.CurrentUserAllHosts

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

在上一个技能中,我们更改了 Get-Help 的默认参数值,以在您使用 Get-Help 或通用参数 -? 时自动显示丰富的联机帮助。但是,当 cmdlet 没有联机帮助时,此方法会产生错误。

更好的方法是首先检查给定命令是否存在联机帮助,然后才打开联机帮助。如果没有在线帮助,则应显示默认的本地帮助。

此方法无法通过默认参数实现。相反,Get-Help cmdlet 本身需要进行调整。要将逻辑添加到 Get-Help,可以使用以下代理函数:

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
function Get-Help{
\# clone the original param block taken from Get-Help [CmdletBinding(DefaultParameterSetName='AllUsersView', HelpUri='https://go.microsoft.com/fwlink/?LinkID=113316')]
param(
[Parameter(Position=0, ValueFromPipelineByPropertyName)]
[string]
$Name, [Parameter(ParameterSetName='Online', Mandatory)]
[switch]
$Online, [ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','Workflow','DscResource','Class','Configuration')]
[string[]]
$Category, [string]
$Path, [string[]]
$Component, [string[]]
$Functionality, [string[]]
$Role, [Parameter(ParameterSetName='DetailedView', Mandatory)]
[switch]
$Detailed, [Parameter(ParameterSetName='AllUsersView')]
[switch]
$Full, [Parameter(ParameterSetName='Examples', Mandatory)]
[switch]
$Examples, [Parameter(ParameterSetName='Parameters', Mandatory)]
[string]
$Parameter, [Parameter(ParameterSetName='ShowWindow', Mandatory)]
[switch]
$ShowWindow )

begin {
\# determine whether -Online should be made a default if (
\# user submitted -Name only ( $PSBoundParameters.Count -eq 1 -and
$PSBoundParameters.ContainsKey('Name')
) -or \# system submitted -Name and -Category (when using -?) (
$PSBoundParameters.Count -eq 2 -and
$PSBoundParameters.ContainsKey('Name') -and
$PSBoundParameters.ContainsKey('Category')
)
)
{
\# prerequisites are OK, now check whether there IS online help \# available at all
\# retrieve the help URI $help = Microsoft.PowerShell.Core\Get-Command -Name $Name
\# set the -Online parameter only if there is a help URI $PSBoundParameters['Online']= [string]::IsNullOrWhiteSpace($help.HelpUri) -eq $false }

\# once the parameter adjustment has been processed, call the original \# Get-Help cmdlet with the parameters found in $PSBoundParameters
\# turn the original Get-Help cmdlet into a proxy command receiving the \# adjusted parameters \# with a proxy command, you can invoke its begin, process, and end \# logic separately. That's required to preserve pipeline functionality $cmd = Get-Command -Name 'Get-Help' -CommandType Cmdlet $proxy = {& $cmd @PSBoundParameters}. GetSteppablePipeline($myInvocation.CommandOrigin)

\# now, call its default begin, process, and end blocks in the appropriate \# script blocks so it integrates in real-time pipelines $proxy.Begin($PSCmdlet)
}

process { $proxy.Process($_) }

end { $proxy.End() }

\# use the original help taken from Get-Help for this function <#
.ForwardHelpTargetName Microsoft.PowerShell.Core\Get-Help .ForwardHelpCategory Cmdlet \#>}

运行此代码时,Get-Help 函数现在将覆盖原始的 Get-Help cmdlet。在内部,该函数调用原生的 cmdlet,但在此之前,该函数检查 -Online 是否应设为默认参数。

现在,仅当用户未提交任何有冲突的参数时才发生这种情况,并且仅当首先有可用于所请求命令的联机帮助时才发生这种情况。

现在,无论何时使用 Get-Help 或通用参数 -?,您都会获得丰富的在线帮助(如果可用)或本地默认帮助。

试试以下代码:

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

PS> Connect-IscsiTarget -?

PS> Get-Help Get-Service

PS> Get-Help Get-Service -ShowWindow

由于有可用于 Get-Service 的联机帮助,因此第一个调用将打开浏览器窗口并显示帮助。第二个调用说明了没有可用的联机帮助时发生的情况:此处未将 “-Online“ 作为默认参数,而是显示了默认的本地帮助。

第三和第四次调用说明 Get-Help 仍然可以正常运行。默认情况下,该命令现在会打开联机帮助,但是如果您添加其他参数(例如 -ShowWindow),它们仍将按预期运行。

如果您喜欢此功能,则应将其添加到您的配置文件脚本中。

注意:如果您已通过 $PSDefaultParameterGet-Help 设置了任何默认参数(即,按照前面的提示进行操作时),则这些参数将生效,并且上面的功能无法为您带来任何改善。

确保您没有定义任何会影响 “Get-Help“ 的默认参数:

1
PS> $PSDefaultParameterValues.Keys.ToLower() -like 'get-help*'