PowerShell 技能连载 - 订阅锁定和解锁事件

当一个用户在 Windows 系统中锁定了会话,会发出一个事件。当解锁会话的时候会发出另一个事件。两个事件都可以触发 PowerShell 代码,这样可以实现锁定和解锁系统时运行任意 PowerShell 代码。

以下两个函数演示该功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Start-Fun {
$null = Register-ObjectEvent -InputObject ([Microsoft.Win32.SystemEvents]) -EventName "SessionSwitch" -Action {
Add-Type -AssemblyName System.Speech

$synthesizer = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer

switch($event.SourceEventArgs.Reason) {
'SessionLock' { $synthesizer.Speak("Bye bye $env:username!") }
'SessionUnlock' { $synthesizer.Speak("Nice to see you again $env:username!") }
}
}
}

function End-Fun {
$events = Get-EventSubscriber | Where-Object { $_.SourceObject -eq [Microsoft.Win32.SystemEvents] }
$jobs = $events | Select-Object -ExpandProperty Action
$events | Unregister-Event
$jobs | Remove-Job
}

运行以上代码,然后运行 Start-Fun 将代码附加到事件上。当您锁定或解锁电脑时,您将会得到一个 PowerShell 发出的语音提示。当然,您可以做其它事情,例如将设备设为节能模式。

运行 End-Fun 来移除事件订阅。

PowerShell 技能连载 - Custom Action for Unknown Commands

每当输入一个无法被 PowerShell 搜索到的命令名时,它都可以通过您定义的自定义操作来扩展命令搜索。

以下是一个快速有趣的示例,演示这个概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ExecutionContext.InvokeCommand.CommandNotFoundAction =
{
# second argument is the command that was missing:
$p = $args[1]
# do not try and find it elsewhere
$p.StopSearch = $true

$command = $p.CommandName

# output audio message (make sure your audio is turned up)
$sapi = New-Object -ComObject Sapi.SpVoice
$sapi.Speak("Command $command not found.")
}

当运行完以上代码,然后再次运行一个肯定不存在的命令时,您将会听到一个语音提示(假设您的音量是开启的且扬声器已打开)。当 PowerShell 无法找到一个命令,它会查找所有赋值给 CommandNotFoundAction 的脚本块并执行它。

这个点子是用于改进命令发现。例如,您可能会花时间整理一个流行命令列表和发布这些命令的模块名称。然后,您的自定义脚本块会尝试并查找列表中缺失的命令,并让用户知道缺失的模块名——或者甚至自动下载并安装该模块。

不幸的是,自从 PowerShell 提供了该功能之后,社区中并没有人实现了该功能。现在您可能会有兴趣发明一些比上面发出语音更复杂的功能。

PowerShell 技能连载 - 管理文件共享

Windows 操作系统自带了 “Storage” PowerShell 模块,它可以同时用于 Windows PowerShell 和 PowerShell 7。

这个模块可以管理许多东西,其中之一是文件共享,不过需要管理员特权来运行以下命令。

要获取文件共享的清单(可以通过网络访问的本地文件夹),请试着执行以下代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
PS C:\> Get-FileShare

Name HealthStatus OperationalStatus
---- ------------ -----------------
ADMIN$ Healthy Online
C$ Healthy Online
print$ Healthy Online



PS C:\> Get-FileShare -Name c$

Name HealthStatus OperationalStatus
---- ------------ -----------------
C$ Healthy Online


PS C:\> Get-FileShare -Name c$ | Select-Object -Property *


HealthStatus : Healthy
OperationalStatus : Online
ShareState : Online
FileSharingProtocol : SMB
ObjectId : {1}\\DELL7390\root/Microsoft/Windows/Storage/Providers_v2\WSP_FileShare.ObjectId="{c0c2f698-c81d-11e9-9f6f-80
6e6f6e6963}:FX:SMB||*||C$"
PassThroughClass :
PassThroughIds :
PassThroughNamespace :
PassThroughServer :
UniqueId : smb|DELL7390/C$
ContinuouslyAvailable : False
Description : Standardfreigabe
EncryptData : False
Name : C$
VolumeRelativePath : \
PSComputerName :
CimClass : ROOT/Microsoft/Windows/Storage:MSFT_FileShare
CimInstanceProperties : {ObjectId, PassThroughClass, PassThroughIds, PassThroughNamespace...}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties

类似地,其它动词可以执行相关的任务,例如改变一个已有的共享 (Set) 或者创建一个新的共享 (New):

1
2
3
4
5
6
7
8
9
PS C:\> Get-Command -Noun FileShare

CommandType Name Version Source
----------- ---- ------- ------
Function Debug-FileShare 2.0.0.0 Storage
Function Get-FileShare 2.0.0.0 Storage
Function New-FileShare 2.0.0.0 Storage
Function Remove-FileShare 2.0.0.0 Storage
Function Set-FileShare 2.0.0.0 Storage

PowerShell 技能连载 - 在任务栏按钮显示警告状态

当您的脚本需要注意,例如需要用户输入时,我们可以将 Windows 任务栏中的按钮变为橙色,这样用户可以立即知道需要检查您的脚本。

您所需的只是这个模块:

1
Install-Module -Name PsoProgressButton -Scope CurrentUser

下一步,设置一个进度值并且将它设置成红色:

1
2
Set-PsoButtonProgressState -ProgressState Paused
Set-PsoButtonProgressValue -CurrentValue 100

要关闭指示,请运行以下脚本:

1
PS> Set-PsoButtonProgressState -ProgressState NoProgress

PowerShell 技能连载 - 在任务栏按钮显示错误状态

当您的脚本执行时发生错误,如果能通过任务栏按钮显示错误状态,那么是再好不过的了。如果一个任务栏按钮显示红色,您可以立即知道该关注您的脚本。

您所需的只是这个模块:

1
Install-Module -Name PsoProgressButton -Scope CurrentUser

然后,设置一个进度值并且将它设置为红色:

1
2
Set-PsoButtonProgressState -ProgressState Error
Set-PsoButtonProgressValue -CurrentValue 100

要关闭指示,请运行以下代码:

1
PS> Set-PsoButtonProgressState -ProgressState NoProgress

PowerShell 技能连载 - 在任务栏按钮中显示不确定的进度

有时,您不知道脚本的确切进度,但您仍然想通知用户您的脚本“忙”。如果您在 Windows 上运行 PowerShell 脚本,则可以使用任务栏按钮显示不确定的进度条。该进度条将“永远”运行,直到您将其关闭。

您需要的只是安装此模块:

1
Install-Module -Name PsoProgressButton -Scope CurrentUser

接下来,您可以运行下面的命令以打开不确定的进度条。它在代表您的运行 PowerShell 脚本的任务栏按钮内显示:

1
PS> Set-PsoButtonProgressState -ProgressState Indeterminate

要关闭该指示器,请运行以下操作:

1
PS> Set-PsoButtonProgressState -ProgressState NoProgress

PowerShell 技能连载 - Showing Progress in Taskbar Buttons

如果您在 Windows 上运行 PowerShell 脚本,则可以将任务栏按钮用作进度指示器。您需要的只是安装此模块:

1
Install-Module -Name PsoProgressButton -Scope CurrentUser

接下来,您可以运行下面的命令以将任务栏按钮内的进度栏设置为 0 到 100 之间的值。这段代码将指示器设置为 50%:

1
PS> Set-PsoButtonProgressValue -CurrentValue 50

要关闭指示器,请运行以下代码:

1
PS> Set-PsoButtonProgressState -ProgressState NoProgress

PowerShell 技能连载 - 检测多语言在线文档(第 2 部分)

要如何检测一份在线文档支持哪些语言?

如果 URL 使用语言 ID,则很容易创建包含所有可用语言 ID 的 URL 列表。这就是我们到目前为止第一部分中所做的:

1
$list  =  RL  -f

在第二部分中,我们现在确定列表中哪些 URL 实际中可用。但是,只是通过 Invoke-Webrequest 访问该 URL 是不够的:

1
$list  =  RL  -f

事实证明,所有 URL 都能正常地访问 Microsoft WEB 服务器,并返回状态 “OK”(包括不存在的文档地址):

1
PS> New-SCode

这是因为 Microsoft WEB 服务器(与许多其它的一样)首先接受所有 URL。然后,在内部,WEB 服务器弄清楚下一步该怎么做,并将新的 URL 返回到浏览器中。可能返回的是原始的 URL(如果 WEB 服务器找到了资源),也可能是一个全新的URL,例如通用搜索站点或自定义的“未找到”通知。状态 “OK” 与 URL 的有效性并没有关联。

您实际上可以通过禁止自动重定向来查看内部工作过程。对 Invoke-WebRequest 命令添加参数 "-MaximumRedirection 0 -ErrorAction Ignore"

1
$list  =  RL  -f

现在,您看到 Web 服务器如何告诉浏览器,URL 跳转至其他地方,有效地将浏览器重定向到新的 URL。

检查 URL 是否存在,取决于特定的 Web 服务器的工作原理。在微软的例子中,事实证明有效的 URL 会导致单次重定向,而无效的 URL 会导致多次重定向。用重定向的次次数是否为一次,可以区分合法和非法的 URL。

这是最终解决方案,它还支持实时进度条。

它在网格视图窗口中显示可用的本地化在线文档,您可以选择一个或多个以在浏览器中显示。您也可以以 $result 的形式获取结果,然后将其打印到 PDF 并将其提交给其它语言的员工。

1
$list  =  $h.Keys  |  ForEach-Object { $URL  -f

PowerShell 技能连载 - 小心使用数组

使用 PowerShell,您永远不知道 cmdlet 是返回数组还是单个对象。这是因为一旦命令返回多个项目,PowerShell 就会自动包装成数组:

1
2
3
4
5
6
7
# no array:
$test = Get-Service -Name Spooler
$test -is [Array]

# array:
$test = Get-Service -Name S*
$test -is [Array]

理解这一点很重要,因为这意味着运行时条件可以确定变量的类型。这可能会导致意外情况。以下是说明问题的一个示例:

下面的代码返回以 “C” 开头的所有服务的名称,然后取第一个服务名称。这是有可能的,因为不仅有一个以 “C” 开头的服务,因此 PowerShell 返回 $ServiceNames 中的数组,然后您可以在此数组中使用数字索引来选择特定的元素:

1
2
3
4
5
6
7
$Name = 'c*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

但是,您不能假设 $servicenames 始终是一个数组。如果在运行时只有一项与您的请求匹配的服务,则结果不再是一个数组,而是直接是服务名称。

为什么(以及何时)这有关系?当您的代码采用数组特定功能的那一刻,它就十分重要。因为在某些情况下可能不存在该功能或行为不同。

为了说明这一点,下面的代码现在列出了以 “cry” 开头的所有服务。只有一项服务与请求匹配。因此,$servicenames 不再是一个数组。现在是一个字符串。当您在字符串上使用索引时,您会得到该字符串中的一个字母。

现在,相同的代码返回的是一个字符,而不是服务名称:

1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

这些示例似乎有些人为构造,但是您可以在许多难以找到的脚本错误的内部中找到潜在的问题。这就是为什么重要的是要始终确保您在代码使用数组功能时获得的真正是一个数组。

确保您获得数组的一种简单方法是构造器 @():括号中的任何内容都以数组的形式返回。这就是为什么下面代码有效的原因,无论命令是否返回一个或多个结果:

1
2
3
4
5
6
7
8

$Name = 'cry*'

# get service names
$servicenames = @(Get-Service -Name $Name | Select-Object -ExpandProperty Name)

# get first service name
$servicenames[0]
1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
[array]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]
1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
[string[]]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

但是,[array] 更容易使用,因为无论数据类型如何,它总是可以使用,并且 [array] 对于不熟悉类型的用户也更容易理解。

PowerShell 技能连载 - Determining Language Packs (Part 3)

在本系列的第 2 部分中,您已经看到了使用 WMI 与使用命令行工具(如 dism.exe)相比,使用 WMI 查询安装的操作系统语言列表的速度要容易得多,且更快。但是,使用 WMI 仍然需要您知道适当的 WMI 类名。

这就是为什么 PowerShell 提供一个全能的 cmdlet Get-ComputerInfo 的原因。它为您查询各种与计算机相关的信息,然后将其与您联系。 我们也可以通过这种方法解决这个问题:

1
2
$a = Get-ComputerInfo
$a.OsMuiLanguages

但不幸的是,Get-ComputerInfo 总是查询完整的信息集,这使得它很缓慢。但总比没有好,甚至比 dism.exe 更好,而第 2 部分的直接 WMI 查询仍然是效率最高的方法。