PowerShell 技能连载 - 简单的 INI 文件替代

如果您想将设置保存在您的脚本之外并将它们保存在一个独立的配置文件中,那么您可以使用各种数据格式。

INI 文件的并地支持并不充分,所以您得人工处理它们。JSON 和 XML 文件有处理器支持,但是文字内容太复杂,不容易被人类阅读。

If your config data can be expressed as key-value pairs like below, then we have an alternative:
如果您的配置数据是类似这样的键值对,那么我们可以有别的选择:

1
2
3
Name = 'Tom'
ID = 12
Path = 'C:'

将键值对保存为一个纯文本文件,然后使用这段代码来读取该文件:

$hashtable = @{}
$path = 'z:\yourfilename.config'

$payload = Get-Content -Path $path |
Where-Object { $_ -like '*=*' } |
ForEach-Object {
    $infos = $_ -split '='
    $key = $infos[0].Trim()
    $value = $infos[1].Trim()
    $hashtable.$key = $value
}

结果是一个哈希表,您可以用这种方式轻松地读取各项的值:

$hashtable.Name
$hashtable.ID
$hashtable.Path

PowerShell 技能连载 - 删除数组元素

您曾经比较过两个数组吗?Compare-Object 可能有用。请试试这段代码:

$array1 = 1..100
$array2 = 2,4,80,98

Compare-Object -ReferenceObject $array1 -DifferenceObject $array2 |
  Select-Object -ExpandProperty InputObject

执行的结果是 $array1 的内容减去 $array2 的内容。

要获取 $array1$array2 中共有的元素,请使用以下方法:

$array1 = 1..100
$array2 = 2,4,80,98, 112

Compare-Object -ReferenceObject $array1 -DifferenceObject $array2 -ExcludeDifferent -IncludeEqual |
  Select-Object -ExpandProperty InputObject

PowerShell 技能连载 - 快速获取 IP 地址

您是否希望快速获取您的机器或是网络上的机器的 IP 地址列表?以下是实现方法:

#requires -Version 3

$ComputerName = ''

[System.Net.Dns]::GetHostAddresses($ComputerName).IPAddressToString

要只取 IPv4 地址,请使用这种方法:

#requires -Version 1

$ComputerName = ''

[System.Net.Dns]::GetHostAddresses($ComputerName) |
Where-Object {
  $_.AddressFamily -eq 'InterNetwork'
} |
Select-Object -ExpandProperty IPAddressToString

类似地,要获取 IPv6 地址,请改成这种方法:

#requires -Version 1

$ComputerName = ''

[System.Net.Dns]::GetHostAddresses($ComputerName) |
Where-Object {
  $_.AddressFamily -eq 'InterNetworkV6'
} |
Select-Object -ExpandProperty IPAddressToString

PowerShell 技能连载 - 截短文本

假设您希望截掉一个字符串尾部的一些文字。以下是使用字符串操作的传统方法:

$text = "Some text"
$fromRight = 3

$text.Substring(0, $text.Length - $fromRight)

一个更强大的方法是使用 -replace 操作符结合正则表达式:

$text = "Some text"
$fromRight = 3

$text -replace ".{$fromRight}$"

这段代码将去掉文字尾部($)之前 $fromRight 个任意字符(”.“)。

由于正则表达式十分灵活,所以您可以重新编辑它,只截去数字,且最多只截掉 5 个数字:

$text1 = "Some text with digits267686783"
$text2 = "Some text with digits3"

$text1 -replace "\d{0,5}$"
$text2 -replace "\d{0,5}$"

量词“{0,5}”告诉正则表达式引擎需要 0 到 5 个数字,引擎会尽可能多地选取。

PowerShell 技能连载 - 避免使用重定向符

如果您还在使用旧的重定向操作符来将命令的结果输出到一个文件,那么您可以使用新的 PowerShell cmdlet 来代替。以下是原因:

#requires -Version 2

$OutPath = "$env:temp\report.txt"

Get-EventLog -LogName System -EntryType Error, Warning -Newest 10 > $OutPath

notepad.exe $OutPath

这将产生一个文本文件,内容和控制台中显示的精确一致,但不会包含任何对象的特性。

下一个例子确保输出的文本一点也不会被截断,并且输出使用 UTF8 编码——这些参数都是简易重定向所不包含的:

#requires -Version 2

$OutPath = "$env:temp\report.txt"

Get-EventLog -LogName System -EntryType Error, Warning -Newest 10 |
Format-Table -AutoSize -Wrap |
Out-File -FilePath $OutPath -Width 100

notepad.exe $OutPath

PowerShell 技能连载 - 解码 PowerShell 命令

当您需要在一个独立的 powershell.exe 以一个 PowerShell 命令的方式执行代码时,并不十分安全。这要看您从哪儿调用 powershell.exe,您的代码参数可能被解析器修改,而且代码中的特殊字符可能会造成宿主混淆。

一个更健壮的传递命令方法是将它们编码并用解码命令提交。这只适用于短的代码。长度必须限制在 8000 字符左右以内。

$code = {
  Get-EventLog -LogName System -EntryType Error |
  Out-GridView
}

$Bytes = [System.Text.Encoding]::Unicode.GetBytes($code.ToString())
$Encoded = [Convert]::ToBase64String($Bytes)

$args = '-noprofile -encodedcommand ' + $Encoded

Start-Process -FilePath powershell.exe -ArgumentList $args

PowerShell 技能连载 - 定义多行文本

当您需要在 PowerShell 中定义多行文本时,通常可以这样使用 here-string:

$text = @"
  I am safe here
  I can even use "quotes"
"@

$text | Out-GridView

值得注意的重点是分隔符包含(不可见的)回车符。必须在开始标记后有一个,在结束标记前有一个。

一个很特殊的另一种用法是使用脚本块来代替:

$text = {
  I am safe here
  I can even use "quotes"
}

$text.ToString() | Out-GridView

虽然代码颜色不同,并且需要将脚本块转为字符串。这种方法有一定局限性,因为脚本块是一段 PowerShell 代码,并且它会被解析器解析。所以您只能包裹一段不会造成解析器混淆的文本。

这个是个不合法的例子,将会造成语法错误,因为非闭合的双引号:

$text = {
  I am safe here
  I can even use "quotes
}

$text.ToString() | Out-GridView

PowerShell 技能连载 - 当前脚本的路径

在 PowerShell 1.0 和 2.0 中,您需要一堆奇怪的代码来获得当前脚本的位置:

# make sure the script is saved and NOT "Untitled"!

$invocation = (Get-Variable MyInvocation).Value
$scriptPath = Split-Path $invocation.MyCommand.Path
$scriptName = $invocation.MyCommand.Name

$scriptPath
$scriptName

只有将它放在脚本的根部,这段代码才能用。

从 PowerShell 3.0 开始,事情变得更简单了,并且这些特殊变量在您脚本的任意地方都可以用。

# make sure the script is saved and NOT "Untitled"!

$ScriptName = Split-Path $PSCommandPath -Leaf
$PSScriptRoot
$PSCommandPath
$ScriptName

PowerShell 技能连载 - 发现动态参数

在前一个技能中我们展示了如何查找暴露了动态参数的 cmdlet。现在让我们来探索什么事动态参数。这个 Get-CmdletDynamicParameter 函数将返回一个动态参数的列表和它们的缺省值:

#requires -Version 2
function Get-CmdletDynamicParameter
{
  param (
    [Parameter(ValueFromPipeline = $true,Mandatory = $true)]
    [String]
    $CmdletName
  )

  process
  {
    $command = Get-Command -Name $CmdletName -CommandType Cmdlet
    if ($command)
    {
      $cmdlet = New-Object -TypeName $command.ImplementingType.FullName
      if ($cmdlet -is [Management.Automation.IDynamicParameters])
      {
        $flags = [Reflection.BindingFlags]'Instance, Nonpublic'
        $field = $ExecutionContext.GetType().GetField('_context', $flags)
        $context = $field.GetValue($ExecutionContext)
        $property = [Management.Automation.Cmdlet].GetProperty('Context', $flags)
        $property.SetValue($cmdlet, $context, $null)

        $cmdlet.GetDynamicParameters()
      }
    }
  }
}

Get-CmdletDynamicParameter -CmdletName Get-ChildItem

该函数使用一些黑客的办法来暴露动态参数,这种方法是受到 Dave Wyatt 的启发。请参见他的文章 https://davewyatt.wordpress.com/2014/09/01/proxy-functions-for-cmdlets-with-dynamic-parameters/

PowerShell 技能连载 - 查找带动态参数的 cmdlet

有些 cmdlet 暴露了动态参数。它们只在特定的环境下可用。例如 Get-ChildItem 只在当前的位置是文件系统路径(并且是 PowerShell 3.0 以上版本)时才暴露 -File-Directory 参数。

要查找所有带动态参数的 cmdlet,请试试这段代码:

#requires -Version 2

$cmdlets = Get-Command -CommandType Cmdlet

$cmdlets.Count

$loaded = $cmdlets |
Where-Object { $_.ImplementingType }

$loaded.Count

$dynamic = $loaded |
Where-Object {
    $cmdlet = New-Object -TypeName $_.ImplementingType.FullName
    $cmdlet -is [System.Management.Automation.IDynamicParameters]
  }

$dynamic.Count

$dynamic | Out-GridView

您将只会获得已加载并且包含动态参数的 cmdlet。