PowerShell 技能连载 - 将 UNIX 时间转为 DateTime

“UNIX时间”计算自 01/01/1970 以来经过的秒数。

例如,在 Windows 中,您可以从 Windows 注册表中读取安装日期,返回的值为 “Unix时间”:

1
2
3
4
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateUnix = $values.InstallDate

$installDateUnix

结果是类似这样的大数字:

1601308412

To convert “Unix time” to a real DateTime value, .NET Framework provides a type called [DateTimeOffset]:
要将“UNIX时间”转换为真实的 DateTime 值,请使用 .NET Framework 提供的 [DateTimeOffset] 类:

1
2
3
4
$values = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$installDateUnix = $values.InstallDate

[DateTimeOffset]::FromUnixTimeSeconds($installDateUnix)

现在您能得到不同的日期和时间表示:

DateTime      : 28.09.2020 15:53:32
UtcDateTime   : 28.09.2020 15:53:32
LocalDateTime : 28.09.2020 17:53:32
Date          : 28.09.2020 00:00:00
Day           : 28
DayOfWeek     : Monday
DayOfYear     : 272
Hour          : 15
Millisecond   : 0
Minute        : 53
Month         : 9
Offset        : 00:00:00
Second        : 32
Ticks         : 637369052120000000
UtcTicks      : 637369052120000000
TimeOfDay     : 15:53:32
Year          : 2020

要获取本地格式的安装时间,您可以在一行代码中写完它:

1
2
3
PS> [DateTimeOffset]::FromUnixTimeSeconds((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').InstallDate).DateTime

Moday, September 28, 2020 15:53:32

PowerShell 技能连载 - 创建动态参数

动态参数是一种特殊的参数,可以根据运行时条件显示或隐藏。 您的 PowerShell 函数可以例如具有一个参数,并基于用户选择的操作,将显示其他参数。或者,只有在用户具有管理员权限时才能显示参数。

不幸的是,组合动态参数并不是一件轻松的事。借助称为 “dynpar” 的模块,使用动态参数变得同样简单,就像使用“普通”静态参数一样简单,然后您可以简单地使用名为 [Dynamic()] 的新属性指定动态参数,该属性告诉 PowerShell 需要满足以哪些条件以显示参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
param
(
# regular static parameter
[string]
$Normal,

# show -Lunch only at 11 a.m. or later
[Dynamic({(Get-Date).Hour -ge 11})]
[switch]
$Lunch,

# show -Mount only when -Path refers to a local path (and not a UNC path)
[string]
$Path,

[Dynamic({$PSBoundParameters['Path'] -match '^[a-z]:'})]
[switch]
$Mount
)

您可以在此处找到一份详细的操作指南:https://github.com/tobiaspsp/modules.dynpar

PowerShell 技能连载 - 分割文本行(第 3 部分)

在上一个技能中,我们将一大块多行文本拆分为单独的行,并删除了所有空行。

然而,当一行不是真正的空,而是包含空格(空格或制表符)时,它仍然会被输出:

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
# $data is a single string and contains blank lines
$data = @'

Server1


Server2
Cluster4


'@

$data = $data.Trim()

# split in single lines and remove empty lines
$regex = '[\r\n]{1,}'
$array = $data -split $regex

$array.Count

$c = 0
Foreach ($_ in $array)
{
'{0:d2} {1}' -f $c, $_
$c++
}

在这里,我们在 “Server2” 正上方的行中添加了几个空格(当然,在列表中看不到)。以下是执行结果:

00 Server1
01
02 Server2
03 Cluster4

由于我们要在任意数量的 CR 和 LF 字符处拆分,因此空格会破坏该模式。

与其将正则表达式变成一个更复杂的野兽,不如为这些事情附加一个简单的 Where-Object 来进行精细修饰:

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
# $data is a single string and contains blank lines
$data = @'

Server1


Server2
Cluster4


'@

$data = $data.Trim()

# split in single lines and remove empty lines
$regex = '[\r\n]{1,}'
$array = $data -split $regex |
Where-Object { [string]::IsNullOrWhiteSpace($_) -eq $false }

$array.Count

$c = 0
Foreach ($_ in $array)
{
'{0:d2} {1}' -f $c, $_
$c++
}

[string]::IsNullOrEmpty() 代表我们所追求的情况,因此符合条件的行被 Where-Object 删除。结果就是所需要的理想结果:

00 Server1
01 Server2
02 Cluster4

PowerShell 技能连载 - 分割文本行(第 2 部分)

假设您的脚本获取文本输入数据,并且您需要将文本拆分为单独的行。在上一个技能中,我们建议了一些正则表达式来完成这项工作。但是如果输入文本包含空行怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# $data is a single string and contains blank lines
$data = @'

Server1


Server2
Cluster4


'@

# split in single lines and remove empty lines
$regex = '[\r\n]{1,}'

如您所见,我们使用的正则表达式会自动处理文本中间的空白行,但文本开头或结尾的空白行会保留。

00
01 Server1
02 Server2
03 Cluster4
04

那是因为我们在任意数量的新行处分割,所以我们也在文本的开头和结尾处分割。我们实际上是自己生成了这两个剩余的空白行。

为了避免这些空行,我们必须确保文本的开头和结尾没有换行符。这就是 Trim() 可以做的事情:

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
# $data is a single string and contains blank lines
$data = @'

Server1


Server2
Cluster4


'@

$data = $data.Trim()

# split in single lines and remove empty lines
$regex = '[\r\n]{1,}'
$array = $data -split $regex

$array.Count

$c = 0
Foreach ($_ in $array)
{
'{0:d2} {1}' -f $c, $_
$c++
}



00 Server1
01 Server2
02 Cluster4

PowerShell 技能连载 - 分割文本行(第 1 部分)

有时,您需要逐行处理多行文本。下面是一个多行字符串的示例:

1
2
3
4
5
6
7
8
9
10
11
# working with 1-dimensional input

# $data is a single string
$data = @'
Server1
Server2
Cluster4
'@

$data.GetType().FullName
$data.Count

将文本拆分为单独的行的一种有效方法是使用带有正则表达式的 -split 运算符,该表达式可以处理各种依赖于平台的行终止符:

1
2
3
4
5
6
7
8
9
10
# split the string in individual lines
# $array is an array with individual lines now

$regex = '[\r\n]{1,}'
$array = $data -split $regex

$array.GetType().FullName
$array.Count

$array

以下是您在脚本中遇到的 $regex 中的正则表达式的一些替代方案:

分隔用的正则表达式 说明
/r 类似于“回车”(ASCII 13)。如果操作系统不是用该字符作为换行符,则分割将会失败。如果操作系统使用该字符加上“换行”(ASCII 10),那么多出来的不可见的换行符会破坏字符串。
/n 类似上面的情况,只是相反。
[\r\n]+ 与上面的示例代码相同。如果有一个或多个字符,PowerShell 会在两个字符处拆分。这样,CR、LF 或 CRLF、LFCR 在拆分时都被删除。但是,多个连续的新行也将全部删除:CRCRCR 或 CRLFCRLF。
(\r\n \r

如果您从文本文件中读取文本,Get-Content 会自动将文本拆分为行。要将整个文本内容作为单个字符串读取,则需要添加 -Raw 参数。

PowerShell 技能连载 - 截屏

借助 System.Windows.Forms 中的类型,PowerShell 可以轻松捕获屏幕并将屏幕截图保存到文件中。下面的代码捕获整个虚拟屏幕,将屏幕截图保存到文件中,然后在相关程序中打开位图文件(如果有):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$Path = "$Env:temp\screenshot.bmp"
Add-Type -AssemblyName System.Windows.Forms

$screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
$width = $screen.Width
$height = $screen.Height
$left = $screen.Left
$top = $screen.Top
$bitmap = [System.Drawing.Bitmap]::new($width, $height)
$MyDrawing = [System.Drawing.Graphics]::FromImage($bitmap)
$MyDrawing.CopyFromScreen($left, $top, 0, 0, $bitmap.Size)

$bitmap.Save($Path)
Start-Process -FilePath $Path

PowerShell 技能连载 - 禁用本地的 Guest 账户

Windows 自带一个名为 “Guest” 的内置帐户。由于此帐户很少使用,最好将它禁用。否则,它的广为人知的名字可能会成为攻击者的目标。

由于帐户名称是本地化的并且可能因文化而略有不同,因此要识别帐户,请使用其 SID:

1
2
3
4
5
PS> Get-Localuser | Where-Object Sid -like 'S-1-5-*-501'

Name Enabled Description
---- ------- -----------
Guest False Built-in account for guest access to the computer/domain

如果该帐户尚未禁用(请查看 ““Enabled”“ 属性),请使用提升权限的 PowerShell 和 Disable-LocalUser cmdlet 禁用该帐户。

PowerShell 技能连载 - 重命名本地管理员账户

出于安全原因,您可能需要考虑重命名内置的本地管理员帐户。这个账户权限很大,它的名字很容易猜到,所以它是攻击者的常用载体。在重命名此帐户之前,请确保您了解后果:

  • 该账户仍能继续工作,但您现在需要使用新分配的名称来登录该帐户。确保没有使用旧的默认名称的自动登录过程
  • 重命名帐户不会更改其 SID,因此老练的攻击者仍然可以使用其众所周知的 SID 锁定此帐户

要重命名内置 Administrator 帐户(或任何其他本地帐户),请以管理员权限启动 PowerShell,然后运行以下代码:

1
PS> Rename-LocalUser -Name "Administrator" -NewName "TobiasA"

要使用该帐户登录,请使用新分配的名称。通过使用账户的众所周知的 SID,即使您不知道其名称,您仍然可以识别重命名的帐户:

1
2
3
4
5
PS> Get-Localuser | Where-Object Sid -like 'S-1-5-*-500'

Name Enabled Description
---- ------- -----------
TobiasA False Built-in account for administering the computer/domain

PowerShell 技能连载 - 识别本地管理员组

内置管理员组的任何成员都可以访问广泛的权限,因此检查该组的成员可以成为安全审核的一部分。虽然 “Administrators” 组默认存在,但其名称可能因文化而异,因为它是本地化的。例如,在德国系统中,该组称为 “Administratoren”。

要访问用户组,而无论文化和命名如何变化,请使用其 SID,它始终为 “S-1-5-32-544”:

1
2
3
4
5
PS> Get-LocalGroup -SID S-1-5-32-544

Name Description
---- -----------
Administrators Administrators have complete and unrestricted access to the...

同样,要转储具有管理员权限的用户和组列表,请使用 SID 而不是组名:

1
PS> Get-LocalGroupMember -SID S-1-5-32-544

PowerShell 技能连载 - 识别本地管理员帐户

Windows 计算机上有一些默认帐户,例如本地 “Administrator” 帐户。虽然默认情况下此帐户存在,但其名称可以因文化而异,并且其名称也可以重命名。

要始终识别本地管理员帐户而不管其名称如何,请按 SID(安全标识符)搜索本地帐户。本地管理员帐户 SID 始终以 “S-1-5-“ 开头并使用 RID “-500”:

1
2
3
4
5
PS> Get-Localuser | Where-Object Sid -like 'S-1-5-*-500'

Name Enabled Description
---- ------- -----------
Administrator False Built-in account for administering the computer/domain