PowerShell 技能连载 - 管理本地用户

PowerShell 5.1 终于发布了管理本地用户账户的 cmdlet。要获取本地用户账户的列表,请使用 Get-LocalUser 并将结果通过管道传给 Select-Object 命令来查看所有属性:

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
PS C:\> Get-LocalUser | Select-Object -Property *


AccountExpires :
Description : Predefined Account to manage computer or domain
Enabled : False
FullName :
PasswordChangeableDate :
PasswordExpires :
UserMayChangePassword : True
PasswordRequired : True
PasswordLastSet : 7/10/2015 2:22:01 PM
LastLogon : 12/8/2015 5:44:47 AM
Name : Administrator
SID : S-1-5-21-2012478179-265285931-690539891-500
PrincipalSource : Local
ObjectClass : User

AccountExpires :
Description : User Account managed by system
Enabled : False
FullName :
PasswordChangeableDate :
PasswordExpires :
UserMayChangePassword : True
PasswordRequired : False
PasswordLastSet :
LastLogon :
Name : DefaultAccount
SID : S-1-5-21-2012478179-265285931-690539891-503
PrincipalSource : Local
ObjectClass : User

AccountExpires :
Description : Predefined Account for Guest access
Enabled : False
FullName :
PasswordChangeableDate :
PasswordExpires :
UserMayChangePassword : False
PasswordRequired : False
PasswordLastSet :
LastLogon :
Name : Guest
SID : S-1-5-21-2012478179-265285931-690539891-501
PrincipalSource : Local
ObjectClass : User
...

PowerShell 技能连载 - 获取 AD 用户属性

缺省情况下,Get-ADUser(由 ActiveDirectory 模块提供,该模块是免费的 Microsoft RSAT 工具的一部分)只获取一小部分缺省属性。要获取更多信息,请使用 -Properties 参数,并且指定您需要获取的属性。

要获取所有 AD 用户的列表,以及他们的备注和描述字段,请使用这段代码:

#requires -Modules ActiveDirectory
Get-ADUser -Filter * -Properties Description, Info

如果你不知道所有可用属性的名字,请使用“*”代替,来获取所有可用的属性。

PowerShell 技能连载 - 小心 Get-Credential 和 SecureString

有些时候,脚本以交互的方式询问凭据或密码。请时刻注意脚本的作者可以获取所有输入信息的明文。仅当您信任脚本和作者的时候才可以输入敏感信息。

请注意:这并不是一个 PowerShell 问题,这是所有软件的共同问题。

让我们看看一个脚本如何利用输入的密码。如果一个脚本需要完整的凭据,它可以检查凭据对象并解出密码明文:

1
2
3
4
$credential = Get-Credential
$password = $credential.GetNetworkCredential().Password

"The password entered was: $password"

类似地,当提示您输入密码作为安全字符串时,脚本的作者也能获取到输入的明文:

1
2
3
4
5
6
7
$password = Read-Host -AsSecureString -Prompt 'Enter Password'

# this is how the owner of a secure string can get back the plain text:
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
$plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

"The password entered was $plaintext"

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

当 PowerShell 自动加密一个安全字符串时,它使用您的身份作为密钥。只有您可以解密该安全字符串。而如果您想用一个共享的密码来加密一段安全字符串,会怎么样呢?

以下是一个经典的做法,用密码来加密:

1
2
3
4
5
6
7
8
9
# $secretKey MUST be of length 8 or 16
# anyone who knows the secret can decrypt the password
$secretKey = 'mysecretmysecret'
$password = 'myPassword'

$SecureString = ConvertTo-SecureString -String $password -AsPlainText -Force
$RandomSecureString = ConvertTo-SecureString -String $secretKey -AsPlainText -Force
$encryptedPassword = ConvertFrom-SecureString -SecureString $SecureString -SecureKey $RandomSecureString
$encryptedPassword

该密钥 ($secretKey) 必须是 8 或 16 个字符的文本。任何知道密钥的人都可以解密(这就是为何 secretKey 不应该是脚本的一部分,而下面的脚本下方加入了硬编码的密钥是为了演示解密的过程。最好以交互的方式输入密钥):

1
2
3
4
5
6
7
8
9
# this is the key to your secret
$secretKey = 'mysecretmysecret'
# this is the encrypted secret as produced by the former code
$encryptedPassword = '76492d1116743f0423413b16050a5345MgB8AEMARQAxAFgAdwBmAHcARQBvAGUAKwBOAGoAYgBzAE4AUgBnAHoARABSAHcAPQA9AHwANQA3ADYAMABjAGYAYQAwAGMANgBkADQAYQBiADYAOAAyAGYAZAA5AGYAMwA5AGYAYQBjADcANQA5ADIAYwAzADkAMAA2ADQANwA1ADcAMQA3ADMAMwBmAGMAMwBlADIAZQBjADcANgAzAGQAYQA1AGIAZABjADYAMgA2AGQANAA='

$RandomSecureString = ConvertTo-SecureString -String $secretKey -AsPlainText -Force
$securestring = $encryptedPassword | ConvertTo-SecureString -SecureKey $RandomSecureString
# Result is a secure string
$SecureString

结果是一个安全字符串,您可以用它来构建一个完整的凭据:

1
2
$credential = New-Object -TypeName PSCredential('yourcompany\youruser', $SecureString)
$credential

您也可以再次检查密码明文:

PS C:\Users\tobwe> $credential.GetNetworkCredential().Password
myPassword

请注意安全字符串的所有者(创建它的人)总是可以取回明文形式的密码。这并不是一个安全问题。创建安全字符串的人在过去的时刻已经知道了密码。安全字符串保护第三方的敏感数据,并将它以其他用户无法接触到的形式保存到内存中。

密钥和对称加密算法的问题是您需要分发密钥,而密钥需要被保护,它既可以用来加密也可以用来解密。

在 PowerShell 5 中有一个简单得多的方法:Protect-CMSMessageUnprotect-CMSMessage,它们使用数字证书和非对称加密。通过这种方法,加密安全信息的一方无需知道解密的密钥,反之亦然。加密的一方只需要制定谁(哪个证书)可用来解密保密信息。

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