PowerShell 技能连载 - 在网格视图窗口中将对象数据显示为表格

通常,当将单个对象输出到 Out-GridView 时,将得到一行数据,并且每个属性都以一列的形式出现:

1
Get-ComputerInfo | Select-Object -Property * | Out-GridView

这样难以查看和过滤特定信息。只需将对象转换为有序哈希表,即可将其显示为网格视图窗口中的表。此外,您现在还可以消除空属性并确保对属性进行排序:

1
2
3
4
5
6
# make sure you have exactly ONE object
$info = Get-ComputerInfo
# find names of non-empty properties
$filledProperties = $info.PSObject.Properties.Name.Where{![string]::IsNullOrWhiteSpace($info.$_)} | Sort-Object
# turn object into a hash table and show in a grid view window
$filledProperties | ForEach-Object { $hash = [Ordered]@{} } { $hash[$_] = $info.$_ } { $hash } | Out-GridView

只要 $info 恰好包含一个对象,该方法就可以完美地工作。例如,您可以调整代码,并使用 “Get-AdUser -Identify SomeName -Properties *“ 代替 “Get-ComputerInfo“ 来列出给定用户的所有 Active Directory 属性。只要确保您精确地指定了一个用户即可。

由于此方法将对象转换为键值对,因此不适用于多个对象。

PowerShell 技能连载 - 移除空白的属性

WMI 和 Get-CimInstance 可以为您提供许多有用的信息,但是返回的对象通常包含许多空属性:

1
PS> Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property *

另外,属性不一定要排序。您可以通过识别和排序不为空的属性来进行修复:

1
2
3
4
5
6
# get all WMI information
$os = Get-CimInstance -ClassName Win32_OperatingSystem
# find names of non-empty properties
$filledProperties = $os.PSObject.Properties.Name.Where{![string]::IsNullOrWhiteSpace($os.$_)} | Sort-Object
# show non-empty properties only
$os | Select-Object -Property $filledProperties

PowerShell 技能连载 - 使用断言

通常,您的代码需要声明某些先决条件。例如,您可能要确保给定的文件夹存在,并使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
# path to download files to
$OutPath = "$env:temp\SampleData"

# does it already exist?
$exists = Test-Path -Path $OutPath -PathType Container

# no, create it
if (!$exists)
{
$null = New-Item -Path $OutPath -ItemType Directory
}

您可以开始使用断言函数库,而不必一遍又一遍地写代码。这是确保文件夹存在的一种:

1
2
3
4
5
6
7
8
filter Assert-FolderExists
{
$exists = Test-Path -Path $_ -PathType Container
if (!$exists) {
Write-Warning "$_ did not exist. Folder created."
$null = New-Item -Path $_ -ItemType Directory
}
}

使用此函数,您的代码将变得更加整洁。这些代码将文件夹路径分配给变量,并同时确保文件夹存在:

1
2
3
4
5
# making sure a bunch of folders exist
'C:\test1', 'C:\test2' | Assert-FolderExists

# making sure the path assigned to a variable exists
($Path = 'c:\test3') | Assert-FolderExists

在此处阅读有关此技术的更多信息:https://powershell.one/code/10.html

PowerShell 技能连载 - 局域网唤醒

无需外部“局域网唤醒”工具了。如果要唤醒网络计算机,只需告诉 PowerShell 目标计算机的 MAC 地址即可。这是一个组成 magic packet 并唤醒机器的函数:

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
function Invoke-WakeOnLan
{
param
(
# one or more MAC addresses
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
# MAC address must be a following this regex pattern
[ValidatePattern('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$')]
[string[]]
$MacAddress
)

begin
{
# instantiate a UDP client
$UDPclient = [System.Net.Sockets.UdpClient]::new()
}
process
{
foreach ($_ in $MacAddress)
{
try {
$currentMacAddress = $_

# get byte array from MAC address
$mac = $currentMacAddress -split '[:-]' |
# convert the hex number into byte
ForEach-Object {
[System.Convert]::ToByte($_, 16)
}

#region compose the "magic packet"

# create a byte array with 102 bytes initialized to 255 each
$packet = [byte[]](,0xFF * 102)

# leave the first 6 bytes untouched, and
# repeat the target MAC address bytes in bytes 7 through 102
6..101 | ForEach-Object {
# $_ is indexing in the byte array,
# $_ % 6 produces repeating indices between 0 and 5
# (modulo operator)
$packet[$_] = $mac[($_ % 6)]
}

#endregion

# connect to port 4000 on broadcast address
$UDPclient.Connect(([System.Net.IPAddress]::Broadcast),4000)

# send the magic packet to the broadcast address
$null = $UDPclient.Send($packet, $packet.Length)
Write-Verbose "Sent magic packet to $currentMacAddress..."
}
catch
{
Write-Warning "Unable to send ${mac}: $_"
}
}
}
end
{
# release the UDP client and free its memory
$UDPclient.Close()
$UDPclient.Dispose()
}
}

运行该函数后,可以通过以下方法唤醒计算机:

1
Invoke-WakeOnLan -MacAddress '24:EE:9A:54:1B:E5', '98:E7:43:B5:B2:2F' -Verbose

要找出目标机器的MAC地址,请在目标机器上运行此行代码或通过远程处理:

1
Get-CimInstance -Query 'Select * From Win32_NetworkAdapter Where NetConnectionStatus=2' | Select-Object -Property Name, Manufacturer, MacAddress

可以在这里找到更多信息:https://powershell.one/code/11.html

PowerShell 技能连载 - 转换十六进制数据

当您添加 “0x” 前缀时,PowerShell 可以交互地转换十六进制数字:

1
2
PS> 0xAB0f
43791

如果十六进制数存储在字符串中,则可以通过将类型应用于表达式来调用转换:

1
2
3
4
PS> $a = 'ab0f'

PS> [int]"0x$a"
43791

PowerShell 技能连载 - 自学习参数完成

对于用户而言,参数完成非常棒,因为始终建议使用有效的参数。许多内置的 PowerShell 命令带有参数完成功能。当您输入以下内容时,您可以看到该效果:

1
PS> Get-EventLog -LogName

-LogName 之后输入一个空格,以在 PowerShell ISE 编辑器中触发自动参数完成。在 PowerShell 控制台中,按 TAB 键。而在 Visual Studio Code 中,按 CTRL + SPACE。Get-EventLog 会自动建议计算机上实际存在的日志的日志名称。

您可以将相同的参数完成功能添加到自己的函数参数中。在前面的技巧中,我们解释了如何添加静态建议。现在让我们来看看如何添加自学习参数完成功能!

假设您使用 -Co​​mputerName 参数编写 PowerShell 函数。为了使您的函数更易于使用,请添加参数完成,以便自动向用户建议计算机名称和 IP 地址。

显然,您无法知道对用户很重要的计算机名称和IP地址,因此您无法添加静态列表。而是使用两个自定义属性:

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
# define [AutoLearn()]
class AutoLearnAttribute : System.Management.Automation.ArgumentTransformationAttribute
{
# define path to store hint lists
[string]$Path = "$env:temp\hints"

# define ID to manage multiple hint lists
[string]$Id = 'default'

# define prefix character used to delete the hint list
[char]$ClearKey = '!'

# define parameterless constructor
AutoLearnAttribute() : base()
{}

# define constructor with parameter for ID
AutoLearnAttribute([string]$Id) : base()
{
$this.Id = $Id
}

# Transform() is called whenever there is a variable or parameter assignment,
# and returns the value that is actually assigned
[object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object] $inputData)
{
# make sure the folder with hints exists
$exists = Test-Path -Path $this.Path
if (!$exists) { $null = New-Item -Path $this.Path -ItemType Directory }

# create a filename for hint list
$filename = '{0}.hint' -f $this.Id
$hintPath = Join-Path -Path $this.Path -ChildPath $filename

# use a hash table to keep hint list
$hints = @{}

# read hint list if it exists
$exists = Test-Path -Path $hintPath
if ($exists)
{
Get-Content -Path $hintPath -Encoding Default |
# remove leading and trailing blanks
ForEach-Object { $_.Trim() } |
# remove empty lines
Where-Object { ![string]::IsNullOrEmpty($_) } |
# add to hash table
ForEach-Object {
# value is not used, set it to $true
$hints[$_] = $true
}
}

# does the user input start with the clearing key?
if ($inputData.StartsWith($this.ClearKey))
{
# remove the prefix
$inputData = $inputData.SubString(1)

# clear the hint list
$hints.Clear()
}

# add new value to hint list
if(![string]::IsNullOrWhiteSpace($inputData))
{
$hints[$inputData] = $true
}
# save hints list
$hints.Keys | Sort-Object | Set-Content -Path $hintPath -Encoding Default

# return the user input (if there was a clearing key at its start,
# it is now stripped)
return $inputData
}
}

# define [AutoComplete()]
class AutoCompleteAttribute : System.Management.Automation.ArgumentCompleterAttribute
{
# define path to store hint lists
[string]$Path = "$env:temp\hints"

# define ID to manage multiple hint lists
[string]$Id = 'default'

# define parameterless constructor
AutoCompleteAttribute() : base([AutoCompleteAttribute]::_createScriptBlock($this))
{}

# define constructor with parameter for ID
AutoCompleteAttribute([string]$Id) : base([AutoCompleteAttribute]::_createScriptBlock($this))
{
$this.Id = $Id
}

# create a static helper method that creates the script block that the base constructor needs
# this is necessary to be able to access the argument(s) submitted to the constructor
# the method needs a reference to the object instance to (later) access its optional parameters
hidden static [ScriptBlock] _createScriptBlock([AutoCompleteAttribute] $instance)
{
$scriptblock = {
# receive information about current state
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

# create filename for hint list
$filename = '{0}.hint' -f $instance.Id
$hintPath = Join-Path -Path $instance.Path -ChildPath $filename

# use a hash table to keep hint list
$hints = @{}

# read hint list if it exists
$exists = Test-Path -Path $hintPath
if ($exists)
{
Get-Content -Path $hintPath -Encoding Default |
# remove leading and trailing blanks
ForEach-Object { $_.Trim() } |
# remove empty lines
Where-Object { ![string]::IsNullOrEmpty($_) } |
# filter completion items based on existing text
Where-Object { $_.LogName -like "$wordToComplete*" } |
# create argument completion results
Foreach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
}.GetNewClosure()
return $scriptblock
}
}

这就是您想要为自己的 PowerShell 函数添加尽可能多的自学习自动完成功能所需要的全部。

这是一个利用这两个属性的新的 PowerShell 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Connect-MyServer
{
param
(
[string]
[Parameter(Mandatory)]
# auto-learn user names to user.hint
[AutoLearn('user')]
# auto-complete user names from user.hint
[AutoComplete('user')]
$UserName,

[string]
[Parameter(Mandatory)]
# auto-learn computer names to server.hint
[AutoLearn('server')]
# auto-complete computer names from server.hint
[AutoComplete('server')]
$ComputerName
)

"Hello $Username, connecting you to $ComputerName"
}

运行代码后,会产生一个新的 Connect-MyServer 命令。-UserName-ComputerName 参数均提供自学习自动补全功能:每当您为这些参数之一分配值时,该参数都会“记住”该参数,并在下次向您建议记住的值。

首次调用 Connect-MyServer 时,没有参数完成。再次调用它时,系统会建议您以前的输入,并且随着时间的推移,您的函数会“学习”对用户重要的参数。

这两个参数使用独立的建议。只需确保在两个属性中都为建议列表提供名称即可。在上面的示例中,-UserName 参数使用 “user” 建议列表,而 -ComputerName 参数使用 “server” 建议列表。

如果要清除有关参数的建议,请在参数前添加一个感叹号。该调用将清除 -ComputerName 参数的建议:

1
2
PS> Connect-MyServer -UserName tobias -ComputerName !server12
Hello tobias, connecting you to server12

重要说明:由于 PowerShell 中存在一个长期存在的错误,参数定义完成在定义实际功能的编辑器脚本窗格中不起作用。它始终可以在交互式控制台(这是最重要的用例)和任何其他脚本窗格中使用。

有关此处使用的技术的更多详细信息,请访问 https://powershell.one/powershell-internals/attributes/custom-attributes

PowerShell 技能连载 - 添加参数自动完成(第 2 部分)

在上一个技能中,我们讨论了已添加到 PowerShell 7 新的 [ArgumentCompletions()] 属性,以及如何使用它向函数参数添加复杂的参数完成功能。

不幸的是,该属性在 Windows PowerShell 中不可用,因此使用此属性,您的代码将不再与 Windows PowerShell 兼容。

当然,您还可以将属性添加到 Windows PowerShell。当您在Windows PowerShell中运行以下代码时, [ArgumentCompletions()] 属性将变为可用。PowerShell 7 代码仍然保持兼容,现在您也可以在Windows PowerShell中开始使用此属性:

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
# are we running in Windows PowerShell?
if ($PSVersionTable.PSEdition -ne 'Core')
{
# add the attribute [ArgumentCompletions()]
$code = @'
using System;
using System.Collections.Generic;
using System.Management.Automation;

public class ArgumentCompletionsAttribute : ArgumentCompleterAttribute
{

private static ScriptBlock _createScriptBlock(params string[] completions)
{
string text = "\"" + string.Join("\",\"", completions) + "\"";
string code = "param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams);@(" + text + ") -like \"*$WordToComplete*\" | Foreach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }";
return ScriptBlock.Create(code);
}

public ArgumentCompletionsAttribute(params string[] completions) : base(_createScriptBlock(completions))
{
}
}
'@

$null = Add-Type -TypeDefinition $code *>&1
}

如您所见,代码仅在 Windows PowerShell 中运行时才添加新属性。在PowerShell 7中,该属性已内置。

现在,无论您打算在 Windows PowerShell 还是Windows 7 中使用它,都可以在函数中使用复杂的参数完成。只需将上面的代码添加到代码中,以确保存在该属性。

这是使用属性并提供参数完成的函数的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Get-Country
{
param
(
# suggest country names:
[ArgumentCompletions('USA','Germany','Norway','Sweden','Austria','YouNameIt')]
[string]
$Name
)

# return parameter
$PSBoundParameters
}

当您在 PowerShell ISE(它是纯粹的 Windows PowerShell)中运行代码,然后在交互式控制台中使用 Get-Country 时,PowerShell ISE 会自动为 -Name 参数建议参数值(国家/地区名称)。

这里还有两件事要考虑:

  • 由于 PowerShell 中存在长期的错误,这种类型的参数完成功能在定义实际功能的编辑器脚本窗格中不起作用。它始终可以在交互式控制台(这是最重要的用例)和任何其他脚本窗格中使用。
  • [ValidateSet()] 属性相反,新的 [ArgumentCompletions()] 属性并不将用户输入限制为列出的值。新属性仅提供您定义的建议,而不以任何方式限制用户输入。

有关此处使用的技术的更多详细信息,请访问 https://powershell.one/powershell-internals/attributes/auto-completion

PowerShell 技能连载 - 添加参数自动完成(第 1 部分)

在 PowerShell 函数参数中添加参数完成功能可以极大地提高函数的可用性。一种常见的方法是将 [ValidateSet()] 属性添加到您的参数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Get-Country
{
param
(
# suggest country names
[ValidateSet('USA','Germany','Norway','Sweden','Austria','YouNameIt')]
[string]
$Name
)

# return parameter
$PSBoundParameters
}

现在,当用户使用 Get-Country 命令并传入 -Name 参数时,该函数现在会在用户按下 TAB 时建议国家/地区名称。像 PowerShell ISE 或 Visual Studio Code 这样的复杂PowerShell 编辑器甚至会自动打开 IntelliSense 菜单,或者在您按 CTRL + SPACE 显示所有的值。

但是,[ValidateSet()] 属性将用户限制为列出的值。无法输入其他值。如果只想为 -ComputerName 参数建议最常用的服务器名称,则将用户限制为仅使用这些服务器名称。这不是一个好主意。

从 PowerShell 7 开始,有一个名为 [ArgumentCompletions()] 的新属性,该属性几乎与 [ValidateSet()] 相似,但它跳过了验证部分。它提供相同的参数补全,但不限制用户输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Get-Country
{
param
(
# suggest country names
[ArgumentCompletions('USA','Germany','Norway','Sweden','Austria','YouNameIt')]
[string]
$Name
)

# return parameter
$PSBoundParameters
}

此版本的 Get-Country 提供国家名称建议,但是如果您愿意,您仍然可以输入其他任何国家名称。

重要提示:由于 PowerShell 中的错误,参数自动完成对于定义具体功能的脚本窗格不起作用。而在 PowerShell 控制台和任何其他编辑器脚本窗格中,参数自动完成可以正常工作。

Windows PowerShell 中缺少新的 [ArgumentCompletions()] 属性,因此在使用它时,您的函数不再与 Windows PowerShell 兼容。我们将在即将到来的提示中解决此问题。

PowerShell 技能连载 - 解析 Windows 产品密钥(第 2 部分)

在上一个技能中,我们解释了如何向 WMI 请求 Windows 的部分产品密钥。如果您丢失了原始产品密钥,可以通过以下方法恢复完整密钥:

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
function Get-WindowsProductKey{
\# test whether this is Windows 7 or older function Test-Win7 {
$OSVersion = [System.Environment]::OSVersion.Version ($OSVersion.Major -eq 6 -and $OSVersion.Minor -lt 2) -or $OSVersion.Major -le 6 }

\# implement decoder $code = @'// original implementation: https://github.com/mrpeardotnet/WinProdKeyFinder
using System;
using System.Collections;

public static class Decoder
{
public static string DecodeProductKeyWin7(byte[] digitalProductId)
{
const int keyStartIndex = 52;
const int keyEndIndex = keyStartIndex + 15;
var digits = new[]
{
'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R',
'T', 'V', 'W', 'X', 'Y', '2', '3', '4', '6', '7', '8', '9',
};
const int decodeLength = 29;
const int decodeStringLength = 15;
var decodedChars = new char[decodeLength];
var hexPid = new ArrayList();
for (var i = keyStartIndex; i <= keyEndIndex; i++)
{
hexPid.Add(digitalProductId[i]);
}
for (var i = decodeLength - 1; i >= 0; i--)
{
// Every sixth char is a separator.
if ((i + 1) % 6 == 0)
{
decodedChars[i] = '-';
}
else
{
// Do the actual decoding.
var digitMapIndex = 0;
for (var j = decodeStringLength - 1; j >= 0; j--)
{
var byteValue = (digitMapIndex << 8) | (byte)hexPid[j];
hexPid[j] = (byte)(byteValue / 24);
digitMapIndex = byteValue % 24;
decodedChars[i] = digits[digitMapIndex];
}
}
}
return new string(decodedChars);
}

public static string DecodeProductKey(byte[] digitalProductId)
{
var key = String.Empty;
const int keyOffset = 52;
var isWin8 = (byte)((digitalProductId[66] / 6) & 1);
digitalProductId[66] = (byte)((digitalProductId[66] & 0xf7) | (isWin8 & 2) * 4);

const string digits = "BCDFGHJKMPQRTVWXY2346789";
var last = 0;
for (var i = 24; i >= 0; i--)
{
var current = 0;
for (var j = 14; j >= 0; j--)
{
current = current*256;
current = digitalProductId[j + keyOffset] + current;
digitalProductId[j + keyOffset] = (byte)(current/24);
current = current%24;
last = current;
}
key = digits[current] + key;
}

var keypart1 = key.Substring(1, last);
var keypart2 = key.Substring(last + 1, key.Length - (last + 1));
key = keypart1 + "N" + keypart2;

for (var i = 5; i < key.Length; i += 6)
{
key = key.Insert(i, "-");
}

return key;
}
}'@ \# compile C# Add-Type -TypeDefinition $code
\# get raw product key $digitalId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name DigitalProductId).DigitalProductId
$isWin7 = Test-Win7 if ($isWin7)
{
\# use static C# method [Decoder]::DecodeProductKeyWin7($digitalId)
}
else {
\# use static C# method: [Decoder]::DecodeProductKey($digitalId)
}
}

PowerShell 技能连载 - 解析 Windows 产品密钥(第 1 部分)

有很多脚本示例,甚至还有密钥恢复工具,它们都承诺会返回完整的产品密钥,但是在许多情况下,返回的密钥不是 Windows 产品密钥。

当您使用密钥恢复工具时,通常会丢失产品密钥,因此没有简单的方法来检查密钥恢复脚本或工具返回的承诺密钥是否正确。

幸运的是,WMI 至少可以返回“部分”产品密钥。这样,您可以验证恢复的密钥是否是有效的。

SoftwareLicensingProduct WMI 类返回有关大多数 Microsoft 产品的许可状态的详细信息。下面的此行获取所有以 “Windows” 开头且许可证状态为非 0 的 Microsoft 产品的所有许可信息:

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
PS> Get-CimInstance -ClassName SoftwareLicensingProduct -Filter 'Name LIKE "Windows%" AND LicenseStatus>0'


ADActivationCsvlkPid :
ADActivationCsvlkSkuId :
ADActivationObjectDN :
ADActivationObjectName :
ApplicationID : 55c92734-d682-4d71-983e-d6ec3f16059f
AutomaticVMActivationHostDigitalPid2 :
AutomaticVMActivationHostMachineName :
AutomaticVMActivationLastActivationTime : 01.01.1601 01:00:00
Description : Windows(R) Operating System, OEM_DM channel
DiscoveredKeyManagementServiceMachineIpAddress :
DiscoveredKeyManagementServiceMachineName :
DiscoveredKeyManagementServiceMachinePort : 0
EvaluationEndDate : 01.01.1601 01:00:00
ExtendedGrace : 4294967295
GenuineStatus : 0
GracePeriodRemaining : 0
IAID :
ID : bd3762d7-270d-4760-8fb3-d829ca45278a
IsKeyManagementServiceMachine : 0
KeyManagementServiceCurrentCount : 4294967295
KeyManagementServiceFailedRequests : 4294967295
KeyManagementServiceLicensedRequests : 4294967295
KeyManagementServiceLookupDomain :
KeyManagementServiceMachine :
KeyManagementServiceNonGenuineGraceRequests : 4294967295
KeyManagementServiceNotificationRequests : 4294967295
KeyManagementServiceOOBGraceRequests : 4294967295
KeyManagementServiceOOTGraceRequests : 4294967295
KeyManagementServicePort : 0
KeyManagementServiceProductKeyID :
KeyManagementServiceTotalRequests : 4294967295
KeyManagementServiceUnlicensedRequests : 4294967295
LicenseDependsOn :
LicenseFamily : Professional
LicenseIsAddon : False
LicenseStatus : 1
LicenseStatusReason : 1074066433
MachineURL :
Name : Windows(R), Professional edition
OfflineInstallationId : 563276155667058052465840741114524545879016766601431504369777043
PartialProductKey : WFG6P
...

不幸的是,此调用需要很长时间才能完成。为了加快速度,请告诉 WMI 您要做什么,以便该调用不会计算您不需要的大量信息。下面的调用仅从所需实例中读取 PartialProductKey 属性,并且速度更快:

1
2
3
PS> Get-CimInstance -Query 'Select PartialProductKey From SoftwareLicensingProduct Where Name LIKE "Windows%" AND LicenseStatus>0' | Select-Object -ExpandProperty PartialProductKey

WFG6P