Application Scripting is Weird

There’s a tendency in the Apple world to paint AppleScript as some uniquely weird inconsistent language. I’m usually amused by that, because then the person doing that will use shell as an example of a consistent language. Which is highly amusing.

But here’s the the thing: the core language, the core AppleScript syntax is really quite consistent. It’s when you get into scripting applications that things get weird, because app devs are not consistent in how they implement things.

So let’s take a look at it via Excel, which has the advantages of being scriptable in wildly different languages on a different platform. We’re going to do a fairly simple set of steps:

  1. Open an excel file
  2. Set a range of columns to be formatted as a table
  3. sort that table by the first column ascending

AppleScript

Here’s how we do this in AppleScript:

set theExcelFile to "pathtofile" as POSIX file
tell application "Microsoft Excel"
     activate
     open theExcelFile
     set theWorkbook to the active workbook
     set theWorksheet to active sheet of theWorkbook
     set theRange to get entire column of range ("A:H")
     set theList to make new list object at theWorksheet with properties {range object:theRange}
     set theSort to sort object of theList
     set theSortRange to sortrange of theSort
     sort theSortRange key1 (column "$A:$A" of theSortRange) order1 sort ascending
end tell

I mean, if you know Excel, and how “Format as Table” actually creates a list object, and sorts are weird within a table/list object, and you script Excel a LOT, this makes sense. You:

  1. Create the path to the file
  2. Tell Excel to start/activate
  3. Tell Excel open the file
  4. Create a reference to the active workbook
  5. Create a reference to the active (work) sheet of the active workbook reference
  6. Create a range of columns
  7. Make a new list object (format as table) in the active sheet for the range you just created and create a reference to that list object
  8. Create a reference to the built in sort object of the list object
  9. Create a reference to the sortrange of that sort object reference
  10. sort the sortrange reference by the first column of the sortrange in ascending order

Okay, so what about say, PowerShell on windows? That has to be way less application-specific right? Surely it’s not that weird…

Powershell

$fileToOpen = "fullpathtofile" 
$excelObject = New-Object -ComObject Excel.Application 
$excelFileObject = $excelObject.Workbooks.Open($fileToOpen)
$excelFileWorksheet = $excelFileObject.ActiveSheet 
$excelFileWorksheetRange = $excelFileWorksheet.Range("A1","H1").entireColumn 
$excelFileTableObject = $excelFileWorksheet.ListObjects.add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange,$excelFileWorksheetRange,$null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes)
$excelFileTableObject.Sort.SortFields.add($excelFileTableObject.Range.Columns.item(1),0,1)
$excelFileTableObject.Sort.Apply() (actually perform the sort)
$excelObject.Visible = $true 

Obviously this is totally different, because here we:

  1. Create the path to the file
  2. Tell Excel to start/activate
  3. Tell Excel to open the file
  4. Create a reference to the active workbook
  5. Create a reference to the active (work) sheet of the active workbook reference
  6. Create a range of columns
  7. Make a new list object (format as table) in the active sheet for the range you just created and create a reference to that list object
  8. Create a sort object made up of the list object specifying the column to search on and how
  9. Apply the sort object to the list object
  10. Actually make the excel file visible

Oh yeah, that’s totally different and that syntax is just as bog-standard PowerShell as can be, unlike that Excel AppleScript which has nothing to do with core AppleScript. 🙄

If you were to do the same thing in Numbers, you’d see a similar syntax, because applications have specific needs that a core language for an OS does not. Any language that can be extended to fit the needs of a specific application is going to get weird based on the needs, features, and naming conventions of the application. For example, “Table” in Excel can cover a lot of very different things. “Table” in Numbers covers basically one thing, it’s not like Numbers has Pivot Tables. So doing table operations in Numbers is similar, but not identical to format as table in Excel.

Any language supporting application scripting is going to get weird as more applications use it. It’s the unavoidable nature of the beast.

Advertisement

Sometimes, High-Level Languages don’t suck

So recently, as an exercise, I wanted to see about moving one of my simpler ASOC (AppleScript ObjectiveC) applications to a different language. Then I thought, why not two? So the first one I’ve been working with is C# via Visual Studio on the Mac (the 2022 preview). It’s um…interesting. First, you still need Xcode to do all the UI setup. I’m not sure why, and it’s a bit janky, but it’s not bad per se.

I think the Xamarin docs need a lot more sample code, it’s really intimidating if you’re new to the platform. But anyway, part of this app, ScutilUtil (https://github.com/johncwelch/ScutilUtil) uses the scutil command to pull info about the computer’s host name. (There may be a “proper” way to do it, but that’s not worth the search when scutil is right there.) To do so, you run scutil with various switches. i.e. /usr/sbin/scutil --get ComputerName to get the computer name.

In ASOC, this is trivial, literally one line thanks to AppleScript’s ‘do shell script’ command:

set my theCurrentComputerName to do shell script "/usr/sbin/scutil --get ComputerName"

Easy-peasy. In C#/Xamarin…sigh. The best way I found was:

Process scutilTest = new Process();
scutilTest.StartInfo.UseShellExecute = false;
scutilTest.StartInfo.RedirectStandardOutput = true;
scutilTest.StartInfo.FileName = "/usr/sbin/scutil";
scutilTest.StartInfo.Arguments = " --get LocalHostName";
scutilTest.Start();
string localHostName = scutilTest.StandardOutput.ReadToEnd();
scutilTest.WaitForExit();      

Like, it works right? But really? I mean, yes, this is a very cross-platform way to do it, but all that to run a single one-line command…it seems unelegant, and it seems unelegant on any of the platforms C#/Xamarin run on. Like, macOS/Linux/Windows all have a command line environment. They all have command line utilities that ship with the OS you might want to use because they’re there, and pretty fast.

Why make calling them so hard? Why not have something that gets you closer to do shell script. I mean, out of all of them, the only command/script environments you will never see on all three is cmd.exe, that’s windows-only and AppleScript is macOS – only, (but not something you’d run often in cases like this.) But shell? All three can have it. PowerShell? All three.

So wouldn’t it make more sense to have a way to test for the presence of a command environment, and a way to just use a command environment? Like say:

string localHostName = RunCommand -Environment zsh -Command "/usr/sbin/scutil" -Arguments "--get localHostName" -NoNewWindow -Wait

I’m using PowerShell-ish syntax here, because VS and .NET et al, but you get the idea. You could even have a way to check for a specific command environment if you wanted to be fancy, etc. Again, all the non-phone/non-tablet .NET platforms have a command-line environment of some form. Why not just allow for that to be used easily?

It’s times like these I wish Visual Studio Mac supported PowerShell as a language. Sigh.

Portable PowerShell In an Application Bundle

So obviously, I have a fondness for PowerShell on macOS, but one of the downsides with really integrating it into things is being able to rely on it as a resource. It’d be nice if you could just include it with a script or bundle, so that it was available for that script or application to use as needed.

Well, turns out, you can, thanks to the PowerShell team making some really good decisions in how they distribute PowerShell on macOS. If you download the installer bundle from the PowerShell github site, https://github.com/PowerShell/PowerShell/releases/tag/v7.2.1 (the current stable version as of this post), and you open the bundle up in something like Pacifist, you realize that all the important powershell bits outside of the man page and an app launcher are all in the same directory:

Everything is in that directory

When I saw that, I decided to do a really quick and dirty test of my “Portable PowerShell” thought. So using Script Debugger, I created a script bundle, which is an AppleScript, but in a bundle format, which means it has the Contents/Resources/ tree. I then copied the usr/local/bin/microsoft/powershell/<version> bundle from the install package into the resources folder. In the script bundle itself, I set up the path to that bundle:

set thePwshAlias to ((path to me as text) & "Contents:Resources:powershell:usr:local:microsoft:powershell:7-preview:pwsh") as alias
set thePwshPath to quoted form of (POSIX path of thePwshAlias)

Then the statement that does the test of this:

do shell script thePwshPath & " -c \"Get-PSDrive\""

And the expected result:

Name           Used (GB)     Free (GB) Provider      Root                                 
----           ---------     --------- --------      ----  
/                 782.49        149.06 FileSystem    / 
Alias                                  Alias 
Env                                    Environment 
Function                               Function
Temp              782.49        149.06 FileSystem   /var/folders/0_/h5qr4rfj2     
Variable                               Variable                    

Which was the expected result. Now, some caveats:

  1. This is literally the extent of my testing. Making this work with custom modules, etc, is going to involve more work on the setup, if that’s even possible.
  2. I HAVE NO IDEA IF THE POWERSHELL LICENSE ALLOWS THIS. If the lawyers get flustered and stern, telling them “Well John said you could do it” will result in me laughing and saying “Yes, technically this is possible, I made no claims as to legality. It may be fine, it may be verboten, I have not checked either way.

My thought on this is that if you were creating a cross platform app and you wanted to implement PowerShell for that app, this is a way to ensure it works on macOS without making the user do a bunch of separate installs. It’s a proof of concept that happens to work, so coolness. As I get time, I’ll beat on it more, there’s some real potential here.

Also, many thanks to the PowerShell team for doing things in a way that made this trivial to test and play with.

Things AppleScript does better

Seeing as I’ve (deservedly) been slightly hard on the Apple automation team(s), I’d like to point out that for all its faults, there are things AppleScript does far better than PowerShell, and things that Microsoft could learn from to improve.

  1. Basic UI functions: I’m talking about really basic things that you might want to put in a script, like a UI for choosing a file, a folder, etc. Like this isn’t even close, PowerShell at best is almost as easy as AppleScript for some things, but at worst…ugh. Like to choose a new file, that’s pretty simple:

    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = [Environment]::GetFolderPath('Desktop') }

    $null = $FileBrowser.ShowDialog()


    That’s not significantly worse that AppleScript’s “Choose File” dialog, which forgoes only the ShowDialog() method. The issue is that depending on how you’re running the script, the file browser may show up behind other windows, and there’s no easy way to force it to the front.

    Where PowerShell falls over is if you want the user to enter text in a dialog box. For AppleScript, it’s literally a single line:

    set theReply to text returned of display dialog "I am a dialog" default answer "default text"

    That’s it. That’s all you need. You can also get the button returned or if the gave up flag hit, it’s all in a single record if you want all three.

    In PowerShell, this is like around 30 lines of code where you’re building the OK and Cancel buttons by hand, setting their sizes, etc. That’s ridiculous for something that simple. The same thing for building a list of text items to choose from. In AppleScript:

    set theChoice to choose from list {"one", "two", "three"} with title "Choose from the list" with prompt "Pick something" default items {"one"} with multiple selections allowed without empty selection allowed

    The result is a list of choices.

    PowerShell, again, you have to build the entire dialog by hand. Like, if i’m doing .NET dev in a text editor, sure. But for what should be a higher-level scripting implementation, that’s ridiculous.

    Ironically, in PowerShell in macOS, you can integrate PowerShell with AppleScript UI primitives like Display Dialog easier than you can do the same thing in Windows. Y’all, come on. This is just stupid.
  2. Interacting with applications. The syntax for AppleScript gets a lot of guff, but launching an application in AppleScript and similar is far more intuitive and easier to learn than in PowerShell.

    tell application “Microsoft Excel” to launch

    or if you want it to launch and become active:

    tell application “Microsoft Excel” to activate

    With PowerShell, it can be simple if the application is in a known location, but if not, you have to know the path, etc. Which can be more than a little painful, but that’s an OS issue.

    Being able to just tell an application to do stuff, “tell application “Microsoft Excel”… is also nice when actually interacting with the running application. As opposed to working with the running copy of Excel:

    $theExcelApp=[Runtime.InteropServices.Marshal]::GetActiveObject("Excel.Application")

    This is the part of a higher level language that shouldn’t require this level of work. The level of detail needed to do simple stuff in PowerShell is way higher than it should be.

There’s other items, but some of them depend on syntax preferences. I find the english-ish nature of AppleScript to be helpful, other people hate it, I hate dot languages, I find them unnecessarily obtuse. The two items I listed here cover rather a lot, so it’s not just “oh two things”.

Honestly, this is part of the reason I find Apple’s hostility towards user-created automation via things like AppleScript so infuriating. The OS has a good base, only needing a better system-level automation framework so you can have more coherency across the board for automation, but instead of building on that foundation, it really seems like Apple is moving to gut the entire thing and force you into Shortcuts, Swift or nothing. That’s really quite dumb.

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.