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 {
    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 = $csvString.Remove(0, $csvString.IndexOf('"'))
    }

    # 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 [PSCustomObject]@{}
    }

    # 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 [PSCustomObject]@{}
    }

    # 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 [PSCustomObject]@{}
    }

    # 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) {
                # 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 {
    param (
        # The object containing properties to convert to CSV
        [Parameter(Mandatory = $true)]
        [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 ""
    }

    # 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 ""
    }

    # 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 ""
    }

    # 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 {
    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 ""
    }

    # 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 ""
    }

    # Return the result as a CSV string
    $result = ConvertTo-CsvString $csvObject
    if (!$result) {
        Write-Error "Failed to convert CSV string to CSV object."
        return ""
    }
    return $result
}