⬆️ ⬇️

How to make friends Mac OS X with Microsoft DFS

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:





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.

Source: https://habr.com/ru/post/102518/



All Articles