PowerShell 技能连载 - 分析 WEB 页面内容

PowerShell 内置了一个 WEB 客户端,它可以获取 HTML 内容。对于简单的 WEB 页面分析,使用 -UseBasicParsing 参数即可。该操作将原原本本地获取原始的 HTML 内容,例如,包含嵌入的链接和图像的列表:

1
2
3
4
5
6
$url = "http://powershellmagazine.com"
$page = Invoke-WebRequest -URI $url -UseBasicParsing

$page.Content | Out-GridView -Title Content
$page.Links | Select-Object href, OuterHTML | Out-GridView -Title Links
$page.Images | Select-Object src, outerHTML | Out-GridView -Title Images

如果忽略了 -UseBasicParsing 参数,那么该 cmdlet 内部使用 Internet Explorer 文档对象模型,并返回更详细的信息:

1
2
3
4
$url = "http://powershellmagazine.com"
$page = Invoke-WebRequest -URI $url

$page.Links | Select-Object InnerText, href | Out-GridView -Title Links

请注意 Invoke-WebRequest 需要您实现设置并且至少打开一次 Internet Explorer,除非您指定了 -UseBasicParsing

PowerShell 技能连载 - 添加额外的安全防护

如果您正在编写 PowerShell 函数,并且知道某个函数可能会造成很多危险结果,那么有一个简单的方法可以增加一层额外的安全防护。以下是了个函数,一个没有安全防护,而另一个有安全防护:

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
function NoSafety
{
param
(
[Parameter(Mandatory)]
$Something
)
"HARM DONE with $Something!"
}

function Safety
{
# step 1: add -WhatIf and -Confirm, and mark as harmful
[CmdletBinding(SupportsShouldProcess,ConfirmImpact="High")]
param
(
[Parameter(Mandatory)]
$Something
)

# step 2: abort function when confirmation was rejected
if (!$PSCmdlet.ShouldProcess($env:computername, "doing something harmful"))
{
Write-Warning "Aborted!"
return
}

"HARM DONE with $Something!"
}

当您运行 “NoSafety“,直接运行完毕。而当您运行 “Safety“,用户会得到一个确认提示,只有确认了该提示,函数才能继续执行。

要实现这个有两个步骤。第一,[CmdletBinding(...)] 语句增加了 WhatIf-Confirm 参数,而 ConfirmImpact="High" 将函数标记为有潜在危险的。

第二,在函数代码中的第一件事情是调用 $PSCmdlet.ShouldProcess,在其中你可以定义确认信息。如果这个调用返回 $false,那么代码中的 -not 操作符 (!) 将抛出结果,而该函数立即返回。

用户仍然可以通过显式关闭确认的方式,不需确认运行该函数:

1
2
PS> Safety -Something test -Confirm:$false
HARM DONE with test!

或者,通过设置 $ConfirmPreference 为 “None“,直接将本 PowerShell 会话中的所有的自动确认对话框关闭:

1
2
3
4
PS> $ConfirmPreference = "None"

PS> Safety -Something test
HARM DONE with test!

PowerShell 技能连载 - 隐藏通用属性

在前一个技能中我们解释了如何在 IntelliSense 中隐藏参数。今天我们想向您介绍一个很酷的副作用!

PowerShell 支持两种类型的函数:简单函数和高级函数。一个简单的函数只暴露了您定义的参数。高级函数还加入了所有常见的参数。以下是两个示例函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Simple
{
param
(
$Name
)
}

function Advanced
{
param
(
[Parameter(Mandatory)]
$Name
)
}

当您在编辑器中通过 IntelliSense 调用 Simple 函数,您只能见到 -Name 参数。当您调用 Advanced 函数时,还能看到一系列常见的参数,这些参数总是出现。

当您使用一个属性(属性的模式为 [Name(Value)]),PowerShell 将创建一个高级函数,并且无法排除通用参数。那么如何既保留高级函数的优点(例如必选参数)但只向用户显示自己的参数呢?

以下是一个秘籍。请对比以下两个函数:

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
function Advanced
{
param
(
[Parameter(Mandatory)]
[string]
$Name,

[int]
$Id,

[switch]
$Force
)
}

function AdvancedWithoutCommon
{
param
(
[Parameter(Mandatory)]
[string]
$Name,

[int]
$Id,

[switch]
$Force,

# add a dummy "DontShow" parameter
[Parameter(DontShow)]
$Anything
)
}

当调用 “Advanced“ 函数时,将显示自定义的参数以及通用参数。当对 “AdvancedWithoutCommon“ 做相同的事时,只会见到自定义的参数但保留高级函数的功能,例如 -Name 参数仍然是必选的。

这种效果是通过添加一个或多个隐藏参数实现的。隐藏参数是从 PowerShell 5 开始引入的,用于加速类方法(禁止显示隐藏属性)。由于类成员不显示通用参数,所以属性值 “DontShow“ 不仅从 IntelliSense 中隐藏特定的成员,而且隐藏所有通用参数。

这恰好导致另一个有趣的结果:虽然通用参数现在从 IntelliSense 中隐藏,但他们仍然存在并且可使用:

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 Test
{
param
(
[Parameter(Mandatory)]
[string]
$Name,

# add a dummy "DontShow" parameter
[Parameter(DontShow)]
$Anything
)

Write-Verbose "Hello $Name"
}



PS> Test -Name tom

PS> Test -Name tom -Verbose
VERBOSE: Hello tom

PS>

PowerShell 技能连载 - 将对象转换为哈希表

在之前的一个技能中我们介绍了如何用 Get-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
25
26
27
28
29
30
31
32
33
# take an object...
$process = Get-Process -id $pid

# ...and turn it into a hash table
$hashtable = $process | ForEach-Object {

$object = $_

# determine the property names in this object and create a
# sorted list
$columns = $_ |
Get-Member -MemberType *Property |
Select-Object -ExpandProperty Name |
Sort-Object

# create an empty hash table
$hashtable = @{}

# take all properties, and add keys to the hash table for each property
$columns | ForEach-Object {
# exclude empty properties
if (![String]::IsNullOrEmpty($object.$_))
{
# add a key (property) to the hash table with the
# property value
$hashtable.$_ = $object.$_
}
}
$hashtable
}


$hashtable | Out-GridView

PowerShell 技能连载 - 隐藏参数

In the previous tip we explained how you can dump all the legal values for a PowerShell attribute. Today we’ll take a look at the [Parameter()] attribute and its value DontShow. Take a look at this function:
在前一个技能中我们介绍了如何导出一个 PowerShell 属性的所有合法值。今天我们将关注 [Parameter()] 属性和它的值 DontShow。我们来看看这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Test-Something
{
param
(
[string]
[Parameter(Mandatory)]
$Name,

[Parameter(DontShow)]
[Switch]
$Internal
)

"You entered: $name"
if ($Internal)
{
Write-Warning "We are in secret test mode!"
}
}

当您运行这个函数时,IntelliSense 只暴露 -Name 参数。-Internal switch 参数并没有显示,然而您仍然可以使用它。它只是一个隐藏的参数:

1
2
3
4
5
6
PS> Test-Something -Name tobias
You entered: tobias

PS> Test-Something -Name tobias -Internal
You entered: tobias
WARNING: We are in secret test mode!

PowerShell 技能连载 - 探索 PowerShell 属性值

您也许知道,您可以对变量和参数添加属性来更有针对性地定义它们。例如,以下代码定义了一个包含只允许三个字符串之一的必选参数的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Test-Attribute
{
[CmdletBinding()]
param
(
[string]
[Parameter(Mandatory)]
[ValidateSet("A","B","C")]
$Choice
)

"Choice: $Choice"
}

如果您想知道这些属性有哪些选择,以下是实现方法。您所需要了解的是代表属性的真实名称。PowerShell 自身的属性都位于 System.Management.Automation 命名空间。以下是两个最常用的:

1
2
[Parameter()] = [System.Management.Automation.ParameterAttribute]
[CmdletBinding()] = [System.Management.Automation.CmdletBindingAttribute]

要查看某个指定属性的合法数值,只需要实例化一个制定类型的对象,并查看它的属性:

1
2
3
[System.Management.Automation.ParameterAttribute]::new() |
Get-Member -MemberType *Property |
Select-Object -ExpandProperty Name

这将返回一个包含所有 [Parameter()] 属性所有合法属性值的列表:

DontShow
HelpMessage
HelpMessageBaseName
HelpMessageResourceId
Mandatory
ParameterSetName
Position
TypeId
ValueFromPipeline
ValueFromPipelineByPropertyName
ValueFromRemainingArguments

当您向每个值添加期望的数据类型时,该列表就更有用了:

1
2
3
4
5
6
7
8
[System.Management.Automation.ParameterAttribute]::new() |
Get-Member -MemberType *Property |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Type = ($_.Definition -split ' ')[0]
}
}

现在它看起来类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
Name                            Type
---- ----
DontShow bool
HelpMessage string
HelpMessageBaseName string
HelpMessageResourceId string
Mandatory bool
ParameterSetName string
Position int
TypeId System.Object
ValueFromPipeline bool
ValueFromPipelineByPropertyName bool
ValueFromRemainingArguments bool

例如这是 [CmdletBinding()] 的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[System.Management.Automation.CmdletBindingAttribute]::new() |
Get-Member -MemberType *Property |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Type = ($_.Definition -split ' ')[0]
}
}



Name Type
---- ----
ConfirmImpact System.Management.Automation.ConfirmImpact
DefaultParameterSetName string
HelpUri string
PositionalBinding bool
RemotingCapability System.Management.Automation.RemotingCapability
SupportsPaging bool
SupportsShouldProcess bool
SupportsTransactions bool
TypeId System.Object

PowerShell 技能连载 - 使用动态参数

大多数 PowerShell 函数使用静态参数。它们是定义在 param() 代码块中的,并且始终存在。一个不太为人所知的情况是您也可以快速地编程添加动态参数。动态参数的最大优势是您可以完全控制它们什么时候可以出现,以及它们可以接受什么类型的数值。它的缺点是需要使用大量底层的代码来“编程”参数属性。

以下是一个示例函数。它只有一个名为 “Company“ 的静态参数。仅当您选择了一个公司,该函数才会添加一个名为 “Department“ 的新的动态参数。新的动态参数 -Department 根据选择的公司暴露出一个可用值的列表。实质上,根据选择的公司,-Department 参数被关联到一个独立的 ValidateSet 属性:

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
function Test-Department
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ValidateSet('Microsoft','Amazon','Google','Facebook')]
$Company
)

dynamicparam
{
# this hash table defines the departments available in each company
$data = @{
Microsoft = 'CEO', 'Marketing', 'Delivery'
Google = 'Marketing', 'Delivery'
Amazon = 'CEO', 'IT', 'Carpool'
Facebook = 'CEO', 'Facility', 'Carpool'
}

# check to see whether the user already chose a company
if ($Company)
{
# yes, so create a new dynamic parameter
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]

# define the parameter attribute
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Mandatory = $false
$attributeCollection.Add($attribute)

# create the appropriate ValidateSet attribute, listing the legal values for
# this dynamic parameter
$attribute = New-Object System.Management.Automation.ValidateSetAttribute($data.$Company)
$attributeCollection.Add($attribute)

# compose the dynamic -Department parameter
$Name = 'Department'
$dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($Name,
[string], $attributeCollection)
$paramDictionary.Add($Name, $dynParam)

# return the collection of dynamic parameters
$paramDictionary
}
}

end
{
# take the dynamic parameters from $PSBoundParameters
$Department = $PSBoundParameters.Department

"Chosen department for $Company : $Department"
}
}

请注意!当您在选择的编辑器中键入 Test-Department,初始情况下只有一个参数:Tbc-Company。当您选择了四个可用得公司之一,第二个参数 -Department 将变得可用,并且显示当前选中的公司可用的部门。

PowerShell 技能连载 - 读取网站内容

通常情况下,通过 PowerShell 的 Invoke-WebRequest 命令来获取原始的 HTML 网站内容是很常见的情况。脚本可以处理 HTML 内容并对它做任意操作,例如用正则表达式从中提取信息:

1
2
3
$url = "www.tagesschau.de"
$w = Invoke-WebRequest -Uri $url -UseBasicParsing
$w.Content

然而,有些时候一个网站的内容是通过客户端脚本代码动态创建的。那么,Invoke-WebRequest` 并不能返回浏览器中所见的完整 HTML 内容。如果仍要获取 HTML 信息,您需要借助一个真实的 WEB 浏览器。一个简单的方法是使用内置的 Internet Explorer:

1
2
3
4
5
6
7
8
$ie = New-Object -ComObject InternetExplorer.Application
$ie.Navigate($url)
do
{
Start-Sleep -Milliseconds 200
} while ($ie.ReadyState -ne 4)

$ie.Document.building.innerHTML

PowerShell 技能连载 - 接受不同的参数类型

个别情况下,您可能会希望创建一个可以接受不同参数类型的函数。假设您希望用户既可以传入一个雇员姓名,也可以传入一个 Active Directory 对象。

在 PowerShell 中有一个固定的原则:变量不能在同一时刻有不同的数据类型。由于参数是变量,所以一个指定的参数只能有一个唯一的类型。

然而,您可以使用参数集来定义互斥的参数,这是一种解决多个输入类型的好方法。以下是一个既可以输入服务名,也可以输入服务对象的示例函数。这基本上是 Get-Service 内部的工作原理,以下示例展示了它的实现方式:

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
function Get-MyService
{
[CmdletBinding(DefaultParameterSetName="String")]
param
(
[String]
[Parameter(Mandatory,Position=0,ValueFromPipeline,ParameterSetName='String')]
$Name,

[Parameter(Mandatory,Position=0,ValueFromPipeline,ParameterSetName='Object')]
[System.ServiceProcess.ServiceController]
$Service
)

process
{
# if the user entered a string, get the real object
if ($PSCmdlet.ParameterSetName -eq 'String')
{
$Service = Get-Service -Name $Name
}
else
{
# else, if the user entered (piped) the expected object in the first place,
# you are good to go
}

# this call tells you which parameter set was invoked
$PSCmdlet.ParameterSetName

# at the end, you have an object
$Service
}

}

我们看看该函数的使用:

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
PS> Get-MyService -Name spooler
String

Status Name DisplayName
------ ---- -----------
Running spooler Print Spooler



PS> $spooler = Get-Service -Name Spooler

PS> Get-MyService -Service $spooler
Object

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler



PS> "Spooler" | Get-MyService
String

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler



PS> $spooler | Get-MyService
Object

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler

如您所见,用户可以传入一个服务名或是 Service 对象。Get-MyService 函数模仿 Get-Service 内部的实现机制,并且返回一个 Service 对象,无论输入什么类型。以下是上述函数的语法:

1
2
3
**Syntax**
Get-MyService [-Name] <string> [<CommonParameters>]
Get-MyService [-Service] <ServiceController> [<CommonParameters>]

PowerShell 技能连载 - 将 VBScript 翻译为 PowerShell

大多数旧的 VBS 脚本可以容易地翻译为 PowerShell。VBS 中关键的命令是 “CreateObject“。它能让您操作系统的库。PowerShell 将 “CreateObject“ 翻译为 “New-Object -ComObject“,而对象模型和成员名称保持相同:

当把这段代码保存为扩展名为 “.vbs” 的文本文件后,这段 VBS 脚本就可以发出语音:

1
2
3
Set obj = CreateObject("Sapi.SpVoice")

obj.Speak "Hello World!"

对应的 PowerShell 代码类似这样:

1
2
$obj = New-Object -ComObject "Sapi.SpVoice"
$obj.Speak("Hello World!")

只有少量的 VBS 内置成员,例如 MsgBoxInputBox。要翻译这些代码,您需要引入 “Microsoft.VisualBasic.Interaction“ 类型。以下是调用 MsgBoxInputBox 的 PowerShell 代码:

1
2
3
4
5
6
Add-Type -AssemblyName Microsoft.VisualBasic

$result = [Microsoft.VisualBasic.Interaction]::MsgBox("Do you want to restart?", 3, "Question")
$result

$result = [Microsoft.VisualBasic.Interaction]::InputBox("Your name?", $env:username, "Enter Name")

以下是支持的 Visual Basic 成员的完整列表:

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
PS> [Microsoft.VisualBasic.Interaction] | Get-Member -Stati


TypeName: Microsoft.VisualBasic.Interaction

Name MemberType Definition
---- ---------- ----------
AppActivate Method static void AppActivate(int ProcessId), static vo...
Beep Method static void Beep()
CallByName Method static System.Object CallByName(System.Object Obj...
Choose Method static System.Object Choose(double Index, Params ...
Command Method static string Command()
CreateObject Method static System.Object CreateObject(string ProgId, ...
DeleteSetting Method static void DeleteSetting(string AppName, string ...
Environ Method static string Environ(int Expression), static str...
Equals Method static bool Equals(System.Object objA, System.Obj...
GetAllSettings Method static string[,] GetAllSettings(string AppName, s...
GetObject Method static System.Object GetObject(string PathName, s...
GetSetting Method static string GetSetting(string AppName, string S...
IIf Method static System.Object IIf(bool Expression, System....
InputBox Method static string InputBox(string Prompt, string Titl...
MsgBox Method static Microsoft.VisualBasic.MsgBoxResult MsgBox(...
Partition Method static string Partition(long Number, long Start, ...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, S...
SaveSetting Method static void SaveSetting(string AppName, string Se...
Shell Method static int Shell(string PathName, Microsoft.Visua...
Switch Method static System.Object Switch(Params System.Object[...



PS>