The Largest PowerShell Community in China has 1885 Members Now!

The largest PowerShell community “PowerShell Tech Interact” in China has 1885 members up to March 23, 2020!

The goal of this community is to:

  1. Help new PowerShellers to get up to PowerShell language
  2. Help every PowerSheller overcome any PowerShell technical difficulties
  3. Share code and information to accelerate the learning process

To contact with the community manager, please visit this MVP link.

Join Us Now! (you may need to install QQ client first)

评论

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

在前面的技巧中,我们研究了 [ArgumentCompleter] 以及此属性如何将聪明的代码添加到为参数提供自动完成值的参数。自动完成功能甚至可以做更多的事情:您可以根据实际情况生成 IntelliSense 菜单。

请看这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Get-OU {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({

[Management.Automation.CompletionResult]::new("'OU=managers,DC=company,DC=local'", "Management", "ProviderItem", "OU where the Management lives")
[Management.Automation.CompletionResult]::new("'OU=subtest,OU=test,DC=company,DC=local'", "Experimental", "DynamicKeyword", "Reserved")
[Management.Automation.CompletionResult]::new("'OU=External,OU=IT,DC=company,DC=local'", "Help Desk", "ProviderItem", "OU where the Helpdesk people reside")

})]
[string]
$OU
)

"Chosen path: $OU"
}

完整代码基本上只创建三个新的CompletionResult对象。每个参数都有四个参数:

  • 自动完成的文字
  • 显示在 IntelliSense 菜单中的文字
  • IntelliSense 菜单的图标
  • IntelliSense 菜单的工具提示

您甚至可以控制 IntelliSense 菜单中显示的图标。这些是预定义的图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> [Enum]::GetNames([System.Management.Automation.CompletionResultType])
Text
History
Command
ProviderItem
ProviderContainer
Property
Method
ParameterName
ParameterValue
Variable
Namespace
Type
Keyword
DynamicKeyword

当您运行此代码然后调用 Get-OU 时,可以按 TAB 键完成 OU X500 路径,也可以按 CTRL + SPACE 打开 IntelliSense 菜单。在菜单内,您会看到所选的图标和友好的文本。选择项目后,将使用 X500 自动完成的文字。

评论

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

在前面的技巧中,我们介绍了将参数完成符添加到参数的各种方法。一种方法是使用 [ArgumentCompleter] 属性,如下所示:如您所见,这仅是完善补全代码的问题:当文件名包含空格时,表达式放在引号内,否则不用。如果希望完成者仅返回文件并忽略文件夹,则将 -File 参数添加到 Get-ChildItem

1
2
3
4
5
6
7
8
9
10
function Get-File {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({Get-ChildItem -Path $env:windir -Name})]
[string]
$FileName
)

"Chosen file name: $FileName"
}

本质上,运行此代码然后调用 Get-File 时,一旦使用 -FileName 参数,就可以按 TAB 或 CTRL + SPACE 自动列出 Windows 文件夹中所有文件的文件名。 PowerShell执行 [ArgumentCompleter] 中定义的脚本块以动态计算出自动完成的列表。

有一个反馈称,完成后,这些值需要检查特殊字符(例如空格),并在必要时用引号将它包裹起来。让我们听取这些反馈意见,看看如何改进自动完成代码:

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-File {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({
Get-ChildItem -Path $env:windir -Name |
ForEach-Object {
if ($_ -like '* *')
{
"'$_'"
}
else
{
$_
}
}

})]
[string]
$FileName
)

"Chosen file name: $FileName"
}

如您所见,这完全是完善补全代码的问题:当文件名包含空格时,表达式需要放在引号内,否则就不需要。如果希望完成者仅返回文件并忽略文件夹,则将 -File 参数添加到 Get-ChildItem

评论

PowerShell 技能连载 - 列出安装的应用程序(第 2 部分)

在上一个技能中,我们读取了注册表,以查找可以启动的应用程序的路径。这种方法效果很好,但存在两个缺陷:首先,列表不包含应用程序的友好名称,其次,列表不完整。仅列出已注册的程序。

让我们获得完整的应用程序列表,并使用三个技巧来克服这些限制:

  • 使用 generic list 作为结果,以便能够将更多信息快速添加到列表中
  • Get-Command 的结果合并到 PowerShell 已知的应用程序中
  • 读取扩展文件信息得到应用程序友好名称

我们从这里开始:从注册表中注册的应用程序列表:

1
2
3
4
5
6
7
8
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*"

[System.Collections.Generic.List[string]]$list =
Get-ItemProperty -Path $key |
Select-Object -ExpandProperty '(Default)' -ErrorAction Ignore

$list

现在,我们将 Get-Command 已知的应用程序添加到列表中:

1
2
3
4
5
[System.Collections.Generic.List[string]]$commands =
Get-Command -CommandType Application |
Select-Object -ExpandProperty Source

$list.AddRange($commands)

最后,删除引号,空的和重复的项目来清理列表:

1
2
3
4
$finalList = $list |
Where-Object { $_ } |
ForEach-Object { $_.Replace('"','').Trim().ToLower() } |
Sort-Object -Unique

现在,通过读取每个文件的扩展信息,将列表变成具有应用程序名称、描述和绝对路径的对象。这也消除了所有不存在的路径。以下是完整的代码:

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
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*"

[System.Collections.Generic.List[string]]$list =
Get-ItemProperty -Path $key |
Select-Object -ExpandProperty '(Default)' -ErrorAction Ignore

[System.Collections.Generic.List[string]]$commands = Get-Command -CommandType Application |
Select-Object -ExpandProperty Source

$list.AddRange($commands)

$finalList = $list |
Where-Object { $_ } |
ForEach-Object { $_.Replace('"','').Trim().ToLower() } |
Sort-Object -Unique |
ForEach-Object {
try {
$file = Get-Item -Path $_ -ErrorAction Ignore
[PSCustomObject]@{
Name = $file.Name
Description = $file.VersionInfo.FileDescription
Path = $file.FullName
}
} catch {}
} |
Sort-Object -Property Name -Unique

$finalList | Out-GridView -PassThru

结果类似这样:

1
2
3
4
5
6
7
8
9
PS> $finalList

Name Description
---- -----------
7zfm.exe 7-Zip File Manager
accicons.exe Microsoft Office component
acrord32.exe Adobe Acrobat Reader DC
agentservice.exe AgentService EXE
aitstatic.exe Application Impact Telemetry...
评论

PowerShell 技能连载 - 列出安装的应用程序(第 1 部分)

是否想知道启动一个应用程序的运行路径?Windows 注册表中有存储以下信息的键:

1
2
3
4
5
6
7
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*"

$lookup = Get-ItemProperty -Path $key |
Select-Object -ExpandProperty '(Default)' -ErrorAction Ignore |
Where-Object { $_ } |
Sort-Object

结果是已注册的应用程序路径的排序列表。您可以轻松地将其转换为查找哈希表,该表根据可执行文件名称查询完整路径:

1
2
3
4
5
6
7
8
9
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*"

$lookup = Get-ItemProperty -Path $key |
Select-Object -ExpandProperty '(Default)' -ErrorAction Ignore |
Where-Object { $_ } |
Group-Object -Property {
$_.Replace('"','').Split('\')[-1].ToLower()
} -AsHashTable -AsString

您现在可以列出所有已注册的应用程序的清单:

1
2
3
4
5
6
7
PS> $lookup.Keys
outlook.exe
winword.exe
snagit32.exe
7zfm.exe
msoasb.exe
...

或者您可以可以将可执行文件的路径转换为它的真实路径:

1
2
PS> $lookup['excel.exe']
C:\Program Files (x86)\Microsoft Office\Root\Office16\EXCEL.EXE
评论

PowerShell 技能连载 - 神秘的动态参数完成器

在上一个技能中,我们介绍了鲜为人知的 “ArgumentCompletion“ 属性,该属性可以为参数提供类似 IntelliSense 的自动完成功能。但是,此属性可以做的更多。之前,我们介绍了以下代码:

1
2
3
4
5
6
7
8
9
10
function Get-Vendor {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({'Microsoft','Amazon','Google'})]
[string]
$Vendor
)

"Chosen vendor: $Vendor"
}

当用户调用 Get-Vendor 时,通过按 TAB 或 CTRL-SHIFT 键,将显示 “ArgumentCompleter“ 属性中列出的建议。

您可能想知道为什么将字符串列表嵌入大括号(脚本块)中。答案是:因为这段代码实际上是在用户调用完成时执行的。您也可以动态生成该自动完成文本。

这个 Submit-Date 函数具有一个称为 -Date 的参数。每当您按 TAB 键时,自动完成程序都会以引用的 ISO 格式完成当前日期和时间:

1
2
3
4
5
6
7
8
9
10
function Submit-Date {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({ '"{0}"' -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss") })]
[DateTime]
$Date
)

"Chosen date: $Date"
}

运行代码来试验效果。接下来,输入 Submit-Date,以及一个空格,然后按 TAB 键。

1
PS> Submit-Date -Date "2020-01-21 16:33:19"

同样,下一个函数实现 -FileName 参数,当您按 TAB 键时,它将自动完成 Windows 文件夹中的实际文件名:

1
2
3
4
5
6
7
8
9
10
11
12
function Get-File {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({Get-ChildItem -Path $env:windir -Name})]
[string]
$FileName
)

"Chosen file name: $FileName"
}

Get-File -FileName

每当用户通过按 TAB 或 CTRL-SPACE 调用自动完成功能时,都会执行提交到 ArgumentCompleter 的脚本块中的代码,并将结果用于自动完成功能。

这可能就是为什么 AutoCompleter 属性不会自动弹出 IntelliSense 而是仅响应用户请求自动弹出的原因。请注意,自动完成功能可能无法在编辑器窗格中使用。它是为交互式PowerShell控制台设计的。

评论

PowerShell 技能连载 - 带颜色的控制台硬拷贝

如果您想硬拷贝 PowerShell 控制台的内容,则可以复制和选择文本,但这会弄乱颜色和格式。

更好的方法是读取控制台屏幕缓冲区,并编写 HTML 文档。然后可以将这些 HTML 文档复制并粘贴到 Word 和其他目标中,并保持格式和颜色。

以下代码当然还不是完美的,但说明了采用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Get-ConsoleBufferAsHtml{
$html = [Text.StringBuilder]'' $null = $html.Append("<pre style='MARGIN: 0in 10pt 0in; line-height:normal'; font-family:Consolas; font-size:10pt; >")
$bufferWidth = $host.UI.RawUI.BufferSize.Width $bufferHeight = $host.UI.RawUI.CursorPosition.Y $rec = [Management.Automation.Host.Rectangle]::new(
0,0,($bufferWidth - 1),$bufferHeight )
$buffer = $host.ui.rawui.GetBufferContents($rec)

for($i = 0; $i -lt $bufferHeight; $i++)
{
$span = [Text.StringBuilder]'' $foreColor = $buffer[$i, 0].Foregroundcolor $backColor = $buffer[$i, 0].Backgroundcolor for($j = 0; $j -lt $bufferWidth; $j++)
{
$cell = $buffer[$i,$j]
if (($cell.ForegroundColor -ne $foreColor) -or ($cell.BackgroundColor -ne $backColor))
{
$null = $html.Append("<span style='color:$foreColor;background:$backColor'>$($span)</span>" )
$span = [Text.StringBuilder]'' $foreColor = $cell.Foregroundcolor $backColor = $cell.Backgroundcolor }
$null = $span.Append([Web.HttpUtility]::HtmlEncode($cell.Character))

}
$null = $html.Append("<span style='color:$foreColor;background:$backColor'>$($span)</span><br/>" )
}

$null = $html.Append("</pre>")
$html.ToString()
}

请注意,此功能需要一个真实的控制台窗口,因此在 PowerShell ISE 中将无法使用。当您运行上述代码时,它将为您提供一个名为 Get-ConsoleBufferAsHtml 的新命令。

要将当前控制台内容硬拷贝到 HTML 文件,请运行以下命令:

1
PS>  Get-ConsoleBufferAsHtml | Set-Content $env:temp\test.html

要在关联的浏览器中打开生成的 HTML,请运行以下命令:

1
PS>  Invoke-Item $env:temp\test.html
评论

PowerShell 技能连载 - 理解 $ErrorView

当 PowerShell 遇到问题时,它会显示一条相当长的错误消息:

1
2
3
4
5
6
7
PS> 1/0
Attempted to divide by zero.
At line:1 char:1
+ 1/0
+ ~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

在现实生活中,您通常只需要第一行,而早在 2006 年,PowerShell 团队就添加了一个名为 $ErrorView 的首选项变量,该变量可以控制错误消息的显示方式。当将它赋值为“CategoryView”时,将大大缩短错误消息:

1
2
3
4
5
6
PS> $ErrorView = 'CategoryView'

PS> 1/0
NotSpecified: (:) [], RuntimeException

PS>

不过,这并不太好用,因为一行无法体现真正重要的信息,并且您可以将它赋值为“NormalView”以返回到默认视图。同时,实用性较差导致多数人从未听说过 $ErrorView

幸运的是,在PowerShell 7(RC1 中引入)中,团队记住并最终解决了该问题。为了不破坏兼容性,他们选择添加第三个选项:ConciseView。现在,单行错误消息可以正常工作,并显示典型用户需要知道的所有信息:

1
2
3
4
5
6
PS> $ErrorView = ConciseView

PS> 1/0
RuntimeException: Attempted to divide by zero.

PS>

作为 PowerShell 开发人员,只需切换回“NormalView”即可查看其余的错误消息。或者(甚至更好)运行 Get-Error -Newest 1 以获取有关最新错误的详细信息:

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
PS C:\> 1/0
RuntimeException: Attempted to divide by zero.
PS C:\> Get-Error -Newest 1

Exception            :
  Type         : System.Management.Automation.RuntimeException
  ErrorRecord  :
      Exception            :
          Type  : System.Management.Automation.ParentContainsErrorRecordException
          Message : Attempted to divide by zero.
          HResult : -2146233087
      CategoryInfo         : NotSpecified: (:) [], ParentContainsErrorRecordException
      FullyQualifiedErrorId : RuntimeException
      InvocationInfo      :
          ScriptLineNumber : 1
          OffsetInLine    : 1
          HistoryId      : -1
          Line            : 1/0
          PositionMessage : At line:1 char:1
                             + 1/0
                             + ~~~
          CommandOrigin  : Internal
      ScriptStackTrace     : at , : line 1
  TargetSite    :
      Name         : Divide
      DeclaringType : System.Management.Automation.IntOps, System.Management.Automation, Version=7.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
      MemberType  : Method
      Module      : System.Management.Automation.dll
  StackTrace    :
 at System.Management.Automation.IntOps.Divide(Int32 lhs, Int32 rhs)
 at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
 at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
 at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
  Message      : Attempted to divide by zero.
  Data         : System.Collections.ListDictionaryInternal
  InnerException :
      Type    : System.DivideByZeroException
      Message : Attempted to divide by zero.
      HResult : -2147352558 ;
  Source       : System.Management.Automation
  HResult      : -2146233087
CategoryInfo          : NotSpecified: (:) [], RuntimeException
FullyQualifiedErrorId : RuntimeException
InvocationInfo    :
  ScriptLineNumber: 1
  OffsetInLine    : 1
  HistoryId       : -1
  Line            : 1/0
  PositionMessage : At line:1 char:1
                     + 1/0
                     + ~~~
  CommandOrigin  : Internal
ScriptStackTrace     : at , : line 1
评论

PowerShell 技能连载 - 参数的智能感知(第 4 部分)

如果输入参数时会为用户建议有效的参数,那岂不是很棒?有时候它们会提示。当您键入以下命令并在 -LogName 之后按空格时,PowerShell ISE 和 Visual Studio Code 会弹出一个 IntelliSense 菜单,其中包含您可以转储的所有日志文件:

1
PS> Get-EventLog -LogName

如果没有弹出自动 IntelliSense(换句话说在 PowerShell 控制台中),则可以按 TAB 键自动完成操作,或者按 CTRL + SPACE 手动强制显示 IntelliSense 选择项。

您可以使用自己的 PowerShell 函数执行相同的操作,并且有多种方法可以执行此操作。在上一个技能中,我们研究了 “ValidateSet“ 属性。今天,我们来看看一个更超级隐蔽的类似属性,名为 “ArgumentCompleter“.

通过 ValidateSet,您可以定义一系列用户可以选择的值。其它值都不允许输入。

如果您想向用户提供建议(例如)最常用的服务器,但又允许用户指定完全不同的服务器怎么办?这正是 “ArgumentCompleter“ 属性起作用的时候。它定义了一个建议值的列表,但并不限制用户使用这些值:

1
2
3
4
5
6
7
8
9
10
function Get-Vendor {
param(
[Parameter(Mandatory)]
[ArgumentCompleter({'Microsoft','Amazon','Google'})]
[string]
$Vendor
)

"Chosen vendor: $Vendor"
}

在交互式 PowerShell 控制台中运行此命令并调用 Get-Vendor 时,现在可以按 TAB 或 CTRL + SPACE 自动完成或打开 IntelliSense 列表。不过,该属性不会自动为您弹出 IntelliSense 菜单,并且在 PowerShell 编辑器的编辑器窗格中可能无法使用。

尽管如此,”ArgumentCompleter“ 属性还是有很大帮助的,特别是对于经常使用命令和制表符完成功能的高级用户而言。通过为参数添加默认选项,用户可以快速浏览这些选项,但也可以提交任何其他参数。

评论

PowerShell 技能连载 - 参数的智能感知(第 3 部分)

如果输入参数时会为用户建议有效的参数,那岂不是很棒?有时候它们会提示。当您键入以下命令并在 -LogName 之后按空格时,PowerShell ISE 和 Visual Studio Code 会弹出一个 IntelliSense 菜单,其中包含您可以转储的所有日志文件:

1
PS> Get-EventLog -LogName

如果没有弹出自动 IntelliSense(换句话说在 PowerShell 控制台中),则可以按 TAB 键自动完成操作,或者按 CTRL + SPACE 手动强制显示 IntelliSense 选择项。

您可以使用自己的 PowerShell 函数执行相同的操作,并且有多种方法可以执行此操作。在上一个技能中,我们研究了使用自定义枚举类型。今天,我们来看看更简单的方法(以及一些实现它的隐藏技巧).

使用 IntelliSense 提供参数的最简单方法可能是使用属性 “ValidateSet”:您只需使用允许的值的列表来限定参数:

1
2
3
4
5
6
7
8
9
10
11
function Get-Vendor
{
param(
[Parameter(Mandatory)]
[ValidateSet('Microsoft','Amazon','Google')]
[string]
$Vendor
)

"Chosen vendor: $Vendor"
}

$vendor 变量是 “string“ 类型的,但是 PowerShell 内部会确保只能赋值为 “ValidateSet” 中列出的值。您也可以对常规变量使用相同的技巧,并为代码增加额外的安全性:

1
2
3
4
5
6
7
[ValidateSet('dc1','dc2','ms01')]$servers = 'dc1'

# works
$servers = 'dc2'

# fails
$servers = 'dc3'

以下是另一个技巧:”ValidateSet” 属性仅适用于变量和参数分配,而不适用于参数设置值。作为函数作者,您可以将一个用户不可选的值作为缺省值赋给参数:

1
2
3
4
5
6
7
8
9
function Get-Vendor {
param(
[ValidateSet('Microsoft','Amazon','Google')]
[string]
$Vendor = 'Undefined'
)

"Chosen vendor: $Vendor"
}

当用户调用不带参数的 Get-Vendor 时,$vendor 将设置为 “Undefined”。用户为参数分配值后,该值将不可用,从而轻松帮助您区分用户是否进行了选择。

评论