diff --git a/src/python/dm/__init__.py b/src/python/dm/__init__.py
index 6342600419f9dbbf99f06d7b74936545fd108b41..bee1cd67a5f8295788348a9d17046274b7a7f583 100644
--- a/src/python/dm/__init__.py
+++ b/src/python/dm/__init__.py
@@ -1 +1 @@
-__version__ = "1.1 (2017.03.01)"
+__version__ = "1.1 (2017.03.03)"
diff --git a/src/python/dm/aps_beamline_tools/cli/daqCli.py b/src/python/dm/aps_beamline_tools/cli/daqCli.py
index 8a3392f3572f0e099ec051c80b001c7eb4566d3a..1ca2d2e24264adc839a8ad1fe845ffed6e7ad003 100755
--- a/src/python/dm/aps_beamline_tools/cli/daqCli.py
+++ b/src/python/dm/aps_beamline_tools/cli/daqCli.py
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 
-import os
 from dm.aps_bss.api.apsBssApi import ApsBssApi
 from dm.ds_web_service.api.experimentDsApi import ExperimentDsApi
 from dm.ds_web_service.api.userDsApi import UserDsApi
@@ -114,19 +113,6 @@ class DaqCli(ApsBeamlineCli):
         # Remove duplicates by converting into set
         return list(set(users+beamlineManagers))
 
-    def getDataDirectory(self):
-        dataDirectory = self.options.dataDirectory
-        replacementMap = os.environ.get('DM_DATA_DIRECTORY_MAP', '')
-        (scheme, host, port, dirPath) = FtpUtility.parseUrl(dataDirectory)
-        if dirPath and replacementMap:
-            # Map entries are expected to be in the form
-            # <original>|<replacement>;<original>|<replacement>;...
-            for entry in replacementMap.split(';'):
-                original = entry.split('|')[0]
-                replacement = entry.split('|')[1]
-                dirPath = dataDirectory.replace(original,replacement)
-        return FtpUtility.assembleUrl(scheme, host, port, dirPath)
-
     def addOrUpdateExperiment(self):
         dsExperimentApi = ExperimentDsApi(self.loginUsername, self.loginPassword, self.dsServiceHost, self.dsServicePort, self.serviceProtocol)
         dsUserApi = UserDsApi(self.loginUsername, self.loginPassword, self.dsServiceHost, self.dsServicePort, self.serviceProtocol)
diff --git a/src/python/dm/aps_beamline_tools/cli/uploadCli.py b/src/python/dm/aps_beamline_tools/cli/uploadCli.py
index 206e92452e1f8ea24a989c7df9a44f0f586941bd..a50e67e822ade9838bcc95dd2b71cf17fca402ac 100755
--- a/src/python/dm/aps_beamline_tools/cli/uploadCli.py
+++ b/src/python/dm/aps_beamline_tools/cli/uploadCli.py
@@ -1,13 +1,11 @@
 #!/usr/bin/env python
 
-import os
 from dm.aps_bss.api.apsBssApi import ApsBssApi
 from dm.ds_web_service.api.experimentDsApi import ExperimentDsApi
 from dm.ds_web_service.api.userDsApi import UserDsApi
 from dm.daq_web_service.api.experimentDaqApi import ExperimentDaqApi
 from dm.common.constants import dmProcessingMode
 
-from dm.common.utility.ftpUtility import FtpUtility
 from dm.common.exceptions.invalidRequest import InvalidRequest
 from dm.common.exceptions.objectNotFound import ObjectNotFound
 from dm.common.utility.configurationManager import ConfigurationManager
@@ -105,19 +103,6 @@ class UploadCli(ApsBeamlineCli):
         # Remove duplicates by converting into set
         return list(set(users+beamlineManagers))
 
-    def getDataDirectory(self):
-        dataDirectory = self.options.dataDirectory
-        replacementMap = os.environ.get('DM_DATA_DIRECTORY_MAP', '')
-        (scheme, host, port, dirPath) = FtpUtility.parseUrl(dataDirectory)
-        if dirPath and replacementMap:
-            # Map entries are expected to be in the form
-            # <original>|<replacement>;<original>|<replacement>;...
-            for entry in replacementMap.split(';'):
-                original = entry.split('|')[0]
-                replacement = entry.split('|')[1]
-                dirPath = dataDirectory.replace(original,replacement)
-        return FtpUtility.assembleUrl(scheme, host, port, dirPath)
-
     def addOrUpdateExperiment(self):
         dsExperimentApi = ExperimentDsApi(self.loginUsername, self.loginPassword, self.dsServiceHost, self.dsServicePort, self.serviceProtocol)
         dsUserApi = UserDsApi(self.loginUsername, self.loginPassword, self.dsServiceHost, self.dsServicePort, self.serviceProtocol)
diff --git a/src/python/dm/common/cli/dmCli.py b/src/python/dm/common/cli/dmCli.py
index 3be3e201d1920e028d5aa994ce90c0985f1eadbc..1fbc063492cdd258ab1ac10b76f7bc24b915df2f 100755
--- a/src/python/dm/common/cli/dmCli.py
+++ b/src/python/dm/common/cli/dmCli.py
@@ -4,6 +4,7 @@ import sys
 import os
 import os.path
 import stat
+import urlparse
 from optparse import OptionGroup
 
 import dm
@@ -242,6 +243,51 @@ class DmCli(object):
         else:
             return '%s' % dmObject
 
+    def getDataDirectory(self):
+        dataDirectory = self.options.dataDirectory
+        replacementMap = os.environ.get('DM_DATA_DIRECTORY_MAP', '')
+        (scheme, host, port, dirPath) = self.parseUrl(dataDirectory)
+        if dirPath and replacementMap:
+            # Map entries are expected to be in the form
+            # <original>:<replacement> <original>:<replacement>;...
+            for entry in replacementMap.split(' '):
+                original = entry.split(':')[0]
+                replacement = entry.split(':')[1]
+                dirPath = dirPath.replace(original,replacement)
+        return self.assembleUrl(scheme, host, port, dirPath)
+
+    @classmethod
+    def parseUrl(cls, url, defaultHost=None, defaultPort=None):
+        host = defaultHost
+        port = defaultPort
+        parseResult = urlparse.urlparse(url)
+        scheme = parseResult.scheme
+        netlocTokens = parseResult.netloc
+        if netlocTokens:
+            netlocTokens = netlocTokens.split(':')
+            host = netlocTokens[0]
+            if len(netlocTokens) > 1:
+                port = int(netlocTokens[1])
+        dirPath = parseResult.path
+        dirPath = os.path.normpath(dirPath)
+        return (scheme, host, port, dirPath)
+
+    @classmethod
+    def assembleUrl(cls, scheme, host, port, dirPath):
+        url = ''
+        if scheme:
+            url += '%s://' % scheme
+        if host:
+            url += '%s' % (host)
+            if port:
+                url += ':%s' % (port)
+        if dirPath:
+            if len(url) and not dirPath.startswith('/'):
+                url += '/%s' % (os.path.normpath(dirPath))
+            else:
+                url += '%s' % (os.path.normpath(dirPath))
+        return url
+
 #######################################################################
 # Testing
 
diff --git a/src/python/dm/daq_web_service/cli/daqWebServiceSessionCli.py b/src/python/dm/daq_web_service/cli/daqWebServiceSessionCli.py
index 48b090e643ed5991b2ec2ea49d3b94843065f216..35ab8be786a9100dc4401d9a9e5f71d9bfb20891 100755
--- a/src/python/dm/daq_web_service/cli/daqWebServiceSessionCli.py
+++ b/src/python/dm/daq_web_service/cli/daqWebServiceSessionCli.py
@@ -26,21 +26,4 @@ class DaqWebServiceSessionCli(DmRestSessionCli):
     def getExperimentName(self):
         return self.options.experimentName
 
-    def getDataDirectory(self):
-        dataDirectory = self.options.dataDirectory
-        if not dataDirectory:
-            return None
-        if dataDirectory.find('://') < 0:
-            fileServerUrl = os.environ.get('DM_FILE_SERVER_URL')
-            dataDirectory = '%s%s' % (fileServerUrl, dataDirectory)
-        replacementMap = os.environ.get('DM_DATA_DIRECTORY_MAP', '')
-        (scheme, host, port, dirPath) = FtpUtility.parseUrl(dataDirectory)
-        if dirPath and replacementMap:
-            # Map entries are expected to be in the form
-            # <original>|<replacement>;<original>|<replacement>;...
-            for entry in replacementMap.split(';'):
-                original = entry.split('|')[0]
-                replacement = entry.split('|')[1]
-                dirPath = dataDirectory.replace(original,replacement)
-        return FtpUtility.assembleUrl(scheme, host, port, dirPath)