PowerShell 技能连载 - 研究 PowerShell 控制台输出

当您在 PowerShell 控制台中看到命令的结果时,通常仅显示部分信息。要查看完整的信息,您需要将其发送到 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
45
46
47
48
49
50
PS> Get-CimInstance -ClassName Win32_BIOS


SMBIOSBIOSVersion : 1.7.1
Manufacturer : Dell Inc.
Name : 1.7.1
SerialNumber : 4ZKM0Z2
Version : DELL - 20170001




PS> Get-CimInstance -ClassName Win32_BIOS | Select-Object -Property *


Status : OK
Name : 1.7.1
Caption : 1.7.1
SMBIOSPresent : True
Description : 1.7.1
InstallDate :
BuildNumber :
CodeSet :
IdentificationCode :
LanguageEdition :
Manufacturer : Dell Inc.
OtherTargetOS :
SerialNumber : 4ZKM0Z2
SoftwareElementID : 1.7.1
SoftwareElementState : 3
TargetOperatingSystem : 0
Version : DELL - 20170001
PrimaryBIOS : True
BiosCharacteristics : {7, 9, 11, 12...}
BIOSVersion : {DELL - 20170001, 1.7.1, Dell - 10000}
CurrentLanguage : enUS
EmbeddedControllerMajorVersion : 255
EmbeddedControllerMinorVersion : 255
InstallableLanguages : 1
ListOfLanguages : {enUS}
ReleaseDate : 28.12.2020 01:00:00
SMBIOSBIOSVersion : 1.7.1
SMBIOSMajorVersion : 3
SMBIOSMinorVersion : 1
SystemBiosMajorVersion : 1
SystemBiosMinorVersion : 7
PSComputerName :
CimClass : root/cimv2:Win32_BIOS
CimInstanceProperties : {Caption, Description, InstallDate, Name...}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties

这是因为 PowerShell 包含的逻辑会自动仅选择对象的最重要属性,以使您专注于重要的事情。为了更好地理解它是如何工作的,这里有一些示例代码来探索 Get-Process 返回的数据。要研究其他 cmdlet,请将代码中的 “Get-Process“ 替换为另一个 cmdlet 的名称:

1
Install-Module -Name PSCommandDiscovery -Scope CurrentUser -Verbose

该代码确定 cmdlet 发出的数据类型,然后找到定义该数据类型视图的 *.format.ps1xml 文件,并返回该定义的前 20 行:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
        </ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Handles</Label>
<Width>7</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>NPM(K)</Label>
<Width>7</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PM(K)</Label>
<Width>8</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>WS(K)</Label>
</ViewSelectedBy>
<GroupBy>
<PropertyName>PriorityClass</PropertyName>
<Label>PriorityClass</Label>
</GroupBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>20</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>10</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Width>13</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Width>12</Width>
</ViewSelectedBy>
<GroupBy>
<ScriptBlock>$_.StartTime.ToShortDateString()</ScriptBlock>
<Label>StartTime.ToShortDateString()</Label>
</GroupBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>20</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>10</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Width>13</Width>
<Alignment>right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Width>12</Width>
</ViewSelectedBy>
<WideControl>
<WideEntries>
<WideEntry>
<WideItem>
<PropertyName>ProcessName</PropertyName>
</WideItem>
</WideEntry>
</WideEntries>
</WideControl>
</View>
<View>
<Name>DateTime</Name>
<ViewSelectedBy>
<TypeName>System.DateTime</TypeName>
</ViewSelectedBy>
<CustomControl>
<CustomEntries>
<CustomEntry>
<CustomItem> <ExpressionBinding> <PropertyName>DateTime</PropertyName> </ExpressionBinding> </CustomItem>

尽管示例代码为了简单起见不会给出完整的定义,但它使您可以更好地了解幕后发生的事情:每当 cmdlet 返回类型为 System.Diagnostics.Process 的对象时,PowerShell 默认都会根据到公开的 XML 定义。

在上面的示例中更改 cmdlet 名称时,您也可以看到其他类型的定义。但是,为简单起见,示例代码仅搜索在 PowerShell 主目录中找到的 *.format.ps1xml 文件,而不在可能存在其他格式定义的所有模块位置中搜索。

注意:本技能仅适用于 Windows PowerShell。

PowerShell 技能连载 - 识别网络访问的来源

Get-NetTCPConnection 返回所有当前的 TCP 网络连接,但是此 cmdlet 不会确切告诉您谁在连接到您的计算机。您仅得到 IP 地址:

1
2
3
4
5
6
7
PS> Get-NetTCPConnection -RemotePort 443

LocalAddress LocalPort RemoteAddress RemotePort State AppliedSetting OwningProcess
------------ --------- ------------- ---------- ----- -------------- ---------
192.168.2.110 60960 13.107.6.171 443 Established Internet 21824
192.168.2.110 60959 20.44.232.74 443 Established Internet 4540
192.168.2.110 60956 52.184.216.226 443 Established Internet 13204

使用计算属性,您可以重新计算返回的值,例如,将 IP 地址发送到公开真实来源的 Web 服务。使用相同的技术,您还可以转换在 OwningProcess 中找到的进程 ID,并返回维护连接的进程名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$process = @{
Name = 'ProcessName'
Expression = { (Get-Process -Id $_.OwningProcess).Name }
}

$darkAgent = @{
Name = 'ExternalIdentity'
Expression = {
$ip = $_.RemoteAddress
(Invoke-RestMethod -Uri "http://ipinfo.io/$ip/json" -UseBasicParsing -ErrorAction Ignore).org

}
}
Get-NetTCPConnection -RemotePort 443 -State Established |
Select-Object -Property RemoteAddress, OwningProcess, $process, $darkAgent

结果提供了对连接的更多了解,并且该示例显示了所有 HTTPS 连接及其外部目标:

RemoteAddress  OwningProcess ProcessName ExternalIdentity
-------------  ------------- ----------- ----------------
13.107.6.171           21824 WINWORD     AS8068 Microsoft Corporation
52.113.194.132         15480 Teams       AS8068 Microsoft Corporation
52.114.32.24           25476 FileCoAuth  AS8075 Microsoft Corporation
142.250.185...         15744 chrome      AS15169 Google LLC
52.114.32.24            3800 OneDrive    AS8075 Microsoft Corporation
52.114.32.24            3800 OneDrive    AS8075 Microsoft Corporation
45.60.13.212            9808 AgentShell  AS19551 Incapsula Inc
18.200.231.29          15744 chrome      AS16509 Amazon.com, Inc.

PowerShell 技能连载 - 探索文件夹结构(第 2 部分)

仅使用几个 cmdlet,您就可以检查文件夹结构,即返回文件夹树中子文件夹的大小。

这是一个返回文件夹总大小和相对大小的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# specify the folder that you want to discover
# $home is the root folder of your user profile
# you can use any folder: $path = 'c:\somefolder'
$path = $HOME

# specify the depth you want to examine (the number of levels you'd like
# to dive into the folder tree)
$Depth = 3

# find all subfolders...
Get-ChildItem $path -Directory -Recurse -ErrorAction Ignore -Depth $Depth |
ForEach-Object {
Write-Progress -Activity 'Calculating Folder Size' -Status $_.FullName

# return the desired information as a new custom object
[pscustomobject]@{
RelativeSize = Get-ChildItem -Path $_.FullName -File -ErrorAction Ignore | & { begin { $c = 0 } process { $c += $_.Length } end { $c }}
TotalSize = Get-ChildItem -Path $_.FullName -File -Recurse -ErrorAction Ignore | & { begin { $c = 0 } process { $c += $_.Length } end { $c }}
FullName = $_.Fullname.Substring($path.Length+1)
}
}

注意代码是如何总结所有文件的大小的:它调用带有开始、处理和结束块的脚本块。 begin 块在管道启动之前执行,并将计数变量设置为零。对于每个传入的管道对象,将重复执行该 “process“ 块,并将 “Length“ 属性中的文件大小添加到计数器。当管道完成时,将执行 “end“ 块并返回计算出的总和。

这种方法比使用 ForEach-Object 快得多,但仍比使用 Measure-Object 快一点。您可以通过这种方式计算各种事物。此代码计算 Get-Service 发出的对象数量:

1
2
# counting number of objects
Get-Service | & { begin { $c = 0 } process { $c++ } end { $c }}

请注意,这是一种“流式”方法,无需将所有对象存储在内存中。对于少量对象,您也可以使用“下载”方法并将所有元素存储在内存中,然后使用 Count 属性:

1
(Get-Service).Count

PowerShell 技能连载 - 探索文件夹结构(第 1 部分)

这是一个快速示例,说明了如何发现文件夹结构。本示例采用任何根文件夹路径,并递归遍历其子文件夹。

对于每个子文件夹,将返回一个新的自定义对象,其中包含文件和子文件夹计数以及相对的子文件夹路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# specify the folder that you want to discover
# $home is the root folder of your user profile
# you can use any folder: $path = 'c:\somefolder'
$path = $HOME

# find all subfolders...
Get-ChildItem $path -Directory -Recurse -ErrorAction Ignore |
ForEach-Object {
# return custom object with relative subfolder path and
# file count
[pscustomobject]@{
# use GetFiles() to find all files in folder:
FileCount = $_.GetFiles().Count
FolderCount = $_.GetDirectories().Count
FullName = $_.Fullname.Substring($path.Length+1)
}
}

PowerShell 技能连载 - 保存文本文件时去掉 BOM

在 Windows上,默认情况下,许多 cmdlet 使用BOM (Byte Order Mask) 编码对文本文件进行编码。 BOM 会在文本文件的开头写入一些额外的字节,以标记用于写入文件的编码。

不幸的是,BOM 编码在 Windows 世界之外并未得到很好的采用。如今,当您在 Windows 系统上保存文本文件并将其上传到 GitHub 时,BOM 编码可能会损坏文件或使其完全不可读。

以下是一段可用于确保以与 Linux 兼容的方式,在不使用 BOM 的情况下保存文本文件:

1
2
3
4
5
$outpath = "$env:temp\nobom.txt"
$text = 'This is the text to write to disk.'
$Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllLines($outpath, $text, $Utf8NoBomEncoding)
$outpath

PowerShell 技能连载 - 信任自签名的 HTTPS 证书

如果您需要访问使用自签名测试证书或已过期或不可信的证书的 HTTPS 网站,PowerShell 将拒绝连接。在大多数情况下,这是正确的选择,但有时您知道目标服务器是安全的。

这是一段通过重写证书策略来信任所有 HTTPS 证书的 PowerShell 代码。新的证书策略始终返回 $true 并完全信任任何证书:

1
2
3
4
5
6
7
8
9
class TrustAll : System.Net.ICertificatePolicy
{
[bool]CheckValidationResult([System.Net.ServicePoint]$sp, [System.Security.Cryptography.X509Certificates.X509Certificate]$cert, [System.Net.WebRequest]$request, [int]$problem)
{
return $true
}
}

[System.Net.ServicePointManager]::CertificatePolicy = [TrustAll]::new()

PowerShell 技能连载 - 转换 Word 文档

现在仍然有许多旧文件格式(.doc 而不是 .docx)的 Microsoft Office 文档。

这是一个简单的 PowerShell 函数,它将旧的 .doc Word 文档转换为 .docx 格式并保存。如果未锁定旧的 Word 文档,则此过程是完全不可见的,并且可以在无人值守的情况下运行:

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
function Convert-Doc2Docx
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]
[Alias('FullName')]
$Path,

[string]
$DestinationFolder

)
begin
{
$word = New-Object -ComObject Word.Application
}

process
{


$pathOut = [System.IO.Path]::ChangeExtension($Path, '.docx')
if ($PSBoundParameters.ContainsKey('DestinationFolder'))
{
$exists = Test-Path -Path $DestinationFolder -PathType Container
if (!$exists)
{
throw "Folder not found: $DestinationFolder"
}
$name = Split-Path -Path $pathOut -Leaf
$pathOut = Join-Path -Path $DestinationFolder -ChildPath $name
}
$doc = $word.Documents.Open($Path)
$name = Split-Path -Path $Path -Leaf
Write-Progress -Activity 'Converting' -Status $name
$doc.Convert()
$doc.SaveAs([ref]([string]$PathOut),[ref]16)
$word.ActiveDocument.Close()

}
end
{
$word.Quit()
}
}

PowerShell 技能连载 - 使用编码标准

使用文本文件时,务必始终使用相同的文本编码进行读取和写入,否则特殊字符可能会损坏,或者文本文件可能变得不可读,这一点很重要。

在 PowerShell 7 中,除非您指定其他编码,否则所有 cmdlet 以及重定向操作符都将使用默认的 UTF8 文本编码。那挺好的。

在W indows PowerShell 中,不同的 cmdlet 使用不同的默认编码。所以,要为所有 cmdlet 和重定向操作符建立通用的默认默认编码,您应该为带有参数 -Encoding 的任何命令设置默认参数值。

将以下行放入您的配置文件脚本中:

1
$PSDefaultParameterValues.Add('*:Encoding', 'UTF8')

此外,在 Windows PowerShell 中,无论 cmdlet 是否具有参数 -Encoding,都指定一个唯一的值。当前推荐的编码为UTF8。

PowerShell 技能连载 - 修复 VSCode PowerShell 问题(第 2 部分)

如果在编辑 PowerShell 脚本时 VSCode 不会启动 PowerShell 引擎,而状态栏中的黄色消息“正在启动 PowerShell”不会消失,则可能的解决方法是使用全新独立的可移植 PowerShell 7 安装作为 VSCode 中的默认 PowerShell 引擎。

首先,运行以下这行代码以创建 Install-PowerShell cmdlet :

1
Invoke-RestMethod -Uri https://aka.ms/install-powershell.ps1 | New-Item -Path function: -Name Install-PowerShell | Out-Null

接下来,在本地文件夹中安装 PowerShell 7 的新副本,例如:

1
Install-PowerShell -Destination c:\portablePowerShell

安装完成后,请确保可以手动启动新的 PowerShell 实例:

1
c:\portablePowerShell\pwsh

现在,继续告诉 VSCode,您要在编辑 PowerShell 脚本时使用此新的 PowerShell 实例运行:在 VSCode 中,选择“文件/首选项/设置”,单击设置左栏中的“扩展”,然后单击子菜单中的“PowerShell 配置”。

接下来,搜索设置“PowerShell 默认版本”,然后输入任何名称,即“portable PowerShell 7”。在上面的“PowerShell Additional Exe Paths”部分中,单击链接“Edit in settings.json”。这将以 JSON 格式打开原始设置文件。

其中,新设置部分已经插入,需要像这样完成:

1
2
3
4
5
6
"powershell.powerShellAdditionalExePaths": [
        {
            "exePath": "c:\\portablePowerShell\\pwsh.exe",
            "versionName": "portable PowerShell 7"
        }
    ]

注意:所有标记和关键字均区分大小写,并且在路径中,反斜杠需要由另一个反斜杠转义。在 “exePath” 中,在下载的可移植 PowerShell 文件夹中指定 pwsh.exe 文件的路径。确保路径不仅指向文件夹,而且指向 pwsh.exe

在 “versionName” 中,使用之前在 “PowerShell Default Version” 中指定的相同标签名称。

保存 JSON 文件后,重新启动 VSCode。该编辑器现在使用您的 portable PowerShell 7,并且在许多情况下,这解决了 PowerShell 启动卡住的问题。

如果您想手动将其他 PowerShell 版本添加到 VSCode,则上面的方法也很有用。当您单击 VSCode 状态栏中的绿色 PowerShell 版本时,任何手动添加的 PowerShell 将出现在选择对话框中(除非已被使用,在这种情况下,该菜单将显示 PowerShell 类型和版本,而不是您的标签名称)。

PowerShell 技能连载 - 修复 VSCode PowerShell 问题(第 1 部分)

有时,VSCode 在尝试启动 PowerShell 引擎时停止,或者报告诸如 “Language Server Startup failed” 之类的错误。

如果您遇到后一种异常,则可能与企业中的安全设置有关。要解决此问题,请在 PowerShell 控制台中运行以下行(这是一长行代码):

1
Import-Module $HOME\.vscode\extensions\ms-vscode.powershell*\modules\PowerShellEditorServices\PowerShellEditorServices.psd1

如果您没有得到提示,那么这不是造成问题的原因。如果确实收到提示要求确认导入此模块的提示,则只需允许运行“来自不受信任的发布者的软件”。该确认仅需要一次,因此下次 VSCode 尝试使用此模块启动 PowerShell 引擎时,很可能会解决您的问题。