PowerShell 技能连载 - 为什么 $MaximumHistoryCount 容量有限

如果您想增加最大命令历史的容量,您可能会遇到这样的限制:

PS C:\> $MaximumHistoryCount  = 100000

The variable cannot be  validated because the value 100000 is not a valid value for the Maximum
HistoryCount variable.

这里并没有提示合法的范围是多少。有意思的地方是这个变量的合法范围保存在哪。答案是:您可以查询这个变量的 ValidateRange 属性:

$variable = Get-Variable MaximumHistoryCount
$variable.Attributes
$variable.Attributes.MinRange
$variable.Attributes.MaxRange

但您遇到一个变量在原始数据类型之外有数值限制,您可能需要检查变量的属性来确认其中是否有验证器属性。

PowerShell 技能连载 - 复制命令历史

您可以将整个命令历史拷贝到剪贴板中:

(Get-History).CommandLine | clip.exe

该技术使用 PowerShell 3.0 带来的自动展开技术。若要在 PowerShell 2.0 中使用它,您需要像这样手工展开属性:

Get-History | Select-Object -ExpandProperty commandline | clip.exe

要只拷贝最后五条命令,只需要为 Get-History 命令加上 -Count 参数即可:

(Get-History -Count 5).CommandLine | clip.exe

PowerShell 技能连载 - 增加历史缓存

当您在 PowerShell 会话中工作一段时间以后,命令历史可能十分有用。每个会话存储了您输入的所有命令,您可以按上下键浏览已输入的命令。

您甚至可以这样搜索历史缓存:

PS C:\> #obje

键入一个注释符(# 号),然后跟上您所能回忆起的命令关键字,然后按下 TAB 键,每按一次 TAB 将会显示命令历史中匹配的一条命令(如果没有匹配成功,将不会显示)。

要限制命令历史的大小,请使用 $MaximumHistoryCount 变量。缺省值是 4096。

PS C:\> $MaximumHistoryCount
4096

PS C:\> $MaximumHistoryCount = 32KB-1

PS C:\> $MaximumHistoryCount
32767

PS C:\>

历史缓存最大允许的容量是 32KB-1。

PowerShell 技能连载 - 获取最后启动时间

在 PowerShell 3.0 以上版本中,可以很容易地用 Get-CimInstance 从 WMI 中获取真实的 DateTime 类型信息。这段代码将告诉您系统上次启动的时间:

#requires -Version 3
(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime

在 PowerShell 2.0 中,您只能使用 Get-WmiObject,它是以 WMI 格式反馈数据的:

(Get-WmiObject -Class Win32_OperatingSystem).LastBootUpTime

这里,您必须手工转换 WMI 格式:

$object = Get-WmiObject -Class Win32_OperatingSystem
$lastboot = $object.LastBootUpTime
$object.ConvertToDateTime($lastboot)

ConvertToDateTime() 转换函数实际上是一个附加的方法。在这个场景背后,是一个静态方法实现了以上工作:

$object = Get-WmiObject -Class Win32_OperatingSystem
$lastboot = $object.LastBootUpTime
[System.Management.ManagementDateTimeConverter]::ToDateTime($lastboot)

PowerShell 技能连载 - 复制对象

在前一个技能中我们演示了 PowerShell 是通过引用存储对象的。如果您想创建一个浮板,您可能需要手工复制对象的所有属性。

以下是一个简单的克隆对象的方法:

$object1 = @{Name='Weltner'; ID=12 }
$object2 = @{Name='Frank'; ID=99 }


$a = $object1, $object2

# clone entire object by serializing it back and forth:
$b = $a | ConvertTo-Json -Depth 99 | ConvertFrom-Json

$b[0].Name = 'changed'
$b[0].Name
$a[0].Name

不过,请注意序列化的过程可能会改变复制的对象类型。

PS C:\> $a[0].GetType().FullName
System.Collections.Hashtable

PS C:\> $b[0].GetType().FullName
System.Management.Automation.PSCustomObject

PS C:\>

PowerShell 技能连载 - 复制数组(第 2 部分)

在前一个技能中我们解释了如何用 Clone() 方法安全地“克隆”一个数组。这将把一个数组的内容复制到一个新的数组。

然而,如果数组的元素是对象(不是数字或字符串等原始数据类型),数组存储了这些对象的内存地址,所以克隆方法虽然创建了一个新的数组,但是新的数组仍然引用了相同对象。请看:

$object1 = @{Name='Weltner'; ID=12 }
$object2 = @{Name='Frank'; ID=99 }


$a = $object1, $object2
$b = $a.Clone()
$b[0].Name = 'changed'
$b[0].Name
$a[0].Name

虽然您克隆了数组 $a,但是新的数组 $b 仍然引用了相同的对象,对对象的更改会同时影响两个数组。只有对数组内容的更改是独立的:

$object1 = @{Name='Weltner'; ID=12 }
$object2 = @{Name='Frank'; ID=99 }


$a = $object1, $object2
$b = $a.Clone()
$b[0] = 'deleted'
$b[0]
$a[0]

PowerShell 技能连载 - 复制数组(第 1 部分)

当您复制变量内容时,您也可以只拷贝“引用”(内存地址),而不是内容。请看这个例子:

$a = 1..10
$b = $a
$b[0] = 'changed'
$b[0]
$a[0]

虽然您改变了 $b,但 $a 也跟着改变。两个变量都引用了相同的内存地址,所以两者具有相同的内容。

要创建一个数组的全新拷贝,您需要先对它进行克隆:

$a = 1..10
$b = $a.Clone()
$b[0] = 'changed'
$b[0]
$a[0]

PowerShell 技能连载 - 使用编码的脚本

在 VBScript 中有编码的脚本。编码并不是隐藏脚本内容的安全方法,但它能使用户获取代码内容略微更难一点。

以下是一个传入 PowerShell 脚本并对它编码的函数:

function ConvertTo-EncodedScript
{
  param
  (
    $Path,

    [Switch]$Open
  )

  $Code = Get-Content -Path $Path -Raw
  $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Code)
  $Base64 = [Convert]::ToBase64String($Bytes)

  $NewPath = [System.IO.Path]::ChangeExtension($Path, '.pse1')
  $Base64 | Set-Content -Path $NewPath

  if ($Open) { notepad $NewPath }
}

编码后的脚本将会以 .pse1 扩展名来保存(这是一个完全随意定义的文件扩展名,并不是微软定义的)。

要执行这段编码后的脚本,请运行这段命令(不能在 PowerShell ISE 中运行):

powershell -encodedcommand (Get-Content 'Z:\pathtoscript\scriptname.pse1' -Raw)

请注意 PowerShell 最多支持大约 8000 个字符的编码命令。编码命令的本意是安全地将 PowerShell 代码传递给 powershell.exe,而不会被特殊字符打断命令行。

PowerShell 技能连载 - 用 try..finally 在 PowerShell 关闭时执行代码

如果您需要在 PowerShell 退出之前执行一些代码,您可以像这样简单地使用 try..finally 代码块:

try {
    # some code
    Start-Sleep -Seconds 20
} finally {
    # this gets executed even if the code in the try block throws an exception
    [Console]::Beep(4000,1000)
}

这段代码模拟一段长时间运行的脚本。甚至您关闭 PowerShell 窗口时,在 finally 块中的代码也会在 PowerShell 停止之前执行。

当然这得当脚本确实在运行时才有效。

PowerShell 技能连载 - 使用后台任务

后台任务可以用来加速您的脚本。如果您的脚本有一系列可并发执行的独立的任务组成,那么就合适使用后台任务。

后台任务适用于这两种情况:

  • 任务需要至少 3-4 秒执行时间。
  • 任务并不会返回大量数据。

以下是一个基本的由 3 个任务组成的后台任务场景。如果依次执行,它们一共约耗费 23 秒时间。通过使用后台任务,它们只消耗 11 秒(由最长的单个任务时间决定)加上一些额外的上下文时间。

#requires -Version 2

# three things you want to do in parallel
# for illustration, Start-Sleep is used
# remove Start-Sleep and replace with real-world
# tasks
$task1 = {
    Start-Sleep -Seconds 4
    dir $home
    }

$task2 = {
    Start-Sleep -Seconds 8
    Get-Service
}
$task3 = {
    Start-Sleep -Seconds 11
    'Hello Dude'
 }

$job1 = Start-Job -ScriptBlock $task1
$job2 = Start-Job -ScriptBlock $task2

$result3 = & $task3

Wait-Job -Job $job1, $job2

$result1 = Receive-Job -Job $job1
$result2 = Receive-Job -Job $job2

Remove-Job $job1, $job2