PowerShell 技能连载 - 使用 NTFS 流(第 5 部分)

在前面的技能中,我们研究了 NTFS 流,并发现 Windows 如何使用“区域信息”流标记下载的文件。您还学习了使用 Unblock-File 从文件中删除此限制。

在最后一部分中,我们做了相反的事情,查找从不受信任的来源下载的文件。例如,此行列出了所有附加了 Zone.Identifier 流的文件:

1
2
3
4
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' }

所有这些文件都来自 Windows 认为不一定可信的来源。

要了解更多信息,您必须阅读附件中的信息流。这段代码揭示了“下载”文件夹中所有“区域信息”流的全部内容:

1
2
3
4
5
6
7
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' } |
ForEach-Object {
Get-Content -Path $_.FullName -Stream Zone.Identifier
}

显然,该信息包含有关引用和来源的信息,因此您可以还原此信息并找出在“下载”文件夹中找到的所有内容的来源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$path = "$env:userprofile\Downloads"

Get-ChildItem -Path $Path -file |
Where-Object { @(Get-Item -Path $_.FullName -Stream *).Stream -contains 'Zone.Identifier' } |
ForEach-Object {
$info = Get-Content -Path $_.FullName -Stream Zone.Identifier
[PSCustomObject]@{
Name = $_.Name
Referrer = @(($info -like 'ReferrerUrl=*').Split('='))[-1]
HostUrl = @(($info -like 'HostUrl=*').Split('='))[-1]
Path = $_.FullName
}
} |
Out-GridView

这使您可以很好地了解下载的原始来源。

PowerShell 技能连载 - 使用 NTFS 流(第 4 部分)

每当您从 Internet(或其他不受信任的来源)下载文件并将其存储在 NTFS 驱动器上时,Windows 就会使用区域标识符对这些文件进行静默标记。例如,这就是为什么 PowerShell 拒绝执行从域外部下载的脚本的原因。

您实际上可以查看区域标识符。只要确保您从 Internet 下载文件并将其存储在 NTFS 驱动器上即可。接下来,使用此行查看区域标识符:

1
Get-Content -Path C:\users\tobia\Downloads\Flyer2021.rar -Stream Zone.Identifier

如果存在该流,则您会看到类似以下信息:

[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://shop.laserkino.de/
HostUrl=https://www.somecompany/Flyer2021.rar

它暴露了文件的来源以及从中检索文件的远程区域的类型。如果没有附加到文件的区域信息,则上述命令将引发异常。

若要从文件中删除区域信息(并删除所有限制),请使用 Unblock-File cmdlet。例如,要取消阻止“下载”文件夹中的所有文件,请尝试以下操作:

1
Get-ChildItem -Path C:\users\tobia\Downloads\ -File | Unblock-File -WhatIf

删除 -WhatIf 参数以实际删除该保护流。

PowerShell 技能连载 - 使用 NTFS 流(第 3 部分)

在上一个技能中,我们解释了 NTFS 流是如何工作的。但是,不可能发现隐藏文件流的名称。在 PowerShell 5及更高版本中,大多数访问文件系统的 cmdlet 都新增了一个名为 -Stream 的新参数。有了它,现在访问 NTFS 流变得很简单,因此现在可以重写以前脚本中使用路径名称中的冒号表示的示例,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# create a sample file
$desktop = [Environment]::GetFolderPath('Desktop')
$path = Join-Path -Path $desktop -ChildPath 'testfile.txt'
'Test' | Out-File -FilePath $Path

# attach hidden info to the file
'this is hidden' | Set-Content -Path $path -Stream myHiddenStream

# get hidden info from the file
Get-Content -Path $path -Stream myHiddenStream

# remove hidden streams
Remove-Item -Path $Path -Stream myHiddenStream

# show file
explorer /select,$Path

现在,还可以查看(并发现)隐藏的 NTFS 流。让我们创建一个带有一堆流的示例文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# create a sample file
$desktop = [Environment]::GetFolderPath('Desktop')
$path = Join-Path -Path $desktop -ChildPath 'testfile.txt'
'Test' | Out-File -FilePath $Path

# attach hidden info to the file
'this is hidden' | Set-Content -Path $path -Stream myHiddenStream
'more info' | Set-Content -Path $path -Stream additionalInfo
'anotherone' | Set-Content -Path $path -Stream 'blanks work, too'
'last' | Set-Content -Path $path -Stream finalStream

# find stream names:
Get-Item -Path $Path -Stream * | Select-Object -Property Stream, Length

现在,Get-Item 可以暴露 NTFS 流,并且输出可能如下所示:

Stream           Length
------           ------
:$DATA               14
additionalInfo       11
blanks work, too     12
finalStream           6
myHiddenStream       16

如您所见,您现在可以发现所有流的名称。流 “:$DATA” 表示文件的“可见的”主要内容。

PowerShell 技能连载 - 使用 NTFS 流(第 2 部分)

在上一个技巧中,我们解释了 NTFS 流如何存储有关文件的其他数据,这引发了一个问题,即如何删除此类流或首先发现隐藏的 NTFS 流。

要删除隐藏的命名流,请使用 Remove-Item——就像您要删除整个文件一样。这是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# create a sample file
$path = "$env:temp\test.txt"
'Test' | Out-File -FilePath $Path

# attach hidden info to the file
'this is hidden' | Set-Content -Path "${path}:myHiddenStream"

# get hidden info from the file
Get-Content -Path "${path}:myHiddenStream"

# remove hidden streams
Remove-Item -Path "${path}:myHiddenStream"

# stream is gone, this raises an error:
Get-Content -Path "${path}:myHiddenStream"

# file with main stream is still there:
explorer /select,$Path

尽管您可以像创建单个文件一样创建和删除 NTFS 流,只需添加一个冒号和流名称即可,但没有找到流名称的简单方法。至少不是我们在这里访问流的方式。在第 3 部分中,我们最终将发现隐藏的流名称。

PowerShell 技能连载 - 使用 NTFS 流(第 1 部分)

在 NTFS 文件系统上,您可以将其他信息存储在隐藏的文件流中。传统上,PowerShell 通过冒号访问文件流,因此这会将隐藏的文本信息附加到纯文本文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# create a sample file
$desktop = [Environment]::GetFolderPath('Desktop')
$path = Join-Path -Path $desktop -ChildPath 'testfile.txt'
'Test' | Out-File -FilePath $Path

# attach hidden info to the file
'this is hidden' | Set-Content -Path "${path}:myHiddenStream"

# attach even more hidden info to the file
'this is also hidden' | Set-Content -Path "${path}:myOtherHiddenStream"

# show file
explorer /select,$Path

该代码首先确定到您的桌面的路径,然后创建一个示例纯文本文件。

接下来,它在名为 “myHiddenStream” 和 “myOtherHiddenStream” 的两个流中添加隐藏信息。当您在资源管理器中查看文件时,这些流仍然不可见。

PowerShell 仍然可以像下面这样访问这些流:

1
2
3
# get hidden info from the file
Get-Content -Path "${path}:myHiddenStream"
Get-Content -Path "${path}:myOtherHiddenStream"

请注意,这些流仅存在于使用 NTFS 文件系统的存储中。当您将这些文件复制到其他文件系统时,即使用 exFat 将它们复制到USB记忆棒中,Windows 将显示一个警告对话框,提示所有流将被删除。

PowerShell 技能连载 - 观看德国电视节目

德国公共广播公司保留着丰富的电视档案,并允许用户通过 Web 界面观看其节目。通常无法下载节目或轻松找到其下载 URL。

以下脚本下载了所有节目及其网络位置的非官方目录:

1
2
3
4
# download the German mediathek database as JSON file
$path = "$env:temp\tv.json"
$url = 'http://www.mediathekdirekt.de/good.json'
Invoke-RestMethod -Uri $url -UseBasicParsing -OutFile $path

下载该文件后,您可以使用其他脚本来显示选择对话框,并浏览要观看的电视节目。然后,您可以选择一个或多个节目,并要求 PowerShell 将这些视频自动下载到您的计算机上。

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
$Path = "$env:temp\tv.json"

$data = Get-Content -Path $Path -Raw |
ConvertFrom-Json |
ForEach-Object { $_ } |
ForEach-Object {
# define a string describing the video. This string will be shown in a grid view window
$title = '{0,5} [{2}] "{1}" ({3})' -f ([Object[]]$_)
# add the original data to the string so when the user select a video,
# the details i.e. download URL is still available
$title | Add-Member -MemberType NoteProperty -Name Data -Value $_ -PassThru
}

$data |
Sort-Object |
Out-GridView -Title 'Select Video(s)' -OutputMode Multiple |
ForEach-Object {
# take the download URL from the attached original data
$url = $_.Data[5]
$filename = Split-Path -Path $url -Leaf
$filepath = Join-Path -Path $env:temp -ChildPath $filename
$title = 'Video download {0} ({1})' -f $_.Data[1], $_.Data[0]
Start-BitsTransfer -Description $title -Source $url -Destination $filepath
# you can use a simple web request as well in case BITS isn't available
# Invoke-WebRequest -Uri $url -OutFile $filepath -UseBasicParsing

# open video in associated player
Invoke-Item -Path $filepath
}

PowerShell 技能连载 - 查找上次登录的用户

要查找有关 Windows 上最后登录的用户的详细信息,可以查询注册表:

1
2
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" |
Select-Object -Property LastLo*, Idle*

结果看起来像这样:

LastLoggedOnDisplayName : Tobias Weltner
LastLoggedOnProvider    : {D6886603-9D2F-4EB2-B667-1971041FA96B}
LastLoggedOnSAMUser     : .\tobia
LastLoggedOnUser        : .\tobia
LastLoggedOnUserSID     : S-1-5-21-2770831484-2260150476-2133527644-1001
IdleTime                : 62486093

同样,此行返回 Windows 注册表中注册的所有用户配置文件:

1
2
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\profilelist\*' |
Select-Object -Property ProfileImagePath, FullProfile

PowerShell 技能连载 - 使用 BITS 来下载文件(第 2 部分)

BITS(背景智能传输系统)是Windows用于下载大文件(例如操作系统更新)的技术。您也可以使用该服务,例如异步下载文件。执行此操作时,您无需等待下载完成,甚至可以在几天内跨越多次重启下载超大文件。每当用户再次登录时,下载就会继续。

下面的示例代码以较低优先级的异步后台任务下载 NASA 火星报告:

1
2
3
$url = 'https://mars.nasa.gov/system/downloadable_items/41764_20180703_marsreport-1920.mp4'
$targetfolder = $env:temp
Start-BitsTransfer -Source $url -Destination $targetfolder -Asynchronous -Priority Low

异步 BITS 传输的缺点是您需要手动完成文件传输,因为 BITS 会将数据下载到隐藏的缓存中。由您决定何时运行 Get-BitsTransfer 并确定已完成的作业,然后使用 Complete-BitsTransfer 完成文件传输。

本示例将检查是否已完成传输并完成下载:

1
2
3
4
5
6
7
8
9
10
11
12
Get-BitsTransfer |
ForEach-Object {
Write-Warning $_.FileList.RemoteName
$_
} |
Where-Object { $_.jobstate -eq 'Transferred' } |
ForEach-Object {
#$_ | Select-Object -Property *
$file = $_.FileList.LocalName
Write-Warning "Copy file $file..."
$_ | Complete-BitsTransfer
}

Windows 正在使用相同的技术来下载大量的操作系统更新。如果您具有管理员特权,则可以检查由其他用户(包括操作系统)发起的 BITS 传输:

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

JobId DisplayName TransferType JobState
----- ----------- ------------ ------
18514439-0e92-4ca8-88b4-4b2aa0036114 MicrosoftMapsBingGeoStore Download Sus...
9e76ff92-65e2-4b3c-bb01-263e485e986a Dell_Asimov.C18EE5B49BDB373421EFA627336E417FC7EBB5B3 Download Sus...
7bd117fe-2326-4198-a2a6-884022acb3ad Dell_Asimov.F10AAAECCA3ED0E344B68351EB619B5356E6C3C5 Download Sus...

PS> Get-BitsTransfer -AllUsers | Select-Object OwnerAccount, Priority, FileList

OwnerAccount Priority FileList
------------ -------- --------
NT AUTHORITY\NETWORK SERVICE Normal {}
NT AUTHORITY\SYSTEM Foreground {https://downloads.dell.com/catalog/CatalogIndexPC.cab}
NT AUTHORITY\SYSTEM Foreground {https://dellupdater.dell.com/non_du/ClientService/Catalog/CatalogI...

PowerShell 技能连载 - 使用 BITS 来下载文件(第 1 部分)

BITS(后台智能传输系统)是 Windows 用于下载大文件(例如操作系统更新)的技术。您也可以使用相同的系统下载大文件。另外一个好处是,在下载文件时,您会获得一个不错的进度条。本示例下载 NASA 火星报告并在下载后播放视频:

1
2
3
4
5
6
7
8
9
$url = 'https://mars.nasa.gov/system/downloadable_items/41764_20180703_marsreport-1920.mp4'
$targetfolder = $env:temp
$filename = Split-Path -Path $url -Leaf
$targetFile = Join-Path -Path $targetfolder -ChildPath $filename

Start-BitsTransfer -Source $url -Destination $targetfolder -Description 'Downloading Video...' -Priority Low


Start-Process -FilePath $targetFile

请注意 Start-BitsTransfer 如何让您选择下载优先级,这样您就可以下载文件而不会占用更重要的事情所需的网络带宽。另请注意,BITS 不适用于所有下载内容。服务器需要支持该技术。

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 技术 QQ 群