PowerShell 技能连载 - 跳出管道

如果您事先知道期望从管道中得到多少个对象,您可以用 Select-Object 命令来停止上游的 cmdlet 执行。这样可以节约很多时间。

这个例子试着在 Windows 文件夹中查找 explorer.exe 的第一个实例。由于 Select-Object 语句的作用,一旦找到第一个实例,管道就结束了。如果没有这个语句,即便已经查找到所需的数据,Get-ChildItem 也会不断地递归扫描 Windows 文件夹。

#requires -Version 3


Get-ChildItem -Path c:\Windows -Recurse -Filter explorer.exe -ErrorAction SilentlyContinue |
Select-Object -First 1

请注意只有在 PowerShell 3.0 以上版本中,Select-Object 才具有中断上游 cmdlet 的能力。在早期的版本中,您仍然会获得前 x 个元素,但是上游的 cmdlet 会得不到“已经获得足够的数据”通知而一直持续执行。

PowerShell 技能连载 - 向管道传递一个数组

如果一个函数返回多于一个值,PowerShell 会将它们封装为一个数组。然而如果您通过管道将它传递至另一个函数,该管道会自动地将数组“解封”,并且一次处理一个数组元素。

如果您需要原原本本地处理一个数组而不希望解封,那么请将返回值封装在另一个数组中。通过这种方式,管道会将外层的数组解封并处理内层的数组。

以下代码演示了这个技能:

#requires -Version 1


function Test-ArrayAsReturnValue1
{
  param($count)

  $array = 1..$count

  return $array
}

function Test-ArrayAsReturnValue2
{
  param($count)

  $array = 1..$count

  return ,$array
}

'Result 1:'
Test-ArrayAsReturnValue1 -count 10 | ForEach-Object -Process {
  $_.GetType().FullName
}

'Result 2:'
Test-ArrayAsReturnValue2 -count 10 | ForEach-Object -Process {
  $_.GetType().FullName
}

当您运行这段代码时,第一个例子将返回数组中的元素。第二个例子将会把整个数组传递给循环。

PS C:\>

Result 1:
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32
System.Int32

Result 2:
System.Object[]

PowerShell 技能连载 - 面向管理员的免费 PowerShell 模块

上个技能中我们收到一个反馈,推荐使用一个名为“Carbon”的免费 PowerShell 模块,其中包含了一大堆有用的 PowerShell 函数。

其中一个是 Get-IPAddress,它能返回您计算机的所有 IP 地址。您可以在这儿找到和下载该模块:http://get-carbon.org/

请始终记着:从外部资源中下载 PowerShell 代码要记得检查一遍代码,确保是您所需要的内容。

PowerShell 技能连载 - 将 CSV 转换为 Excel 文件

PowerShell 可以用 Export-Csv 很容易创建 CSV 文件。如果您的系统中安装了 Microsoft Excel,PowerShell 可以调用 Excel 将一个 CSV 文件转换为一个 XLSX Excel 文件。

以下是一段示例代码。它使用 Get-Process 来获取一些数据,然后将数据写入一个 CSV 文件。Export-Csv 使用 -UseCulture 来确保 CSV 文件使用您所安装的 Excel 期望的分隔符。

$FileName = "$env:temp\Report"

# create some CSV data
Get-Process | Export-Csv -UseCulture -Path "$FileName.csv" -NoTypeInformation -Encoding UTF8

# load into Excel
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$excel.Workbooks.Open("$FileName.csv").SaveAs("$FileName.xlsx",51)
$excel.Quit()

explorer.exe "/Select,$FileName.xlsx"

下一步,Excel 打开该 CSV 文件,然后将数据保存为一个 XLSX 文件。

它工作得很好,不过可能会遇到一个类似这样的异常:

PS>  $excel.Workbooks.Open("$FileName.csv")
Exception  calling "Open" with "1" argument(s): "Old format or  invalid type library. (Exception from HRESULT: 0x80028018
(TYPE_E_INVDATAREAD))"
At line:1 char:1
+  $excel.Workbooks.Open("$FileName.csv")
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [],  MethodInvocationException
    + FullyQualifiedErrorId :  ComMethodTargetInvocation

这是一个长期已知的问题。当您的 Excel 语言和您的 Windows 操作系统不一致时可能会碰到。当您的 Windows 操作系统使用一个本地化的 MUI 包时,也许根本不会遇到这个问题。

要解决这个问题,您可以临时改变该线程的语言文化设置来适应您的 Excel 版本:

$FileName = "$env:temp\Report"

# create some CSV data
Get-Process | Export-Csv -Path "$FileName.csv" -NoTypeInformation -Encoding UTF8

# load into Excel
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true

# change thread culture
[System.Threading.Thread]::CurrentThread.CurrentCulture = 'en-US'

$excel.Workbooks.Open("$FileName.csv").SaveAs("$FileName.xlsx",51)
$excel.Quit()

explorer.exe "/Select,$FileName.xlsx"

这也会带来另外一个问题:当您以 en-US 语言文化设置运行 Excel 的 Open() 方法时,它不再需要 CSV 文件使用您的本地化分隔符。现在它需要的是一个以半角逗号分隔的文件,所以第二个脚本去掉了 -UseCulture 设置。

PowerShell 技能连载 - 谁在使用网络资源?

假设您拥有管理员权限,您可以使用一个简单的 WMI 类来检测某人是否正在通过网络访问您的资源:

PS> Get-WmiObject -Class Win32_ServerConnection |
Select-Object -Property ComputerName, ConnectionID, UserName, ShareName

这个操作也可以远程执行:只需要为 Get-WmiObject 命令增加 -ComputerName 参数即可查看谁在访问该远程计算机上的共享资源。需要拥有目标计算机的管理员权限才可以进行远程操作。

PowerShell 技能连载 - 禁止更新后自动重启

您是否厌烦了 Windows 安装了一些更新后导致的非计划中的重启?

和其它情况类似,您可以通过组策略控制重启,而且多数组策略设置只是注册表键而已。以下是一个通过设置注册表键来实现控制安装更新后的重启设置的示例脚本:

$code =
{
  $key = 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU'
  $name = 'NoAutoRebootWithLoggedOnUsers'
  $type = 'DWord'
  $value = 1

  if (!(Test-Path -Path $key))
  {
    $null = New-Item -Path $key -Force
  }
  Set-ItemProperty -Path $key -Name $name -Value $value -Type $type
}

Start-Process -FilePath powershell.exe -ArgumentList $code -Verb runas -WorkingDirectory c:\

请注意该脚本如何操作注册表:它实际上是通过另一个 PowerShell 实例间接执行的。第二个实例是通过 Start-Process 启动的,而“-Verb Runas”确保了以管理员身份运行这段代码。

如果您当前没有管理员权限,那么会弹出提升权限的对话框供您选择使用管理员权限,或是如果您的账户没有管理员权限的时候要求您选择一个有权限的账户。

PowerShell 技能连载 - 移除空白(和换行)

您也许知道每个 string 对象都有个 Trim() 方法可以删除该字符串开头和结尾的空白:

$text = '    Hello     '
$text.Trim()

一个鲜为人知的事实是,Trim() 也会删掉开头和结尾的换行:

$text = '

 Hello


 '
$text.Trim()

如果您需要,您可以控制 Trim() 函数吃掉的字符。

这个例子删除空格、点号、减号和换行:

$text = '

 ... Hello

 ...---
 '
$text.Trim(" .-`t`n`r")

PowerShell 技能连载 - 从 PowerShell 脚本中接收错误返回值

以下是一个演示 PowerShell 如何返回一个数值型状态码给调用者的简单脚本:

$exitcode = 123

$p = Start-Process -FilePath powershell -ArgumentList "-command get-process; exit $exitcode" -PassThru
Wait-Process -Id $p.Id

'External Script ended with exit code ' + $p.ExitCode

如果您在 PowerShell 中直接调用该脚本(不使用 Start-Process),那么数值型返回值会被赋给 $LASTEXITCODE

$exitcode = 199

powershell.exe "get-process; exit $exitcode"

'External Script ended with exit code ' + $LASTEXITCODE

如果您从一个批处理文件或是 VBScript 中运行一段 PowerShell 脚本,该数值型返回值将会赋给 %ERRORLEVEL% 环境变量,好比 PowerShell 是一个控制台应用程序一样——实际上 powershell.exe 确实是。

PowerShell 技能连载 - 为什么“exit”将会关掉 PowerShell

某些时候,我们会误会“exit”语句的工作方式。以下是一个例子:

function abc
{
  'Start'

  exit 100

  'Done'
}

abc

当您运行这个脚本时,abc 函数会被调用,然后退出。您会见到“Start”提示,但见不到“Done”提示,并且 $LASTEXITCODE 变量的值为 100。但这是实际情况吗?

当您在交互式的 PowerShell 控制台以交互式的方式运行 abc 方式时,该函数仍然退出了,不过这次,PowerShell 也被关闭了。为什么呢?

“Exit”在调用者作用域中退出代码。当您运行一个脚本时,该脚本退出后 PowerShell 仍然继续运行。当您以交互式的方式执行该函数时,交互式的 PowerShell 作为全局作用域退出了,而且由于不存在更高层的作用域了,所以 PowerShell 关闭了。

要更明显一点体现这个观点,我们在上述示例脚本中增加一点内容:

function abc
{
  'Start'

  exit 100

  'Done'
}

'Function starts'
abc
'Function ends'

如您所发现的,“exit”实际上并不是退出 abc 函数,而是退出整个脚本。所以您既见不到“Done”字样也见不到“Function ends”字样。

所以请慎用“exit”语句!它只能用在退出一个脚本并将控制权交还给调用者的时候。

PowerShell 技能连载 - 理解 break、continue、return 和 exit 语句

您是否十分熟悉“break”、“continue”、“return”和“exit”的用法?这些是十分有用的语言概念,以下是一个演示它们不同之处的测试函数:

'Starting'

function Test-Function {
    $fishtank = 1..10

    Foreach ($fish in $fishtank)
    {
        if ($fish -eq 7)
        {
            break      # <- abort loop
            #continue  # <- skip just this iteration, but continue loop
            #return    # <- abort code, and continue in caller scope
            #exit      # <- abort code at caller scope
        }

        "fishing fish #$fish"

    }
    'Done.'
}

Test-Function


'Script done!'

只需要去掉某个关键词的注释并运行脚本,就可以观察循环的执行结果。