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 代码签名创建证书并不直观,更不用说在测试机上确保测试证书值得信任。

所以我们编写了一个函数将上述 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 部分)

在 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}}

PowerShell 技能连载 - 存取 Windows 凭据管理器

如果您需要访问 Windows 凭据管理器存储的凭据(已保存的密码),则 “CredentialManager” 模块可能有所帮助。运行此代码下载并安装它:

1
Install-Module -Name CredentialManager -Scope CurrentUser

安装该模块后,您可以列出其提供的新命令:

1
2
3
4
5
6
7
8
PS> Get-Command -Module CredentialManager

CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Get-StoredCredential 2.0 CredentialManager
Cmdlet Get-StrongPassword 2.0 CredentialManager
Cmdlet New-StoredCredential 2.0 CredentialManager
Cmdlet Remove-StoredCredential 2.0 CredentialManager

Get-StoredCredential 获取存储的凭据。并且 New-StoredCredential 可以与凭据管理器一起存储凭据:
获得存储的存储凭据。 而且,新存储者可以为您与凭据管理器一起存储凭据:

1
New-StoredCredential -Target MyCred -Credentials (Get-Credential) -Type Generic -Persist LocalMachine

现在,当脚本需要访问存储的凭据时,请像这样使用 Get-StoredCredential

1
2
3
4
$cred = Get-StoredCredential -Target MyCred
# show clear text information
$cred.UserName
$cred.GetNetworkCredential().Password

Windows 凭据管理器安全地为本地用户存储凭据。只有最初保存凭据的用户才能检索它。

PowerShell 技能连载 - 使用 HTML 来创建 PDF 报告(第 3 部分)

HTML 方便地将数据格式化为输出报告。在这个最后的部分中,我们将介绍如何将最终 HTML 报告转换为 PDF,以便轻松地将其传递给同事和团队成员。

以下是我们在第二部分介绍的脚本:

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
54
55
56
57
58
59
60
$path = "$env:temp\report.html"

# get data from any cmdlet you wish
$data = Get-Service | Sort-Object -Property Status, Name

# helper function to convert arrays to string lists
function Convert-ArrayToStringList
{
param
(
[Parameter(Mandatory, ValueFromPipeline)]
$PipelineObject
)
process
{
$Property = $PipelineObject.psobject.Properties |
Where-Object { $_.Value -is [Array] } |
Select-Object -ExpandProperty Name

foreach ($item in $Property)
{
$PipelineObject.$item = $PipelineObject.$item -join ','


}

return $PipelineObject
}
}


# compose style sheet
$stylesheet = "
<style>
body { background-color:#AAEEEE;
font-family:Monospace;
font-size:10pt; }
table,td, th { border:1px solid blue;}
th { color:#00008B;
background-color:#EEEEAA;
font-size: 12pt;}
table { margin-left:30px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
h1{color:#DC143C;}
h5{color:#DC143C;}
</style>
"

# output to HTML
$data |
# make sure you use Select-Object to copy the objects
Select-Object -Property * |
Convert-ArrayToStringList |
ConvertTo-Html -Title Report -Head $stylesheet |
Set-Content -Path $path -Encoding UTF8

Invoke-Item -Path $path

结果是位于 $Path 中指定的文件位置中的 HTML 报告。现在,缺少的是将 HTML 文件转换为 PDF 文件的方法。

有很多方法可以实现这一目标,但最方便的方法是使用 Chrome 浏览器。但是,要进行转换,首先需要安装 Chrome 浏览器(如果尚未安装)。

这是获取现有 HTML 文件并将其转换为 PDF 的最终代码:

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
# path to existing HTML file
$path = "$env:temp\report.html"

# determine installation path for Chrome
$Chrome = ${env:ProgramFiles(x86)}, $env:ProgramFiles |
ForEach-Object { "$_\Google\Chrome\Application\chrome.exe" } |
Where-Object { Test-Path -Path $_ -PathType Leaf } |
Select-Object -First 1

if ($Chrome.Count -ne 1) { throw "Chrome not installed." }

# compose destination path by changing file extension to PDF
$destinationPath = [System.IO.Path]::ChangeExtension($Path, '.pdf')

# doing the conversion
& $Chrome --headless --print-to-pdf="$destinationPath" "$Path"

# (this is async so it may take a moment for the PDF to be created)
do
{
Write-Host '.' -NoNewline
Start-Sleep -Seconds 1
} Until (Test-Path -Path $destinationPath)

Invoke-Item -Path $destinationPath