PowerShell 技能连载 - 识别操作系统详细信息

当您查询操作系统详细信息时,WMI 会返回一个数字:

1
2
3
PS> Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -ExpandProperty SuiteMask
272

SuiteMask 实际上是一个位掩码,其中每个位代表一个特定的细节。要将其转换为可读的文本,请使用标志枚举:

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
$SuiteMask = @{
Name = 'SuiteMaskText'
Expression = {
[Flags()] Enum EnumSuiteMask
{
SmallBusinessServer = 1
Server2008Enterprise = 2
BackOfficeComponents = 4
CommunicationsServer = 8
TerminalServices = 16
SmallBusinessServerRestricted = 32
WindowsEmbedded = 64
DatacenterEdition = 128
TerminalServicesSingleSession = 256
HomeEdition = 512
WebServerEdition = 1024
StorageServerEdition = 8192
ComputeClusterEdition = 16384
}

[EnumSuiteMask][int]$_.SuiteMask
}
}

Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property Caption, SuiteMask, $SuiteMask

这将添加一个计算得出的 SuiteMaskText 属性,该属性列出了已安装的操作系统扩展:

Caption                  SuiteMask                                   SuiteMaskText
-------                  ---------                                   -------------
Microsoft Windows 10 Pro       272 TerminalServices, TerminalServicesSingleSession

PowerShell 技能连载 - 识别 Windows 类型

WMI 返回每个不同的 Windows SKU 的密码编号:

1
2
PS> Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -ExpandProperty OperatingSystemSKU
48

要将此数字转换为有意义的文本(例如出于报告目的),请尝试以下操作:

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
$OperatingSystemSKU = @{
Name = 'OperatingSystemSKUText'
Expression = {
$value = $_.OperatingSystemSKU

switch([int]$value)
{
0 {'PRODUCT_UNDEFINED'}
1 {'PRODUCT_ULTIMATE'}
2 {'PRODUCT_HOME_BASIC'}
3 {'PRODUCT_HOME_PREMIUM'}
4 {'PRODUCT_ENTERPRISE'}
5 {'PRODUCT_HOME_BASIC_N'}
6 {'PRODUCT_BUSINESS'}
7 {'PRODUCT_STANDARD_SERVER'}
8 {'PRODUCT_DATACENTER_SERVER'}
9 {'PRODUCT_SMALLBUSINESS_SERVER'}
10 {'PRODUCT_ENTERPRISE_SERVER'}
11 {'PRODUCT_STARTER'}
12 {'PRODUCT_DATACENTER_SERVER_CORE'}
13 {'PRODUCT_STANDARD_SERVER_CORE'}
14 {'PRODUCT_ENTERPRISE_SERVER_CORE'}
15 {'PRODUCT_ENTERPRISE_SERVER_IA64'}
16 {'PRODUCT_BUSINESS_N'}
17 {'PRODUCT_WEB_SERVER'}
18 {'PRODUCT_CLUSTER_SERVER'}
19 {'PRODUCT_HOME_SERVER'}
20 {'PRODUCT_STORAGE_EXPRESS_SERVER'}
21 {'PRODUCT_STORAGE_STANDARD_SERVER'}
22 {'PRODUCT_STORAGE_WORKGROUP_SERVER'}
23 {'PRODUCT_STORAGE_ENTERPRISE_SERVER'}
24 {'PRODUCT_SERVER_FOR_SMALLBUSINESS'}
25 {'PRODUCT_SMALLBUSINESS_SERVER_PREMIUM'}
26 {'PRODUCT_HOME_PREMIUM_N'}
27 {'PRODUCT_ENTERPRISE_N'}
28 {'PRODUCT_ULTIMATE_N'}
29 {'PRODUCT_WEB_SERVER_CORE'}
30 {'PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT'}
31 {'PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY'}
32 {'PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING'}
33 {'PRODUCT_SERVER_FOUNDATION'}
34 {'PRODUCT_HOME_PREMIUM_SERVER'}
35 {'PRODUCT_SERVER_FOR_SMALLBUSINESS_V'}
36 {'PRODUCT_STANDARD_SERVER_V'}
37 {'PRODUCT_DATACENTER_SERVER_V'}
38 {'PRODUCT_ENTERPRISE_SERVER_V'}
39 {'PRODUCT_DATACENTER_SERVER_CORE_V'}
40 {'PRODUCT_STANDARD_SERVER_CORE_V'}
41 {'PRODUCT_ENTERPRISE_SERVER_CORE_V'}
42 {'PRODUCT_HYPERV'}
43 {'PRODUCT_STORAGE_EXPRESS_SERVER_CORE'}
44 {'PRODUCT_STORAGE_STANDARD_SERVER_CORE'}
45 {'PRODUCT_STORAGE_WORKGROUP_SERVER_CORE'}
46 {'PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE'}
47 {'PRODUCT_STARTER_N'}
48 {'PRODUCT_PROFESSIONAL'}
49 {'PRODUCT_PROFESSIONAL_N'}
50 {'PRODUCT_SB_SOLUTION_SERVER'}
51 {'PRODUCT_SERVER_FOR_SB_SOLUTIONS'}
52 {'PRODUCT_STANDARD_SERVER_SOLUTIONS'}
53 {'PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE'}
54 {'PRODUCT_SB_SOLUTION_SERVER_EM'}
55 {'PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM'}
56 {'PRODUCT_SOLUTION_EMBEDDEDSERVER'}
57 {'PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE'}
58 {'PRODUCT_PROFESSIONAL_EMBEDDED'}
59 {'PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT'}
60 {'PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL'}
61 {'PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC'}
62 {'PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC'}
63 {'PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE'}
64 {'PRODUCT_CLUSTER_SERVER_V'}
65 {'PRODUCT_EMBEDDED'}
66 {'PRODUCT_STARTER_E'}
67 {'PRODUCT_HOME_BASIC_E'}
68 {'PRODUCT_HOME_PREMIUM_E'}
69 {'PRODUCT_PROFESSIONAL_E'}
70 {'PRODUCT_ENTERPRISE_E'}
71 {'PRODUCT_ULTIMATE_E'}
72 {'PRODUCT_ENTERPRISE_EVALUATION'}
76 {'PRODUCT_MULTIPOINT_STANDARD_SERVER'}
77 {'PRODUCT_MULTIPOINT_PREMIUM_SERVER'}
79 {'PRODUCT_STANDARD_EVALUATION_SERVER'}
80 {'PRODUCT_DATACENTER_EVALUATION_SERVER'}
84 {'PRODUCT_ENTERPRISE_N_EVALUATION'}
85 {'PRODUCT_EMBEDDED_AUTOMOTIVE'}
86 {'PRODUCT_EMBEDDED_INDUSTRY_A'}
87 {'PRODUCT_THINPC'}
88 {'PRODUCT_EMBEDDED_A'}
89 {'PRODUCT_EMBEDDED_INDUSTRY'}
90 {'PRODUCT_EMBEDDED_E'}
91 {'PRODUCT_EMBEDDED_INDUSTRY_E'}
92 {'PRODUCT_EMBEDDED_INDUSTRY_A_E'}
95 {'PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVE'}
96 {'PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER'}
97 {'PRODUCT_CORE_ARM'}
98 {'PRODUCT_CORE_N'}
99 {'PRODUCT_CORE_COUNTRYSPECIFIC'}
100 {'PRODUCT_CORE_SINGLELANGUAGE'}
101 {'PRODUCT_CORE'}
103 {'PRODUCT_PROFESSIONAL_WMC'}
105 {'PRODUCT_EMBEDDED_INDUSTRY_EVAL'}
106 {'PRODUCT_EMBEDDED_INDUSTRY_E_EVAL'}
107 {'PRODUCT_EMBEDDED_EVAL'}
108 {'PRODUCT_EMBEDDED_E_EVAL'}
109 {'PRODUCT_NANO_SERVER'}
110 {'PRODUCT_CLOUD_STORAGE_SERVER'}
111 {'PRODUCT_CORE_CONNECTED'}
112 {'PRODUCT_PROFESSIONAL_STUDENT'}
113 {'PRODUCT_CORE_CONNECTED_N'}
114 {'PRODUCT_PROFESSIONAL_STUDENT_N'}
115 {'PRODUCT_CORE_CONNECTED_SINGLELANGUAGE'}
116 {'PRODUCT_CORE_CONNECTED_COUNTRYSPECIFIC'}
117 {'PRODUCT_CONNECTED_CAR'}
118 {'PRODUCT_INDUSTRY_HANDHELD'}
119 {'PRODUCT_PPI_PRO'}
120 {'PRODUCT_ARM64_SERVER'}
121 {'PRODUCT_EDUCATION'}
122 {'PRODUCT_EDUCATION_N'}
123 {'PRODUCT_IOTUAP'}
124 {'PRODUCT_CLOUD_HOST_INFRASTRUCTURE_SERVER'}
125 {'PRODUCT_ENTERPRISE_S'}
126 {'PRODUCT_ENTERPRISE_S_N'}
127 {'PRODUCT_PROFESSIONAL_S'}
128 {'PRODUCT_PROFESSIONAL_S_N'}
129 {'PRODUCT_ENTERPRISE_S_EVALUATION'}
130 {'PRODUCT_ENTERPRISE_S_N_EVALUATION'}
135 {'PRODUCT_HOLOGRAPHIC'}
138 {'PRODUCT_PRO_SINGLE_LANGUAGE'}
139 {'PRODUCT_PRO_CHINA'}
140 {'PRODUCT_ENTERPRISE_SUBSCRIPTION'}
141 {'PRODUCT_ENTERPRISE_SUBSCRIPTION_N'}
143 {'PRODUCT_DATACENTER_NANO_SERVER'}
144 {'PRODUCT_STANDARD_NANO_SERVER'}
145 {'PRODUCT_DATACENTER_A_SERVER_CORE'}
146 {'PRODUCT_STANDARD_A_SERVER_CORE'}
147 {'PRODUCT_DATACENTER_WS_SERVER_CORE'}
148 {'PRODUCT_STANDARD_WS_SERVER_CORE'}
149 {'PRODUCT_UTILITY_VM'}
159 {'PRODUCT_DATACENTER_EVALUATION_SERVER_CORE'}
160 {'PRODUCT_STANDARD_EVALUATION_SERVER_CORE'}
161 {'PRODUCT_PRO_WORKSTATION'}
162 {'PRODUCT_PRO_WORKSTATION_N'}
164 {'PRODUCT_PRO_FOR_EDUCATION'}
165 {'PRODUCT_PRO_FOR_EDUCATION_N'}
168 {'PRODUCT_AZURE_SERVER_CORE'}
169 {'PRODUCT_AZURE_NANO_SERVER'}
171 {'PRODUCT_ENTERPRISEG'}
172 {'PRODUCT_ENTERPRISEGN'}
175 {'PRODUCT_SERVERRDSH'}
178 {'PRODUCT_CLOUD'}
179 {'PRODUCT_CLOUDN'}
180 {'PRODUCT_HUBOS'}
182 {'PRODUCT_ONECOREUPDATEOS'}
183 {'PRODUCT_CLOUDE'}
184 {'PRODUCT_ANDROMEDA'}
185 {'PRODUCT_IOTOS'}
186 {'PRODUCT_CLOUDEN'}
default {"$value"}
}

}
}

Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property Caption, OperatingSystemSKU, $OperatingSystemSKU

输出将添加一个具有友好名称的新 OperatingSystemSKUText 属性:

Caption                  OperatingSystemSKU OperatingSystemSKUText
-------                  ------------------ ----------------------
Microsoft Windows 10 Pro                 48 PRODUCT_PROFESSIONAL

PowerShell 技能连载 - 操作系统的启动和安装时间

WMI 类 Win32_OperatingSystem 提供了有关许多日期时间信息的丰富信息,包括上次启动的日期和安装时间:

1
2
3
$dateTimeProps = 'InstallDate', 'LastBootupTime', 'LocalDateTime', 'CurrentTimeZone', 'CountryCode'

Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property $dateTimeProps

结果看起来像这样:

InstallDate     : 03.09.2019 12:42:41
LastBootupTime  : 03.05.2020 12:15:45
LocalDateTime   : 04.05.2020 10:43:55
CurrentTimeZone : 120
CountryCode     : 49

如果您想知道系统运行了多少分钟,或者自安装以来已经过去了几天,请使用 New-TimeSpan:

1
2
3
4
5
6
7
$os = Get-CimInstance -ClassName Win32_OperatingSystem

$installedDays = (New-TimeSpan -Start $os.InstallDate).Days
$runningMinutes = [int](New-TimeSpan -Start $os.LastBootupTime).TotalMinutes

"Your copy of Windows was installed $installedDays days ago."
"Your system is up for {0:n0} minutes." -f $runningMinutes

结果看起来像这样:

Your copy of Windows was installed 243 days ago.
Your system is up for 1.353 minutes.

Get-CimInstance cmdlet可用于在本地和远程计算机上查询信息(前提是您具有适当的权限)。有关如何远程使用Get-CimInstance的更多信息,请访问https://powershell.one/wmi/remote-access

PowerShell 技能连载 - 改变操作系统描述

每个 Windows 操作系统都有一个描述,您可以使用以下命令查看(和更改)该描述:

1
PS> control sysdm.cpl

要通过 PowerShell 自动执行此操作,请使用以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
# change operating system description
# (requires admin privileges)
$values = @{
Description = 'My Computer'
}
Set-CimInstance -Query 'Select * from Win32_OperatingSystem' -Property $values

# read description
# (no admin privileges required)
$description = Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object -ExpandProperty Description

"OS Description: $description"

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