PowerShell's
Get-ChildItem -Recurse
will recurse into symbolic links and junctions, unless prohibited by ACLs. This can easily result in pseudo-infinite loops where PowerShell returns paths such as
C:\Users\All Users\Application Data\Application Data\Application Data
. The
C:\Users
directory is particularly prone to such problems, as it contains both a symbolic link and a junction:
C:\>dir c:\Users /a
Volume in drive C has no label.
Volume Serial Number is 787B-CFFB
Directory of c:\Users
2019-03-14 14:26 <DIR> .
2019-03-14 14:26 <DIR> ..
2019-03-12 00:41 <DIR> Administrator
2018-04-12 00:45 <SYMLINKD> All Users [C:\ProgramData]
2019-01-09 03:50 <DIR> Default
2018-04-12 00:45 <JUNCTION> Default User [C:\Users\Default]
2018-09-24 00:16 <DIR> Public
2018-04-12 00:36 174 desktop.ini
1 File(s) 174 bytes
8 Dir(s) 390.996.451.328 bytes free
The following function
Find-Files
– both in name and behavior inspired by the
Unix find program – avoids this problem as it does not recurse into symbolic links or junctions:
function Find-Files
{
<#
.SYNOPSIS
Lists the contents of a directory. Unlike Get-ChildItem, this function does not recurse into symbolic links or junctions in order to avoid infinite loops.
#>
param (
[Parameter( Mandatory=$false )]
[string]
# Specifies the path to the directory whose contents are to be listed. By default, the current working directory is used.
$LiteralPath = (Get-Location),
[Parameter( Mandatory=$false )]
# Specifies a filter that is applied to each file or directory. Wildcards ? and * are supported.
$Filter,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if file objects should be returned. By default, all file system objects are returned.
$File = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if directory objects should be returned. By default, all file system objects are returned.
$Directory = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if reparse point objects should be returned. By default, all file system objects are returned.
$ReparsePoint = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if the top directory should be returned. By default, all file system objects are returned.
$Self = $true
)
function Enumerate( [System.IO.FileSystemInfo] $Item ) {
$Item;
if ( $Item.GetType() -eq [System.IO.DirectoryInfo] -and ! $Item.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
foreach ($ChildItem in $Item.EnumerateFileSystemInfos() ) {
Enumerate $ChildItem;
}
}
}
function FilterByName {
process {
if ( ( $Filter -eq $null ) -or ( $_.Name -ilike $Filter ) ) {
$_;
}
}
}
function FilterByType {
process {
if ( $_.GetType() -eq [System.IO.FileInfo] ) {
if ( $File ) { $_; }
} elseif ( $_.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
if ( $ReparsePoint ) { $_; }
} else {
if ( $Directory ) { $_; }
}
}
}
$Skip = if ($Self) { 0 } else { 1 };
Enumerate ( Get-Item -LiteralPath $LiteralPath -Force ) | Select-Object -Skip $Skip | FilterByName | FilterByType;
}
Find-Files.ps1