PowerShell 技能连载 - 删除别名

适用于所有 PowerShell 版本

虽然您可以轻松地用 New-AliasSet-Alias 来创建新的别名,但是没有一个 cmdlet 可以删除别名。

PS> Set-Alias -Name devicemanager -Value devmgmt.msc

PS> devicemanager

PS>

要删除一个别名,您通常需要重启动您的 PowerShell。或者您可以通过 Alias: 虚拟驱动器删除它们:

PS> del Alias:\devicemanager

PS>

PowerShell 模块开发流程

我们希望使用 ISE 编辑器开发 PowerShell 的 .psm1 模块时,能够享受和开发 .ps1 脚本一样的体验:

  • 能够在 ISE 中按 F5 键运行并观察执行效果。
  • 能够在 ISE 中设置断点并进行调试。
  • 编辑脚本并保存后,能够使修改处即时生效。

如果直接用 ISE 打开 .psm1 模块文件,是无法直接运行的。我和史瑞克朋友探讨了这个问题,现将他的经验整理如下:

  • 先直接以 .ps1 的方式开发和调试(将模块代码和测试代码写在同一个 .ps1 文件中)。
    • 注意在这种方式下,Export-Member 是不能用的,可以暂时注释掉。
    • 基本开发、调试完以后,将 .ps1 后缀改为 .psm1,并取消 Export-Member 的注释。
  • 同时打开 .psm1 和 .ps1(前者是可复用的模块,后者是最终的应用脚本),即可在 .psm1 中设置断点进行调试。

请注意,如果将模块文件放在 %PSModulePath% 目录下,宿主只会在启动时自动加载一次模块的内容。如果在宿主启动之后编辑了 .psm1 文件,那么已经启动的宿主是不会感知到的,仍然执行的是旧的模块文件代码。

如果要让 .psm1 中的更改即时生效,需要在 .ps1 脚本的头部加上这段代码,显式加载模块:

if (gmo Test-Module) { rmo Test-Module }
ipmo Test-Module

Test-Module # 实际的业务

这样,每次重新运行 .ps1 脚本时,都会重新加载新的 .psm1 文件。待调试完之后,可以把头两行注释掉。

PowerShell 技能连载 - 快速查找 AD 账户

适用于所有 PowerShell 版本

您不必使用额外的 cmdlet 就能在您的活动目录中搜索用户账户或计算机。假设您已登录了一个域,只需要使用这段代码:

$ldap = '(&(objectClass=computer)(samAccountName=dc*))'
$searcher = [adsisearcher]$ldap

$searcher.FindAll()

这段代码将查找所有以“dc”开头的计算机账户。$ldap 可以是任何合法的 LDAP 查询语句。要查找用户,请将“computer”替换为“user”。

PowerShell 技能连载 - 从独立的文件中加载函数

适用于 PowerShell 3.0 及以上版本

为了让事情简化一些,您可能希望将 PowerShell 函数存放在一个独立的文件中。要将这些函数加载到您的业务脚本中,您可以使用这个简单的方法:

请确保包含 PowerShell 函数的脚本文件和业务脚本存放在同一个文件夹下。然后,在您的业务脚本中使用这行简单的代码:

. "$PSScriptRoot\library1.ps1"

这行代码将会从当前脚本存放的文件夹中加载一个称为“library1.ps1”的脚本。不要漏了前面的 . 和空格:“点加文件名”的方式执行一个文件,能够确保该文件中的所有变量和函数都在调用者的上下文中定义,并且当脚本执行完以后不会被清除掉。

请注意 $PSScriptRoot 总是指向脚本所在文件夹的路径(从 PowerShell 3.0 开始)。请确保已经保存了您的脚本,因为只有保存过的脚本才有父文件夹。

PowerShell 技能连载 - 创建优越的报告

当您克隆对象时,您可以修改它们的所有属性。克隆对象时可以导致原始对象“分离”,这是一个不错的主意。当您克隆了对象,您可以对该对象做任意的操作,例如修改或调整它的属性。

只需要用 Select-Object 命令就可以克隆对象。

这个例子列出文件夹中的内容,然后通过 Select-Object 处理,然后将其中的一些数据格式修饰一下。

Get-ChildItem -Path c:\windows |
  # clone the objects and keep the properties you want/add new properties (like "age...")
  Select-Object -Property LastWriteTime, 'Age(days)', Length, Name, PSIsContainer |
  # change the properties of the cloned object as you like
  ForEach-Object {
    # calculate the file/folder age in days
    $_.'Age(days)' = (New-Timespan -Start $_.LastWriteTime).Days

    # if it is a file, change size in bytes to size in MB
    if ($_.PSisContainer -eq $false)
    {
      $_.Length = ('{0:N1} MB' -f ($_.Length / 1MB))
    }

    # do not forget to return the adjusted object so the next one gets it
    $_
  } |
  # finally, select the properties you want in your report:
  Select-Object -Property LastWriteTime, 'Age(days)', Length, Name |
  # sort them as you like:
  Sort-Object -Property LastWriteTime -Descending |
  Out-GridView

该例子的结果以 MB 而不是字节为单位显示文件的大小,并且添加了一个称为“Age(days)”的列表示文件和文件夹创建以来的天数。

PowerShell 技能连载 - 接受多重输入

当您创建 PowerShell 函数时,以下是一个定义了既能够从参数中获取值,又能从管道中获取值的多功能 InputObject 参数的代码模板:

function Get-Something
{
  param
  (
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [Object[]]
    $InputObject
  )

  process
  {
    $InputObject | ForEach-Object {
      $element = $_
      "processing $element"
    }
  }
}

这是该函数的使用效果:

PS> Get-Something -InputObject 1,2,3,4
processing 1
processing 2
processing 3
processing 4

PS> 1,2,3,4 | Get-Something
processing 1
processing 2
processing 3
processing 4

请注意这个参数被定义成对象数组(所以它可以接收多个值)。然后,该参数值被送到 ForEach-Object 命令,将值一个一个取出来。这是针对第一个例子的调用方式。

要能够从管道中接收多个值,请确保对接收管道输入的参数设置了 ValueFromPipeline 属性。下一步,在函数中添加一段 Process 脚本块。这段代码充当循环的作用,和 ForEach-Object 十分相似,并且作用于管道送过来的每一个对象上。

PowerShell 技能连载 - 简单地读取注册表值

以下是最简单的读取注册表值的方法:

$Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$Name = 'RegisteredOwner'

$result = (Get-ItemProperty -Path "Registry::$Key" -ErrorAction Stop).$Name

"Registered Windows Owner: $result"

只需要将 $Key 替换成注册表项,将 $Name 替换成注册表键,就能读取它的值。

PowerShell 技能连载 - 复制命令行历史的工具函数

在前一个技能中我们演示了如何将之前键入的交互式 PowerShell 命令复制到您喜欢的脚本编辑器中。以下是一个能让操作更加简化的函数。如果您喜欢它,您可以将它加入您的配置脚本,那么就可以随时调用它:

function Get-MyGeniusInput
{
  param
  (
    $Count,
    $Minute = 10000
  )

  $cutoff = (Get-Date).AddMinutes(-$Minute)

  $null = $PSBoundParameters.Remove('Minute')
  $result = Get-History @PSBoundParameters |
    Where-Object { $_.StartExecutionTime -gt $cutoff } |
    Select-Object -ExpandProperty CommandLine

  $count = $result.Count
  $result | clip.exe
  Write-Warning "Copied $count command lines to the clipboard!"
}

Get-MyGeniusInput 默认将所有命令行历史都复制到剪贴板。通过 -Count 参数,您可以指定复制的条数,例如最后 5 条命令。而通过 -Minute 参数,您可以指定复制多少分钟之内的历史记录。

PS> Get-MyGeniusInput -Minute 25
WARNING: Copied 32 command lines to the clipboard!

PS> Get-MyGeniusInput -Minute 25 -Count 5
WARNING: Copied 5 command lines to the clipboard!

PS>

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

如果您在使用 PowerShell 的过程中突然发现刚才键入的某些代码起作用了,您接下来会想把这些代码复制并粘贴到一个脚本编辑器中,将它们保存起来,然后分享给朋友。

以下是操作方法:

Get-History -Count  5  |  Select-Object  -ExpandProperty  CommandLine  |  clip.exe

这将会把您最后键入的 5 条命令复制到剪贴板中。

PowerShell 技能连载 - 查找两个日期之间的所有日子

如果您需要知道两个日期之间的间隔天数,那么可以用 New-TimeSpan 轻松地获得:

$startdate = Get-Date
$enddate = Get-Date -Date '2014-09-12'

$difference = New-TimeSpan -Start $startdate -End $enddate
$difference.Days

然而,如果您不仅想知道两者之间的间隔天数,而且还希望精确地获取每一天的日期对象,那么可以用这个方法:

$startdate = Get-Date
$enddate = Get-Date -Date '2014-09-12'

$difference = New-TimeSpan -Start $startdate -End $enddate
$days = [Math]::Ceiling($difference.TotalDays)+1

1..$days | ForEach-Object {
  $startdate
  $startdate = $startdate.AddDays(1)
}

这一次,PowerShell 输出两个指定日期之间的所有日期对象。

当您了解了精确获取每个日期对象(而不仅是总天数)的方法之后,您可以过滤(例如以星期数),并查找距离您放假或退休之前还有多少个星期天或工作日。

以下代码是获取工作日用的:

$startdate = Get-Date
$enddate = Get-Date -Date '2014-09-12'

$difference = New-TimeSpan -Start $startdate -End $enddate
$difference.Days

$days = [Math]::Ceiling($difference.TotalDays)+1

1..$days | ForEach-Object {
  $startdate
  $startdate = $startdate.AddDays(1)
} |
  Where-Object { $_.DayOfWeek -gt 0 -and $_.DayOfWeek -lt 6}

这段代码时统计工作日天数用的:

$startdate = Get-Date
$enddate = Get-Date -Date '2014-09-12'

$difference = New-TimeSpan -Start $startdate -End $enddate
"Days in all: " + $difference.Days

$days = [Math]::Ceiling($difference.TotalDays)+1

$workdays = 1..$days | ForEach-Object {
  $startdate
  $startdate = $startdate.AddDays(1)
} |
  Where-Object { $_.DayOfWeek -gt 0 -and $_.DayOfWeek -lt 6} |
  Measure-Object |
  Select-Object -ExpandProperty Count

"Workdays: $workdays"