PowerShell 技能连载 - 下载 PowerShell 语言参考(或任意文件)

Invoke-WebRequest 可以轻松下载文件。下面的代码下载由 PowerShell Magazine 发布的PowerShell语言参考,并使用关联的程序将其打开:

1
2
3
4
5
6
7
8
9
10
11
12
13
$url = "https://download.microsoft.com/download/4/3/1/43113f44-548b-4dea-b471-0c2c8578fbf8/powershell_langref_v4.pdf"

# get desktop path
$desktop = [Environment]::GetFolderPath('Desktop')
$destination = "$desktop\langref.pdf"

# enable TLS1.2 for HTTPS connections
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

# download PDF file
Invoke-WebRequest -Uri $url -OutFile $destination -UseBasicParsing
# open downloaded file in associated program
Invoke-Item -Path $destination

请注意这段代码如何启用 TLS 1.2。是否需要该协议取决于您的连接类型和防火墙。在以上示例中,并不是必须的。对于其它下载链接,可能是必须的。

PowerShell 技能连载 - 在 Windows PowerShell 和 PowerShell Core 中共享模块

许多 PowerShell 用户开始研究 PowerShell 7,并与内置的 Windows PowerShell 并行运行。

两个 PowerShell 版本都在自己的位置维护各自的 PowerShell 模块。因此当您添加新模块(即通过 Install-Module)时,需要分别对两个版本的 PowerShell 执行此操作。

不过 Windows PowerShell 和 PowerShell 7 共享同一个文件夹路径:尽管是 Windows PowerShell 引入了该目录,但 PowerShell 7 也会检查此文件夹并自动加载位于其中的模块:

C:\Program Files\WindowsPowerShell\Modules

要在两个 PowerShell 版本中同时使用某个模块,请确保将模块复制到此文件夹。使用 Install-Module 时,请使用 -Scope AllUsers(或忽略整个参数)。

由于该文件夹影响所有用户,因此该文件夹受到保护,并且您需要管理员权限才能向其中添加模块。

请注意,PowerShell 还会在另外一个路径中查找模块:

C:\Windows\system32\WindowsPowerShell\v1.0\Modules

这是所有 Microsoft 模块所在的位置。该路径也会和 PowerShell 7 共享。

PowerShell 技能连载 - 安装 ActiveDirectory 模块

这是一个对所有处理 Active Directory 的 PowerShell 用户的好消息:在最新的 Windows 10 版本(企业版、专业版)中,Microsoft 提供了 RSAT 工具,因此不需要另外下载。要将使用 AD 的 PowerShell 命令,只需启用 RSAT 功能(请参见下文)。

此外,PowerShell 7 终于原生支持 Active Directory 模块!如果您开始将新的 PowerShell 与Windows PowerShell 并行使用,则现在可以在 PowerShell 7 中使用以前仅在 Windows PowerShell 中工作的所有 AD cmdlet。

在提升权限的 PowerShell 中运行此命令,以查看可用的 RSAT 组件:

1
2
Get-WindowsCapability -Online |
Where-Object Name -like Rsat*

要使用 Active Directory 和组策略 PowerShell 模块,请启用 RSAT 功能

1
2
3
4
Get-WindowsCapability -Online |
Where-Object Name -like Rsat* |
Where-Object State -ne Installed |
Add-WindowsCapability -Online

完成后,Windows PowerShell 中将同时提供 Active DirectoryGroupPolicy PowerShell 模块。在 PowerShell 7 中,只能使用 ActiveDirectory 模块。

1
2
3
4
5
6
7
8
9
10
PS> Get-Module -Name ActiveDirectory, GroupPolicy -ListAvailable


Directory: C:\Windows\system32\WindowsPowerShell\v1.0\Modules


ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.1.0 ActiveDirectory {Add-ADCentralAccessPolicyMember, Add-ADCom...
Manifest 1.0.0.0 GroupPolicy {Backup-GPO, Block-GPInheritance, Copy-GPO...

PowerShell 技能连载 - 测试等待重启

当 Windows 安装了更新或对操作系统做了相关改变,改变可能需要在重启以后才能生效。当有一个挂起的重启时,操作系统可能不能被完全保护,而且可能无法安装其它软件。

可以通过测试指定的注册表项来确定是否有挂起的重启:

1
2
3
$rebootRequired = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending"

"Pending reboot: $rebootRequired"

如果 $rebootRequired 的值是 $true,那么就存在一个挂起的重启。

PowerShell 技能连载 - 隐藏启动 PowerShell 脚本

没有内置的方法可以隐藏启动 PowerShell 脚本:即使您运行 powershell.exe 并指定 -WindowStyle Hidden,PowerShell 控制台仍将一闪而过。

要隐藏启动 PowerShell 脚本,可以使用 VBScript:

1
2
3
4
5
6
Set objShell = CreateObject("WScript.Shell")
path = WScript.Arguments(0)

command = "powershell -noprofile -windowstyle hidden -executionpolicy bypass -file """ & path & """"

objShell.Run command,0

将 test.vbs 保存,然后确保以 ANSI 编码保存(用记事本并在另存为对话框的地步下拉列表中选择编码)。VBS 无法处理 UTF8 编码的脚本。当您试图运行这样的脚本时,会得到一个非法字符的异常。

要隐藏启动一个 PowerShell 脚本,可以运行这行命令:

请注意,虽然 wscript.exe 隐藏了 PowerShell 的控制台窗口,但您打开任何 WPF窗口(例如使用 Out-GridView)时,将继续工作并且正常显示。

1
Wscript.exe c:\pathtovbs.vbs c:\pathtoPS1file.ps1

PowerShell 技能连载 - 杀死无响应的进程

Get-Process 返回的进程对象可以判断该进程当前是否正在响应窗口消息,从而是否正在响应用户请求。此行代码返回当前的 PowerShell 进程的 “Responding” 属性:

1
2
3
4
5
PS> Get-Process -Id $Pid | Select-Object *respond*

Responding
----------
True

进程偶尔会变得无响应是很常见的,例如由于高负载和较弱的软件体系结构。当某个进程在较长时间内没有响应时,就会“挂起”,会让用户发疯。

这行代码列出了当前未响应的进程:

1
2
3
4
5
6
7
8
9
PS> Get-Process | Where-Object { !$_.Responding }

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
560 28 21752 580 0.38 18544 1 Calculator
915 65 26660 2528 0.39 14244 1 MicrosoftEdge
488 21 7108 7988 0.09 13400 1 MicrosoftEdgeCP
543 27 16148 520 0.31 21200 1 Time
1132 77 63544 836 1.55 15212 1 WinStore.App

请注意,此列表可能包括已启动但不再使用的 Windows 应用程序。由于属性“正在响应”仅描述当前状态,因此它无法确定进程多长时间未响应。

如果您想汇报(或杀死)一段时间未响应的所有进程,则需要自己进行反复检查并跟踪结果。

下面的代码在网格视图窗口中列出了 3 秒钟没有响应的所有进程。然后,用户可以选择一个或多个要杀死的进程(按住 CTRL 键选择多个进程)。

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
# report processes hanging for more than 3 seconds
$timeout = 3

# use a hash table to keep track of processes
$hash = @{}

# use an endless loop and test processes
do
{
Get-Process |
# look at processes with a window only
Where-Object MainWindowTitle |
ForEach-Object {
# use process ID as key to the hash table
$key = $_.id
# if the process is responding, reset the counter
if ($_.Responding)
{
$hash[$key] = 0
}
# else, increment the counter by one
else
{
$hash[$key]++
}
}

# copy the hash table keys so that the collection can be
# modified
$keys = @($hash.Keys).Clone()

# emit all processes hanging for longer than $timeout seconds
# look at all processes monitored
$keys |
# take the ones not responding for the time specified in $timeout
Where-Object { $hash[$_] -gt $timeout } |
ForEach-Object {
# reset the counter (in case you choose not to kill them)
$hash[$_] = 0
# emit the process for the process ID on record
Get-Process -id $_
} |
# exclude those that already exited
Where-Object { $_.HasExited -eq $false } |
# show properties
Select-Object -Property Id, Name, StartTime, HasExited |
# show hanging processes. The process(es) selected by the user will be killed
Out-GridView -Title "Select apps to kill that are hanging for more than $timeout seconds" -PassThru |
# kill selected processes
Stop-Process -Force

# sleep for a second
Start-Sleep -Seconds 1

} while ($true)

当然,您可以轻松更改代码以生成挂起的进程报表。只需将 Stop-Process 替换为您想要执行的任何操作,即可使用 Add-Content 将流程写入日志文件。要避免一次次地记录相同的进程,您可能想要添加某种黑名单,以跟踪已经记录的进程。

PowerShell 技能连载 - 测试网络连接(第 2 部分)

如果您想测试一台特定的计算机或 URL 是否在线,几十年来一直使用 ping 请求 (ICMP)。最近,许多服务器和防火墙关闭了 ICMP 以减少攻击面。默认情况下,Test-NetConnection 使用 ICMP,因此在不响应 ICMP 的计算机上会失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> Test-NetConnection -ComputerName microsoft.com
WARNING: Ping to 40.76.4.15 failed with status: TimedOut
WARNING: Ping to 40.112.72.205 failed with status: TimedOut
WARNING: Ping to 13.77.161.179 failed with status: TimedOut
WARNING: Ping to 40.113.200.201 failed with status: TimedOut
WARNING: Ping to 104.215.148.63 failed with status: TimedOut


ComputerName : microsoft.com
RemoteAddress : 40.76.4.15
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : False
PingReplyDetails (RTT) : 0 ms

Test-NetConnection 内置了另一种计算机无法回避的测试:端口测试。端口提供对特定服务的访问,因此端口必须在任何公共服务可用。对于 Web 服务器,可以使用 80 端口,例如:

1
2
3
4
5
6
7
8
9
PS> Test-NetConnection -ComputerName microsoft.com -Port 80


ComputerName : microsoft.com
RemoteAddress : 104.215.148.63
RemotePort : 80
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
TcpTestSucceeded : True

以下是常用的端口列表:

HTTP: Port 80
HTTPS: Port 443
FTP: Port 21
FTPS/SSH: Port 22
TELNET: Port 23
POP3: Port 110
POP3 SSL: Port 995
IMAP: Port 143
IMAP SSL: Port 993
WMI: Port 135
RDP: Port 3389
DNS: Port 53
DHCP: Port 67, 68
SMB/NetBIOS: 139
NetBIOS over TCP: 445
PowerShell Remoting: 5985
PowerShell Remoting HTTPS: 5986

PowerShell 技能连载 - 测试网络连接(第 1 部分)

PowerShell 随附了 Test-NetConnection 命令,它可以像复杂的 ping 工具一样工作。默认情况下,您可以这样 ping 计算机:

1
2
3
4
5
6
7
8
9
PS> Test-NetConnection -ComputerName powershell.one


ComputerName : powershell.one
RemoteAddress : 104.18.46.88
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : True
PingReplyDetails (RTT) : 26 ms

使用 -TraceRoute,它包括路由跟踪,列出了用于传播到目标的所有网络节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> Test-NetConnection -ComputerName powershell.one -TraceRoute


ComputerName : powershell.one
RemoteAddress : 104.18.46.88
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : True
PingReplyDetails (RTT) : 25 ms
TraceRoute : 192.168.2.1
62.155.243.83
62.154.2.185
62.157.250.38
195.22.215.192
195.22.215.59
104.18.46.88

PowerShell 技能连载 - 探索即插即用设备(第 4 部分)

在前面的技能中,我们研究了 UPnP.UPnPDeviceFinder 以及如何识别网络中的设备。让我们看一些用例。显然,这些用例是您的灵感来源,因为您可以做的事情完全取决于连接到网络的实际设备。

我的第一个用例是管理 NAS 设备,名为 Synology Disk Station。可以通过 WEB 界面进行管理,但我总是会忘记 URL,当然 URL 和端口会根据配置而有所不同。

这是一种搜索任何磁盘站并自动打开其 Web 界面的方法。由于所有连接数据都是从设备中检索的,因此即使 IP 地址更改或您配置了别的端口,此操作也仍然有效。

1
2
3
4
5
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Where-Object ManufacturerName -eq 'Synology' |
Select-Object -ExpandProperty PresentationUrl |
ForEach-Object { Start-Process -FilePath $_ }

注意:如果您没有 Synology 磁盘站,请查看您拥有的其他设备。只需查看 FindByType() 返回的数据,然后根据需要定制 Where-Object

由于该代码枚举了所有 UPnP 设备,因此需要 10 到 20 秒。如果要加快处理速度,可以找出所用设备的 UDN(唯一名称),以后再使用这些 UDN:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Where-Object ManufacturerName -eq 'Synology' |
Select-Object -ExpandProperty UniqueDeviceName

UDN 是唯一的,并且因设备而异,因此,如果您需要多次访问同一设备,则此方法才有意义。在我的情况下,UDN 是 uuid:7379AA6F-6473-6D00-0000-001132283f5e,现在打开 Web 界面的速度要快得多:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("uuid:7379AA6F-6473-6D00-0000-001132283f5e", 0) |
Select-Object -ExpandProperty PresentationUrl |
Foreach-Object { Start-Process -FilePath $_ }

这引出了我的第二个使用场景:我正在使用 Philips Hue 系统管理家里的灯光。飞利浦提供了丰富的 REST API 来实现自动化。您所需要的是网桥的 IP 地址。

这段代码将列出本地网络中所有 Philips Hue 网桥的 IP 地址:

``powershell
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType(“upnp:rootdevice”, 0) |
Where-Object Description -like ‘Philips hue*’ |
Select-Object -ExpandProperty FriendlyName |
ForEach-Object {
if ($_ -match ‘(?\w*).?((?.))‘)
{
$null = $matches.Remove(0)
[PSCustomObject]$matches
}
}


该代码查找根设备,其描述以 "Philips hue" 开头的根设备,然后使用正则表达式拆分 "FriendlyName" 属性的内容并返回网桥名称及其 IP 地址。

就我而言,结果如下所示:

    IP            BridgeName
    --            ----------
    192.168.22.10 BridgeOne
    192.168.23.16 BridgeWork

<!--本文国际来源:[Exploring Plug&amp;Play Devices (Part 4)](https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/exploring-plug-play-devices-part-4)-->

PowerShell 技能连载 - 探索即插即用设备(第 3 部分)

在前一个技能中我们演示了如何使用 UPnP.UPnPDeviceFinder 来查找网络中的设备。您已了解到如何枚举所有的根设备 (“upnp:rootdevice“),以及如何通过设备的唯一标识符来访问设备。

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("upnp:rootdevice", 0) | Out-GridView

在这一部分中,让我们完成搜索类型,看看如何枚举所有设备(而不仅仅是根设备),以及如何枚举设备类型组。

要列出所有设备,请使用 “ssdb:all" 而不是 "upnp:rootdevice`”:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) | Out-GridView

结果包括根设备(“IsRootDevice” 为 $true,“ParentDevice”为空)以及所有子设备(“IsRootDevice” 为 $false,“ParentDevice” 指向该设备链接到的上级设备)。

在 “UniqueDeviceName” 中,可以找到可用于直接访问设备的唯一设备名称:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByUDN("uuid:...", 0)

每个设备都属于一个类别,该类别在“Type”中显示。要查看类型列表,请尝试以下操作:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Select-Object -ExpandProperty Type |
Sort-Object -Unique

结果取决于网络中找到的设备。这是我得到的清单:

urn:dial-multiscreen-org:device:dial:1
urn:schemas-upnp-org:device:Basic:1
urn:schemas-upnp-org:device:InternetGatewayDevice:1
urn:schemas-upnp-org:device:MediaRenderer:1
urn:schemas-upnp-org:device:WANConnectionDevice:1
urn:schemas-upnp-org:device:WANDevice:1

要查找特定类型的所有设备,请将该类型与 FindByType() 一起使用:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("urn:schemas-upnp-org:device:InternetGatewayDevice:1", 0)

最后一点:设备是否响应组搜索,甚至是“upnp:rootdevice”,都取决于设备及其实现。在我的场景中,即使存在那种类型的设备,我也无法获得“Basic”和“WANDevice”组的结果。

如果找不到特定设备,请尝试适用于所有设备的唯一搜索,然后通过“ssdp:all”列出所有设备。如果设备现在显示出来,则可以通过 Where-Object 使用“ssdp:all”和客户端过滤,或者通过查找唯一的设备标识符并通过其 UDN 和 FindByUDN() 直接访问特定设备来加快搜索速度。