PowerShell 技能连载 - 扫描端口

以下是一个扫描本地或远程系统端口的一个直接的方法。您甚至可以指定一个超时值(以毫秒为单位):

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
function Get-PortInfo
{
param
(
[Parameter(Mandatory)]
[Int]
$Port,

[Parameter(Mandatory)]
[Int]
$TimeoutMilliseconds,

[String]
$ComputerName = $env:COMPUTERNAME
)

# try and establish a connection to port async
$tcpobject = New-Object System.Net.Sockets.TcpClient
$connect = $tcpobject.BeginConnect($computername,$port,$null,$null)

# wait for the connection no longer than $timeoutMilliseconds
$wait = $connect.AsyncWaitHandle.WaitOne($timeoutMilliseconds,$false)

# return rich information
$result = @{
ComputerName = $ComputerName
}

if(!$wait) {
# timeout
$tcpobject.Close()
$result.Online = $false
$result.Error = 'Timeout'
} else {
try {
# try and complete the connection
$null = $tcpobject.EndConnect($connect)
$result.Online = $true
}
catch {
$result.Online = $false
}
$tcpobject.Close()
}
$tcpobject.Dispose()

[PSCustomObject]$result
}

扫描端口现在变得十分简单:

1
2
3
4
5
6
7
8
9
10
11
12
PS> Get-PortInfo -Port 139 -TimeoutMilliseconds 1000

ComputerName Online
------------ ------
DESKTOP-7AAMJLF True


PS> Get-PortInfo -Port 139 -TimeoutMilliseconds 1000 -ComputerName storage2

Error ComputerName Online
----- ------------ ------
Timeout storage2 False

PowerShell 技能连载 - 重制控制台颜色

在 PowerShell 控制台里很容易把控制台颜色搞乱。一个不经意的调用意外地改变了颜色值,或者脚本错误地设置了颜色,可能会导致意外的结果。要验证这种情况,请打开一个 PowerShell 控制台(不是编辑器!),并且运行这段代码:

1
PS> [Console]::BackgroundColor = "Green"

要快速地清除颜色,请运行这段代码:

1
PS> [Console]::ResetColor()

接着运行 Clear-Host 可以清空显示。

PowerShell 技能连载 - 解决 SSL 连接问题

有些时候当您尝试连接 WEB 服务时,PowerShell 可能会弹出无法设置安全的 SSL 通道提示。

让我们来深入了解一下这个问题。以下是一段调用德国铁路系统 WEB 服务的代码。它应该返回指定城市的火车站列表:

1
2
3
4
5
6
7
8
9
$city = "Hannover"
$url = "https://rit.bahn.de/webservices/rvabuchung?WSDL"

$object = New-WebServiceProxy -Uri $url -Class db -Namespace test

$request = [test.SucheBahnhoefeAnfrage]::new()
$request.bahnhofsname = $city
$response = $object.sucheBahnhoefe($request)
$response.bahnhoefe

不过,它弹出了一系列关于 SSL 连接问题的异常。通常,这些错误源于两种情况:

  • 该 WEB 网站用于确立 SSL 连接的 SSL 证书非法、过期,或不被信任。
  • 该 SSL 连接需要某种协议,而该协议没有启用。

要解决这些情况,只需要运行以下代码:

1
2
3
4
5
6
# ignore invalid SSL certificates
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

# allowing SSL protocols
$AllProtocols = [Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[Net.ServicePointManager]::SecurityProtocol = $AllProtocols

只要执行一次,对当前的 PowerShell 会话都有效。以上代码现在能够正常返回 Hannover 地区的车站信息。而且如果深入地了解该对象的模型,您甚至可以通过 PowerShell 订火车票。

name                  nummer
----                  ------
Hannover Hbf         8000152
Hannover-Linden/Fi.  8002586
Hannover-Nordstadt   8002576
Hannover Bismarckstr 8002580
Hannover-Ledeburg    8002583
Hannover-Vinnhorst   8006089
Hannover-Leinhausen  8002585
Hannover Wiech-Allee 8002591
Hannover Ander.Misb. 8000578
Hannover Flughafen   8002589
Hannover-Kleefeld    8002584
Hannover-Bornum      8002581
Hann Münden          8006707
HannoverMesseLaatzen 8003487

PowerShell 技能连载 - 删除日期最早的日志文件

如果您正在将日志活动写入文件,可能需要清除一些东西,例如在增加一个新文件的时候总是需要删除最旧的日志文件。

以下是一个简单的实现:

1
2
3
4
5
6
7
8
9
10
11
12
# this is the folder keeping the log files
$LogFileDir = "c:\myLogFiles"

# find all log files...
Get-ChildItem -Path $LogFileDir -Filter *.log |
# sort by last change ascending
# (oldest first)...
Sort-Object -Property LastWriteTime |
# take the first (oldest) one
Select-Object -First 1 |
# remove it (remove -whatif to actually delete)
Remove-Item -WhatIf

如果只希望保留最新的 5 个文件,请像这样更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# this is the folder keeping the log files
$LogFileDir = "c:\myLogFiles"
$Keep = 5

# find all log files...
$files = @(Get-ChildItem -Path $LogFileDir -Filter *.log)
$NumberToDelete = $files.Count - $Keep

if ($NumberToDelete -gt 0)
{
$files |
# sort by last change ascending
# (oldest first)...
Sort-Object -Property LastWriteTime |
# take the first (oldest) one
Select-Object -First $NumberToDelete |
# remove it (remove -whatif to actually delete)
Remove-Item -WhatIf
}

PowerShell 技能连载 - 用 ForEach 实现实时流

传统的 foreach 循环是最快速的循环方式,但是它有一个严重的限制。foreach 循环不支持管道。用户只能等待整个 foreach 循环结束才能处理结果。

以下是一些演示这个情况的示例。在以下代码中,您需要等待一段很长的时间才能“看见”执行结果:

1
2
3
4
5
6
7
8
$result = foreach ($item in $elements)
{
"processing $item"
# simulate some work and delay
Start-Sleep -Milliseconds 50
}

$result | Out-GridView

您无法直接通过管道输出结果。以下代码会产生语法错误:

1
2
3
4
5
6
7
8
$elements = 1..100

Foreach ($item in $elements)
{
"processing $item"
# simulate some work and delay
Start-Sleep -Milliseconds 50
} | Out-GridView

可以使用 $() 语法来使用管道,但是仍然要等待循环结束并且将整个结果作为一个整体发送到管道:

1
2
3
4
5
6
7
8
$elements = 1..100

$(foreach ($item in $elements)
{
"processing $item"
# simulate some work and delay
Start-Sleep -Milliseconds 50
}) | Out-GridView

一下是一个鲜为人知的技巧,向 foreach 循环增加实时流功能:只需要使用一个脚本块!

1
2
3
4
5
6
7
8
$elements = 1..100

& { foreach ($item in $elements)
{
"processing $item"
# simulate some work and delay
Start-Sleep -Milliseconds 50
}} | Out-GridView

现在您可以“看到”它们处理的结果,并且享受实时流的效果。

让然,您可以一开始就不使用 foreach,而是使用 ForEach-Object 管道 cmdlet 来代替:

1
2
3
4
5
6
7
8
9
$elements = 1..100

$elements | ForEach-Object {
$item = $_

"processing $item"
# simulate some work and delay
Start-Sleep -Milliseconds 50
} | Out-GridView

但是,ForEach-Objectforeach 关键字慢得多,并且有些场景无法使用 ForEach-Object。例如,在许多数据库代码中,代码需要一次次地检测结束标记,所以无法使用 ForEach-Object

PowerShell 技能连载 - 列出网络驱动器

有着许多方法可以创建网络驱动器的列表。其中一个需要调用 COM 接口。这个接口也可以通过 VBScript 调用。我们将利用它演示一个特殊的 PowerShell 技术。

要列出所有网络驱动器,只需要运行这几行代码:

1
2
$obj = New-Object -ComObject WScript.Network
$obj.EnumNetworkDrives()

结果类似这样:

1
2
3
4
5
6
PS> $obj.EnumNetworkDrives()

X:
\\storage4\data
Z:
\\127.0.0.1\c$

这个方法对每个网络驱动器返回两个字符串:挂载的驱动器号,以及原始 URL。要将它转为有用的东西,您需要创建一个循环,每次迭代返回两个元素。

以下是一个聪明的方法来实现这个目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$obj = New-Object -ComObject WScript.Network
$result = $obj.EnumNetworkDrives()

Foreach ($entry in $result)
{
$letter = $entry
$null = $foreach.MoveNext()
$path = $foreach.Current


[PSCustomObject]@{
DriveLetter = $letter
UNCPath = $path
}
}

foreach 循环中,有一个很少人知道的自动变量,名为 $foreach,它控制着迭代。当您调用 MoveNext() 方法时,它对整个集合迭代,移动到下一个元素。通过 Current 属性,可以读取到迭代器的当前值。

通过这种方法,循环每次处理两个元素,而不仅仅是一个。两个元素合并为一个自定义对象。结果看起来类似这样:

DriveLetter UNCPath
----------- -------
X:          \\storage4\data
Z:          \\127.0.0.1\c$

PowerShell 技能连载 - 通过 Outlook 发送邮件

您可以用 Send-MailMessage 通过任何 SMTP 服务器放松右键。不过,如果您希望使用 Outlook 客户端,例如要访问地址簿、使用公司的 Exchange 服务器,或者将邮件保存到邮件历史中,那么可以通过这个快速方法来实现:

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
function Send-OutlookMail
{
param
(
[Parameter(Mandatory=$true)]
$To,

[Parameter(Mandatory=$true)]
$Subject,

[Parameter(Mandatory=$true)]
$BodyText,

[Parameter(Mandatory=$true)]
$AttachmentPath
)


$outlook = New-Object -ComObject Outlook.Application
$mail = $outlook.CreateItem(0)
$mail.Subject = $Subject
$mail.to = $To

$mail.BodyFormat = 1 # use 2 for HTML mails
$mail.Attachments.Add([object]$AttachmentPath, 1)
$mail.building = $BodyText
$mail.Display($false)
}

这只是一个基本的模板,您可以投入一些时间使它变得更好。例如,当前版本总是需要一个附件。在更复杂的版本中,您可以使附件变成可选的,并且支持多个附件。或者寻找一种方法发送邮件而不需要用户交互。

PowerShell 技能连载 - 首字母大写

修饰一段文本并不太简单,如果您希望名字或者文本格式正确,并且每个单词都以大写开头,那么工作量通常很大。

有趣的是,每个 CultureInfo 对象有一个内置的 ToTitleCase() 方法,可以完成上述工作。如果您曾经将纯文本转换为全部消协,那么它也可以处理所有大写的单词:

1
2
3
4
5
$text = "here is some TEXT that I would like to title-case (all words start with an uppercase letter)"

$textInfo = (Get-Culture).TextInfo
$textInfo.ToTitleCase($text)
$textInfo.ToTitleCase($text.ToLower())

以下是执行结果:

Here Is Some TEXT That I Would Like To Title-Case (All Words Start With An Upper Letter
Here Is Some Text That I Would Like To Title-Case (All Words Start With An Upper Letter

This method may be especially useful for list of names.
这个方法对于姓名列表很有用。

PowerShell 技能连载 - 连接文本文件

假设一个脚本已经向某个文件夹写入了多个日志文件,所有文件名都为 *.log。您可能希望将它们合并为一个大文件。以下是一个简单的实践:

1
2
3
4
5
6
$OutPath = "$env:temp\summary.log"

Get-Content -Path "C:\Users\tobwe\Documents\ScriptOutput\*.log" |
Set-Content $OutPath

Invoke-Item -Path $OutPath

然而,这个方法并不能提供充分的控制权:所有文件需要放置在同一个文件夹中,并且必须有相同的文件扩展名,而且您无法控制它们合并的顺序。

一个更多功能的方法类似这样:

1
2
3
4
5
6
7
8
$OutPath = "$env:temp\summary.log"

Get-ChildItem -Path "C:\Users\demouser\Documents\Scripts\*.log" -Recurse -File |
Sort-Object -Property LastWriteTime -Descending |
Get-Content |
Set-Content $OutPath

Invoke-Item -Path $OutPath

它利用了 Get-ChildItem 的灵活性,而且可以在读取内容之前对文件排序。通过这种方法,日志保持了顺序,并且最终的日志信息总是在日志文件的最上部。