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 中搜索有用的信息,并且获取得到这些信息的代码。

PowerShell 技能连载 - 注册缺省的 PowerShell 源

如果您使用 PowerShellGet 模块(默认随着 Windows 10 和 Server 2016 分发),您可以方便地下载和安装共享的 PowerShell 脚本和模块:

1
2
3
4
5
6
7
8
PS> Find-Module -Tag Security

Version Name Repository Description
------- ---- ---------- -----------
2.5.0 Carbon PSGallery Carbon is a PowerShell module for automating t...
0.8.1 ACMESharp PSGallery Client library for the ACME protocol, which is...
2.22 DSInternals PSGallery The DSInternals PowerShell Module exposes seve...
1.2.0.0 DSCEA PSGallery DSCEA is a scanning engine for processing Test...

不过有些时候,机器上缺失了缺省的 PSGallery 源,要还原缺省设置,请使用以下代码:

1
PS> Register-PSRepository -Default

PowerShell 技能连载 - 查找安装的软件

大多数已安装的文件将自己注册在 Windows 注册表中的四个位置。以下是一个名为 Get-InstalledSoftware 的快速 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
36
function Get-InstalledSoftware
{
param
(
$DisplayName='*',

$DisplayVersion='*',

$UninstallString='*',

$InstallDate='*'

)

# registry locations where installed software is logged
$pathAllUser = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
$pathCurrentUser = "Registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
$pathAllUser32 = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$pathCurrentUser32 = "Registry::HKEY_CURRENT_USER\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"


# get all values
Get-ItemProperty -Path $pathAllUser, $pathCurrentUser, $pathAllUser32, $pathCurrentUser32 |
# choose the values to use
Select-Object -Property DisplayVersion, DisplayName, UninstallString, InstallDate |
# skip all values w/o displayname
Where-Object DisplayName -ne $null |
# apply user filters submitted via parameter:
Where-Object DisplayName -like $DisplayName |
Where-Object DisplayVersion -like $DisplayVersion |
Where-Object UninstallString -like $UninstallString |
Where-Object InstallDate -like $InstallDate |

# sort by displayname
Sort-Object -Property DisplayName
}

这个函数也演示了如何不使用 PowerShell 驱动器而直接使用原生的注册表路径。方法是在路径前面添加 provider 的名称。在这个例子中是 Registry::

这个函数将所有输出的列(属性)也暴露为参数,所以可以方便地过滤查询结果。以下例子演示了如何查找所有名字包含“Microsoft”的软件:

1
2
3
4
5
6
7
8
9
10
11
PS C:\> Get-InstalledSoftware -DisplayName *Microsoft*

DisplayVersion DisplayName
-------------- -----------
Definition Update for Microsoft Office 2013 (KB3115404) 32-Bit...
15.0.4569.1506 Microsoft Access MUI (English) 2013
15.0.4569.1506 Microsoft Access Setup Metadata MUI (English) 2013
15.0.4569.1506 Microsoft DCF MUI (English) 2013
15.0.4569.1506 Microsoft Excel MUI (English) 2013
15.0.4569.1506 Microsoft Groove MUI (English) 2013
(...)

PowerShell 技能连载 - 禁止 Windows 10 中的 OneDrive

您是否也为任务栏中的 OneDrive 图标感到烦恼?如果您从未使用 OneDrive,以下是两个易于使用的 PowerShell 函数,能够帮助您在资源管理器中隐藏(和显示)OneDrive 图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Disable-OneDrive
{
$regkey1 = 'Registry::HKEY_CLASSES_ROOT\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
$regkey2 = 'Registry::HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
Set-ItemProperty -Path $regkey1, $regkey2 -Name System.IsPinnedToNameSpaceTree -Value 0
}


function Enable-OneDrive
{
$regkey1 = 'Registry::HKEY_CLASSES_ROOT\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
$regkey2 = 'Registry::HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}'
Set-ItemProperty -Path $regkey1, $regkey2 -Name System.IsPinnedToNameSpaceTree -Value 1
}

执行后不需要重启,结果立即生效。

PowerShell 技能连载 - Get-Service 的替代

Get-Service cmdlet 有一系列缺点。例如,没有一个过滤运行中或停止的服务的参数,并且结果不包含服务的启动模式。

WMI 可以传出这些信息。以下是一个获取最常用服务信息的简单函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Get-ServiceWithWMI
{
param
(
$Name = '*',
$State = '*',
$StartMode = '*'
)

Get-WmiObject -Class Win32_Service |
Where-Object Name -like $Name |
Where-Object State -like $State |
Where-Object StartMode -like $StartMode |
Select-Object -Property Name, DisplayName, StartMode, State, ProcessId, Description

}

以下是调用该命令的方法,这行代码显示所有禁用的服务,包括它们的友好名称和描述:

1
PS C:\> Get-ServiceWithWMI -StartMode Disabled | Out-GridView

PowerShell 技能连载 - 轻量级 Robocopy

Robocopy.exe 是一个非常有用并且功能多样的内置命令,它可以高效地将文件从一个地方复制到另一个地方。不幸的是,该命令有很多选项和开关,使得它很难掌握。

如果您只是希望将文件从 A 处拷贝到 B 处,以下是将 robocopy 封装并将这个怪兽转化为易用的复制命令的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Copy-FileWithRobocopy
{
param
(
[Parameter(Mandatory)]
$Source,

[Parameter(Mandatory)]
$Destination,

$Filter = '*'
)

robocopy.exe $Source $Destination $Filter /S /R:0
}

以下是如何使用新命令的方法:下面这行代码将所有 .log 文件从 Windows 文件夹复制到 c:\logs 文件夹:

1
PS> Copy-FileWithRobocopy -Source $env:windir -Destination c:\logs -Filter *.log

PowerShell 技能连载 - 创建一个清单式的摘要对象

从 PowerShell 3 开始,PSCustomObject 可以将从其他地方收集的有用信息方便地合并进来。以下例子从不同的 WMI 类获取各种信息,并且输出为一个清单。该清单可以传递给其它命令,也可以直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# get information from this computer
$Computername = "."

# get basic information (i.e. from WMI)
$comp = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $Computername
$bios = Get-WmiObject -Class Win32_bios -ComputerName $Computername
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computername


# combine everything important in one object
[PSCustomObject]@{
ComputerName = $Computername
Timestamp = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Model = $comp.Model
Manufacturer = $comp.Manufacturer
BIOSVersion = $bios.SMbiosbiosversion
BIOSSerialNumber = $bios.serialnumber
OSVersion = $os.Version
InstallDate = $os.ConvertToDateTime( $os.InstallDate)
LastBoot = $os.ConvertToDateTime($os.lastbootuptime)
LoggedOnUser = $Comp.UserName
}