PowerShell 技能连载 - 在 PowerShell 脚本中嵌入二进制文件(图片、DLL)

如果脚本需要外部二进制资源,例如图片或者 DLL 文件,您当然可以将它们和脚本一起分发。不过,您也可以将这些二进制文件作为文本嵌入您的脚本文件:

  • 以字节方式读取二进制文件
  • 将字节保存为 Base64 编码的字符串

通过这种方式,您的脚本可以从一个文本变量中读取 Base64 编码的二进制,然后将数据转换回字节,并且将它们写入临时文件。

以下是两个演示该概念的函数:

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
function Convert-BinaryToText
{
param
(
[Parameter(Mandatory)]
[string]
$Path
)

$Bytes = [System.IO.File]::ReadAllBytes($Path)
[System.Convert]::ToBase64String($Bytes)
}

function Convert-TextToBinary
{
param
(
[Parameter(Mandatory)]
[string]
$Text,

[Parameter(Mandatory)]
[string]
$OutputPath
)

$Bytes = [System.Convert]::FromBase64String($Text)
[System.IO.File]::WriteAllBytes($OutputPath, $Bytes)
}

Convert-BinaryToText 接受一个任意文件的路径,并返回 Base64 编码的字符串。Convert-BinaryToText 做的是相反的操作:传入 Base64 编码的字符串以及一个目标路径,该函数将自动重建二进制文件。

请注意将二进制文件保存成 Base64 字符串并不能节省空间。您的脚本的大小可能会超过原来的二进制文件大小。但是,即使 Base64 编码的字符串很大,PowerShell也能很好地处理它们,而且从 Base64 编码的字符串中提取二进制文件非常快。

PowerShell 技能连载 - 使用 Windows 10 内置的 SSH 支持

在 2018 年 10 月份,一个 Windows 10 更新加入了内置的 SSH 支持。从此之后,Windows 10 附带了一个名为 “ssh” 的命令行工具。您可以在 PowerShell 中使用它来连接到其它设备(包括 IoT、树莓派等设备)而不需要第三方工具:

1
2
3
4
5
6
7
8
9
PS> ssh
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]
[-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
[-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
[-i identity_file] [-J [user@]host[:port]] [-L address]
[-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
[-Q query_option] [-R address] [-S ctl_path] [-W host:port]
[-w local_tun[:remote_tun]] destination [command]
PS>

PowerShell 技能连载 - 使用最新版的 PowerShell Core

在前一个技能中我们演示了如何下载一个 PowerShell 脚本,用来自动下载最新版的 PowerShell Core。

这个脚本支持一系列参数。默认情况下,它获取最新(稳定版)的生产版本。如果您希望使用包括预览版的最新版,请使用 -Preview 参数。并且,如果您希望使用 MSI 包安装它,请加上 -MSI 参数。

您不一定要将该脚本保存到文件。您可以直接执行下载脚本并通过 Invoke-Expression 执行它。不过,这个 cmdlet 被认为是有风险的,因为它直接执行您提交的任何代码,而没有机会让您事先检查代码。

以下是一行示例代码,用来下载最新版的 PowerShell Core MSI 安装包:

1
Invoke-Expression -Command "& { $(Invoke-RestMethod https://aka.ms/install-powershell.ps1) } -UseMSI -Preview"

PowerShell 技能连载 - 安装 PowerShell Core

您也许知道,Windows PowerShell(随 Windows 分发的)已成为过去,所有的工作都投入到新的跨平台 PowerShell Core 的开发中。新版的 PowerShell 还没有在 Windows 中提供开箱即用的功能,所以如果要使用它,您需要手动下载。

幸运的是,有一个脚本可以为您完成繁重的工作。这是用来下载该脚本的代码:

1
Invoke-RestMethod https://aka.ms/install-powershell.ps1

如果您希望将该脚本保存到一个文件,请使用以下代码:

1
2
3
4
$Path = "$home\desktop\installps.ps1"

Invoke-RestMethod https://aka.ms/install-powershell.ps1 | Set-Content -Path $Path -Encoding UTF8
notepad $Path

这将下载该脚本并在一个记事本中打开该脚本。该脚本文件存放在桌面上,这样您可以用鼠标右键单击它并使用 PowerShell 执行它来下载并安装最新生产版的 PowerShell Core。

PowerShell 技能连载 - 查找公网 IP 地址

这是一个单行程序,检索您当前的公共IP地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Invoke-RestMethod -Uri http://ipinfo.io


ip : 87.153.224.209
hostname : p5799e0d1.dip0.t-ipconnect.de
city : Hannover
region : Lower Saxony
country : DE
loc : 52.3705,9.7332
org : AS3320 Deutsche Telekom AG
postal : 30159
timezone : Europe/Berlin
readme : https://ipinfo.io/missingauth

PowerShell 技能连载 - 实时日志处理

PowerShell 提供了一种强大而简单的方法来监视文件更改。假设您有一个经常更改的日志文件。以下是一个PowerShell脚本,用于监视日志文件的更改。每当发生更改时,都会执行一些代码:

1
2
3
4
5
6
7
# make sure this points to a log file
$Path = '\\myserver\report2.txt'

Get-Content -Path $Path -Tail 0 -Wait |
ForEach-Object {
"Detected $_"
}

只要确保修改 $path 指向某个实际的日志文件。每当向文件附加文本(并且保存更改),ForEach-Object 循环都会执行脚本块并输出 “Detected “。通过这种方式,您可以方便地响应实际的改变。

Get-Content 完成繁重的工作:-Wait 启用内容监视,-Tail 0 确保忽略现有内容,只查找新添加的文本。

PowerShell 技能连载 - 检测键盘按键

通常,只有在真正的控制台窗口中才支持按键检测,因此这种方法不适用于 PowerShell ISE 和其他 PowerShell 宿主。

但是,PowerShell 可以从 Windows Presentation Foundation 中借用一种类型,这种类型可以检查任何键的状态。这样,实现在任何 PowerShell 脚本中都可以工作的“退出”键就变得很简单了,无论是在控制台、Visual Studio Code 还是 PowerShell ISE 中运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore

# choose the abort key
$key = [System.Windows.Input.Key]::LeftCtrl

Write-Warning "PRESS $key TO ABORT!"

do
{
$isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
if ($isCtrl)
{
Write-Host
Write-Host "You pressed $key, so I am exiting!" -ForegroundColor Green
break
}
Write-Host "." -NoNewline
Start-Sleep -Milliseconds 100
} while ($true)

只需要在变量 $key 中选择“退出”按键即可。本例使用的是左 CTRL 键。

PowerShell 技能连载 - 检测存储问题

在 Windows 10 和 Windows Server 2016 中,PowerShell 可以访问存储可靠性数据,这样您就可以发现其中一个附加的存储驱动器是否有问题。这需要管理员特权来执行:

1
2
3
4
5
6
PS> Get-PhysicalDisk | Get-StorageReliabilityCounter

DeviceId Temperature ReadErrorsUncorrected Wear PowerOnHours
-------- ----------- --------------------- ---- ------------
0 0
1 0

要查看所有可用信息,请使用 Select-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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
PS> Get-PhysicalDisk | Get-StorageReliabilityCounter | Select-Object -Property *


(...)
DeviceId : 0
FlushLatencyMax : 104
LoadUnloadCycleCount :
LoadUnloadCycleCountMax :
ManufactureDate :
PowerOnHours :
ReadErrorsCorrected :
ReadErrorsTotal :
ReadErrorsUncorrected :
ReadLatencyMax : 1078
StartStopCycleCount :
StartStopCycleCountMax :
Temperature : 1
TemperatureMax : 1
Wear : 0
WriteErrorsCorrected :
WriteErrorsTotal :
WriteErrorsUncorrected :
WriteLatencyMax : 1128
(...)
FlushLatencyMax :
LoadUnloadCycleCount :
LoadUnloadCycleCountMax :
ManufactureDate :
PowerOnHours :
ReadErrorsCorrected :
ReadErrorsTotal :
ReadErrorsUncorrected :
ReadLatencyMax : 46
StartStopCycleCount :
StartStopCycleCountMax :
Temperature : 0
TemperatureMax : 0
Wear : 0
WriteErrorsCorrected :
WriteErrorsTotal :
WriteErrorsUncorrected :
WriteLatencyMax :
PSComputerName :
(...)

详情和返回数据的数量取决于您的存储制造商和您的驱动器。

PowerShell 技能连载 - 重设 Winsock

PowerShell 既可以运行内置的 PowerShell 命令,也可以运行常规的控制台命令,所以继续用控制台命令处理已知的任务也不是件坏事。

例如,如果您希望重设 winsock,以下是一个可信赖的解决方案:

1
2
3
4
#requires -RunAsAdministrator

netsh winsock reset
netsh int ip reset

请注意这段代码需要管理员特权,并且可能需要重启才能生效。

PowerShell 技能连载 - 使用超棒的 Export-Excel Cmdlet(第 5 部分)

这是我们关于 Doug Finke 的强大而免费的 “ImportExcel” PowerShell 模块的迷你系列文章的第 5 部分。在学习这个技能之前,请确保安装了该模块:

1
PS> Install-Module -Name ImportExcel -Scope CurrentUser -Force

在第 4 部分中,我们研究了由于在输入数据中包含数组而导致的误读数据。正如您所看到的,您只需要使用 -join 操作符将数组转换为字符串,Excel 就可以正确地显示数组,即逗号分隔值的列表。

但是,如果希望在单独的行中显示数组元素,并使用换行符呢?

默认情况下,Excel 只会在选定单元格的输入框中显示单独的行,而不是在所有单元格中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'
$rawData |
ForEach-Object {
# convert column "ReplacementStrings" from array to string
$_.ReplacementStrings = $_.ReplacementStrings -join "`r`n"
# return the changed object
$_
} |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -Show

当您运行这段代码时,”ReplacementStrings” 中的数组将会正确地转换为多行文本,但是您不会在工作表中看到它。只有当您单击某个单元格时才会看到输入区域中显示多行文本。

当您把我们前面部分的信息组合起来时,可以很容易地对 Excel 文件进行后期处理,并像这样将单元格格式化为“文本”和“自动换行”:

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
# get some raw data that contains arrays
$rawData = Get-EventLog -LogName System -Newest 10 |
Select-Object -Property TimeWritten, ReplacementStrings, InstanceId


# create this Excel file
$Path = "$env:temp\report.xlsx"
# make sure the file is deleted so we have no
# effects from previous data still present in the
# file. This requires that the file is not still
# open and locked in Excel
$exists = Test-Path -Path $Path
if ($exists) { Remove-Item -Path $Path}

$sheetName = 'Testdata'

# save the Excel object model by using -PassThru instead of -Show
$excel = $rawData |
ForEach-Object {
# convert column "ReplacementStrings" from array to string
$_.ReplacementStrings = $_.ReplacementStrings -join "`r`n"
# return the changed object
$_
} |
Export-Excel -Path $path -ClearSheet -WorksheetName $sheetName -AutoSize -PassThru

#region Post-process the column with the misinterpreted formulas
# remove the region to repro the original Excel error
$sheet1 = $excel.Workbook.Worksheets[$sheetName]
# reformat cell to number type "TEXT" with WordWrap and AutoSize
Set-Format -Address $sheet1.Cells['B:B'] -NumberFormat 'Text' -WrapText -AutoSize
#endregion

Close-ExcelPackage -ExcelPackage $excel -Show