PowerShell 技能连载 - 简单解析设置文件(第三部分)

在前一个技能中我们了解了 ConvertFrom-StringData 如何将纯文本的键值对转换为哈希表。还缺少另一个方向的操作:将哈希表转为纯文本。有了它以后,您就拥有了一个将设置和信息保存到文件的小型框架。

我们首先创建一个包含一些数据的哈希表:

1
2
3
4
5
6
7
$test = @{
Name = 'Tobias'
ID = 12
Conf = 'PowerShell Conference EU'
}

$test

结果看起来如下:

1
2
3
4
5
Name                           Value
---- -----
Conf PowerShell Conference EU
Name Tobias
ID 12

以下是名为 ConvertFrom-Hashtable 的函数,传入一个哈希表,并将它转换为纯文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
filter ConvertFrom-Hashtable
{
$_.GetEnumerator() |
ForEach-Object {
# get hash table key and value
$value = $_.Value
$name = $_.Name

# escape "\" in strings
if ($value -is [string]) { $value = $value.Replace('\','\\') }

# compose key-value pair as plain text
'{0}={1}' -f $Name, $value
}
}

让我们看看哈希表是如何转换的:

1
2
3
4
5
6
PS> $test | ConvertFrom-Hashtable
Conf=PowerShell Conference EU
Name=Tobias
ID=12

PS>

您可以用 ConvertFrom-StringData 转换到另一种形式:

1
2
3
4
5
6
7
8
9
10
11
PS> $test | ConvertFrom-Hashtable | ConvertFrom-StringData

Name Value
---- -----
Conf PowerShell Conference EU
Name Tobias
ID 12



PS>

所以基本上,您可以将哈希表保存为纯文本,并在稍后使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$test = @{
Name = 'Tobias'
ID = 12
Conf = 'PowerShell Conference EU'
}

$path = "$env:temp\settings.txt"

# save hash table as file
$test | ConvertFrom-Hashtable | Set-Content -Path $path -Encoding UTF8
notepad $path

# read hash table from file
Get-Content -Path $path -Encoding UTF8 |
ConvertFrom-StringData |
Out-GridView

请注意这种方法对简单的字符串和数字型数据有效。它不能处理复杂数据类型,因为这个转换操作并不能序列化对象。

PowerShell 技能连载 - 简单解析设置文件(第二部分)

在前一个技能中我们使用了 ConvertFrom-StringData 来将纯文本键值对转换为哈希表。

以下是一个转换失败的例子:

1
2
3
4
5
6
$settings = @'
Machine=Server12
Path=c:\test
'@

$settings | ConvertFrom-StringData

当您查看结果时,很快能发现失败的原因:

1
2
3
4
Name                           Value
---- -----
Machine Server12
Path c: est

显然,ConvertFrom-StringData 将 “\“ 视为一个转义符,在上述例子中增加了一个制表符 (“\t“),并吃掉了字面量 “t”。

要解决这个问题,请始终将 “\“ 转义为 “\\“。以下是正确的代码:

1
2
3
4
5
6
$settings = @'
Machine=Server12
Path=c:\\test
'@

$settings | ConvertFrom-StringData

现在结果看起来正确了:

1
2
3
4
Name                           Value
---- -----
Machine Server12
Path c:\test

PowerShell 技能连载 - 简单解析设置文件(第一部分)

假设您需要将设置用最简单的方式保存到一个文件中。设置文件可能看起来像这样:

1
2
3
4
5
6
7
$settings = '
Name=Weltner
FirstName=Tobias
ID=12
Country=Germany
Conf=psconf.eu
'

您可以将这些设置用 Set-Content 保存到文件中,并用 Get-Content 再把它们读出来。

那么,如何解析该信息,来存取独立的项目呢?有一个名为 ConvertFrom-StringData 的 cmdlet,可以将键值对转化为哈希表:

1
2
3
4
5
6
7
8
9
10
11
12
$settings = @'
Name=Weltner
FirstName=Tobias
ID=12
Country=Germany
Conf=psconf.eu
'@

$hash = $settings | ConvertFrom-StringData

$hash.Name
$hash.Country

PowerShell 技能连载 - 在 PowerShell 标题栏中添加实时时钟(第二部分)

在前一个技能中我们演示了一段代码,可以在后台线程中更新 PowerShell 标题栏,显示一个实时时钟。

难道不能更完美一点,显示当前的路径位置?挑战之处在于,如何让后台线程知道前台 PowerShell 的当前路径?

有一个名为 $ExecutionContext 的 PowerShell 变量可以提供关于上下文状态的各种有用信息,包括当前路径。我们将 $ExecutionContext 从前台传递给后台线程,该后台线程就可以显示当前前台的路径。

试试看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$code =
{
# submit the host process RawUI interface and the execution context
param($RawUi, $ExecContext)

do
{
# find the current location in the host process
$location = $ExecContext.SessionState.Path.CurrentLocation
# compose the time and date display
$time = Get-Date -Format 'HH:mm:ss dddd MMMM d'
# compose the title bar text
$title = "$location $time"
# output the information to the title bar of the host process
$RawUI.WindowTitle = $title
# wait a half second
Start-Sleep -Milliseconds 500
} while ($true)
}
$ps = [PowerShell]::Create()
$null = $ps.AddScript($code).AddArgument($host.UI.RawUI).AddArgument($ExecutionContext)
$handle = $ps.BeginInvoke()

当您运行这段代码时,PowerShell 的状态栏显示当前路径和实时时钟。当您切换当前路径时,例如运行 “cd c:\windows“,标题栏会立刻更新。

用以上代码可以处理许多使用场景:

  • 您可以在午餐时间快到时显示通知
  • 您可以在指定时间之后结束 PowerShell 会话
  • 您可以在标题栏中显示 RSS 订阅项目

PowerShell 技能连载 - 在 PowerShell 标题栏中添加实时时钟(第一部分)

要持续地更新 PowerShell 的标题栏,例如显示当前的日期和时间,您需要一个后台线程来处理这个工作。如果没有后台线程,PowerShell 会一直不停地忙于更新标题栏,导致无法使用。

以下是一个演示了如何在标题栏显示实时时钟的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$code =
{
# submit the host process RawUI interface and the execution context
param($RawUi)

do
{
# compose the time and date display
$time = Get-Date -Format 'HH:mm:ss dddd MMMM d'
# compose the title bar text
$title = "Current Time: $time"
# output the information to the title bar of the host process
$RawUI.WindowTitle = $title
# wait a half second
Start-Sleep -Milliseconds 500
} while ($true)
}
$ps = [PowerShell]::Create()
$null = $ps.AddScript($code).AddArgument($host.UI.RawUI)
$handle = $ps.BeginInvoke()

这里最关键的是将 $host.UI.RawUI 对象从 PowerShell 前台传递给后台线程代码。只有这样,后台线程才能存取 PowerShell 前台拥有的标题栏对象。

PowerShell 技能连载 - 当 Add-Type 失败之后

Add-Type 可以将外部 DLL 文件中的 .NET 程序集载入 PowerShell 中。这在大多数情况下工作量好,以下是一个调用示例(当然,需要 SharePoint DLL 可用):

1
PS> Add-Type -Path "C:\SharepointCSMO\Microsoft.SharePoint.Client.dll"

但是对某些 DLL 文件,这个命令会执行失败,PowerShell 返回一个““Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.”异常。

如果发生这种情况,一种解决办法是使用过时的(但是仍然可用的)LoadFrom() 方法:

1
PS> [Reflection.Assembly]::LoadFrom("C:\SharepointCSMO\Microsoft.SharePoint.Client.dll")

为什么 Add-Type 方法会失败?Add-Type 维护着一个定制的和程序集相关的版本号。所以如果您试图加载的文件版本比期望的低,Add-Type 会拒绝加载它。相比之下,LoadFrom() 不关心版本号,所以和旧版本兼容。

PowerShell 技能连载 - 理解 PowerShell 和文件系统

PowerShell 维护着它自己的位置:

1
2
3
4
5
PS> Get-Location

Path
----
C:\Users\tobwe

当前路径指向所有 cmdlet 使用的相对路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Get-Location

Path
----
C:\Users\tobwe



PS> Resolve-Path -Path .

Path
----
C:\Users\tobwe

还有另一个当前路径,是由 Windows 维护的,影响所有 .NET 方法。它可能 PowerShell 的当前路径不同:

1
2
3
4
5
6
7
PS> [Environment]::CurrentDirectory
C:\test

PS> [System.IO.Path]::GetFullPath('.')
C:\test

PS>

所以如果在脚本中使用跟文件系统有关的 .NET 方法,可能需要先同步两个路径。这行代码确保 .NET 使用和 PowerShell 相同的文件系统路径:

1
PS> [Environment]::CurrentDirectory = $ExecutionContext.SessionState.Path.CurrentFileSystemLocation

同步之后,cmdlet 和 .NET 方法在同一个路径上工作:

1
2
3
4
5
6
7
8
9
10
PS> [Environment]::CurrentDirectory = $ExecutionContext.SessionState.Path.CurrentFileSystemLocation

PS> [System.IO.Path]::GetFullPath('.')
C:\Users\tobwe

PS> Resolve-Path '.'

Path
----
C:\Users\tobwe

PowerShell 技能连载 - 查看 PowerShell 当前的文件系统路径

To find out the path your PowerShell is currently using, simply run Get-Location:
要查看 PowerShell 的当前路径,只要用 Get-Location 命令即可:

1
2
3
4
5
PS> Get-Location

Path
----
C:\Users\tobwe

然而,当前路径不一定指向一个文件系统位置。如果您将位置指向注册表,例如这样:

1
2
3
4
5
6
7
PS> cd hkcu:\

PS> Get-Location

Path
----
HKCU:\

如果您想知道 PowerShell 当前使用的文件系统路径,而不管当前使用什么 provider,请使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS> $ExecutionContext.SessionState.Path

CurrentLocation CurrentFileSystemLocation
--------------- -------------------------
HKCU:\ C:\Users\tobwe



PS> $ExecutionContext.SessionState.Path.CurrentFileSystemLocation

Path
----
C:\Users\tobwe


PS> Get-Location

Path
----
HKCU:\

CurrentFileSystemLocation 总是返回文件系统的当前位置,这可能和 Get-Location 返回的不一样。

PowerShell 技能连载 - 探讨 Windows PowerShell 和 PowerShell Core

PowerShell 当前有两个版本:随 Windows 发布、基于完整 .NET 框架的的 “Windows PowerShell”,以及基于 .NET Core、支持跨平台、能够运行在 Nano Server 等平台的 “PowerShell Core”。

面向某个具体 PowerShell 版本的脚本作者可以使用 #requires 语句来确保他们的脚本运行于指定的版本。

例如,要确保一个脚本运行于 PowerShell Core 中,请将这段代码放在脚本顶部:

1
2
#requires -PSEdition Core
Get-Process

请确保把这段代码保存到磁盘。#requires 只对脚本有效。

当您在 Windows 机器上的 “Windows PowerShell” 中运行这段脚本,将会报错:

1
2
3
4
5
6
7
8
9
PS> C:\Users\abc\requires  core.ps1
The script 'requires core.ps1' cannot be run because it contained a "#requires" statement for PowerShell
editions 'Core'. The edition of PowerShell that is required by the script does not match the currently
running PowerShell Desktop edition.
+ CategoryInfo : NotSpecified: (requires core.ps1:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ScriptRequiresUnmatchedPSEdition


PS>

类似地,甚至更重要的是,当您将 “Core” 替换为 “Desktop”,脚本将无法在受限的 “PowerShell Core” 版本中运行。如果您的脚本依赖于传统 Windows 系统中的某些特性,并且依赖于完整 .NET Framework,这种做法十分明智。

PowerShell 技能连载 - Windows PowerShell 和 PowerShell Core

最近关于 PowerShell 版本有一些混淆。在 GitHub 上有一个名为 “PowerShell 6” 的开源倡议

这是否意味着开源的 PowerShell 6 是 PowerShell 5 的继任者,并且最终和 Windows 一起发布?

并不是的。现在只有两个不同的 PowerShell,所谓的 “PowerShell Editions”。

“Windows PowerShell” 就我们所知会持续存在,并且将会随着将来的 Windows 版本发布,对应完整版 .NET Framework。

开源的 PowerShell 6 本意是基于 “PowerShell Core” 工作,这是一个有限的 .NET 子集 (.NET Core)。它的目的是在一个最小化的环境,例如 Nano Server 中运行,并且能够支持 Linux 和 Apple 等不同的平台。

从 PowerShell 5.1 开始,您可以这样检查 “PowerShell Edition”:

1
2
PS> $PSVersionTable.PSEdition
Desktop

“Desktop” 表示您在完整的 .NET Framework 上运行 “Windows PowerShell”。”Core” 表示您在 .NET Core 上运行 “PowerShell Core”。