PowerShell 技能连载 - 管理凭据(第四部分)

在前一个脚本中我们演示了如何以加密的方式将一个凭据保存到磁盘上。一个类似的方法只将密码保存到加密的文件中。这段代码将创建一个加密的密码文件:

1
2
3
# read in the password, and save it encrypted
$text = Read-Host -AsSecureString -Prompt 'Enter Password'
$text | Export-Clixml -Path "$home\desktop\mypassword.xml"

它只能由保存的人读取,而且必须在同一台机子上操作。第二个脚本可以用该密码登录其它系统而无需用户交互:

1
2
3
4
5
6
# read in the secret and encrypted password from file
$password = Import-Clixml -Path "$home\desktop\mypassword.xml"

# add the username and create a credential object
$username = 'yourCompany\yourUserName'
$credential = New-Object -TypeName PSCredential($username, $password)

凭据对象可以用在所有支持 -Credential 参数的 cmdlet 中。

1
2
3
# use the credential with any cmdlet that exposes the –Credential parameter
# to log in to remote systems
Get-WmiObject -Class Win32_LogicalDisk -ComputerName SomeServer -Credential $credential

PowerShell 技能连载 - 管理凭据(第三部分)

对于无人值守的脚本,以硬编码的方式将密码保存在脚本中是不安全且不推荐的。

有一种替代方法是,您可以一次性提示输入密码,然后创建一个凭据对象,然后在您的脚本中需要的地方使用它。这段代码提示输入一个密码,然后创建一个凭据对象:

1
2
3
$password = Read-Host -AsSecureString -Prompt 'Enter Password'
$username = 'myCompany\myUserName'
$credential = New-Object -TypeName PSCredential($username, $password)

凭据对象可以用在任何接受 -Credential 参数的 cmdlet 中。

1
2
3
# use the credential with any cmdlet that exposes the –Credential parameter
# to log in to remote systems
Get-WmiObject -Class Win32_LogicalDisk -ComputerName SomeServer -Credential $credential

PowerShell 技能连载 - 管理凭据(第二部分)

对于无人值守运行的脚本,您可以从代码创建登录凭据。这需要将密码以明文的方式存在脚本中(这显然是不安全的,除非您用加密文件系统(EFS)加密您的脚本,或是用其它办法来保护内容):

1
2
3
4
5
6
7
$password = 'topsecret!' | ConvertTo-SecureString -AsPlainText -Force
$username = 'myCompany\myUserName'
$credential = New-Object -TypeName PSCredential($username, $password)

# use the credential with any cmdlet that exposes the –Credential parameter
# to log in to remote systems
Get-WmiObject -Class Win32_LogicalDisk -ComputerName SomeServer -Credential $credential

PowerShell 技能连载 - 管理凭据(第一部分)

假设您每天都要运行一个需要凭据的脚本。一个使用强壮凭据的安全方法是将它们保存到一个加密的文件中。这段代码提示输入凭据,然后将它们保存到您桌面上的 XML 文件中:

1
2
$credential = Get-Credential -UserName train\user02 -Message 'Please provide credentials'
$credential | Export-Clixml -Path "$home\desktop\myCredentials.xml"

密码是以您的身份加密的,所以只有您(并且只能在保存凭据的机器上)能存取该凭据。

以下是读取保存的凭据的代码:

1
2
3
4
5
$credential = Import-Clixml -Path "$home\desktop\myCredentials.xml"

# use the credential with any cmdlet that exposes the –Credential parameter
# to log in to remote systems
Get-WmiObject -Class Win32_LogicalDisk -ComputerName SomeServer -Credential $credential

PowerShell 技能连载 - 解析纯文本(第三部分)

在前一个技能中我们演示了如何用 Select-String 在纯文本中查找指定的词。费了一些功夫通过指定的 pattern 来提取实际的值:

1
2
3
4
PS C:\> $data = ipconfig | select-string 'IPv4'
PS C:\> [regex]::Matches($data,"\b(?:\d{1,3}\.){3}\d{1,3}\b") | Select-Object -ExpandProperty Value

192.168.2.112

不过这些功夫并不是必要的,因为 Select-String 已经在使用正则表达式来做匹配,然后返回匹配的对象。

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
PS C:\> ipconfig |
Select-String '\b(?:\d{1,3}\.){3}\d{1,3}\b' |
Select-Object -Property *



IgnoreCase : True
LineNumber : 16
Line : IPv4 Address. . . . . . . . . . . : 192.168.2.112
Filename : InputStream
Path : InputStream
Pattern : \b(?:\d{1,3}\.){3}\d{1,3}\b
Context :
Matches : {192.168.2.112}

IgnoreCase : True
LineNumber : 17
Line : Subnet Mask . . . . . . . . . . . : 255.255.255.0
Filename : InputStream
Path : InputStream
Pattern : \b(?:\d{1,3}\.){3}\d{1,3}\b
Context :
Matches : {255.255.255.0}

IgnoreCase : True
LineNumber : 19
Line : 192.168.2.1
Filename : InputStream
Path : InputStream
Pattern : \b(?:\d{1,3}\.){3}\d{1,3}\b
Context :
Matches : {192.168.2.1}

所以您可以简单地使用 Where-Object 和类似 -like 的操作符来预过滤,识别出只包含感兴趣内容的行(例如只包含 “IPV4” 的行),然后将一个正则表达式 pattern 传给 Select-String,并计算最终结果:

1
2
3
4
5
6
7
8
9
10
11
PS C:\> ipconfig |
# do raw prefiltering and get only lines containing this word
Where-Object { $_ -like '*IPv4*' } |
# do RegEx filtering using a pattern for IPv4 addresses
Select-String '\b(?:\d{1,3}\.){3}\d{1,3}\b' |
# get the matching values
Select-Object -ExpandProperty Matches |
# get the value for each match
Select-Object -ExpandProperty Value

192.168.2.112

PowerShell 技能连载 - 解析纯文本(第二部分)

在前一个技能中我们解释了如何使用 Select-String 和正则表达式从纯文本结果中提取有用的信息:

1
2
3
4
PS C:\> $data = ipconfig | select-string 'IPv4'
PS C:\> [regex]::Matches($data,"\b(?:\d{1,3}\.){3}\d{1,3}\b") | Select-Object -ExpandProperty Value

192.168.2.112

PowerShell 支持 -match 参数,它也能够处理正则表达式。不过它在一行里只能找到一个匹配项。在多数场景中这也不是大问题,因为一行中通常只包含了一个匹配项。您所做的只需要在管道中使用 -match,则原始的数据会逐行地输入:

1
2
3
4
5
6
7
8
9
PS C:\> ipconfig |
# do raw filtering to only get lines with this word
Where-Object { $_ -like '*IPv4*' } |
# do RegEx filtering to identify the value matching the pattern
Where-Object { $_ -match '\b(?:\d{1,3}\.){3}\d{1,3}\b' } |
# return the results from -match which show in $matches
Foreach-Object { $matches[0] }

192.168.2.112

PowerShell 技能连载 - 解析纯文本(第一部分)

有些时候,您可能希望从纯文本结果中提取一些有用的信息。一个简单的办法是使用 Select-String 命令。这个例子只提取包含“IPv4”的文本行:

1
2
3
PS C:\> ipconfig | Select-String 'IPv4'

IPv4 Address. . . . . . . . . . . : 192.168.2.112

如果您只对实际的 IP 地址感兴趣,您可以改进这个结果,用正则表达式来提取您感兴趣的部分:

1
2
3
4
PS C:\> $data = ipconfig | select-string 'IPv4'
PS C:\> [regex]::Matches($data,"\b(?:\d{1,3}\.){3}\d{1,3}\b") | Select-Object -ExpandProperty Value

192.168.2.112

[Regex]::Matches() 输入原始数据和正则表达式 pattern,后者描述了您感兴趣的部分。符合该 pattern 的内容可以在“Value”属性中找到。

PowerShell 技能连载 - 调整简单界面

在前一个技能中您学到了如何使用 Show-Command 为基于文本的命令创建简单的 UI:

1
2
3
4
5
6
7
8
#requires -Version 3.0

function Send-MailMessageUI
{
Show-Command -Name Send-MailMessage
}

Send-MailMessageUI

如果您想调整 UI 中显示的参数的个数,只需要编写您自己的函数即可。

在下面的例子中,Send-MailMessage 被包裹在一个自定义的函数中,并只暴露其中的某些属性,然后在内部初始化其它的属性(例如 SMTP 服务器和凭据)。

以下是一个非常简单的 email 发送函数,只显示发送 email 的文本框:

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

function Send-MailMessageCustomized
{
param
(
[Parameter(Mandatory)]
[string]
$From,

[Parameter(Mandatory)]
[string]
$To,

[Parameter(Mandatory)]
[string]
$Subject,

[Parameter(Mandatory)]
[string]
$building,

[switch]
$BodyAsHTML
)
$username = 'mymailusername'
$password = 'mymailpassword' # Dangerous, never hardcode! Consider using Get-Credential instead.
$myServer = 'mail.mymailserver.mycompany.com'

$passwordSecure = $password | ConvertTo-SecureString -AsPlainText -Force
$myCred = New-Object -TypeName PSCredential($username, $passwordSecure)

Send-MailMessage -From $From -To $To -Subject $Subject -building $building -BodyAsHtml:$BodyAsHTML -SmtpServer $myServer -Encoding UTF8 -Credential $myCred
}

function Send-MailMessageUI
{
Show-Command -Name Send-MailMessageCustomized
}

Send-MailMessageUI

PowerShell 技能连载 - 创建简单的 UI

函数和 cmdlet 参数是 PowerShell 提供的基础功能,这些文本界面可以轻松地转换成图形界面。

如果您想发送一条消息,您可以使用 Send-MailMessage 并通过文本参数的方式提交细节信息。或者,您可以创建一个图形界面,并将它命名为 Send-MailMessageUI

1
2
3
4
5
6
7
8
#requires -Version 3.0

function Send-MailMessageUI
{
Show-Command -Name Send-MailMessage
}

Send-MailMessageUI

现在,您可以运行 Send-MailMessageUI,所有参数都将变成文本框和复选框。甚至不会脚本开发的人现在也能填写这个表单,然后点击“运行”来执行命令。

PowerShell 技能连载 - 扩展 Robocopy

PowerShell 可以向原有的命令(例如 robocopy)添加值。请看以下的函数——它用 robocopy 来拷贝文件,并且当拷贝完成时,增加了“扁平拷贝”选项来打开目标文件夹:

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

function Copy-FileWithRobocopy
{
param
(
[Parameter(Mandatory)]
[string]$Source,

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

[string]$Filter = '*',

[int]$RetryCount = 0,

[string]$ExcludeDirectory = '',

[switch]$Open,

[switch]$FlatCopy,

[switch]$NoRecurse
)

$Recurse = '/S'
if ($NoRecurse) { $Recurse = '' }

robocopy.exe $Source $Destination $Filter /R:$RetryCount $Recurse /XD $ExcludeDirectory

if ($FlatCopy)
{
Get-ChildItem -Path $Destination -Recurse -Filter $Filter |
Move-Item -Destination $Destination -Force
Get-ChildItem -Path $Destination -Directory |
Remove-Item -Recurse -Force
}

if ($Open)
{
explorer $Destination
}
}

这将会把 Windows 文件夹下所有子文件夹中的 log 文件拷贝到名为 c:\\logs 的新文件夹,并且执行扁平化拷贝:

1
PS>  Copy-FileWithRobocopy -Source $env:windir -Destination c:\logs -Filter *.log -FlatCopy -Open

当您在生产系统使用这段代码之前,请观察 -FlatCopy 是如何工作的:它只是在目标文件夹中查找匹配指定的过滤器,然后将它们移到根目录,最后删除所有文件夹。

所以重复的文件将会被覆盖,而且如果目标文件夹的子文件夹中有其他数据,也会被删除。这是一个很简单的操作,适用于许多情况,但也有很多改进空间。