PowerShell 技能连载 - 通过管道输入数据

在前一个技能里我们演示了 Convert-Umlaut 如何转换一个字符串中的特殊字符。这在一个函数接受管道输入的时候更有用。让我们来看看增加这种特性所需要做的改变。

在不支持管道的情况下,该函数大概长这个样子:

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

function Convert-Umlaut
{
param
(
[Parameter(Mandatory)]
$Text
)

$output = $Text.Replace('ö','oe').Replace('ä','ae').Replace('ü','ue').Replace('ß','ss').Replace('Ö','Oe').Replace('Ü','Ue').Replace('Ä','Ae')
$isCapitalLetter = $Text -ceq $Text.toUpper()
if ($isCapitalLetter)
{
$output = $output.toUpper()
}
$output
}

可以通过这种方式执行:

1
2
PS C:\> Convert-Umlaut -Text "Mößler, Christiansön"
Moessler, Christiansoen

然而,它不能像这样执行:

1
PS C:\> "Mößler, Christiansön" | Convert-Umlaut

要增加管道功能,需要做两件事:

  1. 参数需要标记为支持管道数据。
  2. 在迭代中对每个输入的元素进行处理的代码需要放置在 “process“ 代码块中。

以下是改变后的代码:

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

function Convert-Umlaut
{
param
(
[Parameter(Mandatory, ValueFromPipeline)]
$Text
)

process
{
$output = $Text.Replace('ö','oe').Replace('ä','ae').Replace('ü','ue').Replace('ß','ss').Replace('Ö','Oe').Replace('Ü','Ue').Replace('Ä','Ae')
$isCapitalLetter = $Text -ceq $Text.toUpper()
if ($isCapitalLetter)
{
$output = $output.toUpper()
}
$output
}
}

现在,也可以通过管道传输数据了:

1
2
PS C:\> "Mößler, Christiansön" | Convert-Umlaut
Moessler, Christiansoen

PowerShell 技能连载 - 替换类似 “Umlauts” 的特殊字符

支持 PowerShell 2.0 以上版本

有些时候我们需要将一些字符替换,例如德语的 “Umlauts”,来适应用户名或邮箱地址。

以下是一个演示如何实现这个功能的小函数:

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

function Convert-Umlaut
{
param
(
[Parameter(Mandatory)]
$Text
)

$output = $Text.Replace('ö','oe').Replace('ä','ae').Replace('ü','ue').Replace('ß','ss').Replace('Ö','Oe').Replace('Ü','Ue').Replace('Ä','Ae')
$isCapitalLetter = $Text -ceq $Text.toUpper()
if ($isCapitalLetter)
{
$output = $output.toUpper()
}
$output
}

要转换一个字符串,请这样使用:

1
2
PS C:\> Convert-Umlaut -Text "Mößler, Christiansön"
Moessler, Christiansoen

PowerShell 技能连载 - 友好地使用 Robocopy

支持 PowerShell 2.0 以上版本

Robocopy 是一个用于拷贝文件的工具,它在 PowerShell 里的功能也是一样。然而您可以用 PowerShell 将 robocopy 封装在一个对用户友好的 PowerShell 函数中。通过这种方式,您不再需要记忆 robocopy 别扭的命令行选项。取而代之的是 PowerShell 参数和智能提示功能。

一次 robocopy 的调用可能看起来如下:

1
PS C:\> Invoke-Robocopy -Source $env:windir -Destination c:\logs -Filter *.log -Recurse -Open

以下是封装函数:

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
#requires -Version 3

function Invoke-Robocopy
{
param
(
[String]
[Parameter(Mandatory)]
$Source,

[String]
[Parameter(Mandatory)]
$Destination,

[String]
$Filter = '*',

[Switch]
$Recurse,

[Switch]
$Open
)

if ($Recurse)
{
$DoRecurse = '/S'
}
else
{
$DoRecurse = ''
}

robocopy $Source $Destination $Filter $DoRecurse /R:0

if ($Open)
{
explorer.exe $Destination
}
}

PowerShell 技能连载 - 直接使用 .NET 类型

Cmdlet 内含了纯 .NET 代码,所以感谢 cmdlet,我们通常无需接触 .NET 代码。不过,如果您需要的话仍然可以使用。以下是一系列调用示例,演示了如何调用 .NET 方法:

#requires -Version 2
[System.Convert]::ToString(687687687, 2)

[Math]::Round(4.6)

[Guid]::NewGuid()

[System.IO.Path]::ChangeExtension('c:\test.txt', 'bak')

[System.Net.DNS]::GetHostByName('dell1')
[System.Net.DNS]::GetHostByAddress('192.168.1.124')

[Environment]::SetEnvironmentVariable()

# dangerous, save your work first
[Environment]::FailFast('Oops')

Add-Type -AssemblyName PresentationFramework
$dialog = New-Object Microsoft.Win32.OpenFileDialog
$dialog.ShowDialog()
$dialog.FileName

PowerShell 技能连载 - 使用工作流来并发执行代码

如果您希望同时执行多个任务,以下有多种方法用 Powershell 实现。一种是使用工作流。它们是 PowerShell 3.0 中引入的:

#requires -Version 3
workflow Test-ParallelForeach
{
  param
  (
    [String[]]
    $ComputerName
  )

  foreach -parallel -throttlelimit 8 ($Machine in $ComputerName)
  {
    "Begin $Machine"
    Start-Sleep -Seconds (Get-Random -min 3 -max 5)
    "End $Machine"
  }
}

$list = 1..20

Test-ParallelForeach -ComputerName $list | Out-GridView

Test-ParallelForeach 处理一个计算机列表(在这个例子中,是一个数字列表)。它们同时执行。要控制资源的使用,并行循环将节流限制为 8,所以所以在这个例子中的 20 台计算机是 8 个一组处理的。

请注意使用工作流需要更多地了解它们的架构和限制。这个例子关注于工作流提供的并行循环技术。

PowerShell 技能连载 - 用 #requires 语句装饰脚本

PowerShell 支持一系列 #requires 语句。技术上它们是注释,但是 PowerShell 会检查这些语句所申明的必要条件,并且如果条件不满足,它将不会执行这个脚本。另外,#requires 语句能快速地告知您运行脚本的前提条件。

#requires -Modules PrintManagement
#requires -Version 3
#Requires -RunAsAdministrator

#requires 语句必须是一个脚本的第一条语句,并且它只对保存的脚本有效。

PowerShell 技能连载 - 不要混合不同的对象

如果您连续输出完全不同的对象,您可能丢失信息。请看这个例子:

#requires -Version 2

$hash = @{
Name = 'PowerShell Conference EU'
Date = 'April 20, 2016'
City = 'Hannover'
URL = 'www.psconf.eu'
}
New-Object -TypeName PSObject -Property $hash

$b = Get-Process -Id $pid
$b

当您运行这段代码时,您将得到这样的结果:

Date           URL           Name                     City
----           ---           ----                     ----
April 20, 2016 www.psconf.eu PowerShell Conference EU Hannover
                             powershell_ise

看起来 $b (process) 的几乎所有属性都丢失了。原因是 PowerShell 是实时输出对象的,而且首次提交的对象决定了在表格中显示哪些属性。所有接下来的对象都将纳入这张表格中。

如果您必须要输出不同的对象,请将它们用管道输出到 Out-Host。每次您输出到 Out-Host,PowerShell 都将创建一个具有新的表格标题的输出。

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...}