1

I attempted this question earlier today but finally realized I had completely mangled what I was trying to ask and also posted the wrong script code. This is attempt #2.

I have two Macs that reboot automatically early each morning in order to keep all of their various services functioning optimally. Each Mac needs to mount a volume on the other after they boot up. This is easy enough to do, except for a case where one machine hangs during the restart for any reason.

If one of the machines is unavailable when the other boots, then obviously its attempt to mount the unavailable machine will fail. The script is designed to run every minute or so in a loop for as long as the remote volume has not been mounted, since eventually it will come up. But when the mount fails, it produces an error dialog on the machine trying to perform the mount, which then breaks the loop and the script does not repeat.

Is there a way in AppleScript to attempt to mount a server, but without an error dialog if that server is unavailable in the moment?

I thought this was a main purpose for try blocks, but it hasn't made any difference.

Here is the script I currently run at boot time (it's an Automator application). The workflow is a Get Specified Server block, followed by a Run AppleScript block. Here is the AppleScript:

on run {input, parameters}
    try
        set server to (item 1 of input) -- this gets the server address from the Get Specified Servers block
    end try
    set vol to "Streaming"
    tell application "Finder"
        set isConnected to disk vol exists
    end tell
    repeat while isConnected = false -- as long as the volume is not present, try to mount it

        try
            tell application "Finder"
                mount volume server & "/" & vol -- This produces the error dialog and halts the script, if the server is unavailable
            end tell
        end try
        delay 2

        tell application "Finder"
            set isConnected to disk vol exists
        end tell
        if isConnected = false then
            delay 60 -- if the volume still doesn't exist, wait a minute before trying again
        end if

    end repeat
    return input
end run

Everything works perfectly as long as the remote volume is ready and accessible. But if not, the error dialog appears.

Edit: I found this thread and this quite complicated thread elsewhere that are attempting to address the same issue, but no simple, definitive solution seems to have been found.

6
  • As you mentioned in another post that the server was setup for File Sharing, if it's not running a Web Server too, then curl will fail even if the server is available. In that case you could substitute with ping e.g. ping -t 2 -c 1 192.168.2.102 2>/dev/null | awk '/0 packets received/{print $4}' returns 0 if it fails. That said, and mentioning something from another post about the server hanging in the reboot... Depending on where in the process it hangs, ping could succeed without the actual File Sharing resource being available. Apr 10, 2020 at 0:29
  • Good point, and no I'm not running a web server so I'll have to see what works and how reliably.
    – JVC
    Apr 10, 2020 at 2:49
  • See my edited answer... @user3439894 is totally right that my method shouldn't work, now that I think about it. What I don't know is why it's working... Apr 10, 2020 at 11:34
  • Just an FYI The mount volume command is a part of Standard Additions in AppleScript not Finder and should not be wrapped in a tell application "Finder" block. In other words, Finder doesn't understand the mount volume command and it actually fails silently with error number -10004 and then has to run the the command again. Apr 10, 2020 at 16:48
  • 1
    Yes it still works with the tell application "Finder" block because AppleScript eats the error and then runs the mount volume command again in its proper context. It's a non-fatal error and improper coding as it needlessly usurps CPU cycles. Apr 10, 2020 at 16:59

5 Answers 5

1

Using a macOS Catalina system as the file server with /Users/Shared/Videos as an SMB shared resource, and having done a bit of testing, found the following example AppleScript code to work well when testing using Script Editor from another system on the network in which the credentials have already been saved in its Keychain:

set ipAddress to "172.16.15.141"
set sharedResource to "Videos"

if workgroupAvailable(ipAddress) then
    mount volume "smb://" & ipAddress & "/" & sharedResource
end if

on workgroupAvailable(ipAddress)
    set workGroup to do shell script ¬
        "smbutil status " & ipAddress & " | awk '/Workgroup:/{print $2}'"
    if workGroup is equal to "" then
        return false
    else
        return true
    end if
end workgroupAvailable

The replies from the above example AppleScript code were:

When the shared resource was not available:

 tell current application
    do shell script "smbutil status 172.16.15.141 | awk '/Workgroup:/{print $2}'"
    --> ""
 end tell

When the shared resource was available:

tell current application
    do shell script "smbutil status 172.16.15.141 | awk '/Workgroup:/{print $2}'"
        --> "WORKGROUP"
end tell
tell application "Script Editor"
    mount volume "smb://172.16.15.141/Videos"
        --> file "Videos:"
end tell
Result:
file "Videos:"

What I found with using the smbutil status command over curl, which on my network required httpd running on the server, and ping, which could reply even if the shared resource wasn't available, was that smbutil status failed to return the Workgroup name if the shared resource wasn't available to be mounted and in turn no attempt to mount occurs if the Workgroup isn't available.

This example AppleScript code also did not cause any dialog boxes to appear at any point when the shared resource wasn't available.

Note that although testing was done with the rebooting of the file server and attempting to connect at various times during the reboot and the system actually being available, I was unable to test if the system had hung during the reboot, as it just has never hung during any reboots. As I know this is an unresolved issue with the rebooting of your server, you can give this a try if you so choose.

Obviously you can adapt the example AppleScript code to suite your specific needs.


Note: The example AppleScript code is just that and does not contain any error handling as may be appropriate. The onus is upon the user to add any error handling as may be appropriate, needed or wanted. Have a look at the try statement and error statement in the AppleScript Language Guide. See also, Working with Errors. Additionally, the use of the delay command may be necessary between events where appropriate, e.g. delay 0.5, with the value of the delay set appropriately.

6
  • Very interesting! I wonder if it will matter that my OSes are much older (Sierra on both machines). I'll have to experiment with this and see where I get. Thanks for the detailed answer!
    – JVC
    Apr 10, 2020 at 16:50
  • 1
    @JVC, Unfortunately I don't have a macOS Sierra system available at the moment to test with, however I do not anticipate any issues using this with macOS Sierra, under the same other conditions. Apr 10, 2020 at 16:54
  • @JVC, Is this method working for you? Apr 12, 2020 at 18:17
  • I haven't yet had a chance to see about implementing it yet so I don't know. Hope to get to it this week.
    – JVC
    Apr 12, 2020 at 18:18
  • @JVC, have you had a chance yet to see if this method works for you? Apr 30, 2020 at 18:25
1

The simplest and most portable way to do this would be via shell script. The reason being is that you avoid any of the GUI popup error dialogs completely which as you've found, breaks your script. It's better to handle them in the shell .

Below is a very short script bash/zsh script to connect to a particular server share. You can customize it by manipulating the variables in the top portion. The key here is to store the user's password in the Keychain so that it connects seamlessly on all subsequent connections.

The script is actually fairly short; you can remove all of the printf statements as I only included them so it would output each step to the console for your reference. It, like all other Unix commands exits with a 0 for a successful run and a 1 if there was a problem.

n=1                        # initialize iteration variable
max=3                      # max number of attempts
cflag=1                    # initialize connection flag / 0=true; 1=false
sleeptime=5                # number of seconds to sleep between retries
svr="sername.example"      # server name/IP address
loc="Network Share"        # directory/folder location
user="JVC"                 # username which to authenticate with 


#### Code is Below ###

while [ ${n} -le 3 ] && [ ${cflag} -gt 0 ]    #While not connected and attempts < max
do
if  [ ! $(ping -c 1 ${svr}) ]; then
  printf "Attempt ${n}: Device ${svr} not ready. Sleeping for retry...\n"
  sleep ${sleeptime}
  n=$(( ${n} + 1 ))               # increase count
else 
  cflag=0                         # Set connection flag to "true"
  printf "Device found.  Connecting to "
  printf "smb://${user}@${svr}/${loc}.\n"
  open "smb://${user}@${svr}"     # Connect and mount the share
fi 
done

# Print error message and exit with appropriate exit code.
if [ ${n} -eq ${max} ]; then
  printf "Could not connect to ${svr} in ${n} tries. Giving up."
  exit 1
fi
exit 0
10
  • Ah very nice. Seems there are in fact many potential ways to crack this nut, but they all need more use of shell! I'll play around with this and see what I get. Thanks!
    – JVC
    Apr 12, 2020 at 23:04
  • Mounting a fileshare is a Unix process, so it actually works "better" in the shell than in AS. I use AS to script the GUI - for system things, I prefer a shell script.
    – Allan
    Apr 12, 2020 at 23:16
  • IMO ping is not a foolproof indicator that a shared resource is available to be mounted because ping can indeed succeed with a successful reply from the IP address while the shared resource is not yet available to be mounted. This is especially true for the OP as his server hangs on reboot. It can/could easily hang at a point where it's pingable but shares not yet available. This is why after it had already been mentioned by me in a comment to the OP and mentioned again by Wowfunhappy I choose to pursue and answer based on a different verb of smbutil mentioned by klanomath. Apr 12, 2020 at 23:16
  • 1
    BTW If there is more than one shared resource open "smb://${user}@${svr}" throws up a GUI dialog "Select the volumes you want to mount on ..." Did you actually mean to use smb://${user}@${svr}/${loc} with the open command? (It too throws up a GUI connecting dialog, but that goes away if there is no error mounting.) I agree a shell script is a good way to go, but not with ping because of it false positive possibilities. Using smbutil status is way more reliable then ping because if the Workgroup: isn't returned, then it's not ready to mount. Apr 12, 2020 at 23:16
  • All that said, I'm assuming your didn't throughly test out your script as it tries to connect when the server isn't available. Maybe run your code through https://www.shellcheck.net and also change the logic around if [ ! $(ping -c 1 ${svr}) ]; then because aside from the error it currently throws, even properly quoted so it doesn't error, it's still flawed logic. Apr 12, 2020 at 23:16
1

To check if a server share is available and mount it if it is, a shell script would be much simpler than Applescript.

And to check the server, it is better to use smbutil rather than ping.

This example Bash script

  • defines server and share name
  • checks if the share is already mounted, and exits if it is.
  • then checks if the server is available and if yes, mounts it
#!/bin/bash

server=my_server
share=my_share

mount | grep -q "^//.*/$share on /Volumes/" && exit 0

if smbutil status $server 2>/dev/null | grep -q '^Server:'; then
    open smb://$server/$share
else
    exit 1
fi

If you need to provide credentials to mount the share, you can add them to the server variable like this:

server="Username:Password@my_server"
0

I ran into this problem some time ago, and my solution was to use curl beforehand to determine if an address was accessible.

Make a subroutine to check if an address exists:

on addressExists(address)
    try
        do shell script "curl -I --connect-timeout 0.5 'http://" & address & "'" --will error if address does not exist
        return true
    on error
        return false
    end try
end addressExists

Then connect to the server only if the subroutine says it is accessible:

if addressExists(serverAddress) then
    tell application "Finder" to mount volume "smb://" & serverAddress
end if

Tricky thing here: you'll notice that the addressExists subroutine appends http:// to the beginning—as is needed by curl—whereas the main script appends smb://, as is needed by Finder. Make sure the server address variable does not itself contain a protocol.

If it helps, here's the larger project where I used this: https://github.com/ideasonpurpose/NAS-Location-Sharing-Workflow

Minor compatibility note: this method works on 10.12 and above, but does not work on 10.9. I've never tested it on 10.10 or 10.11.


Edit: So a comment above by @user3439894 pointed out that this really shouldn't work. A machine serving over samba shouldn't be serving anything over http.

But, it did work for me, and continues to work. My whole company uses this script quite heavily, and the Mac Mini we connect to doesn't have web sharing turned on. But, I've only ever tested this method with that single mac mini server, so I don't know that it will be universal.

Using ping would likely be more robust than curl, although I'm pretty sure I tried ping first and couldn't get it to work reliably. I don't remember exactly, however, since it's been a couple years.

I may dive back into this for my own purposes in the next couple weeks, and I'll update this answer if I do...

7
  • Aha! I was thinking along these lines but couldn't figure out how to check if a server was available, without actually requesting the mount. I'll give this a shot and report back... thanks!!
    – JVC
    Apr 10, 2020 at 0:11
  • 1
    Why don't you replace curl with smbutil? (Examples: smbutil view //user:password@hostname | grep "share_name" or smbutil view //user:password@hostname 2>/dev/null | awk ... which has no results on error or lists all shares if smb is available)
    – klanomath
    Apr 10, 2020 at 12:31
  • 1
    On my network your proposed solution, as is, only works if httpd is running on the target IP address! As I mentioned in my comment, for the OP's purpose (his server at times hangs during a reboot mentioned in another post), "Depending on where in the process it hangs, ping could succeed without the actual File Sharing resource being available." So ping is not a perfect solution either. Apr 10, 2020 at 13:14
  • 1
    @klanomath, I wouldn't think anyone would want to hard code user name and password in their script, I certainly wouldn't. That said, smbutil view //user:password@hostname (with the proper credentials) returns smbutil: server rejected the authentication: Authentication error unless I'm already connected through Finder. Not sure why, a quirk maybe, but I wouldn't use this particular method anyway. Apr 10, 2020 at 17:13
  • @klanomath, However, I have to give you some thanks as I did devise an answer using a different verb of the smbutil command. I posted it as a separate answer as I did take the time to give it a good testing and found it reliable enough to post it as a separate answer. Apr 10, 2020 at 17:13
-2

Determining whether a URL is reachable

This is something that is a lot simpler than people think, but also not usually the first thing that would pop into anyone's mind because this is one of only a few practical uses for AppleScript's URL class.

You can take an everyday internet URL as a string and coerce it to type class URL, e.g.

"https://duckduckgo.com" as URL

which returns the following record:

{
    class: URL, 
    scheme: secure http URL, 
    path: "https://duckduckgo.com", 
    host: {
        class: Internet address, 
        DNS form: "duckduckgo.com", 
        port: 443, 
        dotted decimal form: "52.213.95.108"
    }
}

(though AppleScript won't pretty-print it). Here's a similar coercion using a web address that I know does not exist:

"https://quackquackyo.com" as URL
    --> {
            class: URL, 
            scheme: secure http URL, 
            path: "https://quackquackyo.com", 
            host: {
                class: Internet address, 
                DNS form: "quackquackyo.com", 
                port: 443
            }
        }

Then it's just a game of spot the difference: if a URL resolves to an address that is reachable, AppleScript's URL class object returns a dotted decimal form representing the IP address of the server; if the URL does not resolve, then this IP address is omitted.

Using this discriminator, we can create a handler that returns true or false depending on whether or not a given URL is reachable:

on urlIsReachable(www as URL)
    local www
    set www to www's host & {dotted decimal form:false}
    return www's dotted decimal form ≠ false
end urlIsReachable

To use:

urlIsReachable("https://www.google.com")    --> true
urlIsReachable("https://CK-mac.local")      --> true
urlIsReachable("http://CK.local")           --> true
urlIsReachable("http://CK-maaaaaaac.local") --> false

Bear in mind, however, this doesn't tell you that the machines on my network called CK-mac and CK have the afp or smb file sharing ports open, and trying to resolve an afp:// or smb:// URL will throw an error, because the URL class object doesn't recognise very many schemes. It also means that if a machine has closed its http or https ports (80 and 443, respectively), then those will return false even if ports 445 (smb) or 548 (afp) are open. So, while this method has limitations, I don't think it's a very common situation that file sharing ports are open on a machine with hypertext transfer ports specifically closed. AppleScript's URL class also recognises ftp:// URLs, but if it resolves for any of your personal devices, you should probably turn off its ftp server so that it doesn't.

Determining whether a disk is mounted in Finder

It looks like you have a method for determining whether or not a disk is currently mounted on your filesystem, and it's a perfectly suitable one. I'm always mindful, however, how temperamental Finder can be, and how easily it blocks when doing other tasks. Therefore, personally, if I can avoid involving it (in pretty much anything), I will. And, of course, if you can leverage AppleScript's built-in capabilities without calling out to another application to do a job, that's (almost) always going to be faster and more reliable. So, to this end, here's a quick handler:

on volumeIsMounted(name as text)
    local name
    "/Volumes/" & name as «class furl» as {alias, text}
    return result's class ≠ text
end volumeIsMounted

To use:

volumeIsMounted("MEDIA LIBRARY")      --> true
volumeIsMounted("rsaref")             --> true
volumeIsMounted("MEDIAAAAAA LIBRARY") --> false

For reference, "MEDIA LIBRARY" is the name of remote disk on my network attached to machine CK.local, which is mounted as an afp fileshare for the purposes of testing. I also wanted to test a remote-remote server, so I mounted an ftp server available to everyone at "ftp://static.zedz.net/pub/security/cryptography/libraries/math/rsaref" (this is not me endorsing the safety of opening any files you might find on this server, so just be thoughtful before you consider accessing any random fileshare over the internet). "MEDIAAAAAA LIBRARY", I'm guessing you know, is not mounted on my filesystem.


This actually frees your entire script from having to use Finder at all. mount volume is a Standard Additions command, so doesn't need to be used in a Finder tell block, and will be more performant if it's not. Nothing else here or in your script requires Finder either. That's a win, in my book.

4
  • I like your logic in approaching a script like this, but you've completely lost me with the checking of URLs, since this is strictly about mounting local-network remote volumes, and I don't see anything anywhere that does this. Maybe I just don't understand AppleScript well enough to interpret how this script works, but I don't see a mount volume command anywhere, so how does anything get mounted?
    – JVC
    Apr 12, 2020 at 13:14
  • OK wait I think I get it now. This isn't supposed to mount anything, this is just to tell if a volume is available or not, then if it is I should be able to mount it. Yes?
    – JVC
    Apr 12, 2020 at 13:20
  • Well I've tried to use this but I can't figure out where it's supposed to go or how it's supposed to be implemented. I like the thinking behind this the most, so If you can give a complete explanation of what to do with this that'd be great, otherwise I'll just keep trying other things.
    – JVC
    Apr 12, 2020 at 15:45
  • 1
    Sure, no problem. I apologise, I guess I wasn't being as clear as I thought, but you correctly figured out the aim of the post. But let me update the answer with a workable script.
    – CJK
    Apr 12, 2020 at 19:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .