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”。

PowerShell 技能连载 - 设置 Powershell 标题文本

您也许知道可以通过类似这样一行代码改变 PowerShell 宿主窗口的标题文本:

1
PS> $host.UI.RawUI.WindowTitle = "Hello  World!"

如果把这段代码加入 prompt 函数,标题文本就可以每次变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function prompt
{
# get current path
$path = Get-Location

# get current time
$date = Get-Date -Format 'dddd, MMMM dd'

# create title text
$host.UI.RawUI.WindowTitle = ">>$path<< [$date]"

# output prompt
'PS> '
}

PowerShell 每次完成一条命令之后,都会执行 “prompt“ 函数。在标题栏中,您将始终能看到当前的路径和日期,而 PowerShell 编辑器中的命令提示符被简化成 “PS> “。

PowerShell 技能连载 - 清除所有用户变量

在前一个技能中我们演示了如何用类似这样的方法来查找内置的 PowerShell 变量:

1
2
3
4
5
$ps = [PowerShell]::Create()
$null = $ps.AddScript('$null=$host;Get-Variable')
$ps.Invoke()
$ps.Runspace.Close()
$ps.Dispose()

现在我们来做相反的事情,创建一个函数来查找仅由你创建的用户变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Get-UserVariable ($Name = '*')
{
# these variables may exist in certain environments (like ISE, or after use of foreach)
$special = 'ps','psise','psunsupportedconsoleapplications', 'foreach', 'profile'

$ps = [PowerShell]::Create()
$null = $ps.AddScript('$null=$host;Get-Variable')
$reserved = $ps.Invoke() |
Select-Object -ExpandProperty Name
$ps.Runspace.Close()
$ps.Dispose()
Get-Variable -Scope Global |
Where-Object Name -like $Name |
Where-Object { $reserved -notcontains $_.Name } |
Where-Object { $special -notcontains $_.Name } |
Where-Object Name
}

现在可以很容易查找所有由您(或您的脚本)创建并仍然停留在内存中的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> Get-UserVariable

Name Value
---- -----
hash {Extensions, Link, Options, GPOLink...}
prop lParam
reserved {$, ?, ^, args...}
result {System.Management.Automation.PSVariable, System.Management.Automation.Ques...
varCount 43



PS> Get-UserVariable -Name pr*

Name Value
---- -----
prop lParam

如果要清理您的运行空间,您可以用一行代码清除所有变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS> Get-UserVariable

Name Value
---- -----
hash {Extensions, Link, Options, GPOLink...}
key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\H...
prop lParam
reserved {$, ?, ^, args...}
result {System.Management.Automation.PSVariable, System.Management.Automation.Ques...
varCount 43



PS> Get-UserVariable | Remove-Variable

PS> Get-UserVariable

PS>

PowerShell 技能连载 - 查找 PowerShell 缺省变量(第三部分)

在前一个技能中我们演示了如何用类似如下的方法来查找内置的 PowerShell 变量:

1
2
3
4
5
$ps = [PowerShell]::Create()
$null = $ps.AddScript('$null=$host;Get-Variable')
$ps.Invoke()
$ps.Runspace.Close()
$ps.Dispose()

显然,这段代码还是漏了一些不是由 PowerShell 核心引擎创建的变量,而是由具体宿主加入的变量,例如 powershell.exe,或者 ISE。这些缺失的变量需要手工添加。幸好不是很多:

1
2
3
4
5
6
7
8
9
10
$ps = [PowerShell]::Create()
$null = $ps.AddScript('$null=$host;Get-Variable')
[System.Collections.ArrayList]$result = $ps.Invoke() |
Select-Object -ExpandProperty Name
$ps.Runspace.Close()
$ps.Dispose()

# add host-specific variables
$special = 'ps','psise','psunsupportedconsoleapplications', 'foreach', 'profile'
$result.AddRange($special)

现在这段代码能够获取包含所有保留 PowerShell 变量的列表,并且如果我们还缺少了某些变量,只需要将它们添加到 $special 列表即可。

顺便说一下,这段代码完美地演示了如何用 [System.Collections.ArrayList] 来创建一个更好的数组。跟常规的 [Object[]] 数组相比,ArrayList 对象拥有例如 AddRange(),能快速批量加入多个元素,等其它方法。

PowerShell 技能连载 - 查找 PowerShell 缺省变量(第二部分)

在前一个技能里我们解释了如何使用独立全新的 PowerShell 实例来获取所有缺省变量。当您仔细查看这些变量,会发现还是丢失了某些变量。

以下是一个稍微修改过的版本,名为 Get-BuiltInPSVariable,能返回所有保留的 PowerShell 变量:

1
2
3
4
5
6
7
8
9
10
11
12
function Get-BuiltInPSVariable($Name='*')
{
# create a new PowerShell
$ps = [PowerShell]::Create()
# get all variables inside of it
$null = $ps.AddScript('$null=$host;Get-Variable')
$ps.Invoke() |
Where-Object Name -like $Name
# dispose new PowerShell
$ps.Runspace.Close()
$ps.Dispose()
}

为了不遗漏任何一个内置的 PowerShell 变量,这个做法使用了 AddScript() 方法来代替 AddCommand(),来执行多于一条命令。有一些 PowerShell 变量要等待至少一条命令执行之后才创建。

您现在可以获取所有的 PowerShell 内置变量,或搜索指定的变量:

1
2
3
4
5
6
7
8
9
10
11
12
PS> Get-BuiltInPSVariable -Name *pref*

Name Value
---- -----
ConfirmPreference High
DebugPreference SilentlyContinue
ErrorActionPreference Continue
InformationPreference SilentlyContinue
ProgressPreference Continue
VerbosePreference SilentlyContinue
WarningPreference Continue
WhatIfPreference False

PowerShell 技能连载 - 查找 PowerShell 缺省变量(第一部分)

有些时候识别出 PowerShell 管理的缺省变量十分有用,这样能帮您区分内置的变量和自定义的变量。Get-Variable 总是输出所有的变量。

以下是一个简单的技巧,使用一个独立、全新的 PowerShell 运行空间来确定内置的 PowerShell 变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
# create a new PowerShell
$ps = [PowerShell]::Create()
# get all variables inside of it
$null = $ps.AddCommand('Get-Variable')
$result = $ps.Invoke()
# dispose new PowerShell
$ps.Runspace.Close()
$ps.Dispose()

# check results
$varCount = $result.Count
Write-Warning "Found $varCount variables."
$result | Out-GridView

当您运行这段代码时,该代码输出找到的变量数量,以及这些变量。