PowerShell 技能连载 - 增加“命令未找到”处理器

当 PowerShell 遇到一个未知的命令名时,您会见到一条红色的信息。

然而,从 PowerShell 3.0 开始,引入了一个“CommandNotFoundHandler”功能,可以在程序中使用。它可以记录信息,或者尝试解决问题。

这是一个简单的例子。当您运行这段代码后,无论何时遇到一个 PowerShell 不知道的命令,它会运行 Show-Command 并用合法的命令打开一个帮助工具:

$ExecutionContext.InvokeCommand.CommandNotFoundAction =
{
  param(
    [string]
    $commandName,

    [System.Management.Automation.CommandLookupEventArgs]
    $eventArgs
  )

  Write-Warning "Command $commandName was not found. Opening LookilookiTool."
  $eventArgs.CommandScriptBlock = { Show-Command }

}

PowerShell 技能连载 - 自动修正 PowerShell 代码的大小写

当您编写 PowerShell 脚本时,可能常常没有使用正确的大小写,或只使用部分参数名,或使用别名而不是 cmdlet 名称。这些技术上都是可行的,因为 PowerShell 命令是大小写不敏感的,参数名可以省略,并且别名也是一种合法的命令类型。

然而,当使用正确的的大小写、完整的参数名称,以及 cmdlet 名字而不是别名时,脚本的可读性会更好。

在 PowerShell ISE 中,要纠正这些东西,只需要将光标放置在命令名或参数名处,然后按下 TAB 键。Tab 展开功能将会读取原有的代码并将它替换成大小写正确的、名字完整的版本。

PowerShell 技能连载 - 查找已加载的程序集

要列出一个 PowerShell 会话中加载的所有 .NET 程序集,请试试这段代码:

[System.AppDomain]::CurrentDomain.GetAssemblies() |
  Where-Object Location |
  Sort-Object -Property FullName |
  Select-Object -Property Name, Location, Version |
  Out-GridView

列出和对比已加载的程序集可以有助于对比 PowerShell 会话,并且检查区别。多数时间,区别在于已加载的模块,所以如果缺少了程序集,您可能需要先加载一个 PowerShell 模块来使用它们。

或者,您可以使用 Add-Type 命令根据名字或文件地址手动加载程序集。

PowerShell 技能连载 - 查找 cmdlet 参数别名

PowerShell cmdlet 和函数可以带有参数,并且这些参数可以有(更短的)别名。一个典型的例子是 -ErrorAction 通用参数,它也可以通过 -ea 别名访问。

参数别名不是自动完成的。您需要预先知道它们。以下这个脚本可以提取任意 PowerShell 函数或 cmdlet 的参数别名:

#requires -Version 3

$command = 'Get-Process'

(Get-Command $command).Parameters.Values |
  Select-Object -Property Name, Aliases

PowerShell 技能连载 - 简化参数属性

如果您的系统运行的是 PowerShell 3.0 及以上版本,您可以简化函数参数的属性。布尔属性的缺省值都为 $true,所以这在 PowerShell 2.0 中是缺省代码:

function Get-Sample
{
  Param
  (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [string]
    $Name
  )
}

从 PowerShell 3.0 开始,它浓缩成:

function Get-Sample
{
  Param
  (
    [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string]
    $Name
  )
}

新的代码更精炼,但是无法在 PowerShell 2.0 中运行。

PowerShell 技能连载 - 创建临时文件名

如果您只是需要创建一个临时文件名(而不是真的需要创建该文件),而且您希望控制文件的扩展名,以下是一个实现该需求的简单函数:

$elements = @()
$elements += [System.IO.Path]::GetTempPath()
$elements += [System.Guid]::NewGuid()
$elements += 'csv'


$randomPath = '{0}{1}.{2}' -f $elements
$randomPath

您可以很容易地根据它创建您自己的函数:

function New-TemporaryFileName($Extension='txt')
{
  $elements = @()
  $elements += [System.IO.Path]::GetTempPath()
  $elements += [System.Guid]::NewGuid()
  $elements += $Extension.TrimStart('.')


  '{0}{1}.{2}' -f $elements
}

这是该函数的使用方法:

PS C:\> New-TemporaryFileName
C:\Users\Tobias\AppData\Local\Temp\8d8e5001-2be8-469d-9bc8-e2e3324cce66.txt

PS C:\> New-TemporaryFileName ps1
C:\Users\Tobias\AppData\Local\Temp\412c40df-e691-44c1-8c94-f7ce30bb4875.ps1

PS C:\> New-TemporaryFileName csv
C:\Users\Tobias\AppData\Local\Temp\47b1a65f-2705-4926-8a72-21f05430f2c5.csv

PowerShell 技能连载 - 为什么 GetTempFileName() 是有害的

有些人可能会用 .NET 方法来试图生成一个随机的临时文件名:

$path = [System.IO.Path]::GetTempFileName()
$path

它确实可以用。不过它还做了些别的事情。它实际上用那个文件名创建了一个空白文件:

PS C:\> Test-Path $path
True

如果您没有清除临时文件,将会在创建了 65535 个临时文件之后得到一个异常。

在 PowerShell 5.0 中,New-TemporaryFile 做了相同的事情,但是它返回了一个文件,这样您可以立即确认确实创建了一个文件,而不是一个文件名。

PowerShell 技能连载 - 删除别名

在 PowerShell 中创建新的别名很常见。但是如果您做错了什么,要怎么办?

PS C:\> Set-Alias -Name ping -Value notepad

PS C:\> ping 127.0.0.1

当创建了一个别名之后,并没有 cmdlet 可以移除它。您必须得关闭 PowerShell 并打开一个新的 PowerShell 会话来“忘记”掉自定义的别名。

或者,您可以利用 alias: 虚拟驱动器,并且像移除文件一样移除别名:

PS C:\> del alias:ping

PS C:\> ping 127.0.0.1

Pinging 127.0.0.1 with 32 bytes of data:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128

Ping statistics for 127.0.0.1:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms

PS C:\>

PowerShell 技能连载 - 快速创建编码的 PowerShell 命令

当在 PowerShell 控制台之外执行 PowerShell 代码时,您需要传递代码给 powershell.exe。要确保您的代码不与特殊字符冲突,命令可以编码后传给 powershell.exe。

一个最简单的将纯文本命令行转换为编码后的命令的方法如下:

PS C:\> cmd /c echo powershell { Get-Service | Where-Object Status -eq Running }
powershell -encodedCommand IABHAGUAdAAtAFMAZQByAHYAaQBjAGUAIAB8ACAAVwBoAGUAcgBlAC0ATwBiAGoAZQ
BjAHQAIABTAHQAYQB0AHUAcwAgAC0AZQBxACAAUgB1AG4AbgBpAG4AZwAgAA== -inputFormat xml -outputFormat
 xml
PS C:\>

Here you’d find out that you can run the Get-Service | Where-Object statement as an encoded command like this:
然后可以以这样的方式执行编码后的 Get-Service | Where-Object 语句。

powershell.exe -encodedCommand
IABHAGUAdAAtAFMAZQByAHYAaQBjAGUAIAB8ACAAVwBoAGUAcgBlAC0ATwBiAGoAZQBjAHQAIABTAHQAYQB0AHUAcwAgAC0AZQBxACAAUgB1AG4AbgBpAG4AZwAgAA==

当您在 cmd.exe(或 PowerShell 控制台中)运行这段语句时,您能够得到所有运行中的服务。只需要移除 -inputFormat-outputFormat 参数,并且移除所有换行符。编码后的命令是一个长长的字符串。

PowerShell 技能连载 - 为变量增加 ValidateRange

如果您希望为一个变量增加一个合法数值的范围,您可以向该变量添加一个 ValidateRange 属性,很像函数参数的工作方式。唯一的区别在,它手工作用于您期望的变量上:

$test = 1
$variable = Get-Variable test
$validateRange = New-Object -TypeName System.Management.Automation.ValidateRangeAttribute(1,100)
$variable.Attributes.Add($validateRange)
$test = 10
$test = 100
$test = 1000

变量 $test 现在只允许 1 到 100 的数值。当您试图赋一个该范围之外的值时,会得到一个异常。

PS C:\> $test =  101
The variable cannot be  validated because the value 101 is not a valid value for the test
variable.
At line:1 char:1
+ $test = 101
+ ~~~~~~~~~~~
    +  CategoryInfo          : MetadataError:  (:) [], ValidationMetadataException
    +  FullyQualifiedErrorId : ValidateSetFailure