Although this is the ABBYY corporate blog, in this article I will not touch our products and our company, but I’ll tell you about solving one practical issue: access to network resources from Mac OS X in a situation when the company uses Microsoft DFS (Distributed File System). In large companies, this technology is used at every turn, and ABBYY is no exception. Indeed, instead of a scattered server system with resources scattered across them, the user sees a logically built network resource tree.
When five years ago I decided to change my working laptop on a MacBook Pro, this was ambiguous in the team. Especially skeptical looked at it in team of system administrators. Although I was in charge of this team over the past five years, I was given very minor concessions, and the use of Mac OS, from the point of view of our admins, went beyond just concessions. But I said that I would take all the problems on me and almost kept my word, although, I confess, sometimes I still had to annoy administrators with my Mac problems. But I managed to solve the problem with DFS myself, because she really got me.
')
The problem of accessing DFS has been discussed in forums for a long time; all interested parties have been waiting for normal support of this technology from Apple for several years. But, unfortunately, things are still there (in Cupertino, apparently).
I will briefly outline the problem: I regularly receive letters with links in the UNC format to files like these:
\\dfs\Common\Media\Pictures\New Year Party\Aram on his head.jpg
At the same time, the latest versions of Outlook automatically inserted a URL link into emails, which looks like this:
file:////dfs/Common/Media/Pictures/New%20Year%20Party/…
For me, this is a disservice, because Mac OS does not know which protocol to use via this link. If suddenly lucky, Outlook didn’t set the link itself, then Mail.app in Snow Leopard can automatically convert UNC paths to the
smb://server/share/path
view. A good function, but to help it, you need to be very lucky: the source path should not be to the DFS resource and the link should not be enclosed in the HTML text. In addition, the enthusiasm of Mail.app ends on the first space encountered, therefore, very often such automatic links are also broken. But even open the “Connect to server” window in the Finder, copy the source text with handles, fix all backslashes to straight lines, everything will work out only if the source path is the real path to the real computer, not the DFS path.
It must be admitted that there is a global solution to this problem, called
ADmitMac, but it costs a lot of money and does a lot of other things, which is more harmful than useful, because this program climbs too deep into the system.
The second, simpler solution, called
Diffiss , is similar to the experimental work of a novice programmer. The product allows you to navigate the DFS tree, however, until the main application window is closed, which you can’t open after that except by restarting the application. This program allows you not to keep in mind the correspondence of DFS nodes to real servers, and to walk through the DFS tree, but it does not help you to open links from emails. It is necessary to walk on the tree with handles, first inside Diffiss, then inside Finder. But for a while even this was a relief.
Recently, I almost accidentally stumbled upon
this article , which made me take up a solid solution to the problem, that is, to make solid crutches, in order to hover until the time when Apple finally supports DFS in the system itself at the Finder level. By the way, in Samba 3.0.28, supplied with OS X Snow Leopard, work with DFS is supported (which I actually used).
Crutches consist of two parts. One is a bash script that takes as input the name of the DFS root server and the source path, and on output gives the UNC path to the directory on the real server. The second part is a small AppleScript script that gets the user’s source path, runs the bash script and opens the required directory in Finder.
Consider each of the details separately.
#! / bin / bash
# This script is designed to convert the DFS path to the resource.
# in the path to a real shared resource on a Windows network
# The script is waiting at the entrance:
# arg1: DFS root server name
# arg2: UNC path that needs to be brought to the real path
# The script assumes that user authentication is performed via Kerberos.
if [ -z "$ 1" ] || [ -z "$ 2" ] ; then
echo -e "Error: script assumes two arguments."
echo
exit 100
fi
DFS_SERVER = $ 1
# Convert backslashes to straight lines, remove possible spaces in the beginning
# strings and translate all letters to lowercase
DFS_PATH = $ ( echo -n $ 2 | sed 's / \\ / \ // g' | sed 's / ^ * // g' | tr "[: upper:]" "[: lower:]" )
real_share = ""
exit_code = "0"
# Get a list of DFS nodes using rpcclient (see the expression at the end of the loop,
# immediately after 'done'). This way of invoking the command is done in order to avoid
# of using channels, as for the implementation of channels the cycle is performed in a subshell,
# that masks the variables we want to modify in a loop.
while read smb_path; do
read comment
read state
read num_stores
# Separate fields from labels, delete spaces in the beginning, and translate letters into lowercase
smb_path = $ ( echo $ smb_path | awk -F: '{gsub (/ ^ [\ t] + /, "", $ 2); print $ 2}' | \
tr "[: upper:]" "[: lower:]" )
num_stores = $ ( echo $ num_stores | awk -F: '{gsub (/ ^ [\ t] + /, "", $ 2); print $ 2}' )
state = $ ( echo $ state | awk -F: '{gsub (/ ^ [\ t] + /, "", $ 2); print $ 2}' )
# Verify that the numbers and num_stores and state variables are entered
# they are not empty. If not, then it seems to have failed.
expr " $ num_stores + $ state " > / dev / null 2 > / dev / null
if [ $? ! = 0 ] || [ -z " $ num_stores " ] || [ -z " $ state " ] ; then
echo -e "Error while trying to get the list of DFS nodes."
exit_code = "200"
break
fi
# The number of real resources associated with a node may be more than one.
# In such cases, we will use the smbclient call to select a resource.
for ( ( store = 0 ; store < $ num_stores ; store ++ ) ) ; do
read server
read share
done
# Check whether the beginning of the DFS path matches any of the resources
# from the resulting list
if [ " $ state " -eq "1" ] && [ [ " $ DFS_PATH " == " $ smb_path " * ] ] ; then
if [ " $ num_stores " -gt "1" ] ; then
# If the number of resources per node is more than one, call smbclient.
# Issue consists of two lines, we are interested in the second
while read buff; do
if [ ! -z " $ buff " ] && [ [ " $ buff " == "//" * ] ] ; then
real_share = " $ buff " ;
else
# If the second line does not look like
# as a UNC path to a resource, then an error has occurred
echo -e "Error calling smbclient: $ buff "
exit_code = "300"
fi
done << ( smbclient -k -c showconnect $ smb_path 2 > / dev / null )
else
server = $ ( echo $ server | awk -F: '{gsub (/ ^ [\ t] + /, "", $ 2); print $ 2}' )
share = $ ( echo $ share | awk -F: '{gsub (/ ^ [\ t] + /, "", $ 2); print $ 2}' )
real_share = "// $ server / $ share "
fi
# Add the rest of the UNC path to the real server and resource
if [ ! -z " $ real_share " ] ; then
smb_path_len = " $ {# smb_path} "
rest_of_path = $ ( awk -v len = " $ smb_path_len " -v awk_string = " $ DFS_PATH " \
'BEGIN {print substr (awk_string, len + 1)}' )
real_share = " $ real_share $ rest_of_path "
fi
# We are looking for the first match, so leave the loop
break
fi
done << ( rpcclient -k --command = "dfsenum 3" $ DFS_SERVER | sed 's / \\ / \ // g' )
# If no value has been assigned to the real_share variable (most likely because
# that the original path was not found in the list, or because an error occurred,
# return source path
if [ -z " $ real_share " ] ; then
real_share = $ DFS_PATH
fi
# To return the result to AppleScript, we need to display it.
# to standard output
echo $ real_share
exit $ exit_code
The script, of course, is not perfect, everything can certainly be done better. I rarely write scripts at all, the Mac OS user doesn’t usually need them, so I haven’t got my hand yet. Technical details are clear from the comments, and in fact he does the following:
- using the rpcclient program, which is part of the Samba package, we will find out the node table in the DFS root server, linked to real servers. Here it should be noted that the script assumes authentication through Kerberos, which most likely means that your computer is registered in the domain and you are already logged in with a domain name and password. Without it, living in an environment of Windows-servers is very tight.
- next, we parse the resulting table (in the above article, the structure of the rpcclient output is described, so I will not repeat it here). As we analyze the nodes, we are looking for the one that coincides with the beginning of our original path. As soon as we find, we stop further searches, compose the real path from the parts and output it with the echo command. This is the standard way to transfer work from the shell to AppleScript.
In this whole story there is one feature. The fact is that DFS contains built-in duplication mechanisms that allow you to store mirror copies of data on different servers. Therefore, a single node can correspond to several real resources on different servers. It would not have been wise and just take one of them (if you don’t change anything in the code, the last one will be taken), but I decided to take the choice that Samba would have made, for which in such cases smbclient is run with the only command that returns the to the resource on the server to which you want to connect. By the way, I suspect that Samba selects nodes at random (the code did not look), but oh well.
Now, when we figured out the first script, let's move on to the second one. It is written so that it can be run both from the command line (using osascript) and interactively. In fact, there is a third way to start, about it below.
- This script connects the Windows network resource in the Finder via the DFS path to the resource.
- The script calls an external bash script to convert the DFS path to the path
- to real shared resource
property DFSRootServer: "dfs.mycompany" - insert the name of the DFS root server here
property BashScriptPath: "/ Users / Shared / Scripts / dfs_to_share" - the path to our bash script
property isRunFromCommandLine: true
- This handler is used by ThisService to organize
- call the script from the "Services" menu.
- We also call it from the "run" method. The source UNC path is passed as an argument.
on process ( input )
try
set realPath to do shell script "/ bin / bash" & BashScriptPath & ¬
"" & DFSRootServer & "'" & input & "'"
on error errMessage number errNumber
- Our script returns different error codes in different situations, we process here
if errNumber is 100 then
set errMessage to "Internal error when executing script."
else if errNumber is 200 then
set errMessage to "Unable to get table of DFS nodes from server. ¬
Check if you have an active Kerberos ticket. "
else if errNumber is 300 then
set errMessage to "Error using smbclient to get the DFS node."
else
set errMessage to "Unknown error while executing shell script:" & ( errNumber as text )
end if
if isRunFromCommandLine then
- In case the application is launched from the command line,
- display the message in the console
log errMessage
else
display dialog errMessage buttons { "Close" }
end if
return
end try
try
(* The most convenient way to force the Finder to open a directory along a given path is
use the "open location" method. He automatically mounts the device and
Opens the desired directory in the Finder window. But this method requires an input
path in the URL representation, so we use python to convert
UNC paths to URL view *)
set urlOfPath to "smb:" & ( do shell script ¬
"python -c 'import urllib, sys; print urllib.pathname2url (sys.argv [1])'" & ¬
quoted form of realPath )
tell application "Finder"
open location urlOfPath
end tell
on error errMessage
display dialog errMessage buttons { CloseButton }
end try
end process
on run argv
set ConnectButton to "Connect"
set CancelButton to Cancel
- We check if we were started from the command line with passing the path as a parameter
if ( count of argv ) is equal to 0 then
display dialog "Enter UNC path:" default answer ""
buttons { ConnectButton, CancelButton } default button 1
copy the result as list to { UNC_path, button_pressed }
if button_pressed is ConnectButton and UNC_path is not "" then
set isRunFromCommandLine to false
process ( UNC_path )
end if
else
process ( item 1 of argv )
end if
end run
The run method is automatically called after running the script, it displays a single window in which the user is prompted to enter the source path to the desired resource. The main functionality of the script is enclosed in the "process" method. It receives the actual path to the server as input, runs the bash script described above, interprets the result and opens the resulting path in the Finder program.
At this, the last stage, one problem arises. The Finder has a handy Connect to Server dialog box. Unfortunately, this functionality is not available through AppleScript, therefore, having tried several different options, I stopped at the “open location” method, which is waiting for the URL to enter. And for this it is necessary to convert the line returned by the first script into a URL representation, replacing some characters with their codes. Fortunately, Mac OS X comes with a python interpreter with libraries that have a convenient method that does exactly what we need. We feed the resulting URL to Finder, and it magically opens a window with the directory we need.
If you try these scripts, you will notice one feature: if you connect to a resource through the Finder dialog box, the directly shared resource is mounted on the server, and in the Finder you can walk around the entire directory tree in this resource. As a result of the work of the described script, only the directory referenced by the source path and its subdirectories is available. Like who, but I like this behavior more: you get what you ask for and nothing more. We need to go deeper above, we can take part of the path and connect only this part, or select the connected server in the sidebar of the Finder window, and go down the hierarchy from it.
Now, about the promised third way to run the script. In the programs on Cocoa, the Services menu is available (Services), and it would be very convenient if our script could be called from this menu. Select the path in the text, select an item from this menu (or press the corresponding keys corresponding to this item) and open the path in the Finder. It turned out that it is easy to do, there is a free program (donationware), which is called
ThisService , which allows you to enable the script in the “Services” menu. For this purpose, I divided the script into two methods, since for the work of ThisService it is required that the script have a “process” method that accepts a string as input. After a simple setup, it all worked.
There is another handy program that is just as free, called
Apptivate , which allows you to assign system-wide hotkeys to launch any applications, including scripts. I tied the launch of the script to the key combination, and now I can get my version of the “Connect to server” window on the screen at any time.
By the way, one important observation regarding Snow Leopard. If you already have a valid Kerberos ticket, and you disconnect all SMB volumes, then the ticket is lost. This funny behavior leads to the fact that from time to time you suddenly discover that you cannot connect to the network objects, although you did it all five minutes ago. This is clearly seen if you launch the “View Tickets” program (this can be done from the “Keychain”), connect and disconnect volumes, and see what happens to your ticket. As a result, I decided to always have it at hand, and started a hot key on it in the Apptivate program.
In conclusion, I must warn you that everything described was tested only on my computer (Mac OS X Snow Leopard, version 10.6.4) and only within the walls of ABBYY. Write in the comments about any inaccuracies that you notice, and suggest what can be improved. And I, and all who are interested, we will only be grateful.