diff --git a/src/main/java/unimelb/mf/client/instrument/Upload.java b/src/main/java/unimelb/mf/client/instrument/Upload.java
index aec92a084c60e0d6e187258ff1217dcb72a1cb4c..d42e2154972e0295c7d797b0a92c3261cf376fab 100644
--- a/src/main/java/unimelb/mf/client/instrument/Upload.java
+++ b/src/main/java/unimelb/mf/client/instrument/Upload.java
@@ -126,8 +126,8 @@ public class Upload {
     }
 
     public final boolean pathContextMatches(String srcPath) {
-        if (_pathContext != null && srcPath!=null) {
-            return _pathContext.replaceFirst("/+$","").equals(srcPath.replaceFirst("/+$", ""));
+        if (_pathContext != null && srcPath != null) {
+            return _pathContext.replaceFirst("/+$", "").equals(srcPath.replaceFirst("/+$", ""));
         }
         return false;
     }
@@ -136,6 +136,14 @@ public class Upload {
         return _srcContext;
     }
 
+    public final SourceContext resolveSourceContext() throws Throwable {
+        _srcContext = null;
+        if (_pathContext != null) {
+            _srcContext = SourceContext.resolve(_pathContext, false);
+        }
+        return _srcContext;
+    }
+
     public final void print(PrintStream ps, int indent) {
         indent = indent < 0 ? 0 : indent;
         String indentStr = indent > 0 ? StringUtils.stringOf(' ', indent) : "";
diff --git a/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadFind.java b/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadList.java
similarity index 96%
rename from src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadFind.java
rename to src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadList.java
index 9689278bfbd886d7df03c856090602c8a0e0f992..976297cf16437e50e9960fd04b0bf5d182ebe681 100644
--- a/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadFind.java
+++ b/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadList.java
@@ -22,10 +22,10 @@ import unimelb.mf.client.session.MFSession;
 import unimelb.picocli.converters.DateTimeConverter;
 import unimelb.picocli.converters.PathConverter;
 
-@Command(name = "instrument-upload-find", abbreviateSynopsis = true, synopsisHeading = "\nUSAGE:\n  ", parameterListHeading = "\nPARAMETERS:\n", optionListHeading = "\nOPTIONS:\n", separator = " ", sortOptions = false)
-public class InstrumentUploadFind implements MFCommand {
+@Command(name = "instrument-upload-list", abbreviateSynopsis = true, synopsisHeading = "\nUSAGE:\n  ", parameterListHeading = "\nPARAMETERS:\n", optionListHeading = "\nOPTIONS:\n", separator = " ", sortOptions = false)
+public class InstrumentUploadList implements MFCommand {
 
-    public static final String APPLICATION_NAME = "instrument-upload-find";
+    public static final String APPLICATION_NAME = "instrument-upload-list";
 
     @Option(names = {
             "--mf.config" }, paramLabel = "<mflux.cfg>", description = "Mediaflux server configuration file. Defaults to $HOME/.Arcitecta/mflux.cfg", required = false, converter = PathConverter.MustBeRegularFile.class)
@@ -65,7 +65,7 @@ public class InstrumentUploadFind implements MFCommand {
     @Override
     public MFSession authenticate() throws Throwable {
         MFConfigBuilder mfcb = new MFConfigBuilder();
-        mfcb.setApp(InstrumentUploadFind.APPLICATION_NAME);
+        mfcb.setApp(InstrumentUploadList.APPLICATION_NAME);
         if (this.mfCFG != null) {
             mfcb.loadFromConfigFile(mfCFG.toFile());
         } else {
@@ -215,7 +215,7 @@ public class InstrumentUploadFind implements MFCommand {
     }
 
     public static void main(String[] args) {
-        CommandLine cl = new CommandLine(new InstrumentUploadFind());
+        CommandLine cl = new CommandLine(new InstrumentUploadList());
         cl.setExecutionExceptionHandler(new IExecutionExceptionHandler() {
             @Override
             public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult)
diff --git a/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadMissingFind.java b/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadMissingFind.java
new file mode 100644
index 0000000000000000000000000000000000000000..9759c1935d33fd03ff5c2a54714606a865bcf1b9
--- /dev/null
+++ b/src/main/java/unimelb/mf/client/instrument/cli/InstrumentUploadMissingFind.java
@@ -0,0 +1,299 @@
+package unimelb.mf.client.instrument.cli;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.IExecutionExceptionHandler;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+import picocli.CommandLine.ParseResult;
+import unimelb.mf.client.cli.MFCommand;
+import unimelb.mf.client.instrument.Upload;
+import unimelb.mf.client.instrument.UploadShareable;
+import unimelb.mf.client.session.MFConfigBuilder;
+import unimelb.mf.client.session.MFSession;
+import unimelb.picocli.converters.DateTimeConverter;
+import unimelb.picocli.converters.PathConverter;
+
+@Command(name = "instrument-upload-missing-find", abbreviateSynopsis = true, synopsisHeading = "\nUSAGE:\n  ", parameterListHeading = "\nPARAMETERS:\n", optionListHeading = "\nOPTIONS:\n", separator = " ", sortOptions = false)
+public class InstrumentUploadMissingFind implements MFCommand {
+
+    public static final String APPLICATION_NAME = "instrument-upload-missing-find";
+
+    @Option(names = {
+            "--mf.config" }, paramLabel = "<mflux.cfg>", description = "Mediaflux server configuration file. Defaults to $HOME/.Arcitecta/mflux.cfg", required = false, converter = PathConverter.MustBeRegularFile.class)
+    private Path mfCFG;
+
+    @Option(names = { "-i", "--id" }, paramLabel = "<id>", description = "The instrument upload shareable id")
+    private Long id;
+
+    @Option(names = { "-t", "--token" }, paramLabel = "<token>", description = "The instrument upload shareable token.")
+    private String token;
+
+    @Option(names = { "-l",
+            "--levels" }, paramLabel = "<n>", description = "Number of levels of sub-directories to search. Defaults to 1.", defaultValue = "1")
+    private int levels;
+
+    @Option(names = { "-a",
+            "--after" }, paramLabel = "<dd-MMM-yyyy>", description = "Include only the data directories modified after this date (inclusive).", converter = DateTimeConverter.class)
+    private Date completedAfter;
+
+    @Option(names = { "-b",
+            "--before" }, paramLabel = "<dd-MMM-yyyy>", description = "Include only the data directories modified before this date (exclusive).", converter = DateTimeConverter.class)
+    private Date completedBefore;
+
+    @Option(names = { "-c", "--compare" }, description = "Compare with local source file/directory.")
+    private boolean compare;
+
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "display help infomation.")
+    private boolean printHelp;
+
+    @Parameters(paramLabel = "<parent-directories...>", description = "Parent directories to search.", arity = "1..")
+    private List<Path> parentDirs;
+
+    @Override
+    public MFSession authenticate() throws Throwable {
+        MFConfigBuilder mfcb = new MFConfigBuilder();
+        mfcb.setApp(InstrumentUploadList.APPLICATION_NAME);
+        if (this.mfCFG != null) {
+            mfcb.loadFromConfigFile(mfCFG.toFile());
+        } else {
+            mfcb.findAndLoadFromConfigFile();
+        }
+        mfcb.setConsoleLogon(true);
+        return MFSession.create(mfcb);
+    }
+
+    @Override
+    public void execute(MFSession session) throws Throwable {
+
+        Settings settings = new Settings();
+        if (this.id != null) {
+            settings.setShareableId(this.id);
+        } else if (this.token != null) {
+            settings.setShareableToken(this.token);
+        } else {
+            settings.setShareableToken(DataMoverSettings.getToken());
+        }
+        if (!settings.hasShareableIdOrToken()) {
+            throw new IllegalArgumentException("No shareable id or token is specified!");
+        }
+        if (this.levels > 0) {
+            settings.setLevels(this.levels);
+        }
+        if (this.completedAfter != null) {
+            settings.setAfter(completedAfter);
+        }
+        if (this.completedBefore != null) {
+            settings.setBefore(completedBefore);
+        }
+        if (this.compare) {
+            settings.setCompare(compare);
+        }
+        settings.setParentDirectories(parentDirs);
+        if (settings.parentDirectories().isEmpty()) {
+            throw new IllegalArgumentException("No parent directory path is specified!");
+        }
+
+        List<Upload> uploads = UploadShareable.getUploads(session, settings.shareableId(), settings.shareableToken(),
+                false);
+
+        findMissingDirectories(settings.parentDirectories(), settings.levels(), settings.after(), settings.before(),
+                settings.compare(), uploads);
+        
+    }
+
+    private Map<Path, Set<Upload>> findMissingDirectories(Collection<Path> parentDirectories, int levels, Date after,
+            Date before, boolean compare, List<Upload> uploads) throws Throwable {
+        Map<Path, Set<Upload>> result = new LinkedHashMap<>();
+        for (Path parentDir : parentDirectories) {
+            findMissingDirectories(parentDir, levels, after, before, compare, uploads, result);
+        }
+        return result;
+    }
+
+    private void findMissingDirectories(Path parentDir, int levels, Date after, Date before, boolean compare,
+            List<Upload> uploads, Map<Path, Set<Upload>> result) throws IOException {
+
+        int rootDepth = parentDir.getNameCount();
+
+        Files.walkFileTree(parentDir, new SimpleFileVisitor<Path>() {
+
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                Objects.requireNonNull(dir);
+                Objects.requireNonNull(attrs);
+                int depth = dir.getNameCount();
+                if (depth > rootDepth + levels) {
+                    return FileVisitResult.SKIP_SUBTREE;
+                }
+                if (depth > rootDepth) {
+                    if (lastModifiedTimeMatches(attrs.lastModifiedTime(), after, before)) {
+                        Set<Upload> matchingUploads = getMatchingUploads(dir, uploads);
+                        if (!matchingUploads.isEmpty()) {
+                            result.put(dir, matchingUploads);
+                        }
+                    }
+                }
+                return FileVisitResult.CONTINUE;
+            }
+
+        });
+
+    }
+
+    private static boolean lastModifiedTimeMatches(FileTime lastModified, Date after, Date before) {
+        if (after != null || before != null) {
+            long lastModifiedMillis = lastModified.toMillis();
+            if (after != null) {
+                long afterMillis = after.getTime();
+                if (lastModifiedMillis < afterMillis) {
+                    return false;
+                }
+            }
+            if (before != null) {
+                long beforeMillis = before.getTime();
+                if (lastModifiedMillis >= beforeMillis) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static Set<Upload> getMatchingUploads(Path dir, Collection<Upload> uploads) {
+        Set<Upload> matching = new LinkedHashSet<>();
+        for (Upload upload : uploads) {
+            if (upload.pathContextMatches(dir.toAbsolutePath().toString())) {
+                matching.add(upload);
+            }
+        }
+        return matching;
+    }
+
+    public static class Settings {
+        private Long _id;
+        private String _token;
+        private int _levels;
+        private Date _after;
+        private Date _before;
+        private boolean _compare = false;
+        private Set<Path> _parentDirs = new LinkedHashSet<>();
+
+        Long shareableId() {
+            return _id;
+        }
+
+        String shareableToken() {
+            return _token;
+        }
+
+        int levels() {
+            return _levels;
+        }
+
+        Date before() {
+            return _before;
+        }
+
+        Date after() {
+            return _after;
+        }
+
+        boolean compare() {
+            return _compare;
+        }
+
+        Collection<Path> parentDirectories() {
+            return Collections.unmodifiableCollection(_parentDirs);
+        }
+
+        public Settings setShareableId(Long id) {
+            _id = id;
+            return this;
+        }
+
+        public Settings setShareableToken(String token) {
+            _token = token;
+            return this;
+        }
+
+        public Settings setLevels(int levels) {
+            _levels = levels;
+            return this;
+        }
+
+        public boolean hasShareableIdOrToken() {
+            return _id != null || _token != null;
+        }
+
+        public Settings setBefore(Date before) {
+            _before = before;
+            return this;
+        }
+
+        public Settings setAfter(Date after) {
+            _after = after;
+            return this;
+        }
+
+        public Settings setCompare(boolean compare) {
+            _compare = compare;
+            return this;
+        }
+
+        public Settings addParentDirectories(Collection<Path> parentDirs) {
+            if (parentDirs != null) {
+                for (Path parentDir : parentDirs) {
+                    if (Files.isDirectory(parentDir)) {
+                        _parentDirs.add(parentDir);
+                    } else {
+                        System.err.printf("'%s' does not exist or it is not a directory.", parentDir.toString());
+                    }
+                }
+            }
+            return this;
+        }
+
+        public Settings setParentDirectories(Collection<Path> parentDirs) {
+            _parentDirs.clear();
+            addParentDirectories(parentDirs);
+            return this;
+        }
+
+    }
+
+    public static void main(String[] args) {
+        CommandLine cl = new CommandLine(new InstrumentUploadList());
+        cl.setExecutionExceptionHandler(new IExecutionExceptionHandler() {
+            @Override
+            public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult)
+                    throws Exception {
+                if (ex instanceof IllegalArgumentException) {
+                    System.err.println("Error: " + ex.getMessage());
+                    cl.usage(System.err);
+                    return 1;
+                } else {
+                    throw ex;
+                }
+            }
+        });
+        System.exit(cl.execute(args));
+    }
+}
diff --git a/src/main/scripts/unix/unimelb-mf-instrument-upload-find b/src/main/scripts/unix/unimelb-mf-instrument-upload-find
deleted file mode 100644
index f089c1eecb8d4744d65e657f406375e4ddefd00c..0000000000000000000000000000000000000000
--- a/src/main/scripts/unix/unimelb-mf-instrument-upload-find
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-
-# ${ROOT}/bin/
-BIN=$(dirname ${BASH_SOURCE[0]})
-
-# current directory
-CWD=$(pwd)
-
-# ${ROOT}/
-ROOT=$(cd ${BIN}/../../ && pwd && cd ${CWD})
-
-# ${ROOT}/lib/
-LIB=${ROOT}/lib
-
-# ${ROOT}/lib/unimelb-mf-clients.jar
-JAR=${LIB}/unimelb-mf-clients.jar
-
-# check if unimelb-mf-clients.jar exists
-[[ ! -f $JAR ]] && echo "${JAR} is not found." >&2 && exit 2
-
-# JRE included?
-JRE=${ROOT}/jre
-if [[ -d ${JRE} ]]; then
-  JAVA=${JRE}/bin/java
-else
-  JAVA=$(which java)
-  [[ -z ${JAVA} ]] && echo "could not find java" && exit 1
-fi
-
-# execute the command
-$JAVA -XX:+UseG1GC -XX:+UseStringDeduplication -Xmx1g -cp "${JAR}" unimelb.mf.client.instrument.cli.InstrumentUploadFind ${1+"$@"}
diff --git a/src/main/scripts/windows/unimelb-mf-instrument-upload-find.cmd b/src/main/scripts/windows/unimelb-mf-instrument-upload-find.cmd
deleted file mode 100644
index cdc8a446a51ee893c9b9951bbc8f90075efa7e4f..0000000000000000000000000000000000000000
--- a/src/main/scripts/windows/unimelb-mf-instrument-upload-find.cmd
+++ /dev/null
@@ -1,21 +0,0 @@
-@echo off
-
-setlocal
-
-pushd "%~dp0..\..\"
-set "ROOT=%cd%"
-popd
-
-set "JRE=%ROOT%\jre"
-
-if exist "%JRE%\" (
-    set "JAVA=%JRE%\bin\java"
-) else (
-    set JAVA=java
-)
-
-set JAR=%ROOT%\lib\unimelb-mf-clients.jar
-
-"%JAVA%" -XX:+UseG1GC -XX:+UseStringDeduplication -Xmx1g -cp "%JAR%" unimelb.mf.client.instrument.cli.InstrumentUploadFind %*
-
-endlocal