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

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

PowerShell 技能连载 - 查找 PowerShell 类

从 PowerShell 5 开始,您可以定义 PowerShell 类。它们是动态定义的,并且存在于内存中。那么要如何知道这些类的名字?

我们首先定义一个简单的,没有任何内容的类:

1
2
3
4
class TestClass
{

}

如何确认内存中确实存在一个名为 “TestClass” 的类?以下是一个名为 Get-PSClass 的工具函数:

1
2
3
4
5
6
7
8
9
10
11
function Get-PSClass($Name = '*')
{
[AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GetCustomAttributes($false) |
Where-Object { $_ -is [System.Management.Automation.DynamicClassImplementationAssemblyAttribute]} } |
ForEach-Object { $_.GetTypes() |
Where-Object IsPublic |
Where-Object { $_.Name -like $Name } |
Select-Object -ExpandProperty Name
}
}

执行这个函数后,它会返回当前内存中所有定义的 PowerShell 类(在我们的 PowerShell 例子中,在前几个技能实验中有好几个 PowerShell 类):

1
2
3
4
5
6
PS> Get-PSClass
HelperStuff
Employee
TestClass

PS>

您也可以显示地测试一个类名:

1
2
3
4
5
6
7
8
PS> Get-PSClass -Name TestClass
TestClass

PS> (Get-PSClass -Name TestClass) -ne $null
True

PS> (Get-PSClass -Name TestClassNotExisting) -ne $null
False

您也可以使用通配符。一下代码将返回所有以 “A” 至 “H” 字母开头的类:

1
2
3
PS> Get-PSClass -Name '[A-H]*'
HelperStuff
Employee

PowerShell 技能连载 - 使用 PowerShell 类(二)

从 PowerShell 5 开始,您可以定义 PowerShell 类。您可以使用类来创建新对象,并通过创建一个或多个构造函数,您可以方便地初始化新创建的对象。

让我们看看效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Employee
{
[int]$Id
[string]$Name

Employee([int]$Id, [string]$Name)
{
$this.Id = $Id
$this.Name = $Name
}
Employee ([string]$Name)
{
$this.Id = -1
$this.Name = $Name
}
Employee ()
{
$this.Id = -1
$this.Name = 'Undefined'
}
}

这段代码运行后,将创建一个包含三个构造函数的 “Employee” 新类。以下是如何使用新类的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PS> [Employee]::new()

Id Name
-- ----
-1 Undefined



PS> [Employee]::new('Tobias')

Id Name
-- ----
-1 Tobias



PS> [Employee]::new(999, 'Tobias')

Id Name
-- ----
999 Tobias



PS>

每次调用都使用一个新的构造函数,并且该类根据需要创建相应的对象。

PowerShell 技能连载 - 使用 PowerShell 类(一)

从 PowerShell 5 开始,您可以定义类。它们有许多应用场景。一个是为有用的工具函数创建一个库来更好地整理它们。要实现这个功能,这个类要定义一些 “static” 方法。以下是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HelperStuff
{
# get first character of string and throw exception
# when string is empty or multi-line
static [char] GetFirstCharacter([string]$Text)
{
if ($Text.Length -eq 0) { throw 'String is empty' }
if ($Text.Contains("`n")) { throw 'String contains multiple lines' }
return $Text[0]
}

# get file extension in lower case
static [string] GetFileExtension([string]$Path)
{
return [Io.Path]::GetExtension($Path).ToLower()
}
}

“HelperStuff” 类定义了 “GetFirstCharacter“ 和 “GetFileExtension“ 两个静态方法。现在查找和使用这些工具函数非常方便:

1
2
3
4
5
6
7
8
9
10
PS> [HelperStuff]::GetFirstCharacter('Tobias')
T

PS> [HelperStuff]::GetFileExtension('c:\TEST.TxT')
.txt

PS> [HelperStuff]::GetFileExtension($profile)
.ps1

PS>

PowerShell 技能连载 - 读取注册表键值(临时解决办法)

在前一个技能中我们演示了 Get-ItemProperty 无法读取数据错误的注册表键值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> $key =  "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group  Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"


PS> Get-ItemProperty -Path $key
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyComma
nd


PS>

有一个变通办法,您可以使用 Get-Item 代替,来存取注册表键,这将使用它的 .NET 成员来读取所有值:

1
2
3
4
5
6
7
8
9
10
11
$key = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"

$key = Get-Item -Path $key

$hash = @{}
foreach ($prop in $key.Property)
{
$hash.$prop = $key.GetValue($prop)
}

$hash

结果看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
Name                           Value
---- -----
Extensions [{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{0F6B957E-509E-11D1-A7CC-0000F87571E3}]
Link Local
Options 0
GPOLink 1
Version 65537
GPOName Guidelines of the local group
lParam 0
DSPath LocalGPO
FileSysPath C:\WINDOWS\System32\GroupPolicy\Machine
DisplayName Guidelines of the local group

PowerShell 技能连载 - 读取注册表键值失败

有些时候,读取注册表键值可能会失败,提示奇怪的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> $key =  "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group  Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"


PS> Get-ItemProperty -Path $key
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyComma
nd


PS>

当发生这种情况时,用 regedit.exe 检查注册表发现一个或多个键值已破坏。在我们的例子中,”lParam” 的值似乎在所有的 Windows 机器中都是错误的。Regedit.exe 报告“(invalid … value)”。

在这个例子中,Get-ItemProperty 指令并不会读出任何值。您无法也排除该值:

1
2
3
4
5
6
7
8
9
10
PS>  Get-ItemProperty -Path $key -Include * -Exclude lParam
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key -Include * -Exclude lParam
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand


PS>

可以采取的措施是只读取合法的键值:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Get-ItemProperty -Path $key -Name DSPath


DSPath : LocalGPO
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersi
on\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersi
on\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}
PSChildName : 0
PSProvider : Microsoft.PowerShell.Core\Registry


PS>

PowerShell 技能连载 - 每日问候(带语音)

在前一个技能中我们解释了如何在 PowerShell 配置文件中增加个人问候。这个问候信息也可以朗读出来,假设音量打开的情况下。这对所有的 PowerShell 宿主都有效,包括 VSCode。

这将把代码增加到您的配置文件脚本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# create profile if it does not yet exist
$exists = Test-Path -Path $Profile.CurrentUserAllHosts
if (!$exists)
{
$null = New-Item -Path $Profile.CurrentUserAllHosts -ItemType File -Force
}

# add code to profile
@'
$greetings =
'Hello there!',
'Glad to see you!',
'Happy coding!',
'Have a great day!',
'May the PowerShell be with you!'

$text = $greetings | Get-Random
$null = (New-Object -COM Sapi.SpVoice).Speak($text)
'@ | Add-Content -Path $Profile.CurrentUserAllHosts -Encoding Default

要编辑用户配置文件,请运行这段代码:

1
PS> notepad $profile.CurrentUserAllHosts