PowerShell 技能连载 - 查找 Active Directory 用户账号

有很多用于 Active Directory 的 Module 和 Cmdlet,但是有些时候用 .NET 代码来做反而更方便快捷。

比如说,如果您只是想知道,某个用户是否存在于您的 Active Directory中,那么实现查找一个用户是很容易的:

# sending LDAP query to Active Directory
$searcher = [ADSISearcher]'(&(objectClass=User)(objectCategory=person)(SamAccountName=tobias*))'
# finding first match
$searcher.FindOne()
# finding ALL matches
$searcher.FindAll()

这段代码将查找所有 SamAccountName 以 “tobias” 开头的用户账号。您可以接着用这个方法来便捷地找出这个用户所在的位置:

# find account location
$searcher.FindAll() | Select-Object -ExpandProperty Path

PowerShell 多行输入对话框、打开文件对话框、文件夹浏览对话框、输入框及消息框

我热爱 PowerShell,当我需要提示用户输入时,我常常更喜欢使用 GUI 控件,而不是让他们什么都往控制台里输入有些东西比如说浏览文件或文件夹,或输入多行文本,不是很方便直接往 PowerShell 的命令行窗口中输入。所以我想我可以分享一些我常常用于这些场景的 PowerShell 脚本。

您可以从这儿下载包含以下所有函数的脚本。

显示消息框:

# Show message box popup and return the button clicked by the user.
Add-Type -AssemblyName System.Windows.Forms
function Read-MessageBoxDialog([string]$Message, [string]$WindowTitle, [System.Windows.Forms.MessageBoxButtons]$Buttons = [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::None)
{
    return [System.Windows.Forms.MessageBox]::Show($Message, $WindowTitle, $Buttons, $Icon)
}

提示简易的用户输入(单行):

# Show input box popup and return the value entered by the user.
function Read-InputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText)
{
    Add-Type -AssemblyName Microsoft.VisualBasic
    return [Microsoft.VisualBasic.Interaction]::InputBox($Message, $WindowTitle, $DefaultText)
}

提示输入一个文件路径(基于脚本小子的一篇文章):

# Show an Open File Dialog and return the file selected by the user.
function Read-OpenFileDialog([string]$WindowTitle, [string]$InitialDirectory, [string]$Filter = "All files (*.*)|*.*", [switch]$AllowMultiSelect)
{
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Title = $WindowTitle
    if (![string]::IsNullOrWhiteSpace($InitialDirectory)) { $openFileDialog.InitialDirectory = $InitialDirectory }
    $openFileDialog.Filter = $Filter
    if ($AllowMultiSelect) { $openFileDialog.MultiSelect = $true }
    $openFileDialog.ShowHelp = $true    # Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE.
    $openFileDialog.ShowDialog() > $null
    if ($AllowMultiSelect) { return $openFileDialog.Filenames } else { return $openFileDialog.Filename }
}

提示输入一个文件夹(使用 System.Windows.Forms.FolderBrowserDialog 有可能会导致执行时挂起,要看系统配置以及从控制台运行还是从 PowerShell ISE中运行):

# Show an Open Folder Dialog and return the directory selected by the user.
function Read-FolderBrowserDialog([string]$Message, [string]$InitialDirectory)
{
    $app = New-Object -ComObject Shell.Application
    $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory)
    if ($folder) { return $folder.Self.Path } else { return '' }
}

提示用户输入多行文本(基于TechNet 这篇文章中的代码):

function Read-MultiLineInputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText)
{
<#
    .SYNOPSIS
    Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.

    .DESCRIPTION
    Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.

    .PARAMETER Message
    The message to display to the user explaining what text we are asking them to enter.

    .PARAMETER WindowTitle
    The text to display on the prompt window's title.

    .PARAMETER DefaultText
    The default text to show in the input box.

    .EXAMPLE
    $userText = Read-MultiLineInputDialog "Input some text please:" "Get User's Input"

    Shows how to create a simple prompt to get mutli-line input from a user.

    .EXAMPLE
    # Setup the default multi-line address to fill the input box with.
    $defaultAddress = @'
    John Doe
    123 St.
    Some Town, SK, Canada
    A1B 2C3
    '@

    $address = Read-MultiLineInputDialog "Please enter your full address, including name, street, city, and postal code:" "Get User's Address" $defaultAddress
    if ($address -eq $null)
    {
        Write-Error "You pressed the Cancel button on the multi-line input box."
    }

    Prompts the user for their address and stores it in a variable, pre-filling the input box with a default multi-line address.
    If the user pressed the Cancel button an error is written to the console.

    .EXAMPLE
    $inputText = Read-MultiLineInputDialog -Message "If you have a really long message you can break it apart`nover two lines with the powershell newline character:" -WindowTitle "Window Title" -DefaultText "Default text for the input box."

    Shows how to break the second parameter (Message) up onto two lines using the powershell newline character (`n).
    If you break the message up into more than two lines the extra lines will be hidden behind or show ontop of the TextBox.

    .NOTES
    Name: Show-MultiLineInputDialog
    Author: Daniel Schroeder (originally based on the code shown at http://technet.microsoft.com/en-us/library/ff730941.aspx)
    Version: 1.0
#>
    Add-Type -AssemblyName System.Drawing
    Add-Type -AssemblyName System.Windows.Forms

    # Create the Label.
    $label = New-Object System.Windows.Forms.Label
    $label.Location = New-Object System.Drawing.Size(10,10)
    $label.Size = New-Object System.Drawing.Size(280,20)
    $label.AutoSize = $true
    $label.Text = $Message

    # Create the TextBox used to capture the user's text.
    $textBox = New-Object System.Windows.Forms.TextBox
    $textBox.Location = New-Object System.Drawing.Size(10,40)
    $textBox.Size = New-Object System.Drawing.Size(575,200)
    $textBox.AcceptsReturn = $true
    $textBox.AcceptsTab = $false
    $textBox.Multiline = $true
    $textBox.ScrollBars = 'Both'
    $textBox.Text = $DefaultText

    # Create the OK button.
    $okButton = New-Object System.Windows.Forms.Button
    $okButton.Location = New-Object System.Drawing.Size(510,250)
    $okButton.Size = New-Object System.Drawing.Size(75,25)
    $okButton.Text = "OK"
    $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })

    # Create the Cancel button.
    $cancelButton = New-Object System.Windows.Forms.Button
    $cancelButton.Location = New-Object System.Drawing.Size(415,250)
    $cancelButton.Size = New-Object System.Drawing.Size(75,25)
    $cancelButton.Text = "Cancel"
    $cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })

    # Create the form.
    $form = New-Object System.Windows.Forms.Form
    $form.Text = $WindowTitle
    $form.Size = New-Object System.Drawing.Size(610,320)
    $form.FormBorderStyle = 'FixedSingle'
    $form.StartPosition = "CenterScreen"
    $form.AutoSizeMode = 'GrowAndShrink'
    $form.Topmost = $True
    $form.AcceptButton = $okButton
    $form.CancelButton = $cancelButton
    $form.ShowInTaskbar = $true

    # Add all of the controls to the form.
    $form.Controls.Add($label)
    $form.Controls.Add($textBox)
    $form.Controls.Add($okButton)
    $form.Controls.Add($cancelButton)

    # Initialize and show the form.
    $form.Add_Shown({$form.Activate()})
    $form.ShowDialog() > $null   # Trash the text of the button that was clicked.

    # Return the text that the user entered.
    return $form.Tag
}

所有的这些,除了多行输入框以外,都是直接使用了现有的 Windows Form 或 Visual Basic 控件。以下是我的多行输入框显示的模样:

Multi-line input box

我曾经使用动词 Get 作为函数的前缀,然后改成了动词 Show,但是读完整篇文章以后,我觉得使用动词 Read 也许是最合适的(而且它和 Read-Host Cmdlet的形式相匹配)。

希望您觉得这篇文章有用。

Happy coding!

将 Windows 8.1 的命令提示符替换为 PowerShell

在 Windows 8.1 中,增加了一个“将 WIN+X 菜单中将命令提示符替换为 Windows PowerShell”功能,您注意到了吗?

打开这个选项的方法是:右键单击 Windows 任务栏,选择“属性”。在“导航”选项卡中,您可以找到这个功能。

PowerShell 技能连载 - 在 ISE 中重设 PowerShell 宿主

想象一下您在 ISE 编辑器中长时间地编写一个脚本。当您开发的时候,您也许定义了变量、创建了函数、加载了对象,等等。

要确保您的脚本能像所希望的那样运行,您最终需要一个干净的测试环境。

获得一个干净的 PowerShell 并且移除所有变量和函数的最简单办法如下:

在 ISE 编辑器中,选择 文件 > 新建 PowerShell 选项卡。这实际上将创建一个新的 PowerShell 选项卡,以及一个全新的 PowerShell 宿主。这将确保没有任何不希望存在的旧变量和函数存在。

PowerShell 技能连载 - 查找 Cmdlet

Get-Command 可以用来查找 Cmdlet,但是在 PowerShell 3.0 中,它往往会返回比想象中还要多的 Cmdlet。由于自动加载模块的原因,Get-Command 不仅返回当前已加载 Module 中的 Cmdlet,还会返回所有可用 Module 中的 Cmdlet。

如果您仅希望在当前已加载的 Module 中查找一个 Cmdlet,请使用新的 -ListImported 参数:

PS> Get-Command -Verb Get | Measure-Object
Count    : 422

PS> Get-Command -Verb Get -ListImported | Measure-Object
Count    : 174

PowerShell 技能连载 - 从ISE编辑器中粘贴 PowerShell 代码

PowerShell ISE 编辑器的代码复制粘贴功能十分强大,例如将代码复制粘贴到 Microsoft Word 和其它文字处理器。由于 ISE 将剪贴板文字格式化为 RTF 格式,颜色代码将不会丢失。

然而,粘贴的时候字体往往太大或太小。

您可以在 ISE 编辑器中更改它。打开工具 > 选项,然后选择一个不同的字号。您在这儿选择的字号将用于粘贴的代码中。

当您更改完字号并关闭对话框之后,试着用 ISE 编辑器窗口右下角的滑块调整字体显示大小。这里的缩放级别只会影响 ISE 编辑器中的代码,并且不会影响粘贴的代码。

您也可以用鼠标滚轮来调整 ISE 编辑器中的显示字体大小。

PowerShell 技能连载 - 创建日历(和日期列表)

以下是一段创建 DateTime 集合的脚本片段。只需要指定年和月,脚本将会针对该月的每一天创建一个 DateTime 对象:

$month = 8
$year = 2013

1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year }

这段代码十分有用:只要加一个日期过滤器,您就可以过滤出工作日来。它将列出指定月份的所有周一至周五(因为它排除了 weekday 0(星期日)和 weekday 6(星期六)):

$month = 8
$year = 2013

1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year } |
  Where-Object { 0,6 -notcontains $_.DayOfWeek }

类似地,以下代码将统计指定月份所有星期三和星期五的天数:

$month = 8
$year = 2013

$days = 1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year } |
  Where-Object { 3,5 -contains $_.DayOfWeek }

$days
"There are {0} Wednesdays and Fridays" -f $days.Count

PowerShell 技能连载 - 发生了什么?

经常地,您需要用 PowerShell 来获取数据,并且您需要提取信息的一部分并且把它们用于报表。类似如下:

$serial = Get-WmiObject -Class Win32_OperatingSystem |
  Select-Object -Property SerialNumber

"Serial Number is $serial"

但是上述代码产生的结果如下:

Serial Number is @{SerialNumber=00261-30000-00000-AA825}

当您查看 $serial 的值,它看起来似乎很正常:

PS> $serial

SerialNumber
------------
00261-30000-00000-AA825

但问题出在列头(译者注:我们只需要 SerialNumber 的值,而不是需要一个包含 SerialNumber 属性的临时对象)。您可以用 Select-Object 只选出一列,用 -ExpandProperty 而不是 -Property 就可以消除列头:

$serial = Get-WmiObject -Class Win32_OperatingSystem |
  Select-Object -ExpandProperty SerialNumber

"Serial Number is $serial"

现在,一切正常了:

Serial Number is 00261-30000-00000-AA825

PowerShell 技能连载 - 密码混淆器脚本

曾经需要将密码保存在脚本中?曾经需要自动弹出一个身份验证对话框?对于前者,将密码和其它身份信息存储在脚本中是很糟糕的对于后者,如果您这么做了的话,至少能使黑客更难于窃取信息。

以下是一个脚本生成器。运行它,并且输入一个域/用户名和密码,脚本生成器会为您生成一段新脚本。

$pwd = Read-Host 'Enter Password'
$user = Read-Host 'Enter Username'
$key = 1..32 |
  ForEach-Object { Get-Random -Maximum 256 }

$pwdencrypted = $pwd |
  ConvertTo-SecureString -AsPlainText -Force |
  ConvertFrom-SecureString -Key $key

$text = @()
$text += '$password = "{0}"' -f ($pwdencrypted -join ' ')
$text += '$key = "{0}"' -f ($key -join ' ')
$text += '$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(" "))'
$text += '$cred = New-Object system.Management.Automation.PSCredential("{0}", $passwordSecure)' -f $user
$text += '$cred'

$newFile = $psise.CurrentPowerShellTab.Files.Add()
$newFile.Editor.Text = $text | Out-String

这段脚本包含混淆过的密码脚本,看起来大概类似这样:

$password = "76492d1116743f0423413b16050a5345MgB8AFcAMABGAEIANAB1AGEAdQA3ADUASABhAE0AMgBNADUAUwBnAFYAYQA1AEEAPQA9AHwAMgAyAGIAZgA1ADUAZgA0ADIANAA0ADUANwA2ADAAMgA5ADkAZAAxAGUANwA4ADUAZQA4ADkAZAA1AGMAMAA2AA=="
$key = "246 185 95 207 87 105 146 74 99 163 58 194 93 229 80 241 160 35 68 220 130 193 84 113 122 155 208 49 152 86 85 178"
$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(" "))
$cred = New-Object system.Management.Automation.PSCredential("test\tobias", $passwordSecure)
$cred

当您运行它,它将生成一个 Credential 对象,您可以立即将它用于身份验证。只要将它传给一个需要 Credential 对象的形参即可。

再强调一下,这并不是安全的。但是要想获取密码的明文还需要更多点知识才行。

快速替换文本文件中的字符串

不用开什么vim、emac、UltraEdit、Eclipse之类的编辑器了,PowerShell可以帮助手无寸铁的您快速地替换文本文件中的字符串:

dir *.txt -Recurse | % {
    (gc $_ -Raw) | % { $_ `
        -creplace '111', 'AAA' `
        -creplace '222', 'BBB' `
        -creplace '333', 'CCC'
    } | sc $_
}

注意 -creplace 区分大小写,-replace 不区分大小写。并且它们支持正则表达式!