PowerShell 技能连载 - 带对话框的必选参数

通常地,当您将一个函数参数标记为“必选的”,如果用户遗漏了这个参数,PowerShell 将提示用户:

function Get-Something
{
      param
      (
            [Parameter(Mandatory=$true)]
            $Path
      )

      "You entered $Path"
}

结果如下所示:

PS> Get-Something
cmdlet Get-Something at command pipeline position 1
Supply values for the following parameters:
Path:

以下是另一种选择:如果用户遗漏了 -Path,该函数弹出一个打开文件对话框:

function Get-Something
{
      param
      (
            $Path = $(
              Add-Type -AssemblyName System.Windows.Forms
              $dlg = New-Object -TypeName  System.Windows.Forms.OpenFileDialog
              if ($dlg.ShowDialog() -eq 'OK') { $dlg.FileName } else { throw 'No Path submitted'}
            )
      )

      "You entered $Path"
}

PowerShell 技能连载 - 用逗号作为十进制数分隔符

也许您还没有意识到,PowerShell 在输入输出时用的是不同的十进制分隔符——这也许会导致脚本用户产生混淆。

当您输入信息时,PowerShell 接受的是语言中性的格式(使用“.”作为十进制分隔符)。当输出信息时,它使用的是您的区域设置(所以在许多国家,使用的是“,”)。

请实践一下看看以下是否和您的文化相符:

$a = 1.5
$a
1,5

这是一个良好的设计,因为使用语言中性的输入格式,脚本执行情况永远相同,无论区域设置如何。然而,如果您希望用户能使用逗号作为分隔符,请看以下脚本:

function Multiply-LocalNumber
{
      param
      (
            [Parameter(Mandatory=$true)]
            $Number1,

            $Number2 = 10
      )

      [Double]$Number1 = ($Number1 -join '.')
      [Double]$Number2 = ($Number2 -join '.')

      $Number1 * $Number2
}

用户可以任选一种方式运行:

PS> Multiply-LocalNumber 1.5 9.223
13,8345

PS> Multiply-LocalNumber 1,5 9,223
13,8345

当用户选择使用逗号,PowerShell 实际上将它解释成一个数组。这是为什么脚本将数组用“.”连接的原因,实际上是将数组转换为一个数字。-join 的执行结果是一个字符串,该字符串需要被转换成一个数字,所以一切正常。

当然,这是个有点黑客的技巧,它总比每次首先得指导您的用户必须使用“.”分隔符来得好。

PowerShell 技能连载 - 检测合法的时间

如果您想检测某个信息类似“是否是合法的日期”,以下是一个检测的函数:

function Test-Date
{
    param
    (
        [Parameter(Mandatory=$true)]
        $Date
    )

    (($Date -as [DateTime]) -ne $null)
}

这段代码使用 -as 操作符尝试将输入数据转换为 DateTime 格式。如果转换失败,则结果为 $null,所以函数可以根据转换的结果返回 $true 或 $false。请注意,-as 操作符使用您的本地 DateTime 格式。

PowerShell 技能连载 - 朗读英文和德文(以及西班牙文,或您指定的语言)

Windows 8 是第一个完整支持本地化的文本到语音引擎的操作系统。所以您现在可以用 PowerShell 来朗读(以及咒骂)。

同时,操作系统永远有英文引擎,所以您的计算机拥有两种语言能力。

以下是一个用于德文系统的示例脚本(它可以很容易改为您的地域)。只需要修改语言 ID 即可(例如“de-de”代表德文),就可以让 Windows 说另一种语言。

请注意,在 Windows 8 之前,只附带了英文引擎。在 Windows 8 中,您可以使用您的本地语言。其它语言不可用。

$speaker = New-Object -ComObject SAPI.SpVoice
$speaker.Voice = $speaker.GetVoices() | Where-Object { $_.ID -like '*de-de*'}
$null = $speaker.Speak('Ich spreche Deutsch')
$speaker.Voice = $speaker.GetVoices() | Where-Object { $_.ID -like '*en-us*'}
$speaker.Speak('But I can of course also speak English.')

PowerShell 技能连载 - 单行内为多个变量赋值

当您将某个值赋给一个变量时,您可以用括号把表达式括起来。这个表达式还将返回该数值。我们看看它的样子:

$a = Get-Service
($a = Get-Service)

见到它们的区别了吗?第二行不仅将 Get-Service 的结果赋值给一个变量,而且将把结果输出至控制台。

实际上您也可以利用上第二行的结果。请看如下代码:

$b = ($a = Get-Service).Name
$a
$b

这将把所有的服务赋值给 $a,并把所有的服务名称赋值给 $b。

再次地,您可以将这个结果再用括号括起来,以供下次继续复用这个结果:

$c = ($b = ($a = Get-Service).Name).ToUpper()
$a
$b
$c

现在 $c 将包含所有大写形式的服务名。很另类的写法。

PowerShell 技能连载 - Ping 主机

有很多种方法可供您 ping 主机。以下是一个简单的将传统的 ping.exe 结合进您的脚本的方法:

function Test-Ping
{
    param([Parameter(ValueFromPipeline=$true)]$Name)

    process {
      $null = ping.exe $Name -n 1 -w 1000
      if($LASTEXITCODE -eq 0) { $Name }
    }
}

Test-Ping 接受一个主机名或 IP 地址作为参数并且返回 ping 是否成功。通过这种方法,您可以传入一个大的主机或 IP 地址列表,然后获得在线的结果:

'??','127.0.0.1','localhost','notthere',$env:COMPUTERNAME | Test-Online

PowerShell 技能连载 - 当发生错误时播放一段声音

为了吸引用户的注意力,您的脚本可以很容易地播放 WAV 声音文件。以下是一个简单的函数:

function Play-Alarm {
    $path = "$PSScriptRoot\Alarm06.wav"
    $playerStart = New-Object Media.SoundPlayer $path
    $playerStart.Load()
    $playerStart.PlaySync()
}

这段脚本假设 WAV 文件存放在和脚本相同的目录。请注意 PowerShell 2.0 并不支持 $PSScriptRoot

您只需要确保设置了 $path 变量并指向一个您希望的合法的 WAV 文件即可。

缺省情况下,PowerShell 将会等待直到声音播放完。如果您希望 PowerShell 继续执行而不是等待,请将 PlaySync() 替换成 Play()

PowerShell 技能连载 - 根据主机名获取 DNS IP 地址

有一个 GetHostByName() .NET 函数十分有用。它可以查询一个主机名并返回其当前的 IP 地址:

[System.Net.DNS]::GetHostByName('someName')

通过一个简单的 PowerShell 包装,它可以转换成一个多功能的很棒的小函数:

function Get-IPAddress
{
  param
  (
    [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [String[]]
    $Name
  )

  process
  { $Name | ForEach-Object { try { [System.Net.DNS]::GetHostByName($_) } catch { } }}
}

您现在可以直接使用这个函数(来获取您的 IP 地址)了。您可以传入一个或多个计算机名(逗号分隔)。您甚至可以通过 Get-ADComputer 或者 Get-QADComputer 管道传入数据。

Get-IPAddress
Get-IPAddress -Name TobiasAir1
Get-IPAddress -Name TobiasAir1, Server12, Storage1
'TobiasAir1', 'Server12', 'Storage1' | Get-IPAddress
Get-QADComputer | Get-IPAddress
Get-ADComputer -Filter * | Get-IPAddress

这样做是可行的,因为这个函数包含管道绑定以及一个参数序列化器。

-Name 参数为 ForEach-Object 提供数据,所以无论用户传入多少个机器名,它们都能被正确处理。

-Name 参数既能以参数的方式,也能以值的方式从管道中接收数据。所以您可以传入任何包含“Name”属性的对象,也可以传入任何纯字符串的列表。

注意该函数有一个非常简易的错误处理器。如果您传入了一个无法解析的计算机名,那么什么事也不会发生。如果您需要处理错误信息,请在 catch 代码块中添加代码。

PowerShell 技能连载 - 读写 NTFS 流

当一个文件存储在 NTFS 文件系统分区时,您可以向它附加数据流来存储隐藏信息。

以下是一个将 PowerShell 代码隐藏在 NTFS 流中的例子。当您运行这段代码时,它将在您的桌面上创建一个新的 PowerShell 脚本文件,然后在 ISE 编辑器中打开这个文件:

$path = "$home\Desktop\secret.ps1"

$secretCode = {
  Write-Host -ForegroundColor Red 'This is a miracle!';
  [System.Console]::Beep(4000,1000)
}

Set-Content -Path $path -Value '(Invoke-Expression ''[ScriptBlock]::Create((Get-Content ($MyInvocation.MyCommand.Definition) -Stream SecretStream))'').Invoke()'
Set-Content -Path $path -Stream SecretStream -Value $secretCode
ise $path

这个新的文件将看上去只是包含以下代码:

(Invoke-Expression '[ScriptBlock]::Create((Get-Content ($MyInvocation.MyCommand.Definition) -Stream SecretStream))').Invoke()

而当您运行这个脚本文件时,它将显示一段红色的文本并且蜂鸣一秒钟。所以新创建的脚本实际上执行了嵌入在隐藏 NTFS 流中名为“SecretStream”的代码。

要向 NTFS 卷中的(任何)文件附加隐藏信息,请使用 Add-ContentSet-Content 命令以及 -Stream 参数。

要从一个流中读取隐藏信息,请使用 Get-Content 命令,并为 -Stream 参数指定存储数据时用的名字。

PowerShell 技能连载 - 快速创建新的本地管理员账户

是否有为了测试而创建新的本地管理员账户的经历?假设您已经以 Administrator 账户登录,并且使用管理员特权开启 PowerShell,那么增加这样一个账户只需要几行代码就可以完成:

$user = 'splitpersonality'

net user /add $user
net localgroup Administrators /add $user

注意目标的组名是本地化的,所以在非英文系统中,您需要将 Administrators 替换成您的 Administrators 组的本地化名称。

译者注:我在中文操作系统上实验了一下,直接用 Administrators 也没有问题的。