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

PowerShell 技能连载 - 移除 Windows 10 应用

Windows 10 预装了一系列应用程序。幸运的是,您可以用 PowerShell 来移除您不想要的程序。当然,需要管理员权限。

要查看安装了哪些应用程序,请运行这段代码:

Get-AppxPackage -User $env:USERNAME

这将列出所有用您自己的用户账户安装的应用程序。要移除应用程序,请使用 PackageFullName,并且将它传给 Remove-AppxPackage 命令。请在更改之前备份您的系统,而且风险自负。多数应用程序并不是必须的。

PowerShell 技能连载 - 修正远程发送者信息

如果您使用 Invoke-Command 来远程执行 PowerShell 代码,您可能会注意到 PowerShell 远程操作会添加一个新的 PSComputerName 属性用来表示数据的来源。

这段代码将获取名为 dc-01 的机器的进程列表。PSComputerName 属性指明了源计算机名。当您使用多于一台电脑时十分有用。

#requires -Version 2
$code = {
  Get-Process
}

Invoke-Command -ScriptBlock $code -ComputerName dc-01

然而,如果您将结果用管道输出到 Out-GridViewPSComputerName 属性消失了。

作为一个变通办法,当您将结果输出到 Select-Object 命令时,PSComputerName 属性将会在网格视图窗口中正确地显示。

#requires -Version 2
$code = {
  Get-Process |
    Select-Object -Property Name, ID, Handles, CPU
}

Invoke-Command -ScriptBlock $code -ComputerName dc-01 |
  Out-GridView

PowerShell 技能连载 - 设置新的 Windows 注册所有者

这一小段代码将提示输入新的注册所有者名,然后将更新 Windows 注册表中的值。请注意需要管理员权限。

#requires -RunAsAdministrator


$NewName = Read-Host -Prompt 'Enter New Registered Windows Owner'

Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name RegisteredOwner -Value $NewName -Type String

这也是一个更改 Windows 注册表的模板代码。