PowerShell 技能连载 - Invoke-WebRequest vs. Invoke-RestMethod

Invoke-WebRequest 只是简单地从任意的网站下载内容。接下来读取内容并分析它,是您的工作。在前一个技能中我们解释了如何下载 PowerShell 代码,并且执行它:

1
2
3
4
5
$url = "http://bit.ly/e0Mw9w"
$page = Invoke-WebRequest -Uri $url
$code = $page.Content
$sb = [ScriptBlock]::Create($code)
& $sb

(请注意:请在 PowerShell 控制台中运行以上代码。如果在 PowerShell ISE 等编辑器中运行,杀毒引擎将会阻止运行)

类似地,如果数据是以 XML 格式返回的,您可以将它转换为 XML。并且做进一步处理,例如从银行获取当前的汇率:

1
2
3
4
$url = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
$page = Invoke-WebRequest -Uri $url
$data = $page.Content -as [xml]
$data.Envelope.Cube.Cube.Cube

作为对比,Invoke-RestMethod 不仅仅下载数据,而且遵循数据的类型。您会自动得到正确的数据。以下是上述操作的简化版本,用的是 Invoke-RestMethod 来代替 Invoke-WebRequest

调用 Rick-Ascii(从 PowerShell 控制台运行):

1
2
3
4
$url = "http://bit.ly/e0Mw9w"
$code = Invoke-RestMethod -Uri $url
$sb = [ScriptBlock]::Create($code)
& $sb

获取货币汇率:

1
2
3
$url = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
$data = Invoke-RestMethod -Uri $url
$data.Envelope.Cube.Cube.Cube

PowerShell 技能连载 - 下载 PowerShell 代码

在之前的技能中我们介绍了如何用 Invoke-WebRequest 下载任意网页的 HTML 内容。这也可以用来下载 PowerShell 代码。Invoke-WebRequest 可以下载任意 WEB 服务器提供的内容,所以以下示例代码可以下载一段 PowerShell 脚本:

1
2
3
4
$url = "http://bit.ly/e0Mw9w"
$page = Invoke-WebRequest -Uri $url
$code = $page.Content
$code | Out-GridView

如果您信任这段代码,您可以方便地下载和运行它:

1
Invoke-Expression -Command $code

这段代码适用于 PowerShell 控制台,您将见到一个“跳舞的 Rick Ascii 艺术”并听到有趣的音乐,如果在另一个编辑器中运行以上代码,您的 AV 可能会阻止该调用并且将它识别为一个严重的威胁。这是因为下载的代码会检测它运行的环境,由于它需要在控制台中运行,所以如果在其它地方运行它,将会启动一个 PowerShell 控制台。这个操作是由杀毒引擎控制的,随后会被系统阻止。

PowerShell 技能连载 - 隐藏进度条

有些时候,cmdlet 自动显示一个进度条,以下是一个显示进度条的例子:

1
2
$url = "http://www.powertheshell.com/reference/wmireference/root/cimv2/"
$page = Invoke-WebRequest -URI $url

Invoke-WebRequest 获取一个 WEB 页面的原始内容,如果获取内容消耗一定的时间,将会显示进度条。

Whenever you run a cmdlet that shows a progress bar, you can hide the progress bar by temporarily changing the $ProgressPreference variable. Just make sure you restore its original value or else, you permanently hide progress bars for the current PowerShell session:
当您运行一个会显示进度条的 cmdlet,您可以通过临时改变 $ProgressPreference 变量来隐藏进度条。只需要记得恢复它的初始值或是设置成其它值,这样就可以针对当前 PowerShell 会话隐藏进度条。

1
2
3
4
5
6
7
$old = $ProgressPreference
$ProgressPreference = "SilentlyContinue"

$url = "http://www.powertheshell.com/reference/wmireference/root/cimv2/"
$page = Invoke-WebRequest -URI $url

$ProgressPreference = $old

除了保存和恢复原始的变量内容,您还可以使用脚本块作用域:

1
2
3
4
5
6
7
& {
$ProgressPreference = "SilentlyContinue"
$url = "http://www.powertheshell.com/reference/wmireference/root/cimv2/"
$page = Invoke-WebRequest -URI $url
}

$ProgressPreference

如您所见,当脚本块执行完之后,原始变量自动恢复原始值。

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 将变得可用,并且显示当前选中的公司可用的部门。