PowerShell 技能连载 - 有趣的声音提示

适用于所有 PowerShell 版本

如果您的计算机装有声卡,那么这段代码可以让您的同事们吓一跳:

function prompt
{
  1..3 | ForEach-Object {
    $frequency = Get-Random -Minimum 400 -Maximum 10000
    $duration = Get-Random -Minimum 100 -Maximum 400
    [Console]::Beep($frequency, $duration)
  }
  'PS> '
  $host.ui.RawUI.WindowTitle = Get-Location
}

这段代码将会缩短您的 PowerShell 提示符,并且在标题栏上显示当前的路径。这还算是有益的功能。搞破坏的部分是每次执行一条命令,都会发出随机频率的刺耳的三连音:)。

PowerShell 技能连载 - 产生多个返回值

适用于所有 PowerShell 版本

如果一个 PowerShell 函数需要产生多个返回信息,最佳的实践方式是返回多个对象,然后将信息分别存储在对象的各个属性中。

以下是一个有趣的例外情况,它在某些场景中较为适用。尽管返回多个信息就可以了,并且要确保将结果赋值给多个变量:

function Get-MultipleData
{
  Get-Date
  'Hello'
  1+4
}

$date, $text, $result = Get-MultipleData

"The date is $date"
"The text was $text"
"The result is $result"

这个测试函数产生 3 段信息,然后将结果存储在 3 个不同的变量中。

PowerShell 技能连载 - 编辑“hosts”文件

适用于所有 PowerShell 版本

如果您常常需要修改“hosts”文件,那么手工用提升权限的记事本实例来打开文件是相当乏味的事情。这是因为该文件只能被 Administrators 用户修改,所以普通的记事本实例无法修改它。

以下是一段您可以直接使用,或者调整一下用来打开任何需要提升权限的程序的脚本。

function Show-HostsFile
{
  $Path = "$env:windir\system32\drivers\etc\hosts"
  Start-Process -FilePath notepad -ArgumentList $Path -Verb runas
}

PowerShell 技能连载 - 请注意 UNC 路径!

适用于所有 PowerShell 版本

许多 cmdlet 可以处理 UNC 路径,但是使用 UNC 路径会导致很多古怪的情况。请看以下:

PS> Test-Path -Path \\127.0.0.1\c$
True

这段代码返回了 true,该 UNC 路径存在。现在将当前驱动器变为一个非文件系统驱动器,然后再次实验:

PS> cd hkcu:\

PS> Test-Path -Path \\127.0.0.1\c$
False

同样的路径现在返回了 false。这是因为 UNC 路径并不包含驱动器号,而 PowerShell 需要驱动器号来指定正确的提供器。如果一个路径不包含驱动器号,那么 PowerShell 假设使用当前驱动器的提供器。所以如果您将当前的目录改为注册表,PowerShell 尝试在那儿查找 UNC 路径,那么就会失败。

更糟糕的是,出于某些未知的原因,但您用 net use 来映射驱动器时,PowerShell 在使用 cmdlet 来访问驱动器时可能会也可能不会产生混淆。

解决方案十分简单:当您用 cmdlet 访问 UNC 时,始终在 UNC 路径前面加上正确的提供器名称。这将消除该问题:

PS> Test-Path -Path FileSystem::\\127.0.0.1\c$
True

PS> cd hkcu:\

PS> Test-Path -Path \\127.0.0.1\c$
False

PS> Test-Path -Path FileSystem::\\127.0.0.1\c$
True

如果您遇到了 net use 产生的问题,也可以使用同样的办法,在路径前面加上 “FileSystem::。该问题可以立刻得到解决。

PowerShell 技能连载 - 查找 AD 用户

适用于所有 PowerShell 版本

假如您已登录到了一个活动目录域中,那么只需要执行一些简单的命令就可以搜索活动目录。在前一个技巧中我们演示了最基本的脚本。以下是一个扩展,它能够定义一个搜索的根(搜索的起点),就像一个扁平的搜索一样(相对于在容器中递归而言)。

它也演示了如何将活动目录的搜索结果转换成实际的用户对象:

$SAMAccountName = 'tobias'
$SearchRoot = 'LDAP://OU=customer,DC=company,DC=com'
$SearchScope = 'OneLevel'

$ldap = "(&(objectClass=user)(samAccountName=*$SAMAccountName*))"
$searcher = [adsisearcher]$ldap
$searcher.SearchRoot = $SearchRoot
$searcher.PageSize = 999
$searcher.SearchScope = $SearchScope

$searcher.FindAll() |
  ForEach-Object { $_.GetDirectoryEntry()  } |
  Select-Object -Property *

PowerShell 技能连载 - 查找并提取注册表键的路径

适用于所有 PowerShell 版本

在前一个技巧中,我们演示了如何将一个 PowerShell 内部的路径格式转换为一个真实的路径。以下是一个用力。这段代码递归地搜索 HKEY_CURRENT_USER 键,并且找出所有包含单词“_powershell_”的注册表键(您可以将搜索关键字换成任何别的):

Get-ChildItem -Path HKCU:\ -Include *PowerShell* -Recurse -ErrorAction SilentlyContinue |
  Select-Object -Property *Path* |
  Out-GridView

这段代码输出所有名称中包含“Path_”的属性。如您所见,注册表键中有两个属性包含该关键字:_PSPath 和 _PSParentPath_。两者都是 PowerShell 内置的路径格式。

要提取所有满足搜索条件的注册表键的路径,请使用以下代码:

Get-ChildItem -Path HKCU:\ -Include *PowerShell* -Recurse -ErrorAction SilentlyContinue |
  ForEach-Object {
    Convert-Path -Path $_.PSPath
  }

PowerShell 技能连载 - 修正 PowerShell 中的路径

适用于所有 PowerShell 版本

有些时候,您会为某些奇怪的路径格式感到困惑,比如这个:

Microsoft.PowerShell.Core\FileSystem::C:\windows\explorer.exe

这是一个完整的 PowerShell 路径名,路径中包含了了模块名和提供器名。要得到一个纯的路径名,请使用以下代码:

Convert-Path -Path Microsoft.PowerShell.Core\FileSystem::C:\windows\explorer.exe

PowerShell 技能连载 - 修正名单中的大小写

适用于所有 PowerShell 版本

假设您的工作是更新一份名单。以下方法可以确保只有名字的第一个字母改成大写。这个方法对于姓-名的方式也是有效的:

$names = 'some-wILD-casING','frank-PETER','fred'

Foreach ($name in $names)
{
  $corrected = foreach ($part in $name.Split('-'))
  {
    $firstChar = $part.Substring(0,1).ToUpper()
    $remaining = $part.Substring(1).ToLower()

    "$firstChar$remaining"
  }
  $corrected -join '-'
}

Some-Wild-Casing
Frank-Peter
Fred

PowerShell 技能连载 - 使系统休眠

适用于 PowerShell 所有版本

以下是一个简单的系统调用,可以使系统休眠(当然,前提是启用了休眠功能):

function Start-Hibernation
{
  rundll32.exe PowrProf.dll, SetSuspendState 0,1,0
}

请注意这个函数调用是大小写敏感的!

PowerShell 技能连载 - 指定递归深度

适用于 PowerShell 3.0 及更高版本

当使用 Get-ChildItem 来列出文件夹内容时,可以用 -Recurse 参数来对子目录进行递归。然而,这导致无法控制递归深度。Get-ChildItem 会在所有子目录中搜索,无限地递归下去。

Get-ChildItem -Path $env:windir -Filter *.log -Recurse -ErrorAction SilentlyContinue

有些时候,我们会见到一种类似这样的方法,来试图控制递归的深度:

Get-ChildItem -Path $env:windir\*\*\* -Filter *.log -ErrorAction SilentlyContinue

然而,这并不能限制只递归 3 层。实际上,它的作用是搜索 3 层及 3 层以上的文件夹。它不会搜索 1 层或 2 层的文件夹。

限制递归深度的唯一办法是自己实现递归算法:

function Get-MyChildItem
{
  param
  (
    [Parameter(Mandatory = $true)]
    $Path,

    $Filter = '*',

    [System.Int32]
    $MaxDepth = 3,

    [System.Int32]
    $Depth = 0
  )

  $Depth++

  Get-ChildItem -Path $Path -Filter $Filter -File

  if ($Depth -le $MaxDepth)
  {
    Get-ChildItem -Path $Path -Directory |
      ForEach-Object { Get-MyChildItem -Path $_.FullName -Filter $Filter -Depth $Depth -MaxDepth $MaxDepth}
  }

}

Get-MyChildItem -Path c:\windows -Filter *.log -MaxDepth 2 -ErrorAction SilentlyContinue |
  Select-Object -ExpandProperty FullName

这段代码将获取您 Windows 文件夹中深度在 2 层以内的 *.log 文件。