PowerShell 技能连载 - 从 WMI 中搜索有用的信息

WMI 是一个很好的信息源,但要找到正确的 WMI 类来查询并不总是那么容易。

一下是一个小小的搜索工具:它提示输入一个关键字,然后根据在 WMI 中搜索所有合适的关键字。结果将显示在一个网格视图窗口中,然后您可以选择一个类并按下“确定”按钮,该工具将查询出匹配的结果:

#requires -Version 3

function Search-WMI
{
    param([Parameter(Mandatory=$true)]$Keyword)

    Get-WmiObject -Class "Win32_*$Keyword*" -List |
    Where-Object { $_.Properties.Count -gt 6 } |
    Where-Object { $_.Name -notlike 'Win32_Perf*' } |
    Sort-Object -Property Name |
    Select-Object -Property @{Name='Select one of these classes'; Expression={$_.Name }} |
    Out-GridView -Title 'Choose one' -OutputMode Single |
    ForEach-Object {
        Get-WmiObject -Class $_.'Select one of these classes' | Out-GridView
    }
}

Search-WMI -Keyword network

PowerShell 技能连载 - 使用网格窗口作为一个通用的对话框

Out-GridView 不止可以用于显示结果。您可以将它转换为一个很有用的通用对话框。

假设您希望显示一系列运行中的程序,然后结束掉选中的程序。传统的方式大概是这样:

Get-Process |
  Where-Object { $_.MainWindowTitle } |
  Out-GridView -OutputMode Single |
  Stop-Process -WhatIf

当您将源对象通过管道传给 Out-GridView 命令时,用户将能看到所有的普通对象属性。这也许没问题,但如果您希望用户体验更好一些,您可以使用一些更高级的 PowerShell 技巧:

#requires -Version 3

$prompt = 'Choose the process you want to terminate:'

Get-Process |
  Where-Object { $_.MainWindowTitle } |
  ForEach-Object {
    New-Object PSObject -Property @{$prompt = $_ | Add-Member -MemberType ScriptMethod -Name ToString -Force -Value  { '{0} [{1}]' -f $this.Description, $this.Id } -PassThru }
  } |
  Out-GridView -OutputMode Single -Title $prompt |
  Select-Object -ExpandProperty $prompt |
  Stop-Process -WhatIf

请先看用户体验:该网格窗口不再让人感到疑惑。现在让我们检查一下如何实现这种用户体验。

在结果通过管道传给 Out-GridView 之前,它们被重新打包成一个只有单个属性的对象。该属性包含了您在 $prompt 中定义的名称,所以它基本上就是您想呈现给用户的信息。

当您做完这些并将包裹后的对象通过管道传给 Out-GridView 后,您可以看到该对象的文字呈现。要控制文字呈现的方式,我们将它的 ToString() 方法用一个显示您期望的值的方法来覆盖。在这个例子里,它显示进程的描述和进程的 ID。

最后,被用户选中的对象将再被拆包。通过这种方法,您可以获取源对象。

PowerShell 技能连载 - 以不同的格式输出文件大小

当您将一个数值赋给一个变量时,您也许希望按不同的单位显示该数字。字节的方式很清晰,但是有些时候以 KB 或 MB 的方式显示更合适。

以下是一个聪明的技巧,它用一个更多样化的版本覆盖了内置的 ToString() 方法。该方法包括了单位,您希望的位数,以及后缀文字。通过这种方式,您可以根据需要按各种格式显示数字。

变量的内容并没有被改变,所以变量仍然存储着 Integer 数值。您可以安全地用于排序及和其它值比较:

#requires -Version 1


$a = 1257657656
$a = $a | Add-Member -MemberType ScriptMethod -Name tostring -Force -Value { param($Unit = 1MB, $Digits=1, $Suffix=' MB') "{0:n$Digits}$Suffix" -f ($this/($Unit)) } -PassThru

以下是多种使用 $a 的例子:

PS> $a
1.199,4 MB

PS> $a.ToString(1GB, 0, ' GB')
1 GB

PS> $a.ToString(1KB, 2, ' KB')
1.228.181,30 KB

PS> $a -eq 1257657656
True

PS> $a -eq 1257657657
False

PS> $a.GetType().Name
Int32

PowerShell 技能连载 - 转换日期、时间格式

以下是一个简单的 PowerShell 过滤器,它能够将任意的 DateTime 对象转换为您所需要的日期/时间格式:

#requires -Version 1

filter Convert-DateTimeFormat
{
  param($OutputFormat='yyyy-MM-dd HH:mm:ss fff')

  try {
    ([DateTime]$_).ToString($OutputFormat)
  } catch {}
}

以下是如何运行它的一些例子:

PS> Get-Date | Convert-DateTimeFormat
2015-10-23 14:38:37 140

PS> Get-Date | Convert-DateTimeFormat -OutputFormat 'dddd'
Friday

PS> Get-Date | Convert-DateTimeFormat -OutputFormat 'MM"/"dd"/"yyyy'
10/23/2015

PS> '2015-12-24' | Convert-DateTimeFormat -OutputFormat 'dddd'
Thursday

如您所见,您可以将 DateTime 类型数据,或是能够转换为 DateTime 类型的数据通过管道传给 Convert-DateTimeFormat。默认情况下,该函数以 ISO 格式格式化,但是您还可以通过 -OutputFormat 指定您自己的格式。

通过源码,您可以查看到类似日期和时间部分的字母。请注意这些字母是大小写敏感的(“m”代表分钟,而“M”代表月份)。并且您指定了越多的字母,就能显示越多的细节。

所有希望原样显示的文字必须用双引号括起来。

PowerShell 技能连载 - 等待进程启动

PowerShell 内置了等待一个进程或多个进程结束的功能:只需要用 Wait-Process 命令。

但是并不支持相反的功能:等待一个进程启动。以下是一个等待任意进程的函数:

#requires -Version 1
function Wait-ForProcess
{
    param
    (
        $Name = 'notepad',

        [Switch]
        $IgnoreAlreadyRunningProcesses
    )

    if ($IgnoreAlreadyRunningProcesses)
    {
        $NumberOfProcesses = (Get-Process -Name $Name -ErrorAction SilentlyContinue).Count
    }
    else
    {
        $NumberOfProcesses = 0
    }


    Write-Host "Waiting for $Name" -NoNewline
    while ( (Get-Process -Name $Name -ErrorAction SilentlyContinue).Count -eq $NumberOfProcesses )
    {
        Write-Host '.' -NoNewline
        Start-Sleep -Milliseconds 400
    }

    Write-Host ''
}

当您运行这段代码时,PowerShell 将会暂停直到您运行一个 Notepad 的新实例:

Wait-ForProcess -Name notepad -IgnoreAlreadyRunningProcesses

当您忽略了 -IgnoreAlreadyRunningProcesses 参数时,如果已有至少一个 Notepad 的实例在运行,PowerShell 将会立即继续。

PowerShell 技能连载 - 根据参数值执行不同的代码

以下是一个使用一系列选项的操作参数的简单概念。每个选项对应一个将被执行的脚本块。

#requires -Version 2
function Invoke-SomeAction
{
  param
  (
    [String]
    [Parameter(Mandatory=$true)]
    [ValidateSet('Deploy','Delete','Refresh')]
    $Action
  )

  $codeAction = @{}
  $codeAction.Deploy = { 'Doing the Deployment' }
  $codeAction.Delete = { 'Doing the Deletion' }
  $codeAction.Refresh = { 'Doing the Refresh' }

  & $codeAction.$Action

}

当运行这段代码后,键入 Invoke-SomeAction,ISE 将会提供它所支持的“Deployment”、“Deletion”和“Refresh”操作。相对简单的 PowerShell 控制台至少会提供 action 参数的 tab 补全功能。

根据您的选择,PowerShell 将会执行合适的脚本块。如您所见,该操作脚本块可以包括任意代码,甚至多页代码。

PowerShell 技能连载 - 查找脚本块变量

脚本块定义了一段 PowerShell 代码而并不执行它。最简单的定义脚本块的方法是将代码放入花括号中。

脚本块有一系列高级功能,能够检测花括号内部的代码。其中的一个功能是直接访问抽象语法树 (AST)。AST 可以分分析代码内容。以下是一个读出脚本块中所有变量名的例子:

#requires -Version 3


$scriptblock = {

   $test = 1
   $abc = 2

}

$scriptblock.Ast.FindAll( { $args[0] -is [System.Management.Automation.Language.VariableExpressionAst] }, $true ) |
  Select-Object -ExpandProperty VariablePath | Select-Object -ExpandProperty UserPath

PowerShell 技能连载 - 远程获取已安装的软件列表

在前一个技能中,我们介绍了 Get-Software 函数,它可以获取本地计算机已安装的软件。

如果您在远程系统上已经安装了 PowerShell 远程操作(在 Windows Server 2012 及以上版本中默认是开启的),并且如果您拥有合适的权限,请试试这个增强的版本。它同时支持本地和远程调用:

#requires -Version 2

function Get-Software
{
    param
    (
        [string]
        $DisplayName='*',

        [string]
        $UninstallString='*',

        [string[]]
        $ComputerName
    )

    [scriptblock]$code =
    {

        param
        (
        [string]
        $DisplayName='*',

        [string]
        $UninstallString='*'

        )
      $keys = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
       'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

      Get-ItemProperty -Path $keys |
      Where-Object { $_.DisplayName } |
      Select-Object -Property DisplayName, DisplayVersion, UninstallString |
      Where-Object { $_.DisplayName -like $DisplayName } |
      Where-Object { $_.UninstallString -like $UninstallString }

    }
    if ($PSBoundParameters.ContainsKey('ComputerName'))
    {
    Invoke-Command -ScriptBlock $code -ComputerName $ComputerName -ArgumentList $DisplayName, $UninstallString
    }
    else
    {
        & $code -DisplayName $DisplayName -UninstallString $UninstallString
    }
}

请注意这个函数如何将查找软件的代码包裹在代码块中。接下来,它将检测 $PSBoundParameters 来判断用户是否传入了 -ComputerName 参数。如果没有传入,该代码将在本地执行。

否则,Invoke-Command 将在指定的远程计算机(支持多台)上运行这段代码。在这个例子中,Invoke-Command 将过滤参数传递给远程代码。

PowerShell 技能连载 - 从注册表中读取已安装的软件

以下是查看已安装的软件的快速方法。Get-Software 函数读取所有用户的 32 位和 64 位软件的安装地址。

#requires -Version 1

function Get-Software
{
    param
    (
        [string]
        $DisplayName='*',

        [string]
        $UninstallString='*'
    )

    $keys = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
            'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'

    Get-ItemProperty -Path $keys |
      Where-Object { $_.DisplayName } |
      Select-Object -Property DisplayName, DisplayVersion, UninstallString |
      Where-Object { $_.DisplayName -like $DisplayName } |
      Where-Object { $_.UninstallString -like $UninstallString }
}

它甚至包含了过滤参数,所以您可以当您指定 -DisplayName-UninstallString(或两者)时,您可以轻松地过滤结果,仅显示您期望的软件产品。两个参数都支持通配符。

以下是一个调用的示例,显示所有的 Office 组件到一个网格视图窗口中:

Get-Software -DisplayName *Office* | Out-GridView

PowerShell 技能连载 - 显示消息对话框

PowerShell 是基于控制台的,但有些时候加入一些简单的对话框也很不错。以下是一个称为 Show-MessageBox 的函数,可以显示所有标准消息框,并支持智能显示参数:

#requires -Version 2

Add-Type -AssemblyName PresentationFramework
function Show-MessageBox
{
    param
    (
        [Parameter(Mandatory=$true)]
        $Prompt,

        $Title = 'Windows PowerShell',

        [Windows.MessageBoxButton]
        $Buttons = 'YesNo',

        [Windows.MessageBoxImage]
        $Icon = 'Information'
    )

    [System.Windows.MessageBox]::Show($Prompt, $Title, $Buttons, $Icon)
}


$result = Show-MessageBox -Prompt 'Rebooting.' -Buttons OKCancel -Icon Exclamation

if ($result -eq 'OK')
{
  Restart-Computer -Force -WhatIf
}