PowerShell 技能连载 - 将 PowerShell 脚本作为命令(第 2 部分)

在上一个技能中,我们讨论了一种扩展 PowerShell 命令集的简易方法。通过将脚本保存到一个文件夹中,并且将文件夹添加到环境变量 $env:path 中,PowerShell 将会识别出该文件夹中的所有脚本并将它们作为新命令。

PowerShell 脚本支持和函数相同的用户参数机制。让我们看看如何将一个使用参数的新的基于脚本的命令加入 PowerShell。

将以下脚本保存到 c:\myPsCommands 目录中的 “New-Password.ps1”。您可能需要先创建该文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[CmdletBinding()]
param
(
$CapitalLetter = 4,
$Numeric = 1,
$LowerLetter = 3,
$Special = 2
)

$characters = & {
'ABCDEFGHKLMNPRSTUVWXYZ' -as [char[]] |
Get-Random -Count $CapitalLetter

'23456789'.ToCharArray() |
Get-Random -Count $Numeric

'abcdefghkmnprstuvwxyz'.ToCharArray() |
Get-Random -Count $LowerLetter

'§$%&?=#*+-'.ToCharArray() |
Get-Random -Count $Special

} | Sort-Object -Property { Get-Random }
$characters -join ''

下一步,将文件夹路径添加到 PowerShell 的命令搜索路径,例如运行这段代码:

1
PS> $env:path += ";c:\myPSCommands" 

现在您可以想普通命令一样运行存储在文件夹中的任意脚本。如果脚本的开始处有 param() 块,那么支持传入参数。当您按示例操作后,就可以得到一个名为 New-Password 的命令,用来生成复杂密码,以及通过参数帮您组合密码:

1
2
PS> New-Password -CapitalLetter 2 -Numeric 1 -LowerLetter 8 -Special 2
yx+nKfph?M8rw

PowerShell 技能连载 - 将 PowerShell 脚本作为命令(第 1 部分)

一种扩展 PowerShell 命令的简单方法是使用脚本。要将一段脚本转换为命令,请选择一个文件夹并将 PowerShell 脚本存储在该文件夹中。脚本的名字将会转化为命令名。

例如,将以下脚本以 “New-Password” 名字保存在一个文件夹中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$CapitalLetter = 4
$Numeric = 1
$LowerLetter = 3
$Special = 2

$characters = & {
'ABCDEFGHKLMNPRSTUVWXYZ' -as [char[]] |
Get-Random -Count $CapitalLetter

'23456789'.ToCharArray() |
Get-Random -Count $Numeric

'abcdefghkmnprstuvwxyz'.ToCharArray() |
Get-Random -Count $LowerLetter

'§$%&?=#*+-'.ToCharArray() |
Get-Random -Count $Special

} | Sort-Object -Property { Get-Random }
$characters -join ''

该脚本将产生一个随机的密码,然后您可以通过顶部的变量来控制组合。

要使用该脚本作为新的命令,请确保 PowerShell 包含您在搜索命令中保存脚本的文件夹。假设您将脚本保存在名为 “c:\myPsCommands” 文件夹下。然后运行以下代码将会将该文件夹添加到命令搜索路径中:

1
$env:path += ";c:\myPsCommands"

一旦您做了这个调整,就可以输入命令名 “New-Password“ 轻松运行您的脚本。本质上该脚本名称转化为一个可执行的命令名。

PowerShell 技能连载 - 导出 Edge 的 Cookie

如果您希望查找或者导出 Edge 浏览器存储的网站 cookie,PowerShell 可以帮您导出以上信息。cookie 列表实际上存储在一个 SQLite 数据库的 “Cookies” 表中。

从 PowerShellGallery.com 安装了 “ReallySimpleDatabase” 免费模块之后,连接和读取数据库十分容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
#requires -Modules ReallySimpleDatabase

<#
make sure you install the required module before you run this script:
Install-Module -Name ReallySimpleDatabase -Scope CurrentUser
#>

$path = "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Network\Cookies"

$db = Get-Database -Path $PATH

$db.InvokeSql('select * from cookies') |
Select-Object host_key, name

PowerShell 技能连载 - 研究 PowerShell 命令结果

HTML 是一种简单的格式化输出报告的方法。在这个三部曲系列中,我们首先演示如何生成 HTML 报告,然后展示一种简单的方法将 HTML 报告转为 PDF 文档。

一个简单的研究命令返回结果的方法是使用 Select-Object 显示第一个(随机的)返回结果的所有属性。以下是一个例子:

1
Get-Service | Select-Object -Property * -First 1

通过这种方法,您可以获得一个返回结果,它的所有属性都可见,并且您可以看见这些属性中的实际数值来更好地评估要使用哪些属性。

另一个研究的方法是使用 Get-Member 从更偏技术/定义的角度查看所有可用的属性:

···powershell
Get-Service | Get-Member -MemberType *property


现在,您可以查看所有返回的数据类型,以及每个定义为 "`{get;}`"(只读)或 "`{get;set;}`"(读写)的属性。
<!--本文国际来源:[Investigating PowerShell Command Results](https://blog.idera.com/database-tools/powershell/powertips/investigating-powershell-command-results/)-->

PowerShell 技能连载 - 通过 SNMP 查询高级的打印机

许多网络打印机支持使用 SNMP 查询设备信息,例如序列号、状态和纸仓中纸张的大小,以及错误信息。

在 Windows 系统中,操作系统通过 PowerShell 已经提供所有 SNMP 查询所需的组件。您所需要知道的只是打印机的 IP 地址。当然,请确保它已开机并支持 SNMP。

以下是测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
# define your printer network IP here:
$Printer_IP = '192.168.2.200'

# connect to printer:
$SNMP = New-Object -ComObject olePrn.OleSNMP
$SNMP.Open($Printer_IP,'public')

# get device description
$SNMP.Get(".1.3.6.1.2.1.25.3.2.1.3.1")
# get device serial number
$SNMP.Get(".1.3.6.1.2.1.43.5.1.1.17.1")
$SNMP.Close()

这段代码非常短小。

更有挑战性的是:除了设备描述和序列号之外您能找到哪些信息?如何知道您可以查询的剩余信息片段的 ID 号。下面是一个最常用的 ID 列表。不过,并非所有打印机都支持所有的 ID:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#region list of IDs that you can ask your printer:
# (not all IDs will work with all printers)
$OID_RAW_DATA = ".1.3.6.1.2.1.43.18.1.1"
$OID_CONSOLE_DATA = ".1.3.6.1.2.1.43.16"
$OID_CONTACT = ".1.3.6.1.2.1.1.4.0"
$OID_LOCATION = ".1.3.6.1.2.1.1.6.0"
$OID_SERIAL_NUMBER = ".1.3.6.1.2.1.43.5.1.1.17.1"
$OID_SYSTEM_DESCRIPTION = ".1.3.6.1.2.1.1.1.0"
$OID_DEVICE_DESCRIPTION = ".1.3.6.1.2.1.25.3.2.1.3.1"
$OID_DEVICE_STATE = ".1.3.6.1.2.1.25.3.2.1.5.1"
$OID_DEVICE_ERRORS = ".1.3.6.1.2.1.25.3.2.1.6.1"
$OID_UPTIME = ".1.3.6.1.2.1.1.3.0"
$OID_MEMORY_SIZE = ".1.3.6.1.2.1.25.2.2.0"
$OID_PAGE_COUNT = ".1.3.6.1.2.1.43.10.2.1.4.1.1"
$OID_HARDWARE_ADDRESS = ".1.3.6.1.2.1.2.2.1.6.1"
$OID_TRAY_1_NAME = ".1.3.6.1.2.1.43.8.2.1.13.1.1"
$OID_TRAY_1_CAPACITY = ".1.3.6.1.2.1.43.8.2.1.9.1.1"
$OID_TRAY_1_LEVEL = ".1.3.6.1.2.1.43.8.2.1.10.1.1"
$OID_TRAY_2_NAME = ".1.3.6.1.2.1.43.8.2.1.13.1.2"
$OID_TRAY_2_CAPACITY = ".1.3.6.1.2.1.43.8.2.1.9.1.2"
$OID_TRAY_2_LEVEL = ".1.3.6.1.2.1.43.8.2.1.10.1.2"
$OID_TRAY_3_NAME = ".1.3.6.1.2.1.43.8.2.1.13.1.3"
$OID_TRAY_3_CAPACITY = ".1.3.6.1.2.1.43.8.2.1.9.1.3"
$OID_TRAY_3_LEVEL = ".1.3.6.1.2.1.43.8.2.1.10.1.3"
$OID_TRAY_4_NAME = ".1.3.6.1.2.1.43.8.2.1.13.1.4"
$OID_TRAY_4_CAPACITY = ".1.3.6.1.2.1.43.8.2.1.9.1.4"
$OID_TRAY_4_LEVEL = ".1.3.6.1.2.1.43.8.2.1.10.1.4"
$OID_BLACK_TONER_CARTRIDGE_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.1"
$OID_BLACK_TONER_CARTRIDGE_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.1"
$OID_BLACK_TONER_CARTRIDGE_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.1"
$OID_CYAN_TONER_CARTRIDGE_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.2"
$OID_CYAN_TONER_CARTRIDGE_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.2"
$OID_CYAN_TONER_CARTRIDGE_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.2"
$OID_MAGENTA_TONER_CARTRIDGE_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.3"
$OID_MAGENTA_TONER_CARTRIDGE_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.3"
$OID_MAGENTA_TONER_CARTRIDGE_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.3"
$OID_YELLOW_TONER_CARTRIDGE_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.4"
$OID_YELLOW_TONER_CARTRIDGE_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.4"
$OID_YELLOW_TONER_CARTRIDGE_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.4"
$OID_WASTE_TONER_BOX_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.5"
$OID_WASTE_TONER_BOX_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.5"
$OID_WASTE_TONER_BOX_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.5"
$OID_BELT_UNIT_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.6"
$OID_BELT_UNIT_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.6"
$OID_BELT_UNIT_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.6"
$OID_BLACK_DRUM_UNIT_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.7"
$OID_BLACK_DRUM_UNIT_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.7"
$OID_BLACK_DRUM_UNIT_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.7"
$OID_CYAN_DRUM_UNIT_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.8"
$OID_CYAN_DRUM_UNIT_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.8"
$OID_CYAN_DRUM_UNIT_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.8"
$OID_MAGENTA_DRUM_UNIT_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.9"
$OID_MAGENTA_DRUM_UNIT_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.9"
$OID_MAGENTA_DRUM_UNIT_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.9"
$OID_YELLOW_DRUM_UNIT_NAME = ".1.3.6.1.2.1.43.11.1.1.6.1.10"
$OID_YELLOW_DRUM_UNIT_CAPACITY = ".1.3.6.1.2.1.43.11.1.1.8.1.10"
$OID_YELLOW_DRUM_UNIT_LEVEL = ".1.3.6.1.2.1.43.11.1.1.9.1.10"
#endregion

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