Csv.psm1
<#
.SYNOPSIS This function converts a CSV string to a CSV object. .DESCRIPTION This function takes in a CSV representation as a string and returns a custom object with properties that correspond to the CSV. .PARAMETER csvString The string containing the CSV representation. #> function ConvertTo-CsvObject { [CmdletBinding()] [OutputType([Object[]])] param ( # The string containing the CSV representation. [Parameter(Mandatory=$true)] [String]$csvString, # If this parameter is set, strings consisting solely of digits with an optional '-' in front # which are shorter than the maximum length will be converted to integer variables. [Parameter(Mandatory=$false)] [ValidateNotNull()] [ValidateScript({ $_ -gt 0 -and $_ -le 6})] [Int]$convertIntegersMaxLength, # Select if strings "true" and "false" are processed into boolean $true and $false [Parameter(Mandatory=$false)] [Switch]$convertBooleans = [Switch]::Present, # Select if strings should be trimmed of starting and trailing whitespace. [Parameter(Mandatory=$false)] [Switch]$trimStrings ) # Remove extra first line for Excel if present if ($csvString -like "sep=,*") { $csvString = [Regex]::new("sep=,[\s]+([\s\S]*)").Match($csvString).Groups[1].Value } # Output CSV string to temporary file # Try to create a temporary file try { $tempFile = New-TemporaryFile } catch { Write-Error "Failed to create temporary file for converting CSV string to CSV object. $($_.Exception.Message)" return $null } # Try to set the content of the temporary file try { $csvString | Set-Content -NoNewline -Path $tempFile.FullName } catch { Write-Error "Failed to set content of temporary file for converting CSV string to CSV object. $($_.Exception.Message)" return $null } # Try to import CSV file to object try { $csvObject = Import-Csv -Path $tempFile.FullName } catch { Write-Error "Failed to import temporary file into CSV object. $($_.Exception.Message)" return $null } # Convert single objects to array $csvObject = ConvertTo-Array $csvObject # Process the CSV if ($convertIntegersMaxLength -or $convertBooleans -or $trimStrings) { foreach ($csv in $csvObject) { foreach ($propertyName in $csv.PSObject.Properties.Name) { if ($null -eq $csv.$propertyName) { continue } # Check for string if ($csv.$propertyName.GetType() -eq [String]) { # Check if string should be converted to an integer if ($convertIntegersMaxLength -and $csv.$propertyName.length -le $convertIntegersMaxLength -and [Regex]::new("^-?[0-9]{1,$($convertIntegersMaxLength)}$").IsMatch($csv.$propertyName)) { $csv.$propertyName = [Int]$csv.$propertyName } # Check if string should be converted to boolean elseif ($convertBooleans -and $csv.$propertyName.ToLower().Trim() -in @("true", "false")) { $csv.$propertyName = $csv.$propertyName.Trim() -eq "true" } # Check if string should be trimmed elseif ($trimStrings) { $csv.$propertyName = $csv.$propertyName.Trim() } } } } } return $csvObject } <# .SYNOPSIS This function converts a CSV object to a CSV string. .DESCRIPTION This function takes in a custom object and returns the CSV representation of that object as a single string. .PARAMETER csvObject The object containing properties to convert to CSV. #> function ConvertTo-CsvString { [CmdletBinding()] [OutputType([String])] param ( # The object containing properties to convert to CSV [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [ValidateNotNull()] [PSObject[]]$csvObject ) # Output CSV object to temporary file # Try to create a temporary file try { $tempFile = New-TemporaryFile } catch { Write-Error "Failed to create temporary file for converting CSV object to CSV string. $($_.Exception.Message)" return $null } # Try to export CSV object to temporary file try { $csvObject | Export-Csv -Path $tempFile.FullName -NoTypeInformation } catch { Write-Error "Failed to export CSV object into temporary file. $($_.Exception.Message)" return $null } # Try to get content of temporary file to CSV string try { $csvString = Get-Content $tempFile.FullName -Raw } catch { Write-Error "Failed to get content of temporary file to CSV string. $($_.Exception.Message)" return $null } # Add extra line so that Excel opens properly $csvString = "sep=,`r`n" + $csvString return $csvString } <# .SYNOPSIS This function filters a CSV string, keeping rows which have $true as the value for the specified columns. #> function Get-FilteredCsvString { [CmdletBinding()] [OutputType([Object[]])] param ( # The CSV string to filter. [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String]$csv, # The columns used to filter the CSV string. [Parameter(Mandatory=$false)] [AllowNull()] [AllowEmptyString()] [String]$columns, # Select if the filtering is 'and' or 'or'. [Parameter(Mandatory=$false)] [ValidateSet("and", "or")] [String]$filtering = "and" ) # Convert the CSV string to CSV object $csvObject = ConvertTo-CsvObject $csv if (!$csvObject) { Write-Error "Failed to convert input CSV string to CSV object." return $null } # Filter the CSV by the column names if ($columns) { $columnNames = ConvertTo-Array ($columns.Split(",") | ForEach-Object { $_.Trim() }) # Run through each entry in the CSV $csvObject = $csvObject | ForEach-Object -Process { $csvEntry = $_ # Perform a count of the number of properties which are true $filterResult = ConvertTo-Array ($columnNames | ForEach-Object -Process { if ($csvEntry.$_ -eq $true) { $true } }) # All the properties are true, the entry stays if ($filtering -eq "and" -and $filterResult.length -eq $columnNames.length) { $csvEntry } # At least one property is true, the entry stays elseif ($filtering -eq "or" -and $filterResult.length -gt 0) { $csvEntry } } } # No items left after filtering if (!$csvObject) { return $null } # Return the result as a CSV string $result = ConvertTo-CsvString $csvObject if (!$result) { Write-Error "Failed to convert CSV string to CSV object." return $null } return $result } <# .SYNOPSIS This function returns the result of combining CSVs. .DESCRIPTION This function returns the result of combining CSVs. The resulting CSV contains all the columns which were present in any of original CSVs. The combined CSV will contain the columns in sorted order. #> function Get-CombinedCsvString { [CmdletBinding()] [OutputType([String])] param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [String[]]$csvStrings ) # Remove the sep=, from the beginning if present $csvStrings = $csvStrings | ForEach-Object -Process { if ($_ -like "sep=,*") { [Regex]::new("sep=,[\s]+([\s\S]*)").Match($_).Groups[1].Value } else { $_ } } # Collate all the column names $columnNames = @{} foreach ($csvString in $csvStrings) { $columns = $csvString.Split("`r`n")[0].Split(",") | ForEach-Object { if ([Regex]::new("^`"(.)*`"$").IsMatch($_)) { [Regex]::new("^`"(.*)`"$").Match($_).Groups[1].Value } else { $_ } } foreach ($column in $columns) { $columnNames[$column] = $true } } $columnNames = $columnNames.Keys | Sort-Object # Convert each of the CSV strings to CSV objects $csvObjects = @() foreach ($csvString in $csvStrings) { $csvObject = ConvertTo-CsvObject -csvString $csvString if (!$csvObject) { Write-Error "Not all CSV strings to combine could be converted to CSV objects." return $null } # Add in additional columns if they don't exist for each row foreach ($csvRow in $csvObject) { foreach ($columnName in $columnNames) { if ($columnName -notIn $csvRow.PSObject.Properties.Name) { $csvRow | Add-Member -NotePropertyName $columnName -NotePropertyValue $null -Force } } } $csvObjects += $csvObject } # Combine the CSV objects $combinedCsvObject = @() foreach ($csvObject in $csvObjects) { foreach ($csvRow in $csvObject) { $combinedCsvObject += $csvRow } } # Convert the combined CSV into a CSV string $combinedCsvString = ConvertTo-CsvString -csvObject $combinedCsvObject if ([String]::IsNullOrWhiteSpace($combinedCsvString)) { Write-Error "Failed to convert combined CSV object into CSV string." return $null } # Return the combined CSV string return $combinedCsvString } |