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 |