PowerShell 技能连载 - 查找缺省的 MAPI 客户端

您机器上的 MAPI 客户端就是处理类似“mailto:” URL 的缺省电子邮件客户端。我们设计一个函数来查找是否有 MAPI 客户端,如果有的话,查看具体是哪一个。该函数从 Windows 注册表中获取这项信息:

function Get-MAPIClient
{
    function Remove-Argument
    {
      param
      (
        $CommandLine
      )

      $divider = ' '
      if ($CommandLine.StartsWith('"'))
      {
        $divider = '"'
        $CommandLine = $CommandLine.SubString(1)
      }

      $CommandLine.Split($divider)[0]
    }

  $path = 'Registry::HKEY_CLASSES_ROOT\mailto\shell\open\command'

  # create new object to return values
  $returnValue = 1 | Select-Object -Property HasMapiClient, Path, MailTo

  $returnValue.hasMAPIClient = Test-Path -Path $path

  if ($returnValue.hasMAPIClient)
  {
    $values = Get-ItemProperty -Path $path
    $returnValue.MailTo = $values.'(default)'
    $returnValue.Path = Remove-Argument $returnValue.MailTo
    if ((Test-Path -Path $returnValue.Path) -eq $false)
    {
      $returnValue.hasMAPIClient = $true
    }
  }


  $returnValue
}

Get-MAPIClient

以下是使用结果:

PowerShell 技能连载 - 从命令行获取参数

在前一个技巧中,我们演示了如何从命令行中提取命令名,并忽略所有参数。今天,您将学习到如何用一个函数同时获取到命令名和参数。该函数将命令行分割为实际的命令名和它的参数,并返回一个自定义对象:

function Get-Argument
{
  param
  (
    $CommandLine
  )

  $result = 1 | Select-Object -Property Command, Argument

  if ($CommandLine.StartsWith('"'))
  {
    $index = $CommandLine.IndexOf('"', 1)
    if ($index -gt 0)
    {
      $result.Command = $CommandLine.SubString(0, $index).Trim('"')
      $result.Argument = $CommandLine.SubString($index+1).Trim()
      $result
    }
  }
  else
  {
    $index = $CommandLine.IndexOf(' ')
    if ($index -gt 0)
    {
      $result.Command = $CommandLine.SubString(0, $index)
      $result.Argument = $CommandLine.SubString($index+1).Trim()
      $result
    }
  }
}



Get-Argument -CommandLine 'notepad c:\test'
Get-Argument -CommandLine '"notepad.exe" c:\test'

结果如下:

这是一个实际应用中的例子:它获取所有运行中的进程,并返回每个进程的命令名和参数:

Get-WmiObject -Class Win32_Process |
  Where-Object { $_.CommandLine } |
  ForEach-Object {
    Get-Argument -CommandLine $_.CommandLine
  }

以下是结果的样子:

既然命令和参数都分开了,您还可以像这样为信息分组:

Get-WmiObject -Class Win32_Process |
  Where-Object { $_.CommandLine } |
  ForEach-Object {
    Get-Argument -CommandLine $_.CommandLine
  } |
  Group-Object -Property Command |
  Sort-Object -Property Count -Descending |
  Out-GridView

PowerShell 技能连载 - 从命令行中提取可执行程序名

有些时候我们需要从命令行提取命令名。以下是实现的方法:

代码如下:

function Remove-Argument
{
  param
  (
    $CommandLine
  )

  $divider = ' '
  if ($CommandLine.StartsWith('"'))
  {
    $divider = '"'
    $CommandLine = $CommandLine.SubString(1)
  }

  $CommandLine.Split($divider)[0]
}

PowerShell 技能连载 - 弹出对话框时播放随机的音效

您也许了解了如何用脚本打开一个 MsgBox 对话框。今天,您将学习如何用一段代码打开一个 MsgBox,同时播放一段随机的音效,吸引用户的注意力并增加趣味性。当用户操作 MsgBox 的时候,音效立即停止:

# find random WAV file in your Windows folder
$randomWAV = Get-ChildItem -Path C:\Windows\Media -Filter *.wav |
  Get-Random |
  Select-Object -ExpandProperty Fullname

# load Forms assembly to get a MsgBox dialog
Add-Type -AssemblyName System.Windows.Forms

# play random sound until MsgBox is closed
$player = New-Object Media.SoundPlayer $randomWAV
$player.Load();
$player.PlayLooping()
$result = [System.Windows.Forms.MessageBox]::Show("We will reboot your machine now. Ok?", "PowerShell", "YesNo", "Exclamation")
$player.Stop()

PowerShell 技能连载 - 通过 Google 图片搜索自动下载图片

在前一个技巧中您学到了如何用 Invoke-WebRequest 从 Google 图片搜索中获取图片链接。Invoke-WebRequest 还可以做更多的东西。它可以获取图片 URL 并下载图片。

以下是具体做法:

$SearchItem = 'PowerShell'
$TargetFolder = 'c:\webpictures'

if ( (Test-Path -Path $TargetFolder) -eq $false) { md $TargetFolder }

explorer.exe $TargetFolder

$url = "https://www.google.com/search?q=$SearchItem&espv=210&es_sm=93&source=lnms&tbm=isch&sa=X&tbm=isch&tbs=isz:lt%2Cislt:2mp"

$browserAgent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36'
$page = Invoke-WebRequest -Uri $url -UserAgent $browserAgent
$page.Links |
  Where-Object { $_.href -like '*imgres*' } |
  ForEach-Object { ($_.href -split 'imgurl=')[-1].Split('&')[0]} |
  ForEach-Object {
    $file = Split-Path -Path $_ -Leaf
    $path = Join-Path -Path $TargetFolder -ChildPath $file
    Invoke-WebRequest -Uri $_ -OutFile $path
  }

您可以下载所有匹配关键字“PowerShell”的高分辨率的图片到您指定的 $TargetFolder 文件夹中。

PowerShell 技能连载 - 从 Google 图片搜索中获取图片 URL

当您想从互联网下载信息时,Invoke-WebRequest 是您的好帮手。例如,您可以发送一个请求到 Google 并使用 PowerShell 检验它的结果。

Google 也知道您在这么做,所以当您从 PowerShell 发送一个查询时,Google 返回加密的链接。要获取真实的链接,您需要告诉 Google 您使用的不是 PowerShell 而是一个普通的浏览器。这可以通过设置浏览器代理字符串。

这段脚本输入一个关键字并返回所有符合搜索关键字,并且大于 2 兆像素的所有图片的原始地址:

$SearchItem = 'PowerShell'

$url = "https://www.google.com/search?q=$SearchItem&espv=210&es_sm=93&source=lnms&tbm=isch&sa=X&tbm=isch&tbs=isz:lt%2Cislt:2mp"
$browserAgent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36'
$page = Invoke-WebRequest -Uri $url -UserAgent $browserAgent
$page.Links |
  Where-Object { $_.href -like '*imgres*' } |
  ForEach-Object { ($_.href -split 'imgurl=')[-1].Split('&')[0]}

PowerShell 技能连载 - 查找注册的事件源

每个 Windows 日志文件都有一个注册的事件源列表。要找出哪个事件源注册到哪个事件日志,您可以直接查询 Windows 注册表。

这段代码将列出所有注册到“System”事件日志的事件源:

$LogName = 'System'
$path = "HKLM:\System\CurrentControlSet\services\eventlog\$LogName"
Get-ChildItem -Path $path -Name

PowerShell 技能连载 - 智能感知显示变量的技巧

在 PowerShell ISE 编辑器中,当您键入一个美元符号,将弹出一个智能感知菜单列出当前定义的所有变量。此时当您键入更多字符时,您不仅看见以这些字符开头的变量,而且还能看见在名字任意位置包含这些字符的变量。

要想只看到以您键入的字符开头的变量,请按下 ESC 键关闭智能感知菜单,然后按下 CTRL+SPACE 重新打开它。现在,它将只显示以您键入的字符开头的变量。

PowerShell 技能连载 - 将日志写入自定义的事件日志

我们常常需要在脚本运行时记录一些信息。如果将日志信息写入文本文件,那么您需要自己维护和管理它们。您还可以使用 Windows 自带的日志系统,并享受它带来的各种便利性。

要达到这个目的,您只需要初始化一个您自己的日志。这只需要由管理员操作一次。操作方法是以管理员身份启动 PowerShell,然后输入一行代码:

这样就好了。您现在有了一个可以记录“LogonScript”、“MaintenanceScript”和“Miscellaneous”事件源的日志文件。接下来,您可能只需再进行一些配置,告诉日志系统日志文件的最大容量,以及容量达到最大值的时候需要做什么操作即可:

现在,您新的日志文件最大可增长到 500MB,并且记录在被新记录覆盖之前可以保持 30 天。

您现在可以关闭您的特权窗口。写日志文件并不需要特殊的权限,并且可以从任何普通的脚本或登录脚本中写入日志。所以打开一个普通的 PowerShell 控制台,然后输入以下代码:

现在记录事件十分简单了,您可以根据需要选择任意的事件编号或消息。唯一的前提是只能写入已注册的事件源。

使用 Get-EventLog,您可以很方便地分析机器中的脚本问题:

所以,既然您可以方便地使用工业级强度的 Windows 日志系统,何须费劲地将信息记在纯文本文件中呢?

PowerShell 技能连载 - 记录所有错误

在上一个技巧中您学到了只有将 cmdlet 的 -ErrorAction 参数设为 "Stop",才可以用异常处理器捕获 cmdlet 的错误。但使用这种方式改变了 cmdlet 的行为。它将导致 cmdlet 发生第一个错误的时候停止执行。

请看下一个例子:它将在 windows 文件夹中递归地扫描 PowerShell 脚本。如果您希望捕获错误(例如存取受保护的子文件夹),这将无法工作:

try
{
  Get-ChildItem -Path $env:windir -Filter *.ps1 -Recurse -ErrorAction Stop
}
catch
{
  Write-Warning "Error: $_"
}

以上代码将捕获第一个错误,但 cmdlet 将会停止执行,并且不会继续扫描剩下的子文件夹。

如果您只是需要隐藏错误提示信息,但需要完整的执行结果,而且异常处理器不会捕获到任何东西:

try
{
  Get-ChildItem -Path $env:windir -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue
}
catch
{
  Write-Warning "Error: $_"
}

所以如果您希望一个 cmdlet 运行时不会中断,并且任然能获取一个您有权限的文件夹的完整列表,那么请不要使用异常处理器。相反,使用 -ErrorVariable 并将错误信息静默地保存到一个变量中。

当该 cmdlet 执行结束时,您可以获取该变量的值并产生一个错误报告:

Get-ChildItem -Path $env:windir -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue -ErrorVariable myErrors

Foreach ($incidence in $myErrors)
{
    Write-Warning ("Unable to access " + $incidence.CategoryInfo.TargetName)
}