PowerShell 技能连载 - 批量重命名文件

假设您有一整个文件夹的图片文件,并希望它们的名字标准化。

这个脚本演示了如何批量重命名图片文件:

$i = 0

Get-ChildItem -Path c:\pictures -Filter *.jpg |
ForEach-Object {
    $extension = $_.Extension
    $newName = 'pic_{0:d6}{1}' -f  $i, $extension
    $i++
    Rename-Item -Path $_.FullName -NewName $newName
}

文件夹中所有的 JPG 文件都被重命名了。新的文件名是“pic_”加上四位数字。

您可以很容易地修改脚本来重命名其它类型的文件,或是使用其它文件名模板。

PowerShell 技能连载 - 对密码加密

如果您确实需要在脚本中保存一个凭据对象,以下是将一个安全字符串转换为加密文本的方法:

$password = Read-Host -Prompt 'Enter Password' -AsSecureString
$encrypted = $password | ConvertFrom-SecureString
$encrypted | clip.exe
$encrypted

当运行这段代码时,会要求您输入密码。接下来密码会被转换为一系列字符并存入剪贴板中。加密的密钥是您的身份标识加上您的机器标识,所以只能用相同机器的相同用户对密码解密。

下一步,用这段代码可以将您的密码密文转换为凭据对象:

$secret = '01000000d08c9ddf0115d1118c7a00c04fc297eb01000000d4a6c6bfcbbb75418de6e9672d85e73600...996f8365c8c82ea61f94927d3e3b14000000c6aecec683717376f0fb18519f326f6ac9cd89dc'
$username = 'test\user'

$password = $secret | ConvertTo-SecureString

$credential = New-Object -TypeName System.Management.Automation.PSCredential($username, $password)

# example call
Start-Process notepad -Credential $credential -WorkingDirectory c:\

将加密的密码字符串写入脚本中,然后使用指定的用户名来验证身份。

现在,$cred 中保存的凭据对象可以在任何支持 -Credential 参数的 cmdlet 或函数中使用了。

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