PowerShell 技能连载 - 理解 PowerShell 中 .NET 类型名称的变体

PowerShell 使用 .NET 类型名,例如将值转换为指定的类型。脚本中常常可以使用各种格式来定义 .NET 类型。以下是它们各自的用意和含义:

1
2
3
4
5
6
7
8
# short name for "Integer" data type
[int]12.4
# official .NET type name
[system.int32]12.4
# here is how you get there
[int].FullName
# with official names, the namespace "System" is always optional
[int32]12.4

简单来说,PowerShell 维护着它自己的“类型加速器”:.NET 类型的别名。查看任意类型的 FullName 属性,可以获得完整正式的 .NET 类型名。类型名前面的 “System.” 是可以省略的。

PowerShell 技能连载 - 极简的错误处理

错误处理不必做的很复杂。它可以简单到检测上一条命令是否执行成功:

1
2
3
4
5
6
7
# suppress errors by default
$ErrorActionPreference = 'SilentlyContinue'
# if a command runs into an error...
Get-Process -Name zumsel
# ...then $? is $false, and you can exit PowerShell
# with a return value, i.e. 55
if (!$?) { exit 55 }

PowerShell 将上一个命令是否遇到错误的信息记录在 $? 变量中。在这个例子中,返回的是 $false。使用 exit 加上一个正数,就可以退出脚本,并且将退出码返回给调用者。

PowerShell 技能连载 - 临时禁用 PSReadLine 模块

从 PowerShell 5 开始,PowerShell 控制台支持彩色文本特性,以及一系列由 PSReadLine 模块提供的新特性。

如果您从更早的 PowerShell 版本升级到 PowerShell 5,而丢失了彩色文本功能,那么您可以从 PSGallery 下载和安装 PSReadLine 模块:

1
PS C:\> Install-Module -Name PSReadLine -Scope CurrentUser

类似地,如果您的 PS5+ 控制台和以前的行为不一致,例如不会执行粘贴入的代码块,那么您可以临时禁止该模块:

1
2
3
4
5
# disable PS5+ console handler temporarily
Remove-Module psreadline

# re-enable PS5+ console handler again
Import-Module psreadline

PowerShell 技能连载 -用 Windows 事件日志记录脚本日志

使用内置的 Windows 事件日志架构来记录脚本日志是很棒的方法,而且非常简单。以下是准备日志记录的初始步骤(需要管理员特权):

1
2
3
4
#requires -runasadministrator

New-EventLog -LogName PSScriptLog -Source Logon, Installation, Misc, Secret
Limit-EventLog -LogName PSScriptLog -MaximumSize 10MB -OverflowAction OverwriteAsNeeded

您可能需要改变日志的名称以及(或)错误源的名称。如果名字没有被占用,这些源可以起任意名称。

现在,每个普通用户可以使用以下代码来写入新的事件日志:

1
Write-EventLog -LogName PSScriptLog -Source Logon -EntryType Warning -EventId 123 -Message "Problem in script $PSCommandPath"

请注意 Write-EventLog 使用和前面定义相同的 logfile,并且 source 名称必须是前面定义过的之一。

但脚本写入信息到日志文件后,您可以搜索日志或者用 Get-EventLog 创建报告:

1
PS C:\> Get-EventLog -LogName PSScriptLog -EntryType Error -Message *test.ps1*

PowerShell 技能连载 - Getting File Extension

By converting a path to a FileInfo object, you can easily determine the path parent folder or file extension. Have a look:

([IO.FileInfo]'c:\test\abc.ps1').Extension

([IO.FileInfo]'c:\test\abc.ps1').DirectoryName

PowerShell 技能连载 - Working with [FileInfo] Object

Often, code needs to check on files, and for example test whether the file exists or exceeds a given size. Here is some commonly used code:

$logFile = "$PSScriptRoot\mylog.txt"

$exists = Test-Path -Path $logFile
if ($exists)
{
  $data = Get-Item -Path $logFile
  if ($data.Length -gt 100KB)
  {
    Remove-Item -Path $logFile
  }

}

By immediately converting a string path into a FileInfo object, you can do more with less:

[System.IO.FileInfo]$logFile = "$PSScriptRoot\mylog.txt"
if ($logFile.Exists -and $logFile.Length -gt 0KB) { Remove-Item -Path $logFile }

You can convert any path to a FileInfo object, even if it is not representing a file. That’s what the property “Exists” is for: it tells you whether the file is present or not.

Twitter This Tip!ReTweet this Tip!

PowerShell 技能连载 - 轻松记录脚本日志

从 PowerShell 5 开始,您可以在任何宿主中使用 Strart-Transcript 来记录脚本的所有输出内容。以下是向各种脚本轻松添加日志的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# add this: ############################
$logFile = "$PSScriptRoot\mylog.txt"
Start-Transcript -Path $logFile -Append
#########################################

"Hello"

($a = Get-Service)

"I received $($a.Count) services."
Write-Host "Watch out: direct output will not be logged!"


# end logging ###########################
Stop-Transcript
#########################################

只需要将注释块中的代码添加到脚本的开始和结束处。日志文件将会在脚本所在的目录创建。由于 $logFile 使用 $PSScriptRoot(脚本的当前文件夹),请确保已经将脚本保存并以脚本的方式运行。否则,$PSScriptRoot 变量可能为空。

只需要确保脚本输出所有您需要的信息,就可以在 logfile 中看到它。例如将赋值语句放在括号中,PowerShell 将不只是赋值,而且将它们输出到 output。

警告:除了用 Write-Host 直接写到宿主的信息,所有输入输出信息都将被记录下。这些信息只能在屏幕上看到。

PowerShell 技能连载 - 多语言语音输出

在 Windows 10 上,操作系统自带了一系列高质量的文本转语言引擎,而且不局限于英文。可用的 TTS 引擎数量依赖于您所安装的语言。

PowerShell 可以发送文本到这些 TTS 引擎,并且通过 tag 可以控制使用的语言。所以如果您同时安装了英语和德语的 TTS 引擎,您可以像下面这样混用不同的语言:

1
2
3
4
5
6
7
8
9
$text = "<LANG LANGID=""409"">Your system will restart now!</LANG>
<LANG LANGID=""407""><PITCH MIDDLE = '2'>Oh nein, das geht nicht!</PITCH></LANG>
<LANG LANGID=""409"">I don't care baby</LANG>
<LANG LANGID=""407"">Ich rufe meinen Prinz! Herbert! Tu was!</LANG>
"

$speaker = New-Object -ComObject Sapi.SpVoice
$speaker.Rate = 0
$speaker.Speak($text)

如果您希望使用不同的语言,只需要将 LANGID 数字调整为您希望使用的文化代号。

PowerShell 技能连载 - 从字符串中移除文本

有时候,您也许听说过 Trim()TrimStart()TrimEnd() 可以 移除字符串中的文本。并且它们工作起来很正常:

1
2
3
4
5
PS C:\> $testvalue = "this is strange"
PS C:\> $testvalue.TrimEnd("strange")
this is

PS C:\>

但是这个呢:

1
2
3
4
5
PS C:\> $testvalue = "this is strange"
PS C:\> $testvalue.TrimEnd(" strange")
this i

PS C:\>

实际情况是 Trim() 方法将您的参数视为一个字符的列表。所有这些字符都将被移除。

如果您只是想从字符串的任意位置移除文本,请使用 Replace() 来代替:

1
2
3
4
PS C:\> $testvalue.Replace(" strange", "")
this is

PS C:\>

如果您需要进一步的控制,请使用正则表达式和锚定。要只从字符串的尾部移除文本,以下代码可以实现这个功能。只有结尾部分的 “strange” 字符串会被移除。

1
2
3
4
5
6
7
$testvalue = "this is strange strange strange"

$searchText = [Regex]::Escape("strange")
$anchorTextEnd = "$"
$pattern = "$searchText$anchorTextEnd"

$testvalue -replace $pattern

PowerShell 技能连载 - 解压序列化的数据

在前一个技能中您学习到了如何使用 Export-CliXml 命令来序列化数据并且用 Compress-Archive 将巨大的 XML 文件压缩成远远小于原始文件的尺寸。

今天,我们进行相反的操作:假设获得一个包含 XML 序列化数据的 ZIP 文件,然后恢复序列化的对象。当然这假设您已基于昨天的技能创建了这样的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# path to existing ZIP file
$ZipPath = "$env:TEMP\data1.zip"

# by convention, XML file inside the ZIP file has the same name
$Path = [IO.Path]::ChangeExtension($ZipPath, ".xml")

# expand ZIP file
Expand-Archive -Path $ZipPath -DestinationPath $env:temp -Force

# deserialize objects
$objects = Import-Clixml -Path $Path

# remove XML file again
Remove-Item -Path $Path -Recurse -Force

$objects | Out-GridView