PowerShell 技能连载 - 删除所有指定层级下的子文件夹

以下是另一个听起来复杂,但实际上并没有那么复杂的文件系统任务。假设您需要移除某个文件系统指定层级之下所有文件夹。以下是实现方法:

1
2
3
4
5
6
7
8
# Task:

# remove all folders one level below the given path
Get-ChildItem -Path "c:\sample\*\" -Directory -Recurse |
# remove -WhatIf to actually delete
# ATTENTION: test thoroughly before doing this!
# you may want to add -Force to Remove-Item to forcefully delete
Remove-Item -Recurse -WhatIf

只需要在路径中使用 “*“。要删除某个指定路径下的两层之下的目录,相应地做以下调整:

1
2
3
4
5
6
7
8
# Task:

# remove all folders TWO levels below the given path
Get-ChildItem -Path "c:\sample\*\*\" -Directory -Recurse |
# remove -WhatIf to actually delete
# ATTENTION: test thoroughly before doing this!
# you may want to add -Force to Remove-Item to forcefully delete
Remove-Item -Recurse -WhatIf

PowerShell 技能连载 - 从目录结构中删除所有文件

有些任务听起来复杂,实际上并没有那么复杂。假设我们需要清除一个目录结构,移除所有文件,而留下空白的文件夹。我们进一步假设有一些文件位于白名单上,不应移除。如果用 PowerShell,那么实现起来很容易:

1
2
3
4
5
6
7
8
9
10
11
12
# Task:
# remove all files from a folder structure, but keep all folders,
# and keep all files on a whitelist

$Path = "c:\sample"
$WhiteList = "important.txt", "something.csv"

Get-ChildItem -Path $Path -File -Exclude $WhiteList -Recurse -Force |
# remove -WhatIf if you want to actually delete files
# ATTENTION: test thoroughly before doing this!
# you may want to add -Force to Remove-Item to forcefully remove files
Remove-Item -WhatIf

PowerShell 技能连载 - 在 PowerShell 标题栏显示 RSS 标题

一个新的 PowerShell 后台线程可以在后台工作,例如每 5 秒钟在 PowerShell 窗口标题栏上显示新的 RSS 供稿。

首先,我们先看看如何获取新闻供稿内容:

1
2
3
4
5
$RSSFeedUrl = 'https://www.technologyreview.com/stories.rss'
$xml = Invoke-RestMethod -Uri $RSSFeedUrl
$xml | ForEach-Object {
"{0} {1}" -f $_.title, $_.description
}

可以自由调整 RSS 公告的地址。如果修改了地址,请也注意调整 “title“ 和 “description“ 属性名。每个 RSS 供稿可以任意命名这些属性。

以下是向标题栏添加新内容的完整解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$Code = {
# receive the visible PowerShell window reference
param($UI)

# get RSS feed messages
$RSSFeedUrl = 'https://www.technologyreview.com/stories.rss'
$xml = Invoke-RestMethod -Uri $RSSFeedUrl

# show a random message every 5 seconds
do
{
$message = $xml | Get-Random
$UI.WindowTitle = "{0} {1}" -f $message.title, $message.description
Start-Sleep -Seconds 5
} while ($true)
}

# create a new PowerShell thread
$ps = [PowerShell]::Create()
# add the code, and a reference to the visible PowerShell window
$null = $ps.AddScript($Code).AddArgument($host.UI.RawUI)

# launch background thread
$null = $ps.BeginInvoke()

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