Public/Select-IntegrityReportFile.ps1
|
function Select-IntegrityReportFile { <# .SYNOPSIS Reads a Compare-FileShareIntegrity CSV report and outputs filtered file paths. .DESCRIPTION Parses the CSV report, filters rows by status and/or path pattern, and outputs a list of RelativePath strings suitable for piping to Repair-FileShareIntegrity. Supports four path-selection modes (mutually exclusive, optional): -FilterPath — literal substring match (no regex). -FilterRegex — regex pattern match. -IncludePaths — explicit list of paths. -Interactive — Out-GridView / Out-ConsoleGridView dialog. .PARAMETER ReportPath Path to the CSV integrity report. .PARAMETER Status Status values to include. Default: SizeMismatch, ByteMismatch, HashMismatch, MissingOnDestination. .PARAMETER FilterPath Literal substring to match against RelativePath (case-insensitive). .PARAMETER FilterRegex Regex pattern to match against RelativePath. .PARAMETER IncludePaths Explicit list of relative file paths to include. .PARAMETER Interactive Opens a multi-select dialog. .EXAMPLE Select-IntegrityReportFile -ReportPath '.\data_integrity-report.csv' -Status ByteMismatch .EXAMPLE Select-IntegrityReportFile -ReportPath '.\report.csv' -Status ByteMismatch -FilterRegex '\.xlsx$' .OUTPUTS [string[]] Relative file paths. #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter(Mandatory)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string]$ReportPath, [Parameter()] [ValidateSet('SizeMismatch', 'ByteMismatch', 'HashMismatch', 'MissingOnDestination', 'MissingOnSource', 'CheckError', 'OK')] [string[]]$Status = @('SizeMismatch', 'ByteMismatch', 'HashMismatch', 'MissingOnDestination'), [Parameter()] [string]$FilterPath, [Parameter()] [string]$FilterRegex, [Parameter()] [string[]]$IncludePaths, [Parameter()] [switch]$Interactive ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # ── Validate parameter combinations ────────────────────────────────────── $filterModes = @( ($null -ne $FilterPath -and $FilterPath -ne ''), ($null -ne $FilterRegex -and $FilterRegex -ne ''), ($null -ne $IncludePaths -and $IncludePaths.Count -gt 0), $Interactive.IsPresent ) | Where-Object { $_ } if (($filterModes | Measure-Object).Count -gt 1) { throw 'Only one of -FilterPath, -FilterRegex, -IncludePaths, or -Interactive may be specified.' } # ── Parse the report ───────────────────────────────────────────────────── $headerLine = [System.IO.File]::ReadLines($ReportPath) | Select-Object -First 1 $delimiter = if ($headerLine -match ';') { ';' } else { ',' } $allRows = @(Import-Csv -Path $ReportPath -Delimiter $delimiter) if ($allRows.Count -eq 0) { Write-Host 'Report is empty. Nothing to output.' -ForegroundColor Yellow return } Write-Host "Loaded $($allRows.Count) row(s) from report." -ForegroundColor Cyan # ── Filter by status ───────────────────────────────────────────────────── $filtered = @($allRows | Where-Object { $_.Status -in $Status }) if ($filtered.Count -eq 0) { Write-Host "No rows matching status: $($Status -join ', ')." -ForegroundColor Yellow return } Write-Host " $($filtered.Count) row(s) matching status: $($Status -join ', ')." -ForegroundColor Cyan # ── Apply path filter ──────────────────────────────────────────────────── if ($FilterPath) { $filtered = @($filtered | Where-Object { $_.RelativePath.IndexOf($FilterPath, [System.StringComparison]::OrdinalIgnoreCase) -ge 0 }) Write-Host " $($filtered.Count) row(s) matching path '$FilterPath'." -ForegroundColor Cyan } elseif ($FilterRegex) { $filtered = @($filtered | Where-Object { $_.RelativePath -match $FilterRegex }) Write-Host " $($filtered.Count) row(s) matching regex '$FilterRegex'." -ForegroundColor Cyan } elseif ($IncludePaths) { $includeSet = [System.Collections.Generic.HashSet[string]]::new( [string[]]$IncludePaths, [System.StringComparer]::OrdinalIgnoreCase ) $filtered = @($filtered | Where-Object { $includeSet.Contains($_.RelativePath) }) $notFound = @($IncludePaths | Where-Object { $_ -notin ($filtered | ForEach-Object { $_.RelativePath }) }) if ($notFound.Count -gt 0) { Write-Warning "$($notFound.Count) path(s) not found in report (or don't match status filter)." } Write-Host " $($filtered.Count) row(s) from explicit path list." -ForegroundColor Cyan } elseif ($Interactive) { $displayItems = $filtered | ForEach-Object { [PSCustomObject]@{ RelativePath = $_.RelativePath Status = $_.Status SourceSize = $_.SourceSize DestSize = $_.DestSize } } $gridCmd = Get-Command Out-ConsoleGridView -ErrorAction SilentlyContinue if (-not $gridCmd) { $gridCmd = Get-Command Out-GridView -ErrorAction SilentlyContinue } if (-not $gridCmd) { throw 'Interactive mode requires Out-ConsoleGridView (Install-Module Microsoft.PowerShell.ConsoleGuiTools) or Out-GridView.' } Write-Host " Opening interactive selector ($($gridCmd.Name))..." -ForegroundColor Cyan $picked = $displayItems | & $gridCmd.Name -Title 'Select files (Space to toggle, Enter to confirm)' -OutputMode Multiple if (-not $picked -or @($picked).Count -eq 0) { Write-Host ' No files selected.' -ForegroundColor Yellow return } $pickedSet = [System.Collections.Generic.HashSet[string]]::new( [string[]]@($picked | ForEach-Object { $_.RelativePath }), [System.StringComparer]::OrdinalIgnoreCase ) $filtered = @($filtered | Where-Object { $pickedSet.Contains($_.RelativePath) }) Write-Host " $($filtered.Count) file(s) selected interactively." -ForegroundColor Cyan } # ── Output ─────────────────────────────────────────────────────────────── if ($filtered.Count -eq 0) { Write-Host 'No files matched. Nothing to output.' -ForegroundColor Yellow return } Write-Host "$($filtered.Count) file path(s) output." -ForegroundColor Green $filtered | ForEach-Object { $_.RelativePath } } |