I've noticed some behavior in PowerShell that I can't explain, but I'm hoping that someone else can.
If I want to build a list of file objects from drive C:\
, and I want to ignore shortcut folders (reparse points) such as C:\Documents and Settings\
. The following command works well:
$FileList = @(Get-ChildItem -Path C:\ -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
The Where-Object
command returns no files as expected, since C:\Documents and Settings\
is a reparse point.
However, if I run the Test-Connection
command first, then the Get-ChildItem
command appears to ignore the -Attributes !ReparsePoint
parameter, and it traverses 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*"};
In this case, the Where-Object
command displays a lot of files. Note that the Test-Connection
can be run against any computer, not just the local computer to exhibit this behavior.
I've duplicated this behavior on computers running PowerShell 4.0 and PowerShell 5.1. Can anyone please explain what's happening?
Additional note: To duplicate this behavior, please make sure that you are using an elevated instance of PowerShell (Run As Administrator). If you use a standard instance of PowerShell, you won't have permissions to view C:\Documents and Settings\
.
The Get-ChildItem cmdlet gets the items in one or more specified locations. If the item is a container, it gets the items inside the container, known as child items. You can use the Recurse parameter to get items in all child containers and use the Depth parameter to limit the number of levels to recurse.
Get-ChildItem (GCI) gets items and if the item is a container, it will get child items available inside the container. Location specified in PowerShell Get-ChildItem can be file system directory, registry, or certificate store. Let's understand the PowerShell Get-ChildItem cmdlet with examples.
To answer the first issue where it seems to ignore the -Attributes !ReparsePoint
, see my full SO answer with examples here: How to prevent recursion through node_modules for gci script. The TLDR is that by adding the -Recurse
parameter to Get-ChildItem
means that it will iterate through -all- items first then it will apply the filtering. That means it will faithfully exclude Reparse Points i.e. the specific folder: C:\Documents and Settings
but since it get's all items first, it will also return everything underneath that folder as well. e.g. C:\Documents and Settings\desktop.ini
because that isn't a Reparse Point, it's a document. That's why you can't use the -Recurse
parameter without additional directory level filtering if that is your intention.
The second issue, executing Test-Connection
changes what Get-ChildItem
returns, does seem like a bug, in the manner that, it looks like this is a case of PowerShell not "fully" elevating the user when the Get-ChildItem
is first executed. But at the same time, this might be intentional by the PowerShell team to remove the case of unintended duplicate items being returned.
To investigate, let's open a new PowerShell prompt as Administrator. I will slightly change the code to include a -Depth 1
just to make it easier to run through the code without having to enumerate my whole C:
drive ;-) but, it will still illustrate the issue:
$FileList = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
When I run this, the first clue to what is going on is the error message:
Get-ChildItem : Access to the path 'C:\Documents and Settings' is denied.
This highlights the fact that even though we are running the PowerShell prompt as Administrator, we are not running in a manner "elevated" enough to access the hidden, reparse point, system folder, "Documents and Settings". We have to keep in mind that the "Documents and Settings" folder is meant as a cheap workaround for legacy (bad coders ;-) compatibility with Windows Vista etc. and is not meant for "daily" normal usage. And if we do use it, it may cause unintentional duplicate/recursive items to be returned. i.e.:
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
We see that we got the contents of the Users folder, but due to the error earlier, we did not return the contents of the Documents and Settings folder. This is good and likely intentional because we did not get any duplicate items. We only got the "real" items on the "real" path.
If we run the Test-Connection
command next, followed by the same command (with different variable names to eliminate confusion):
Test-Connection -Computer MyComputer;
$FileList2 = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList2 | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
We notice 2 things:
.
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
This, I would say, is unintentional. Returning the items from the Reparse Point path is not desired. If we were feeding this information into other script tasks, e.g. Remove-Item
, then we may run into unintended issues.
Now, let's move on to investigate why a seemingly innocuous command, Test-Connection
, would cause a different outcome. The earlier Access Denied error message gives me a lead that something around authentication seems to be causing the difference. Let's close the PowerShell prompt and re-open it. Let's re-run the same first command:
$FileList = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
And let's check our Credentials to make sure that we are running as Administrator:
PS C:\> $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
PS C:\> $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
True
Indeed, we are running the prompt as Administrator, so that's not the issue. Let's look more into the Identity:
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
....
From this, we can see the "well known" group S-1-5-32-544
as administrators. Notice the Impersonation Level is "None". If we run the Test-Connection
and the same commands:
Test-Connection -Computer D4700;
$FileList2 = @(Get-ChildItem -Path C:\ -Depth 1 -Recurse -Force -Attributes !ReparsePoint);
$FileList2 | Where-Object {$_.DirectoryName -like "*Documents and Settings*"};
And get the principal object:
PS C:\> $currentPrincipal2 = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
PS C:\> $currentPrincipal2.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
True
This proves we are still running as administrator. Now let's look at the identity:
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
....
This time we see that the Impersonation Level is Impersonation
. From the 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.
When we did the Test-Connection
it required a network resource on the local computer in order to perform the ping. In order to do this, it transparently elevated the prompt to Impersonate
level from None
(we can even see this happened because the Token Number changed). This has the side effect of allowing the Get-ChildItem
to now have the elevated rights to access the system file Documents and Settings. i.e. like checking off "show hidden files" in File Explorer, and now allowing it to enumerate through the Reparse Point.
We can also observe that this is the cause when we remote into another machine:
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
....
When we do this, we see that it returns Documents and Settings without needing Test-Connection
to be executed first. Looking at the Principal, we see that the Impersonation Level is set to Impersonation
.
Ultimately this tells us that the cause of this incongruity is the Impersonation Level setting. In order to access the hidden system file, and Reparse Point, we need the Impersonation Level set to at least Impersonation
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With