Articles

ColdFusion Gemini Vibes Gone Wrong

... or maybe I'm just not doing it right.

Image about ColdFusion Gemini Vibes Gone Wrong
June 11, 2025

I like creating custom tag wrappers for command line executable that match built-in ColdFusion functions and utilize the same parameters. I do my best to match the same output when it matters. I thought that this would be a simple task to test AI LLM since it there's plenty of existing documentation and examples available for both technologies. I've manually done this multiple times in the past and thought it'd be cool if I took a backseat and let AI figure it out. I guess my expectactions were too high as the process didn't get too far in the amount of time that I chose to invest.

FREE ADVERTISEMENT: For more info on using AI as a ColdFusion developer, check out the Working Code podcast episode 220 "Embracing AI with Dan Wilson". (I recently started his Udemy course "Generative AI for Developers: How to Use AI In Your Workday", but haven't finished it as of this post.)

I use VSCode, but haven't installed or used any AI extensions yet.  When the feature was initially introduced, I believe it was automatically enabled and I immediately disabled it because it was slowing me down. I also prefer to "think for myself" and didn't want to accidentally become overly dependent on it. I like using a combination of X's Grok, GoogleGemini and Microsoft Copilot dedicated apps & web pages to challenge my assumptions in an effort to identify "what I know" versus "what I don't know that I don't know".

While AI chat services lack Artificial general intelligence (AGI), they do excel at pattern matching. So I put this to the test on a Sunday afternoon to "create a ColdFusion Custom Tag (CFTag) that accepts the same parameters and performs the same functions and returns the same output as CFZip using 7-Zip."

NOTE: This CFML-to-EXE wrapper function took way too long and only really managed to support support extremely simple ZIP and LIST functions. (I say this because I haven't had time to review the other functions and, based on this experience, don't trust that they would work as expected.)

7-Zip (Windows)

7-Zip is a "file archiver with a high compression ratio" and is available for Windows only. It's free software with open source and most code is under the GNU LGPL license. No registration or payment is required to use 7-Zip in any environment and they offer a command line version (7za.exe).

The reason I like this this Windows executable is because it can be much faster when creating ZIP archives using either default or no compression settings.... and I mean a LOT faster.

DEFAULT COMPRESSION: 26.8 versus 11.4 seconds

NO COMPRESSION: 29.5 seconds versus 759 milliseconds

Supported Formats

Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM

Unpacking only: APFS, AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VHDX, VMDK, XAR and Z.

Basic commands include:

  • a: Add files to archive
  • b: Benchmark
  • d: Delete files from archive
  • e: Extract files from archive (without using directory names)
  • h: Calculate hash values for files
  • i: Show information about supported formats
  • l: List contents of archive
  • rn: Rename files in archive
  • t: Test integrity of archive
  • u: Update files to archive
  • x: eXtract files with full paths

CFZip

CFZip "manipulates ZIP and Java Archive (JAR) files".

The descriptions for Adobe, Lucee and BoxLang are a little different and I haven't fully explored and compared them determine if they are 100% the same when it comes to results.

Adobe ColdFusion

Manipulates ZIP and Java Archive (JAR) files. (Added in ColdFusion 8.) In addition to the basic zip and unzip functions, use the cfzip tag to delete entries from an archive, filter files, read files in binary format, list the contents of an archive, and specify an entry path used in an executable JAR file.

Actions supported: delete, list, read, readBinary, unzip, zip

Lucee CFML

In addition to the basic zip and unzip functions, use the cfzip tag supports:

deleting entries from an archive, filter files, read files in binary format, list the contents of an archive, specify an entry path used in an executable JAR file.

Actions supported: delete, list, read, readBinary, unzip, zip

NOTE: Lucee also supports a compress tag that supports formats bzip, bzip2, gzip, tar, tar.bz, tbz (tar bzip), tbz2, tgz (tar gzip), tar.gz, and zip and an Extact tag that supports bzip, gzip, tar, tbz (tar bzip), tgz (tar gzip), and zip.

BoxLang

BoxLang's Zip component is a "powerful component that allows you to interact with zip/gzip files."

Actions supported: delete, list, read, readBinary, unzip, zip

Platform-Specific CFZip Contrasts and Comparisons (precursary; based on documentation)

  • BoxLang's ZIP component doesn't support password or storePath. (perhaps undocumented?), but supports a flatList flag for returning data in an array of structs. (I believe that this tranformation can be also accomplished by using a QoQ.)

  • Lucee has additional support for filterDelimiters.

  • Adobe references maxUnzipRatio for unzipping. I'm not sure what this does and it's not explained anywhere. Adobe also references support for in-memory files using the ram: path protocol.

Using AI to Create a CFZip-like Wrapper for 7-Zip

Limitations:

  • The entryPath attribute for zip action is not fully replicated as 7-Zip's a command does not directly support re-rooting files within the archive in the same flexible way as CFZIP. It will primarily include the source file/directory relative path within the archive.
  • Parsing 7-Zip's console output for list is text-based and might be fragile if 7-Zip's output format changes in future versions.
  • The compression attribute (e.g., DEFLATE, STORE) from CFZIP is not replicated as 7-Zip's -tzip handles this internally for ZIP archives; compressionLevel provides similar control over strength.
  • This tag does not handle encryption (password protection) which CFZIP supports.
  • This tag does not support add action for adding entries to an existing zip without implicitly recompressing/updating the entire archive. 7-Zip's a command implies add/update.
  • Error messages were initially added to indicate 7-Zip executable issues or parsing failures. 

NOTE: AI didn't reference CFZip parameters like prefix, password, showDirectory or filter. It also didn't consider or recommend using -bd with 7-Zip to supress the progress bar.

CFML Errors encountered (due to a poor understanding of ColdFusion):

Working on "ZIP" Action

  • Initial Code Review: I requested using ordered structs [:] as they are better IMHO for debugging purposes
  • Docs: The action parameter is optional and defaults to "zip". (Gemini made it required.)
  • CFError: Parameter validation error for the MID function. The function takes 3 parameters.
  • CFError: All variables defined with the var keyword must be declared inside a function. (also, changed the default to "compress" instead of "zip")
  • CFError: All variables defined with the var keyword must be declared inside a function.
  • requested to use cfparam to define attributes-scoped variables.
  • CFError: Using VAR in a custom tag? This isn't a function. var is not allowed. So what does it do? It switches to the local scope. The variables scope for a custom tag is isolcated.
  • CFError: The value of the attribute type, which is currently Expression, is invalid. (Switched to rethrow).
  • CFError: Variable ISDIRECTORY is undefined. (Is it making up BIFs?)
  • CFError: Variable CFEXECUTE is undefined. It tried to set it to a variable and then check for exitcode. (Wha? For real?)
  • NOTICED: CFexecute didn't include any timeout, so it would never have waited for a response.
  • CFError: Attribute validation error for CFEXECUTE tag in cfscript.It does not allow the attribute(s) APPLICATION,RESULT. The valid attribute(s) are ARGUMENTS,ERRORFILE,ERRORVARIABLE,NAME,OUTPUTFILE,TIMEOUT,VARIABLE. (Hallucinating?)
  • It's using a combination of ordered & named keys with CFExecute. Recommended using an ordered struct and passing it as attributeCollection. (This is my best practice for debugging CFExecute calls.)
  • Recommended using modern json notation for the struct creation. (Rather then setting each key individually.)
  • Regressed and attempted to assign CFExecute to a variable.
  • CFError: Attribute validation error for tag CFEXECUTE.It requires the attribute(s): NAME. (It used application rather than name.)
  • CFError: Attribute validation error for tag CFEXECUTE.It has an invalid attribute combination: arguments,errorvariable,name,outputfile,timeout,variable. Possible combinations are:<li>Required attributes: 'name'. Optional attributes: 'arguments,errorfile,outputfile,timeout'. <li>Required attributes: 'name'. Optional attributes: 'arguments,errorfile,timeout,variable'. <li>Required attributes: 'name'. Optional attributes: 'arguments,errorvariable,outputfile,timeout'. <li>Required attributes: 'name'. Optional attributes: 'arguments,errorvariable,timeout,variable'. (Outputfile and variable parameters can't be both specified. NOTE: I think that Outputfile should be able to be passed, but only if it's empty.)
  • Revamp: CompressionLevel is set to 5 by default. Make it so the compression level is empty by default and only apply if it's numeric and between 0 and 9.
  • ZIP works! (with extremely basic MVP unit test)

Working on "LIST" Action

  • The initial logic is not parsing any file data and throws a manual error indicating it can't find any data.
  • Trying to parse the header with this poor rule: if (findNoCase("Date Time Attr Size Compressed Name", variables.line) GT 0) { without taking variable quantity of spaces into consideration
  • UGH: Rather than using regex, Gemini uses listgetat() for year, month, day, etc. (I used Grok to generate regex to parse.)
  • Unnecessary complexity: Too much detection is being performed. (Why is separatorLineIndex being detected? Why not pass all data to the parseZipListings regex function and ignore any lines that don't match the pattern?)
  • CFError: All variables defined with the var keyword must be declared inside a function. (Gemini continues to add var declarations to a custom tag.)
  • CFError: Hallucination... DirectoryName tag doesn't exist. Why using createdatetime with 6 params when parsedatetime can be used with 2?
  • CFError: All variables defined with the var keyword must be declared inside a function. (AGAIN!)
  • List data is not returned same as CF. Should be returned as a query, but no explicit data types were defined.
  • CFError: Variable FINDLAST is undefined.
  • CFError: Variable LASTINDEXOF is undefined. (Replaced FINDLAST with another non-existent function.)
  • AI attempted to gaslight me claiming that modern CFML can "dump a query row as a struct using queryName[1] notation".
  • Not using name argument and is instead setting a static caller.zipInfo = zipInfoQuery; rather than using caller[arguments.name] = zipInfoQuery;.
  • Number of issues with the output: Name is incorrectly displaying size, Size is incorrectly displaying 0, Directory is incorrectly empty, and DateLastModified is incorrectly the same date for all files. NA should be the default for EncryptionAlgorithm.
  • I noticed that directories were included in the the results by default. (showDirectory isn't an option, but should be added back in.)
  • Realized that returning the CRC is possible using 7za using the -slt flag, but the technical output format is different. Manually wrote regex and provided instructions on how to use it.
  • I noticed CRCs are in HEX format. Request to convert "CRC to long/bigint. (NOTE: Adobe's documentation just states "Checksum", but doesn't indicate it uses a long integer.) Lucee & BoxLang don't describe the type of data returned for LIST results.
  • 7ZA: I noticed that 7-Zip returns files in alphabetical order, but this just could just be coincidence. I'm not exactly sure which order CFZip data returned in. Either way, queries can be re-ordered using QoQ.
  • 7ZA: I noticed that 7-Zip returns DastLastModified with millecond accuracy. CFZip is rounded... sometimes rounded randomly up or down. (Personally, I prefer accuracy.)
  • I noticed that Directory was truncated with the right-most character missing.
  • I noticed that 7za outputted the slashes using native OS \ instead of java /. Update to normalize the output.
  • I just realized that cfthrow was replaced everywhere with cflog. I never asked for this and not sure why it was added.
  • Gaslighting attempt: Regarding LastModified parsed value: If it's not valid or empty, it will explicitly be set to createDate(1, 1, 1), which is ColdFusion's way of representing an "empty" or "null" date value for a timestamp column. Wha? 1,1,1 evaluates as 2001-01-01, not NULL. AI was also setting CRC to 0 instead of ignoring it and allowing it to become NULL.
  • Due to performance differences with LIST, I considered supporting both regular and technical file listing thinking that the output and parsing was slowing it down. (While comments & CRC are nice to have, they aren't always essential.) Upon refreshing, AI changed the default sevenZipPath that I had statically identified for a "more common installation location". The common location contained a space, but Gemini didn't wrap it in quotes. I'm not sure if this is required for "name" when using CFExecute, but I often relocate files so that I don't have to deal with space issues. I checked and the "source" attribute was propery quoted.
  • Performance for regular, non-techincal LIST actions wasn't much faster. I asked it to revert to the previous version, and then it went all the way back to the pre-CRC version. It apologized, started refreshing the code, got about 80% of the way and then never finished responding.

Automatic Local Versioning

NOTE: I use the Local History VS Code extension. Each time I modify & save a file using VSCode, a copy is saved to the local history sub-directory and renamed as originalFilename_yyyymmddHHnnss.ext. This approach provides a full version history that can be reviewed with CodeCompare to see what is changing with every iteration.

AI Sample Source (Use at your own discretion)

NOTE: This is not how I would write this function.  I'll circle back and build a better CFTag in the near future.

<!---
    Custom Tag: cf_7zip
    Description: Mimics CFZIP functionality using the 7za.exe executable, providing extended
                 support for compression level and number of CPU threads.
    Compatibility: ColdFusion 2016 and later.
    Dependencies: 7za.exe must be installed and accessible via system PATH or specified
                 by the 'sevenZipPath' attribute.
    Note: This is a test and was written by Gemini AI on 6/8/2025 and required too much effort IMHO.
                 ZIP & LIST actions have been partially tested. CF's DELETE action was ignored.
                 USe at your own risk. Not recommended for production. This is for test purposes only.

    Usage Example:
    <cf_7zip action="zip"
             file="C:\temp\myarchive.zip"
             source="C:\temp\myfiles"
             recurse="true"
             overwrite="true"
             compressionLevel="9"
             numThreads="4"
             timeout="120" /> <!--- Uses default sevenZipPath, adds 120-second timeout --->

    <cf_7zip action="unzip"
             file="C:\temp\myarchive.zip"
             destination="C:\temp\extracted"
             overwrite="true" />

    <cf_7zip action="list"
             file="C:\temp\myarchive.zip" name="myZipList" /> <!--- Example with 'name' attribute --->
    <cfdump var="#myZipList#" />

    <cf_7zip action="read"
             file="C:\temp\myarchive.zip"
             entryPath="myfiles/a.txt"
             charset="UTF-8" />
    <cfoutput>#caller.zipEntry#</cfoutput>

    Attributes:
    - action (optional, default="zip"):
        - "zip" (or "compress"): Compresses files/directories into a ZIP archive.
        - "unzip" (or "uncompress"): Uncomppresses a ZIP archive.
        - "list": Lists the contents of a ZIP archive.
        - "read": Reads a specific text entry from a ZIP archive.
        - "readBinary": Reads a specific binary entry from a ZIP file.
    - file (required): Path to the .zip archive.
    - source (required for zip): Path to file(s) or directory to compress.
    - destination (required for unzip): Path to directory to uncompress into.
    - entryPath (optional): Specific entry within the archive for read/unzip.
                            For 'zip', this is largely ignored due to 7-Zip's behavior.
    - filter (optional): File mask (e.g., "*.txt") for zip/unzip/list.
    - recurse (optional, boolean, default="true"): Whether to include subdirectories
                                                    when compressing a directory.
    - overwrite (optional, boolean, default="true"): Whether to overwrite existing files
                                                     (in archive for zip, in destination for unzip).
    - charset (optional, string, default="UTF-8"): Character set for text reads.
    - allEntries (optional, boolean, default="true"): For unzip, whether to uncompress all
                                                      entries or only those specified by entryPath/filter.
    - compressionLevel (optional, numeric, default=""): 7-Zip compression level (0-9).
                                                         0=fastest, 9=strongest. (-mx switch).
                                                         If empty, 7-Zip's default compression will be used.
    - numThreads (optional, numeric, default="2"): Number of CPU threads to use for compression.
                                                   (-mmt switch, max 8 or number of logical cores)
    - sevenZipPath (optional, string, default="c:\Program Files\7-Zip\7za.exe"): Full path to the 7za.exe executable.
                                                                                If not provided, assumes this default path.
    - timeout (optional, numeric, default="60"): Maximum number of seconds to wait for the 7-Zip process to complete.
    - name (required for list): The name of the query variable to store the list result in.

    Output Variables (set in caller scope):
    - [Name attribute value] (for action="list"): A ColdFusion Query object with columns:
                                          Name, Directory, Size, CompressedSize, Type, DateLastModified, Comment, CRC, EncryptionAlgorithm.
    - caller.zipEntry (for action="read", "readBinary"): The content of the specified entry (string for read, binary for readBinary).
                                                         Note: For "list" action, CFZIP does NOT set caller.zipEntry.

    Limitations:
    - The 'entryPath' attribute for 'zip' action is not fully replicated as 7-Zip's 'a' command
      does not directly support re-rooting files within the archive in the same flexible way as CFZIP.
      It will primarily include the source file/directory relative path within the archive.
    - Parsing 7-Zip's console output for 'list' is text-based and might be fragile if 7-Zip's
      output format changes in future versions.
    - The 'compression' attribute (e.g., DEFLATE, STORE) from CFZIP is not replicated as 7-Zip 'a -tzip' handles
      this internally for ZIP archives; 'compressionLevel' provides similar control over strength.
    - This tag does not handle encryption (password protection) which CFZIP supports.
    - This tag does not support 'add' action for adding entries to an *existing* zip without implicitly
      recompressing/updating the entire archive. 7-Zip's 'a' command implies add/update.
    - Error messages will indicate 7-Zip executable issues or parsing failures.
--->
<cfscript>
    // Define attributes using cfparam for validation and default values
    cfparam(name="attributes.action", default="zip", type="string");
    cfparam(name="attributes.file", type="string"); // Required, so no default
    cfparam(name="attributes.source", default="", type="string");
    cfparam(name="attributes.destination", default="", type="string");
    cfparam(name="attributes.entryPath", default="", type="string");
    cfparam(name="attributes.filter", default="", type="string");
    cfparam(name="attributes.recurse", default=true, type="boolean");
    cfparam(name="attributes.overwrite", default=true, type="boolean");
    cfparam(name="attributes.charset", default="UTF-8", type="string");
    cfparam(name="attributes.allEntries", default=true, type="boolean");
    // Changed default to empty string and type to string to allow it to be empty
    cfparam(name="attributes.compressionLevel", default="", type="string");
    cfparam(name="attributes.numThreads", default=2, type="numeric");
    cfparam(name="attributes.sevenZipPath", default="c:\Program Files\7-Zip\7za.exe", type="string");
    cfparam(name="attributes.timeout", default=60, type="numeric"); // Added timeout parameter
    cfparam(name="attributes.name", default="", type="string"); // Added 'name' attribute for list action

    // Internal variables are now explicitly scoped to 'variables'
    command = "";
    args = "";
    tempDir = "";
    tempFile = "";
    i = 0;
    line = "";
    lines = [];
    j = 0;
    char = "";
    currentPart = "";
    inWord = false;
    datePart = "";
    timePart = "";
    pathColumnEnd = 0;
    remainingLine = "";
    parts = [];
    lineParts = [];
    currentSource = "";
    sourceList = [];
    extractedFileName = "";
    extractedFilePath = "";

    // Variables to hold cfexecute output/error
    commandOutput = "";
    commandError = "";

    // Struct to hold cfexecute attributes for attributecollection
    cfexecuteArgs = [:];

    // Get the system's file separator (e.g., "\" on Windows, "/" on Unix-like systems)
    fileSep = createObject("java", "java.lang.System").getProperty("file.separator");

    /**
     * Helper function to process an entry's data and add it as a row to the query.
     * @param targetQuery The query object to add the row to.
     * @param entryData A struct containing the parsed key-value pairs for a single entry.
     */
    function processAndAddEntry(required query targetQuery, required struct entryData) {
        // Use a local struct for function-scoped variables
        var local = {};

        // Default values for robustness if keys are missing from 7-Zip output
        local.entryName = structKeyExists(entryData, "Path") ? entryData["Path"] : "";
        local.entrySize = structKeyExists(entryData, "Size") ? val(entryData["Size"]) : 0;
        local.entryCompressedSize = structKeyExists(entryData, "Packed Size") ? val(entryData["Packed Size"]) : 0;
        local.entryModified = structKeyExists(entryData, "Modified") ? entryData["Modified"] : "";
        local.entryComment = structKeyExists(entryData, "Comment") ? entryData["Comment"] : "";
        local.entryCRC = structKeyExists(entryData, "CRC") ? entryData["CRC"] : "";
        local.entryEncrypted = structKeyExists(entryData, "Encrypted") ? entryData["Encrypted"] : "NA"; // Default to "NA"

        // Normalize backslashes to forward slashes for consistency with ColdFusion's internal path handling
        local.entryName = replace(local.entryName, "\", "/", "all");

        // Determine if it's a directory based on "Folder = +" or Attributes 'D'
        // ColdFusion CFZIP list action typically omits directories.
        local.isDirectory = (structKeyExists(entryData, "Folder") && entryData["Folder"] EQ "+") || (structKeyExists(entryData, "Attributes") && findNoCase("D", entryData["Attributes"]) GT 0);

        // Only add the row if it's not a directory, to mimic CFZIP's default list behavior
        if (!local.isDirectory) {
            // Determine the Directory (path) from the full name for files
            local.lastSlashPos = local.entryName.lastIndexOf("/");

            local.entryDirectory = "";
            if (local.lastSlashPos > 0) {
                // Extract everything before the last slash, INCLUDING the slash itself
                local.entryDirectory = left(local.entryName, local.lastSlashPos);
            }

            // Type is always "File" if we are excluding directories
            local.entryType = "File";

            // Add a new row to the query
            queryAddRow(targetQuery);

            querySetCell(targetQuery, "Name", local.entryName);
            querySetCell(targetQuery, "Directory", local.entryDirectory);
            querySetCell(targetQuery, "Size", javacast("long", local.entrySize));
            querySetCell(targetQuery, "CompressedSize", javacast("long", local.entryCompressedSize));
            querySetCell(targetQuery, "Type", local.entryType);

            // Parse DateLastModified: If valid, set it; otherwise, do not set the cell
            if (len(local.entryModified) && isDate(local.entryModified)) {
                querySetCell(targetQuery, "DateLastModified", parseDateTime(local.entryModified));
            }

            querySetCell(targetQuery, "Comment", local.entryComment);

            // Convert CRC from hexadecimal string to numeric (bigint): If valid hex, set it; otherwise, do not set the cell
            if (len(local.entryCRC) && reFindNoCase("^[0-9A-F]+$", local.entryCRC) GT 0) {
                querySetCell(targetQuery, "CRC", javacast("long", inputBaseN(local.entryCRC, 16)));
            }

            querySetCell(targetQuery, "EncryptionAlgorithm", (local.entryEncrypted EQ "-" ? "NA" : local.entryEncrypted));
        }
    }


    // --- Input Validation and Setup ---
    // 'attributes.file' is already validated by cfparam's lack of a default,
    // which means it *must* be provided by the caller.

    // Check if the target zip file exists for actions other than 'zip'
    if (attributes.action NEQ "zip" AND attributes.action NEQ "compress" AND !fileExists(attributes.file)) {
        throw(type="7Zip.FileDoesNotExist", message="The specified zip file '#attributes.file#' does not exist for action '#attributes.action#'.");
    }

    // Ensure the 7za.exe executable exists if a full path is explicitly provided.
    if (!fileExists(attributes.sevenZipPath)) {
        throw(type="7Zip.ExecutableNotFound", message="The 7-Zip executable '#attributes.sevenZipPath#' was not found. Please verify the path or ensure it's in your system's PATH.");
    }

    // --- Action Logic using a switch statement ---
    try {
        switch (attributes.action) {
            case "zip": // CFZIP standard action
            case "compress": // Alias for backward compatibility with this tag
                // Validate 'source' attribute for compression
                if (!len(attributes.source)) {
                    throw(type="7Zip.MissingArgument", message="The 'source' attribute is required for action '#attributes.action#'.");
                }

                command = attributes.sevenZipPath;
                // 'a': Add files to archive. If archive doesn't exist, it's created.
                // '-tzip': Force creation of a ZIP format archive (mimicking CFZIP's default).
                args = "a -tzip";
                if (attributes.overwrite) {
                    args &= " -y"; // '-y': Assume Yes on all queries (useful for overwriting existing files in archive).
                }

                // Apply compression level if provided and valid
                if (len(attributes.compressionLevel)) {
                    if (isNumeric(attributes.compressionLevel) && attributes.compressionLevel GTE 0 && attributes.compressionLevel LTE 9) {
                        args &= " -mx" & attributes.compressionLevel; // '-mx': Set compression method/level (0=fastest, 9=strongest).
                    } else {
                        throw(type="7Zip.InvalidArgument", message="Invalid 'compressionLevel'. Must be a number between 0 and 9, or left empty.");
                    }
                }

                // Apply number of threads if valid
                if (isNumeric(attributes.numThreads) && attributes.numThreads GTE 1 && attributes.numThreads LTE 8) { // 7-Zip typically supports up to 8 threads effectively
                    args &= " -mmt" & attributes.numThreads; // '-mmt': Set number of CPU threads.
                } else {
                    throw(type="7Zip.InvalidArgument", message="Invalid 'numThreads'. Must be a number between 1 and 8 (inclusive).");
                }

                // Add the output archive path (quoted for spaces)
                args &= " " & chr(34) & attributes.file & chr(34);

                // Corrected to use directoryExists()
                if (len(attributes.source) && directoryExists(attributes.source)) {
                    if (attributes.recurse) {
                        // For directories with recursion, 7-Zip 'a' command includes the base directory name.
                        // Example: 7za a archive.zip C:\MyDir\* will add contents of MyDir under MyDir/
                        args &= " " & chr(34) & attributes.source & fileSep & "*" & chr(34);
                        // CFZIP's 'entryPath' allows re-rooting, which 7-Zip's 'a' command doesn't easily support.
                        // This implementation preserves the source directory's relative path within the archive.
                    } else {
                        // 7-Zip's 'a' command fundamentally includes directory contents recursively by default.
                        // Non-recursive compression of a directory itself (i.e., just the directory entry, not its contents)
                        // is not directly equivalent to CFZIP's non-recursive directory handling with 'a'.
                        throw(type="7Zip.UnsupportedFeature", message="Compression of a directory without recursion ('recurse=false') is not directly supported by 7-Zip's 'a' command like CFZIP. Please set recurse='true' or compress individual files within the directory.");
                    }
                } else {
                    // Source is a file or a comma-separated list of files
                    sourceList = listToArray(attributes.source);
                    for (i=1; i <= arrayLen(sourceList); i++) {
                        currentSource = trim(sourceList[i]);
                        if (!fileExists(currentSource)) {
                            throw(type="7Zip.FileDoesNotExist", message="Source file '#currentSource#' does not exist.");
                        }
                        args &= " " & chr(34) & currentSource & chr(34);
                    }
                    // Similar to directories, entryPath for files is complex with 7-Zip 'a' command
                    // if it implies renaming the file inside the archive.
                }

                // Prepare arguments for cfexecute using attributecollection
                cfexecuteArgs = [
                    "name": '"' & command & '"',
                    "arguments": args,
                    "variable": "variables.commandOutput", // Still needs to be variables.commandOutput for output variable
                    "errorvariable": "variables.commandError", // Still needs to be variables.commandError for error variable
                    "timeout": attributes.timeout
                ];

                // Execute the 7-Zip command (no assignment as it returns void in CFScript)
                cfexecute(attributecollection=cfexecuteArgs);

                // Check for errors based on content in the error output stream
                if (len(commandError)) {
                    throw(type="7Zip.ExecutionError", message="7-Zip compression failed. Error: #commandError#. Output: #commandOutput#");
                }
                break;

            case "unzip": // CFZIP standard action
            case "uncompress": // Alias for backward compatibility with this tag
                // Validate 'destination' attribute for uncompression
                if (!len(attributes.destination)) {
                    throw(type="7Zip.MissingArgument", message="The 'destination' attribute is required for action '#attributes.action#'.");
                }
                // Create destination directory if it's specified and doesn't exist
                if (len(attributes.destination) && !directoryExists(attributes.destination)) {
                    try {
                        cfdirectory(action="create", directory=attributes.destination);
                    } catch (any e) {
                        throw(type="7Zip.DirectoryError", message="Could not create destination directory '#attributes.destination#'. Error: #e.message#.");
                    }
                }

                command = attributes.sevenZipPath;
                // 'x': Extract with full path names. This is generally preferred for preserving directory structure.
                args = "x " & chr(34) & attributes.file & chr(34);
                // Specify output directory
                args &= " -o" & chr(34) & attributes.destination & chr(34);
                if (attributes.overwrite) {
                    args &= " -y"; // '-y': Assume Yes on all queries (overwrite existing files).
                }

                // Handle 'filter' or 'entryPath' for selective uncompression
                if (len(attributes.filter) && len(attributes.entryPath)) {
                    throw(type="7Zip.InvalidArgument", message="Cannot specify both 'filter' and 'entryPath' for action '#attributes.action#'. Please choose one.");
                }

                if (len(attributes.filter)) {
                    args &= " " & chr(34) & attributes.filter & chr(34); // Apply file mask
                } else if (len(attributes.entryPath)) {
                    args &= " " & chr(34) & attributes.entryPath & chr(34); // Extract specific entry
                } else if (!attributes.allEntries) {
                    // If allEntries is false, either filter or entryPath must be provided, otherwise it's ambiguous.
                    throw(type="7Zip.InvalidArgument", message="When 'allEntries' is false for action '#attributes.action#', either 'filter' or 'entryPath' must be specified to indicate which entries to uncompress.");
                }

                // Prepare arguments for cfexecute using attributecollection
                cfexecuteArgs = [
                    "name": '"' & command & '"',
                    "arguments": args,
                    "variable": "variables.commandOutput",
                    "errorvariable": "variables.commandError",
                    "timeout": attributes.timeout
                ];

                // Execute the 7-Zip command (no assignment as it returns void in CFScript)
                cfexecute(attributecollection=cfexecuteArgs);

                if (len(commandError)) {
                    throw(type="7Zip.ExecutionError", message="7-Zip uncompression failed. Error: #commandError#. Output: #commandOutput#");
                }
                break;

            case "list":
                // Validate 'name' attribute for list action
                if (!len(attributes.name)) {
                    throw(type="7Zip.MissingArgument", message="The 'name' attribute is required for action '#attributes.action#'.");
                }

                command = attributes.sevenZipPath;
                // Add the -slt flag for technical information
                args = "l -slt " & chr(34) & attributes.file & chr(34); // 'l -slt': List contents with technical info

                // Prepare arguments for cfexecute using attributecollection
                cfexecuteArgs = [
                    "name": '"' & command & '"',
                    "arguments": args,
                    "variable": "variables.commandOutput",
                    "errorvariable": "variables.commandError",
                    "timeout": attributes.timeout
                ];

                // Execute the 7-Zip command
                cfexecute(attributecollection=cfexecuteArgs);

                // Check for errors based on content in the error output stream
                if (len(commandError)) {
                    throw(type="7Zip.ExecutionError", message="7-Zip list failed. Error: #commandError#. Output: #commandOutput#");
                }

                // Create a new ColdFusion Query object for the output
                zipInfoQuery = queryNew(
                    "Name,Directory,Size,CompressedSize,Type,DateLastModified,Comment,CRC,EncryptionAlgorithm",
                    "varchar,varchar,bigint,bigint,varchar,timestamp,varchar,bigint,varchar" // CRC is now bigint
                );

                // Variables for parsing -slt output
                local.currentEntryData = {};
                local.lines = listToArray(commandOutput, chr(10));
                // Regex to capture key-value pairs from -slt output
                // Group 1: Key (e.g., Path, Size, Modified)
                // Group 2: Value
                local.sltRegex = "^\s*([a-zA-Z\s]+)\s*=\s*(.*?)\s*$";
                local.isFirstEntry = true; // Flag to skip initial archive metadata (archive comment, physical size etc.)

                for (i=1; i <= arrayLen(local.lines); i++) {
                    local.line = trim(local.lines[i]);

                    // An empty line usually signals the end of an entry's data block
                    if (len(local.line) EQ 0) {
                        // If we have accumulated data for an entry and it's not the initial archive metadata
                        // Also, skip the archive's own metadata block (which is the first one encountered)
                        if (!structIsEmpty(local.currentEntryData) && !local.isFirstEntry) {
                            processAndAddEntry(zipInfoQuery, local.currentEntryData);
                        }
                        // Reset for next entry
                        local.currentEntryData = {};
                        continue;
                    }

                    // The "----------" line indicates the end of the archive's general metadata
                    // and the start of individual file/folder entries.
                    if (local.line EQ "----------") {
                        local.isFirstEntry = false; // Next blocks will be actual entries
                        local.currentEntryData = {}; // Clear any partial archive metadata that might have been picked up before the separator
                        continue;
                    }

                    // Parse key-value pairs
                    local.matches = reFindNoCase(local.sltRegex, local.line, 1, true);

                    if (local.matches.len[1] > 0) {
                        local.key = trim(mid(local.line, local.matches.pos[2], local.matches.len[2]));
                        local.value = trim(mid(local.line, local.matches.pos[3], local.matches.len[3]));
                        local.currentEntryData[local.key] = local.value;
                    }
                }

                // After the loop, process any remaining data for the last entry
                // This handles the case where the last entry is not followed by an empty line
                if (!structIsEmpty(local.currentEntryData) && !local.isFirstEntry) {
                    processAndAddEntry(zipInfoQuery, local.currentEntryData);
                }

                // Set output variable in the caller scope using the 'name' attribute value
                caller[attributes.name] = zipInfoQuery;

                // caller.zipEntry is ONLY set for read/readBinary actions, not list.
                break;

            case "read":
            case "readBinary":
                // Validate 'entryPath' for reading specific content
                if (!len(attributes.entryPath)) {
                    throw(type="7Zip.MissingArgument", message="The 'entryPath' attribute is required for action '#attributes.action#'.");
                }

                tempDir = getTempDirectory(); // Get a temporary directory path
                // 7-Zip's 'e' (extract) command strips the path from the extracted file,
                // so we just get the filename part from the entryPath.
                extractedFileName = getFileFromPath(attributes.entryPath);
                // Create a unique temporary file path to avoid conflicts
                extractedFilePath = tempDir & fileSep & createUUID() & "_" & extractedFileName;

                command = attributes.sevenZipPath;
                // 'e': Extract without full path names (flattens archive structure into target dir).
                // This is suitable for extracting a single file.
                args = "e " & chr(34) & attributes.file & chr(34) & " " & chr(34) & attributes.entryPath & chr(34) & " -o" & chr(34) & tempDir & chr(34);
                if (attributes.overwrite) {
                    args &= " -y"; // '-y': Overwrite if the temporary file already exists.
                }

                // Prepare arguments for cfexecute using attributecollection
                cfexecuteArgs = [
                    "name": '"' & command & '"',
                    "arguments": args,
                    "variable": "variables.commandOutput",
                    "errorvariable": "variables.commandError",
                    "timeout": attributes.timeout
                ];

                // Execute the 7-Zip command (no assignment as it returns void in CFScript)
                cfexecute(attributecollection=cfexecuteArgs);

                if (len(commandError)) {
                    throw(type="7Zip.ExecutionError", message="7-Zip extraction for '#attributes.action#' failed. Error: #commandError#. Output: #commandOutput#");
                }

                // Verify that the file was actually extracted
                if (!fileExists(extractedFilePath)) {
                    throw(type="7Zip.ExtractionError", message="Extracted file '#extractedFilePath#' not found in temp directory after 7-Zip extraction. This might indicate an issue with the archive or entryPath. 7-Zip output: #commandOutput#");
                }

                try {
                    if (attributes.action EQ "read") {
                        // Read as text with specified character set
                        caller.zipEntry = fileRead(extractedFilePath, attributes.charset);
                    } else { // action EQ "readBinary"
                        caller.zipEntry = fileReadBinary(extractedFilePath);
                    }
                } catch (any e) {
                    throw(type="7Zip.FileReadError", message="Failed to read extracted file '#extractedFilePath#'. Error: #e.message#. Detail: #e.detail#.");
                } finally {
                    // Always attempt to delete the temporary file after reading
                    try {
                        cffile(action="delete", file=extractedFilePath);
                    } catch (any e) {
                        // Removed cflog warning as requested
                    }
                }
                break;

            default:
                // Handle unsupported actions
                throw(type="7Zip.InvalidAction", message="Invalid action '#attributes.action#'. Valid actions are: zip, unzip, list, read, readBinary.");
        }
    } catch (any e) {
        // Rethrow the caught exception to preserve the original error context
        rethrow;
    }
</cfscript>