An experiment in sending tasks to
PowerShell from CL. The Perl Batcave has this thing lying around:
Win32::PowerShell::IPC. It's possible to steal this technique for Common Lisp.
CCL handles a lot of the tricky OS-level details (like I/O on pipes). But a PowerShell "cmdlet" normally produces an array of objects—which have "properties"—and it can be expedient to get them in the form of S-expressions (or at least lists).
A quick-and-dirty way to do this is to arrange for PowerShell to format the result as one big string, and then parse the output into a Lisp list. This is done by designating two separator characters: the "vertical tab" character as a separator between objects, and the null character to separate properties within an object. These correspond (respectively) to #\PageUp and #\Nul in CCL, or `v and `0 in PowerShell. (Unlike many other languages, PowerShell uses the backtick as an escape character in double-quoted strings.)
The code
Licensed under Apache 2. The external-process management is specific to Clozure CL.
Usage examples
? (open-powershell)
#<EXTERNAL-PROCESS (powershell.exe -Command -)[#<A Foreign Pointer #x7C4>] (RUNNING) #x2101A0216D> Now the *powershell* variable is set to a running PowerShell. Here's an example of piping commands to it: find-big-folders recursively looks for folders containing more than 500 files. Then it returns the full pathname and modification time for each such directory. The run-powershell* function takes care of parsing the command output.
(defun find-big-folders (directory-name)
(run-powershell* ($ "Get-ChildItem '~A' -Recurse ~
| Where-Object {$_.PSIsContainer -eq $True} ~
| Where-Object {$_.GetFiles().Count -gt 500}"
directory-name => "$_.FullName, $_.LastWriteTime"))) Trying this on the directory where the HyperSpec lives:
? (find-big-folders "C:/HyperSpec")
(("C:\\HyperSpec\\Body" "2/12/2020 8:03:53 AM")
("C:\\HyperSpec\\Issues" "2/12/2020 8:04:12 AM"))
Another example: extracting links from an HTML page via Invoke-WebRequest. This will signal an error if the page isn't accessible—namely, a Lisp condition of type powershell-error.
(defun get-links (url)
(run-powershell* ($ "(Invoke-WebRequest '~A').Links"
url => "$_.href, $_.innerHTML"))) This will provide a list of Win32 links:
? (get-links "https://cliki.net/Win32")
(("https://github.com/Zulu-Inuoe/win32" "win32") ("/CFFI" "CFFI")
("https://github.com/Lovesan/doors" "Doors") ("/Windows" "Windows")
("https://github.com/quek/cl-win32ole/" "cl-win32ole")
("/SBCL" "SBCL") ("/CLISP" "CLISP") ("/cffi" "cffi")
("/trivial-garbage" "trivial-garbage")
("https://github.com/ailisp/Graphic-Forms" "Graphic-Forms")
("/Windows" "Windows") ("/BSD" "BSD")
("https://github.com/sharplispers/cormanlisp" "Corman Lisp")
("/Common%20Lisp%20implementation" "Common Lisp implementation")
("/Win32" "Current version")
("/site/history?article=Win32" "History")
("/site/backlinks?article=Win32" "Backlinks")
("/site/edit-article?title=Win32&from-revision=3799349377"
"Edit")
("/site/edit-article?create=t" "Create") ("/" "Home")
("/site/recent-changes" "Recent Changes") ("/CLiki" "About")
("/Text%20Formatting" "Text Formatting") ("/site/tools" "Tools")
("/site/register" "register"))
Then there's the Atom feed format. CLiki uses it. We can use XPath in PowerShell to select feed entries. Since Atom is a namespaced XML format, this requires providing the Namespace option to Select-XML:
(defparameter *atom-entry-properties*
"$_.Node.updated, $_.Node.link.href, $_.Node.content.innerText")
(defun get-atom-entries (url)
(run-powershell* ($ "Select-XML -XPath '//atom:entry' -Namespace @{atom='http://www.w3.org/2005/Atom'} ~
-Content (Invoke-WebRequest '~A')" url => *atom-entry-properties*))) A wrinkle
There doesn't seem to be any obvious way to send Ctrl+C to PowerShell via IPC. So the problem is how to break out of a PowerShell command by signaling from Lisp.
Windows IPC