PowerShell 技能连载 - 高级排序(第 4 部分)

在上一个部分中,我们说明了如何使用脚本块对排序进行更多的控制。例如,您可以使用 “-as“ 运算符来转换数据以控制排序算法。

这样,您可以“修正”传入数据的数据类型。例如将某些或所有数字数据转为字符串类型,再传给 Sort-Object。看看第一个例子的结果,以及下方的修正结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> 1,2,"13",4,11,3,"2" | Sort-Object
1
13
2
3
4
11
2

PS> 1,2,"13",4,11,3,"2" | Sort-Object -Property { $_ -as [double] }
1
2
2
3
4
11
13

同样,您可以完全随机化列表,快速地创建简单的随机密码:

1
2
3
4
5
PS> -join ('abcdefghkmnrstuvwxyz23456789*+#'.ToCharArray() | Get-Random -count 5 | Sort-Object -Property { Get-Random } )
m2v6u

PS> -join ('abcdefghkmnrstuvwxyz23456789*+#'.ToCharArray() | Get-Random -count 5 | Sort-Object -Property { Get-Random } )
b+g7t

或者,您可以根据计算值进行排序。下面的示例按文件年龄排序,通过从创建时间中减去上次写入时间计算得出:

1
2
3
Get-ChildItem -Path c:\windows -Filter *.log -Depth 1 -ErrorAction Ignore |
Sort-Object -Property { $_.LastWriteTime - $_.CreationTime } |
Select-Object -Property Name, LastWriteTime, CreationTime, @{Name='Age (Days)';Expression={ [int]($_.LastWriteTime - $_.CreationTime).TotalDays }}

结果看起来类似这样:

Name                                 LastWriteTime       CreationTime        Age (Days)
----                                 -------------       ------------        ----------
setupapi.upgrade.log                 28.09.2020 15:04:56 28.09.2020 15:40:12          0
setuperr.log                         11.07.2021 14:37:51 11.07.2021 14:37:51          0
setupact.log                         16.10.2021 12:59:17 16.10.2021 12:59:17          0
setupapi.setup.log                   28.09.2020 17:48:22 28.09.2020 17:47:36          0
setupapi.offline.20191207_091437.log 07.12.2019 10:14:37 07.12.2019 10:13:02          0
setupapi.offline.log                 28.09.2020 15:40:12 28.09.2020 15:09:47          0
setupapi.dev.20201120_180252.log     20.11.2020 18:02:52 28.09.2020 17:51:43         53
setupapi.dev.20210514_095516.log     14.05.2021 09:55:16 27.02.2021 04:17:58         76
setupapi.dev.20210226_041725.log     26.02.2021 04:17:25 22.11.2020 04:18:58         96
lvcoinst.log                         23.10.2021 15:30:45 19.07.2021 08:46:48         96
PFRO.log                             25.10.2021 13:54:27 11.07.2021 14:41:28        106
WindowsUpdate.log                    26.10.2021 10:47:38 10.07.2021 15:05:05        108
setupapi.dev.20210710_201855.log     10.07.2021 20:18:55 27.02.2021 04:17:58        134
setupapi.dev.20210915_082529.log     15.09.2021 08:25:29 27.02.2021 04:17:58        200
NetSetup.LOG                         17.04.2020 15:19:26 26.08.2019 17:23:11        235
setupapi.dev.log                     26.10.2021 06:27:25 27.02.2021 04:17:58        241
mrt.log                              13.10.2021 08:30:39 12.11.2020 07:16:17        335
PASSWD.LOG                           25.10.2021 13:54:28 28.09.2020 17:47:22        392
ReportingEvents.log                  26.10.2021 09:37:23 03.09.2019 10:42:58        784
Gms.log                              25.10.2021 13:54:32 26.08.2019 17:27:50        791

请注意如何以非常相似的方式将哈希表与 Select-Object 一起使用。在示例中,通过将哈希表提交给 Select-Object,将“Age (Days)”属性添加到输出中。在内部,哈希表定义了新属性的名称并提供了一个脚本块来计算属性值。此脚本块的工作原理与此处讨论的脚本块基本相同:$_ 表示整个对象,您可以使用任何 PowerShell 表达式来转换或计算该值。

PowerShell 技能连载 - 高级排序(第 3 部分)

在上一个提示中,您已经看到 Sort-Object 如何接受哈希表作为参数,对排序进行高级控制。 例如,此行代码按状态排序,然后显示名称,并为每个属性使用单独的排序方向:

1
2
3
Get-Service |
Sort-Object -Property @{Expression='Status'; Descending=$true}, @{Expression='DisplayName'; Descending=$false } |
Select-Object -Property DisplayName, Status

但是,当您查看结果时,”Status” 属性的排序方向似乎是错的。我们已经澄清了这一点,因为 “Status” 实质上是一个数字常量,而 Sort-Object 排序的底层逻辑是对原始数据进行排序。所以它是根据内部的数值型常量对 “Status” 进行排序,而不是根据可见的友好名称。

要确保 Sort-Object 是按照用户“看见”对象的内容对它们排序,请使用哈希表的另一个功能:”Expression” 键不仅能接受属性的名称,而且用支持脚本块(代码)转换为任何数据,再对该数据排序。在脚本块内,$_ 表示完整的传入对象。

要确保 “Status” 根据文本来排序而不是根据背后的数值来排序,请将其转换为字符串,如下所示:

1
2
3
Get-Service |
Sort-Object -Property @{Expression={[string]$_.Status}; Descending=$true}, @{Expression='DisplayName'; Descending=$false } |
Select-Object -Property DisplayName, Status

事实上,在排序之前改变数据的能力,能改变所有排序的游戏规则。

假设您有一个 HTTPS 连接列表,用于保存浏览器连接到的远程 IP 地址,并且您正在尝试通过 Sort-Object 来对这些 IP 地址进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property RemoteAddress

RemoteAddress RemotePort State OwningProcess
------------- ---------- ----- -------------
142.250.185.74 443 TimeWait 0
142.250.186.46 443 TimeWait 0
20.199.120.182 443 Established 3552
20.199.120.182 443 Established 5564
20.42.65.89 443 Established 9836
20.42.65.89 443 TimeWait 0
20.42.73.24 443 TimeWait 0
35.186.224.25 443 TimeWait 0
45.60.13.212 443 Established 7644
51.104.30.131 443 Established 10220

如果您将仔细查看结果,您将看到 “RemoteAddress” 未正确排序:例如 “20.199.120.182” 列在了 “20.42.65.89” 的前面。

这是因为这些 IPv4 地址被视为字符串,因此 Sort-Object 使用默认的字母数字排序算法。

您现在知道如何以更合适的数据类型转换数据。例如,IPv4 地址可以被视为软件版本(数据类型[版本])正确排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property @{Expression={ $_.RemoteAddress -as [version] }}

RemoteAddress RemotePort State OwningProcess
------------- ---------- ----- -------------
20.42.65.89 443 Established 9836
20.42.65.89 443 TimeWait 0
20.199.120.182 443 Established 3552
20.199.120.182 443 Established 5564
35.186.224.25 443 TimeWait 0
45.60.13.212 443 Established 7644
51.104.30.131 443 Established 10220
142.250.185.74 443 TimeWait 0
142.250.186.46 443 TimeWait 0

现在 IP 地址已可以正确排序。

注意:当将数据转换为其他数据类型(如此处所示)时,始终优先使用 -as 运算符进行直接类型转换。如果数据不能转换为所需的数据类型,则 -as 运算符只会返回 $null,而直接类型转换将会产生异常。

在前面的例子中,如果 “RemoteAddress” 会显示一个 IPv6 地址(不能转换为 [version]),幸亏 -as 操作符,这些项目不会被排序,而会放在排序数据的开头.

要缩短代码,您可以简写哈希表的键(只要它们保持唯一):

1
2
3
Get-NetTCPConnection -RemotePort 443 |
Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess |
Sort-Object -Property @{E={ $_.RemoteAddress -as [version] }}

如果您需要的只是数据转换,则可以完全跳过哈希表并将脚本块直接传给 -Property 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Get-NetTCPConnection -RemotePort 443 | Select-Object -Property RemoteAddress, RemotePort, State, OwningProcess | Sort-Object -Property { $_.RemoteAddress -as [version] }

RemoteAddress RemotePort State OwningProcess
------------- ---------- ----- -------------
20.42.65.89 443 Established 9836
20.42.65.89 443 TimeWait 0
20.199.120.182 443 Established 3552
20.199.120.182 443 Established 5564
35.186.224.25 443 TimeWait 0
45.60.13.212 443 Established 7644
51.104.30.131 443 Established 10220
142.250.185.74 443 TimeWait 0
142.250.186.46 443 TimeWait 0

PowerShell 技能连载 - 高级排序(第 2 部分)

Sort-Object 支持高级排序,并在您传入哈希表时提供更多控制。例如,哈希表可以单独控制多个属性的排序方向。

例如,此行代码按状态对服务进行排序,然后按名称排序。排序方向可以通过 -Descending 开关参数控制,但始终适用于所有选定的属性:

1
2
3
Get-Service | Sort-Object -Property Status, DisplayName | Select-Object -Property DisplayName, Status

Get-Service | Sort-Object -Property Status, DisplayName -Descending | Select-Object -Property DisplayName, Status

要单独控制排序方向,请传入哈希表并使用 ExpressionDescending 键。这首先按状态(降序)对服务进行排序,然后按显示名称(升序):

1
2
3
Get-Service |
Sort-Object -Property @{Expression='Status'; Descending=$true}, @{Expression='DisplayName'; Descending=$false } |
Select-Object -Property DisplayName, Status

注意:当您查看结果时,您可能会对“状态”属性的内容排序方式感到恼火。尽管代码要求降序排序,但您首先会看到正在运行的服务,然后是停止的服务。

这个问题的简单答案是:您现在知道如何单独控制排序,因此如果排序顺序错误,只需尝试颠倒排序顺序,看看是否适合您。

深入的答案是:”Status“ 属性实际上是一个数字常量,而 Sort-Object 始终对底层数据进行排序。因此,当您使数字常量可见时,会发现排序顺序是正确的:

1
2
3
Get-Service |
Sort-Object -Property @{Expression='Status'; Descending=$true}, @{Expression='DisplayName'; Descending=$false } |
Select-Object -Property DisplayName, { [int]$_.Status }

正如您现在所看到的,”Running“ 实际上是常量 “4”,而 “Stopped” 由常量 1 表示。

不要错过我们的下一个技能,以获得更多控制(以及对由底层数字常量引起的问题的优雅修复)。

PowerShell 技能连载 - 高级排序(第 1 部分)

Sort-Object easily sorts results for you. For primitive data such as numbers or strings, simply add Sort-Object to your pipeline. This gets you a sorted list of lottery numbers:
Sort-Object 能轻松对结果排序。对于数字或字符串等原始数据,只需将 Sort-Object 添加到您的管道中。这将为您提供一个排序的彩票号码列表:

1
2
3
4
$lottery = 1..49 | Get-Random -Count 7 | Sort-Object
# set the string you want to use to separate numbers in your output
$ofs = ','
"Your numbers are $lottery"

具有多个属性的对象数据要求您定义要排序的属性。此行代码按状态对服务进行排序:

1
Get-Service | Sort-Object -Property Status

Sort-Object 甚至支持多种排序。此行代码按状态对服务进行排序,然后按服务名称排序:

1
Get-Service | Sort-Object -Property Status, DisplayName | Select-Object -Property DisplayName, Status

要反转排序顺序,请添加 -Descending 开关参数。

有关排序的更多控制,例如,对一个属性升序排序和另一个属性降序排序,请参阅我们的下一个提示。

PowerShell 技能连载 - 速度很重要

PowerShell 是一种通用自动化语言,因此它的目标是多功能且易于使用。速度不是首要任务。

如果您确实关心最大速度,那么有一些 cmdlet 几乎可以完全满足 .NET 调用的功能。在这些实例中使用直接 .NET 调用会更快,尤其是在经常调用这些 cmdlet 时(例如在循环中)。但另一方面是它使您的代码更难阅读。

这里有一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# cmdlet
PS> Join-Path -Path $env:temp -ChildPath test.txt
C:\Users\tobia\AppData\Local\Temp\test.txt

# direct .NET
PS> [System.IO.Path]::Combine($env:temp, 'test.txt')
C:\Users\tobia\AppData\Local\Temp\test.txt


# cmdlet
PS> Get-Date

Monday, October 4, 2021 12:34:46

# direct .NET
PS> [DateTime]::Now

Monday, October 4, 2021 12:34:52

PowerShell 技能连载 - 读取文本文件(快速)

如果您需要加载大文本文件并使用 Get-Content,那么您可以节省大量时间。

如果您没有立即通过管道处理通过管道发出的结果,则可能需要添加参数 -ReadCount 0。这可以使读取文本文件的速度提升 100 倍。

如果没有此参数,Get-Content 会单独对每个文本行产生一次输出。如果这些行要传递给管道处理,那么没有问题。但是如果要将文本存储在变量中并使用其他处理方式,则这是浪费时间,例如经典的 foreach 循环。

PowerShell 技能连载 - Working with Get-WinEvent

如果您想从 Windows 事件日志中读取系统事件,Get-Eventlog 是个易于使用且简单的选择。这段代码能够获取最新的 10 个错误和警告事件:

1
PS> Get-EventLog -LogName System -EntryType Error,Warning -Newest 10

不幸的是,Get-Eventlog 已被弃用,并且它不再可用于 PowerShell 7。弃用有很明显的原因:该 cmdlet 只能从“经典”日志文件中读取,它很缓慢并且具有其他限制。

这就是为什么 PowerShell 在3.0版中推出更好的替代命令:Get-WinEvent。PowerShell 7 也有此 cmdlet。

Unfortunately, Get-WinEvent is much harder to use because there are no intuitive parameters, and instead your filter criteria needs to be specified as a hash table.
不幸的是,Get-Winevent 更难使用,因为没有直观的参数,而是需要通过哈希表指定过滤条件。

但是,通过“代理函数”,您可以指导 Get-WinEvent 使用您所熟悉的旧式参数。以下是代码:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<#Suggestion to improve Get-WinEvent in order to make it compatible to the commonly used Get-EventLog callsBelow is a prototype using a proxy function. Run it to enhance Get-WinEvent.To get rid of the enhancement, either restart PowerShell or run:Remove-Item -Path function:Get-WinEventNote that the prototype emits the composed hash table to the console (green)
\#>function Get-WinEvent
{ [CmdletBinding(DefaultParameterSetName='GetLogSet', HelpUri='https://go.microsoft.com/fwlink/?LinkID=138336')]
param(

[Parameter(ParameterSetName='ListLogSet', Mandatory=$true, Position=0)]
[AllowEmptyCollection()]
[string[]]
${ListLog}, [Parameter(ParameterSetName='LogNameGetEventlog', Mandatory=$true, Position=0)] <#NEW\#>
[Parameter(ParameterSetName='GetLogSet', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${LogName}, [Parameter(ParameterSetName='ListProviderSet', Mandatory=$true, Position=0)]
[AllowEmptyCollection()]
[string[]]
${ListProvider}, <# Get-EventLog supports wildcards, Get-WinEvent does not. Needs to be corrected. #> [Parameter(ParameterSetName='GetProviderSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
[string[]]
${ProviderName}, [Parameter(ParameterSetName='FileSet', Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${Path}, [Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[ValidateRange(1, 9223372036854775807)]
[long]
${MaxEvents},
<# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateRange(0, 2147483647)]
[int]
${Newest},
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[Parameter(ParameterSetName='LogNameGetEventlog')] <#NEW\#>
[Alias('Cn')]
[ValidateNotNullOrEmpty()] <#CORRECTED\#> [string] <# used to be [String[]], Get-WinEvent accepts [string] only, should be changed to accept string arrays \#> ${ComputerName}, [Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[Parameter(ParameterSetName='FileSet')]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
${Credential}, [Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='GetLogSet')]
[ValidateNotNull()]
[string]
${FilterXPath}, [Parameter(ParameterSetName='XmlQuerySet', Mandatory=$true, Position=0)]
[xml]
${FilterXml}, [Parameter(ParameterSetName='HashQuerySet', Mandatory=$true, Position=0)]
[hashtable[]]
${FilterHashtable}, [Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='ListLogSet')]
[Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[switch]
${Force}, [Parameter(ParameterSetName='GetLogSet')]
[Parameter(ParameterSetName='GetProviderSet')]
[Parameter(ParameterSetName='FileSet')]
[Parameter(ParameterSetName='HashQuerySet')]
[Parameter(ParameterSetName='XmlQuerySet')]
[switch]
${Oldest},
<# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[datetime]
${After}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[datetime]
${Before},
<# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[string[]]
${UserName}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog', Position=1)]
[ValidateRange(0, 9223372036854775807)]
[ValidateNotNullOrEmpty()]
[long[]]
${InstanceId}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[ValidateNotNullOrEmpty()]
[ValidateRange(1, 2147483647)]
[int[]]
${Index}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('ET')]
[ValidateNotNullOrEmpty()]
[ValidateSet('Error','Information','FailureAudit','SuccessAudit','Warning')]
[string[]]
${EntryType}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('ABO')]
[ValidateNotNullOrEmpty()]
[string[]]
${Source}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[Alias('MSG')]
[ValidateNotNullOrEmpty()]
[string]
${Message}, <# NEW \#> [Parameter(ParameterSetName='LogNameGetEventlog')]
[switch]
${AsBaseObject},
[Parameter(ParameterSetName='ListGetEventlog')]
[switch]
${List}, [Parameter(ParameterSetName='ListGetEventlog')]
[switch]
${AsString}



)

begin {
try {
$outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1 }
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Diagnostics\Get-WinEvent', [System.Management.Automation.CommandTypes]::Cmdlet)

\# if the user chose the Get-EventLog compatible parameters, \# compose the appropriate filter hash table $scriptCmd = if ($PSCmdlet.ParameterSetName -eq 'LogNameGetEventlog')
{
\# mandatory parameter $filter = @{
LogName = $PSBoundParameters['Logname']
}
$null = $PSBoundParameters.Remove('LogName')

if ($PSBoundParameters.ContainsKey('Before'))
{
$filter['EndTime'] = $PSBoundParameters['Before']
$null = $PSBoundParameters.Remove('Before')
}
if ($PSBoundParameters.ContainsKey('After'))
{
$filter['StartTime'] = $PSBoundParameters['After']
$null = $PSBoundParameters.Remove('After')
}
if ($PSBoundParameters.ContainsKey('EntryType'))
{
\# severity is translated to an integer array
$levelFlags = [System.Collections.Generic.List[int]]@()

\# string input converted to integer array if ($PSBoundParameters['EntryType'] -contains 'Error')
{
$levelFlags.Add(1) \# critical $levelFlags.Add(2) \# error }
if ($PSBoundParameters['EntryType'] -contains 'Warning')
{
$levelFlags.Add(3) \# warning }
if ($PSBoundParameters['EntryType'] -contains 'Information')
{
$levelFlags.Add(4) \# informational $levelFlags.Add(5) \# verbose }


\# default to 0 if ($levelFlags.Count -gt 0)
{
$filter['Level'] = [int[]]$levelFlags }

\# audit settings stored in Keywords key if ($PSBoundParameters['EntryType'] -contains 'FailureAudit')
{
$filter['Keywords'] += 0x10000000000000 }
if ($PSBoundParameters['EntryType'] -contains 'SuccessAudit')
{
$filter['Keywords'] += 0x20000000000000 }
$null = $PSBoundParameters.Remove('EntryType')
}
if ($PSBoundParameters.ContainsKey('InstanceId'))
{
$filter['ID'] = $PSBoundParameters['InstanceId']
$null = $PSBoundParameters.Remove('InstanceId')
}
if ($PSBoundParameters.ContainsKey('Source'))
{
$filter['ProviderName'] = $PSBoundParameters['Source']
$null = $PSBoundParameters.Remove('Source')
}

$PSBoundParameters['FilterHashtable'] = $filter Write-Host ($filter | Out-String) -ForegroundColor Green
if ($PSBoundParameters.ContainsKey('Newest'))
{
$PSBoundParameters['MaxEvents'] = $PSBoundParameters['Newest']
$null = $PSBoundParameters.Remove('Newest')
}
}


$scriptCmd =
{
& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw }
}

process {
try {
$steppablePipeline.Process($_)
} catch {
throw }
}

end {
try {
$steppablePipeline.End()
} catch {
throw }
}
<#

.ForwardHelpTargetName Microsoft.PowerShell.Diagnostics\Get-WinEvent .ForwardHelpCategory Cmdlet \#>}

当您执行完这个函数,就可以通过您在 Get-EventLog 中熟悉的相同参数使用 Get-WinEvent

1
2
3
4
5
6
PS> Get-WinEvent -LogName System -EntryType Error,Warning -Newest 10

Name Value
---- -----
LogName {System}
Level {1, 2, 3}

您还可以获得基于参数使用的哈希表过滤器的键和值。

PowerShell 技能连载 - 调整脚本性能

如果脚本运行速度速度慢,找出导致延迟的地方并优化并不是一件很简单的事。使用名为 “psprofiler” 的 PowerShell 模块,可以测试脚本中的每行所需的时间。它在 Windows PowerShell 和 PowerShell 7 中都能运行。

首先安装模块:

1
PS> Install-Module -Name PSProfiler -Scope CurrentUser

下一步,用 Measure-Script 执行脚本:

1
PS> Measure-Script -Path 'C:\Users\tobias\test123.ps1'

Once your script completes, you get a sophisticated report telling you exactly how often each line of your script was executed, and how long it took:
脚本完成后,就会获得一份复杂的报告,告诉您脚本每行执行的次数,以及消耗的时间:

  Count  Line       Time Taken Statement
  -----  ----       ---------- ---------
      1     1    00:00.0033734 $Path = "$env:temp\tv.json"
      0     2    00:00.0000000
      1     3    00:28.1602885 $data = Get-Content -Path $Path -Raw |
      0     4    00:00.0000000 ConvertFrom-Json |
      1     5    00:26.6558438 ForEach-Object { $_ } |
      0     6    00:00.0000000 ForEach-Object {
      0     7    00:00.0000000
 101000     8    00:01.4408993   $title = '{0,5} [{2}] "{1}" ({3})' -f ([Object[]]$_)
 101000     9    00:13.6815132   $title | Add-Member -MemberType NoteProperty -Name Data -Value $_ -PassThru
      0    10    00:00.0000000 }
      0    11    00:00.0000000
...

PowerShell 技能连载 - 将 Ticks 转换为 DateTime

偶尔,日期和时间信息以所谓的“缺陷”的格式存储为 “Ticks”。 Ticks 是自 01/01/1601 以来,100 纳秒的单位数。Active Directory 在内部使用此格式,但您也可以在其他地方找到它。 以下是以 “Ticks” 为单位的 Windows 安装时间的示例:

1
2
3
4
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateTicks = $values.InstallTime

$installDateTicks

结果是(非常)大的 64 比特数字:

132457820129777032

要将 Ticks 转换为 DateTime,请使用 [DateTimeOffset]

1
2
3
4
5
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateTicks = $values.InstallTime

$installDate = [DateTimeOffset]::FromFileTime($installDateTicks)
$installDate.DateTime

PowerShell 技能连载 - 将 UNIX 时间转为 DateTime

“UNIX时间”计算自 01/01/1970 以来经过的秒数。

例如,在 Windows 中,您可以从 Windows 注册表中读取安装日期,返回的值为 “Unix时间”:

1
2
3
4
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateUnix = $values.InstallDate

$installDateUnix

结果是类似这样的大数字:

1601308412

To convert “Unix time” to a real DateTime value, .NET Framework provides a type called [DateTimeOffset]:
要将“UNIX时间”转换为真实的 DateTime 值,请使用 .NET Framework 提供的 [DateTimeOffset] 类:

1
2
3
4
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateUnix = $values.InstallDate

[DateTimeOffset]::FromUnixTimeSeconds($installDateUnix)

现在您能得到不同的日期和时间表示:

DateTime      : 28.09.2020 15:53:32
UtcDateTime   : 28.09.2020 15:53:32
LocalDateTime : 28.09.2020 17:53:32
Date          : 28.09.2020 00:00:00
Day           : 28
DayOfWeek     : Monday
DayOfYear     : 272
Hour          : 15
Millisecond   : 0
Minute        : 53
Month         : 9
Offset        : 00:00:00
Second        : 32
Ticks         : 637369052120000000
UtcTicks      : 637369052120000000
TimeOfDay     : 15:53:32
Year          : 2020

要获取本地格式的安装时间,您可以在一行代码中写完它:

1
2
3
PS> [DateTimeOffset]::FromUnixTimeSeconds((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').InstallDate).DateTime

Moday, September 28, 2020 15:53:32
PowerShell 技术 QQ 群