Public/File/Expand-ArchivesInDirectory.ps1
<#
.SYNOPSIS Recursively extracts archives from specified directories and optionally deletes them post-extraction. .DESCRIPTION The Expand-ArchivesInDirectory function searches for various archive files within the given directories, extracts them using 7-Zip, and can delete the original archives if desired. It supports multiple archive formats and allows customization of extraction locations and duplicate file handling. .PARAMETER LiteralPath Specifies one or more root directories to start the recursive search for archives. .PARAMETER ExtractLocation Determines where the extracted files will be placed. Options are 'SameFolder' or 'Subfolder'. 'SameFolder' extracts all files to the same folder as the archive. 'Subfolder' extracts all files to a subfolder with the same name as the archive. .PARAMETER DeleteArchives If specified, the original archives will be deleted after successful extraction. .PARAMETER AutoRenameDuplicates If specified, automatically renames duplicate files during extraction to the same folder. .PARAMETER MaxThreads Specifies the maximum number of threads to use for parallel processing. Defaults to 16. .EXAMPLE Expand-ArchivesInDirectory -LiteralPath "C:\User\Downloads" -DeleteArchives Extracts all archives found in "C:\User\Downloads" and its subdirectories, then deletes the original archives after successful extraction. .EXAMPLE Expand-ArchivesInDirectory -LiteralPath "D:\Data", "E:\BackupArchives" -ExtractLocation SameFolder -AutoRenameDuplicates Extracts archives found in both "D:\Data" and "E:\BackupArchives" into their respective folders, automatically renaming any duplicate files. .EXAMPLE Expand-ArchivesInDirectory -LiteralPath "F:\Projects" -MaxThreads 8 Extracts all archives found in "F:\Projects" using up to 8 threads for parallel processing. .NOTES Author: Futuremotion Date: 10-03-2024 URL: https://github.com/futuremotiondev #> function Expand-ArchivesInDirectory { param ( [Parameter(Mandatory=$true)] [ValidateScript({ # Validate that the provided path exists and is a directory if (-not(Test-Path -LiteralPath $_ -PathType Container)) { throw "Passed folder does not exist. ($_)" } else { return $true } })] [ValidateNotNullOrEmpty()] [Alias("PSPath","Directory","Folder")] [String[]] $LiteralPath, # The root directory to start the recursive search for archives [ValidateSet('SameFolder','Subfolder', IgnoreCase = $true)] [String] $ExtractLocation = 'Subfolder', # Determines where extracted files will be placed [Switch] $DeleteArchives, # If set, deletes archives after extraction [Switch] $AutoRenameDuplicates, # If set, automatically renames duplicates during extraction [Int32] $MaxThreads = 16 # Maximum number of threads for parallel processing ) begin { # Locate the 7-Zip command line tool $7zCMD = Get-Command 7z.exe -CommandType Application -ErrorAction SilentlyContinue if(-not($7zCMD)){ $7zCMD = Get-Command 'C:\Program Files\7-Zip\7z.exe' -CommandType Application -ErrorAction SilentlyContinue if(-not($7zCMD)){ throw "Can't locate 7z.exe (7-Zip Command Line) in PATH or Installation Directory." } } # Initialize a list to store directories $DirectoryList = [System.Collections.Generic.List[String]]@() } process { foreach ($Path in $LiteralPath) { # Add valid directories to the list if (Test-Path -Path $Path -PathType Container) { $DirectoryList.Add($Path) } else { Write-Error "Passed folder does not exist on disk: $Path" -ErrorAction Continue } } } end { # Define parameters for searching archive files once $getArchivesSplat = @{ Recurse = $true Depth = 10 File = $true Include = '*.zip', '*.7z', '*.rar', '*.cab', '*.tar', '*.gz', '*.gzip', '*.tgz', '*.lzh', '*.rpm', '*.deb', '*.dmg' } # Process each directory in parallel $DirectoryList | ForEach-Object -Parallel { # Use variables from the parent scope $7zCMD = $Using:7zCMD $DeleteArchives = $Using:DeleteArchives $ExtractLocation = $Using:ExtractLocation $getArchivesSplat = $Using:getArchivesSplat $Directory = $_ $getArchivesSplat.Path = $Directory # Retrieve all archive files in the directory $Archives = Get-ChildItem @getArchivesSplat # If no archives are found, skip iteration if($Archives.Length -eq 0){ return } foreach ($Archive in $Archives) { # Extract file and directory information $ArchivePath = $Archive.FullName $ArchiveNoExtension = [System.IO.Path]::GetFileNameWithoutExtension($ArchivePath) $ArchiveFolder = [System.IO.Directory]::GetParent($ArchivePath).FullName # Determine extraction parameters based on user settings $OutputDir = if ($ExtractLocation -eq 'Subfolder') { Join-Path -Path $ArchiveFolder -ChildPath $ArchiveNoExtension } else { $ArchiveFolder } $7zParams = "x", $ArchivePath, "-o$OutputDir", "-y" if ($ExtractLocation -eq 'SameFolder' -and $AutoRenameDuplicates) { $7zParams += "-aou" } # Execute the extraction command & $7zCMD $7zParams 2>&1 | Out-Null if($LASTEXITCODE -eq 0){ # Log successful extraction Write-Host "Successfully extracted $ArchivePath" -f Blue if($DeleteArchives){ try { # Attempt to delete the archive after extraction Remove-Item -LiteralPath $ArchivePath -Force | Out-Null Write-Host "Successfully deleted $ArchivePath" -f Blue } catch { Write-Error "Failed to delete archive after extraction ($ArchivePath)" -ErrorAction Continue } } } else { # Log extraction failure Write-Error "Failed to extract $ArchivePath" -ErrorAction Continue } } } -ThrottleLimit $MaxThreads } } |