PowerShell 技能连载 - 当格式化失败时

也许您遇到了PowerShell遇到一个奇怪的格式问题:当您分别执行线路时,与执行同一行相比,输出格式会更改。
您在使用 PowerShell 时可能会遇到一个奇怪的格式问题:当您一行一行执行代码时,得到的输出结果和以整体的方式执行一段代码有所不同。

这是要执行的示例代码:

Get-Process -Id $pid
Get-Date
Get-Service -Name Spooler

当您将三行代码作为脚本整体运行时,只有第一个命令返回表格,后两个显示列表。但是,当您逐行执行代码时,它们的格式不同,并显示为表格,甚至是单行纯文本。

这是 PowerShell 实时输出格式的副作用:当输出格式化器遇到第一个返回数据时,它必须决定格式化和写入,即表头。所有剩余的数据将插入到该输出格式中。

每当您的脚本返回多个对象并且这些对象具有不同类型时,PowerShell 就会意识到这些对象不适合现有表设计。在这种情况下,所有后续对象将格式化成列表视图。

如果您想更好地控制此行为,则可以随时将输出发送到 Out-Default,这将关闭当前的输出。任何后续对象都将启动新的输出格式。以下代码将始终显示相同的显示格式,无论您是作为脚本运行还是单独运行命令:

1
2
3
Get-Process -Id $pid | Out-Default
Get-Date | Out-Default
Get-Service -Name Spooler

PowerShell 技能连载 - 获取系统正常运行时间

The Get-ComputerInfo cmdlet 可以提供有关 Windows 客户端或服务器的大量信息,例如正常运行时间和其他相关信息。

试试以下代码:

1
2
3
4
5
PS> Get-ComputerInfo | Select-Object -Property *Upt*

CsWakeUpType OsLastBootUpTime OsUptime
------------ ---------------- --------
PowerSwitch 25.10.2022 17:32:23 6.23:07:47.4872044

该命令获取所有名字中包含 “upt” 的属性,这些属性恰好都包含了和系统运行时间有关的信息。

当然,您还可以将信息存储到变量中,并单独查询属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS> $info = Get-ComputerInfo | Select-Object -Property *Upt*

PS> $info.OsUptime


Days : 6
Hours : 23
Minutes : 9
Seconds : 28
Milliseconds : 617
Ticks : 6017686177952
TotalDays : 6,96491455781481
TotalHours : 167,157949387556
TotalMinutes : 10029,4769632533
TotalSeconds : 601768,6177952
TotalMilliseconds : 601768617,7952


PS> $info.OsUptime.TotalHours
167,157949387556

由于属性值显示在一列中,它们显示为一行字符串。如果您单独查询它们,例如 OsUptime,它们将暴露它们的所有自身属性。

PowerShell 技能连载 - 无人值守读取 PFX 证书

PowerShell 配备了一个名为 Get-PfxCertificate 的 cmdlet,您可以用来将证书和私钥加载到内存中。但是如果证书受密码保护,则有一个强制性提示来输入密码。您不能通过参数提交密码,因此该 cmdlet 不能无人值守使用。

这是一个替代的函数,允许通过参数输入密码,从而允许以无人值守的方式即时加载 pfx 证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Get-PfxCertificateUnattended
{
param
(
[String]
[Parameter(Mandatory)]
$FilePath,

[SecureString]
[Parameter(Mandatory)]
$Password
)

# get clear text password
$plaintextPassword = [PSCredential]::new("X", $Password).GetNetworkCredential().Password


[void][System.Reflection.Assembly]::LoadWithPartialName("System.Security")
$container = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$container.Import($FilePath, $plaintextPassword, 'PersistKeySet')
$container[0]
}

请注意,该功能始终返回 pfx 文件中发现的第一个证书 如果您的PFX文件包含多个证书,则可能需要在最后一行代码中调整索引。

PowerShell 技能连载 - 创建新的代码签名测试证书

PowerShell 配备了一个名为 New-SelfSignedCertificate 的 cmdlet,可以创建各种自签名的测试证书。但是,使用它为 PowerShell 代码签名创建证书并不直观,更不用说在测试机上确保测试证书值得信任。

That’s why here you find a function that embeds the cmdlet and makes it much easier to create code signing certificates both persistent and exportable:
所以我们编写了一个函数将上述 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function New-CodeSigningCert
{
[CmdletBinding(DefaultParametersetName="__AllParameterSets")]
param
(
[Parameter(Mandatory)]
[String]
$FriendlyName,

[Parameter(Mandatory)]
[String]
$Name,

[Parameter(Mandatory,ParameterSetName="Export")]
[SecureString]
$Password,

[Parameter(Mandatory,ParameterSetName="Export")]
[String]
$FilePath,

[Switch]
$Trusted
)

# create new cert
$cert = New-SelfSignedCertificate -KeyUsage DigitalSignature -KeySpec Signature -FriendlyName $FriendlyName -Subject "CN=$Name" -KeyExportPolicy ExportableEncrypted -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(5) -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3')


if ($Trusted)
{
$Store = New-Object system.security.cryptography.X509Certificates.x509Store("Root", "CurrentUser")
$Store.Open("ReadWrite")
$Store.Add($cert)
$Store.Close()
}


$parameterSet = $PSCmdlet.ParameterSetName.ToLower()

if ($parameterSet -eq "export")
{
# export to file
$cert | Export-PfxCertificate -Password $Password -FilePath $FilePath

$cert | Remove-Item
explorer.exe /select,$FilePath
}
else
{
$cert
}
}

PowerShell 技能连载 - 获取卷 ID(第 2 部分)

在 Windows 10 及以上版本,您可以使用 Get-Volume 获取有关驱动器的卷 ID 和其他信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> Get-Volume

DriveLetter FriendlyName FileSystemType DriveType HealthStatus OperationalStatus SizeRemaining Size
----------- ------------ -------------- --------- ------------ ----------------- ------------- ----
WINRETOOLS NTFS Fixed Healthy OK 315.58 MB 990 MB
C OS NTFS Fixed Healthy OK 154.79 GB 938.04 GB
Image NTFS Fixed Healthy OK 107.91 MB 12.8 GB
DELLSUPPORT NTFS Fixed Healthy OK 354.46 MB 1.28 GB
``

请注意,尽管您最初只会看到一小部分可用信息。但将数据发送到管道可以查看到所有信息,包括卷 ID:

```powershell
Get-Volume | Select-Object -Property *

这是一个示例:

1
2
3
4
5
6
7
8
PS> Get-Volume | Select-Object -Property DriveLetter, FileSystemLabel, Size, Path

DriveLetter FileSystemLabel Size Path
----------- --------------- ---- ----
WINRETOOLS 1038086144 \\?\Volume{733298ae-3d76-4f5f-acc4-50fdca0c6401}\
C OS 1007210721280 \\?\Volume{861c48b0-d434-48d3-995a-0573c1336eb7}\
Image 13739487232 \\?\Volume{9dc0ed9d-86fd-4cd5-9ed8-3249f57720ad}\
DELLSUPPORT 1371533312 \\?\Volume{b0f36c9e-2372-47f9-8b84-cdf65447c9c6}\

PowerShell 技能连载 - 遮罩输入框(第 2 部分)

永远不要将纯文本输入框用于保密信息和密码——用户输入的文本可能被记录和利用。请始终使用遮罩输入框。这是使用参数的一种简单方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
param
(
[Parameter(Mandatory)]
[SecureString]
# asking secret using masked input box
$secret
)

# internally, get back plain text
$data = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret)
$plain =[Runtime.InteropServices.Marshal]::PtrToStringAuto($data)

Write-Host "You secret: $plain" -ForegroundColor Yellow

只需将数据类型 [SecureString] 用于您的参数,这样将将其强制添加一个带遮罩的输入框。

PowerShell 技能连载 - 遮罩输入框(第 1 部分)

永远不要将纯文本输入框用于保密信息和密码——用户输入的文本可能被记录和利用。请始终使用遮罩输入框。这是用户提示的一种简单方法:

1
2
3
4
5
6
7
8
# asking secret using masked input box
$secret = Read-Host "Enter secret" -AsSecureString

# internally, get back plain text
$data = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret)
$plain =[Runtime.InteropServices.Marshal]::PtrToStringAuto($data)

Write-Host "You secret: $plain" -ForegroundColor Yellow

PowerShell 技能连载 - 查找 MSI 产品代码(第 2 部分)

On Windows 10 and better, finding MSI packages and their product codes no longer requires WMI queries. Instead, you can use Get-Package:
在 Windows 10 及以上版本,查找 MSI 软件包及其产品代码不再需要 WMI 查询。相反,您可以使用 Get-Package 来替代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Get-Package | 
Select-Object -Property Name, @{
Name='ProductCode'
Expression={
$code = $_.Metadata["ProductCode"]
if ([string]::IsNullOrWhiteSpace($code) -eq $false)
{
$code
}
else
{
'N/A'
}
}
} |
Format-List

PowerShell 技能连载 - 查找 MSI 产品代码(第 1 部分)

如果您需要已安装的 MSI 软件包及其产品代码列表,则可以使用 WMI 查询信息。以下操作可能需要几秒钟:

1
2
Get-CimInstance -ClassName Win32_Product | 
Select-Object -Property Name, @{Name='ProductCode'; Expression={$_.IdentifyingNumber}}