为什么 Test-Connection 会强制枚举重新分析点?

Why does Test-Connection force enumeration of reparse points?

我注意到 PowerShell 中有一些我无法解释的行为,但我希望其他人可以解释。

如果我想从驱动器 C:\ 构建一个文件对象列表,并且我想忽略 C:\Documents and Settings\ 等快捷文件夹(重新分析点)。以下命令运行良好:

$FileList = @(Get-ChildItem -Path C:\ -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

Where-Object 命令 returns 没有预期的文件,因为 C:\Documents and Settings\ 是一个重新分析点。

但是,如果我先运行Test-Connection命令,那么Get-ChildItem命令似乎忽略了-Attributes !ReparsePoint参数,它遍历了C:\Documents and Settings\ .

Test-Connection -Computer MyComputer;
$FileList = @(Get-ChildItem -Path C:\ -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

在这种情况下,Where-Object命令显示了很多文件。请注意,Test-Connection 可以针对任何计算机 运行,而不仅仅是表现出此行为的本地计算机。

我在计算机 运行ning PowerShell 4.0 和 PowerShell 5.1 上复制了这种行为。谁能解释一下发生了什么?


附加说明:要复制此行为,请确保您使用的是提升的 PowerShell 实例(运行 作为管理员)。如果您使用 PowerShell 的标准实例,您将无权查看 C:\Documents and Settings\.

要回答似乎忽略了 -Attributes !ReparsePoint 的第一个问题,请在此处查看我的完整 SO 答案和示例:How to prevent recursion through node_modules for gci script。 TLDR 是通过将 -Recurse 参数添加到 Get-ChildItem 意味着它将遍历 -all-first 然后 它将应用过滤。这意味着它将忠实地排除重新分析点,即特定文件夹:C:\Documents and Settings 但由于它得到所有项目 first,它也会 return 该文件夹下的所有内容.例如C:\Documents and Settings\desktop.ini 因为 不是 重分析点,它是 文档 。这就是为什么你不能在没有额外目录级过滤的情况下使用 -Recurse 参数的原因,如果这是你的意图的话。

第二个问题,执行 Test-Connection 改变了 Get-ChildItem returns,看起来确实像一个错误,看起来这是 PowerShell 的情况而不是"fully" 在 Get-ChildItem 首次执行时提升用户。但与此同时,这可能是 PowerShell 团队有意为之,以消除 returned.

意外重复项的情况。

为了进行调查,让我们以管理员身份打开一个新的 PowerShell 提示符。我将稍微更改代码以包含 -Depth 1 只是为了更容易 运行 通过代码而不必枚举我的整个 C: 驱动器 ;-) 但是,它仍然会说明问题:

$FileList = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

当我 运行 执行此操作时,第一个线索就是错误消息:

Get-ChildItem : Access to the path 'C:\Documents and Settings' is denied.

这突出了一个事实,即即使我们 运行 以管理员身份运行 PowerShell 提示符,我们也没有 运行 以足以访问隐藏、重新解析的方式 运行 运行点,系统文件夹,"Documents and Settings"。我们必须记住,"Documents and Settings" 文件夹是旧版( 糟糕的编码器 ;-)与 Windows Vista 等兼容的廉价解决方法,并且是不适合 "daily" 正常使用。如果我们确实使用它,它可能会导致无意中 duplicate/recursive 项目被 returned。即:

PS C:\> $FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
PS C:\>
PS C:\> $FileList | Where-Object {$_.DirectoryName -like "*Users*"};


    Directory: C:\Users


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a-hs-       2018-09-15   1:31 AM            174 desktop.ini

我们看到我们得到了 Users 文件夹的内容,但是由于之前的错误,我们没有 return Documents and Settings 文件夹的内容。这很好而且可能是故意的,因为我们没有得到任何重复的项目。我们只在 "real" 路径上得到了 "real" 项。

如果我们运行接下来是Test-Connection命令,接着是相同的命令(使用不同的变量名以消除混淆):

Test-Connection -Computer MyComputer;
$FileList2 = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList2 | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

我们注意到两件事:

  1. 我们没有收到任何拒绝访问错误消息。
  2. 我们得到更多(重复)项目:

.

PS C:\> $FileList2 | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};    

    Directory: C:\Documents and Settings


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a-hs-       2018-09-15   1:31 AM            174 desktop.ini

PS C:\> $FileList2 | Where-Object {$_.DirectoryName -like "*Users*"};


    Directory: C:\Users


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a-hs-       2018-09-15   1:31 AM            174 desktop.ini

我想说,这是无意的。不需要从重分析点路径返回项目。如果我们将此信息提供给其他脚本任务,例如Remove-Item,那么我们可能会 运行 出现意外问题。

现在,让我们继续研究为什么看似无害的命令 Test-Connection 会导致不同的结果。较早的 Access Denied 错误消息给了我一个线索,即身份验证似乎是导致差异的原因。让我们关闭 PowerShell 提示符并 re-open 它。让我们 re-run 同样的第一个命令:

$FileList = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

让我们检查我们的凭据以确保我们运行宁作为管理员:

PS C:\> $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
PS C:\> $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
True

的确,我们运行以管理员身份出现提示,所以这不是问题所在。让我们进一步了解身份:

PS C:\> $currentPrincipal.Identities


AuthenticationType : Kerberos
ImpersonationLevel : None
IsAuthenticated    : True
IsGuest            : False
IsSystem           : False
IsAnonymous        : False
Name               : Contoso\HAL9256
Owner              : S-1-5-32-544
....
Token              : 3076
....

由此可见,"well known"组S-1-5-32-544为管理员。请注意模拟级别是 "None"。如果我们 运行 Test-Connection 和相同的命令:

Test-Connection -Computer D4700;
$FileList2 = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList2 | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};

并获取主体对象:

PS C:\> $currentPrincipal2 = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
PS C:\> $currentPrincipal2.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
True

这证明我们仍然是 运行管理员。下面我们来看看身份:

PS C:\> $currentPrincipal2.Identities


AuthenticationType : Kerberos
ImpersonationLevel : Impersonation
IsAuthenticated    : True
IsGuest            : False
IsSystem           : False
IsAnonymous        : False
Name               : Contoso\HAL9256
Owner              : S-1-5-32-544
....
Token              : 4500
....

这次我们看到模拟级别是Impersonation。来自 Impersonation Level Enum Docs:

Impersonation 3

The server process can impersonate the client's security context on its local system. The server cannot impersonate the client on remote systems.

当我们执行 Test-Connection 时,它需要本地计算机上的网络资源才能执行 ping。为了做到这一点,它透明地将提示从 None 提升到 Impersonate 级别(我们甚至可以看到这是因为令牌号发生了变化)。这具有允许 Get-ChildItem 现在具有访问系统文件文档和设置的提升权限的副作用。即在文件资源管理器中勾选 "show hidden files",现在允许它通过重分析点进行枚举。

当我们远程进入另一台机器时,我们也可以观察到这是原因:

PS C:\> Enter-PSSession -ComputerName RemoteMachine -Credential (Get-Credential) -Authentication Kerberos

[RemoteMachine]: PS C:\Users\HAL9256\Documents> $FileList = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
[RemoteMachine]: PS C:\Users\HAL9256\Documents> $FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};


    Directory: C:\Documents and Settings


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a-hs-        7/16/2016   7:21 AM            174 desktop.ini


[RemoteMachine]: PS C:\Users\HAL9256\Documents>
[RemoteMachine]: PS C:\Users\HAL9256\Documents> $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
[RemoteMachine]: PS C:\Users\HAL9256\Documents> $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
True
[RemoteMachine]: PS C:\Users\HAL9256\Documents>
[RemoteMachine]: PS C:\Users\HAL9256\Documents> $currentPrincipal.Identities

AuthenticationType : Kerberos
ImpersonationLevel : Impersonation
IsAuthenticated    : True
IsGuest            : False
IsSystem           : False
IsAnonymous        : False
Name               : Contoso\HAL9256
Owner              : S-1-5-32-544
....
Token              : 4420
....

当我们这样做时,我们看到它 returns 文档和设置,而不需要先执行 Test-Connection。查看 Principal,我们看到模拟级别设置为 Impersonation

最终这告诉我们,造成这种不协调的原因是模拟级别设置。为了访问隐藏的系统文件和 Reparse Point,我们需要将 Impersonation Level 至少设置为 Impersonation.