PowerShell 技能连载 - 用口令对文本信息加密

适用于 PowerShell 3.0 及以上版本

在前一个技能中,我们介绍了如何使用 Windows 注册表中的 Windows 产品序列号来加密文本信息。

如果您觉得这种方式不够安全,那么可以使用自己指定的密钥来加密。以下例子演示了如何使用密码作为加密密钥:

$Path = "$env:temp\secret.txt"
$Secret = 'Hello World!'
$Passphrase = 'Some secret key'

$key = [Byte[]]($Passphrase.PadRight(24).Substring(0,24).ToCharArray())

$Secret |
  ConvertTo-SecureString -AsPlainText -Force |
  ConvertFrom-SecureString -Key $key |
  Out-File -FilePath $Path

notepad $Path

要解密一段密文,您需要知道对应的密码:

$Passphrase = Read-Host 'Enter the secret pass phrase'

$Path = "$env:temp\secret.txt"

$key = [Byte[]]($Passphrase.PadRight(24).Substring(0,24).ToCharArray())

try
{
  $decryptedTextSecureString = Get-Content -Path $Path -Raw |
  ConvertTo-SecureString -Key $key -ErrorAction Stop

  $cred = New-Object -TypeName System.Management.Automation.PSCredential('dummy', $decryptedTextSecureString)
  $decryptedText = $cred.GetNetworkCredential().Password
}
catch
{
  $decryptedText = '(wrong key)'
}
"The decrypted secret text: $decryptedText"

PowerShell 技能连载 - 用 Windows 加密信息

适用于 PowerShell 3.0 及以上版本

要存储机密信息,您可以使用 SecureString 对象将其保存到磁盘上。PowerShell 自动使用用户账户作为密钥,所以只有保存该信息的用户可以读取它。

如果您希望该机密信息不绑定到特定的用户,而是绑定到某台机器,您可以使用 Windows 产品序列号作为密钥。请注意这并不是特别安全,因为密钥在 Windows 注册表中时公开可见的。它还有个使用前提是 Windows 是使用合法产品序列号安装的。

以下这段代码接受任意文本信息,然后用 Windows 产品序列号对它进行加密并保存到磁盘上:

$Path = "$env:temp\secret.txt"
$Secret = 'Hello World!'

$regKey = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name DigitalProductID
$encryptionKey = $regKey.DigitalProductID

$Secret |
  ConvertTo-SecureString -AsPlainText -Force |
  ConvertFrom-SecureString -Key ($encryptionKey[0..23]) |
  Out-File -FilePath $Path

notepad $Path

这是对加密的文本进行解密的代码:

$Path = "$env:temp\secret.txt"

$regKey = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name DigitalProductID
$encryptionKey = $regKey.DigitalProductID

$decryptedTextSecureString = Get-Content -Path $Path -Raw |
  ConvertTo-SecureString -Key ($secureKey[0..23])

$cred = New-Object -TypeName System.Management.Automation.PSCredential('dummy', $decryptedTextSecureString)
$decryptedText = $cred.GetNetworkCredential().Password

"The decrypted secret text: $decryptedText"

请注意如何 PSCredential 对象来对密文进行解密并还原出明文的。

PowerShell 技能连载 - 查找 Exchange 邮箱

适用于 Microsoft Exchange 2013

要查看邮箱的个数,只需要使用 Exchange cmdlet 并且用 Measure-Object 来统计结果:

Get-Mailbox –ResultSize Unlimited |
  Measure-Object |
  Select-Object -ExpandProperty Count

类似地,要查看所有共享的邮箱,使用这段代码:

Get-Mailbox –ResultSize Unlimited -RecipientTypeDetails SharedMailbox |
  Measure-Object |
  Select-Object -ExpandProperty Count

若要只查看用户邮箱,需要稍微调整一下:

Get-Mailbox –ResultSize Unlimited -RecipientTypeDetails UserMailbox |
  Measure-Object |
  Select-Object -ExpandProperty Count

PowerShell 技能连载 - 智能参数验证

适用于 PowerShell 2.0 及以上版本

当您用 PowerShell 创建带参数的函数时,请明确地告知 PowerShell 该参数的类型。

这是一个简单的例子,您需要输入一个星期数:

function Get-Weekday
{
  param
  (
    $Weekday
  )

  "You chose $Weekday"
}

用户可以传入任何东西,不仅是正常的星期数:

PS> Get-Weekday -Weekday NoWeekday
You chose NoWeekday

有些时候,您可能会看到用正则表达式实现的验证器:

function Get-Weekday
{
  param
  (
    [ValidatePattern('Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday')]
    $Weekday
  )

  "You chose $Weekday"
}

现在,用户的输入被限定在了这些模式中,如果输入的值不符合正则表达式的模式,PowerShell 将会抛出一个异常。然而,错误信息并不是很有用,并且用户输入的时候并不能享受到智能提示的便利。

一个更好的方法是使用验证集合:

function Get-Weekday
{
  param
  (
    [ValidateSet('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')]
    $Weekday
  )

  "You chose $Weekday"
}

现在,用户只能输入您允许的值,并且用户在 PowerShell ISE 中输入的时候会获得智能提示信息,显示允许输入的值。

如果您了解您期望值对应的 .NET 的枚举类型,那么可以更简单地将该类型绑定到参数上:

function Get-Weekday
{
  param
  (
    [System.DayOfWeek]
    $Weekday
  )

  "You chose $Weekday"
}

PowerShell 技能连载 - 发现高影响级别 cmdlet

适用于 PowerShell 所有版本

cmdlet 可以定义它们的影响力有多大。通常,那些会对系统造成不可恢复影响的 cmdlet 的“影响级别”设置为“高”。

当您运行这样一个 cmdlet 时,PowerShell 将会弹出一个确认对话框,防止您不小心误操作。确认对话框也能防止您在无人值守的情况下运行这些 cmdlet。

要查看 cmdlet 的“影响级别”为多高,可以用这段代码输出该信息:

Get-Command -CommandType Cmdlet |
  ForEach-Object {
    $type = $_.ImplementingType
    if ($type -ne $null)
    {
      $type.GetCustomAttributes($true) |
      Where-Object { $_.VerbName -ne $null } |
      Select-Object @{Name='Name';
      Expression={'{0}-{1}' -f $_.VerbName, $_.NounName}}, ConfirmImpact
    }
  } |
  Sort-Object ConfirmImpact -Descending

要只查看影响级别为“高”的 cmdlet,只需要加一个过滤器:

Get-Command -CommandType Cmdlet |
  ForEach-Object {
    $type = $_.ImplementingType
    if ($type -ne $null)
    {
      $type.GetCustomAttributes($true) |
      Where-Object { $_.VerbName -ne $null } |
      Select-Object @{Name='Name';
      Expression={'{0}-{1}' -f $_.VerbName, $_.NounName}}, ConfirmImpact
    }
  } |
  Sort-Object ConfirmImpact -Descending |
  Where-Object { $_.ConfirmImpact -eq 'High' }

要以无人值守的方式运行这些 cmdlet 并且不让自动提示框出现,请加上 -Confirm:$False 参数。

PowerShell 技能连载 - ISE 自动完成技巧

适用于 PowerShell 3.0 ISE 及以上版本

当您希望选择某个 cmdlet 返回的信息时,通常使用的是 Select-Object

Get-Process | Select-Object -Property Name, Company, Description

然而,您需要手工键入希望显示的属性名。当您键入“-Property”之后并不会弹出智能提示。

一个不为人知的技巧是按下 CTRL+SPACE 来手动显示智能提示菜单。如您所见,PowerShell 会开心地提供所有属性的列表,前提是上一级的 cmdlet 定义了输出的类型。

要选择多余一个属性,在键入逗号之后,再次按下 CTRL+SPACE 即可。

PowerShell 技能连载 - 访问非 Microsoft LDAP 服务

适用于 PowerShell 所有版本

Microsoft 和 Dell 提供了一些 Active Directory 的免费 cmdlet,分别是 RSAT 工具和 Quest 的一部分。它们使访问域控制器变得更简单。

要访问一个非 Microsoft 的 LDAP 服务器,由于没有现成的 cmdlet,所以可以使用 .NET 框架的功能。

以下是一些示例代码,演示了如何连接这种 LDAP 服务器,提交 LDAP 查询,并且获取信息。

该脚本假设 LDAP 服务器架设在 192.168.1.1 的 389 端口,是“mycompany.com”域的一部分,有一个名为“SomeGroup”的工作组。该脚本列出该工作组的用户账户:

$LDAPDirectoryService = '192.168.1.1:389'
$DomainDN = 'dc=mycompany,dc=com'
$LDAPFilter = '(&(cn=SomeGroup))'


$null = [System.Reflection.Assembly]::LoadWithPartialName('System.DirectoryServices.Protocols')
$null = [System.Reflection.Assembly]::LoadWithPartialName('System.Net')
$LDAPServer = New-Object System.DirectoryServices.Protocols.LdapConnection $LDAPDirectoryService
$LDAPServer.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous

$LDAPServer.SessionOptions.ProtocolVersion = 3
$LDAPServer.SessionOptions.SecureSocketLayer =$false

$Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
$AttributeList = @('*')

$SearchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $DomainDN,$LDAPFilter,$Scope,$AttributeList

$groups = $LDAPServer.SendRequest($SearchRequest)

foreach ($group in $groups.Entries)
{
  $users=$group.attributes['memberUid'].GetValues('string')
  foreach ($user in $users) {
    Write-Host $user
  }
}

PowerShell 技能连载 - 查找只读型和常量型变量

适用于 PowerShell 所有版本

有些变量是受保护且不可改变的。要查这些变量,请看如下代码:

Get-Variable |
  Where-Object { $_.Options -like '*Constant*' -or $_.Options -like '*ReadOnly*' } |
  Select-Object -Property Name, Options, Description

执行的结果类似这样:

Name                                           Options Description
----                                           ------- -----------
?                                   ReadOnly, AllScope Status des letzten Befehls
ConsoleFileName                     ReadOnly, AllScope Name der aktuellen Kons...
Error                                         Constant
ExecutionContext                    Constant, AllScope Die für Cmdlets verfügb...
false                               Constant, AllScope Boolean False
HOME                                ReadOnly, AllScope Ordner mit dem Profil d...
Host                                Constant, AllScope Ein Verweis auf den Hos...
PID                                 Constant, AllScope Aktuelle Prozess-ID
PSCulture                           ReadOnly, AllScope Die Kultur der aktuelle...
PSHOME                              Constant, AllScope Der übergeordnete Ordne...
psISE                                         Constant
PSUICulture                         ReadOnly, AllScope Die Benutzeroberflächen...
psUnsupportedConsoleAppl...                   Constant
PSVersionTable                      Constant, AllScope Versionsinformationen f...
ShellId                             Constant, AllScope "ShellID" gibt die aktu...
true                                Constant, AllScope Boolean True

有意思的地方是如何用 Where-Object 来过滤这些变量。这段代码使用了字符串对比和 -like。这是因为变量选项是各种标志,并且标志可以组合使用。通过使用 -like 和占位符,您可以基本安全地用您希望的标志来过滤,即便设置了其它标志。

PowerShell 技能连载 - 只读及强类型变量

适用于 PowerShell 所有版本

要让 PowerShell 脚本更鲁棒,您可以针对脚本变量编写一系列约束条件。

这么做的效果是,PowerShell 将会为您监控这些约束条件,并且如果某些条件不满足,将抛出错误。

第一个约束条件是传入的数据类型必须和期望的相符。将数据类型放在方括号中,然后将它放在变量前面。这将会使一个通用类型的变量转换为一个强类型的变量。

PS> $profile.AllUsersAllHosts
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

PS> [int]$ID = 12

PS> $ID = 17

PS> $ID = '19'

PS> $ID = 'wrong'
Cannot convert type "string"...

请看 $ID 变量是如何成为一个只能存储 Integer 数据的变量。望您将一个非 Integer 值,(例如 ‘19’)传入时,它将自动转换成 Integer 类型。如果无法转换(例如 ‘wrong’),PowerShell 将会抛出一个错误。

下一个约束条件是“只读”状态。如果您确信某个变量在脚本的某一部分中不可被更改,请将它保住为只读。任何试图改变该变量的操作都会触发一个 PowerShell 异常:

PS> $a = 1

PS> $a = 100

PS> Set-Variable a -Option ReadOnly

PS> $a
100

PS> $a = 200
Cannot  overwrite variable.

PS> Set-Variable a -Option None -Force

PS> $a = 212

请注意写保护如何打开和关闭。要将写保护关闭,只需要将变量开关设为“None”,并且别忘了 -Force 开关。

如您所见,“ReadOnly”选项是一个软保护开关。您可以控制它开和关。在前一个技能中,您学到了“Constant”选项。Constant 的意思如它们的名字:常量。和“ReadOnly”不同,常量无法变回可写状态。