PowerShell 技能连载 - 得到一个借口

以下是一个快速的方法来得到一个借口——假设您有 Internet 连接:

#requires -Version 3
function Get-Excuse
{
  $url = 'http://pages.cs.wisc.edu/~ballard/bofh/bofhserver.pl'
  $ProgressPreference = 'SilentlyContinue'
  $page = Invoke-WebRequest -Uri $url -UseBasicParsing
  $pattern = '(?m)<br><font size = "\+2">(.+)'
  if ($page.Content -match $pattern)
  {
    $matches[1]
  }
}

它演示了如何使用 Invoke-WebRequest 来下载一个网页的 HTML 内容,然后使用正则表达式来抓取网页的内容。

PowerShell 技能连载 - 谁在监听?(第二部分)

如果您的系统是 Windows 8 或 Windows Server 2012 或以上版本,您可以使用 Get-NetTcpConnection 来找出哪个网络端口正在使用中,以及谁在监听这些端口。

以下脚本不仅列出正在使用的端口而且列出了正在监听该端口的进程。如果进程是 “svchost”,该脚本还会找出是哪个服务启动了这个进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#requires -Version 3 -Modules NetTCPIP

$service = @{}
$Process = @{
Name = 'Name'
Expression = {
$id = $_.OwningProcess
$name = (Get-Process -Id $id).Name
if ($name -eq 'svchost')
{
if ($service.ContainsKey($id) -eq $false)
{
$service.$id = Get-WmiObject -Class win32_Service -Filter "ProcessID=$id" | Select-Object -ExpandProperty Name
}
$service.$id -join ','
}
else
{
$name
}
}
}

Get-NetTCPConnection |
Select-Object -Property LocalPort, OwningProcess, $Process |
Sort-Object -Property LocalPort, Name -Unique

结果类似如下:

LocalPort OwningProcess Name
--------- ------------- ----
      135           916 RpcEptMapper,RpcSs
      139             4 System
      445             4 System
     5354          2480 mDNSResponder
     5985             4 System
     7680           544 Appinfo,BITS,Browser,CertPropSvc,DoSvc,iphlpsvc,Lanm...
     7779             4 System
    15292          7364 Adobe Desktop Service
    27015          2456 AppleMobileDeviceService
(...)

PowerShell 技能连载 - 谁在监听?(第一部分)

一个过去十分好用的 netstat.exe 可以告诉您应用程序在监听哪些端口,不过结果是纯文本。PowerShell 可以用正则表达式将文本分割成 CSV 数据,ConvertFrom-Csv 可以将文本转换为真实的对象。

这是一个如何用 PowerShell 处理最基础数据的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#requires -Version 2
NETSTAT.EXE -anop tcp|
Select-Object -Skip 4|
ForEach-Object -Process {
[regex]::replace($_.trim(),'\s+',' ')
}|
ConvertFrom-Csv -d ' ' -Header 'proto', 'src', 'dst', 'state', 'pid'|
Select-Object -Property src, state, @{
name = 'process'
expression = {
(Get-Process -PipelineVariable $_.pid).name
}
} |
Format-List

结果类似如下:

src     : 0.0.0.0:135
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service,
          AdobeIPCBroker...}

src     : 0.0.0.0:445
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service,
          AdobeIPCBroker...}

src     : 0.0.0.0:5985
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service,
          AdobeIPCBroker...}

src     : 0.0.0.0:7680
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service,
          AdobeIPCBroker...}

src     : 0.0.0.0:7779
state   : LISTEN
process : {Adobe CEF Helper, Adobe CEF Helper, Adobe Desktop Service,
          AdobeIPCBroker...}

PowerShell 技能连载 - 将对象发送到记事本

在前一个技能里我们演示了如何将文本发送到一个全新的记事本实例中。今天,您会获得一个增强版的 Out-Notepad:您现在可以通过管道传输任何东西到记事本了。如果内容不是字符串,Out-Notepad 会使用内置的 PowerShell ETS 将它转换为文本并且合适地显示出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#requires -Version 2
function Out-Notepad
{
param
(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[Object]
[AllowEmptyString()]
$Object,

[Int]
$Width = 150
)

begin
{
$al = New-Object System.Collections.ArrayList
}

process
{
$null = $al.Add($Object)
}
end
{
$text = $al |
Format-Table -AutoSize -Wrap |
Out-String -Width $Width

$process = Start-Process notepad -PassThru
$null = $process.WaitForInputIdle()


$sig = '
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
'

$type = Add-Type -MemberDefinition $sig -Name APISendMessage2 -PassThru
$hwnd = $process.MainWindowHandle
[IntPtr]$child = $type::FindWindowEx($hwnd, [IntPtr]::Zero, "Edit", $null)
$null = $type::SendMessage($child, 0x000C, 0, $text)
}
}

您现在可以通过管道传输任何东西到 Out-Notepad。它将原样显示:

PS C:\> Get-Content $env:windir\system32\drivers\etc\hosts | Out-Notepad

如果您通过管道传送对象,Out-Notepad 会将它们转换为文本并且不会截断任何东西。您可能会希望用 -Width 参数来确定页宽,以便正常显示:

PS C:\> Get-EventLog -LogName System -EntryType Error, Warning -Newest 10 | Out-Notepad -Width 130

另外您可能需要最大化记事本或禁用换行来查看正确的格式。

PowerShell 技能连载 - 发送文本到记事本

记事本可以用来显示文本结果。通常,您需要将文本保存到文件,然后用记事本打开该文件。不过还有一个更好的办法:打开一个空白的记事本,然后用 Windows 消息直接把文本发送到未命名的记事本编辑器中。

这个函数称为 Out-Notepad。无论您传给这个函数什么文本,它都会在记事本的一个未命名实例中显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#requires -Version 2
function Out-Notepad
{
param
(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[String]
[AllowEmptyString()]
$Text
)

begin
{
$sb = New-Object System.Text.StringBuilder
}

process
{
$null = $sb.AppendLine($Text)
}
end
{
$text = $sb.ToString()

$process = Start-Process notepad -PassThru
$null = $process.WaitForInputIdle()


$sig = '
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
'

$type = Add-Type -MemberDefinition $sig -Name APISendMessage -PassThru
$hwnd = $process.MainWindowHandle
[IntPtr]$child = $type::FindWindowEx($hwnd, [IntPtr]::Zero, "Edit", $null)
$null = $type::SendMessage($child, 0x000C, 0, $text)
}
}

这是一些示例调用:

PS C:\> Get-Content $env:windir\system32\drivers\etc\hosts | Out-Notepad

PS C:\> Get-Process | Out-String | Out-Notepad

PowerShell 技能连载 - 神奇的下划线变量

以下是一个非常特别(并且有详细文档的)的使用 PowerShell 变量的方法。请看这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#requires -Version 2

function Test-DollarUnderscore
{
param
(
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[string]
$Test
)

process
{
"received: $Test"
}
}

它初看起来并没有什么特别之处。您可以将数值赋给 -Test 参数,并且该函数返回它们:

1
2
PS C:\> Test-DollarUnderscore -Test 'Some Data'
received: Some Data

但是请看当您通过管道传送一个数据给该函数时发生了什么:

1
2
3
4
5
PS C:\> 1..4 | Test-DollarUnderscore -Test { "I am receiving $_" }
received: I am receiving 1
received: I am receiving 2
received: I am receiving 3
received: I am receiving 4

-Test 参数瞬间自动神奇地接受脚本块了(虽然赋予的类型是 string)。而且在脚本块中,您可以存取输入管道的数据。

您能得到这个非常特别的参数支持功能是因为您为一个必选参数设置了 ValueFromPipelineByPropertyName=$true,并且输入的数据没有一个属性和该参数匹配。

PowerShell 技能连载 - 换算货币

PowerShell 是一个非常有用的语言,可以调用 Web Service 和访问网页。如果您将两者合并成一个动态参数,就能得到一个专业的,支持实时汇率的货币换算器。

以下 ConvertTo-Euro 函数可以输入其他货币并转换成欧元。该函数有一个 -Currency 参数,并可以动态地传入欧洲中央银行支持的货币。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function ConvertTo-Euro
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[Double]
$Value
)

dynamicparam
{
$Bucket = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

$Attributes = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
$AttribParameter = New-Object System.Management.Automation.ParameterAttribute
$AttribParameter.Mandatory = $true
$Attributes.Add($AttribParameter)

if ($script:currencies -eq $null)
{
$url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
$result = Invoke-RestMethod -Uri $url
$script:currencies = $result.Envelope.Cube.Cube.Cube.currency
}

$AttribValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($script:currencies)
$Attributes.Add($AttribValidateSet)

$Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter('Currency',[String], $Attributes)
$Bucket.Add('Currency', $Parameter)

$Bucket
}

begin
{
foreach ($key in $PSBoundParameters.Keys)
{
if ($MyInvocation.MyCommand.Parameters.$key.isDynamic)
{
Set-Variable -Name $key -Value $PSBoundParameters.$key
}
}

$url = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
$rates = Invoke-RestMethod -Uri $url
$rate = $rates.Envelope.Cube.Cube.Cube |
Where-Object { $_.currency -eq $Currency} |
Select-Object -ExpandProperty Rate
}

process
{
$result = [Ordered]@{
Value = $Value
Currency = $Currency
Rate = $rate
Euro = ($Value / $rate)
Date = Get-Date
}

New-Object -TypeName PSObject -Property $result
}
}

该函数演示了如何向动态参数填充动态数据,以及该数据如何缓存以免智能感知每次触发一个新的请求过程。

以下使一些您可能期待的例子(需要 Internet 连接):

PS C:\> 100, 66.9 | ConvertTo-Euro -Currency DKK

Value    : 100
Currency : DKK
Rate     : 7.4622
Euro     : 13,4008737369677
Date     : 26.01.2016 21:32:44

Value    : 66,9
Currency : DKK
Rate     : 7.4622
Euro     : 8,96518453003136
Date     : 26.01.2016 21:32:45



PS C:\>  ConvertTo-Euro -Currency USD -Value 99.78

Value    : 99,78
Currency : USD
Rate     : 1.0837
Euro     : 92,0734520623789
Date     : 26.01.2016 21:33:01

PowerShell 技能连载 - 统计一个 Word 文档中的页数

假设您有一系列 Word 文档,并且希望知道它们共有多少页。以下是一个函数,传入一个 Word 文件参数,便能得到它包含多少页:

#requires -Version 1

# adjust path to point to an existing Word file:
$Path = "C:\...\SomeChapter.doc"
$word = New-Object -ComObject Word.Application
$word.Visible = $true
$binding = 'System.Reflection.BindingFlags' -as [type]
$doc = $word.Documents.Open($Path)
$doc.Repaginate()
$prop = $doc.BuiltInDocumentProperties(14)
$pages = [System.__ComObject].invokemember('value',$binding::GetProperty,$null,$prop,$null)
$doc.Close(0)
$word.Quit()
"$Path has $Pages pages."

如果这对您有用,可以将它改为一个函数,您就可以用 PowerShell 来统计多个 Word 文档共有多少页。

PowerShell 技能连载 - 将窗口置于前台

PowerShell 可以使用 Add-Type 来操作 Windows 内置的 API 功能。通过这种方法,可以很容易地将所有进程的窗口置于前台。以下是您需要的函数:

#requires -Version 2
function Show-Process($Process, [Switch]$Maximize)
{
  $sig = '
    [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
    [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
  '

  if ($Maximize) { $Mode = 3 } else { $Mode = 4 }
  $type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
  $hwnd = $process.MainWindowHandle
  $null = $type::ShowWindowAsync($hwnd, $Mode)
  $null = $type::SetForegroundWindow($hwnd)
}

要测试 Show-Process,以下是一段示例代码,演示如何使用它:

# launch Notepad minimized, then make it visible
$notepad = Start-Process notepad -WindowStyle Minimized -PassThru
Start-Sleep -Seconds 2
Show-Process -Process $notepad
# switch back to PowerShell, maximized
Start-Sleep -Seconds 2
Show-Process -Process (Get-Process -Id $PID) -Maximize
# switch back to Notepad, maximized
Start-Sleep -Seconds 2
Show-Process -Process $notepad -Maximize
# switch back to PowerShell, normal window
Start-Sleep -Seconds 2
Show-Process -Process (Get-Process -Id $PID)

PowerShell 技能连载 - 处理数据(第 3 部分)

在第 1 和 第 2 部分,您学到了如何 PowerShell 函数如何处理通过参数和通过管道传入的信息。在这部分中,我们打算介绍一个函数如何接收多行文本并将它加工成一个字符串。

这个函数同时接受参数和管道传入的多行文本。该函数使用 StringBuilder 对象来收集所有的文本行,然后将收到的所有文本行合并成单个字符串:

#requires -Version 2
function Collect-Text
{
  param
  (
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    [String[]]
    [AllowEmptyString()]
    $Text
  )

  begin
  {
    $sb = New-Object System.Text.StringBuilder
  }

  process
  {
   foreach ($line in $Text)
   {
     $null = $sb.AppendLine($line)
   }
  }
  end
  {
    $result = $sb.ToString()
    $result
  }
}

请注意如何既支持通过参数,也支持通过管道传递文本行:

PS C:\> Collect-Text -Text 'Line 1', '', 'Line 2'
Line 1

Line 2


PS C:\> 'Line 1', '', 'Line 2' | Collect-Text
Line 1

Line 2

请注意参数:它使用了 [AllowEmptyString()] 属性。它确保可以接受空字符串参数。在 mandatory (必须)参数中,如果没有这个属性,是不允许空字符串的。