PowerShell 技能连载 - 使用嵌套的哈希表

嵌套的哈希表是代替多维数组的好方法。它能以易于管理的方式存储数据集合。让我们看一个例子:

$person = @{}
$person.Name = 'Weltner'
$person.Id = 12
$person.Address = @{}
$person.Address.Street = 'Canyon Rim'
$person.Address.City = 'Folsom'
$person.Address.Details = @{}
$person.Address.Details.Story = 4
$person.Address.Details.ScenicView = $false

这段代码定义了一个 person 变量。您可以这样查看 person 的内容:

PS> $person
Name                           Value
----                           -----
Name                           Weltner
Id                             12
Address                        {Street, Details, City}

您还可以这样方便地获取其中的一部分信息:

PS> $person
Name
Weltner

PS> $person.Address.City
Folsom

PS> $person.Address.Details.ScenicView
False

PowerShell 技能连载 - 加速数组操作

当您频繁地向数组添加新元素时,您可能会遇到性能问题。以下是一个演示这个问题的反例,您应该避免这样使用:

Measure-Command {
  $ar = @()

  for ($x=0; $x -lt 10000; $x++)
  {
    $ar += $x
  }
}

在循环中,数组用“+=”运算符不断地添加新元素。这将消耗许多时间,因为每次改变数组的大小时,PowerShell 都需要创建一个新的数组。

以下是一个快许多倍的实现方式——用 ArrayList,它专门为大小变化的情况设计:

Measure-Command {
  $ar = New-Object -TypeName System.Collections.ArrayList

  for ($x=0; $x -lt 10000; $x++)
  {
    $ar.Add($x)
  }
}

两段代码实现相同的效果,但第二段效率要高得多。

PowerShell 技能连载 - 用事件日志代替日志文件

人们常常使用文件来记录日志。这样做并没有错,但是使用 Windows 内置的事件日志系统可能会简单得多。

如果您有管理员权限,您可以随时创建新的事件日志:

New-EventLog -LogName myLog -Source JobDue, JobDone, Remark

这将创建一个名为“myLog”的新日志,它的来源为“JobDue”、“JobDone”和“Remark”。管理员权限只是用来创建事件日志用。剩下的操作任何普通用户都可以操作。现在您的日志可以记录到新的事件日志中。

Write-EventLog -LogName myLog -Source JobDue -EntryType Information -EventId 1 -Message 'This could be a job description.'
Write-EventLog -LogName myLog -Source JobDue -EntryType Information -EventId 1 -Message 'This could be another job description.'

通过 Get-EventLog 命令,您可以轻松地解析您的日志并且查找信息:

Get-EventLog -LogName myLog -Source JobDue -After 2014-05-10

通过 Limit-EventLog,您还可以配置您的日志,限制最大大小。

PowerShell 技能连载 - 轻松读取注册表键值

使用 PowerShell 读取注册表是小菜一碟。以下是一段代码模板:

$RegPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion'
$key = Get-ItemProperty -Path "Registry::$RegPath"

现在,只需要将 RegPath 替换成任意的注册表项路径。您还可以从 regedit.exe 中复制粘贴项路径。

当您运行完这段代码,$key 变量被赋值以后,只需键入 $key 以及 .,智能提示将列出该项下的所有键名,您可以简单地选取您希望读取的键。在控制台中,当您键入 . 之后按下 TAB 键可以显示所有可用的键名:

$key.CommonFilesDir
$key.MediaPathUnexpanded
$key.ProgramW6432Dir

PowerShell 技能连载 - 不中断处理 Cmdlet 中的错误

当您想要错误处理器处理 cmdlet 内部产生的错误时,您只能将该 cmdlet 的 -ErrorAction 设为 Stop 才能捕获这类异常。否则,cmdlet 将在内部处理该错误。

这么做是有副作用的,因为将 -ErrorAction 设为 Stop 将会在发生第一个错误的时候停止该 cmdlet。

所以如果您希望不中断一个 cmdlet 并仍然能够获得该 cmdlet 产生的所有错误,那么请使用 -ErrorVariable。这段代码递归地获取您 Windows 文件夹中的所有 PowerShell 脚本(可能需要消耗一些时间)。错误不会导致停止执行,而是记录到一个变量中:

Get-ChildItem -Path c:\Windows -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue -ErrorVariable myErrors

当该 cmdlet 执行完成以后,您可以检测 $myErrors 变量。它包含了所有发生的错误信息。例如,这段代码可以获取所有 Get-ChildItem 无法进入的子文件夹列表:

$myErrors.TargetObject

上面一段代码使用了自动展开特性(PowerShell 3.0 中引入)。所以在 PowerShell 2.0 中,您需要这么写:

$myErrors | Select-Object -ExpandProperty TargetObject

PowerShell 技能连载 - 有趣的路径名

您可以用 -split 运算符轻松地将一个路径分割成独立的部分。结果是一个数组。

只需要用比较运算符来排除您不需要的部分,或者对其中的一部分改名,然后用 -join 运算符将路径合并回来。

以下代码将排除掉某个路径下所有包含单词“test”的子文件夹:

$path = 'C:\folder\test\unit1\testing\results\report.txt'

$path -split '\\' -notlike '*test*' -join '\'

PowerShell 技能连载 - 通过按键跳过配置脚本

有些时候您也许希望跳过配置文件中的某些部分。例如,在 ISE 编辑器中,只需要将这段代码加入您的配置脚本(配置脚本的路径可以在通过 $profile 变量查看,它也有可能还没有创建):

if([System.Windows.Input.Keyboard]::IsKeyDown('Ctrl')) { return }

如果您启动 ISE 编辑器时按住 CTRL 键,将跳过您配置脚本中的剩余部分。

或者,您可以这样使用:

if([System.Windows.Input.Keyboard]::IsKeyDown('Ctrl') -eq $false)
{
    Write-Warning 'You DID NOT press CTRL, so I could execute things here.'
}

这样写的话,仅当您启动 ISE 时没有按住 CTRL 键时,才会运行花括号内部的代码。

如果您希望这段代码也能用在 PowerShel 控制台中,那么需要加载对应的程序集。这段代码在所有的配置脚本中都通用:

Add-Type -AssemblyName PresentationFramework
if([System.Windows.Input.Keyboard]::IsKeyDown('Ctrl') -eq $false)
{
    Write-Warning 'You DID NOT press CTRL, so I could execute things here.'
}

PowerShell 技能连载 - 使用配置脚本

您可能知道 PowerShell 支持配置脚本。只需要确保 $profile 所指定的文件存在即可。它是一个普通的脚本,每当 PowerShell 宿主启动的时候都会执行。

所以可以很方便地配置 PowerShell 环境、加载模块、增加 snap-in,以及做其它调整。这段代码将缩短您的 PowerShell 提示符,并且在标题栏显示当前路径:

function prompt
{
  'PS> '
  $host.UI.RawUI.WindowTitle = Get-Location
}

请注意 $profile 指定的配置脚本是和宿主有关的。每个宿主有独立的配置脚本(包括 PowerShell 控制台、ISE 编辑器以及所有的 PowerShell 宿主)。

要在所有宿主中自动执行代码,请使用这个文件:

$profile.CurrentUserAllHosts

它们的路径基本上相同,除了后者文件名不含宿主名,而只是叫做“profile.ps1”。

PowerShell 技能连载 - 留意副作用

PowerShell 可以使用许多底层的系统函数。例如这个,可以创建一个临时文件名:

[System.IO.Path]::GetTempFileName()

然而,它不仅只做这一件事。它还真实地创建了那个文件。所以如果您使用这个函数来创建临时文件名,您可能最终会在文件系统中创建一堆孤立的文件。请在您的确需要创建一个临时文件的时候才使用它。

PowerShell 技能连载 - 批量重命名文件

假设在一个文件夹中有一大堆脚本(或照片、日志等任意文件),并且您想要重命名所有的文件。比如新文件名的格式为固定前缀 + 自增的编号。

以下是实现方法。

这个例子将重命名指定文件夹中所有扩展名为 .ps1 的 PowerShell 脚本。新文件名为 powershellscriptX.ps1,其中“X”为自增的数字。

请注意脚本禁止了真正的重命名操作。如果要真正地重命名文件,请移除 -WhatIf 参数,但必须非常小心!如果您敲错一个变量或使用了错误的文件夹路径,那么您的脚本将会十分开心地重命名成千上万个错误的文件。

$Path = 'c:\temp'
$Filter = '*.ps1'
$Prefix = 'powershellscript'
$Counter = 1

Get-ChildItem -Path $Path -Filter $Filter -Recurse |
  Rename-Item -NewName {
    $extension = [System.IO.Path]::GetExtension($_.Name)
    '{0}{1}.{2}' -f $Prefix, $script:Counter, $extension
    $script:Counter++
   } -WhatIf