Skip to content

Commit 1743653

Browse files
committed
Release 1.114.2025
1 parent c4bd5ba commit 1743653

11 files changed

Lines changed: 476 additions & 351 deletions

Functions/GenXdev.FileSystem/AssurePester.ps1

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
################################################################################
22
<#
33
.SYNOPSIS
4-
Ensures that Pester testing framework is installed and available.
4+
Ensures Pester testing framework is available for use.
55
66
.DESCRIPTION
7-
This function checks if Pester module is installed. If not found, it attempts to
8-
install it from the PowerShell Gallery and imports it into the current session.
7+
This function verifies if the Pester module is installed in the current
8+
PowerShell environment. If not found, it automatically installs it from the
9+
PowerShell Gallery and imports it into the current session. This ensures that
10+
Pester testing capabilities are available when needed.
911
1012
.EXAMPLE
1113
AssurePester
14+
# This ensures Pester is installed and ready for use
1215
#>
1316
function AssurePester {
1417

@@ -17,36 +20,43 @@ function AssurePester {
1720

1821
begin {
1922

23+
# inform user that we're checking pester installation
2024
Write-Verbose "Checking for Pester module installation..."
2125
}
2226

2327
process {
2428

25-
# attempt to import pester module without showing errors
29+
# attempt silent import of pester to check if it's available
2630
Import-Module -Name Pester -ErrorAction SilentlyContinue
2731

28-
# check if pester module is available
32+
# verify if pester module is now loaded in the current session
2933
if (-not (Get-Module -Name Pester -ErrorAction SilentlyContinue)) {
3034

35+
# notify about installation attempt through verbose and regular output
3136
Write-Verbose "Pester module not found, attempting installation..."
3237
Write-Host "Pester not found. Installing Pester..."
3338

3439
try {
35-
# install pester from powershell gallery
36-
$null = Install-Module -Name Pester -Force -SkipPublisherCheck
40+
# install pester module from the powershell gallery
41+
$null = Install-Module -Name Pester `
42+
-Force `
43+
-SkipPublisherCheck
3744

38-
# import the newly installed module
45+
# load the newly installed pester module
3946
$null = Import-Module -Name Pester -Force
4047

48+
# confirm successful installation
4149
Write-Host "Pester installed successfully."
4250
Write-Verbose "Pester module installation and import completed."
4351
}
4452
catch {
53+
# report any installation failures
4554
Write-Error "Failed to install Pester. Error: $PSItem"
4655
Write-Verbose "Pester installation failed with error."
4756
}
4857
}
4958
else {
59+
# inform that pester is already available
5060
Write-Verbose "Pester module already installed and imported."
5161
}
5262
}

Functions/GenXdev.FileSystem/Find-DuplicateFiles.ps1

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
################################################################################
22
<#
33
.SYNOPSIS
4-
Find duplicate files by name and properties across specified directories.
4+
Find duplicate files across multiple directories based on configurable criteria.
55
66
.DESCRIPTION
7-
Takes an array of directory paths, searches each path recursively for files,
8-
then groups files by name and optionally by size and modified date. Returns
9-
groups containing two or more duplicate files.
7+
Recursively searches specified directories for duplicate files. Files are
8+
considered duplicates if they share the same name and optionally match on size
9+
and modification date. Returns groups of duplicate files for further processing.
1010
1111
.PARAMETER Paths
12-
One or more directory paths to search for duplicate files.
12+
Array of directory paths to recursively search for duplicate files. Accepts
13+
pipeline input and wildcard paths.
1314
1415
.PARAMETER DontCompareSize
15-
Skip file size comparison when determining duplicates.
16+
When specified, file size is not used as a comparison criterion, only names
17+
are matched.
1618
1719
.PARAMETER DontCompareModifiedDate
18-
Skip last modified date comparison when determining duplicates.
20+
When specified, file modification dates are not used as a comparison criterion.
1921
2022
.EXAMPLE
21-
Find-DuplicateFiles -Paths "C:\Folder1","D:\Folder2" -DontCompareSize
23+
Find-DuplicateFiles -Paths "C:\Photos","D:\Backup\Photos"
2224
2325
.EXAMPLE
24-
Get-Item "C:\Folder1","D:\Folder2" | Find-DuplicateFiles
26+
"C:\Photos","D:\Backup\Photos" | fdf -DontCompareSize
2527
#>
2628
function Find-DuplicateFiles {
2729

@@ -58,69 +60,68 @@ function Find-DuplicateFiles {
5860

5961
begin {
6062

61-
# normalize all input paths to full paths
63+
# convert all input paths to full filesystem paths
6264
$normalizedPaths = @()
6365
$Paths | ForEach-Object {
6466
$normalizedPaths += (Expand-Path $_)
6567
}
6668

67-
# helper function to generate unique key for file comparison
69+
# internal helper function to generate unique comparison key for each file
6870
function Get-FileKey([System.IO.FileInfo]$file) {
6971

70-
# start with filename as base key
72+
# start with filename as the base identifier
7173
$key = $file.Name
7274

73-
# add size to key if size comparison is enabled
75+
# include file size in comparison key if enabled
7476
if (-not $DontCompareSize) {
7577
$key += "|$($file.Length)"
7678
}
7779

78-
# add modified date to key if date comparison is enabled
80+
# include modification date in comparison key if enabled
7981
if (-not $DontCompareModifiedDate) {
8082
$key += "|$($file.LastWriteTimeUtc.ToString('o'))"
8183
}
8284

8385
return $key
8486
}
8587

86-
# initialize generic list for better performance with large collections
88+
# initialize high-performance collection for gathering files
8789
$allFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
8890
}
8991

9092
process {
9193

92-
# process each normalized path
9394
foreach ($path in $normalizedPaths) {
9495

95-
# verify directory exists before processing
96+
# verify directory exists before attempting to process
9697
if ([System.IO.Directory]::Exists($path)) {
9798

98-
Write-Verbose "Scanning directory: $path"
99+
Write-Verbose "Scanning directory for duplicates: $path"
99100

100-
# get all files using direct .NET IO methods for performance
101+
# use direct .NET IO for faster recursive file enumeration
101102
[System.IO.Directory]::GetFiles($path, "*.*",
102103
[System.IO.SearchOption]::AllDirectories) |
103104
ForEach-Object {
104105
$null = $allFiles.Add([System.IO.FileInfo]::new($_))
105106
}
106107
}
107108
else {
108-
Write-Warning "Directory not found: $path"
109+
Write-Warning "Skipping non-existent directory: $path"
109110
}
110111
}
111112
}
112113

113114
end {
114115

115-
# group files by composite key and return groups with duplicates
116+
# group files by composite key and return only groups with duplicates
116117
$allFiles |
117118
Group-Object -Property { Get-FileKey $_ } |
118119
Where-Object { $_.Count -gt 1 } |
119120
ForEach-Object {
120-
# create custom object for each group of duplicates
121+
# create result object for each duplicate group
121122
[PSCustomObject]@{
122123
FileName = $_.Group[0].Name
123-
Files = $_.Group
124+
Files = $_.Group
124125
}
125126
}
126127
}

Functions/GenXdev.FileSystem/Find-Item.ps1

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
11
################################################################################
22
<#
33
.SYNOPSIS
4-
Searches for file- or directory- names with optionally filtering regex content matching
4+
Searches for files or directories with optional content filtering.
55
66
.DESCRIPTION
7-
Searches for file- or directory- names, optionally performs a regular expression
8-
match within the content of each matched file.
7+
Performs recursive file and directory searches with support for:
8+
- File/directory name patterns
9+
- Content matching using regular expressions
10+
- Searching across all drives
11+
- Directory-only searches
12+
- Object pipeline output
913
1014
.PARAMETER SearchMask
11-
Specify the file name or pattern to search for. Default is "*".
15+
The file/directory name pattern to search for. Supports wildcards.
16+
Defaults to "*" if not specified.
1217
1318
.PARAMETER Pattern
14-
Regular expression pattern to search within the content of files to match against. Default is ".*".
19+
Regular expression to match against file contents.
20+
Only applies when searching files, not directories.
1521
1622
.PARAMETER AllDrives
17-
Search all drives.
23+
Search across all available drives instead of just the current path.
1824
1925
.PARAMETER Directory
20-
Search for directories only.
26+
Search for directories only, ignoring files.
2127
2228
.PARAMETER PassThru
23-
Pass through the objects to the pipeline.
29+
Output matched items as objects instead of formatted strings.
2430
2531
.EXAMPLE
26-
# Find all files with the .txt extension in the current directory and its subdirectories
32+
# Search for all .txt files in current directory and subdirectories
2733
Find-Item -SearchMask "*.txt"
2834
29-
# or in short
30-
l *.txt
31-
3235
.EXAMPLE
3336
# Find all files with that have the word "translation" in their name
3437
Find-Item -SearchMask "*translation*"
@@ -113,7 +116,7 @@ function Find-Item {
113116
Mandatory = $false,
114117
Position = 1,
115118
ParameterSetName = 'WithPattern',
116-
HelpMessage = "Regular expression pattern to search within the content of files to match against"
119+
HelpMessage = "Regular expression pattern to search within file contents"
117120
)]
118121
[Alias("mc", "matchcontent")]
119122
[PSDefaultValue(Value = ".*")]
@@ -144,23 +147,36 @@ function Find-Item {
144147

145148
begin {
146149

150+
Write-Verbose "Starting Find-Item with SearchMask: $SearchMask"
151+
152+
# normalize and validate the search mask
147153
$SearchMask = $SearchMask.Trim()
148154
if ($SearchMask -eq [string]::Empty) {
149-
150155
$SearchMask = ".\*"
151156
}
152-
$SearchMask = $SearchMask.Trim().Replace("\", [IO.Path]::DirectorySeparatorChar).Replace("/", [IO.Path]::DirectorySeparatorChar).Replace([IO.Path]::DirectorySeparatorChar + [IO.Path]::DirectorySeparatorChar, [IO.Path]::DirectorySeparatorChar)
153157

154-
if ($SearchMask.EndsWith([IO.Path]::DirectorySeparatorChar)) {
158+
# normalize path separators
159+
$SearchMask = $SearchMask.Trim().Replace("\", [IO.Path]::DirectorySeparatorChar).`
160+
Replace("/", [IO.Path]::DirectorySeparatorChar).`
161+
Replace([IO.Path]::DirectorySeparatorChar + [IO.Path]::DirectorySeparatorChar,
162+
[IO.Path]::DirectorySeparatorChar)
155163

164+
# ensure directory paths end with wildcard
165+
if ($SearchMask.EndsWith([IO.Path]::DirectorySeparatorChar)) {
156166
$SearchMask += "*"
157167
}
158168

159-
# expand search mask path to full path
169+
Write-Verbose "Normalized SearchMask: $SearchMask"
170+
171+
# convert to full path
160172
$SearchMask = Expand-Path $SearchMask
173+
Write-Verbose "Expanded SearchMask: $SearchMask"
161174

162-
# get current directory for relative path handling
175+
# store current location for relative path handling
163176
$location = Get-Location | ForEach-Object Path
177+
}
178+
179+
process {
164180

165181
# helper function to search file content
166182
function Search-FileContent {
@@ -172,11 +188,6 @@ function Find-Item {
172188
return Select-String -Path $FilePath -Pattern $Pattern
173189
}
174190

175-
$SearchMask = Expand-Path $SearchMask
176-
}
177-
178-
process {
179-
180191
# output blank line unless passing through objects
181192
if (-not $PassThru) {
182193
"" | Out-Host
@@ -186,14 +197,17 @@ function Find-Item {
186197
if ((-not $Directory) -and ($Pattern -ne ".*") -and
187198
(-not [string]::IsNullOrWhiteSpace($Pattern))) {
188199

189-
# searching all drives
200+
Write-Verbose "Searching files with content pattern: $Pattern"
201+
202+
# search all drives
190203
if ($AllDrives) {
204+
Write-Verbose "Searching across all drives"
191205

192-
# get all drives and process in parallel
206+
# process drives in parallel
193207
Get-PSDrive -ErrorAction SilentlyContinue |
194208
ForEach-Object -ThrottleLimit 8 -Parallel {
195209

196-
# extract filename from search mask
210+
# extract filename part
197211
$file = [IO.Path]::GetFileName($SearchMask)
198212
$filter = [string]::IsNullOrEmpty($file) ? "*" : $file
199213

@@ -208,7 +222,7 @@ function Find-Item {
208222
Where-Object -Property Name -Like $filter |
209223
ForEach-Object {
210224

211-
# check file content for pattern
225+
# check file content
212226
if (Search-FileContent -FilePath $PSItem.FullName `
213227
-Pattern $Pattern) {
214228

@@ -233,8 +247,9 @@ function Find-Item {
233247
return
234248
}
235249

236-
# regular content search in current location
237-
Get-ChildItem $SearchMask -File -Recurse | ForEach-Object {
250+
# regular content search
251+
Get-ChildItem $SearchMask -File -Recurse |
252+
ForEach-Object {
238253
if (($Pattern -eq ".*") -or
239254
(Search-FileContent -FilePath $PSItem.FullName -Pattern $Pattern)) {
240255

@@ -255,8 +270,10 @@ function Find-Item {
255270
return
256271
}
257272

258-
# searching all drives without content pattern
273+
# search all drives without content
259274
if ($AllDrives) {
275+
Write-Verbose "Searching all drives for matching items"
276+
260277
Get-PSDrive -ErrorAction SilentlyContinue |
261278
ForEach-Object -ThrottleLimit 8 -Parallel {
262279
try {
@@ -282,7 +299,9 @@ function Find-Item {
282299
return
283300
}
284301

285-
# regular file/directory search without content pattern
302+
# regular file/directory search
303+
Write-Verbose "Performing regular file/directory search"
304+
286305
$dir = [IO.Path]::GetDirectoryName($SearchMask)
287306
if ([string]::IsNullOrEmpty($dir)) {
288307
$dir = ".$([IO.Path]::DirectorySeparatorChar)"
@@ -321,3 +340,4 @@ function Find-Item {
321340
end {
322341
}
323342
}
343+
################################################################################

0 commit comments

Comments
 (0)