PowerShell 技能连载 - 动态生成 IntelliSense.

在设计 PowerShell 函数时,您可以通过添加智能参数完成 IntelliSense 来提高可用性。

要编写某个参数的 IntelliSense 自动完成,您可以使用动态生成 IntelliSense 列表的 PowerShell 代码来填充函数的每个参数。当然,您使用的代码应该能快速计算出结果,IntelliSense 才不会超时。

如果需要将方法(命令)添加到对象,请通过 Add-Member 添加它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Get-MyLocalUser
{
#Content
param
(
[String]
[Parameter(Mandatory)]
[ArgumentCompleter({
# receive information about current state:
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

Get-LocalUser |
ForEach-Object {
$name = $_.Name
$desc = $_.Sid # showing SID as QuickTip
[System.Management.Automation.CompletionResult]::new($name, $name, "ParameterValue", $desc)
}
})]
$UserName
)

"You chose $UserName"

}

运行代码后,在交互式控制台中键入:

1
PS> Get-MyLocalUser -UserName

-UnerName 后按下空格,IntelliSense 介入并显示所有本地用户名。当您选择一个 IntelliSense 项目时,QuickTip 会显示用户的 SID。

此智能参数完成在 [ArgumentCompleter()] 属性中定义。 它内部的代码生成了 CompletionResult 对象,每个对象对应一个 IntelliSense 列表项。

PowerShell 技能连载 - Windows 重启后自动登录

如果您的自动化脚本需要重新启动机器,并且您希望在重新启动后自动登录,那么以下是一个快速的脚本,它将登录凭据保存到 Windows 注册表:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ask for logon credentials:
$cred = Get-Credential -Message 'Logon automatically'
$password = $cred.GetNetworkCredential().Password
$username = $cred.UserName

# save logon credentials to registry (WARNING: clear text password used):
$path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty -Path $path -Name AutoAdminLogon -Value 1
Set-ItemProperty -Path $path -Name DefaultPassword -Value $password
Set-ItemProperty -Path $path -Name DefaultUserName -Value $username

# restart machine and automatically log on: (remove -WhatIf to test-drive)
Restart-Computer -WhatIf

如果您希望每次计算机启动时自动登录,那么可以使用相同的方法。

显然,此技术可能会增加安全风险:密码以明文的方式写入注册表。请只在合适的地方谨慎使用它。

PowerShell 技能连载 - 自动化下载联想驱动程序(第 2 部分)

在前面的示例中,我们说明了如何从 Web 抓取联想驱动程序信息。在此示例中,返回的一些信息是原始数字信息:例如,表示 “3” 表示需要重启。

在这个技能中,我们想展示如何幻数 (cryptic numeric values) 转换为友好的文本。首先,让我们来看看改进的 Lenovo 函数:

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
function Get-LenovoDriver
{
param
(
$Model = '20JN',

$Os = 'Win7',

$Category = '*'
)


$restartText = @{
'0' = "No restart"
'1' = "Forced restart"
'3' = "Restart required"
'4' = "Shutdown after install"
}

$url = "https://download.lenovo.com/catalog/${model}_$os.xml"
$info = $model, $os
$url = "https://download.lenovo.com/catalog/{0}_{1}.xml" -f $info

$xml = Invoke-RestMethod -Uri $url -UseBasicParsing
$data = [xml]$xml.Substring(3)
[xml]$data = $xml.Substring(3)



$data.packages.package |
Where-Object { $_.Category -like $Category } |
ForEach-Object {
$location = $_.location
$name = $_.category
$rohdaten = Invoke-RestMethod -Uri $location -UseBasicParsing
[xml]$info = $rohdaten.Substring(3)
$filename = $info.Package.Files.Installer.File.Name
$readme = [System.IO.Path]::ChangeExtension($filename, 'txt')
$readme = $info.Package.Files.Readme.file.Name

[PSCustomObject]@{
Category = $name
Command = $info.package.ExtractCommand
Space = $info.package.DiskspaceNeeded
SpaceMB = [Math]::Round( ($info.package.DiskspaceNeeded / 1MB), 1)
Reboot = $info.package.Reboot.type
RebootFriendly = $restartText[$info.package.Reboot.type]
Version = $info.package.version
Download = "https://download.lenovo.com/pccbbs/mobiles/$filename"
ReadMe = "https://download.lenovo.com/pccbbs/mobiles/$readme"
Datum = Get-Date
}
}
}

您现在可以通过以下方式获取有关任何 Lenovo 机器的任何驱动程序更新的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\> Get-LenovoDriver -Model 20JN -Os Win10 | Out-GridView -PassThru


Category : Camera and Card Reader
Command : n1qib04w.exe /VERYSILENT /DIR=%PACKAGEPATH% /EXTRACT="YES"
Space : 6442356
SpaceMB : 6,1
Reboot : 1
RebootFriendly : Forced restart
Version : 3760
Download : https://download.lenovo.com/pccbbs/mobiles/n1qib04w.exe
ReadMe : https://download.lenovo.com/pccbbs/mobiles/n1qib04w.txt
Datum : 09.12.2021 13:21:21

属性 “Reboot“ 显示原始的幻数。 新属性 ““RebootFriendly”“ 则用友好的文本表示,在这个例子中是 “Forced restart”。

让我们解读一下源代码来了解转换的过程。

对于任何转换过程,您需要准备一个哈希表,该表将幻数映射到友好的文本:

1
2
3
4
5
6
$restartText = @{
'0' = "No restart"
'1' = "Forced restart"
'3' = "Restart required"
'4' = "Shutdown after install"
}

接下来,将数值转换为文本,与查找哈希表值一样简单:

1
2
3
4
...
Reboot = $info.package.Reboot.type
RebootFriendly = $restartText[$info.package.Reboot.type]
...

PowerShell 技能连载 - 自动化下载联想驱动程序(第 1 部分)

许多硬件供应商提供基于 Web 的自助服务门户。以下是 Lenovo 返回有关驱动程序和其他更新下载的详细信息的示例:

https://download.lenovo.com/cdrt/tools/drivermatrix/dm.html

如果您需要管理成百上千台机器或需要定期查找信息,您当然希望自动化此资源。典型的第一种方法是检查 HTML 源代码并搜索 Web 服务,或者如果一切都失败了,则使用 Invoke-RestMethod 和 session cookie 来发送表单数据并模仿用户输入。

这不仅复杂,甚至可能完全失败。例如,Lenovo 网站使用 Javascript 编写 Web 前端,因此 PowerShell 和 Invoke-RestMethod 此时起不了作用。您必须使用基于 Selenium 的测试浏览器或其他高级 Web 浏览器自动化。

但是,当您仔细查看 HTML 源代码时,您可能会遇到这样的代码:

1
$.get("../../../catalog/" + x + "_" + document.getElementById("os").value + ".xml", function(data, status)

显然,网站上显示的数据来自静态 XML 文件,因此实际上不需要对 Web 界面进行自动化操作。在这种情况下,您只需要知道这些 XML 文件的命名方式。

这是包装这些 XML 文档的 PowerShell 函数。它返回所有型号的 Lenovo 驱动程序信息,并且是完全自动化的:

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
function Get-LenovoDriver
{
param
(
$Model = '20JN',

$Os = 'Win7',

$Category = '*'
)




$url = "https://download.lenovo.com/catalog/${model}_$os.xml"
$info = $model, $os
$url = "https://download.lenovo.com/catalog/{0}_{1}.xml" -f $info

$xml = Invoke-RestMethod -Uri $url -UseBasicParsing
$data = [xml]$xml.Substring(3)
[xml]$data = $xml.Substring(3)



$data.packages.package |
Where-Object { $_.Category -like $Category } |
ForEach-Object {
$location = $_.location
$name = $_.category
$rohdaten = Invoke-RestMethod -Uri $location -UseBasicParsing
[xml]$info = $rohdaten.Substring(3)
$filename = $info.Package.Files.Installer.File.Name
[PSCustomObject]@{
Category = $name
Command = $info.package.ExtractCommand
Space = $info.package.DiskspaceNeeded
Reboot = $info.package.Reboot.type
Version = $info.package.version
Download = "https://download.lenovo.com/pccbbs/mobiles/$filename"
Datum = Get-Date
}
}
}

您现在可以直接从 PowerShell 命令行检索信息,而不是手动操作 HTML 页面,例如:

1
2
3
4
5
6
7
8
9
10
PS> Get-LenovoDriver -Model 20JN -Os Win10 | Out-GridView -PassThru


Category : ThinkVantage Technology
Command : n1msk20w.exe /VERYSILENT /DIR=%PACKAGEPATH% /EXTRACT="YES"
Space : 33206066
Reboot : 3
Version : 1.82.0.20
Download : https://download.lenovo.com/pccbbs/mobiles/n1msk20w.exe
Datum : 09.12.2021 13:16:00

即使您不管理 Lenovo 硬件,您也可以复用此示例中介绍的一些技术。

PowerShell 技能连载 - Out-GridView 自定义列

当您使用 -OutputMode-PassThru 参数时,Out-GridView 可以是一个通用对话框。执行此操作时,网格视图窗口会在其右下角显示其他按钮,以便您可以选择项目并将它们传递给其他 cmdlet。

此行代码可以帮助选择要停止的服务,例如:

1
Get-Service | Where-Object CanStop | Out-GridView -Title 'Service to stop?' -OutputMode Single | Stop-Service -WhatIf

但是,Out-GridView 无法控制它显示的属性。在上面的示例中,用户实际上只需要查看服务名称以及可能的依赖服务。

当然,您可以使用 Select-Object 来选择要显示的属性。现在网格视图窗口将准确显示您要求的列,但由于您永久删除了所有其他属性并更改了对象类型,后续 cmdlet 可能会如你所想的不能正常工作:

1
Get-Service | Where-Object CanStop | Select-Object -Property DisplayName, DependentServices | Out-GridView -Title 'Service to stop?' -OutputMode Single | Stop-Service -WhatIf

运行上面这行代码时,网格视图窗口现在看起来很棒,但 Stop-Service 将不再停止选择您选择的服务,因为 Select-Object 将对象类型从 Service 更改为自定义对象:

Stop-Service : The specified wildcard character pattern is not valid: @{DisplayName=Windows Audio Endpoint Builder;
DependentServices=System.ServiceProcess.ServiceController[]}

在上一个技能中,我们已经使用了一种隐藏的技术,您可以使用它来告诉 Out-GridView 它应该显示哪些列——无需删除任何属性或更改对象类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# create object that tells PowerShell which column(s) should be visible:
# show "DisplayName", and "DependentServices"
[string[]]$visible = 'DisplayName', 'DependentServices'
$type = 'DefaultDisplayPropertySet'
[System.Management.Automation.PSMemberInfo[]]$info =
[System.Management.Automation.PSPropertySet]::new($type,$visible)


Get-Service |
Where-Object CanStop |
# add the secret object to each object that you pipe into Out-GridView:
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -PassThru |
Out-GridView -Title 'Service to stop?' -OutputMode Single |
Stop-Service -WhatIf

不幸的是,当您这样做时,您可能会遇到红色错误消息。某些 PowerShell 对象(例如 Service)已经使用了我们尝试添加的巧妙技巧,因此您无法覆盖 PSStandardMembers 属性。要解决此问题,只需通过 Select-Object * 运行它们来克隆对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# create object that tells PowerShell which column(s) should be visible:
# show "DisplayName", and "DependentServices"
[string[]]$visible = 'DisplayName', 'DependentServices'
$type = 'DefaultDisplayPropertySet'
[System.Management.Automation.PSMemberInfo[]]$info =
[System.Management.Automation.PSPropertySet]::new($type,$visible)


Get-Service |
Where-Object CanStop |
# clone the objects so they now belong to you:
Select-Object -Property * |
# add the secret object to each object that you pipe into Out-GridView:
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -PassThru |
Out-GridView -Title 'Service to stop?' -OutputMode Single |
Stop-Service -WhatIf

现在一切都很神奇,Out-GridView 仅显示您选择的属性。尽管如此,Stop-Process 继续获得输出信息并停止您选择的服务(删除 -WhatIf 来真实地停止服务,请确保您有管理员权限进行此操作)。

虽然通过 Select-Object 运行对象确实会更改其对象类型,但大多数 cmdlet 仍会继续处理这些对象,因为它们仍包含所有属性。这是最后一个示例:即使 Out-GridView 仅显示您选择的属性,对象仍包含所有属性,包括隐藏在网格视图窗口中的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# create object that tells PowerShell which column(s) should be visible:
# show "Name", "Description" and "MainWindowTitle"
[string[]]$visible = 'Name', 'Description', 'MainWindowTitle'
$type = 'DefaultDisplayPropertySet'
[System.Management.Automation.PSMemberInfo[]]$info =
[System.Management.Automation.PSPropertySet]::new($type,$visible)


Get-Process |
Where-Object MainWindowTitle |
Sort-Object -Property Name |
# clone the objects so they now belong to you:
Select-Object -Property * |
# add the secret object to each object that you pipe into Out-GridView:
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -PassThru |
Out-GridView -Title 'Select a process' -OutputMode Single |
# still all properties available:
Select-Object -Property *

PowerShell 技能连载 - 修复 PowerShellGet 和 Publish-Module

Publish-Module 是一个 cmdlet,用于将模块发布(上传)到 NuGet 仓库。有时,此 cmdlet 会引发奇怪的异常。这种情况下的原因是 nuget.exe 的过时版本。该应用程序负责打包一个模块并保存为.nupkg 文件,并且在您第一次使用 Publish-Module 时会自动下载该应用程序。

要更正此问题并刷新您的 nuget.exe 版本,请运行以下命令:

1
Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile "$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\PowerShellGet\NuGet.exe"

确保在此之后关闭并重新启动所有 PowerShell 会话。如果 Publish-Module 仍然拒绝工作,您可能需要运行以下命令(需要管理员权限):

1
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force

PowerShell 技能连载 - 检测挂起的重启

下面的代码检测是否有挂起的重启:

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

PowerShell 技能连载 - 禁用摄像头

寻求保护隐私吗?这是一个简短的脚本,用于在您的系统上查找已启用的摄像头,并让您禁用任何不想使用的摄像头:

1
2
3
4
  # find working cameras
$result = Get-PnpDevice -FriendlyName *Camera* -Status OK -ErrorAction Ignore |
Out-GridView -Title 'Select Camera Device To Disable' -OutputMode Single |
Disable-PnpDevice -Confirm:$false -Passthru -whatif # remove -WhatIf to actually disable devices)

PowerShell 技能连载 - 使用 SOAP Webservice

尽管 SOAP 尚未广泛用于公共的 Webservice(通常使用更简单的 REST 服务),但在内部,许多公司确实将 SOAP 用于他们的 Webservice。

PowerShell 具有出色的 SOAP 支持,因此您无需大量复杂的代码即可连接和使用 SOAP Webservice。这是少数剩余的免费公共 SOAP Webservice 之一(将德语“bankleitzahl”翻译成银行详细信息):

1
2
$o = New-WebServiceProxy -Uri http://www.thomas-bayer.com/axis2/services/BLZService?wsdl
$o.getBank('25050180')

如您所见,要开始使用 SOAP Webservice,您需要 Webservice 提供的 WSDL URL。此网页以 XML 格式返回整个接口定义,New-WebServiceProxy 根据此信息创建包装 SOAP 数据类型所需的所有代码。

一旦您可以访问(任何)SOAP Webservice,您就可以使用以下代码来检查其方法:

1
2
3
4
5
6
7
8
9
10
11
$o = New-WebServiceProxy -Uri http://www.thomas-bayer.com/axis2/services/BLZService?wsdl

# common methods
$blacklist = 'CreateObjRef', 'Dispose', 'Equals', 'GetHashCode', 'GetLifetimeService', 'InitializeLifetimeService', 'ToString', 'GetType'

# exclude async and common methods
$o | Get-Member -MemberType *method |
Where-Object Name -notlike '*Async*' |
Where-Object Name -notlike 'Begin*' |
Where-Object Name -notlike 'End*' |
Where-Object { $_.Name -notin $blacklist }

PowerShell 技能连载 - 通过 PowerShell 调用 COVID 服务

您想及时了解 Covid 疫情数据吗?试试这个简单的 webservice:

1
2
3
$result = Invoke-RestMethod -Uri "https://coronavirus-19-api.herokuapp.com/countries"

$result -match "Germany"

结果类似于:

country             : Germany
cases               : 4480066
todayCases          : 0
deaths              : 95794
todayDeaths         : 0
recovered           : 4215200
active              : 169072
critical            : 1336
casesPerOneMillion  : 53248
deathsPerOneMillion : 1139
totalTests          : 73348901
testsPerOneMillion  : 871788