PowerShell 技能连载 - 查找缺少邮箱地址的 Active Directory 用户

LDAP 查询的功能非常强大,可以帮助查找缺少信息的账户。

这段代码将返回所有带邮箱地址的 Active Directory 用户:

$searcher = [ADSISearcher]"(&(sAMAccountType=$(0x30000000))(mail=*))"
$searcher.FindAll() |
  ForEach-Object { $_.GetDirectoryEntry() } |
  Select-Object -Property sAMAccountName, name, mail

如果您想查询相反的内容,请通过“!”号进行相反的查询。以下代码可以返回所有缺少邮箱地址的 Active Directory 用户:

$searcher = [ADSISearcher]"(&(sAMAccountType=$(0x30000000))(!(mail=*)))"
$searcher.FindAll() |
  ForEach-Object { $_.GetDirectoryEntry() } |
  Select-Object -Property sAMAccountName, name, mail

用 PowerShell 脚本获取天气实况

只要两行命令,就可以“轻松”地获取实时天气预报:

(curl http://61.4.185.48:81/g/ -UseBasicParsing).Content -cmatch 'var id=(\d+);' | Out-Null
irm "http://www.weather.com.cn/data/sk/$($matches[1]).html" | select -exp weatherinfo

使用效果:

PS >(curl http://61.4.185.48:81/g/ -UseBasicParsing).Content -cmatch 'var id=(\d+);' | Out-Null
PS >irm "http://www.weather.com.cn/data/sk/$($matches[1]).html" | select -exp weatherinfo


city    : 福州
cityid  : 101230101
temp    : 15
WD      : 北风
WS      : 2级
SD      : 79%
WSE     : 2
time    : 10:20
isRadar : 1
Radar   : JC_RADAR_AZ9591_JB

您还可以把第二行改为以下形式,获取更猛的数据:

irm "http://m.weather.com.cn/data/$($matches[1]).html" | select -exp weatherinfo

或:

irm "http://www.weather.com.cn/data/cityinfo/$($matches[1]).html" | select -exp weatherinfo

源代码下载

顺便透露一下,高富帅一般不这么看天气预报哦!

PowerShell 技能月刊

编号 发布时间 标题 PDF
Vol.01 2013年06月 文件系统任务 下载
Vol.02 2013年07月 数组和哈希表 下载
Vol.03 2013年08月 日期、时间和文化 下载
Vol.04 2013年09月 对象和类型 下载
Vol.05 2013年10月 WMI 下载
Vol.06 2013年11月 正则表达式 下载
Vol.07 2013年12月 函数 下载
Vol.08 2013年12月 静态 .NET 方法 下载
Vol.09 2014年01月 注册表 下载
Vol.10 2014年02月 Internet 相关任务 下载
Vol.11 2014年03月 XML 相关任务 下载
Vol.12 2014年08月 安全相关任务 下载

如果您(和我一样)足够懒,也可以用这样一行 PowerShell 代码来下载:

1..12 | ForEach-Object { Invoke-WebRequest "http://powershell.com/cs/PowerTips_Monthly_Volume_$_.pdf" -OutFile "PowerTips_Monthly_Volume_$_.pdf" }

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 脚本中使用 C# 代码

PowerShell 使我们拥有了一门非常强大的脚本语言。许多产品,例如 SharePoint 以 Cmdlet 的形式提供了它们自己的管理扩展。

客户们喜欢脚本语言,是因为它使他们能够编写自己的代码而不需要运行某个编译器,也不需要将可执行程序拷贝到它们的目标计算机中。相对于部署一个脚本,在那些目标计算机中运行一个可执行程序或者在命令行 Shell 中执行一些命令通常需要更复杂的审批过程。

但是从另一方面来说,编写 PowerShell 脚本需要学习一门新的脚本语言并且需要使用他们所熟悉范围之外的工具。作为一个开发者,我喜欢 C# 和 Visual Studio 的智能提示等强大功能。并且,在过去几年内,我用 C# 开发了许多工具——并且我不希望在移植到 PowerShell 的过程中丢弃这些设计好的轮子。

所以如果能在 PowerShell 中复用现有的 C# 代码,而不需要将它以 Cmdlet的形式实现的话,那就十分理想了。

实际上 PowerShell 2.0 提供了一种方式来实现它:使用 Add-Type Cmdlet,它能够通过您提供的 C# 源代码在内存中生成一个新的 .NET 程序集,并且可以将该程序集直接用于同一个会话中的 PowerShell 脚本中。

出于演示的目的,我们假设已有以下简单的 C# 代码,作用是获取和设置 SharePoint 中的 Content Deployment 的 RemoteTimeout 值:

using Microsoft.SharePoint.Publishing.Administration;
using System;

namespace StefanG.Tools
{
    public static class CDRemoteTimeout
    {
        public static void Get()
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            Console.WriteLine("Remote Timeout: "+cdconfig.RemoteTimeout);
        }

        public static void Set(int seconds)
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            cdconfig.RemoteTimeout = seconds;
            cdconfig.Update();
        }
    }
}

除了引用 .NET 框架之外,这个工具还引用了两个 SharePoint DLL(Microsoft.SharePoint.dllMicrosoft.SharePoint.Publishing.dll),它们用来存取 SharePoint 的对象模型。为了确保 PowerShell 能正确地生成程序集,我们需要为 Add-Type Cmdlet 用 -ReferencedAssemblies 参数提供引用信息。

为了指定源代码的语言(可以使用 CSharpCSharpVersion3Visual BasicJScript),您需要使用 -Language 参数。缺省值是 CSharp

在我的系统中我有一个 csharptemplate.ps1[csharptemplate.ps1] 文件,我可以快速地复制和修改成我需要的样子来运行我的 C# 代码:

$Assem = (
...add referenced assemblies here...
    )

$Source = @"
...add C# source code here...
"@

Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp

对于上述的 C# 例子,对应的最终 PowerShell 脚本如下:

$Assem = (
    "Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" ,
    "Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
    )

$Source = @"
using Microsoft.SharePoint.Publishing.Administration;
using System;

namespace StefanG.Tools
{
    public static class CDRemoteTimeout
    {
        public static void Get()
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            Console.WriteLine("Remote Timeout: "+cdconfig.RemoteTimeout);
        }

        public static void Set(int seconds)
        {
            ContentDeploymentConfiguration cdconfig = ContentDeploymentConfiguration.GetInstance();
            cdconfig.RemoteTimeout = seconds;
            cdconfig.Update();
        }
    }
}
"@

Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp

[StefanG.Tools.CDRemoteTimeout]::Get()
[StefanG.Tools.CDRemoteTimeout]::Set(600)

上述例子的最后几行演示了如何在 PowerShell 中调用 C# 方法。

注:文中涉及到的 csharptemplate.ps1 可以在这里[下载][csharptemplate.ps1]。
[csharptemplate.ps1]: /assets/download/csharptemplate.ps1

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 处理纯文本 - 4

命题

QQ 群里的史瑞克朋友提出的一个命题:

$txt="192.168.1
192.168.2
192.168.3
172.19.3
192.16.1
192.16.2
192.16.11
192.16.3
10.0.4
192.16.29
192.16.9
192.16.99
192.16.100"

要求输出:

10.0.4
172.19.3
192.16.1-192.16.3
192.16.9
192.16.11
192.16.29
192.16.99-192.16.100
192.168.1-192.168.3

问题解析

* 将各个 IP 段补充为三位的格式
* 按字符串排序
* 遍历每一行,按照以下规则处理:
    * 如果和上一行连续,则上一段可能没有结束,更新 `$endIP`
    * 如果和上一行不连续
        * 若 `$startIP` 和 `$endIP` 相同,说明是单个 IP,将单个 IP 加入 $result
        * 若 `$startIP` 和 `$endIP` 不同,说明是一段 IP,将一段 IP 加入 $result
        * 更新 `$startIP` 和 `$endIP`
* 最后一行需要特殊处理

PowerShell 实现

$DebugPreference = "Continue"

$txt="192.168.1
192.168.2
192.168.3
172.19.3
192.16.1
192.16.2
192.16.11
192.16.3
10.0.4
192.16.29
192.16.9
192.16.99
192.16.100"
$txt += "`n999.999.999"

$startIP = @(0, 0, 0)
$endIP = @(0, 0, 0)
$result = @()

-split $txt | % {
    $fullSegments = ($_ -split "\." | % {
        "{0:D3}" -f [int]$_
    })
    $fullSegments -join "."
} | sort | % {
    Write-Debug "Processing $_"
    $segments = @($_ -split "\." | % {
        [int]$_
    })

    if ($endIP[0] -eq $segments[0] -and
        $endIP[1] -eq $segments[1] -and
        $endIP[2] + 1 -eq $segments[2]) {
        Write-Debug '和上一个IP连续'
        $endIP = $segments
    } else {
        Write-Debug '和上一个IP不连续'
        if (($startIP -join ".") -eq ($endIP -join ".")) {
            Write-Debug '单个IP'
            $result += $startIP -join "."
        } else {
            Write-Debug '一段IP'
            $result += ($startIP -join ".") + "-" + ($endIP -join ".")
        }

        $startIP = $segments
        $endIP = $segments
    }
}

$result | select -Skip 1

源代码请在这里下载