nacha-report.ps1


<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 2687ebd5-b9f5-403a-bf2b-13fed20fd6cd
 
.AUTHOR Lawrence Billinghurst
 
.COMPANYNAME TriFused
 
.COPYRIGHT 2024
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>



#############################################################################
# TriFused - Nacha-Report
#
# NAME: Nacha-Report.ps1
#
# AUTHOR: Lawrence Billinghurst
# DATE: 3/30/2024
# EMAIL: larry@trifused.com
#
# VERSION HISTORY
# 1.0 2024-03-30 Initial Version.
# > used field mapping from Joshua Nasiatka - Verify-ACH.ps1
# Ref: https://github.com/jossryan/ACH-Verify-Tool
#
# ACH test data generator - https://yawetse.github.io/nachie/
# #############################################################################

<#
.SYNOPSIS
    This script reads a NACH file and outputs a summeary report with out any sensitive account informaion
 
.DESCRIPTION
    NACHA - NACHA (National Automated Clearing House Association) is the organization that manages the development,
     administration, and governance of the ACH Network in the United States. The ACH Network is a payment system that
     allows for the electronic transfer of funds between banks and credit unions.
 
    The NACHA file format adheres to a structure where each line, referred to as a “record,” contains exactly 94 characters,
    making also know as fixed-width ASCII file format. These records are organized into various “fields”, each occupying a
    predetermined position within the line.
 
    A NACHA file contains 6 different types of records:
 
    Type 1. **File Header**: Starts the file, with details like company name and file creation date.
    Type 5. **Batch Header**: Begins a group of transactions, indicating the payment type and originator.
    Type 6. **Entry Detail**: Represents individual financial transactions, detailing account numbers and amounts.
    Type 7. **Addenda**: Optional, provides extra information for a transaction.
    Type 8. **Batch Control**: Ends a batch, summarizing its transactions and total amount.
    Type 9. **File Control**: Concludes the file, summarizing all batches and entries.
 
.PARAMETER ParameterName
    -nachaFilePath C:\FolderA\FolderB\mynachafile.txt (Any extension will work)
 
.EXAMPLE
    .\Nacha-Report.ps1 -nachaFielPath
 
.NOTES
    Additional information about the script, like its version, author, or history.
 
    # #############################################################################
    # VERSION HISTORY
    # 1.0 2024-03-30 Initial Version.
    # > used field mapping from Joshua Nasiatka - Verify-ACH.ps1 // Thanks!!
    # Ref: https://github.com/jossryan/ACH-Verify-Tool
    #
    # ACH test data generator - https://yawetse.github.io/nachie/
    # #############################################################################
 
    Report Format
        --------->>> NACHA File Report <<<---------
        NACHA File Name: ach-test-file.txt
        NACHA File Date: January 06, 2015
        NACHA File Time: 12:13 PM
 
        1, [File Date-YYMMDD], [File Time-HHmm], [Destination Name], [Org Name]
        5, [Batch Number], [Transaction Description], [Effective Date]
            6, [Trans Code], {[Trace Number]}, [Reciver Name], [Amount]
            6, [Trans Code], {[Trace Number]}, [Reciver Name], [(Amount) <--debit]
            7, [Addenda Type Code], [Payment Related Information], [Addenda Sequence Number], [Entry Detail Sequence Number]
        8, [Batch Number], [Lines in Batch], [(Debit Total)],[Credit Total]
        9, [Batch Count],[Block Count], [Entry Count], [(Debit Total)],[Credit Total]
        --------->>> NACHA File Report End <<<---------
 
 
.LINK
    A link to more information or documentation related to the script.
 
#>


# Script logic starts here

param (
    [string]$nachaFilePath = "c:\support\ach-test-file.txt",
    [switch]$ShowTrace6
)

    [System.Collections.ArrayList]$ACHContents = @()

# Check if the file path is provided
if (-not $nachaFilePath) {
    Write-Host "Usage: ./ParseNACHA.ps1 -nachaFilePath <Path to NACHA file>"
    exit 1
}

# Check if the file exists
if (-not (Test-Path $nachaFilePath)) {
    Write-Host "Error: File not found."
    exit 1
}


Function ReadACHLine ($line) {

    # Get record type
    # 1 File Header Record
    # 5 Company/Batch Header Record
    # 6 Entry Detail Record (CCD/PPD Entries)
    # 7 Addenda Record
    # 8 Batch Control Record
    # 9 File Control Record
    # used field mapping from Joshua Nasiatka - Verify-ACH.ps1 script

    $record_type = $line.substring(0,1).trim()
    if ($line -ne '9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999') {
     }

    # 1 - FILE HEADER RECORD
    if ($record_type -eq '1') {
        $record_details = [PSCustomObject]@{
            'record_type'                      = $line.substring(0,1).trim()
            'priority_code'                    = $line.substring(1,2).trim()
            'immediate_destination'            = $line.substring(3,10).trim()
            'immediate_origin'                 = $line.substring(13,10).trim()
            'file_creation_date'               = $line.substring(23,6).trim()
            'file_creation_time'               = $line.substring(29,4).trim()
            'file_id_modifier'                 = $line.substring(33,1).trim()
            'record_size'                      = $line.substring(34,3).trim()
            'blocking_factor'                  = $line.substring(37,2).trim()
            'format_code'                      = $line.substring(39,1).trim()
            'immediate_destination_name'       = $line.substring(40,23).trim()
            'immediate_origin_name'            = $line.substring(63,23).trim()
            'reference_code'                   = $line.substring(86,8).trim()
        }
        
        # ##### Add Line to Output Record
        $ACHContents.Add($record_details) |Out-Null
        # #####
        
    # 5 - COMPANY/BATCH HEADER RECORD
    } elseif ($record_type -eq '5') {
        $record_details = [PSCustomObject]@{
            'record_type'                      = $line.substring(0,1).trim()
            'service_class_code'               = $line.substring(1,3).trim()
            'company_name'                     = $line.substring(4,16).trim()
            'company_discretionary_data_5'       = $line.substring(20,20).trim()
            'company_identification'           = $line.substring(40,10).trim()
            'standard_entry_class_code'        = $line.substring(50,3).trim()
            'company_entry_description'        = $line.substring(53,10).trim()
            'company_descriptive_date'         = $line.substring(63,6).trim()
            'effective_entry_date'             = $line.substring(69,6).trim()
            'settlement_date'                  = $line.substring(75,3).trim()
            'originator_status_code'           = $line.substring(78,1).trim()
            'originating_dfi_identification'   = $line.substring(79,8).trim()
            'batch_number'                     = $line.substring(87,7).trim()
        }
    
        # ##### Add Line to Output Record
        $ACHContents.Add($record_details) |Out-Null
        # #####
  
    # 6 - ENTRY DETAIL RECORD (CCD/PPD ENTRIES)
    } elseif ($record_type -eq '6') {

        $record_details  = [PSCustomObject]@{
            'record_type'                      = $line.substring(0,1).trim()
            'transaction_code'                 = $line.substring(1,2).trim()
            'receiving_dfi_identification'     = $line.substring(3,8).trim()
            'check_digit'                      = $line.substring(11,1).trim()
            'dfi_account_number'               = $line.substring(12,17).trim()
            'amount'                           = $(try{($line.substring(29,10).trim())/100}catch{$line.substring(29,10).trim()})
            'individual_identification_number' = $line.substring(39,15).trim()
            'individual_name'                  = $line.substring(54,22).trim()
            'company_discretionary_data_6'     = $line.substring(76,2).trim()
            'addenda_record_indicator'         = $line.substring(78,1).trim()
            'trace_number'                     = $line.substring(79,15).trim()
        }

        # ##### Add Line to Output Record
        $ACHContents.Add($record_details) |Out-Null
        # #####
    
    # 7 - ADDENDA RECORD
    } elseif ($record_type -eq '7') {
            # Write-Output ">>>>>>>> Record 7 - Addenda Record"
        $record_details = [PSCustomObject]@{
            'record_type'                      = $line.substring(0,1).trim()
            'addenda_type_code'                = $line.substring(1,2).trim()
            'addenda_related'                  = $line.substring(3,80).trim()
            'addenda_sequence_number'          = $line.substring(83,4).trim()
            'entry_detail_sequence_number'     = $line.substring(87,7).trim()
        }

        # ##### Add Line to Output Record
        $ACHContents.Add($record_details) |Out-Null
        # #####

    # 8 - BATCH CONTROL RECORD
    } elseif ($record_type -eq '8') {
                # Write-Output ">>>>>>>> Record 8 - Batch Control Record"
        $record_details = [PSCustomObject]@{
            'record_type'                      = $line.substring(0,1).trim()
            'service_class_code'               = $line.substring(1,3).trim()
            'entry_addenda_count_8'              = $line.substring(4,6).trim()
            'entry_hash'                       = $line.substring(10,10).trim()
            'total_debit_entry'                = $(try{($line.substring(20,12).trim())/100}catch{$line.substring(20,12).trim()})
            'total_credit_entry'               = $(try{($line.substring(32,12).trim())/100}catch{$line.substring(32,12).trim()})
            'company_identification'           = $line.substring(44,10).trim()
            'message_authorization_code'       = $line.substring(54,19).trim()
            'reserved_8'                         = $line.substring(73,6).trim()
            'originating_dfi_identification'   = $line.substring(79,8).trim()
            'batch_number'                     = $line.substring(87,7).trim()
        }
        
        # ##### Add Line to Output Record
        $ACHContents.Add($record_details) |Out-Null
        # #####
  
    # 9 - FILE CONTROL RECORD
    } elseif ($record_type -eq '9') {
        if ($line -ne '9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999') {
            $record_details = [PSCustomObject]@{
                'record_type'                      = $line.substring(0,1).trim()
                'batch_count'                      = $line.substring(1,6).trim()
                'block_count'                      = $line.substring(7,6).trim()
                'entry_addenda_count_9'              = $line.substring(13,8).trim()
                'entry_hash'                       = $line.substring(21,10).trim()
                'total_debit_entry_in_file'        = $(try{($line.substring(31,12).trim())/100}catch{$line.substring(31,12).trim()})
                'total_credit_entry_in_file'       = $(try{($line.substring(43,12).trim())/100}catch{$line.substring(43,12).trim()})
                'reserved_9'                         = $line.substring(55,39).trim()
            }

            # ##### Add Line to Output Record
            $ACHContents.Add($record_details) |Out-Null
            # #####
 
        } else {
            #Write-Output ">>>End of File"
            $script:nine_record_counter += 1
        }

    # NO OTHER RECORD TYPES
    } else {
        Write-Warning "Invalid record, skipping..."
        return
    }
    
}

############################################################################################################################################

# Load the nacha file in memory
$nachaFileContent = Get-Content $nachaFilePath

#clear Output varable
[System.Collections.ArrayList]$ACHContents = @()

# Loop through each line and parse the contents
foreach ($line in $nachaFileContent) {
       ReadACHLine($line)
       }

Write-Host "NACHA File Parsing completed.`r`n"

# Report Build
$NachaFileTime =""
$FinalReport =  "--------->>> NACHA File Report <<<---------`r`n" # Clear and Start building report
$NachafileName = Split-Path -Path $nachaFilePath -Leaf
$FinalReport = $FinalReport + "NACHA File Name: " + $NachafileName + "`r`n"

$ACHContents | ForEach-Object {
    $currentObject = $_
    
    switch ($currentObject.record_type) {
        "1" {
            $ReportOut = "$($currentObject.record_type), $($currentObject.file_creation_date), $($currentObject.file_creation_time), $($currentObject.immediate_destination_name), $($currentObject.immediate_origin_name)"
            $NachaFileDate=$($currentObject.file_creation_date)
            $NachaFileTime=$($currentObject.file_creation_time)
            # Parse the date string into a DateTime object
            $parsedDate = [DateTime]::ParseExact($NachaFileDate, "yymmdd", $null)
            $parsedTime = [DateTime]::ParseExact($NachaFileTime, "HHmm", $null)
            # Convert the DateTime object into a long date format string and add to report
            $FinalReport += "NACHA File Date: " + $parsedDate.ToString("MMMM dd, yyyy") +  "`r`n" # Add Date to Report
            $FinalReport += "NACHA File Time: " + $parsedTime.ToString("hh:mm tt") + "`r`n" # Add Time to Report
        }
         "5" {
              $ReportOut = " $($currentObject.record_type), $($currentObject.batch_number), $($currentObject.company_entry_description), $($currentObject.effective_entry_date)"
         }
         "6" {
            $TransactionCode = $($currentObject.transaction_code)
            switch ($TransactionCode){
                { $_ -in "22", "32" } {$formattedamount =  "{0:N2}" -f $($currentObject.amount)}
                { $_ -in "27", "37" } {$formattedamount =  "({0:N2})" -f $($currentObject.amount)}
            }

                #Show Trace Switch Logic

                $ReportOut = " $($currentObject.record_type), $TransactionCode"
                if ($ShowTrace6) {
                    $ReportOut += ", $($currentObject.trace_number)"
                }
                $ReportOut += ", $($currentObject.individual_name), $formattedamount"

            #$ReportOut = " $($currentObject.record_type), $TransactionCode, $($currentObject.trace_number), $($currentObject.individual_name), $formattedamount"
        }
         "7" {
            $ReportOut =  " $($currentObject.record_type), $($currentObject.addenda_type_code), $($currentObject.addenda_related), $($currentObject.addenda_sequence_number), $($currentObject.entry_detail_sequence_number)"
        }
        "8" {
            $formattedDebitTotal =  "({0:N2})" -f $($currentObject.total_debit_entry)
            $formattedCreditTotal =  "{0:N2}" -f $($currentObject.total_credit_entry)
            $ReportOut =  " $($currentObject.record_type), $($currentObject.batch_number), $($currentObject.entry_addenda_count_8), $formattedDebitTotal,$formattedCreditTotal"
        }
        "9" {
            $formattedDebitTotal =  "({0:N2})" -f $($currentObject.total_debit_entry_in_file)
            $formattedCreditTotal =  "{0:N2}" -f $($currentObject.total_credit_entry_in_file)
            $ReportOut = "$($currentObject.record_type), $($currentObject.batch_count),$($currentObject.block_count), $($currentObject.entry_addenda_count_9), $formattedDebitTotal,$formattedCreditTotal"
        }
    }
            $FinalReport += "`n"+$ReportOut # Add data to report
}

$FinalReport += "`n--------->>> NACHA File Report End <<<---------`n" # Add end to report

return $FinalReport