Parsing CSV header for validation
Input Injection

Your function seems to depend at least on the input file, but this file is asked for by the function. You should follow the IoC principle and pass the file from outside (through function parameter).

var file = ConfigurationManager.AppSettings["catalogue-lines-import-file"];
var lines = ImportCatalogueLines(file);


Maybe like that, but dont get confused, as your method Is private, this should be pushed even further, somewhere outside the entire class....

Always False Conditional

if (header[i] == "catcode")
                        catCodeColumn = i;

                    if (header[i] == "prodcode")
                        prodCodeColumn = i


It Is impossible for header[i] to be equal to two different values at the same time. Use else if or switch.

Duplicit Columns Check

Further if you are super paranoid you might want to check that catCodeColumn And prodCodeColumn Is not assigned more than once. That would mean there Is a duplicit column header (one that you care of).

Redundant Comparisions

catCodeColumn.HasValue == false || prodCodeColumn.HasValue == false


The comparision to false Is redundant, use the negation operator "!".

!catCodeColumn.HasValue || !prodCodeColumn.HasValue


Or use de Morgans law

!(catCodeColumn.HasValue &amp;&amp; prodCodeColumn.HasValue)


Split by comma or do proper csv parsing?

In case some column value contains a comma character (not talking about the quotes needed for it to work), your implementation Will fail to handle it. Of course only if the csv ever contains such values. Even if it should not, you say that users can modify it as they like. Time to get paranoid!

Error Reporting

You should not log everything to Console inside the function.  Especially not if you caught an exception, mute it and return possibly half-built result. Instead throw a custom exception, possibly let it carry the half-built list. But dont return with incomplete result. And put the logging to Its own scope.

General Remarks

Other than that I think your approach Is quite reasonable. If you cannot hardcode or configure the columns offset ať compile time, you have to determine it dynamically... But ofc if you used a proper csv parser with additional features like header checking, the code would look quite different, probably shorter...