Private/Export-IOResult.ps1
|
function Export-IOResult { [CmdletBinding()] param( [Parameter(Mandatory)] [AllowNull()] [AllowEmptyCollection()] [object[]]$Data, [string]$ToCsv, [string]$CommandName = 'IdentityOps' ) if (-not $Data -or $Data.Count -eq 0) { Write-IOLog "No results found." -Level Info -Component $CommandName return } Write-IOLog "Found $($Data.Count) result(s)." -Level Info -Component $CommandName # ── CSV export ──────────────────────────────────────────────────────────── if ($ToCsv) { $resolvedPath = $null try { $fileName = Split-Path $ToCsv -Leaf $parentDir = Split-Path $ToCsv -Parent # Validate filename characters $badChars = [System.IO.Path]::GetInvalidFileNameChars() foreach ($c in $badChars) { if ($fileName.Contains($c)) { throw "Filename contains invalid character: '$c'" } } # Ensure .csv extension if (-not $fileName.EndsWith('.csv', [System.StringComparison]::OrdinalIgnoreCase)) { $fileName = "${fileName}.csv" } # Resolve or create parent directory if ([string]::IsNullOrWhiteSpace($parentDir)) { $parentDir = (Get-Location).Path } else { if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force -ErrorAction Stop | Out-Null } $parentDir = (Resolve-Path $parentDir -ErrorAction Stop).Path } $resolvedPath = Join-Path $parentDir $fileName # Block UNC paths to prevent data exfiltration to network shares if ($resolvedPath -match '^\\\\') { throw "Cannot write to UNC/network paths for security reasons: $resolvedPath" } # Prevent overwriting system files (case-insensitive) $normalizedPath = $resolvedPath.ToLowerInvariant() if ($normalizedPath -match '^[a-z]:\\(windows|program files|program files \(x86\)|programdata\\microsoft)') { throw "Cannot write to protected system directory: $resolvedPath" } } catch { throw [System.Management.Automation.ErrorRecord]::new( [System.ArgumentException]::new("Invalid CSV path '$ToCsv': $($_.Exception.Message)"), 'IO_InvalidCsvPath', [System.Management.Automation.ErrorCategory]::InvalidArgument, $ToCsv ) } try { $Data | Export-Csv -Path $resolvedPath -NoTypeInformation -Encoding utf8BOM -Force Write-Host " [CSV] Exported $($Data.Count) record(s) -> $resolvedPath" -ForegroundColor Green } catch { throw [System.Management.Automation.ErrorRecord]::new( [System.IO.IOException]::new("Failed to write CSV: $($_.Exception.Message)"), 'IO_CsvWriteFailed', [System.Management.Automation.ErrorCategory]::WriteError, $resolvedPath ) } } # Always output to pipeline return $Data } |