PowerShell 技能连载 - 使用在线帮助

PowerShell 的发行版并没有带帮助文件,而本地安装帮助文件需要管理员权限。

更简单的获取帮助的方法是访问 cmdlet 的在线帮助,类似这样:

1
Get-Help -Name Get-Acl -Online

这将启动一个浏览器并导航到在线帮助。在线帮助通常内容更新并且更容易获得。当然,它需要 internet 连接,并且并不是每个 cmdlet 都有在线帮助版本。

PowerShell 技能连载 - 同时输出和赋值

在前一个技能中我们介绍了如何记录脚本结果,以及如何使用括号来同时输出和赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> ($a = Get-Process -Id $pid)

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1595 102 283200 325444 64,56 6436 1 powershell_ise



PS> $a

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1595 102 283200 325444 64,75 6436 1 powershell_ise



PS>

还可以用 -OutVariable 通用参数来实现相同的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> Get-Process -Id $pid -OutVariable b

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1731 105 290336 341688 66,66 6436 1 powershell_ise



PS> $b

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1731 105 290336 341688 66,92 6436 1 powershell_ise



PS>

Tee-Object 是第三种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> Get-Process -Id $pid | Tee-Object -Variable c

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1759 109 292300 343644 71,53 6436 1 powershell_ise



PS> $c

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1759 109 292300 343644 71,69 6436 1 powershell_ise



PS>

以上方法使用了管道,而管道的速度比较慢。如果希望提升性能,那么避免使用管道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> Tee-Object -InputObject (Get-Process -Id $pid) -Variable d

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1761 111 294568 345268 74,31 6436 1 powershell_ise



PS> $d

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1761 111 294568 345268 74,59 6436 1 powershell_ise



PS>

PowerShell 技能连载 - 记录脚本输出

有一系列办法能记录脚本的输出结果,但是一个非常偷懒的办法是使用 Start-Transcript。在 PowerShell 5 中,这个 cmdlet 不仅在 powershell.exe 中支持,而且在所有宿主中都支持。所以您在 PowerShell ISE 或其它编辑器中都可以使用它。另外,transcript 支持嵌套,所以如果您写一个脚本,您可以安全地在起始处加上 Start-Transcript 并且在结尾处加上 Stop-Transcript

Start-Transcript 将所有输出输出写入一个文本文件。如果您没有指定路径,那么该 cmdlet 将会使用默认路径。您只需要确保脚本确实产生了可记录的输出。

要使脚本更详细,请结合这个技巧一起使用:当您将赋值语句放入一对括号 (),该赋值语句也会输出赋值的数据。这个输出结果也会被 transcript 接收到。请试试一下代码:

1
2
3
4
5
6
7
# default assignment, no output
$a = Get-Service
$a.Count

# assignment in parentheses, outputs the assignment
($b = Get-Service)
$b.Count

PowerShell 技能连载 - 在 Linux 的 PowerShell Core 中安装模块

当您想通过 PowerShellGet 库为所有用户安装模块,您需要管理员权限。在 Linux 的 PowerShell Core 中,您可以用 sudo 命令来启用管理员权限,并且运行 PowerShell。只需要把命令写在在大括号中即可。

在 Linux 的 PowerShell Core 上,以下命令将为所有用户从 PowerShell Gallery 中安装 AzureRM.NetCore:

1
sudo powershell -Command {Install-Module -Name AzureRM.Netcore}

PowerShell 技能连载 - PowerShell.exe 的“大括号秘密”

当从 PowerShell 或 PowerShell Core 中调用 powershell.exe 时有一些小秘密:当您执行 powershell.exe 并通过 -Command 传递一些命令时,PowerShell 将运行这个命令并返回纯文本:

1
2
3
$a = powershell -noprofile -Command Get-Service
$a[0].GetType().FullName
System.String

所以当您将代码放在大括号中执行时,PowerShell 会将强类型的结果序列化后返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$a = powershell -noprofile -Command { Get-Service }
$a[0].GetType().FullName
System.Management.Automation.PSObject

$a[0] | Select-Object -Property *

Name : AdobeARMservice
RequiredServices : {}
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
DisplayName : Adobe Acrobat Update Service
DependentServices : {}
MachineName : .
ServiceName : AdobeARMservice
ServicesDependedOn : {}
Status : Running
ServiceType : Win32OwnProcess
StartType : Automatic
Site :
Container :

PowerShell 技能连载 - 解析完全限定名

完全限定名是字符串的格式,而字符串包含丰富的处理数据方法。最强大而又十分简单的是 Split() 方法。

请看使用 Split() 解析完全限定名,获取最后一个元素的名称,是多么简单:

1
2
3
$dn = 'CN=pshero010,CN=Users,DC=powershell,DC=local'
$lastElement = $dn.Split(',')[0].Split('=')[-1]
$lastElement

Split() 总是返回一个字符串数组。通过大括号,您可以操作每一个独立的数组元素。所以这段代码首先用逗号分割,然后取出第一个元素,即“CN=pshero010”。然后,再次使用相同的技术,用“=”分割。在这里,我们关心的是最后一个数组元素。PowerShell 支持负的数组下标,它从数组的尾部开始计算,所以下标为 -1 获取的是最后一个数组元素。任务完成!

PowerShell 技能连载 - 审计登录事件

您是否想知道当您不在的时候是否有人登录过您的 PC?在前一个技能中我们解释了如何从 Windows 安全日志中解析详细的审计信息,假设您拥有管理员权限。

To find out who logged into your PC, try the code below! The function Get-LogonInfo searches for security events with ID 4624. Security information is protected, so you need to be an Administrator to run this code. This is why the code uses a #requires statement that prevents non-Admins from running the code.
要查看谁登录到了您的 PC,请试试以下代码!Get-LogonInfo 函数搜索 ID 为 4624 的安全事件。安全信息是受保护的,所以只有管理员账户才能执行这段代码。这是为什么这段代码使用 #requires 来防止非管理员执行这段代码的原因。

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
#requires -RunAsAdministrator

function Get-LogonInfo
{
param
(
[Int]$Newest = [Int]::MaxValue,
[DateTime]$Before,
[DateTime]$After,
[string[]]$ComputerName,
$Authentication = '*',
$User = '*',
$Path = '*'
)

$null = $PSBoundParameters.Remove('Authentication')
$null = $PSBoundParameters.Remove('User')
$null = $PSBoundParameters.Remove('Path')
$null = $PSBoundParameters.Remove('Newest')


Get-EventLog -LogName Security -InstanceId 4624 @PSBoundParameters |
ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeGenerated
User = $_.ReplacementStrings[5]
Domain = $_.ReplacementStrings[6]
Path = $_.ReplacementStrings[17]
Authentication = $_.ReplacementStrings[10]

}
} |
Where-Object Path -like $Path |
Where-Object User -like $User |
Where-Object Authentication -like $Authentication |
Select-Object -First $Newest
}


$yesterday = (Get-Date).AddDays(-1)
Get-LogonInfo -After $yesterday |
Out-GridView

这个函数也利用了 $PSBoundParameters 哈希表。这个哈希表包含了用户传入的所有参数。只有一部分信息需要传递给 Get-EventLog 命令,所以用于其他参数需要从哈希表中移除。这样,用户可以只传递 BeforeAfterComputerNameGet-EventLog 命令。

接下来,处理事件信息。所有相关的信息都可以在 ReplacementStrings 属性中找到。这个属性是一个数组。正如结果所展示的那样,ID 为 4624 的事件,第六个(下标为 5)元素为用户名,第七个(下标为 6)元素为域名,第十八个(下标为 17)列出执行登录操作的可执行程序路径。

物理上的登陆通常是由 lass ,即本地安全授权执行的。所以要只查看由人类执行的登录操作,请使用以下代码:

1
2
3
$yesterday = (Get-Date).AddDays(-1)
Get-LogonInfo -After $yesterday -Path *\lsass.exe |
Out-GridView

PowerShell 技能连载 - 查找所有 UAC 提权记录

Windows 的“安全”日志包含了丰富的审计信息。默认情况下,它记录了所有的提权请求。当您以管理员身份运行一个应用程序的时候,就会产生一条记录。

要获取您机器上提权的记录列表,请试试以下代码:

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
#requires -RunAsAdministrator

function Get-ElevationInfo
{
param
(
[DateTime]$Before,
[DateTime]$After,
[string[]]$ComputerName,
$User = '*',
$Privileges = '*',
$Newest = [Int]::MaxValue
)

$null = $PSBoundParameters.Remove('Privileges')
$null = $PSBoundParameters.Remove('User')
$null = $PSBoundParameters.Remove('Newest')



Get-EventLog -LogName Security -InstanceId 4672 @PSBoundParameters |
ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeGenerated
User = $_.ReplacementStrings[1]
Domain = $_.ReplacementStrings[2]
Privileges = $_.ReplacementStrings[4]
}
} |
Where-Object Path -like $Privileges |
Where-Object User -like $User |
Select-Object -First $Newest
}

Get-ElevationInfo -User pshero* -Newest 2 |
Out-GridView

Get-ElevationInfo 查询 ID 为 4672 的系统日志。安全信息是受保护的,所以只有管理员账户才能执行这段代码。这是为什么这段代码使用 #requires 来防止非管理员执行这段代码的原因。

这个函数也利用了 $PSBoundParameters 哈希表。这个哈希表包含了用户传入的所有参数。只有一部分信息需要传递给 Get-EventLog 命令,所以用于其他参数需要从哈希表中移除。这样,用户可以只传递 BeforeAfterComputerNameGet-EventLog 命令。

接下来,处理事件信息。所有相关的信息都可以在 ReplacementStrings 属性中找到。这个属性是一个数组。正如结果所展示的那样,ID 为 4672 的事件,第二个(下标为 1)元素为用户名,第三个(下标为 2)元素为域名,第五个(下标为 4)列出获取到的安全特权。

PowerShell 技能连载 - 如何正确地封装多个结果

当一个 PowerShell 函数需要返回多用于一种信息时,一定要将它们打包成一个对象。只有通过这种方法,调用者才能够发现和独立存取该信息。以下是一个快速的例子。

这个函数只是输出三段数据。它以一个包含不同对象的数组形式返回:

1
2
3
4
5
6
7
8
9
10
11
function test
{
33.9
"Hallo"
Get-Date
}

$result = test

$result.Count
$result

以下是一个更好的函数,能返回相同的信息,但是这些信息被封装为一个结构化的对象。通过这种方法,用户可以容易地读取函数返回的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test
{
[PSCustomObject]@{
Number = 33.9
Text = "Hallo"
Date = Get-Date
}

}


$result = test
$result.Count

$result

$result.Number

PowerShell 技能连载 - 探索 WMI

如果您知道 WMI 类查询的名字,Get-WmiObjectGet-CimInstance 两个命令都可以提供丰富的信息。

以下是一个名为 Explore-WMI 的快速的 PowerShell 函数,它可以帮您查找有用的 WMI 类名:

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 Explore-WMI
{
# find all WMI classes that start with "Win32_"...
$class = Get-WmiObject -Class Win32_* -List |
# exclude performance counter classes...
Where-Object { $_.Name -notlike 'Win32_Perf*' } |
# exclude classes with less than 6 properties...
Where-Object { $_.Properties.Count -gt 5 } |
# let the user select one of the found classes
Out-GridView -Title 'Select one' -OutputMode Single

# display selected class name
Write-Warning "Klassenname: $($class.Name)"

# query class...
Get-WmiObject -Class $class.Name |
# and show all of its properties
Select-Object -Property *


# output code
$name = $class.name
" Get-WmiObject -Class $name | Select-Object -property *" | clip.exe

Write-Warning 'Code copied to clipboard. Paste code to try'
}

当运行完这段代码后,调用 Explore-WMI 命令,它将会打开一个 grid view 窗口,显示所有以 “Win32_” 开头的 WMI 类,并且不包括性能计数器类,并且暴露至少 6 个属性。您接下来可以选择其中一个。PowerShell 将会显示这个类的实例和它的所有数据,然后将生成这些结果的命令复制到剪贴板。

通过这种方式可以方便有趣地在 WMI 中搜索有用的信息,并且获取得到这些信息的代码。