Call PowerShell Commands from AppleScript/JXA

This will probably work within ASOC too, since do shell script works there. On macOS, the powershell “executable” is pwsh, usually in usr/local/bin/pwsh for the non-beta versions. If you run pwsh -h, you see that the pwsh utility is quite versatile, and by using the -c parameter, you can treat it like osascript, in that you can pass it a single command as a string, or an entire script block. You can also pass it a PowerShell script with the -f command. So to use this from within AppleScript/JXA, (AppleScript syntax shown), it looks like:

do shell script "/usr/local/bin/pwsh -c \"Get-PSDrive\" "

You can also use this with custom modules:

do shell script "/usr/local/bin/pwsh -c \"Get-MacInfo HardwareModelID\" "

So if you want to use a slightly more coherent shell environment like PowerShell from within AppleScript, you can.

Apple and Automation

Boy, there’s a title fraught with weirdness and uncertainty. Like going back to System 7 Pro and the introduction of AppleScript.

But anyway, watching the 2021 WWDC keynote, I realized that for all intents and purposes, Apple doesn’t have a single automation model/framework. It has like elebenty and they ignore each other almost entirely. Want to operate at the user level? You’re going to use AppleScript, Apple’s very weird JavaScript for Automation implemenation, sharing panes, etc. Individual apps may implement OSA (the framework behind AppleScript and Automator) or they may roll their own.

Oh, did I say Automator? Well other than the fact that’s going away in a few years to be replaced by Shortcuts, you could write Automator Actions using like four languages.

Then there’s the various scripting bridge lanugages like AppleScriptObjectiveC, (ASOC), PyObjC…and at the shell level, well, shell, Python, PowerShell if you like…

And other than within Automator, none of these work well together. You can build actual apps in ASOC, but you can’t debug them in Xcode. Well, display dialog and log statements. So 1990s debugging. Roundtripping between shell and AppleScript is annoying and fragile, and you’re doing a lot of write to a file/read from a file.

AppleScript is barely functional outside of the user GUI context and shell doesn’t really work well in there.

And then there’s Shortcuts. Which do a good job of taking over for automator, and surprised me with the ability to run AppleScript/JSX/Shell commands.

But that’s not a unified automation model, and that’s what Apple needs. Along with documentation, because Apple’s Dev/Automation documentation is, to put it as kindly as I can, pants. Like just pants. What they need, and I try to avoid saying that, but in this case, it works, what Apple needs to do is basically steal the idea behind PowerShell and the documentation of powershell.

Before the shellistas come for me, PowerShell is an object-oriented implementation of .NET, which is way, way more powerful for scripting across all OS levels than shell, which assumes everything is text and is tedious as hell for a lot of things. Like anything where text as your i/o medium doesn’t work well. Shell isn’t bad, but PowerShell is better. It also has some things going on that Apple should use.

PowerShell and .NET

Basically, (and I am drastically simplifying), PowerShell is an object language that takes the .NET frameworks and infrastructure, and simplifies it in a way that makes it more useful to scripters. The syntax is very similar to “real” .NET/C# (Even though you can code for .NET in other things, C# is the “default” and I’m going to use them fairly interchangeably here.)That’s not to say PowerShell is “dumbed down”, not by any means. But, it does mean that PowerShell as a language is targeted more at scripting and automation than full app development. For example, (unlike AppleScript), PowerShell has no UI primitives, which is quite the pain in the keister for when you need scripts that interact end users. Things like choosing from a list, etc., are much easier in AppleScript than in PowerShell. For example, in AppleScript, display dialog is a single line, where it is a bit more complicated in PowerShell.

The AppleScript version:

display dialog "You have to be an admin to do this"

Powershell:

$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("You have to be an admin to do this:,0,"Script Info",0x0 + 0x10)

Not hugely more complicated, but still, having to manually create objects that way for simple things is tedium, and the syntax is a bit more awkward.

But, the thing is, PowerShell out of the box gives you access to basically the entire .NET frameworks, which means you have access to basically all of Windows. And, because of Microsoft not only pushing ISVs to adapt PowerShell as an automation language, and leading the way, you get a lot of support for using PowerShell throughout windows.

PowerShell is also regularly updated, and they’ve even managed to do this in a way that breaks very little. Now, that can be a pain, because the current version is 7.x and Windows, by default, ships with 5.1, but still, you can easily get 7.x. In addition, thanks to things like PowerShell Get, they have a whole setup to add new packages as needed via different “official” and unofficial repositories, something that to my knowledge, Apple never even attempted with any of their automation tools. Not even shell, not really. Maybe sorta/kinda Swift, but still, very thin.

So with PowerShell you have a consistent OS-wide object model that works at all levels, remotely, Application automation, whatever you need. It has a way to create modules for packaging up well, libraries to be easier to use for other folks. Creating modules is far too painful for its own good, but it’s there.

And then, there’s the documentation, and this my friend, is where PowerShell shines. Like Apple has nothing close to the level of documentation, not at the API/Language level, and my god, not even close to the implemenation level. Apple has literally nothing close to this: https://devblogs.microsoft.com/scripting/, and the old “Hey, Scripting Guy!” blog has, now that the guy who created it retired, been turned into a community resource. Here: https://devblogs.microsoft.com/scripting/heyscriptingguyupdate/

On the best day of its life, Automation at Apple never. ever. ever. had that level of support for implementation help just there on a web site. (DTS never supported automation outside of low-level ObjC/Swift for creating OSA implementations in apps. Even ASOC, which mind you, is still a part of Xcode 13 gets you no DTS support, just a “go use a mailing list/forums.” response. Boy, that makes me want to take the time to learn ASOC. Even better, Xcode has NEVER had any debugging support for ASOC. sigh) But that’s not all. The actual language, model, and implementation documentation for PowerShell is light years ahead of what Apple has, especially in terms of consistency.

Apple did, a few years back, finally, finally port the AppleScript language guide from a PDF to a part of the Apple Developer site, and that is pretty solid, because it was written a long time ago when Apple’s core AppleScript team was more than like three people, (and I may be overestimating the current size of that team), but that’s it. There’s nothing compared to what PowerShell has going on. For example: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-host?view=powershell-7.1

While you’re looking at that, take a gander at the lefthand column. Note that it shows different versions of PowerShell. Yeah. Apple doesn’t even document ObjC and Swift that well, much less automation. Also, at least with PowerShell, I have full access to the clipboard. Apple still can’t give us that, or won’t, or whatever. That dates back to System 7 Pro I think. So what, 20 years? 30? Meanwhile, PowerShell has a full suite of clipboard commands. That’s what taking automation seriously does.

So the upshot is, because PowerShell is based on .NET, and gives you such access to it, as you find yourself outgrowing PowerShell, moving into .NET, or mixing in full .NET commands as needed is really easy. Way easier than even moving from ASOC to ObjC or Swift. Like right now, I might be able to code macOS apps easier with .NET than I can with Swift, because I’m more familiar, thanks to my PowerShell work, with .NET than I am with Swift. Given my background with writing code for both macOS and Windows, that’s really sad. But Microsoft gives you that path, Apple does not.

That is something that Apple has, to be frank, failed at. Miserably. Apple’s support, as a company for keeping their automation frameworks and implementations consistent and supported and using them themselves is really quite awful, and as a result, no one else has consistently cared. I doubt it’s even occurred to Apple to structure things so you could move easily from AppleScript to ASOC to ObjC/Swift. I can damned sure tell you Apple has nothing out there to help you with macOS automation from an educational perspective. Which is why, even though the new macOS Shortcuts look cool, I’ve real doubts, because I’ve seen this same thing from Apple before with regard to automation:

Apple: NEW AUTOMATION THING

Customers: Oh cool, where’s the documentation?

Apple: THERE IS NONE

Customers: Oh…okay, are you going to have any courses or training for it?

Apple: OF COURSE NOT

Customers: Okaaay…you going to lead the way and use this internally and in your own applications?

Apple: DON’T BE SILLY, WE’D NEVER DO THAT

Customers: …you going to support people building solutions and systems with this beyond stuff like forums and mailing lists?

Apple: NOT A CHANCE

Customers: …you ever gonna update this?

Apple: LMAO, NO WAY

Customers: …

Apple: NEW AUTOMATION THING

The list here is long. Again, for ASOC, which has been a part of Xcode since…10.5…you can’t even debug it. Even though, there’s actual AppleScript/ASOC IDEs that will debug AppleScript, aka Script Debugger.

So even though Shortcuts on macOS looks really cool, based on past history, it’s going to get ignored as anything other than a way to run iOS Shortcuts on a Mac.

I would love to be wrong here, but Apple has been jerking people around hard in terms of OS automation on the Mac for longer than some of its current employees have been alive. (I’m not exaggerating here, AppleScript is 28 years old, introduced in 1993.) So telling me I have no reason to be so doubtful is going to get you a very long rant.

However…let us pretend that Apple is serious here. That they want to fix the deficiencies. Let’s take a look at those.

Current Issues with macOS Automation

The biggest issue the macOS has is that unlike Windows, there’s no consistent way to do stuff. If you want to do interapplication automation, there’s AppleScript, JavaScript (multiple implementations depending on application developer), custom application-specific stuff, some shell and python.

AppleScript actually has a decent object model, most of the inconsistencies come from the number of different applications supporting it. There’s no way to avoid that, ISVs are going to do things differently. No syntax in the world makes up for that. As well, syntax preferences, for the most part, are personal and a lot of the hate for AppleScript as a syntax is just the worst kind of elitism and nerd gatekeeping.

The problem with the object model is that it really is only designed to work locally, in the context of a logged in user. Remote Apple Events have always been ugly, and a security issue. Even then, Remote Apple Events are simply not designed for a Unix-based OS. Unsurprising, given they predate OS X by about half a decade.

For remote stuff, you’re now in the shell environment, which is its own mess, since you may be using shell, you may be using python, SQL…and the security on those ain’t that great either. Relying on SSH keys et al causes its own special set of problems. Then there’s exchanging data between AppleScript/Apple Events and the shell. Ugh. Yes, you have a lot of options, but after a while, it’s all just more ways of shooting yourself in the foot, with wildly inconsistent syntax and no organized, secure way to communicate across the network.

Oh, and it doesn’t help that the OSA stuff uses Apple Events to exchange data and the shell environment thinks everything is text. sigh

Then there’s the previously detailed issues with Apple’s lack of support for this beyond the barest of efforts. Aka, Apple is lazy as hell some times.

So how do you fix it?

Swiftscript

Yeah, stupid name, but I’m not in marketing. So there’s a few things we want out of real automation on macOS (and this could easily carry over to i(Pad)OS et al. More easily than the current…stuff.)

The current list:

  1. An OS-wide object model. This, by the way, already exists, it’s the Cocoa frameworks. We literally have a solid foundation for an updated object model that can be used between applications, at the shell level, at the user level, it’s all there already. Even has a cool name: Cocoa. Okay, it already has an accepted name.
  2. A consistent OS-wide way to pass data. This is a big weakness of the current system. Honestly, I think adapting the way PowerShell does things and doing a major revamp on the OSA object model so that it extends down into the core os is not just more painless way to go, it’s the way to go that will last the longest. We already can exchange text and objects, let’s just create a single model that creates a consistent, known, documented and supported way of doing this. Since it’s based on Cocoa, it’d work pretty well for i(Pad)OS as appropriate.
  3. Security. Since this is going to be slanging data o’er the land, it should just be encrypted from the start. Apple has a nice infrastructure for this already, so this would be an extension of that. This would also make it easier to handle transparent, effortless secure remoting, and yes, it has to be effortless. The most secure way, (and not that Windows PowerShell ExecutionPolicy idiocy) has to be the default. Build it right the first time, your life gets so much easier.
  4. The syntax should basically be Swift, only like how PowerShell:.NET. That’s pretty braindead, and yes, I know what this would mean for AppleScript, but part of this has to be using Swiftscript as a way to move into “full on” application development in a way that’s not as…Swift Playgrounds are too artificial, I can’t use them to solve the problems I have in my day to day. The Swiftscript syntax being similar, although not identical to Swift would be a huge help. That’s not to say you jettison AppleScript completely. There’s a few concepts from that language that work really well, for example, directing code at an application via a tell block.

    tell application “Finder”
    blah blah blah
    end tell

    may not appeal to the Swift purists, but a) I don’t care about them, and b) it’s pretty intuitive. I’m telling the Finder to do stuff. That’s pretty straightforward. Also, I like “Using terms from” where you have to intermingle code from different applications (a problem you don’t see in Swift now, because that’s not a use case.) It’s a quick way, within an existing code block or a tell block to jam in a bunch of code for a different application. Again, straightforward. What am I doing here? I’m using terms from Microsoft Word.

    Also take a good look at just how the current OSA object model works. It’s old, but it really works well. It needs attention to be sure, but it does a lot of things right. Don’t be afraid to plagiarize yourself, you’re the best source for your own needs.
  5. Documentation and Support. To be fair, Apple’s existing developer documentation is kind of meh anyway, so use this as an excuse to fix that too. Hire technical writers and let them do that. I know Apple has the money, it’s a will issue, not a resource issue. Add Swiftscript into DTS. For the first time, put some real wood behind that damned arrow. Like seriously.
  6. Dogfood it. Apple is horrible here. (I have receipts, please save yourself some grief, don’t challenge me on this.) Everything Apple does has to have the best scripting support in the macOS ecosystem. Period. Again, Apple has the resources, they just don’t care enough to actually do it.
  7. Evangelize it. Like y’all need to create a legion of Chloe Condons and let them loose. Not just at Apple events. Y’all need to be everywhere, and annoyingly so.

I think that covers most of it. Like you don’t have to jack things up too much. Just have this be a slightly higher level of Swift. For example, as AppleScript does now, the runtime should support all the core OS functions (think Standard Additions + Core Language + System Events here) without having to reference any other modules. If a module is needed, consider the PowerShell concept where you add the module/framework to your setup and then just use the methods and properties it brings in. If a script detects a missing module that’s part of the standard OS build, offer to add it in to that person’s setup.

Oh, right, almost forgot:

  1. Create a repository for third-party modules. You already know how to do this, it’s called the App Store. Like Swift packages, only not full-on that. But a place for people to contribute and access third-party modules that they can trust. Only maybe be more transparent. Somtimes, y’all’s App Store shit is extra.
  2. If you wanted a way for third-party languages to plug into this, that wouldn’t be awful.

Is any of this easy? No. But if Apple wants people to take new things like Shortcuts for macOS and whatever else is coming down the pike seriously, then Apple has to do more than the “if we build it and sort of make it possible to use, maybe they’ll come…whatever” stuff they’ve been doing for the last three decades. There’s a lot of ill-will with “official” Apple automation initiatives, and honestly, it’s fully and totally justified. Apple is just the master of the half-assed effort here.

And, if they really want to make automation work the way it should on the OS, well, I started a solution here. There’s good examples in wide-scale production use now (POWERSHELL) that can be improved upon and made more suitable. Apple has the money, the question is, do they have the fucks?

Create New File

Making a Service/Context Menu and working around a bug

So in the macOS & iOS group on Facebook, someone was talking about how they miss the ability to just create new blank files of various kinds without needing the app. The replies were, sadly, about what you expect. Non-sequiters about iWork, “you can do this in shell with ease” without actually showing how, and one reference to an application that does make this easy.

So I got to thinking and was like, wait, this can’t be that hard. It turned out to be not that hard, once I realized there’s a bug in the Finder’s scripting implementation. (Shocking, right?). My first attempt at the code, which one would think would work was:

Tell application "Finder" to make new file with properties {file type:"TEXT", name extension:"txt"} at (the target of the front window) as alias) 

Great idea, but fails, because there’s a bug in the Finder scripting implementation where setting the name extension property like this is allowed, i.e., it’s not a syntax error, but it doesn’t actually do anything. Since I want to make sure the file type and the extension are okay, I thought “Okaaay, let me report this bug, but first, I’ll just add a line that sets the extension as a separate step, so we get:

tell application "Finder"

set theFile to (make new file with properties {file type:"TEXT", name extension:"txt"} at (the target of the front window) as alias)

set name extension of theFile to "txt"

end tell

This works, but sort of. Well, every other try. Here’s the folder contents you see to illustrate (not including the tell statements):

first line results: untitled

second line results untitled.txt

Second run:

first line results: untitled (second file)
untitled.txt (first file)

The second line fails because it’s trying to add “.txt” to the second file, which would create two files named “untitled.txt” which causes a -49 error, “already a file with that name”

If you change nothing, and run the script a THIRD time, you get:

first line results: untitled (second file)
untitled.txt (first file)
untitled 2 (third file)

If you run the script again, the cycle repeats. That’s annoying, but it also means if you add a static character to the end of the file name, you still have the problem when you have to add the extension.

So what I finally came up with was:

tell application "Finder"
set theTime to (time of (current date)) as text

set theFile to (make new file with properties {file type:"TEXT", name extension:"txt"} at (the target of the front window) as alias)

set theName to name of theFile & "_" & theTime & ".txt"
set name of theFile to theName
end tell

so first, we get theTime as a string, which is the string version of an integer that represents the number of seconds since midnight. Not intuitive, but, unless you run the script more than once a second, you’re pretty safe. Since this is designed to be a context menu item/service menu item, that’s unlikely.

Now we create the new blank file, unchanged, because hey, one day it might work, Apple might fix an AppleScript bug (just pretend it might happen), and so that line is set.

Next, we create a new string for the name, which includes the extension as part of the name. Playing with the name extension property is kind of fragile, this works more reliably and accomplishes the goal. We add theTime to the end of the name after an underscore, so the name is always unique

Next, set the file’s name to this name we just created, and we’re done!

To make this into a context menu item, usable when you ctrl-click on a folder, you create a new automator action for the Finder, set it to work with files and folders, and you’re set. But there’s a place where this won’t work well: the actual desktop. If you just ctrl-click on the Desktop, no love. If you open a Finder window, go to your home directory, then right-click on the desktop there, this script tends to fail with an error -1700 because it can’t use the Desktop the way we’re trying to use it.

I don’t know why, but I also don’t care, because the workaround is mostly simple. Here’s the code:

tell application “Finder”

set theTime to (time of (current date)) as text

try

set theFile to (make new file with properties {file type:"TEXT", name extension:"txt"} at (the target of the front window) as alias)

on error errMsg number errNum

if errNum is -1700 then

set theFile to (make new file with properties {file type:"TEXT", name extension:"txt"} at desktop as alias)

end if

end try

set theName to name of theFile & "_" & theTime & ".txt"
set name of theFile to theName
end tell

The new lines are the try/end try block. We try to create the new file in the “normal” location, if that fails, we assume this is because it’s the Desktop folder (the only folder I’ve hit that for), so if the errNum is -1700 (integer, not text), we then slightly modify the new file line to create it using the built-in “desktop” location that the Finder’s dictionary knows about. Once that’s done, we dump out of the try block and the last lines are just the same as the other version.

This service in automator is still a Finder action, but takes no inputs. So it never shows up in the context menu, but it is available via the “Services” menu in the Finder’s “Finder” menu. It’s not as great as I’d like, but it gets the job done, and you can create different versions of these for whatever file you want, word, excel, jpeg, whatever.

It’d be nice if Apple fixed this bug, because honestly, this should be one line to create the file. I’m undecided if the -1700 error issue is a bug or just something I dislike.