Jump to content

multiple file selection dialog box using lisp & powershell


Recommended Posts

Posted

Attached is an  improved version of getfiled that implements powershell with the option to select single or multiple files returning a list of the selected item(s):

Examples:

(pl:getfiledps "Select Files" (getvar"dwgprefix") "dwg" 0) ; for single selection of file type dwg
(pl:getfiledps "Select Files" (strcat (getenv "userprofile") "\\downloads") "dwg;dxf" 1) ; for multiple selection of file types dwg & dxf

Feedback & comments are welcome.

image.thumb.png.99afddbce9f2bbe7040f12a82ceae770.png

pl_getfiledps.lsp

  • Like 5
Posted

Many work computers are likely limited for using Powershell.

 

Unrelated to Powershell being blocked, I get an error loading.

 

 error: string too long on input

 

Posted

String too long - just a guess, file path and file name string length? - going back old school when we had 256? characters for it all - but just a guess

 

Posted

I'm not going to troubleshoot it since Powershell is blocked at work, but if the load has that error, then it's something in the Code, probable in the DCL write-line part.

  • Like 1
Posted

Thanks for troubleshooting. That could definitely be caused by having a write-line statement that's way too long.

I've made the changes to reduce the write-line on the dcl. Kindly troubleshoot some more for offices that do allow for running powershell with the attached revised version...thanks in advance.

pl_getfiledps.lsp

Posted (edited)

One observation: in older versions of AutoCAD (2000 and 2002, at least), 'vla-get-HWND' applies to the Document object rather than the Acad object.

Edited by GLAVCVS
  • Like 1
Posted (edited)

Works great often do old fashioned DOS to get file names list or use the VL files functions. 

 

A suggestion remove "result" from the local variables list. As the answer disappears.

Or 

; Example:
;  (setq files (pl:getfiledps "Select Files" (getvar"dwgprefix") "dwg" 1))

 

A side comment I have used Powershell with Bricscad and sometimes does not work. Your code may help as you do a deeper dive into running Powershell. I generally only do a "Power shell and script" I have used it to change programs, I jump to Excel from CAD. 

Edited by BIGAL
  • Like 1
Posted
On 9/14/2026 at 5:28 AM, SLW210 said:

I'm not going to troubleshoot it since Powershell is blocked at work, but if the load has that error, then it's something in the Code, probable in the DCL write-line part.

Could you test if this PowerShell-p function would return nil on your powershell blocked computers?

; PowerShell-p function to check if powershell is available to run...Returns T if available
(defun PowerShell-p (/ shell result)
  (setq shell (vlax-create-object "WScript.Shell")
        result (vl-catch-all-apply 'vlax-invoke-method (list shell 'Run "powershell.exe -NoProfile -Command \"exit 0\"" 0 T))
  )
  (vlax-release-object shell)
  (and
    (not (vl-catch-all-error-p result))
    (= result 0)
  )
)

 

Posted
17 hours ago, GLAVCVS said:

One observation: in older versions of AutoCAD (2000 and 2002, at least), 'vla-get-HWND' applies to the Document object rather than the Acad object.

so does the pl:getfiledps function still run properly on those older AutoCAD versions?

Posted

 

21 hours ago, GLAVCVS said:

One observation: in older versions of AutoCAD (2000 and 2002, at least), 'vla-get-HWND' applies to the Document object rather than the Acad object.

 

4 hours ago, Paul Li said:

so does the pl:getfiledps function still run properly on those older AutoCAD versions?

 

Yes, I was working on that for my home computer with 2000i.

 

Since I had some home time today with AutoCAD 2000i, I did some updating to your code. Seems to work great with minimal testing.

 

Here is my Test Code...

 


; ================================================================
; Test command
; ================================================================

(defun c:SelectDWGs (/ files)

  (setq
    files
    (pl:getfiledps
      "Select DWG Files"
      (getvar "DWGPREFIX")
      "dwg"
      1
    )
  )

  (if files

    (progn

      (princ
        "\nSelected files:"
      )

      (foreach file files

        (princ
          (strcat
            "\n"
            file
          )
        )
      )
    )

    (princ
      "\nNo files selected."
    )
  )

  (princ)
)

 

pl_getfiledps_2000i.lsp

  • Like 3
Posted

@SLW210 Your revised version works beautifully and can now support older versions of AutoCAD...thanks a bunch!!!

Posted (edited)
22 hours ago, Paul Li said:

so does the pl:getfiledps function still run properly on those older AutoCAD versions?

 

Yes: as @SLW210 says, it seems to work well.

 

On the other hand:

Have you considered having the script written only if it does not already exist in the expected directory, and keeping PowerShell running from the initial load?

This would make the dialog box load faster on subsequent calls.

In this regard, the script check/creation could be done when the code is loaded. This would also improve the speed of the first execution slightly.

Edited by GLAVCVS
  • Like 1
Posted

@GLAVCVS thanks for the feedback. I assume to use a single Powershell session you would have to run a batch file that launches Powershell first before starting autocad? Then when autocad closes or crashes out that session of Powershell also closes.

  • Like 1
Posted

I added a note in the file where to change to keep PowerShell open.

 

; ----------------------------------------------------------
      ; Wait until PowerShell finishes.
      ; ----------------------------------------------------------

      (vlax-invoke-method
        shell
        "Run"
        cmd
        0                                         ; <=== Change 0 to 1 if you want PowerShell to stay open. 
        :vlax-true
      )

      (vlax-release-object shell)
    )
  )

 

I was running some tests at work to see if there was a method to determine if PowerShell was blocked to give a message...

 

    (princ "\nPowerShell access is blocked.")
    (princ "\nPowerShell access is allowed."))

 

Right now all I get on the commandline is...

 

error: Automation Error. Description was not provided.

 

 

Though I had to get to some of my "paid to do" work done yesterday.

 

This is probably above my pay grade.

 

I just realized who the OP is, a big time Guru over at Autodesk forums and AUGI.

  • Like 2
Posted
On 9/16/2026 at 9:15 PM, Paul Li said:

@GLAVCVS thanks for the feedback. I assume to use a single Powershell session you would have to run a batch file that launches Powershell first before starting autocad? Then when autocad closes or crashes out that session of Powershell also closes.

You can start a PowerShell instance from LISP and keep it running in the background, ready to receive instructions.

I've played around with this idea a little and ended up with the following code.

I haven't tested it thoroughly enough, so its robustness could probably be improved.

But it may be useful.

 

(defun PS-START (/ fp sh cmd n)

  (vl-load-com)
  ;| --------------------------------------------------------------
   Si existe READY, suponemos de momento que ya está funcionando.
   Posteriormente podemos hacer PS-ALIVE más rigurosa.
  |; --------------------------------------------------------------

  (setq *PS-SERVER* (strcat (getenv "TEMP") "\\PL_PS_Server.ps1")
	*PS-TCMD*    (strcat (getenv "TEMP") "\\PL_PS_Command.tmp")
	*PS-CMD*    (strcat (getenv "TEMP") "\\PL_PS_Command.txt")
	*PS-OUT*    (strcat (getenv "TEMP") "\\PL_PS_Output.txt")
	*PS-READY*  (strcat (getenv "TEMP") "\\PL_PS_Ready.txt")
  )
  (if (findfile *PS-READY*)
    T
    (progn
      ;|Clean previous files / Limpiar archivos anteriores|;
      (if (findfile *PS-CMD*)
        (vl-file-delete *PS-CMD*)
      )
      (if (findfile *PS-OUT*)
        (vl-file-delete *PS-OUT*)
      )


      ;|Create server script / Crear script servidor|;
      (setq fp (open *PS-SERVER* "w"))

      ;|Nombres de los archivos de comunicación|;
      (write-line "$cmd = $env:TEMP + '\\PL_PS_Command.txt'" fp)
      (write-line "$out = $env:TEMP + '\\PL_PS_Output.txt'" fp)
      (write-line "$ouTmp = $env:TEMP + '\\PL_PS_Output.tmp'" fp)
      (write-line "$ready = $env:TEMP + '\\PL_PS_Ready.txt'" fp)


      ;|Indicate Powershelll ready / Señalizar que PowerShell está preparado|;
      (write-line "Set-Content -Path $ready -Value $PID -Encoding Default" fp)

      ;|Main loop / Bucle principal
       Powershell remains here for entire session / PowerShell permanece aquí durante toda la sesión|;
      (write-line "while ($true) {" fp)

      ;|Is there any pending request? / ¿Hay una orden pendiente?|;
      (write-line "    if (Test-Path $cmd) {" fp)

      ;|Read ALL code / Leer TODO el código.
        -Raw permite que PS-EXEC pueda mandar scripts de varias líneas, no solamente una instrucción
      |;
      (write-line "        $code = Get-Content -Path $cmd -Raw -Encoding Default" fp)

      ;|Delete request inmediately / Borrar la petición inmediatamente. Esto indica que el servidor ya se ha hecho cargo de ella.|;
      (write-line "        Remove-Item $cmd -Force" fp)

      ;|Orden para terminar el servidor|;
      (write-line "        if ($code.Trim() -eq '__EXIT__') {" fp)
      (write-line "            break" fp)
      (write-line "        }" fp)

      ;|Execute code IN THIS POWERSHELL SESSION / Ejecutar código EN ESTA MISMA SESIÓN POWERSHELL|;
      (write-line "        try {" fp)
      (write-line "            $result = Invoke-Expression $code" fp)

      ;| OUT must always be created so that PS-EXEC knows the process has finished
       Hay que crear siempre OUT. Incluso si la expresión devuelve $null necesitamos crear
       el archivo para que PS-EXEC sepa que hemos terminado.
      |;
      (write-line "            if ($null -eq $result) {" fp)
      (write-line "                Set-Content -Path $ouTmp -Value '' -Encoding Default" fp)
      (write-line "            }" fp)
      (write-line "            else {" fp)
      (write-line "                $result | Out-File -FilePath $ouTmp -Encoding Default" fp)
      (write-line "            }" fp)

      (write-line "            Move-Item $ouTmp $out -Force" fp);rename PL_PS_Output.tmp / renombramos PL_PS_Output.tmp
       
      ;|Capturar errores|;

      (write-line "        }" fp)
      (write-line "        catch {" fp)
      (write-line "            ('__ERROR__ ' + $_.Exception.Message) | Out-File -FilePath $out -Encoding Default" fp)
      (write-line "        }" fp)

      ; close / cerrar 'IF Test-Path'
      (write-line "    }" fp)

      ;| Sleep / Demorar 10 ms.
        Prevents resident Powershell instance from continuously using a CPU core while it has nothing to do
        Evita que el PowerShell residente utilice continuamente un núcleo de CPU mientras no tiene nada que hacer
      |;

      (write-line "    Start-Sleep -Milliseconds 10" fp)

      ; cerrar WHILE
      (write-line "}" fp)

      ;|PowerShell está terminando|;
      (write-line "Remove-Item $ready -Force -ErrorAction SilentlyContinue" fp)

      (close fp)


      ;|Arrancar PowerShell|;

      (setq cmd
        (strcat
          "powershell.exe "
          "-NoLogo "
          "-NoProfile "
          "-NonInteractive "
          "-STA "
          "-ExecutionPolicy Bypass "
          "-WindowStyle Hidden "
          "-File \""
          *PS-SERVER*
          "\""
        )
      )

      (setq sh (vlax-create-object "WScript.Shell"))
      (vlax-invoke-method sh 'Run cmd 0 :vlax-false)

      (vlax-release-object sh)


      ;| Esperar a que PowerShell cree READY
         Máximo: 5 segundos
      |;

      (setq n 0)

      (while
        (and
          (not (findfile *PS-READY*))
          (< n 500)
        )
	(repeat 10000 (getvar "MILLISECS"))
	(princ "\rEsperando...")
        (setq n (1+ n))
      )


      ;|Result / Resultado|;
      (if (findfile *PS-READY*)
        T
        nil
      )
    )
  )
)



(defun PS-EXEC (code / fp lin result t0)

  ;| Clears any previous result / Borrar posible resultado anterior|;
  (if (findfile *PS-OUT*)
    (vl-file-delete *PS-OUT*)
  )

  ;| Write code that Powershell should executed / Escribir el código que debe ejecutar PowerShell|;
  (setq fp (open *PS-CMD* "w"))
  (write-line code fp)
  (close fp)

  ;|Waiting for a Powershell response / Esperar a que PowerShell responda|;
  (while (not (findfile *PS-TOUT*));(not (findfile *PS-OUT*))
    (setq t0 1000)
    (repeat 1000 (setq t0 (1+ t0)))
  )

  ;|Reading response / Leer respuesta|;
  (setq fp (open *PS-TOUT* "r"));(open *PS-OUT* "r"))
  (while (setq lin (read-line fp))
    (setq result (cons lin result))
  )
  (close fp)

  ;| Delete result / Borrar resultado|;
  (vl-file-delete *PS-OUT*)

  (reverse result)
)


(defun PL-FILEDIALOG-INIT (/ hwnd)

  ;; HWND de ESTA instancia concreta de AutoCAD
  (setq hwnd
    (vla-get-HWND (if (minusp (- (atoi (getvar "ACADVER")) 16)) (vla-get-activeDocument (vlax-get-acad-object)) (vlax-get-acad-object)))
  )

  ;; Windows Forms + OpenFileDialog
  (PS-EXEC
    (strcat
      "Add-Type -AssemblyName System.Windows.Forms;"
      "$script:f=New-Object System.Windows.Forms.OpenFileDialog"
    )
  )

  ;|Small 'wrapper' to convert AutoCAD's HWND into a valid 'IWin32Window' for ShowDialog(owner)
   Pequeño 'wrapper' para convertir el HWND de AutoCAD en un 'IWin32Window' válido para 'ShowDialog (owner)'
  |;
  (PS-EXEC
    (strcat
      "Add-Type -TypeDefinition "
      "'using System;"
      "using System.Windows.Forms;"
      "public class WindowWrapper:IWin32Window{"
      "private IntPtr h;"
      "public WindowWrapper(IntPtr hwnd){h=hwnd;}"
      "public IntPtr Handle{get{return h;}}"
      "}' "
      "-ReferencedAssemblies System.Windows.Forms"
    )
  )

  ;|Locate AutoCAD and create owner
   Localizar AutoCAD y crear el 'owner'
  |;
  (PS-EXEC
    (strcat
      "$script:owner=New-Object WindowWrapper("
      (itoa hwnd)
      ")"
    )
  )

  T
)


(defun pl:getfiledps (title dir ext flg / psquote makefilter code)

  ;| Escape / Escapar ' PowerShell|;
  (defun psquote (s / p n)
    (setq n 0)
    (while (setq p (vl-string-search "'" s n))
      (setq s (strcat (substr s 1 p) "''" (substr s (+ p 2)))
	    n (+ p 2)
      )
    )
    s
  )

  (defun makefilter (s / p r x)
    (setq r "")
    (while (setq p (vl-string-search ";" s))
      (setq x (substr s 1 p)
	    r (strcat r "*." x ";")
	    s (substr s (+ p 2))
      )
    )
    (strcat r "*." s)
  )

  ;|Default values / Valores por defecto|;
  (if (/= (type title) 'STR)
    (setq title "Select Files")
  )

  (if
    (not
      (and
        (= (type dir) 'STR)
        (vl-file-directory-p dir)
      )
    )
    (setq dir (getvar "DWGPREFIX"))
  )

  (if (= (type ext) 'STR)
    (setq ext (makefilter ext))
    (setq ext "*.dwg")
  )


  ;| Build only the code needed for this call: Powershell, Forms, f and owner ALREADY EXISTS
     Construir únicamente el código necesario para ESTA llamada: PowerShell, Forms, f y owner YA EXISTEN |;

  (setq code
    (strcat
      "$script:f.FileName='';"
      "$script:f.Title='"
      (psquote title)
      "';"
      "$script:f.InitialDirectory='"
      (psquote dir)
      "';"
      "$script:f.Filter='Files ("
      ext
      ")|"
      ext
      "';"
      "$script:f.Multiselect="
      (if
        (and
          (= (type flg) 'INT)
          (/= flg 0)
        )
        "$true;"
        "$false;"
      )
      "$r=$script:f.ShowDialog($script:owner);"
      "if($r -eq "
      "[System.Windows.Forms.DialogResult]::OK)"
      "{$script:f.FileNames}"
    )
  )


  ;| Execute in the resident Powershell instance / Ejecutar en PowerShell residente |;
  (PS-EXEC code)
)

;|If Powershell is not already running, start a instance / SI POWERSHELL NO ESTÁ INSTANCIADO, SE CREA UNA INSTANCIA|;
(if (not *PS*) (progn (setq *PS* (PS-Start)) (PL-FILEDIALOG-INIT)))

 

How does it work?...

When the code is loaded, a PowerShell instance and a base script are created.

After that, simply call the pl:getfiledps function whenever you need it.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...