src/Public/Import-Media.ps1
|
Register-RenderKitFunction "Import-Media" function Import-Media { <# .SYNOPSIS Scans, filters, classifies, and optionally transfers media into a RenderKit project. .DESCRIPTION Runs the RenderKit import workflow in interactive wizard mode or parameter-driven mode. Without `-ScanAndFilter`, the command returns drive candidates or an interactive source selection. With `-ScanAndFilter`, it executes scan/filter, optional classification, optional transaction-safe transfer, and final reporting. Supports `-WhatIf` / `-Confirm` via `SupportsShouldProcess`. .PARAMETER SelectSource Uses interactive source selection instead of only listing drive candidates. .PARAMETER IncludeFixed Includes fixed disks in source candidate discovery. .PARAMETER IncludeUnsupportedFileSystem Includes drives with unsupported file systems in source candidate discovery. .PARAMETER ScanAndFilter Enables full import workflow (scan, filter, selection, optional classification/transfer). .PARAMETER SourcePath Explicit source folder path to scan (for example `E:\DCIM`). .PARAMETER FolderFilter Folder-name filters used during scan results filtering. .PARAMETER FromDate Start date for file timestamp filter. .PARAMETER ToDate End date for file timestamp filter. Must be equal to or later than `FromDate`. .PARAMETER Wildcard Wildcard patterns for file filtering (for example `*.mp4`, `*.wav`). .PARAMETER InteractiveFilter Prompts for additional filter criteria interactively. .PARAMETER PreviewCount Maximum number of rows shown in preview tables (1..500). .PARAMETER AutoSelectAll Automatically selects all matched files. .PARAMETER AutoConfirm Automatically confirms selected files for import. .PARAMETER Classify Enables classification into template/mapping destination folders. .PARAMETER ProjectRoot Target RenderKit project root for classification and transfer. .PARAMETER TemplateName Template name used for classification. .PARAMETER UnassignedHandling How files without mapping are handled: `Prompt`, `ToSort`, or `Skip`. .PARAMETER UnassignedFolderName Folder name used when unassigned files are routed to the "to sort" destination. .PARAMETER Transfer Enables phase 4 transaction-safe transfer after classification. .PARAMETER TransferHashAlgorithm Hash algorithm used for transfer integrity checks. Allowed values: `SHA256`, `SHA1`, `MD5`. .EXAMPLE Import-Media Starts interactive wizard mode (no parameters). .EXAMPLE Import-Media -SelectSource Shows interactive drive selection and returns selected source candidate. .EXAMPLE Import-Media -ScanAndFilter -SourcePath "E:\DCIM" -FolderFilter "100EOSR","101EOSR" -Wildcard "*.mp4","*.mov" -PreviewCount 50 Runs scan/filter with explicit path and preview settings. .EXAMPLE Import-Media -ScanAndFilter -SourcePath "E:\DCIM" -FromDate (Get-Date).AddDays(-2) -ToDate (Get-Date) -Classify -ProjectRoot "D:\Projects\ClientA_2026" -TemplateName "default" Runs scan/filter and classification for the given project and template. .EXAMPLE Import-Media -ScanAndFilter -SourcePath "E:\DCIM" -Classify -Transfer -ProjectRoot "D:\Projects\ClientA_2026" -TemplateName "default" -TransferHashAlgorithm SHA256 -WhatIf Simulates classified transfer with integrity hashing. .INPUTS None. You cannot pipe input to this command. .OUTPUTS System.Object Returns either drive candidate data (discovery mode) or a detailed import summary object (scan/filter mode). .LINK Get-RenderKitDriveCandidate .LINK Select-RenderKitDriveCandidate .LINK Get-Help Import-Media -Detailed .LINK https://github.com/djtroi/RenderKit #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = '"Media" is already singular (Latin plural of medium, but treated as uncountable in English).')] [CmdletBinding(SupportsShouldProcess)] param( [switch]$SelectSource, [switch]$IncludeFixed, [switch]$IncludeUnsupportedFileSystem, [switch]$ScanAndFilter, [string]$SourcePath, [string[]]$FolderFilter, [Nullable[datetime]]$FromDate, [Nullable[datetime]]$ToDate, [string[]]$Wildcard, [switch]$InteractiveFilter, [ValidateRange(1, 500)] [int]$PreviewCount = 30, [switch]$AutoSelectAll, [switch]$AutoConfirm, [switch]$Classify, [string]$ProjectRoot, [string]$TemplateName, [ValidateSet("Prompt", "ToSort", "Skip")] [string]$UnassignedHandling = "Prompt", [string]$UnassignedFolderName = "TO SORT", [switch]$Transfer, [ValidateSet("SHA256", "SHA1", "MD5")] [string]$TransferHashAlgorithm = "SHA256" ) $isWizardMode = ($PSBoundParameters.Count -eq 0) $wizardTransferSimulate = $false Write-RenderKitLog -Level Debug -Message "Import-Media started: WizardMode=$isWizardMode, ScanAndFilter=$($ScanAndFilter.IsPresent), SelectSource=$($SelectSource.IsPresent), Classify=$($Classify.IsPresent), Transfer=$($Transfer.IsPresent)." if ($isWizardMode) { $wizardConfig = Start-RenderKitImportInteractiveSetup ` -UnassignedHandling $UnassignedHandling if (-not $wizardConfig) { return $null } $ScanAndFilter = [bool]$wizardConfig.ScanAndFilter $SelectSource = $false $IncludeFixed = [bool]$wizardConfig.IncludeFixed $IncludeUnsupportedFileSystem = [bool]$wizardConfig.IncludeUnsupportedFileSystem $SourcePath = [string]$wizardConfig.SourcePath $FolderFilter = @($wizardConfig.FolderFilter) $InteractiveFilter = [bool]$wizardConfig.InteractiveFilter $AutoSelectAll = [bool]$wizardConfig.AutoSelectAll $AutoConfirm = [bool]$wizardConfig.AutoConfirm $ProjectRoot = [string]$wizardConfig.ProjectRoot $Classify = [bool]$wizardConfig.Class $Transfer = [bool]$wizardConfig.Transfer $UnassignedHandling = [string]$wizardConfig.UnassignedHandling } if (-not $ScanAndFilter) { if ($SelectSource) { return Select-RenderKitDriveCandidate ` -IncludeFixed:$IncludeFixed ` -IncludeUnsupportedFileSystem:$IncludeUnsupportedFileSystem } return Get-RenderKitDriveCandidate ` -IncludeFixed:$IncludeFixed ` -IncludeUnsupportedFileSystem:$IncludeUnsupportedFileSystem } if ($null -ne $FromDate -and $null -ne $ToDate -and $FromDate -gt $ToDate) { Write-RenderKitLog -Level Error -Message "-FromDate must be earlier than or equal to -ToDate." throw "-FromDate must be earlier than or equal to -ToDate." } $importStartedAt = Get-Date $resolvedSourcePath = Resolve-RenderKitImportSourcePath ` -SourcePath $SourcePath ` -SelectSource:$SelectSource ` -IncludeFixed:$IncludeFixed ` -IncludeUnsupportedFileSystem:$IncludeUnsupportedFileSystem if ([string]::IsNullOrWhiteSpace($resolvedSourcePath)) { Write-RenderKitLog -Level Warning -Message "No source was selected for scanning." return $null } if ($isWizardMode) { Show-RenderKitImportWizardStatus ` -Title "Import context before scan" ` -Data ([ordered]@{ ProjectRoot = $ProjectRoot SourcePath = $resolvedSourcePath IncludeFixed = [bool]$IncludeFixed IncludeUnsupportedFileSystem = [bool]$IncludeUnsupportedFileSystem InteractiveFilter = [bool]$InteractiveFilter }) } Write-Information "Phase 2: scanning source '$resolvedSourcePath'..." -InformationAction Continue Write-RenderKitLog -Level Info -Message "Phase 2: scanning source '$resolvedSourcePath'..." $catalog = @(Get-RenderKitImportFileCatalog -SourcePath $resolvedSourcePath) $criteria = New-RenderKitImportCriterion ` -FolderFilter $FolderFilter ` -FromDate $FromDate ` -ToDate $ToDate ` -Wildcard $Wildcard if ($InteractiveFilter) { $additionalCriteria = Read-RenderKitImportAdditionalCriterion if ($additionalCriteria) { $criteria = Merge-RenderKitImportCriterion ` -BaseCriteria $criteria ` -AdditionalCriteria $additionalCriteria } } $matchedFiles = @( Get-RenderKitImportFilteredFile ` -Files $catalog ` -Criteria $criteria | Sort-Object LastWriteTime, RelativePath ) if ($isWizardMode) { Show-RenderKitImportWizardStatus ` -Title "Scan + filter result" ` -Data ([ordered]@{ ScannedFiles = $catalog.Count MatchedFiles = $matchedFiles.Count SourcePath = $resolvedSourcePath }) } Show-RenderKitImportPreviewTable ` -Files $matchedFiles ` -PreviewCount $PreviewCount ` -Title "Phase 2 preview" $selectedFiles = @( Select-RenderKitImportFileSubset ` -Files $matchedFiles ` -AutoSelectAll:$AutoSelectAll ) if ($selectedFiles.Count -eq 0) { Write-RenderKitLog -Level Info -Message "No files selected for import." } else { Show-RenderKitImportPreviewTable ` -Files $selectedFiles ` -PreviewCount $PreviewCount ` -Title "Selected files" if ($isWizardMode) { while ($true) { Show-RenderKitImportWizardStatus ` -Title "Selection checkpoint" ` -Data ([ordered]@{ MatchedFiles = $matchedFiles.Count SelectedFiles = $selectedFiles.Count SelectionMode = if ($AutoSelectAll) { "Auto-select all" } else { "Manual selection" } }) $selectionReviewAction = Read-RenderKitImportSelectionReviewAction if ($selectionReviewAction -eq "Continue") { break } if ($selectionReviewAction -eq "Cancel") { $selectedFiles = @() Write-RenderKitLog -Level Info -Message "Import cancelled during selection review." break } $selectedFiles = @( Select-RenderKitImportFileSubset ` -Files $matchedFiles ) if ($selectedFiles.Count -eq 0) { Write-RenderKitLog -Level Info -Message "No files selected for import." break } Show-RenderKitImportPreviewTable ` -Files $selectedFiles ` -PreviewCount $PreviewCount ` -Title "Selected files (updated)" } } } $matchedTotalBytes = Get-RenderKitImportTotalByte -Files $matchedFiles $selectedTotalBytes = Get-RenderKitImportTotalByte -Files $selectedFiles $confirmed = $false if ($selectedFiles.Count -gt 0) { $confirmed = Confirm-RenderKitImportSelection ` -FileCount $selectedFiles.Count ` -TotalBytes $selectedTotalBytes ` -AutoConfirm:$AutoConfirm } if (-not $confirmed -and $selectedFiles.Count -gt 0) { Write-RenderKitLog -Level Info -Message "Import cancelled by user." } $shouldClassify = $Classify -or $Transfer if ($Transfer -and -not $Classify) { Write-RenderKitLog -Level Info -Message "Phase 4 requires classification. Phase 3 will run automatically." } $classificationResult = $null if ($shouldClassify -and $confirmed -and $selectedFiles.Count -gt 0) { Write-Information "Phase 3: classifying selected files..." -InformationAction Continue $effectiveUnassignedHandling = $UnassignedHandling $classificationResult = Get-RenderKitImportFileClassification ` -Files $selectedFiles ` -ProjectRoot $ProjectRoot ` -TemplateName $TemplateName ` -UnassignedHandling $effectiveUnassignedHandling ` -UnassignedFolderName $UnassignedFolderName Show-RenderKitImportClassificationPreview ` -Files $classificationResult.Files ` -PreviewCount $PreviewCount ` -Title "Phase 3 classification" } elseif ($shouldClassify -and $selectedFiles.Count -eq 0) { Write-RenderKitLog -Level Info -Message "Phase 3 skipped because no files were selected." } elseif ($shouldClassify -and -not $confirmed) { Write-RenderKitLog -Level Info -Message "Phase 3 skipped because import was not confirmed." } $classifiedFiles = @() if ($classificationResult) { $classifiedFiles = @($classificationResult.Files) } if ($isWizardMode -and $confirmed -and $selectedFiles.Count -gt 0) { $transferMode = Read-RenderKitImportTransferModeInteractive switch ($transferMode) { "Real" { $Transfer = $true $wizardTransferSimulate = $false } "Simulate" { $Transfer = $true $wizardTransferSimulate = $true } default { $Transfer = $false $wizardTransferSimulate = $false } } Show-RenderKitImportWizardStatus ` -Title "Transfer decision" ` -Data ([ordered]@{ TransferRequested = [bool]$Transfer TransferMode = if ($Transfer) { if ($wizardTransferSimulate) { "Simulation" } else { "Real" } } else { "No transfer" } ClassifiedFiles = if ($classificationResult) { $classificationResult.FileCount } else { 0 } }) } $transferResult = $null if ($Transfer -and $confirmed -and $selectedFiles.Count -gt 0) { if (-not $classificationResult) { Write-RenderKitLog -Level Error -Message "Phase 4 requires a Phase 3 classification result." throw "Phase 4 requires a Phase 3 classification result." } $simulateTransfer = [bool]$WhatIfPreference -or $wizardTransferSimulate $executeTransfer = $true if ($simulateTransfer) { Write-Information "Phase 4: transaction-safe transfer simulation." -InformationAction Continue } else { $executeTransfer = $PSCmdlet.ShouldProcess( $classificationResult.ProjectRoot, "Phase 4 transfer of $($classificationResult.Files.Count) classified file(s)" ) } if ($simulateTransfer -or $executeTransfer) { Write-Information "Phase 4: transaction-safe transfer..." -InformationAction Continue $transferResult = Invoke-RenderKitImportTransactionSafeTransfer ` -ClassifiedFiles $classificationResult.Files ` -ProjectRoot $classificationResult.ProjectRoot ` -HashAlgorithm $TransferHashAlgorithm ` -Simulate:$simulateTransfer if ($simulateTransfer -and $isWizardMode -and -not [bool]$WhatIfPreference) { $runRealTransfer = Read-RenderKitImportYesNo ` -Prompt "Simulation completed. Execute real transfer now?" ` -Default $false if ($runRealTransfer) { $executeRealTransfer = $PSCmdlet.ShouldProcess( $classificationResult.ProjectRoot, "Phase 4 REAL transfer of $($classificationResult.Files.Count) classified file(s)" ) if ($executeRealTransfer) { Write-Information "Phase 4: transaction-safe REAL transfer..." -InformationAction Continue $transferResult = Invoke-RenderKitImportTransactionSafeTransfer ` -ClassifiedFiles $classificationResult.Files ` -ProjectRoot $classificationResult.ProjectRoot ` -HashAlgorithm $TransferHashAlgorithm $wizardTransferSimulate = $false } else { Write-RenderKitLog -Level Info -Message "Real transfer skipped by ShouldProcess." } } else { Write-RenderKitLog -Level Info -Message "Real transfer cancelled. Keeping simulation result only." } } } else { Write-RenderKitLog -Level Info -Message "Phase 4 skipped by ShouldProcess." } } elseif ($Transfer -and $selectedFiles.Count -eq 0) { Write-RenderKitLog -Level Info -Message "Phase 4 skipped because no files were selected." } elseif ($Transfer -and -not $confirmed) { Write-RenderKitLog -Level Info -Message "Phase 4 skipped because import was not confirmed." } $importEndedAt = Get-Date $finalReport = New-RenderKitImportFinalReport ` -ImportStartedAt $importStartedAt ` -ImportEndedAt $importEndedAt ` -SourcePath $resolvedSourcePath ` -ScanFileCount $catalog.Count ` -MatchedFileCount $matchedFiles.Count ` -SelectedFileCount $selectedFiles.Count ` -SelectedTotalBytes $selectedTotalBytes ` -Classification $classificationResult ` -Transfer $transferResult Show-RenderKitImportFinalReport -Report $finalReport $revisionLogPath = $null $effectiveProjectRoot = $null if ($transferResult -and -not [string]::IsNullOrWhiteSpace([string]$transferResult.ProjectRoot)) { $effectiveProjectRoot = [string]$transferResult.ProjectRoot } elseif ($classificationResult -and -not [string]::IsNullOrWhiteSpace([string]$classificationResult.ProjectRoot)) { $effectiveProjectRoot = [string]$classificationResult.ProjectRoot } elseif (-not [string]::IsNullOrWhiteSpace($ProjectRoot)) { try { $effectiveProjectRoot = Resolve-RenderKitImportProjectRoot -ProjectRoot $ProjectRoot } catch { Write-RenderKitLog -Level Warning -Message "Phase 5 skipped: invalid project root '$ProjectRoot'." } } if ($confirmed -and [bool]$WhatIfPreference) { Write-RenderKitLog -Level Info -Message "Phase 5 skipped in WhatIf mode (simulation)." } elseif ($confirmed -and -not [string]::IsNullOrWhiteSpace($effectiveProjectRoot)) { try { $revisionLogPath = Write-RenderKitImportRevisionLog ` -ProjectRoot $effectiveProjectRoot ` -ImportStartedAt $importStartedAt ` -ImportEndedAt $importEndedAt ` -SourcePath $resolvedSourcePath ` -ScanFileCount $catalog.Count ` -MatchedFileCount $matchedFiles.Count ` -SelectedFileCount $selectedFiles.Count ` -SelectedTotalBytes $selectedTotalBytes ` -Filters $criteria ` -Classification $classificationResult ` -Transfer $transferResult ` -FinalReport $finalReport } catch { Write-RenderKitLog -Level Warning -Message "Phase 5 revision log could not be written: $($_.Exception.Message)" } } elseif ($confirmed) { Write-RenderKitLog -Level Warning -Message "Phase 5 skipped: no project root context was available." } return [PSCustomObject]@{ SourcePath = $resolvedSourcePath ImportStartedAt = $importStartedAt ImportEndedAt = $importEndedAt ScanFileCount = $catalog.Count MatchedFileCount = $matchedFiles.Count SelectedFileCount = $selectedFiles.Count MatchedTotalBytes = $matchedTotalBytes SelectedTotalBytes = $selectedTotalBytes MatchedTotalGB = [Math]::Round(([double]$matchedTotalBytes / 1GB), 3) SelectedTotalGB = [Math]::Round(([double]$selectedTotalBytes / 1GB), 3) Filters = $criteria Confirmed = $confirmed Classification = $classificationResult ClassifiedFileCount = if ($classificationResult) { $classificationResult.FileCount } else { 0 } AssignedFileCount = if ($classificationResult) { $classificationResult.AssignedCount } else { 0 } ToSortFileCount = if ($classificationResult) { $classificationResult.ToSortCount } else { 0 } SkippedFileCount = if ($classificationResult) { $classificationResult.SkippedCount } else { 0 } UnassignedFileCount = if ($classificationResult) { $classificationResult.UnassignedCount } else { 0 } Transfer = $transferResult ImportedFileCount = if ($transferResult) { $transferResult.ImportedFileCount } else { 0 } SimulatedFileCount = if ($transferResult) { $transferResult.SimulatedFileCount } else { 0 } FailedTransferFileCount = if ($transferResult) { $transferResult.FailedFileCount } else { 0 } TransferDurationSeconds = if ($transferResult) { $transferResult.DurationSeconds } else { 0 } TransferAverageSpeedMBps = if ($transferResult) { $transferResult.AverageSpeedMBps } else { 0 } FinalReport = $finalReport RevisionLogPath = $revisionLogPath Files = $selectedFiles ClassifiedFiles = $classifiedFiles } } |