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

PowerShell 技能连载 - 从照片中读取拍摄日期

如果您想重新整理您的照片库,以下这段代码能帮您从照片文件中读取拍摄日期信息。

这个例子使用了一个系统函数来查找“我的照片”的路径,然后递归搜索它的子文件夹。输出的结果通过管道传递给 Get-DataTaken,该函数返回照片的文件名、文件夹名,以及照片的拍摄时间。

function Get-DateTaken
{
  param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias('FullName')]
    [String]
    $Path
  )

  begin
  {
    $shell = New-Object -COMObject Shell.Application
  }

  process
  {
  $returnvalue = 1 | Select-Object -Property Name, DateTaken, Folder
    $returnvalue.Name = Split-Path $path -Leaf
    $returnvalue.Folder = Split-Path $path
    $shellfolder = $shell.Namespace($returnvalue.Folder)
    $shellfile = $shellfolder.ParseName($returnvalue.Name)
    $returnvalue.DateTaken = $shellfolder.GetDetailsOf($shellfile, 12)

    $returnvalue
  }
}

$picturePath = [System.Environment]::GetFolderPath('MyPictures')
Get-ChildItem -Path $picturePath -Recurse -ErrorAction SilentlyContinue |
  Get-DateTaken

PowerShell 技能连载 - 远程读取已安装的软件

大多数软件都会在注册表中登记自己。以下是一段从能从本地和远程的 32 位及 64 位注册表中读取已安装的软件列表的代码。它还是一个演示如何读取远程注册表的不错的例子。

# NOTE: RemoteRegistry Service needs to run on a target system!
$Hive = 'LocalMachine'

# you can specify as many keys as you want as long as they are all in the same hive
$Key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', 'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'

# you can specify as many value names as you want
$Value = 'DisplayName', 'DisplayVersion', 'UninstallString'

# you can specify a remote computer name as long as the RemoteRegistry service runs on the target machine,
# you have admin permissions on the target, and the firewall does not block you. Default is the local machine:
$ComputerName = $env:COMPUTERNAME

# add the value "RegPath" which will contain the actual Registry path the value came from (since you can specify more than one key)
$Value = @($Value) + 'RegPath'

# now for each regkey you specified...
$Key | ForEach-Object {
  # ...open the hive on the appropriate machine
  $RegHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive, $ComputerName)

  # ...open the key in that hive...
  $RegKey = $RegHive.OpenSubKey($_)

  # ...find names of all subkeys...
  $RegKey.GetSubKeyNames() | ForEach-Object {
    # ...open subkeys...
    $SubKey = $RegKey.OpenSubKey($_)
    # ...and read all the requested values from each subkey
    # ...to store them, use Select-Object to create a simple new object
    $returnValue = 1 | Select-Object -Property $Value

    $Value | ForEach-Object {
      $returnValue.$_ = $subkey.GetValue($_)
    }

    # ...add the current regkey path name
    $returnValue.RegPath = $SubKey.Name

    # return the values:
    $returnValue

    # close the subkey
    $SubKey.Close()
  }

  # close the regkey
  $RegKey.Close()
  # close the hive
  $RegHive.Close()

} | Out-GridView

PowerShell 技能连载 - 远程执行 gpupdate

您可以用这样的一段脚本远程执行 gpupdate.exe

function Start-GPUpdate
{
    param
    (
        [String[]]
        $ComputerName
    )

    $code = {
        $rv = 1 | Select-Object -Property ComputerName, ExitCode
        $null = gpupdate.exe /force
        $rv.Exitcode = $LASTEXITCODE
        $rv.ComputerName = $env:COMPUTERNAME
        $rv
    }
    Invoke-Command -ScriptBlock $code -ComputerName $ComputerName |
      Select-Object -Property ComputerName, ExitCode

}

Start-GPUpdate 接受一个或多个计算机名,然后对每台计算机运行 gpupdate.exe,并返回执行结果。

这段脚本利用了 PowerShell 远程管理技术,所以它需要目标计算机启用了 PowerShell 远程管理,并且您需要这些机器的本地管理员权限。

PowerShell 技能连载 - 获取数据库连接字符串

您是否疑惑过一个数据库的连接字符串到底长什么样?当您从控制面板中创建一个数据源时,一个向导将指引您完成整个创建过程。以下是一个利用这个向导并获取生成的连接字符串的方法。

请注意该向导的选择要依赖于您机器上所安装的数据库驱动。

function Get-ConnectionString
{

  $Path = Join-Path -Path $env:TEMP -ChildPath 'dummy.udl'

  $null = New-Item -Path $Path -ItemType File -Force

  $CommandArg = """$env:CommonProgramFiles\System\OLE DB\oledb32.dll"",OpenDSLFile "  + $Path


  Start-Process -FilePath Rundll32.exe -Argument $CommandArg -Wait
  $ConnectionString = Get-Content -Path $Path | Select-Object -Last 1
  $ConnectionString | clip.exe
  Write-Warning 'Connection String is also available from clipboard'
  $ConnectionString

}

当您调用 Get-ConnectionString 方法时,将会创建一个临时的 udl 文件,并且用控制面板向导打开它。您可以通过向导完成配置。配置完成之后,PowerShell 将会检测临时文件并且返回连接字符串。

它的工作原理是 Get-Process 函数带了 -Wait 参数,它能够挂起脚本的执行,直到向导退出。在向导退出以后,脚本就可以安全地访问 udl 文件了。