PowerShell 技能连载 - 自动找借口的脚本

译者注:您没有看错!这是近期最邪恶的一个技巧,文末有译者机器上的实验效果。

厌倦了每次自己想蹩脚的借口?以下脚本能让您每调用一次 Get-Excuse 就得到一个新的接口!您所需的一切只是 Internet 连接:

function Get-Excuse
{
  $url = 'http://pages.cs.wisc.edu/~ballard/bofh/bofhserver.pl'
  $ProgressPreference = 'SilentlyContinue'
  $page = Invoke-WebRequest -Uri $url -UseBasicParsing
  $pattern = '<br><font size = "\+2">(.+)'

  if ($page.Content -match $pattern)
  {
    $matches[1]
  }
}

如果您需要通过代理服务器或者身份认证来访问 Internet,那么请查看函数中 Invoke-WebRequest 的参数。您可以通过它提交代理服务器信息,例如身份验证信息。

译者注:以下是 Get-Excuse 为笔者找的“借口”,很有创意吧 ;-)

PS >Get-Excuse
your process is not ISO 9000 compliant
PS >Get-Excuse
evil hackers from Serbia.
PS >Get-Excuse
piezo-electric interference
PS >Get-Excuse
Bogon emissions
PS >Get-Excuse
because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.
PS >Get-Excuse
Your cat tried to eat the mouse.
PS >Get-Excuse
It works the way the Wang did, what's the problem
PS >Get-Excuse
Telecommunications is upgrading.
PS >Get-Excuse
Your computer's union contract is set to expire at midnight.
PS >Get-Excuse
Daemon escaped from pentagram
PS >Get-Excuse
nesting roaches shorted out the ether cable
PS >Get-Excuse
We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.
PS >Get-Excuse
Root nameservers are out of sync

PowerShell 技能连载 - 导出和导入 PowerShell 历史

PowerShell 保存了您键入的所有命令列表,但是当您关闭 PowerShell 时,这个列表就丢失了。

以下是一个保存当前命令历史到文件的单行代码:

Get-History | Export-Clixml $env:temp\myHistory.xml

当您启动一个新的 PowerShell 控制台或 ISE 编辑器实例时,您可以将保存的历史读入 PowerShell:

Import-Clixml $env:\temp\myHistory.xml | Add-History

不过,加载历史并不会影响键盘缓冲区,所以按下上下键并不会显示新导入的历史条目。然而,您可以用 TAB 自动完成功能来查找您之前输入的命令:

#(KEYWORD) <-现在按下(TAB)键!

PowerShell 技能连载 - 将单词首字母转换为大写

要正确地将单词首字母转换为大写,您可以用正则表达式或者一点系统函数:

用正则表达式的话,您可以这样做:

$sentence = 'here is some text where i would like the first letter to be capitalized.'
$pattern = '\b(\w)'
[RegEx]::Replace($sentence, $pattern, { param($x) $x.Value.ToUpper() })

用系统函数的话,这样做可以达到相同的效果:

$sentence = 'here is some text where i would like the first letter to be capitalized.'
(Get-Culture).TextInfo.ToTitleCase($sentence)

正则表达式稍微复杂一点,但是功能更多。例如如果出于某种古怪的原因,您需要将每个单词的首字母替换为它的 ASCII 码,那么正则表达式可以轻松地实现:

$sentence = 'here is some text where i would like the first letter to be capitalized.'
$pattern = '\b(\w)'
[RegEx]::Replace($sentence, $pattern, { param($x) [Byte][Char]$x.Value })

PowerShell 技能连载 - 查找缺省的 Outlook 配置文件

PowerShell 可以操作 COM 对象,例如 Outlook 应用程序。以下简单的两行代码能返回当前的 Outlook 配置文件名:

$outlookApplication = New-Object -ComObject Outlook.Application
$outlookApplication.Application.DefaultProfileName

PowerShell 技能连载 - PowerShell 4.0 中的动态方法

从 PowerShell 4.0 开始,方法名可以是一个变量。以下是一个简单的例子:

$method = 'ToUpper'
'Hello'.$method()

当您需要调用的方法须通过一段脚本计算得到的时候,这个特性十分有用。

function Convert-Text
{
  param
  (
    [Parameter(Mandatory)]
    $Text,
    [Switch]$ToUpper
  )

  if ($ToUpper)
  {
    $method = 'ToUpper'
  }
  else
  {
    $method = 'ToLower'
  }
  $text.$method()
}

以下是用户调用该函数的方法:

PS> Convert-Text 'Hello'
hello
PS> Convert-Text 'Hello' -ToUpper
HELLO

缺省情况下,该函数将文本转换为小写。当指定了开关参数 -ToUpper 时,函数将文本转换为大写。由于动态方法特性的支持,该函数不需要为此写两遍代码。

译者注:在旧版本的 PowerShell 中,您可以通过 .NET 方法(而不是脚本方法)中的反射来实现相同的目的。虽然它不那么整洁,但它能运行在 PowerShell 4.0 以下的环境:

function Convert-Text
{
  param
  (
    [Parameter(Mandatory)]
    $Text,
    [Switch]$ToUpper
  )

  if ($ToUpper)
  {
    $method = 'ToUpper'
  }
  else
  {
    $method = 'ToLower'
  }
  $methodInfo = $Text.GetType().GetMethod($method, [type[]]@())
  $methodInfo.Invoke($Text, $null)
}

PowerShell 技能连载 - PowerShell 4.0 中的动态参数

在 PowerShell 中,您可以使用变量来指代属性名。这段示例脚本定义了四个 profile 的属性名,然后在一个循环中分别查询这些属性值:

$list = 'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost'
foreach ($property in $list)
{
    $profile.$property
}

您也可以在一个管道中使用它:

'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost' |
ForEach-Object { $profile.$_ }

通过这种方式,您可以检查和返回 PowerShell 当前使用的所有 profile:

'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost' |
ForEach-Object { $profile.$_ } |
Where-Object { Test-Path $_ }

类似地,您可以首先使用 Get-Member 来获取一个指定对象包含的所有属性。以下代码可以返回 PowerShell 的“PrivateData”对象中所有名字包含“color”的属性:

$host.PrivateData | Get-Member -Name *color* | Select-Object -ExpandProperty Name

接下来,您可以用一行代码获取所有的颜色设置:

$object = $host.PrivateData
$object | Get-Member -Name *color* -MemberType *property | ForEach-Object {
    $PropertyName = $_.Name
    $PropertyValue = $object.$PropertyName
    "$PropertyName = $PropertyValue"
} |
Out-GridView

PowerShell 技能连载 - 替换文本中的指定字符

如果您只是需要替换文本中字符出现的所有位置,这是很简单的。以下可以将文本中所有的“l”变为大写:

"Hello World".Replace('l', 'L')

然而有些时候,您需要替换特定位置的某几个字符。我们假设您的文本是一个比特掩码,并且您需要对某些比特置位或清除。以上代码是不能用的,因为它一口气改变了所有的位:

PS> "110100011110110".Replace('1', '0')

000000000000000

而且您也不能通过索引来改变字符。您可以读取一个字符(例如检查某一个位是否为“1”),但您无法改变它的值:

PS> "110100011110110"[-1] -eq '1'
False

PS> "110100011110110"[-2] -eq '1'
True

PS> "110100011110110"[-2] = '0'
无法对 System.String 类型的对象进行索引。

要改变一个字符串中的某些字符,请将它转换为一个 StringBuilder

PS> $sb = New-Object System.Text.StringBuilder("1101100011110110")

PS> $sb[-1]
0

PS> $sb[-1] -eq '1'
False

PS> $sb[-2] -eq '1'
True

PS> $sb[-2] = '0'

PS> $sb[-2] -eq '1'
False

PS> $sb.ToString()
110100011110100

以下是将二进制转换为十进制格式的方法:

PS> $sb.ToString()
110100011110100

PS> [System.Convert]::ToInt64($sb.ToString(), 2)
26868

PowerShell 技能连载 - 怪异的文本格式化(以及解决方法)

试试以下的代码并且找到问题所在:

$desc = Get-Process -Id $pid | Select-Object -Property Description
"PowerShell process description: $desc"

这段代码的目的是获取 PowerShell 宿主进程并且读取进程的描述信息,然后输出到字符串。它的结果看起来是怪异的:

PowerShell process description: @{Description=Windows PowerShell}

这是因为代码中选择了整个 Description 属性,而且结果不仅是描述字符串,而且包括了整个属性:

PS> $desc

Description
-----------
Windows PowerShell ISE

当您只选择一个属性时,请确保使用 -ExpandProperty 而不是 -Property。前者避免产生一个属性列,并且字符串看起来正常了:

PS> $desc = Get-Process -Id $pid | Select-Object -ExpandProperty Description
PS> "PowerShell process description: $desc"
PowerShell process description: Windows PowerShell ISE

PowerShell 技能连载 - 在 PowerShell 中查找服务

Get-Service 可以列出计算机上的所有服务,但是返回的信息十分少。您无法很容易地看出一个服务做什么、它是一个 Microsoft 服务还是一个第三方服务,以及服务所对应的可执行程序。

通过合并一些信息,您可以获取许多更丰富的信息。以下是一个 Find-Service 函数,可以返回一系列丰富的信息:

function Find-Service
{
    param
    (
        $Name = '*',
        $DisplayName = '*',
        $Started
    )
    $pattern = '^.*\.exe\b'

    $Name = $Name.Replace('*','%')
    $DisplayName = $DisplayName.Replace('*','%')

    Get-WmiObject -Class Win32_Service -Filter "Name like '$Name' and DisplayName like '$DisplayName'"|
      ForEach-Object {

        if ($_.PathName -match $pattern)
        {
            $Path = $matches[0].Trim('"')
            $file = Get-Item -Path $Path
            $rv = $_ | Select-Object -Property Name, DisplayName, isMicrosoft, Started, StartMode, Description, CompanyName, ProductName, FileDescription, ServiceType, ExitCode, InstallDate, DesktopInteract, ErrorControl, ExecutablePath, PathName
            $rv.CompanyName = $file.VersionInfo.CompanyName
            $rv.ProductName = $file.VersionInfo.ProductName
            $rv.FileDescription = $file.VersionInfo.FileDescription
            $rv.ExecutablePath = $path
            $rv.isMicrosoft = $file.VersionInfo.CompanyName -like '*Microsoft*'
            $rv
        }
        else
        {
            Write-Warning ("Service {0} has no EXE attached. PathName='{1}'" -f $_.PathName)
        }
      }
}

Find-Service | Out-GridView

PowerShell 技能连载 - 获取 1000 个以上 Active Directory 结果

当您使用 ADSISearcher 时,默认情况下,Active Directory 只返回前 1000 个搜索结果。这是一个防止意外的 LDAP 查询导致域控制器负荷过重的安全保护机制。

如果您需要完整的搜索结果,并且明确地知道它将超过 1000 条记录,请设置 PageSize 为 1000。通过这种方式,ADSISearcher 每一批返回 1000 个搜索结果元素。

以下查询将会返回您域中的所有用户账户(在运行这个查询之前,您也许需要联系一下您的域管理员):

$searcher = [ADSISearcher]"sAMAccountType=$(0x30000000)"

# get all results, do not stop at 1000 results
$searcher.PageSize = 1000

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