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

Sort-Object 是用于排序的 cmdlet:只需指定要排序的属性,Sort-Object 涵盖其余部分,包括根据属性数据类型选择正确的排序算法:

1
Get-Service | Sort-Object -Property DisplayName -Descending

一个鲜为人知的事实是 Sort-Object 也接受哈希表,这给了你更多的控制权。例如,您可以像这样轻松地对多个属性进行排序:

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

但是,如果您想控制每个属性的排序方向,则需要一个哈希表。此示例按降序对状态进行排序,但按升序对显示名称进行排序:

1
2
3
4
5
6
$displayName = @{
Expression = "DisplayName"
Descending = $false
}

Get-Service | Sort-Object -Property Status, $displayName -Descending

PowerShell 技能连载 - 对文本做哈希

PowerShell 附带了 Get-FileHash 命令,它读取文件并计算唯一的哈希值。这非常适合测试文件是否具有相同的内容。但是,无法对纯文本进行哈希处理。

当然,您可以将要哈希的文本写入文件,然后使用 Get-FileHash

更好的方法是 Get-FileHash 的一个很少人知道的功能。除了传入文件路径之外,您还可以提交所谓的 “MemoryStream“,当您将文本加载内存流,再调用命令,就能得到哈希值:

1
2
3
4
5
6
7
8
9
10
11
$text = 'this is a test'

$memoryStream = [System.IO.MemoryStream]::new()
$streamWriter = [System.IO.StreamWriter]::new($MemoryStream)
$streamWriter.Write($text)
$streamWriter.Flush()
$memoryStream.Position = 0
$hash = Get-FileHash -InputStream $MemoryStream -Algorithm 'SHA1'
$memoryStream.Dispose()
$streamWriter.Dispose()
$hash.Hash

只是不要忘记在使用后处理 MemoryStreamStreamWriter 对象以释放内存。

PowerShell 技能连载 - 检测 Windows 版本(第 3 部分)

在上一个技巧中,我们说明访问 WinRT 类 AnalyticsInfo 似乎是读取当前 Windows 10 版本的唯一受支持方式。与使用上一个示例中的异步方法不同,为了仅获取当前的 Windows 10 版本,这里有一个更简单的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# get raw Windows version
[int64]$rawVersion =
[Windows.System.Profile.AnalyticsInfo,Windows.System.Profile,ContentType=WindowsRuntime].
GetMember('get_VersionInfo').Invoke( $Null, $Null ).DeviceFamilyVersion

# decode bits to version bytes
$major = ( $rawVersion -band 0xFFFF000000000000l ) -shr 48
$minor = ( $rawVersion -band 0x0000FFFF00000000l ) -shr 32
$build = ( $rawVersion -band 0x00000000FFFF0000l ) -shr 16
$revision = $rawVersion -band 0x000000000000FFFFl

# compose version
$winver = [System.Version]::new($major, $minor, $build, $revision)
$winver

请注意,PowerShell 7 (pwsh.exe) 无法访问此 API。该代码需要 Windows PowerShell (powershell.exe)。

PowerShell 技能连载 - 检测 Windows 版本(第 2 部分)

在之前的技能中,我们报告了 ReleaseId 已弃用,无法再用于正确识别当前的 Windows 10 版本。相反,应该使用 DisplayVersion

1
2
PS> (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').DisplayVersion
20H2

但是,DisplayVersion 也不是确定当前 Windows 10 版本的可靠方法,因为其最初的目的是使外壳能够向用户报告当前版本。它也可能在未来发生变化。

识别当前 Windows 10 版本的唯一受支持的安全方法是使用名为 AnalyticsInfo 的操作系统类。不过用起来比较复杂,因为该类在 WinRT 中异步运行。PowerShell 7 (pwsh.exe) 无法访问此类。但是,内置的 Windows PowerShell (powershell.exe) 可以创建包装器并返回信息:

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
# load WinRT and runtime types
[System.Void][Windows.System.Profile.AnalyticsInfo,Windows.System.Profile,ContentType=WindowsRuntime]
Add-Type -AssemblyName 'System.Runtime.WindowsRuntime'

# define call and information to query
[Collections.Generic.List[System.String]]$names = 'DeviceFamily',
'OSVersionFull',
'FlightRing',
'App',
'AppVer'

$task = [Windows.System.Profile.AnalyticsInfo]::GetSystemPropertiesAsync($names)

# use reflection to find method definition
$definition = [System.WindowsRuntimeSystemExtensions].GetMethods().Where{
$_.Name -eq 'AsTask' -and
$_.GetParameters().Count -eq 1 -and
$_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1'
}

# create generic method
$Method = $definition.MakeGenericMethod( [System.Collections.Generic.IReadOnlyDictionary[System.String,System.String]] )

# call async method and wait for completion
$task = $Method.Invoke.Invoke($null, $task)
$null = $task.Wait(-1)

# emit output
$task.Result

结果类似于:

Key           Value
---           -----
OSVersionFull 10.0.19042.985.amd64fre.vb_release.191206-1406
FlightRing    Retail
App           powershell_ise.exe
AppVer        10.0.19041.1
DeviceFamily  Windows.Desktop

OSVersionFull 返回有关当前 Windows 版本的完整详细信息。

请注意,上面示例中调用的方法可以检索更多详细信息。$names 列出要查询的属性名称。不幸的是,没有办法发现可用的属性名称,因为第三方方可能会添加无穷无尽的附加信息。本示例中使用的五个属性是唯一保证的属性。

PowerShell 技能连载 - 检测 Windows 版本(第 1 部分)

可以从 Windows 注册表轻松读取当前的 Windows 版本:

1
2
3
4
5
PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | Select-Object -Property ReleaseId, DisplayVersion

ReleaseId DisplayVersion
--------- --------------
2009 20H2

请注意,不推荐使用 ReleaseId 中显示的信息。新的 Windows 10 版本改为使用 DisplayVersion 属性。要正确识别 Windows 10 版本,请确保使用 DisplayVersion 而不是 ReleaseId

在上面的示例中,您可以看到 Windows 10 报告了许多不同版本的相同 ReleaseId (2009)。例如,DisplayVersion 20H1 也使用 ReleaseId 2009。

微软宣布 ReleaseId 已弃用,不能再用于正确识别 Windows 10 版本。这对于脚本以及 WSUS、WAC 等工具可能很重要。例如,像 Get-WindowsImage 这样的 cmdlet 现在很难识别正确的 Windows 10 版本。

PowerShell 技能连载 - 原版 Windows 10 产品密钥

有大量 PowerShell 脚本到处流传,它们生成可以解码原始的 Windows 10 产品密钥。大多数这些脚本使用过时的算法,这些算法不再适用于 Windows 10。

这是检索原始 Windows 10 产品密钥的更简单的方法:

1
2
Get-CimInstance -ClassName SoftwareLicensingService |
Select-Object -ExpandProperty OA3xOriginalProductKey

如果该命令不产生任何返回信息,则操作系统安装中没有存储单独的产品密钥。

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
...