Skip to content

Commit 3897cdc

Browse files
committed
Release 1.94.2025
1 parent 1ce03f4 commit 3897cdc

981 files changed

Lines changed: 4073 additions & 5975 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 0 additions & 3 deletions
This file was deleted.

ExampleCmdlet.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
################################################################################
2+
3+
function AssurePester {
4+
5+
Import-Module -Name Pester -ErrorAction SilentlyContinue
6+
7+
# Check if Pester is installed
8+
if (-not (Get-Module -Name Pester -ErrorAction SilentlyContinue)) {
9+
10+
Write-Host "Pester not found. Installing Pester..."
11+
12+
# Install Pester from the PowerShell Gallery
13+
try {
14+
Install-Module -Name Pester -Force -SkipPublisherCheck | Out-Null
15+
Import-Module -Name Pester -Force | Out-Null
16+
Write-Host "Pester installed successfully."
17+
}
18+
catch {
19+
20+
Write-Error "Failed to install Pester. Error: $PSItem"
21+
}
22+
}
23+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
################################################################################
2+
<#
3+
.SYNOPSIS
4+
Expands any given file reference to a full pathname.
5+
6+
.DESCRIPTION
7+
Expands any given file reference to a full pathname, with respect to the user's
8+
current directory. Can optionally assure that directories or files exist.
9+
10+
.PARAMETER FilePath
11+
The file path to expand to a full path.
12+
13+
.PARAMETER CreateDirectory
14+
Will create directory if it does not exist.
15+
16+
.PARAMETER CreateFile
17+
Will create an empty file if it does not exist.
18+
19+
.EXAMPLE
20+
Expand-Path -FilePath ".\myfile.txt" -CreateFile
21+
22+
.EXAMPLE
23+
ep ~\documents\test.txt -CreateFile
24+
#>
25+
function Expand-Path {
26+
27+
[CmdletBinding()]
28+
[Alias("ep")]
29+
30+
param(
31+
########################################################################
32+
[Parameter(
33+
Mandatory = $true,
34+
Position = 0,
35+
ValueFromPipeline = $true,
36+
ValueFromPipelineByPropertyName = $true,
37+
HelpMessage = "Path to expand"
38+
)]
39+
[ValidateNotNullOrEmpty()]
40+
[string] $FilePath,
41+
########################################################################
42+
[Parameter(
43+
Mandatory = $false,
44+
HelpMessage = "Will create directory if it does not exist"
45+
)]
46+
[switch] $CreateDirectory,
47+
########################################################################
48+
[Parameter(
49+
Mandatory = $false,
50+
HelpMessage = "Will create an empty file if it does not exist"
51+
)]
52+
[switch] $CreateFile,
53+
########################################################################
54+
[Parameter(
55+
Mandatory = $false,
56+
HelpMessage = "Will delete the file if it already exists"
57+
)]
58+
[switch] $DeleteExistingFile
59+
########################################################################
60+
)
61+
62+
begin {
63+
64+
# normalize path separators and remove double separators
65+
$normalizedPath = $FilePath.Trim().Replace("\", [IO.Path]::DirectorySeparatorChar).
66+
Replace("/", [IO.Path]::DirectorySeparatorChar).
67+
Replace([IO.Path]::DirectorySeparatorChar + [IO.Path]::DirectorySeparatorChar,
68+
[IO.Path]::DirectorySeparatorChar)
69+
70+
# check if path ends with a directory separator
71+
$hasTrailingSeparator = $normalizedPath.EndsWith(
72+
[System.IO.Path]::DirectorySeparatorChar) -or
73+
$normalizedPath.EndsWith([System.IO.Path]::AltDirectorySeparatorChar)
74+
}
75+
76+
process {
77+
78+
# expand home directory if path starts with ~
79+
if ($normalizedPath.StartsWith("~")) {
80+
$normalizedPath = Join-Path (Resolve-Path ~).Path `
81+
$normalizedPath.Substring(1)
82+
}
83+
84+
# handle absolute paths (drive letter or UNC)
85+
if ((($normalizedPath.Length -gt 1) -and
86+
($normalizedPath.Substring(1, 1) -eq ":")) -or
87+
$normalizedPath.StartsWith("\\")) {
88+
89+
try {
90+
$normalizedPath = [System.IO.Path]::GetFullPath($normalizedPath)
91+
}
92+
catch {
93+
Write-Verbose "Failed to normalize path, keeping original"
94+
}
95+
}
96+
else {
97+
# handle relative paths
98+
try {
99+
$normalizedPath = [System.IO.Path]::GetFullPath(
100+
[System.IO.Path]::Combine($pwd, $normalizedPath))
101+
}
102+
catch {
103+
$normalizedPath = Convert-Path $normalizedPath
104+
}
105+
}
106+
107+
# handle directory/file creation if requested
108+
if ($CreateDirectory -or $CreateFile) {
109+
110+
# get directory path accounting for trailing separator
111+
$directoryPath = if ($hasTrailingSeparator) {
112+
[IO.Path]::TrimEndingDirectorySeparator($normalizedPath)
113+
}
114+
else {
115+
[IO.Path]::TrimEndingDirectorySeparator(
116+
[System.IO.Path]::GetDirectoryName($normalizedPath))
117+
}
118+
119+
# create directory if it doesn't exist
120+
if (-not [IO.Directory]::Exists($directoryPath)) {
121+
$null = [IO.Directory]::CreateDirectory($directoryPath)
122+
Write-Verbose "Created directory: $directoryPath"
123+
}
124+
}
125+
126+
# delete existing file if requested
127+
if ($DeleteExistingFile -and [IO.File]::Exists($normalizedPath)) {
128+
129+
# verify path doesn't point to existing directory
130+
if ([IO.Directory]::Exists($normalizedPath)) {
131+
throw "Cannot create file: Path refers to an existing directory"
132+
}
133+
134+
if (-not (Remove-ItemWithFallback -Path $normalizedPath)) {
135+
136+
throw "Failed to delete existing file: $normalizedPath"
137+
}
138+
139+
Write-Verbose "Deleted existing file: $normalizedPath"
140+
}
141+
142+
# handle file creation if requested
143+
if ($CreateFile) {
144+
145+
# verify path doesn't point to existing directory
146+
if ([IO.Directory]::Exists($normalizedPath)) {
147+
throw "Cannot create file: Path refers to an existing directory"
148+
}
149+
150+
151+
# create empty file if it doesn't exist
152+
if (-not [IO.File]::Exists($normalizedPath)) {
153+
$null = [IO.File]::WriteAllText($normalizedPath, "")
154+
Write-Verbose "Created empty file: $normalizedPath"
155+
}
156+
}
157+
158+
# clean up trailing separators except for root paths
159+
while ([IO.Path]::EndsInDirectorySeparator($normalizedPath) -and
160+
$normalizedPath.Length -gt 4) {
161+
$normalizedPath = [IO.Path]::TrimEndingDirectorySeparator($normalizedPath)
162+
}
163+
164+
return $normalizedPath
165+
}
166+
167+
end {
168+
}
169+
}
170+
################################################################################
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
################################################################################
2+
<#
3+
.SYNOPSIS
4+
Find duplicate files by name and properties across specified directories.
5+
6+
.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.
10+
11+
.PARAMETER Paths
12+
One or more directory paths to search for duplicate files.
13+
14+
.PARAMETER DontCompareSize
15+
Skip file size comparison when determining duplicates.
16+
17+
.PARAMETER DontCompareModifiedDate
18+
Skip last modified date comparison when determining duplicates.
19+
20+
.EXAMPLE
21+
Find-DuplicateFiles -Paths "C:\Folder1","D:\Folder2" -DontCompareSize
22+
23+
.EXAMPLE
24+
Get-Item "C:\Folder1","D:\Folder2" | Find-DuplicateFiles
25+
#>
26+
function Find-DuplicateFiles {
27+
28+
[CmdletBinding()]
29+
[Alias("fdf")]
30+
31+
param(
32+
###############################################################################
33+
[Parameter(
34+
Mandatory = $true,
35+
Position = 0,
36+
ValueFromPipeline = $true,
37+
ValueFromPipelineByPropertyName = $true,
38+
HelpMessage = "One or more directory paths to search for duplicates"
39+
)]
40+
[ValidateNotNullOrEmpty()]
41+
[string[]] $Paths,
42+
###############################################################################
43+
[Parameter(
44+
Mandatory = $false,
45+
Position = 1,
46+
HelpMessage = "Skip file size comparison when grouping duplicates"
47+
)]
48+
[switch] $DontCompareSize,
49+
###############################################################################
50+
[Parameter(
51+
Mandatory = $false,
52+
Position = 2,
53+
HelpMessage = "Skip last modified date comparison when grouping duplicates"
54+
)]
55+
[switch] $DontCompareModifiedDate
56+
###############################################################################
57+
)
58+
59+
begin {
60+
61+
# normalize all input paths to full paths
62+
$normalizedPaths = @()
63+
$Paths | ForEach-Object {
64+
$normalizedPaths += (Expand-Path $_)
65+
}
66+
67+
# helper function to generate unique key for file comparison
68+
function Get-FileKey([System.IO.FileInfo]$file) {
69+
70+
# start with filename as base key
71+
$key = $file.Name
72+
73+
# add size to key if size comparison is enabled
74+
if (-not $DontCompareSize) {
75+
$key += "|$($file.Length)"
76+
}
77+
78+
# add modified date to key if date comparison is enabled
79+
if (-not $DontCompareModifiedDate) {
80+
$key += "|$($file.LastWriteTimeUtc.ToString('o'))"
81+
}
82+
83+
return $key
84+
}
85+
86+
# initialize generic list for better performance with large collections
87+
$allFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
88+
}
89+
90+
process {
91+
92+
# process each normalized path
93+
foreach ($path in $normalizedPaths) {
94+
95+
# verify directory exists before processing
96+
if ([System.IO.Directory]::Exists($path)) {
97+
98+
Write-Verbose "Scanning directory: $path"
99+
100+
# get all files using direct .NET IO methods for performance
101+
[System.IO.Directory]::GetFiles($path, "*.*",
102+
[System.IO.SearchOption]::AllDirectories) |
103+
ForEach-Object {
104+
$null = $allFiles.Add([System.IO.FileInfo]::new($_))
105+
}
106+
}
107+
else {
108+
Write-Warning "Directory not found: $path"
109+
}
110+
}
111+
}
112+
113+
end {
114+
115+
# group files by composite key and return groups with duplicates
116+
$allFiles |
117+
Group-Object -Property { Get-FileKey $_ } |
118+
Where-Object { $_.Count -gt 1 } |
119+
ForEach-Object {
120+
# create custom object for each group of duplicates
121+
[PSCustomObject]@{
122+
FileName = $_.Group[0].Name
123+
Files = $_.Group
124+
}
125+
}
126+
}
127+
}
128+
################################################################################

0 commit comments

Comments
 (0)