;;; ---------------------------------------------------------------------------
;;; BatchScript.lsp
;;; code compiled by YZ August 2026
;;; ---------------------------------------------------------------------------
;;; RUN THE SAME OPERATION ON EVERY DRAWING IN A FOLDER
;;;
;;; Type one line of AutoCAD commands, point at a folder, and BatchScript
;;; writes a script that repeats that line for every drawing it finds -- then
;;; runs it.
;;;
;;; The line uses the token *file* wherever a drawing name belongs. So this:
;;;
;;;     _.open *file* _.saveas  *file* _.close
;;;
;;; becomes, for a folder of two hundred drawings, two hundred lines that
;;; open each one, save it in the current format, and close it. That single
;;; example is the standard fix for a whole project stuck in an old DWG
;;; version.
;;;
;;; Anything you can type at the command line can go in that line: purge a
;;; whole set, audit it, insert a revised title block, run one of your own
;;; LISP commands across every sheet. The drawings never need opening by hand
;;; and you never need to write a script file yourself.
;;;
;;; ---------------------------------------------------------------------------
;;; HOW IT WORKS
;;;
;;; The script line is split on the *file* token into fixed fragments. For
;;; every drawing found, those fragments are re-joined with the drawing's
;;; full path -- in quotes, so spaces in folder names cannot break it --
;;; substituted at each token position. The assembled lines are written to a
;;; script file and handed to the SCRIPT command.
;;;
;;; AutoCAD executes a script line by line exactly as if it had been typed,
;;; which is why an ordinary command sequence works unaltered.
;;;
;;; Folder scanning is recursive when the sub-folders box is ticked: each
;;; folder's sub-folders are found and searched in turn, all the way down.
;;;
;;; ---------------------------------------------------------------------------
;;; SAVED SCRIPT LINES
;;;
;;; Every line you save is remembered and offered again from the Load button,
;;; so the operations you run regularly are one click away. The list, the
;;; last folder used, and the sub-folder setting are all written to a small
;;; configuration file and reloaded next time.
;;;
;;; Load can also read an existing .scr file: its first line is read and the
;;; drawing name in it is turned back into a *file* token, which recovers an
;;; editable script line from a script somebody else wrote.
;;;
;;; ---------------------------------------------------------------------------
;;; IMPORTANT -- READ BEFORE RUNNING
;;;
;;; A batch script modifies drawings without opening them in front of you and
;;; there is no undo across files. A line with a mistake in it will apply
;;; that mistake to every drawing in the folder.
;;;
;;; Test on a copy of the folder first. Every time.
;;;
;;; Note also that the script starts as soon as the dialog is accepted, and
;;; the current drawing is not part of it -- AutoCAD runs the script in the
;;; drawing you are in, so the first thing your line should normally do is
;;; open a file.
;;;
;;; ---------------------------------------------------------------------------
;;;   BATCHSCRIPT - build and run a script across a folder of drawings
;;; ---------------------------------------------------------------------------

(vl-load-com)

;;; ---------------------------------------------------------------------------
;;; *BatchScript:Saved*
;;;
;;; The remembered script lines. Global so the list survives between calls
;;; within a session; it is also written to the configuration file so it
;;; survives between sessions.
;;; ---------------------------------------------------------------------------

(or *BatchScript:Saved* (setq *BatchScript:Saved* nil))

;;; ---------------------------------------------------------------------------
;;; BatchScript:WorkFolder
;;;
;;; Returns a folder that can be written to, for the configuration and script
;;; files.
;;;
;;; The AutoCAD Support folder is preferred because it is per-user, roams
;;; with the profile, and is never cleaned out. If it cannot be determined
;;; the folder holding acad.pat is tried, and finally AutoCAD's own temporary
;;; folder, which always exists.
;;;
;;; The chain always succeeds, so no part of the program has to handle
;;; "nowhere to write" as a failure case.
;;; ---------------------------------------------------------------------------

(defun BatchScript:WorkFolder ( / dir )
    (cond
        (   (and (setq dir (getvar 'roamablerootprefix))
                 (vl-file-directory-p (strcat (vl-string-right-trim "\\" dir) "\\Support"))
            )
            (strcat (vl-string-right-trim "\\" dir) "\\Support\\")
        )
        (   (setq dir (findfile "acad.pat"))
            (strcat (vl-string-right-trim "\\" (vl-filename-directory dir)) "\\")
        )
        (   (strcat (vl-string-right-trim "\\" (getvar 'tempprefix)) "\\"))
    )
)

;;; ---------------------------------------------------------------------------
;;; BatchScript:Split
;;;
;;; Splits a string into a list of the pieces between every occurrence of a
;;; delimiter.
;;;
;;; The pieces are what matter, not the delimiters: splitting the script line
;;; on *file* gives exactly the fragments that must be re-joined around each
;;; drawing name. A line with one token yields two fragments, a line with two
;;; tokens yields three, and so on.
;;; ---------------------------------------------------------------------------

(defun BatchScript:Split ( str del / lst pos )
    (while (setq pos (vl-string-search del str))
        (setq lst (cons (substr str 1 pos) lst)
              str (substr str (+ pos 1 (strlen del)))
        )
    )
    (reverse (cons str lst))
)

;;; ---------------------------------------------------------------------------
;;; BatchScript:Join
;;;
;;; Joins a list of fragments with the given separator between each pair --
;;; the exact inverse of the split above.
;;; ---------------------------------------------------------------------------

(defun BatchScript:Join ( lst del / out )
    (setq out (car lst))
    (foreach frag (cdr lst)
        (setq out (strcat out del frag))
    )
    out
)

;;; ---------------------------------------------------------------------------
;;; BatchScript:AllFiles
;;;
;;; Returns the full paths of every file in a folder matching the filter,
;;; optionally descending into every sub-folder.
;;;
;;;   dir    - folder to search
;;;   subs   - non-nil to include sub-folders, at any depth
;;;   filter - wildcard filter, e.g. "*.dwg"
;;;
;;; Returns a list of full paths, or nil.
;;; ---------------------------------------------------------------------------

(defun BatchScript:AllFiles ( dir subs filter / folders )

    ;; Every sub-folder of a folder, recursively, as full paths. "." and ".."
    ;; are removed by name rather than by position: their place in the
    ;; listing is not guaranteed, and descending into ".." would recurse
    ;; upwards forever.
    (defun BatchScript:SubFolders ( folder / here )
        (apply 'append
            (mapcar
               '(lambda ( f )
                    (setq here (strcat folder "\\" f))
                    (cons here (BatchScript:SubFolders here))
                )
                (vl-remove "." (vl-remove ".." (vl-directory-files folder nil -1)))
            )
        )
    )

    (if (and dir (vl-file-directory-p (setq dir (vl-string-right-trim "\\" dir))))
        (progn
            (setq folders (cons dir (if subs (BatchScript:SubFolders dir))))
            (apply 'append
                (mapcar
                   '(lambda ( folder )
                        (mapcar '(lambda ( name ) (strcat folder "\\" name))
                                (vl-directory-files folder filter 1)
                        )
                    )
                    folders
                )
            )
        )
    )
)

;;; ---------------------------------------------------------------------------
;;; BatchScript:PickFolder
;;;
;;; Opens the native Windows folder picker and returns the chosen path with
;;; any trailing backslash removed, or nil if cancelled.
;;;
;;; Two COM objects are created and both must be released explicitly; an
;;; unreleased Shell object survives the command and leaks for the rest of
;;; the session. The releases sit outside the catch so they happen even when
;;; the picker itself fails.
;;;
;;;   msg - prompt shown in the picker
;;;   dir - starting folder, or nil
;;;   flg - Shell BROWSEINFO flags; 512 hides the "New Folder" button
;;; ---------------------------------------------------------------------------

(defun BatchScript:PickFolder ( msg dir flg / err fold path shell self )
    (setq err
        (vl-catch-all-apply
           '(lambda ( / app hwnd )
                (setq app   (vlax-get-acad-object)
                      shell (vla-getinterfaceobject app "Shell.Application")
                      hwnd  (vl-catch-all-apply 'vla-get-hwnd (list app))
                      fold  (vlax-invoke-method shell 'browseforfolder
                                (if (vl-catch-all-error-p hwnd) 0 hwnd) msg flg dir
                            )
                )
                ;; The Folder object has no path of its own; its Self
                ;; property is the FolderItem that does.
                (if fold
                    (setq self (vlax-get-property fold 'self)
                          path (vl-string-right-trim "\\" (vlax-get-property self 'path))
                    )
                )
            )
        )
    )
    (if self  (vlax-release-object self))
    (if fold  (vlax-release-object fold))
    (if shell (vlax-release-object shell))
    (if (vl-catch-all-error-p err)
        nil
        path
    )
)

;;; ---------------------------------------------------------------------------
;;; BatchScript:WriteConfig / BatchScript:ReadConfig
;;;
;;; Save and reload the remembered settings.
;;;
;;; Each value is written with vl-prin1-to-string and read back with read,
;;; which round-trips strings, numbers and whole lists faithfully -- quotes
;;; and backslashes in folder paths included. Writing raw text would not
;;; survive a path containing a quote or a comma.
;;;
;;; The read is deliberately forgiving: a truncated or hand-edited file
;;; simply leaves the remaining settings at their defaults rather than
;;; raising an error.
;;;
;;;   file - configuration file path
;;;   syms - list of symbols to save or restore, in order
;;; ---------------------------------------------------------------------------

(defun BatchScript:WriteConfig ( file syms / des )
    (if (setq des (open file "w"))
        (progn
            (foreach sym syms
                (write-line (vl-prin1-to-string (eval sym)) des)
            )
            (close des)
            t
        )
    )
)

(defun BatchScript:ReadConfig ( file syms / des line )
    (if (and (setq file (findfile file))
             (setq des  (open file "r"))
        )
        (progn
            (foreach sym syms
                (if (setq line (read-line des))
                    ;; A malformed line is skipped rather than aborting the
                    ;; whole read, so one bad entry cannot cost the others.
                    (   (lambda ( val )
                            (if (not (vl-catch-all-error-p val)) (set sym val))
                        )
                        (vl-catch-all-apply 'read (list line))
                    )
                )
            )
            (close des)
            t
        )
    )
)

;;; ---------------------------------------------------------------------------
;;; BATCHSCRIPT
;;; ---------------------------------------------------------------------------

(defun c:BatchScript

    ( / *error* BatchScript:FillList BatchScript:ShowFolder BatchScript:LoadDialog
        BatchScript:RemoveNth
        cfg dch dcl des dir folder frags line ptr result scrfile scrline sub
        tmp vals vars work
    )

    (setq vars '("CMDECHO")
          vals (mapcar 'getvar vars)
    )

    ;; The error handler closes any open undo group with (command "_.UNDO"
    ;; "_End"). AutoCAD 2015 and later refuse a (command) call inside an *error*
    ;; handler unless the routine says up front that it will use one, so that
    ;; declaration has to be made before the handler can ever fire.
    (vl-catch-all-apply '(lambda ( ) (*push-error-using-command*)) '())

    (defun *error* ( msg )
        (if (= 'file (type des)) (close des))
        (if (and (= 'int (type dch)) (< 0 dch)) (unload_dialog dch))
        (if (and (= 'str (type dcl)) (findfile dcl)) (vl-file-delete dcl))
        (mapcar 'setvar vars vals)
        (while (= 8 (logand 8 (getvar 'undoctl)))
            (command "_.UNDO" "_End")
            (vl-catch-all-apply '(lambda ( ) (*pop-error-mode*)) '())
        )
        (if (and msg (not (wcmatch (strcase msg t) "*break*,*cancel*,*exit*")))
            (princ (strcat "\n** BATCHSCRIPT error: " msg " **"))
        )
        (princ)
    )

    ;;; -----------------------------------------------------------------------
    ;;; BatchScript:FillList
    ;;;
    ;;; Loads a list box tile with the supplied strings.
    ;;; -----------------------------------------------------------------------

    (defun BatchScript:FillList ( key lst )
        (start_list key)
        (foreach x lst (add_list x))
        (end_list)
        lst
    )

    ;;; -----------------------------------------------------------------------
    ;;; BatchScript:ShowFolder
    ;;;
    ;;; Displays a folder path in a text tile, shortened with an ellipsis if
    ;;; it is too long for the space. Without this a deep network path simply
    ;;; runs off the edge of the dialog and the useful end of it is invisible.
    ;;; -----------------------------------------------------------------------

    (defun BatchScript:ShowFolder ( key str )
        (set_tile key
            (cond
                (   (null str) "")
                (   (< 50 (strlen str)) (strcat (substr str 1 47) "..."))
                (   str)
            )
        )
    )

    ;;; -----------------------------------------------------------------------
    ;;; BatchScript:RemoveNth
    ;;;
    ;;; Removes the item at the given position from a list.
    ;;;
    ;;; Removal is by POSITION rather than by value, because two identical
    ;;; script lines could otherwise both disappear when only one was
    ;;; selected.
    ;;; -----------------------------------------------------------------------

    (defun BatchScript:RemoveNth ( n lst / idx )
        (setq idx -1)
        (vl-remove-if '(lambda ( x ) (= n (setq idx (1+ idx)))) lst)
    )

    ;;; -----------------------------------------------------------------------
    ;;; BatchScript:LoadDialog
    ;;;
    ;;; The secondary dialog: pick a saved script line, delete one, or browse
    ;;; for an existing .scr file to read a line out of.
    ;;;
    ;;; Returns the chosen script line, or nil.
    ;;;
    ;;;   handle - the loaded dialog handle, reused for this second dialog
    ;;; -----------------------------------------------------------------------

    (defun BatchScript:LoadDialog ( handle / choice )

        ;;  Reads the first non-empty line of a file.
        (defun BatchScript:FirstLine ( file / des line )
            (if (setq des (open file "r"))
                (progn
                    (while (and (setq line (read-line des)) (= "" line)))
                    (close des)
                    line
                )
            )
        )

        ;;  Turns a line from an existing script back into an editable script
        ;;  line, by replacing whatever looks like a drawing path with the
        ;;  *file* token.
        ;;
        ;;  The line is split on the double-quote character, which isolates
        ;;  the quoted file names. A fragment is treated as a file name if it
        ;;  contains a folder path or names a file that actually exists.
        (defun BatchScript:Tokenise ( str / lst )
            (if (setq lst (BatchScript:Split str "\""))
                (apply 'strcat
                    (mapcar
                       '(lambda ( frag )
                            (if (or (/= "" (vl-filename-directory frag))
                                    (findfile frag)
                                )
                                "*file*"
                                frag
                            )
                        )
                        lst
                    )
                )
            )
        )

        (if (not (new_dialog "loadscript" handle))
            (progn (alert "The load dialog could not be opened.") nil)
            (progn
                (BatchScript:FillList "scrlst" *BatchScript:Saved*)
                ;; Pre-select the first entry so Load works immediately.
                (if *BatchScript:Saved* (setq ptr (set_tile "scrlst" "0")))

                (action_tile "scrlst" "(setq ptr $value)")

                (action_tile "delete"
                    (vl-prin1-to-string
                       '(if ptr
                            (progn
                                (setq *BatchScript:Saved*
                                    (BatchScript:RemoveNth (atoi ptr) *BatchScript:Saved*)
                                )
                                (BatchScript:FillList "scrlst" *BatchScript:Saved*)
                                (setq ptr (if *BatchScript:Saved* (set_tile "scrlst" "0")))
                            )
                        )
                    )
                )

                (action_tile "browse"
                    (vl-prin1-to-string
                       '(if (and (setq tmp  (getfiled "Select script file" "" "scr" 16))
                                 (setq line (BatchScript:FirstLine tmp))
                                 (setq line (BatchScript:Tokenise line))
                            )
                            (progn
                                (setq *BatchScript:Saved*
                                    (cons (setq ptr line) *BatchScript:Saved*)
                                )
                                (done_dialog 1)
                            )
                        )
                    )
                )

                ;; The list box holds an index; the real line is fetched from
                ;; the saved list at the moment Load is pressed.
                (action_tile "accept"
                    "(if ptr (setq ptr (nth (atoi ptr) *BatchScript:Saved*))) (done_dialog 1)"
                )
                (action_tile "cancel" "(setq ptr nil) (done_dialog 0)")

                (start_dialog)
                ptr
            )
        )
    )

    ;;; =======================================================================
    ;;;                       M A I N   R O U T I N E
    ;;; =======================================================================

    (setvar 'cmdecho 0)

    (setq work    (BatchScript:WorkFolder)
          cfg     (strcat work "YZ_BatchScript.cfg")
          scrfile (strcat work "YZ_BatchScript.scr")
    )

    ;; ---- load the remembered settings --------------------------------------
    ;; The defaults describe the commonest job of all: open every drawing,
    ;; save it in the current format, close it.

    (setq scrline "_.open *file* _.saveas  *file* _.close"
          dir     (getvar 'dwgprefix)
          sub     "1"
    )
    (BatchScript:ReadConfig cfg '(scrline dir sub *BatchScript:Saved*))

    ;; Guard against a configuration file that has been hand-edited or has
    ;; gone stale, so a missing folder or a corrupted value cannot break the
    ;; dialog before the user ever sees it.
    (if (not (= 'str (type scrline))) (setq scrline "_.open *file* _.saveas  *file* _.close"))
    (if (not (= 'str (type sub)))     (setq sub "1"))
    (if (not (and (= 'str (type dir)) (vl-file-directory-p dir)))
        (setq dir (getvar 'dwgprefix))
    )

    ;; ---- build the dialog ---------------------------------------------------
    ;; Written to a uniquely named temporary file and deleted on exit, so two
    ;; AutoCAD sessions cannot collide and nothing is left behind.

    (cond
        (   (not
                (and
                    (setq dcl (vl-filename-mktemp nil nil ".dcl"))
                    (setq des (open dcl "w"))
                    (progn
                        (foreach line
                           '(
                                "// Shared button sizes, so the rows line up."
                                "butt12  : button { width = 12; fixed_width = true; alignment = centered; }"
                                "butt15  : button { width = 15; fixed_width = true; alignment = centered; }"
                                "butt15t : button { width = 15; fixed_width = true; alignment = centered;"
                                "                   height = 2.5; fixed_height = true; }"
                                ""
                                "batchscript : dialog { label = \"Batch Script Writer\";"
                                "  spacer;"
                                "  : text { alignment = left;"
                                "           label = \"Enter script operations. Use the *file* token where a drawing name belongs.\"; }"
                                "  spacer;"
                                "  : edit_box { edit_width = 51.5; edit_limit = 2048; fixed_width = true;"
                                "               label = \"Script line:\"; key = \"scr\"; }"
                                "  spacer;"
                                "  : row {"
                                "    : butt15 { key = \"fn\"; label = \"&Filename token\"; mnemonic = \"F\"; }"
                                "    : butt15 { key = \"ld\"; label = \"&Load script\"   ; mnemonic = \"L\"; }"
                                "    : butt15 { key = \"sv\"; label = \"&Save script\"   ; mnemonic = \"S\"; }"
                                "    : butt15 { key = \"cl\"; label = \"&Clear\"         ; mnemonic = \"C\"; }"
                                "  }"
                                "  spacer;"
                                "  : boxed_column { label = \"Drawing folder\";"
                                "    : row {"
                                "      : text   { alignment = left; key = \"dir_text\"; }"
                                "      : butt12 { key = \"dir\"; label = \"&Browse\"; mnemonic = \"B\"; }"
                                "    }"
                                "    : toggle { label = \"&Include sub-folders\"; key = \"sub_dir\"; mnemonic = \"I\"; }"
                                "    spacer;"
                                "  }"
                                "  spacer;"
                                "  : row {"
                                "    : butt15t { key = \"accept\"; label = \"&Run script\"; is_default = true; mnemonic = \"R\"; }"
                                "    : butt15t { key = \"cancel\"; label = \"C&ancel\"    ; is_cancel  = true; mnemonic = \"a\"; }"
                                "  }"
                                "}"
                                ""
                                "loadscript : dialog { label = \"Select script to load\";"
                                "  spacer;"
                                "  : list_box { key = \"scrlst\"; width = 64; fixed_width = true; alignment = centered; }"
                                "  spacer;"
                                "  : row {"
                                "    : butt15 { key = \"accept\"; label = \"&Load\"     ; is_default = true; mnemonic = \"L\"; }"
                                "    : butt15 { key = \"cancel\"; label = \"&Cancel\"   ; is_cancel  = true; mnemonic = \"C\"; }"
                                "    : butt15 { key = \"delete\"; label = \"&Delete\"   ; mnemonic = \"D\"; }"
                                "    : butt15 { key = \"browse\"; label = \"B&rowse...\"; mnemonic = \"r\"; }"
                                "  }"
                                "  spacer;"
                                "}"
                            )
                            (write-line line des)
                        )
                        (setq des (close des))
                        (< 0 (setq dch (load_dialog dcl)))
                    )
                    (new_dialog "batchscript" dch)
                )
            )
            (princ "\nUnable to create the dialog.")
        )

        (   t
            (set_tile "scr"     scrline)
            (set_tile "sub_dir" sub)
            (BatchScript:ShowFolder "dir_text" dir)

            ;;; -------------------------------------------------------------
            ;;; Callbacks.
            ;;;
            ;;; Written as quoted lists and converted with vl-prin1-to-string
            ;;; rather than hand-assembled strings, because the printer emits
            ;;; correct quoting every time and hand-escaping does not.
            ;;; -------------------------------------------------------------

            ;;  Browse for the drawing folder.
            (action_tile "dir"
                (vl-prin1-to-string
                   '(if (setq tmp (BatchScript:PickFolder
                                      "Select the folder of drawings to process..." nil 512))
                        (BatchScript:ShowFolder "dir_text" (setq dir tmp))
                    )
                )
            )

            ;;  Append the *file* token at the end of the line, so the user
            ;;  does not have to remember the exact spelling.
            (action_tile "fn"
                (vl-prin1-to-string
                   '(set_tile "scr" (setq scrline (strcat scrline "*file*")))
                )
            )

            ;;  Clear, and put the cursor back in the edit box.
            (action_tile "cl"
                (vl-prin1-to-string
                   '(progn
                        (set_tile  "scr" (setq scrline ""))
                        (mode_tile "scr" 2)
                    )
                )
            )

            ;;  Load a saved or existing script line.
            (action_tile "ld"
                (vl-prin1-to-string
                   '(if (setq tmp (BatchScript:LoadDialog dch))
                        (setq scrline (set_tile "scr" tmp))
                    )
                )
            )

            ;;  Save the assembled script to a file of the user's choosing,
            ;;  without running it.
            (action_tile "sv"
                (vl-prin1-to-string
                   '(cond
                        (   (zerop (strlen scrline))
                            (alert "No script operations have been entered.")
                        )
                        (   (< (length (setq frags (BatchScript:Split scrline "*file*"))) 2)
                            (alert "The *file* token was not found in the script line.")
                        )
                        (   (null (setq folder (BatchScript:AllFiles dir (= "1" sub) "*.dwg")))
                            (alert "No drawings were found in that folder.")
                        )
                        (   (and (setq tmp (getfiled "Save script as" "" "scr" 1))
                                 (setq des (open tmp "w"))
                            )
                            (foreach path folder
                                ;; The path is wrapped in quotes so that
                                ;; spaces in folder names cannot split it into
                                ;; separate command arguments.
                                (write-line
                                    (BatchScript:Join frags (strcat "\"" path "\""))
                                    des
                                )
                            )
                            (setq des (close des))
                            (if (not (member scrline *BatchScript:Saved*))
                                (setq *BatchScript:Saved* (cons scrline *BatchScript:Saved*))
                            )
                            (alert (strcat "Script saved.\n\n"
                                           (itoa (length folder)) " drawings."))
                        )
                    )
                )
            )

            (action_tile "sub_dir" "(setq sub $value)")
            (action_tile "scr"     "(setq scrline $value)")

            ;;  Run: validated here, before the dialog closes, so a mistake
            ;;  can be corrected without losing everything else.
            (action_tile "accept"
                (vl-prin1-to-string
                   '(cond
                        (   (zerop (strlen scrline))
                            (alert "Please enter a script line.")
                        )
                        (   (< (length (setq frags (BatchScript:Split scrline "*file*"))) 2)
                            (alert "The *file* token was not found in the script line.")
                        )
                        (   (null (setq folder (BatchScript:AllFiles dir (= "1" sub) "*.dwg")))
                            (alert "No drawings were found in that folder.")
                        )
                        (   (done_dialog 1))
                    )
                )
            )

            (setq result (start_dialog)
                  dch    (unload_dialog dch)
            )
            (vl-file-delete dcl)
            (setq dcl nil)

            (if (/= 1 result)
                (princ "\nCancelled.")
                (if (setq des (open scrfile "w"))
                    (progn
                        (foreach path folder
                            (write-line
                                (BatchScript:Join frags (strcat "\"" path "\""))
                                des
                            )
                        )
                        (setq des (close des))

                        ;; Remember the line and the settings before running,
                        ;; so they are not lost if the script itself fails.
                        (if (not (member scrline *BatchScript:Saved*))
                            (setq *BatchScript:Saved* (cons scrline *BatchScript:Saved*))
                        )
                        (BatchScript:WriteConfig cfg '(scrline dir sub *BatchScript:Saved*))

                        (princ (strcat "\nRunning script over " (itoa (length folder))
                                       " drawing" (if (= 1 (length folder)) "" "s") "..."))

                        ;; System variables are restored BEFORE the script
                        ;; starts: SCRIPT hands control to the script file and
                        ;; does not come back here, so anything left until
                        ;; afterwards would never be put back.
                        (mapcar 'setvar vars vals)
                        (vl-cmdf "_.script" scrfile)
                    )
                    (princ (strcat "\nUnable to write the script file: " scrfile))
                )
            )
        )
    )

    (if (and (= 'str (type dcl)) (findfile dcl)) (vl-file-delete dcl))
    (mapcar 'setvar vars vals)
    (princ)
)

(princ "\nBatchScript loaded. Type BATCHSCRIPT to run commands over a folder of drawings.")
(princ)

;;; ---------------------------------------------------------------------------
;;; End of file
;;; ---------------------------------------------------------------------------
