PowerShell 技能连载 - 自动化操作 Defender 杀毒软件(第 1 部分)

在 Windows 上,PowerShell 带有用于自动化操作内置防病毒引擎 “Defender” 的 cmdlet。

如果您想自动更新签名,请尝试以下操作:

1
PS C:\> Update-MpSignature

如果您在计划任务的脚本中运行此命令,则现在可以完全控制。无需管理员特权。

同样,PowerShell 可以仅使用一个命令随时启动快速扫描:

1
PS C:\> Start-MpScan -ScanType QuickScan

扫描进度显示为 PowerShell 进度条,无需打开烦人的对话框。

如果您想知道您最近面临的最新威胁,请让 Defender 输出其威胁分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\> Get-MpThreat

CategoryID : 27
DidThreatExecute : False
IsActive : True
Resources :
RollupStatus : 1
SchemaVersion : 1.0.0.0
SeverityID : 1
ThreatID : 311978
ThreatName : PUADlManager:Win32/DownloadSponsor
TypeID : 0
PSComputerName :

PowerShell 技能连载 - 存储任何加密的文本

假设您的脚本需要敏感输入,例如数据库的连接字符串或其他文本信息。

管理此类机密信息的一种方法是将它们存储为 [SecureString],并安全地序列化此信息到 XML。以下是此部分的操作:

1
2
3
4
5
6
7
8
$Path = "$env:temp\safeconnectionstring.test"


[ordered]@{
Con1 = 'secret1' | ConvertTo-SecureString -AsPlainText -Force
Con2 = 'secret2' | ConvertTo-SecureString -AsPlainText -Force
Con3 = 'secret3' | ConvertTo-SecureString -AsPlainText -Force
} | Export-Clixml -Path $Path

它将三段机密信息存储到一个哈希表中,将其转换为安全字符串,然后安全地将它们导出到 XML。机密的关键是运行此脚本的用户和机器,因此只有此人(在同一台计算机上)才能稍后读取信息。

如果您不想将机密信息存储在任何地方,您还可以交互式地输入它们:

1
2
3
4
5
6
7
8
$Path = "$env:temp\safeconnectionstring.test"


[ordered]@{
Con1 = Read-Host -Prompt Secret1 -AsSecureString
Con2 = Read-Host -Prompt Secret1 -AsSecureString
Con3 = Read-Host -Prompt Secret1 -AsSecureString
} | Export-Clixml -Path $Path

Now, when it is time to use the secrets, you need a way to convert secure strings back to plain text. This is what this script does:
现在,当需要使用这些机密信息时,您需要一种将安全字符串转换回纯文本的方法。此脚本的操作:

1
2
3
4
5
6
7
$hash = Import-Clixml -Path $Path
# important: MUST cast $keys to [string[]] or else you cannot modify the hash
# in the loop:
[string[]]$keys = $hash.Keys
$keys | ForEach-Object {
$hash[$_] = [PSCredential]::new('xyz', $hash[$_]).GetNetworkCredential().Password
}

结果($hash)是一个哈希表,其中包含以纯文本形式保存的机密信息,因此在此示例中,您可以通过三个键“con1”、“con2”和“con3”访问这三个机密信息:

1
2
3
4
5
6
7
8
PS> $hash.Con1
secret1

PS> $hash.Con2
secret2

PS> $hash.Con3
secret3

PowerShell 技能连载 - 使用合适的数据类型(第 2 部分)

第 1 部分中我们介绍了将数据转换为更适合的 .NET 数据类型后数据的可用性如何提高。

如果无法为数据找到现有的数据类型以为其提供结构,也可以创建自己的数据类型。

假设您需要处理名称。这是一个名为 [TeamMember] 的自定义数据类型,可以为名称添加结构:

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
class TeamMember
{
[string]$FirstName
[string]$LastName


TeamMember([string]$Name)
{
# case correct text
$newName = [cultureinfo]::InvariantCulture.TextInfo.ToTitleCase($Name)
# automatically find delimiter character
$delimiter = $newName.ToCharArray() -match '[,. ]' | Select-Object -First 1

# no delimiter?
if ($delimiter.Count -eq 0)
{
$this.LastName = $Name
return
}

$array = $newName.Split($delimiter)

# based on found delimiter
switch -Regex ($delimiter)
{
# dot or space:
'[.\s]' {
$this.FirstName = $array[0]
$this.LastName = $array[1]
}
# comma
'\,' {
$this.FirstName = $array[1]
$this.LastName = $array[0]
}
# invalid
default {
$this.LastName = $Name
}
}
}
}

运行此代码后,定义了名为 [TeamMember] 的新数据类型。现在可以将包含名称的字符串简单地转换为结构化数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS> [TeamMember]'tobias weltner'

FirstName LastName
--------- --------
Tobias Weltner



PS> [TeamMember]'tobias.weltner'

FirstName LastName
--------- --------
Tobias Weltner



PS> [TeamMember]'weltner,tobias'

FirstName LastName
--------- --------
Tobias Weltner

还有个额外的好处,即自动纠正大小写,或者说,您的类可以包含任何您喜欢的魔法。当您后来使用该类型时,您不再需要担心它。

更好的是,当将此类型分配给变量时,它将自动将任何名称转换为新结构,即使在以后的赋值中也是如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> [TeamMember]$name = 'Tobias Weltner'

PS> $name

FirstName LastName
--------- --------
Tobias Weltner


PS> $name = 'kloVER,kaRL'

PS> $name

FirstName LastName
--------- --------
Karl Klover

自定义类的自动转换的秘密在于其构造函数。当构造函数接受一个 [string] 类型的参数时,它可以自动将任何字符串转换为新结构。

类的构造函数和类名相同,并且输入参数为 [string]

1
2
3
4
TeamMember([string]$Name)
{
...
}

PowerShell 技能连载 - 使用合适的数据类型(第 1 部分)

Windows 是一个 API 驱动的操作系统,而 PowerShell 也是如此。与其他使用纯文本作为基础元素并让用户通过 grep 和正则表达式来结构化数据的 shell 相比,PowerShell(和底层的 .NET 框架)提供了一组丰富的数据类型,您可以从中选择最适合的来完美地存储数据。

默认情况下,PowerShell 仅使用基本数据类型,例如[string](文本),[int](数字),[double](浮点数),[datetime](日期和时间)和[bool](真和假)。

You however can pick any other data type that you find more suitable:
但是,您可以选择任何其他您认为更合适的数据类型:

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> [System.IO.FileInfo]'c:\test\somefile.txt'

Mode LastWriteTime Length Name
---- ------------- ------ ----
darhsl 01.01.1601 01:00 () somefile.txt



PS> [System.IO.FileInfo]'c:\test\somefile.txt' | Select-Object -Property *


Mode : darhsl
VersionInfo :
BaseName : somefile
Target :
LinkType :
Name : somefile.txt
Length :
DirectoryName : c:\test
Directory : c:\test
IsReadOnly : True
Exists : False
FullName : c:\test\somefile.txt
Extension : .txt
CreationTime : 01.01.1601 01:00:00
CreationTimeUtc : 01.01.1601 00:00:00
LastAccessTime : 01.01.1601 01:00:00
LastAccessTimeUtc : 01.01.1601 00:00:00
LastWriteTime : 01.01.1601 01:00:00
LastWriteTimeUtc : 01.01.1601 00:00:00
Attributes : -1

通过将通用数据类型(如字符串)转换为更适当的数据类型,访问单个信息变得更加容易。例如,如果您想解析文件路径,通过将字符串转换为[System.Io.FileInfo],您可以轻松地拆分路径并提取驱动器、父文件夹、文件名、没有扩展名的文件名或扩展名:

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
PS> $path = [System.IO.FileInfo]'c:\test\somefile.txt'

PS> $path.DirectoryName
c:\test

PS> $path.FullName
c:\test\somefile.txt

PS> $path.Name
somefile.txt

PS> $path.BaseName
somefile

PS> $path.Extension
.txt

PS> $path.Directory.Parent

Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs- 15.02.2023 17:33 c:\



PS> $path.Directory.Parent.Name
c:\

PowerShell 技能连载 - 研究 ConfirmImpact(第 2 部分:脚本作者视角)

在前面的部分中,已经解释了 PowerShell 将 $ConfirmPreference 自动变量作为其风险缓解系统的一部分使用:每当一个 PowerShell 命令自身的 “ConfirmImpact“ 高于或等于 $ConfirmPreference 中的设置时,PowerShell 将显示自动确认对话框。

作为函数或脚本作者,您可以使用属性 CmdletBinding() 设置函数或脚本的“影响级别”:

1
2
3
4
5
6
7
8
9
10
11
12
function Remove-Something
{
[CmdletBinding(ConfirmImpact='Medium')]
param
(
[Parameter(Mandatory)]
[string]
$Name
)

"You entered $Name"
}

示例函数 Remove-Something 已将其 ConfirmImpact 声明为 “Medium“,因此当您运行它时,如果 $ConfirmPreference 设置为 “Medium“ 以上的级别,PowerShell 将触发自动确认。

二进制 cmdlet 也是一样。要找出二进制 cmdlet 的确认影响,您可以使用以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
filter Get-ConfirmInfo
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
[string]
$CommandName
)

$CommandInfo = Get-Command -Name $CommandName
[System.Management.Automation.CommandMetaData]::new($CommandInfo) |
Select-Object -Property Name, ConfirmImpact, SupportsShouldProcess
}

当您提交命令名称时,它将显示命令作者定义的 ConfirmImpact,以及命令是否支持模拟开关,例如 -WhatIf(在这个情况下,SupportsShouldProcess 显示 $true)。

1
2
3
4
5
6
7
PS> 'Get-Random', 'Remove-Item', 'Stop-Process' | Get-ConfirmInfo

Name ConfirmImpact SupportsShouldProcess
---- ------------- ---------------------
Get-Random Medium False
Remove-Item Medium True
Stop-Process Medium True

由于 Get-ConfirmInfo 支持管道,因此您甚至可以检查您的 PowerShell 命令集并搜索高影响级别的命令:

1
2
3
4
5
6
7
8
9
10
11
12
PS> Get-Command -Verb Remove | Get-ConfirmInfo | Where-Object ConfirmImpact -eq High

Name ConfirmImpact SupportsShouldProcess
---- ------------- ---------------------
Remove-CIAccessControlRule High True
Remove-CIVApp High True
Remove-CIVAppNetwork High True
Remove-CIVAppTemplate High True
Remove-BCDataCacheExtension High True
Remove-ClusterAffinityRule High True
Remove-ClusterFaultDomain High True
(...)

PowerShell 技能连载 - 研究 ConfirmImpact(第 1 部分:用户视角)

在 PowerShell 中,默认情况下,$ConfimPreference 变量设置为 “High“。这个设置控制什么?

1
2
PS> $ConfirmPreference
High

任何 PowerShell 命令(二进制 cmdlet 或函数)都可以设置自己的 “ConfirmImpact“:允许的值为 NoneLowMediumHighConfirmImpact 是一个评估 cmdlet 效果的关键性程度。

默认情况下,当 $ConfirmImpact 设置为 “High“ 时,PowerShell 将在你运行设置了 ConfirmImpactHigh 的cmdlet 或函数时自动要求确认(所以你只会在运行像 AD 账户这样不能轻易恢复的东西的 cmdlets 时看到确认对话框弹出)。

作为用户,您可以调整这个风险缓解系统。如果你在一个十分敏感的生产系统上工作,你可能想把 $ConfirmPreference 降低到 Medium 或甚至 Low,以在运行 PowerShell 命令或脚本时获得更多的确认。当设置为 “Low“时,即使创建一个新文件夹也会触发自动确认:

1
2
3
4
5
6
7
8
9
10
PS> $ConfirmPreference = 'Low'

PS> New-Item -Path c:\testfolder

Confirm
Are you sure you want to perform this action?
Performing the operation "Create File" on target "Destination:
C:\testfolder".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):

同样地,如果你想运行一个脚本而不被自动确认对话框打断,你可以把 $ConfirmPreference 设置为 “None“,从而关闭这个风险缓解系统。这比为脚本中可能触发确认的每个命令添加 -Confirm:$false 参数来手动覆盖自动确认要高效得多。

$ConfirmPreference 的任何更改都会在关闭当前 PowerShell 会话时恢复为默认值。它们不会自动持久化。如果您想永久更改这些设置,请创建一个配置文件脚本并将所有永久更改添加到这个脚本中。当 PowerShell 启动时,它会自动启动。

这样的配置文件脚本的路径可以在这里找到:

1
2
PS> $profile.CurrentUserAllHosts
C:\Users\USERNAME\OneDrive\Documents\WindowsPowerShell\profile.ps1

PowerShell 技能连载 - 选择最佳方法:单词转大写(第 2 部分)

在 PowerShell 中,当您需要解决一个问题时,有四种不同来源的命令可以选择。在这个迷你系列中,我们依次查看所有方法。要解决的是同一个问题:如何将一个单词的首字母改为大写。请注意,这是我们随意选为例子的一个问题。该解决方案适用于任何想用 PowerShell 解决的问题。

在 PowerShell 中要解决一个问题最简单的方法是使用合适的 PowerShell cmdlet。可以使用 Get-Command 来搜索已有的 cmdlet。在第一部分中我们已经发现没有一个特定的 PowerShell cmdlet 可以做这件事,所以不得不使用低级的方法来解决这个任务。

由于我们现在已经有解决方案,我们只需要将以下代码转为一个全新的 PowerShell cmdlet(这样我们不需要重复发明这个解决方案,以及我们的生产代码变得更精炼切易于理解和回顾):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$text = "thIS is    A  TEST teXT"
# split text in words
$words = $text -split '\s{1,}' |
# use ForEach-Object to break down the problem to solving ONE instance of your problem
# regardless of how many words there are, the following script block deals with
# one word at a time:
ForEach-Object {
$theWord = $_
# use .NET string methods on the object to solve your issue:
$theWord.SubString(0,1).toUpper() + $theWord.SubString(1).ToLower()
}

# the result is a string array. Use the -join operator to turn it into ONE string:
$result = $words -join ' '
$result

将一段代码转为一个可重用的 cmdlet 只需要按照这些固定的步骤:将代码封装在一个函数中,然后定义它的输入(被称为参数)。例如这样:

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
function Convert-CapitalizeWord
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
$Text
)

process
{
# split text in words
$words = $Text -split '\s{1,}' |
# use ForEach-Object to break down the problem to solving ONE instance of your problem
# regardless of how many words there are, the following script block deals with
# one word at a time:
ForEach-Object {
$theWord = $_
# use .NET string methods on the object to solve your issue:
$theWord.SubString(0,1).toUpper() + $theWord.SubString(1).ToLower()
}

# the result is a string array. Use the -join operator to turn it into ONE string:
$result = $words -join ' '
$result
}
}

请注意头部和尾部的代码。函数中实现逻辑的部分(这个例子中的文本转换部分)仍然保持不变。

当您运行以上代码,PowerShell 设置您的新函数。然后您可以按自己的喜好任意多次调用它,并且由于它是支持管道的,所以您甚至可以通过管道将文本从其它 cmdlet 传给它。如果您想,您可以使用 Get-Content 来读取整个文本并使用 Convert-CapitalizeWord 将每个单词的首字母改为大写——PowerShell 中的函数像一个奇迹,能够使得函数可复用以及可伸缩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# you get automatic prompts when you forget to submit mandatory arguments:
PS> Convert-CapitalizeWord
cmdlet Convert-CapitalizeWord at command pipeline position 1
Supply values for the following parameters:
Text: heLLO WOrld
Hello World

# you can submit your text to the -Text parameter:
PS> Convert-CapitalizeWord -Text 'this iS a LONG teXT'
This Is A Long Text

# you can pipe as many texts as you like (scalable) via the pipeline:
PS> 'Hello world!', 'someTHING else' | Convert-CapitalizeWord
Hello World!
Something Else

您可以将函数复制/粘贴到需要使用的脚本中。

为了让函数认识并自动加载(类似所有其它“内置”PowerShell cmdlet),并且成为您 shell 的一个永久的命令扩展,您可以将函数存储在模块中。

目前,收获是:通过包装代码在函数,使得代码可重用,自动添加了可扩展性(在上面的例子中,我们现在可以转换在一个调用中转换一个或者上千个字符串),以及使生产脚本代码变得更短,可以专注于它真正想要完成什么。

PowerShell 技能连载 - 选择最佳方法:单词转大写(第 3 部分)

在 PowerShell 中,当您需要解决一个问题时,有四种不同来源的命令可以选择。在这个迷你系列中,我们依次查看所有方法。要解决的是同一个问题:如何将一个单词的首字母改为大写。请注意,这是我们随意选为例子的一个问题。该解决方案适用于任何想用 PowerShell 解决的问题。

在前一部分中,我们已经使用 PowerShell 操作符和通用的字符串方法来解决问题。然而,在代码中通常有一个真理:使用越专用的命令,代码就越简洁。

所以 PowerShell 可以使用另一个来源的命令:从和 PowerShell 一起分发的上千个 .NET 库中选择静态 .NET 方法。有一个比通用操作符和字符串方法简单得多的解决方案:

1
2
3
4
5
6
$text = "thIS is    A  TEST teXT"
[CultureInfo]::InvariantCulture.TextInfo.ToTitleCase($text)
Words that are ALL CAPITALIZED will remain untouched:


This Is A TEST Text

如果您不喜欢有些例外的单词没有被转成首字母大写,那么先将文本转为全小写然后传给该方法:

1
2
3
4
5
PS> [CultureInfo]::InvariantCulture.TextInfo.ToTitleCase('TEST remains aLL uppER Case')
TEST Remains All Upper Case

PS> [CultureInfo]::InvariantCulture.TextInfo.ToTitleCase('TEST remains aLL uppER Case unless you lowerCASE YOUR text beFORE'.ToLower())
Test Remains All Upper Case Unless You Lowercase Your Text Before

如您所见(和这个系列之前的部分相比),空格仍然保持不变,由于我们从没有将文本分割为独立的单词。如果您不喜欢这一点,并且想将多个空格符合并为一个,只需要添加 -replace 运算符。它能将所有字符串整理好:

1
2
3
4
5
6
7
$text = "thIS is    A  TEST teXT"
# title convert and then replace two or more spaces with one space only:
[CultureInfo]::InvariantCulture.TextInfo.ToTitleCase($text.ToLower()) -replace '\s{2,}', ' '
Now this approach returns the exact same result as in our previous parts:


This Is A Test Text

以上代码很短并且很简单,这样您可以直接在代码合适的地方使用它,但是您下星期或者下个月要做相同的转换时还能记得它吗?

所以仍然可以将该代码包装成为一个函数。您可以将我们在第 2 部分中创建的函数升级为更新更有效的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Convert-CapitalizeWord
{
param
(
[Parameter(Mandatory,ValueFromPipeline)]
$Text
)

process
{
[CultureInfo]::InvariantCulture.TextInfo.ToTitleCase($text.ToLower()) -replace '\s{2,}', ' '
}
}

当您运行该函数,它将和之前的版本一样灵活和可扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# you get automatic prompts when you forget to submit mandatory arguments:
PS> Convert-CapitalizeWord
cmdlet Convert-CapitalizeWord at command pipeline position 1
Supply values for the following parameters:
Text: heLLO WOrld
Hello World

# you can submit your text to the -Text parameter:
PS> Convert-CapitalizeWord -Text 'this iS a LONG teXT'
This Is A Long Text

# you can pipe as many texts as you like (scalable) via the pipeline:
PS> 'Hello world!', 'someTHING else' | Convert-CapitalizeWord
Hello World!
Something Else