diff --git a/CentralRepository/build.xml b/CentralRepository/build.xml
deleted file mode 100644
index 4e8c2ff783ae403993f2b6f9e6c9cf6e62737be0..0000000000000000000000000000000000000000
--- a/CentralRepository/build.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
-<!-- for some information on what you could do (e.g. targets to override). -->
-<!-- If you delete this file and reopen the project it will be recreated. -->
-<project name="org.sleuthkit.autopsy.centralrepository" default="netbeans" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">
-    <description>Builds, tests, and runs the project org.sleuthkit.autopsy.centralrepository.</description>
-    <import file="nbproject/build-impl.xml"/>
-    <import file="../BootstrapIvy.xml"/>
-
-    <property name="thirdparty.dir" value="${basedir}/../thirdparty" />
-    <property name="modules.dir" value="${basedir}/release/modules/" />
-    <property name="ext.dir" value="${modules.dir}/ext" />
-
-    <target name="resolve">
-        <ivy:settings file="ivysettings.xml" />
-        <ivy:resolve file="ivy.xml" conf="central-repository"/>
-    </target>
- 
-    <target name="retrieve" depends="resolve">
-        <ivy:retrieve conf="central-repository"  pattern="${basedir}/release/modules/ext/[artifact]-[revision](-[classifier]).[ext]" />   
-    </target>
-
-    <target name="init" depends="retrieve, harness.init" />
-      
-    <target name="clean" depends="projectized-common.clean">
-        <!--Override clean to delete jars, etc downloaded with Ivy  
-        or copied in from thirdparty folder.  This way we don't end up with 
-        out-of-date/unneeded stuff in the installer-->
-        <delete dir="${basedir}/release"/>
-    </target>
-</project>
diff --git a/CentralRepository/manifest.mf b/CentralRepository/manifest.mf
deleted file mode 100644
index b798470437a66784c49570e9ffd64ff5210db734..0000000000000000000000000000000000000000
--- a/CentralRepository/manifest.mf
+++ /dev/null
@@ -1,6 +0,0 @@
-Manifest-Version: 1.0
-AutoUpdate-Show-In-Client: true
-OpenIDE-Module: org.sleuthkit.autopsy.centralrepository
-OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/centralrepository/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.0
-OpenIDE-Module-Install: org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.class
diff --git a/CentralRepository/nbproject/build-impl.xml b/CentralRepository/nbproject/build-impl.xml
deleted file mode 100644
index 60467ca5c3a0f0f767c67c9319d5be9e276d6407..0000000000000000000000000000000000000000
--- a/CentralRepository/nbproject/build-impl.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-*** GENERATED FROM project.xml - DO NOT EDIT  ***
-***         EDIT ../build.xml INSTEAD         ***
--->
-<project name="org.sleuthkit.autopsy.centralrepository-impl" basedir="..">
-    <fail message="Please build using Ant 1.7.1 or higher.">
-        <condition>
-            <not>
-                <antversion atleast="1.7.1"/>
-            </not>
-        </condition>
-    </fail>
-    <property file="nbproject/private/suite-private.properties"/>
-    <property file="nbproject/suite.properties"/>
-    <fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail>
-    <property file="${suite.dir}/nbproject/private/platform-private.properties"/>
-    <property file="${suite.dir}/nbproject/platform.properties"/>
-    <macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2">
-        <attribute name="name"/>
-        <attribute name="value"/>
-        <sequential>
-            <property name="@{name}" value="${@{value}}"/>
-        </sequential>
-    </macrodef>
-    <macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2">
-        <attribute name="property"/>
-        <attribute name="value"/>
-        <sequential>
-            <property name="@{property}" value="@{value}"/>
-        </sequential>
-    </macrodef>
-    <property file="${user.properties.file}"/>
-    <nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
-    <nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
-    <nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
-    <fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness).">
-        <condition>
-            <not>
-                <contains string="${cluster.path.evaluated}" substring="platform"/>
-            </not>
-        </condition>
-    </fail>
-    <import file="${harness.dir}/build.xml"/>
-</project>
diff --git a/CentralRepository/nbproject/project.properties b/CentralRepository/nbproject/project.properties
deleted file mode 100644
index efcb9ba609d0e144deea9645c26b7088dd6f60b2..0000000000000000000000000000000000000000
--- a/CentralRepository/nbproject/project.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-file.reference.commons-dbcp2-2.1.1.jar=release/modules/ext/commons-dbcp2-2.1.1.jar
-file.reference.commons-logging-1.2.jar=release/modules/ext/commons-logging-1.2.jar
-file.reference.commons-pool2-2.4.2.jar=release/modules/ext/commons-pool2-2.4.2.jar
-file.reference.postgresql-42.1.1.jar=release/modules/ext/postgresql-42.1.1.jar
-file.reference.sqlite-jdbc-3.16.1.jar=release/modules/ext/sqlite-jdbc-3.16.1.jar
-javac.source=1.8
-javac.compilerargs=-Xlint -Xlint:-serial
-license.file=../LICENSE-2.0.txt
-nbm.homepage=http://www.sleuthkit.org/autopsy/
diff --git a/CentralRepository/nbproject/project.xml b/CentralRepository/nbproject/project.xml
deleted file mode 100644
index 1c66ca32ff484902bc129a6035d12137daa1f435..0000000000000000000000000000000000000000
--- a/CentralRepository/nbproject/project.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.apisupport.project</type>
-    <configuration>
-        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
-            <code-name-base>org.sleuthkit.autopsy.centralrepository</code-name-base>
-            <suite-component/>
-            <module-dependencies>
-                <dependency>
-                    <code-name-base>org.netbeans.api.progress</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>1</release-version>
-                        <specification-version>1.47.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.modules.options.api</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>1</release-version>
-                        <specification-version>1.45.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.awt</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.67.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.modules</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.48.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.nodes</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.45.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.util</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.7.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.util.lookup</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>8.33.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.6.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.windows</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>6.75.1</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.sleuthkit.autopsy.core</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>10</release-version>
-                        <specification-version>10.8</specification-version>
-                    </run-dependency>
-                </dependency>
-            </module-dependencies>
-            <public-packages/>
-            <class-path-extension>
-                <runtime-relative-path>ext/sqlite-jdbc-3.16.1.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\sqlite-jdbc-3.16.1.jar</binary-origin>
-            </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/commons-dbcp2-2.1.1.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\commons-dbcp2-2.1.1.jar</binary-origin>
-            </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/postgresql-42.1.1.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\postgresql-42.1.1.jar</binary-origin>
-            </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/commons-pool2-2.4.2.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\commons-pool2-2.4.2.jar</binary-origin>
-            </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/commons-logging-1.2.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\commons-logging-1.2.jar</binary-origin>
-            </class-path-extension>
-        </data>
-    </configuration>
-</project>
diff --git a/CentralRepository/nbproject/suite.properties b/CentralRepository/nbproject/suite.properties
deleted file mode 100644
index 29d7cc9bd6fdd81453543cdf1bcf1dab301e3a92..0000000000000000000000000000000000000000
--- a/CentralRepository/nbproject/suite.properties
+++ /dev/null
@@ -1 +0,0 @@
-suite.dir=${basedir}/..
diff --git a/Core/ivy.xml b/Core/ivy.xml
index d40831b7736fce028d20fed2b8984837ec091b4e..b6cfe4a5682459b700e0e41ebb136350747f49e0 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -19,5 +19,7 @@
         <dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
         <dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
 
+        <dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
+        <dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
     </dependencies>
 </ivy-module>
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index eb4739d209bc1c76858bb4596b132513956f4255..0fa567cf007141947e940ec0b34b9e1ba8a56930 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -362,6 +362,14 @@
                 <runtime-relative-path>ext/curator-framework-2.8.0.jar</runtime-relative-path>
                 <binary-origin>release/modules/ext/curator-framework-2.8.0.jar</binary-origin>
             </class-path-extension>
+            <class-path-extension>
+                <runtime-relative-path>ext/commons-dbcp2-2.1.1.jar</runtime-relative-path>
+                <binary-origin>release\modules\ext\commons-dbcp2-2.1.1.jar</binary-origin>
+            </class-path-extension>
+<class-path-extension>
+                <runtime-relative-path>ext/commons-pool2-2.4.2.jar</runtime-relative-path>
+                <binary-origin>release\modules\ext\commons-pool2-2.4.2.jar</binary-origin>
+            </class-path-extension>
         </data>
     </configuration>
 </project>
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
index eb810e708c1307d2d918b9aa21a2dad90a4c17f5..f12c2f7e5d207111217e0f966ce9a4ea2fefac28 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
@@ -36,8 +36,10 @@
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.DerivedFile;
 import org.sleuthkit.datamodel.LayoutFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.SleuthkitCase;
 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
+import org.sleuthkit.datamodel.SpecialDirectory;
 import org.sleuthkit.datamodel.TskCoreException;
 import org.sleuthkit.datamodel.TskFileRange;
 import org.sleuthkit.datamodel.VirtualDirectory;
@@ -45,6 +47,7 @@
 import org.sleuthkit.datamodel.TskDataException;
 import org.apache.commons.lang3.StringUtils;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.AbstractContent;
 import org.sleuthkit.datamodel.CarvingResult;
 import org.sleuthkit.datamodel.TskData;
 
@@ -295,7 +298,7 @@ public synchronized List<AbstractFile> openFiles(Content dataSource, String file
      * @param atime           The accessed time of the file.
      * @param mtime           The modified time of the file.
      * @param isFile          True if a file, false if a directory.
-     * @param parentFile      The parent file from which the file was derived.
+     * @param parentObj       The parent object from which the file was derived.     
      * @param rederiveDetails The details needed to re-derive file (will be
      *                        specific to the derivation method), currently
      *                        unused.
@@ -317,7 +320,7 @@ public synchronized DerivedFile addDerivedFile(String fileName,
             long size,
             long ctime, long crtime, long atime, long mtime,
             boolean isFile,
-            AbstractFile parentFile,
+            Content parentObj,
             String rederiveDetails, String toolName, String toolVersion, String otherDetails,
             TskData.EncodingType encodingType) throws TskCoreException {
         if (null == caseDb) {
@@ -325,7 +328,7 @@ public synchronized DerivedFile addDerivedFile(String fileName,
         }
         return caseDb.addDerivedFile(fileName, localPath, size,
                 ctime, crtime, atime, mtime,
-                isFile, parentFile, rederiveDetails, toolName, toolVersion, otherDetails, encodingType);
+                isFile, parentObj, rederiveDetails, toolName, toolVersion, otherDetails, encodingType);
     }
 
     /**
@@ -496,12 +499,9 @@ private List<java.io.File> getFilesAndDirectories(List<String> localFilePaths) t
      * database, recursively adding the contents of directories.
      *
      * @param trans              A case database transaction.
-     * @param parentDirectory    The root virtual direcotry of the data source.
+     * @param parentDirectory    The root virtual directory of the data source or the parent local directory.
      * @param localFile          The local/logical file or directory.
      * @param encodingType       Type of encoding used when storing the file
-     *
-     * @returns File object of file added or new virtualdirectory for the
-     * directory.
      * @param progressUpdater    Called after each file/directory is added to
      *                           the case database.
      *
@@ -510,14 +510,14 @@ private List<java.io.File> getFilesAndDirectories(List<String> localFilePaths) t
      * @throws TskCoreException If there is a problem completing a database
      *                          operation.
      */
-    private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile,
+    private AbstractFile addLocalFile(CaseDbTransaction trans, SpecialDirectory parentDirectory, java.io.File localFile,
             TskData.EncodingType encodingType, FileAddProgressUpdater progressUpdater) throws TskCoreException {
         if (localFile.isDirectory()) {
             /*
-             * Add the directory as a virtual directory.
+             * Add the directory as a local directory.
              */
-            VirtualDirectory virtualDirectory = caseDb.addVirtualDirectory(parentDirectory.getId(), localFile.getName(), trans);
-            progressUpdater.fileAdded(virtualDirectory);
+            LocalDirectory localDirectory = caseDb.addLocalDirectory(parentDirectory.getId(), localFile.getName(), trans);
+            progressUpdater.fileAdded(localDirectory);
 
             /*
              * Add its children, if any.
@@ -525,11 +525,11 @@ private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory pare
             final java.io.File[] childFiles = localFile.listFiles();
             if (childFiles != null && childFiles.length > 0) {
                 for (java.io.File childFile : childFiles) {
-                    addLocalFile(trans, virtualDirectory, childFile, progressUpdater);
+                    addLocalFile(trans, localDirectory, childFile, progressUpdater);
                 }
             }
 
-            return virtualDirectory;
+            return localDirectory;
         } else {
             return caseDb.addLocalFile(localFile.getName(), localFile.getAbsolutePath(), localFile.length(),
                     0, 0, 0, 0,
@@ -677,13 +677,10 @@ public synchronized DerivedFile addDerivedFile(String fileName,
      * database, recursively adding the contents of directories.
      *
      * @param trans              A case database transaction.
-     * @param parentDirectory    The root virtual direcotry of the data source.
+     * @param parentDirectory    The root virtual directory of the data source or the parent local directory.
      * @param localFile          The local/logical file or directory.
      * @param progressUpdater notifier to receive progress notifications on
      *                           folders added, or null if not used
-     *
-     * @returns File object of file added or new virtualdirectory for the
-     * directory.
      * @param progressUpdater    Called after each file/directory is added to
      *                           the case database.
      *
@@ -695,7 +692,7 @@ public synchronized DerivedFile addDerivedFile(String fileName,
      * @deprecated Use the version with explicit EncodingType instead
      */
     @Deprecated
-    private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException {
+    private AbstractFile addLocalFile(CaseDbTransaction trans, SpecialDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException {
         return addLocalFile(trans, parentDirectory, localFile, TskData.EncodingType.NONE, progressUpdater);
     }
 
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties
rename to Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/README-POSTGRES-TESTING.md b/Core/src/org/sleuthkit/autopsy/centralrepository/README-POSTGRES-TESTING.md
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/README-POSTGRES-TESTING.md
rename to Core/src/org/sleuthkit/autopsy/centralrepository/README-POSTGRES-TESTING.md
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/README_MONGODB_TESTING.md b/Core/src/org/sleuthkit/autopsy/centralrepository/README_MONGODB_TESTING.md
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/README_MONGODB_TESTING.md
rename to Core/src/org/sleuthkit/autopsy/centralrepository/README_MONGODB_TESTING.md
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/actions/Bundle.properties
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/Bundle.properties
rename to Core/src/org/sleuthkit/autopsy/centralrepository/actions/Bundle.properties
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamCaseEditDetailsDialog.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamEditCaseInfoAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamEditCaseInfoAction.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/actions/EamEditCaseInfoAction.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/actions/EamEditCaseInfoAction.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties
rename to Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableCellRenderer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableCellRenderer.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableCellRenderer.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableCellRenderer.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
similarity index 98%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
index 2fa17e42d87449485efa836775e8e05fb421747e..e6fa8730a704004e5478845e080ac71b37084612 100644
--- a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java
@@ -1095,6 +1095,20 @@ public void setArtifactInstanceKnownBad(EamArtifact eamArtifact) throws EamDbExc
 
                 preparedUpdate.executeUpdate();
             } else {
+                // In this case, the user is tagging something that isn't in the database,
+                // which means the case and/or datasource may also not be in the database.
+                // We could improve effiency by keeping a list of all datasources and cases
+                // in the database, but we don't expect the user to be tagging large numbers
+                // of items (that didn't have the CE ingest module run on them) at once.
+                
+                if(null == getCaseDetails(eamInstance.getEamCase().getCaseUUID())){
+                    newCase(eamInstance.getEamCase());
+                }
+                
+                if (null == getDataSourceDetails(eamInstance.getEamDataSource().getDeviceID())) {
+                    newDataSource(eamInstance.getEamDataSource());
+                }
+                
                 eamArtifact.getInstances().get(0).setKnownStatus(TskData.FileKnown.BAD);
                 addArtifact(eamArtifact);
             }
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifact.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifact.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifact.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifact.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactInstance.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactInstance.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactInstance.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamCase.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamCase.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamCase.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamCase.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDataSource.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDataSource.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDataSource.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbPlatformEnum.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbPlatformEnum.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbPlatformEnum.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbPlatformEnum.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalSet.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalSet.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalSet.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalSet.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/BadFileTagRunner.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/BadFileTagRunner.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/BadFileTagRunner.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/BadFileTagRunner.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
similarity index 88%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
index a0a78786053b0f70c121ec3092878f5463fbfa70..d2b1beebbb230f17d4844bf1a99c262046882330 100644
--- a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java
@@ -35,6 +35,20 @@ public class Installer extends ModuleInstall {
     private final PropertyChangeListener pcl = new CaseEventListener();
     private final IngestEventsListener ieListener = new IngestEventsListener();
 
+    private static Installer instance;
+
+    public synchronized static Installer getDefault() {
+        if (instance == null) {
+            instance = new Installer();
+        }
+        return instance;
+    }
+    
+    private Installer() {
+        super();
+    }
+    
+    
     @Override
     public void restored() {
         Case.addPropertyChangeListener(pcl);
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/NewArtifactsRunner.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/NewArtifactsRunner.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/NewArtifactsRunner.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/NewArtifactsRunner.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/bad.png b/Core/src/org/sleuthkit/autopsy/centralrepository/images/bad.png
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/bad.png
rename to Core/src/org/sleuthkit/autopsy/centralrepository/images/bad.png
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/good.png b/Core/src/org/sleuthkit/autopsy/centralrepository/images/good.png
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/good.png
rename to Core/src/org/sleuthkit/autopsy/centralrepository/images/good.png
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/import16.png b/Core/src/org/sleuthkit/autopsy/centralrepository/images/import16.png
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/import16.png
rename to Core/src/org/sleuthkit/autopsy/centralrepository/images/import16.png
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/options-icon.png b/Core/src/org/sleuthkit/autopsy/centralrepository/images/options-icon.png
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/images/options-icon.png
rename to Core/src/org/sleuthkit/autopsy/centralrepository/images/options-icon.png
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/license-centralrepository.txt b/Core/src/org/sleuthkit/autopsy/centralrepository/license-centralrepository.txt
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/license-centralrepository.txt
rename to Core/src/org/sleuthkit/autopsy/centralrepository/license-centralrepository.txt
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamOptionsPanelController.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamOptionsPanelController.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamOptionsPanelController.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ImportHashDatabaseDialog.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.form
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.form
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.form
diff --git a/CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java
similarity index 100%
rename from CentralRepository/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java
rename to Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageTagsDialog.java
diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java
index 748870d7832de6ba8228eb89ac7fb89d442ead44..0b660acd2a3d535fd790ca399692671fed57420e 100644
--- a/Core/src/org/sleuthkit/autopsy/core/Installer.java
+++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java
@@ -212,6 +212,7 @@ public Installer() {
         packageInstallers.add(org.sleuthkit.autopsy.corecomponents.Installer.getDefault());
         packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault());
         packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault());
+        packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault());
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
index f80866ac9bf5051e8d6e3ba57cafcd9c80a3e0f1..c4f35e9b8a11867ce0fc9a9b35c9fc0fdc2c4c9c 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java
@@ -26,6 +26,7 @@
 import java.awt.datatransfer.StringSelection;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
@@ -456,12 +457,13 @@ public boolean isSupported(Node node) {
             return false;
         }
 
-        Content content = node.getLookup().lookup(Content.class);
-        if (content != null) {
-            try {
-                return content.getAllArtifactsCount() > 0;
-            } catch (TskException ex) {
-                logger.log(Level.WARNING, "Couldn't get count of BlackboardArtifacts for content", ex); //NON-NLS
+        for (Content content : node.getLookup().lookupAll(Content.class)) {
+            if ( (content != null)  && (!(content instanceof BlackboardArtifact)) ){
+                try {
+                    return content.getAllArtifactsCount() > 0;
+                } catch (TskException ex) {
+                    logger.log(Level.SEVERE, "Couldn't get count of BlackboardArtifacts for content", ex); //NON-NLS
+                }
             }
         }
         return false;
@@ -693,22 +695,28 @@ protected ViewUpdate doInBackground() {
             // blackboard artifact, if any.
             Lookup lookup = selectedNode.getLookup();
 
-            // Get the content.
-            Content content = lookup.lookup(Content.class);
-            if (content == null) {
+            // Get the content. We may get BlackboardArtifacts, ignore those here.
+            ArrayList<BlackboardArtifact> artifacts = new ArrayList<>();
+            Collection<? extends Content> contents = lookup.lookupAll(Content.class);
+            if (contents.isEmpty()) {
                 return new ViewUpdate(getArtifactContents().size(), currentPage, ERROR_TEXT);
             }
-
-            // Get all of the blackboard artifacts associated with the content. These are what this
-            // viewer displays.
-            ArrayList<BlackboardArtifact> artifacts;
-            try {
-                artifacts = content.getAllArtifacts();
-            } catch (TskException ex) {
-                logger.log(Level.WARNING, "Couldn't get artifacts", ex); //NON-NLS
-                return new ViewUpdate(getArtifactContents().size(), currentPage, ERROR_TEXT);
+            Content underlyingContent = null;
+            for (Content content : contents) {
+                if ( (content != null)  && (!(content instanceof BlackboardArtifact)) ) {
+                    // Get all of the blackboard artifacts associated with the content. These are what this
+                    // viewer displays.
+                    try {
+                        artifacts = content.getAllArtifacts();
+                        underlyingContent = content;
+                        break;
+                    } catch (TskException ex) {
+                        logger.log(Level.SEVERE, "Couldn't get artifacts", ex); //NON-NLS
+                        return new ViewUpdate(getArtifactContents().size(), currentPage, ERROR_TEXT);
+                    }
+                }
             }
-
+ 
             if (isCancelled()) {
                 return null;
             }
@@ -716,7 +724,7 @@ protected ViewUpdate doInBackground() {
             // Build the new artifact contents cache.
             ArrayList<ResultsTableArtifact> artifactContents = new ArrayList<>();
             for (BlackboardArtifact artifact : artifacts) {
-                artifactContents.add(new ResultsTableArtifact(artifact, content));
+                artifactContents.add(new ResultsTableArtifact(artifact, underlyingContent));
             }
 
             // If the node has an underlying blackboard artifact, show it. If not,
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
index 4b522d0f415e2c0c6689b48752088543cd6f522a..25cd62e7199400cec7448cbb09673b1b4f7dd4c0 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java
@@ -37,7 +37,6 @@
 import java.util.TreeSet;
 import java.util.logging.Level;
 import java.util.prefs.Preferences;
-import java.util.stream.Stream;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingUtilities;
@@ -59,8 +58,6 @@
 import org.openide.nodes.Children;
 import org.openide.nodes.Node;
 import org.openide.nodes.Node.Property;
-import org.openide.nodes.NodeAdapter;
-import org.openide.nodes.NodeMemberEvent;
 import org.openide.util.NbBundle;
 import org.openide.util.NbPreferences;
 import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
@@ -82,8 +79,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
     private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName());
     @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
     static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
-    @NbBundle.Messages("DataResultViewerTable.pleasewaitNodeDisplayName=Please Wait...")
-    private static final String PLEASEWAIT_NODE_DISPLAY_NAME = Bundle.DataResultViewerTable_pleasewaitNodeDisplayName();
     private static final Color TAGGED_COLOR = new Color(200, 210, 220);
     /**
      * The properties map:
@@ -103,8 +98,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
      */
     private final Map<String, ETableColumn> columnMap = new HashMap<>();
 
-    private final PleasewaitNodeListener pleasewaitNodeListener = new PleasewaitNodeListener();
-
     private Node currentRoot;
 
     /*
@@ -218,15 +211,8 @@ public void setNode(Node selectedNode) {
                 hasChildren = selectedNode.getChildren().getNodesCount() > 0;
             }
 
-            Node oldNode = this.em.getRootContext();
-            if (oldNode != null) {
-                oldNode.removeNodeListener(pleasewaitNodeListener);
-            }
-
             if (hasChildren) {
                 currentRoot = selectedNode;
-                pleasewaitNodeListener.reset();
-                currentRoot.addNodeListener(pleasewaitNodeListener);
                 em.setRootContext(currentRoot);
                 setupTable();
             } else {
@@ -764,35 +750,6 @@ private void listenToVisibilityChanges(boolean b) {
         }
     }
 
-    private class PleasewaitNodeListener extends NodeAdapter {
-
-        private volatile boolean load = true;
-
-        public void reset() {
-            load = true;
-        }
-
-        @Override
-        public void childrenAdded(final NodeMemberEvent nme) {
-            Node[] delta = nme.getDelta();
-            if (load && containsReal(delta)) {
-                load = false;
-                //JMTODO: this looks suspicious
-                if (SwingUtilities.isEventDispatchThread()) {
-                    setupTable();
-                } else {
-                    SwingUtilities.invokeLater(() -> setupTable());
-                }
-            }
-        }
-
-        private boolean containsReal(Node[] delta) {
-            return Stream.of(delta)
-                    .map(Node::getDisplayName)
-                    .noneMatch(PLEASEWAIT_NODE_DISPLAY_NAME::equals);
-        }
-    }
-
     /**
      * This custom renderer extends the renderer that was already being used by
      * the outline table. This renderer colors a row if the tags property of the
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
index e3c750826d2dff1b2ebec83a66576c61b73f40f2..a1d85d50df0bbb7220cb157728c1356f02bacf34 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2011-2017 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,17 +22,19 @@
 import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.List;
-import org.openide.nodes.Children;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
+import org.openide.nodes.Children;
 import org.openide.nodes.Sheet;
 import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
 import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
+import static org.sleuthkit.autopsy.datamodel.Bundle.*;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
 import org.sleuthkit.datamodel.AbstractFile;
@@ -47,25 +49,22 @@
  */
 public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends AbstractContentNode<T> {
 
-    private static final Logger LOGGER = Logger.getLogger(AbstractAbstractFileNode.class.getName());
+    private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName());
+    @NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description")
+    private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
 
     /**
-     * @param <T> type of the AbstractFile data to encapsulate
-     * @param abstractFile file to encapsulate
+     * @param abstractFile file to wrap
      */
     AbstractAbstractFileNode(T abstractFile) {
         super(abstractFile);
-        String name = abstractFile.getName();
-        int dotIndex = name.lastIndexOf(".");
-        if (dotIndex > 0) {
-            String ext = name.substring(dotIndex).toLowerCase();
-
+        String ext = abstractFile.getNameExtension();
+        if (StringUtils.isNotBlank(ext)) {
+            ext = "." + ext;
             // If this is an archive file we will listen for ingest events
             // that will notify us when new content has been identified.
-            for (String s : FileTypeExtensions.getArchiveExtensions()) {
-                if (ext.equals(s)) {
-                    IngestManager.getInstance().addIngestModuleEventListener(pcl);
-                }
+            if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
+                IngestManager.getInstance().addIngestModuleEventListener(pcl);
             }
         }
         // Listen for case events so that we can detect when case is closed
@@ -103,7 +102,6 @@ private void removeListeners() {
                 } catch (NullPointerException ex) {
                     // Skip
                 }
-
             }
         } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
             if (evt.getNewValue() == null) {
@@ -127,194 +125,125 @@ private void updateSheet() {
         this.setSheet(createSheet());
     }
 
-    // Note: this order matters for the search result, changed it if the order of property headers on the "KeywordSearchNode"changed
-    public static enum AbstractFilePropertyType {
+    @NbBundle.Messages({"AbstractAbstractFileNode.nameColLbl=Name",
+        "AbstractAbstractFileNode.locationColLbl=Location",
+        "AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time",
+        "AbstractAbstractFileNode.changeTimeColLbl=Change Time",
+        "AbstractAbstractFileNode.accessTimeColLbl=Access Time",
+        "AbstractAbstractFileNode.createdTimeColLbl=Created Time",
+        "AbstractAbstractFileNode.sizeColLbl=Size",
+        "AbstractAbstractFileNode.flagsDirColLbl=Flags(Dir)",
+        "AbstractAbstractFileNode.flagsMetaColLbl=Flags(Meta)",
+        "AbstractAbstractFileNode.modeColLbl=Mode",
+        "AbstractAbstractFileNode.useridColLbl=UserID",
+        "AbstractAbstractFileNode.groupidColLbl=GroupID",
+        "AbstractAbstractFileNode.metaAddrColLbl=Meta Addr.",
+        "AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.",
+        "AbstractAbstractFileNode.typeDirColLbl=Type(Dir)",
+        "AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)",
+        "AbstractAbstractFileNode.knownColLbl=Known",
+        "AbstractAbstractFileNode.inHashsetsColLbl=In Hashsets",
+        "AbstractAbstractFileNode.md5HashColLbl=MD5 Hash",
+        "AbstractAbstractFileNode.objectId=Object ID",
+        "AbstractAbstractFileNode.mimeType=MIME Type",
+        "AbstractAbstractFileNode.extensionColLbl=Extension"})
+    public enum AbstractFilePropertyType {
 
-        NAME {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.nameColLbl");
-                    }
-                },
-        LOCATION {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.locationColLbl");
-                    }
-                },
-        MOD_TIME {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.modifiedTimeColLbl");
-                    }
-                },
-        CHANGED_TIME {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.changeTimeColLbl");
-                    }
-                },
-        ACCESS_TIME {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.accessTimeColLbl");
-                    }
-                },
-        CREATED_TIME {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.createdTimeColLbl");
-                    }
-                },
-        SIZE {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.sizeColLbl");
-                    }
-                },
-        FLAGS_DIR {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.flagsDirColLbl");
-                    }
-                },
-        FLAGS_META {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.flagsMetaColLbl");
-                    }
-                },
-        MODE {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.modeColLbl");
-                    }
-                },
-        USER_ID {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.useridColLbl");
-                    }
-                },
-        GROUP_ID {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.groupidColLbl");
-                    }
-                },
-        META_ADDR {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.metaAddrColLbl");
-                    }
-                },
-        ATTR_ADDR {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.attrAddrColLbl");
-                    }
-                },
-        TYPE_DIR {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.typeDirColLbl");
-                    }
-                },
-        TYPE_META {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.typeMetaColLbl");
-                    }
-                },
-        KNOWN {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.knownColLbl");
-                    }
-                },
-        HASHSETS {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.inHashsetsColLbl");
-                    }
-                },
-        MD5HASH {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.md5HashColLbl");
-                    }
-                },
-        ObjectID {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.objectId");
+        NAME(AbstractAbstractFileNode_nameColLbl()),
+        LOCATION(AbstractAbstractFileNode_locationColLbl()),
+        MOD_TIME(AbstractAbstractFileNode_modifiedTimeColLbl()),
+        CHANGED_TIME(AbstractAbstractFileNode_changeTimeColLbl()),
+        ACCESS_TIME(AbstractAbstractFileNode_accessTimeColLbl()),
+        CREATED_TIME(AbstractAbstractFileNode_createdTimeColLbl()),
+        SIZE(AbstractAbstractFileNode_sizeColLbl()),
+        FLAGS_DIR(AbstractAbstractFileNode_flagsDirColLbl()),
+        FLAGS_META(AbstractAbstractFileNode_flagsMetaColLbl()),
+        MODE(AbstractAbstractFileNode_modeColLbl()),
+        USER_ID(AbstractAbstractFileNode_useridColLbl()),
+        GROUP_ID(AbstractAbstractFileNode_groupidColLbl()),
+        META_ADDR(AbstractAbstractFileNode_metaAddrColLbl()),
+        ATTR_ADDR(AbstractAbstractFileNode_attrAddrColLbl()),
+        TYPE_DIR(AbstractAbstractFileNode_typeDirColLbl()),
+        TYPE_META(AbstractAbstractFileNode_typeMetaColLbl()),
+        KNOWN(AbstractAbstractFileNode_knownColLbl()),
+        HASHSETS(AbstractAbstractFileNode_inHashsetsColLbl()),
+        MD5HASH(AbstractAbstractFileNode_md5HashColLbl()),
+        ObjectID(AbstractAbstractFileNode_objectId()),
+        MIMETYPE(AbstractAbstractFileNode_mimeType()),
+        EXTENSION(AbstractAbstractFileNode_extensionColLbl());
 
-                    }
-                },
-        MIMETYPE {
-                    @Override
-                    public String toString() {
-                        return NbBundle.getMessage(this.getClass(), "AbstractAbstractFileNode.mimeType");
+        final private String displayString;
 
-                    }
-                },
+        private AbstractFilePropertyType(String displayString) {
+            this.displayString = displayString;
+        }
+
+        @Override
+        public String toString() {
+            return displayString;
+        }
     }
 
     /**
      * Fill map with AbstractFile properties
      *
-     * @param map map with preserved ordering, where property names/values are
-     * put
-     * @param content to extract properties from
+     * @param map     map with preserved ordering, where property names/values
+     *                are put
+     * @param content The content to get properties for.
      */
-    public static void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
-
-        String path = "";
-        try {
-            path = content.getUniquePath();
-        } catch (TskCoreException ex) {
-            LOGGER.log(Level.SEVERE, "Except while calling Content.getUniquePath() on {0}", content); //NON-NLS
-        }
-
-        map.put(AbstractFilePropertyType.NAME.toString(), AbstractAbstractFileNode.getContentDisplayName(content));
-        map.put(AbstractFilePropertyType.LOCATION.toString(), path);
-        map.put(AbstractFilePropertyType.MOD_TIME.toString(), ContentUtils.getStringTime(content.getMtime(), content));
-        map.put(AbstractFilePropertyType.CHANGED_TIME.toString(), ContentUtils.getStringTime(content.getCtime(), content));
-        map.put(AbstractFilePropertyType.ACCESS_TIME.toString(), ContentUtils.getStringTime(content.getAtime(), content));
-        map.put(AbstractFilePropertyType.CREATED_TIME.toString(), ContentUtils.getStringTime(content.getCrtime(), content));
-        map.put(AbstractFilePropertyType.SIZE.toString(), content.getSize());
-        map.put(AbstractFilePropertyType.FLAGS_DIR.toString(), content.getDirFlagAsString());
-        map.put(AbstractFilePropertyType.FLAGS_META.toString(), content.getMetaFlagsAsString());
-        map.put(AbstractFilePropertyType.MODE.toString(), content.getModesAsString());
-        map.put(AbstractFilePropertyType.USER_ID.toString(), content.getUid());
-        map.put(AbstractFilePropertyType.GROUP_ID.toString(), content.getGid());
-        map.put(AbstractFilePropertyType.META_ADDR.toString(), content.getMetaAddr());
-        map.put(AbstractFilePropertyType.ATTR_ADDR.toString(), Long.toString(content.getAttrType().getValue()) + "-" + content.getAttributeId());
-        map.put(AbstractFilePropertyType.TYPE_DIR.toString(), content.getDirType().getLabel());
-        map.put(AbstractFilePropertyType.TYPE_META.toString(), content.getMetaType().toString());
-        map.put(AbstractFilePropertyType.KNOWN.toString(), content.getKnown().getName());
-        map.put(AbstractFilePropertyType.HASHSETS.toString(), getHashSetHitsForFile(content));
-        map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash());
-        map.put(AbstractFilePropertyType.ObjectID.toString(), content.getId());
-        map.put(AbstractFilePropertyType.MIMETYPE.toString(), content.getMIMEType() == null ? "" : content.getMIMEType());
+    static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
+        map.put(NAME.toString(), getContentDisplayName(content));
+        map.put(LOCATION.toString(), getContentPath(content));
+        map.put(MOD_TIME.toString(), ContentUtils.getStringTime(content.getMtime(), content));
+        map.put(CHANGED_TIME.toString(), ContentUtils.getStringTime(content.getCtime(), content));
+        map.put(ACCESS_TIME.toString(), ContentUtils.getStringTime(content.getAtime(), content));
+        map.put(CREATED_TIME.toString(), ContentUtils.getStringTime(content.getCrtime(), content));
+        map.put(SIZE.toString(), content.getSize());
+        map.put(FLAGS_DIR.toString(), content.getDirFlagAsString());
+        map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
+        map.put(MODE.toString(), content.getModesAsString());
+        map.put(USER_ID.toString(), content.getUid());
+        map.put(GROUP_ID.toString(), content.getGid());
+        map.put(META_ADDR.toString(), content.getMetaAddr());
+        map.put(ATTR_ADDR.toString(), content.getAttrType().getValue() + "-" + content.getAttributeId());
+        map.put(TYPE_DIR.toString(), content.getDirType().getLabel());
+        map.put(TYPE_META.toString(), content.getMetaType().toString());
+        map.put(KNOWN.toString(), content.getKnown().getName());
+        map.put(HASHSETS.toString(), getHashSetHitsForFile(content));
+        map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
+        map.put(ObjectID.toString(), content.getId());
+        map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
+        map.put(EXTENSION.toString(), content.getNameExtension());
     }
 
     /**
      * Used by subclasses of AbstractAbstractFileNode to add the tags property
      * to their sheets.
-     * @param ss the modifiable Sheet.Set returned by Sheet.get(Sheet.PROPERTIES)
+     *
+     * @param ss the modifiable Sheet.Set returned by
+     *           Sheet.get(Sheet.PROPERTIES)
      */
+    @NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
     protected void addTagProperty(Sheet.Set ss) {
-        final String NO_DESCR = NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.addFileProperty.desc");
-        List<ContentTag> tags;
+        List<ContentTag> tags = new ArrayList<>();
+        try {
+            tags.addAll(Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(content));
+        } catch (TskCoreException ex) {
+            logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
+        }
+        ss.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
+                NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
+                        .distinct()
+                        .collect(Collectors.joining(", "))));
+    }
+
+    private static String getContentPath(AbstractFile file) {
         try {
-            tags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(content);
+            return file.getUniquePath();
         } catch (TskCoreException ex) {
-            tags = new ArrayList<>();
-            LOGGER.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
+            logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + file, ex); //NON-NLS
+            return "";            //NON-NLS
         }
-        ss.put(new NodeProperty<>("Tags", NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.addFileProperty.tags.displayName"),
-                NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", "))));
     }
 
     static String getContentDisplayName(AbstractFile file) {
@@ -330,12 +259,11 @@ static String getContentDisplayName(AbstractFile file) {
         }
     }
 
-    @SuppressWarnings("deprecation")
-    private static String getHashSetHitsForFile(AbstractFile content) {
+    private static String getHashSetHitsForFile(AbstractFile file) {
         try {
-            return StringUtils.join(content.getHashSetNames(), ", ");
+            return StringUtils.join(file.getHashSetNames(), ", ");
         } catch (TskCoreException tskCoreException) {
-            LOGGER.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
+            logger.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
             return "";
         }
     }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
index 03066faa550f84c74e099b7ed7854a530a947c0c..bf139f9a476ba703a9e440b0568cd3f0d3ae3b4a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java
@@ -25,6 +25,7 @@
 import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
 import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
 import org.sleuthkit.autopsy.datamodel.accounts.Accounts.AccountsRootNode;
+import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.DerivedFile;
 import org.sleuthkit.datamodel.Directory;
@@ -32,6 +33,7 @@
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.SleuthkitItemVisitor;
 import org.sleuthkit.datamodel.SleuthkitVisitableItem;
@@ -112,11 +114,21 @@ public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) {
             return new VirtualDirectoryNode(ld);
         }
 
+        @Override
+        public AbstractContentNode<? extends Content> visit(LocalDirectory ld) {
+            return new LocalDirectoryNode(ld);
+        }
+
         @Override
         public AbstractContentNode<? extends Content> visit(SlackFile sf) {
             return new SlackFileNode(sf);
         }
 
+        @Override
+        public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
+            return new BlackboardArtifactNode(art);
+        }
+
         @Override
         protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
             throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
index 0a88cfd5dc59824f6e5d1a881c242a750669b6d7..82a8130c23ac17ce40e8481392524631d84f514f 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java
@@ -21,8 +21,8 @@
 import java.util.List;
 import java.util.logging.Level;
 
-import org.openide.util.NbBundle;
 import org.openide.util.lookup.Lookups;
+import org.openide.util.Lookup;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.TskCoreException;
@@ -48,13 +48,23 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
      * @param content Underlying Content instances
      */
     AbstractContentNode(T content) {
-        //TODO consider child factory for the content children
-        super(new ContentChildren(content), Lookups.singleton(content));
+        this(content, Lookups.singleton(content) );
+    }
+
+    /**
+     * Handles aspects that depend on the Content object
+     *
+     * @param content Underlying Content instances
+     * @param lookup   The Lookup object for the node.
+     */
+    AbstractContentNode(T content, Lookup lookup) {
+         //TODO consider child factory for the content children
+        super(new ContentChildren(content), lookup);
         this.content = content;
         //super.setName(ContentUtils.getSystemName(content));
         super.setName("content_" + Long.toString(content.getId())); //NON-NLS
     }
-
+    
     /**
      * Return the content data associated with this node
      *
@@ -66,8 +76,7 @@ public T getContent() {
 
     @Override
     public void setName(String name) {
-        throw new UnsupportedOperationException(
-                NbBundle.getMessage(this.getClass(), "AbstractContentNode.exception.cannotChangeSysName.msg"));
+        super.setName(name);
     }
 
     @Override
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java
index a6db3c1df1cbca62aa2264693caf1185962089ba..b8a6807c2409d0aa3300f51642ad36262f38e17a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java
@@ -69,14 +69,11 @@ protected Sheet createSheet() {
             s.put(ss);
         }
 
-        Map<String, Object> map = new LinkedHashMap<String, Object>();
-        AbstractAbstractFileNode.fillPropertyMap(map, content);
+        Map<String, Object> map = new LinkedHashMap<>();
+        fillPropertyMap(map, getContent());
 
-        AbstractFilePropertyType[] fsTypes = AbstractFilePropertyType.values();
-        final int FS_PROPS_LEN = fsTypes.length;
         final String NO_DESCR = NbBundle.getMessage(this.getClass(), "AbstractFsContentNode.noDesc.text");
-        for (int i = 0; i < FS_PROPS_LEN; ++i) {
-            final AbstractFilePropertyType propType = AbstractFilePropertyType.values()[i];
+        for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) {
             final String propString = propType.toString();
             ss.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString)));
         }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
index 7317b5781f2476f721456a2fba7b51ffd9f77095..0e1c7cb8aa6afb5dfe9f9042fa0a639b6bcad388 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java
@@ -25,6 +25,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -34,6 +35,7 @@
 import java.util.stream.Collectors;
 import javax.swing.Action;
 import org.openide.nodes.Children;
+import org.openide.nodes.Node;
 import org.openide.nodes.Sheet;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle;
@@ -43,9 +45,11 @@
 import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
 import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
 import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
+import static org.sleuthkit.autopsy.datamodel.DataModelActionsFactory.VIEW_IN_NEW_WINDOW;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
 import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked;
+import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
 import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
 import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
 import org.sleuthkit.datamodel.AbstractFile;
@@ -61,7 +65,7 @@
  * Node wrapping a blackboard artifact object. This is generated from several
  * places in the tree.
  */
-public class BlackboardArtifactNode extends DisplayableItemNode {
+public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifact> {
 
     private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName());
 
@@ -70,8 +74,9 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
             build();
 
     private final BlackboardArtifact artifact;
-    private final Content associated;
+    private Content associated = null;
     private List<NodeProperty<? extends Object>> customProperties;
+    
     /*
      * Artifact types which should have the full unique path of the associated
      * content as a property.
@@ -130,11 +135,18 @@ public void propertyChange(PropertyChangeEvent evt) {
      * @param iconPath icon to use for the artifact
      */
     public BlackboardArtifactNode(BlackboardArtifact artifact, String iconPath) {
-        super(Children.LEAF, createLookup(artifact));
+        super(artifact, createLookup(artifact));
 
         this.artifact = artifact;
-        //this.associated = getAssociatedContent(artifact);
-        this.associated = this.getLookup().lookup(Content.class);
+        
+        // Look for associated Content  i.e. the source file for the artifact
+        for (Content content : this.getLookup().lookupAll(Content.class)) {
+            if ( (content != null)  && (!(content instanceof BlackboardArtifact)) ){
+                this.associated = content;
+                break;
+            }
+        }
+        
         this.setName(Long.toString(artifact.getArtifactID()));
         this.setDisplayName();
         this.setIconBaseWithExtension(iconPath);
@@ -148,20 +160,18 @@ public BlackboardArtifactNode(BlackboardArtifact artifact, String iconPath) {
      * @param artifact artifact to encapsulate
      */
     public BlackboardArtifactNode(BlackboardArtifact artifact) {
-        super(Children.LEAF, createLookup(artifact));
-
-        this.artifact = artifact;
-        this.associated = this.getLookup().lookup(Content.class);
-        this.setName(Long.toString(artifact.getArtifactID()));
-        this.setDisplayName();
-        this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); //NON-NLS
-        Case.addPropertyChangeListener(pcl);
+        
+        this(artifact, ExtractedContent.getIconFilePath(artifact.getArtifactTypeID()));
     }
 
     private void removeListeners() {
         Case.removePropertyChangeListener(pcl);
     }
 
+    public BlackboardArtifact getArtifact() {
+        return this.artifact;
+    }
+    
     @Override
     @NbBundle.Messages({
         "BlackboardArtifactNode.getAction.errorTitle=Error getting actions",
@@ -211,10 +221,7 @@ public Action[] getActions(boolean context) {
      */
     private void setDisplayName() {
         String displayName = ""; //NON-NLS
-        if (associated != null) {
-            displayName = associated.getName();
-        }
-
+        
         // If this is a node for a keyword hit on an artifact, we set the
         // display name to be the artifact type name followed by " Artifact"
         // e.g. "Messages Artifact".
@@ -238,9 +245,30 @@ private void setDisplayName() {
                 // Do nothing since the display name will be set to the file name.
             }
         }
+        
+        if (displayName.isEmpty() && artifact != null) {
+            displayName = artifact.getName();
+        }
+        
         this.setDisplayName(displayName);
+        
     }
 
+    /**
+     * Return the name of the associated source file/content 
+     * 
+     * @return source file/content name
+     */
+    public String getSrcName() {
+        
+        String srcName = "";
+        if (associated != null) {
+            srcName =  associated.getName();
+        }
+        return srcName;
+    }
+    
+    
     @NbBundle.Messages({
         "BlackboardArtifactNode.createSheet.artifactType.displayName=Artifact Type",
         "BlackboardArtifactNode.createSheet.artifactType.name=Artifact Type",
@@ -264,7 +292,7 @@ protected Sheet createSheet() {
         ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"),
                 NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"),
                 NO_DESCR,
-                this.getDisplayName()));
+                this.getSrcName()));
         if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
             try {
                 BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
@@ -555,4 +583,9 @@ public boolean isLeafTypeNode() {
     public String getItemType() {
         return getClass().getName();
     }
+
+    @Override
+    public <T> T accept(ContentNodeVisitor<T> v) {
+         return v.visit(this);
+    }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
index a712c1bdb74c41660d045c174a69e0dc851b83ed..dd8a8cb5ff58d8f4a831e51678168fe1e4a7c7f2 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties
@@ -1,24 +1,4 @@
 OpenIDE-Module-Name=DataModel
-AbstractAbstractFileNode.nameColLbl=Name
-AbstractAbstractFileNode.locationColLbl=Location
-AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time
-AbstractAbstractFileNode.changeTimeColLbl=Change Time
-AbstractAbstractFileNode.accessTimeColLbl=Access Time
-AbstractAbstractFileNode.createdTimeColLbl=Created Time
-AbstractAbstractFileNode.sizeColLbl=Size
-AbstractAbstractFileNode.flagsDirColLbl=Flags(Dir)
-AbstractAbstractFileNode.flagsMetaColLbl=Flags(Meta)
-AbstractAbstractFileNode.modeColLbl=Mode
-AbstractAbstractFileNode.useridColLbl=UserID
-AbstractAbstractFileNode.groupidColLbl=GroupID
-AbstractAbstractFileNode.metaAddrColLbl=Meta Addr.
-AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.
-AbstractAbstractFileNode.typeDirColLbl=Type(Dir)
-AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
-AbstractAbstractFileNode.knownColLbl=Known
-AbstractAbstractFileNode.inHashsetsColLbl=In Hashsets
-AbstractAbstractFileNode.md5HashColLbl=MD5 Hash
-AbstractAbstractFileNode.mimeType = MIME Type
 AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg=No Node defined for the given SleuthkitItem
 AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg=No Node defined for the given DisplayableItem
 AbstractContentNode.exception.cannotChangeSysName.msg=Cannot change the system name.
@@ -80,17 +60,6 @@ DataSourcesNode.name=Data Sources
 DataSourcesNode.createSheet.name.name=Name
 DataSourcesNode.createSheet.name.displayName=Name
 DataSourcesNode.createSheet.name.desc=no description
-DeletedContent.fsDelFilter.text=File System
-DeletedContent.allDelFilter.text=All
-DeletedContent.deletedContentsNode.name=Deleted Files
-DeletedContent.createSheet.name.name=Name
-DeletedContent.createSheet.name.displayName=Name
-DeletedContent.createSheet.name.desc=no description
-DeletedContent.createSheet.filterType.name=Type
-DeletedContent.createSheet.filterType.displayName=Type
-DeletedContent.createSheet.filterType.desc=no description
-DeletedContent.createKeys.maxObjects.msg=There are more Deleted Files than can be displayed. Only the first {0} Deleted Files will be shown.
-DeletedContent.createNodeForKey.typeNotSupported.msg=Not supported for this type of Displayable Item\: {0}
 DirectoryNode.parFolder.text=[parent folder]
 DirectoryNode.curFolder.text=[current folder]
 DirectoryNode.getActions.viewFileInDir.text=View File in Directory
@@ -233,16 +202,12 @@ VolumeNode.createSheet.description.desc=no description
 VolumeNode.createSheet.flags.name=Flags
 VolumeNode.createSheet.flags.displayName=Flags
 VolumeNode.createSheet.flags.desc=no description
-AbstractAbstractFileNode.objectId=Object ID
 ArtifactStringContent.getStr.artifactId.text=Artifact ID
 DeleteReportAction.actionDisplayName.singleReport=Delete Report
 DeleteReportAction.actionDisplayName.multipleReports=Delete Reports
 DeleteReportAction.actionPerformed.showConfirmDialog.title=Confirm Deletion
 DeleteReportAction.actionPerformed.showConfirmDialog.single.msg=Do you want to delete 1 report from the case?
 DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg=Do you want to delete {0} reports from the case?
-AbstractAbstractFileNode.addFileProperty.desc=no description
-AbstractAbstractFileNode.addFileProperty.tags.name=Tags
-AbstractAbstractFileNode.addFileProperty.tags.displayName=Tags
 BlackboardArtifactNode.createSheet.tags.name=Tags
 BlackboardArtifactNode.createSheet.tags.displayName=Tags
 FileTypeExtensionFilters.tskImgFilter.text=Images
@@ -255,4 +220,4 @@ FileTypeExtensionFilters.autDocHtmlFilter.text=HTML
 FileTypeExtensionFilters.autDocOfficeFilter.text=Office
 FileTypeExtensionFilters.autoDocPdfFilter.text=PDF
 FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text
-FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text
\ No newline at end of file
+FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
index 6dbd8002eefc4eb16981620f5d93cb9dd420b161..2277cb4b5608e49459127b9a3b651391246ec656 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
@@ -31,6 +31,8 @@ interface ContentNodeVisitor<T> {
     T visit(ImageNode in);
 
     T visit(VirtualDirectoryNode lcn);
+    
+    T visit(LocalDirectoryNode ldn);
 
     T visit(VolumeNode vn);
 
@@ -43,6 +45,9 @@ interface ContentNodeVisitor<T> {
     T visit(LocalFileNode dfn);
     
     T visit(SlackFileNode sfn);
+    
+    T visit(BlackboardArtifactNode bban);
+    
 
     /**
      * Visitor with an implementable default behavior for all types. Override
@@ -96,9 +101,19 @@ public T visit(VirtualDirectoryNode ldn) {
             return defaultVisit(ldn);
         }
 
+        @Override
+        public T visit(LocalDirectoryNode ldn) {
+            return defaultVisit(ldn);
+        }
+
         @Override
         public T visit(SlackFileNode sfn) {
             return defaultVisit(sfn);
         }
+        
+        @Override
+        public T visit(BlackboardArtifactNode bban) {
+            return defaultVisit(bban);
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java
index 1b457966fe184d19dd5a2d4dbb02bc2f7e3a6bfe..8034a4f9fb4285c4f6f13a91fdb73380a0a0bcf6 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java
@@ -42,6 +42,7 @@
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.ReadContentInputStream;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.TskException;
@@ -378,6 +379,11 @@ public Void visit(Directory dir) {
         public Void visit(VirtualDirectory dir) {
             return visitDir(dir);
         }
+        
+        @Override
+        public Void visit(LocalDirectory dir) {
+            return visitDir(dir);
+        }
 
         private java.io.File getFsContentDest(Content fsc) {
             String path = dest.getAbsolutePath() + java.io.File.separator
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
index dc1aa9a8bf0b410605589652132e59b66ce8bec0..db9967b743cdacb36cbddca0753af13faeddaa0e 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java
@@ -44,6 +44,7 @@
 import org.sleuthkit.datamodel.File;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.VirtualDirectory;
 
@@ -229,6 +230,38 @@ public static List<Action> getActions(VirtualDirectory directory, boolean isArti
         actionsList.addAll(ContextMenuExtensionPoint.getActions());
         return actionsList;
     }
+    
+    public static List<Action> getActions(LocalDirectory directory, boolean isArtifactSource) {
+        List<Action> actionsList = new ArrayList<>();
+        actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), directory));
+        LocalDirectoryNode directoryNode = new LocalDirectoryNode(directory);
+        actionsList.add(null); // creates a menu separator
+        actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, directoryNode));
+        actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, directoryNode));
+        actionsList.add(null); // creates a menu separator
+        actionsList.add(ExtractAction.getInstance());
+        actionsList.add(null); // creates a menu separator
+        actionsList.add(AddContentTagAction.getInstance());
+        if (isArtifactSource) {
+            actionsList.add(AddBlackboardArtifactTagAction.getInstance());
+        }
+        
+        final Collection<AbstractFile> selectedFilesList =
+                new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
+        if(selectedFilesList.size() == 1) {
+            actionsList.add(DeleteFileContentTagAction.getInstance());
+        }
+        if(isArtifactSource) {
+            final Collection<BlackboardArtifact> selectedArtifactsList =
+                    new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
+            if(selectedArtifactsList.size() == 1) {
+                actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance());
+            }
+        }
+        
+        actionsList.addAll(ContextMenuExtensionPoint.getActions());
+        return actionsList;
+    }
 
     public static List<Action> getActions(LocalFile file, boolean isArtifactSource) {
         List<Action> actionsList = new ArrayList<>();
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
index 589dfc4c86d2c52b33383fb1fb4cee10e212c6c2..0b6ced4f169a645bc47fa04c58f14512243d48a9 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java
@@ -1,15 +1,15 @@
 /*
  * Autopsy Forensic Browser
- * 
- * Copyright 2013-2017 Basis Technology Corp.
+ *
+ * Copyright 2011-2017 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -39,6 +39,7 @@
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.core.UserPreferences;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import static org.sleuthkit.autopsy.datamodel.Bundle.*;
 import org.sleuthkit.autopsy.ingest.IngestManager;
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.Content;
@@ -58,14 +59,15 @@ public class DeletedContent implements AutopsyVisitableItem {
 
     private SleuthkitCase skCase;
 
+    @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System",
+        "DeletedContent.allDelFilter.text=All"})
     public enum DeletedContentFilter implements AutopsyVisitableItem {
 
-        FS_DELETED_FILTER(0,
-                "FS_DELETED_FILTER", //NON-NLS
-                NbBundle.getMessage(DeletedContent.class, "DeletedContent.fsDelFilter.text")),
-        ALL_DELETED_FILTER(1,
-                "ALL_DELETED_FILTER", //NON-NLS
-                NbBundle.getMessage(DeletedContent.class, "DeletedContent.allDelFilter.text"));
+        FS_DELETED_FILTER(0, "FS_DELETED_FILTER", //NON-NLS
+                Bundle.DeletedContent_fsDelFilter_text()),
+        ALL_DELETED_FILTER(1, "ALL_DELETED_FILTER", //NON-NLS
+                Bundle.DeletedContent_allDelFilter_text());
+
         private int id;
         private String name;
         private String displayName;
@@ -110,15 +112,13 @@ public SleuthkitCase getSleuthkitCase() {
 
     public static class DeletedContentsNode extends DisplayableItemNode {
 
-        private static final String NAME = NbBundle.getMessage(DeletedContent.class,
-                "DeletedContent.deletedContentsNode.name");
-        private SleuthkitCase skCase;
+        @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files")
+        private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name();
 
         DeletedContentsNode(SleuthkitCase skCase) {
             super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME));
             super.setName(NAME);
             super.setDisplayName(NAME);
-            this.skCase = skCase;
             this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS
         }
 
@@ -133,6 +133,9 @@ public <T> T accept(DisplayableItemNodeVisitor<T> v) {
         }
 
         @Override
+        @NbBundle.Messages({
+            "DeletedContent.createSheet.name.displayName=Name",
+            "DeletedContent.createSheet.name.desc=no description"})
         protected Sheet createSheet() {
             Sheet s = super.createSheet();
             Sheet.Set ss = s.get(Sheet.PROPERTIES);
@@ -141,9 +144,9 @@ protected Sheet createSheet() {
                 s.put(ss);
             }
 
-            ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.name.name"),
-                    NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.name.displayName"),
-                    NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.name.desc"),
+            ss.put(new NodeProperty<>("Name", //NON-NLS
+                    Bundle.DeletedContent_createSheet_name_displayName(),
+                    Bundle.DeletedContent_createSheet_name_desc(),
                     NAME));
             return s;
         }
@@ -303,6 +306,9 @@ public <T> T accept(DisplayableItemNodeVisitor<T> v) {
             }
 
             @Override
+            @NbBundle.Messages({
+                "DeletedContent.createSheet.filterType.displayName=Type",
+                "DeletedContent.createSheet.filterType.desc=no description"})
             protected Sheet createSheet() {
                 Sheet s = super.createSheet();
                 Sheet.Set ss = s.get(Sheet.PROPERTIES);
@@ -311,10 +317,9 @@ protected Sheet createSheet() {
                     s.put(ss);
                 }
 
-                ss.put(new NodeProperty<>(
-                        NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.filterType.name"),
-                        NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.filterType.displayName"),
-                        NbBundle.getMessage(this.getClass(), "DeletedContent.createSheet.filterType.desc"),
+                ss.put(new NodeProperty<>("Type", //NON_NLS
+                        Bundle.DeletedContent_createSheet_filterType_displayName(),
+                        Bundle.DeletedContent_createSheet_filterType_desc(),
                         filter.getDisplayName()));
 
                 return s;
@@ -334,7 +339,7 @@ public String getItemType() {
                 return DisplayableItemNode.FILE_PARENT_NODE_KEY;
             }
         }
-        
+
         static class DeletedContentChildren extends ChildFactory.Detachable<AbstractFile> {
 
             private final SleuthkitCase skCase;
@@ -375,6 +380,10 @@ protected void removeNotify() {
             }
 
             @Override
+            @NbBundle.Messages({"# {0} - The deleted files threshold",
+                "DeletedContent.createKeys.maxObjects.msg="
+                + "There are more Deleted Files than can be displayed."
+                + " Only the first {0} Deleted Files will be shown."})
             protected boolean createKeys(List<AbstractFile> list) {
                 List<AbstractFile> queryList = runFsQuery();
                 if (queryList.size() == MAX_OBJECTS) {
@@ -382,14 +391,10 @@ protected boolean createKeys(List<AbstractFile> list) {
                     // only show the dialog once - not each time we refresh
                     if (maxFilesDialogShown == false) {
                         maxFilesDialogShown = true;
-                        SwingUtilities.invokeLater(new Runnable() {
-                            @Override
-                            public void run() {
-                                JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(this.getClass(),
-                                        "DeletedContent.createKeys.maxObjects.msg",
-                                        MAX_OBJECTS - 1));
-                            }
-                        });
+                        SwingUtilities.invokeLater(()
+                                -> JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
+                                        DeletedContent_createKeys_maxObjects_msg(MAX_OBJECTS - 1))
+                        );
                     }
                 }
                 list.addAll(queryList);
@@ -428,14 +433,13 @@ static private String makeQuery(DeletedContent.DeletedContentFilter filter) {
                         logger.log(Level.SEVERE, "Unsupported filter type to get deleted content: {0}", filter); //NON-NLS
 
                 }
-                
-                if(UserPreferences.hideKnownFilesInViewsTree()) {
+
+                if (UserPreferences.hideKnownFilesInViewsTree()) {
                     query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
                             + " OR known IS NULL)"; //NON-NLS
                 }
 
                 query += " LIMIT " + MAX_OBJECTS; //NON-NLS
-                
                 return query;
             }
 
@@ -456,6 +460,9 @@ private List<AbstractFile> runFsQuery() {
             /**
              * Get children count without actually loading all nodes
              *
+             * @param sleuthkitCase
+             * @param filter
+             *
              * @return
              */
             static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) {
@@ -495,9 +502,7 @@ public FileNode visit(Directory f) {
 
                     @Override
                     protected AbstractNode defaultVisit(Content di) {
-                        throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
-                                "DeletedContent.createNodeForKey.typeNotSupported.msg",
-                                di.toString()));
+                        throw new UnsupportedOperationException("Not supported for this type of Displayable Item: " + di.toString());
                     }
                 });
             }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
index 5cc152e4653dc6a856a5f4b935e533ff92a97466..e6e8afed52723bf96b0bb9be8553be3076077f12 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
@@ -41,6 +41,8 @@ public interface DisplayableItemNodeVisitor<T> {
     T visit(LocalFileNode dfn);
 
     T visit(VirtualDirectoryNode ldn);
+    
+    T visit(LocalDirectoryNode ldn);
 
     T visit(DirectoryNode dn);
 
@@ -370,6 +372,11 @@ public T visit(VirtualDirectoryNode ldn) {
             return defaultVisit(ldn);
         }
 
+        @Override
+        public T visit(LocalDirectoryNode ldn) {
+            return defaultVisit(ldn);
+        }
+
         @Override
         public T visit(Tags.RootNode node) {
             return defaultVisit(node);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
index ca4602e30a94f7952379658eb33aca6287955066..ad740a501196eebf046a65f80fd601056d3149ab 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java
@@ -428,7 +428,7 @@ private void updateDisplayName() {
 
         @Override
         public boolean isLeafTypeNode() {
-            return true;
+            return false;
         }
 
         @Override
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
index 2e1fba50ccccf30e74f8c525c9aa10842dfd9548..d8ef2a2f5ea026d95f24b3eaeb8f25677b8f92fc 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
@@ -353,29 +353,14 @@ private String createQuery(FileTypesByExtension.SearchFilterInterface filter) {
             throw new IllegalArgumentException("Empty filter list passed to createQuery()"); // NON-NLS
         }
 
-        String query = "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
-                + (UserPreferences.hideKnownFilesInViewsTree() ? " AND (known IS NULL OR known != "
-                + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ")
-                + " AND (NULL "; //NON-NLS
-
-        if (skCase.getDatabaseType().equals(TskData.DbType.POSTGRESQL)) {
-            // For PostgreSQL we get a more efficient query by using builtin
-            // regular expression support and or'ing all extensions. We also
-            // escape the dot at the beginning of the extension.
-            // We will end up with a query that looks something like this:
-            // OR LOWER(name) ~ '(\.zip|\.rar|\.7zip|\.cab|\.jar|\.cpio|\.ar|\.gz|\.tgz|\.bz2)$')
-            query += "OR LOWER(name) ~ '(\\";
-            query += StringUtils.join(filter.getFilter().stream()
-                    .map(String::toLowerCase).collect(Collectors.toList()), "|\\");
-            query += ")$'";
-        } else {
-            for (String s : filter.getFilter()) {
-                query += "OR LOWER(name) LIKE '%" + s.toLowerCase() + "'"; // NON-NLS
-            }
-        }
-
-        query += ')';
-        return query;
+        return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
+                + (UserPreferences.hideKnownFilesInViewsTree()
+                ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")"
+                : " ")
+                + " AND (extension IN (" + filter.getFilter().stream()
+                        .map(String::toLowerCase)
+                        .map(s -> "'"+StringUtils.substringAfter(s, ".")+"'")
+                        .collect(Collectors.joining(", ")) + "))";
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
index c759b425d7a964135c294e14ab2517a82592785c..0d541d7e1a4ae741cf3a7a9b30c450d3df0c8223 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java
@@ -79,7 +79,7 @@ protected Sheet createSheet() {
         }
 
         Map<String, Object> map = new LinkedHashMap<>();
-        fillPropertyMap(map, content);
+        fillPropertyMap(map);
 
         ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.name"),
                 NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"),
@@ -134,11 +134,12 @@ public Action[] getActions(boolean context) {
         }
         
         actionsList.addAll(ContextMenuExtensionPoint.getActions());
-        return actionsList.toArray(new Action[0]);
+        return actionsList.toArray(new Action[actionsList.size()]);
     }
 
-    private static void fillPropertyMap(Map<String, Object> map, LayoutFile content) {
-        AbstractAbstractFileNode.fillPropertyMap(map, content);
+    
+      void fillPropertyMap(Map<String, Object> map) {
+        AbstractAbstractFileNode.fillPropertyMap(map, getContent());
         map.put(LayoutContentPropertyType.PARTS.toString(), content.getNumParts());
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9bab8d5b2e1f3dad30a5f82c86b9d811d08bc3a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java
@@ -0,0 +1,86 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2017 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datamodel;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.openide.nodes.Sheet;
+import org.openide.util.NbBundle;
+import org.sleuthkit.datamodel.LocalDirectory;
+
+/**
+ * Node for a local directory
+ */
+public class LocalDirectoryNode extends SpecialDirectoryNode {
+
+    public static String nameForLocalDir(LocalDirectory ld) {
+        return ld.getName();
+    }
+
+    public LocalDirectoryNode(LocalDirectory ld) {
+        super(ld);
+
+        this.setDisplayName(nameForLocalDir(ld));
+        this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
+
+    }
+    
+    @Override
+    @NbBundle.Messages({
+        "LocalDirectoryNode.createSheet.name.name=Name",
+        "LocalDirectoryNode.createSheet.name.displayName=Name",
+        "LocalDirectoryNode.createSheet.name.desc=no description",
+        "LocalDirectoryNode.createSheet.noDesc=no description"})
+    protected Sheet createSheet() {
+        Sheet s = super.createSheet();
+        Sheet.Set ss = s.get(Sheet.PROPERTIES);
+        if (ss == null) {
+            ss = Sheet.createPropertiesSet();
+            s.put(ss);
+        }
+
+        ss.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(),
+                Bundle.LocalDirectoryNode_createSheet_name_displayName(),
+                Bundle.LocalDirectoryNode_createSheet_name_desc(),
+                getName()));
+
+        // At present, a LocalDirectory will never be a datasource - the top level of a logical
+        // file set is a VirtualDirectory
+        Map<String, Object> map = new LinkedHashMap<>();
+        fillPropertyMap(map, getContent());
+
+        final String NO_DESCR = Bundle.LocalDirectoryNode_createSheet_noDesc();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
+        }
+        addTagProperty(ss);
+
+        return s;
+    }
+
+    @Override
+    public <T> T accept(ContentNodeVisitor<T> v) {
+        return v.visit(this);
+    }
+
+    @Override
+    public <T> T accept(DisplayableItemNodeVisitor<T> v) {
+        return v.visit(this);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
index 5738356c46a874d2a7007e805d545527f3ce3233..e14e41416e4bebd3b3ec40f0704e0926c3852951 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java
@@ -71,7 +71,7 @@ protected Sheet createSheet() {
         }
 
         Map<String, Object> map = new LinkedHashMap<>();
-        fillPropertyMap(map, content);
+        fillPropertyMap(map, getContent());
 
         ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.name"),
                 NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"),
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..a25034481af2fec29f592c4b0be6a33119d2cbc6
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/SpecialDirectoryNode.java
@@ -0,0 +1,82 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2011-2017 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.autopsy.datamodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.swing.Action;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
+import org.sleuthkit.autopsy.directorytree.ExtractAction;
+import org.sleuthkit.autopsy.directorytree.FileSearchAction;
+import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
+import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.SpecialDirectory;
+
+/**
+ * Parent class for special directory types (Local and Virtual)
+ */
+public abstract class SpecialDirectoryNode extends AbstractAbstractFileNode<SpecialDirectory> {
+
+    public SpecialDirectoryNode(SpecialDirectory sd) {
+        super(sd);
+    }
+        
+    /**
+     * Right click action for this node
+     *
+     * @param popup
+     *
+     * @return
+     */
+    @Override
+    @NbBundle.Messages({"SpecialDirectoryNode.action.runIngestMods.text=Run Ingest Modules",
+        "SpecialDirectoryNode.getActions.viewInNewWin.text=View in New Window"
+    })
+    public Action[] getActions(boolean popup) {
+        List<Action> actions = new ArrayList<>();
+        for (Action a : super.getActions(true)) {
+            actions.add(a);
+        }
+
+        actions.add(new NewWindowViewAction(
+                Bundle.SpecialDirectoryNode_action_runIngestMods_text(), this));
+        actions.add(null); // creates a menu separator
+        actions.add(ExtractAction.getInstance());
+        actions.add(null); // creates a menu separator
+        actions.add(new FileSearchAction(
+                Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
+        actions.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
+        actions.addAll(ContextMenuExtensionPoint.getActions());
+        return actions.toArray(new Action[0]);
+    }
+
+    @Override
+    public boolean isLeafTypeNode() {
+        return false;
+    }
+
+    @Override
+    public String getItemType() {
+        // use content.isDataSource if different column settings are desired
+        return DisplayableItemNode.FILE_PARENT_NODE_KEY;
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
index ac861c88475f6eff7d111508ab881e6f8e4cdec4..3d78d3f13d48fe7e7f5bbbbce5f30458ca4da973 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java
@@ -20,91 +20,47 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
-import javax.swing.Action;
 import org.openide.nodes.Sheet;
 import org.openide.util.NbBundle;
-import org.openide.util.NbBundle.Messages;
 import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.directorytree.ExtractAction;
-import org.sleuthkit.autopsy.directorytree.FileSearchAction;
-import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
-import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
-import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.SleuthkitCase;
 import org.sleuthkit.datamodel.TskCoreException;
-import org.sleuthkit.datamodel.TskData;
 import org.sleuthkit.datamodel.VirtualDirectory;
 
 /**
- * Node for layout dir
+ * Node for a virtual directory
  */
-public class VirtualDirectoryNode extends AbstractAbstractFileNode<VirtualDirectory> {
+public class VirtualDirectoryNode extends SpecialDirectoryNode {
 
     private static final Logger logger = Logger.getLogger(VirtualDirectoryNode.class.getName());
     //prefix for special VirtualDirectory root nodes grouping local files
     public final static String LOGICAL_FILE_SET_PREFIX = "LogicalFileSet"; //NON-NLS
 
-    public static String nameForLayoutFile(VirtualDirectory ld) {
+    public static String nameForVirtualDirectory(VirtualDirectory ld) {
         return ld.getName();
     }
 
     public VirtualDirectoryNode(VirtualDirectory ld) {
         super(ld);
 
-        this.setDisplayName(nameForLayoutFile(ld));
+        this.setDisplayName(nameForVirtualDirectory(ld));
 
         String name = ld.getName();
 
-        //set icon for name, special case for some built-ins
-        if (name.equals(VirtualDirectory.NAME_UNALLOC)) {
-            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-deleted.png"); //NON-NLS
-        } else if (ld.isDataSource()) {
+        //set icon for name, special case for logical file set
+        if (ld.isDataSource()) {
             this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS
-        } else if (name.equals(VirtualDirectory.NAME_CARVED)) {
-            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //TODO NON-NLS
         } else {
-            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); //NON-NLS
+            this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-virtual.png"); //TODO NON-NLS
         }
-
     }
-
-    /**
-     * Right click action for this node
-     *
-     * @param popup
-     *
-     * @return
-     */
+    
     @Override
-    @NbBundle.Messages({"VirtualDirectoryNode.action.runIngestMods.text=Run Ingest Modules"})
-    public Action[] getActions(boolean popup) {
-        List<Action> actions = new ArrayList<>();
-        for (Action a : super.getActions(true)) {
-            actions.add(a);
-        }
-
-        actions.add(new NewWindowViewAction(
-                NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.getActions.viewInNewWin.text"), this));
-        actions.add(null); // creates a menu separator
-        actions.add(ExtractAction.getInstance());
-        actions.add(null); // creates a menu separator
-        actions.add(new FileSearchAction(
-                Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
-        actions.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
-        actions.addAll(ContextMenuExtensionPoint.getActions());
-        return actions.toArray(new Action[0]);
-    }
-
-    @Override
-    @Messages({"VirtualDirectoryNode.createSheet.size.name=Size (Bytes)",
+    @NbBundle.Messages({"VirtualDirectoryNode.createSheet.size.name=Size (Bytes)",
         "VirtualDirectoryNode.createSheet.size.displayName=Size (Bytes)",
         "VirtualDirectoryNode.createSheet.size.desc=Size of the data source in bytes.",
         "VirtualDirectoryNode.createSheet.type.name=Type",
@@ -133,7 +89,7 @@ protected Sheet createSheet() {
 
         if (!this.content.isDataSource()) {
             Map<String, Object> map = new LinkedHashMap<>();
-            fillPropertyMap(map, content);
+            fillPropertyMap(map, getContent());
 
             final String NO_DESCR = NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.noDesc");
             for (Map.Entry<String, Object> entry : map.entrySet()) {
@@ -186,39 +142,4 @@ public <T> T accept(ContentNodeVisitor<T> v) {
     public <T> T accept(DisplayableItemNodeVisitor<T> v) {
         return v.visit(this);
     }
-
-    @Override
-    public boolean isLeafTypeNode() {
-        return false;
-    }
-
-    /**
-     * Convert meta flag long to user-readable string / label
-     *
-     * @param metaFlag to convert
-     *
-     * @return string formatted meta flag representation
-     */
-    public static String metaFlagToString(short metaFlag) {
-
-        String result = "";
-
-        short allocFlag = TskData.TSK_FS_META_FLAG_ENUM.ALLOC.getValue();
-        short unallocFlag = TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue();
-
-        if ((metaFlag & allocFlag) == allocFlag) {
-            result = TskData.TSK_FS_META_FLAG_ENUM.ALLOC.toString();
-        }
-        if ((metaFlag & unallocFlag) == unallocFlag) {
-            result = TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.toString();
-        }
-
-        return result;
-    }
-
-    @Override
-    public String getItemType() {
-        // use content.isDataSource if different column settings are desired
-        return DisplayableItemNode.FILE_PARENT_NODE_KEY;
-    }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index 21146610da1535ff944120921e0ada331656b81e..c13b2442bc051299c9c365d82a6c3e9bbccd5fed 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -20,6 +20,7 @@
 
 import java.awt.event.ActionEvent;
 import java.beans.PropertyVetoException;
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -43,6 +44,7 @@
 import org.sleuthkit.autopsy.core.UserPreferences;
 import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
 import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
 import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode;
 import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
@@ -53,10 +55,12 @@
 import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
 import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
 import org.sleuthkit.autopsy.datamodel.LocalFileNode;
+import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode;
 import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
 import org.sleuthkit.autopsy.datamodel.Reports;
 import org.sleuthkit.autopsy.datamodel.SlackFileNode;
 import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
+import static org.sleuthkit.autopsy.directorytree.Bundle.*;
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -66,11 +70,13 @@
 import org.sleuthkit.datamodel.File;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.TskData;
 import org.sleuthkit.datamodel.TskException;
 import org.sleuthkit.datamodel.VirtualDirectory;
-import static org.sleuthkit.autopsy.directorytree.Bundle.*;
+import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
+import org.sleuthkit.datamodel.TskCoreException;
 
 /**
  * A node used to wrap another node before passing it to the result viewers. The
@@ -210,6 +216,26 @@ public Node.PropertySet[] getPropertySets() {
         return propertySets;
     }
 
+    /**
+     * Gets the display name for the wrapped node.
+     * 
+     * OutlineView used in the DataResult table uses getDisplayName() to populate
+     * the first column, which is Source File.
+     * 
+     * Hence this override to return the 'correct' displayName for the wrapped node.
+     *
+     * @return The display name for the node.
+     */
+    @Override
+    public String getDisplayName() {
+        final Node orig = getOriginal();
+        String name = orig.getDisplayName();
+        if ((orig instanceof BlackboardArtifactNode)) {
+            name = ((BlackboardArtifactNode) orig).getSrcName();
+        }
+        return name;
+    }
+    
     /**
      * Adds information about which child node of this node, if any, should be
      * selected. Can be null.
@@ -248,16 +274,20 @@ private static class DataResultFilterChildren extends FilterNode.Children {
 
         private boolean filterKnown;
         private boolean filterSlack;
+        private boolean filterArtifacts;    // display message artifacts in the DataSource subtree
 
         /**
          * the constructor
          */
         private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
             super(arg);
+            
+            this.filterArtifacts = false;
             switch (SelectionContext.getSelectionContext(arg)) {
                 case DATA_SOURCES:
                     filterSlack = filterSlackFromDataSources;
                     filterKnown = filterKnownFromDataSources;
+                    filterArtifacts = true;                    
                     break;
                 case VIEWS:
                     filterSlack = filterSlackFromViews;
@@ -291,6 +321,16 @@ protected Node[] createNodes(Node key) {
                     return new Node[]{};
                 }
             }
+            
+            // filter out all non-message artifacts, if displaying the results from the Data Source tree
+            BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class);
+            if (art != null && filterArtifacts)  {
+                if ( (art.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) &&             
+                    (art.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) {
+                   return new Node[]{};
+                }
+             }
+             
             return new Node[]{new DataResultFilterNode(key, sourceEm, filterKnown, filterSlack)};
         }
     }
@@ -350,6 +390,8 @@ public List<Action> visit(BlackboardArtifactNode ban) {
                 n = new DirectoryNode((Directory) c);
             } else if ((c = ban.getLookup().lookup(VirtualDirectory.class)) != null) {
                 n = new VirtualDirectoryNode((VirtualDirectory) c);
+            } else if ((c = ban.getLookup().lookup(LocalDirectory.class)) != null) {
+                n = new LocalDirectoryNode((LocalDirectory) c);
             } else if ((c = ban.getLookup().lookup(LayoutFile.class)) != null) {
                 n = new LayoutFileNode((LayoutFile) c);
             } else if ((c = ban.getLookup().lookup(LocalFile.class)) != null
@@ -459,8 +501,20 @@ private class GetPreferredActionsDisplayableItemNodeVisitor extends DisplayableI
 
         @Override
         public AbstractAction visit(BlackboardArtifactNode ban) {
-            return new ViewContextAction(
-                    NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban);
+            BlackboardArtifact artifact = ban.getArtifact();
+                try {
+                    if ( (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) ||             
+                         (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) {
+                        if (artifact.hasChildren()) {
+                            return openChild(ban);
+                        }
+                    }
+                }
+                catch (TskCoreException ex) {
+                    LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting children from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
+                }
+                return new ViewContextAction(
+                        NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban);
         }
 
         @Override
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java
index 2b10d57f0937008f093d4bef556d6fb2b80c487a..9fbd6425dc93d60220b01c12d5ee6042781c812e 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java
@@ -26,16 +26,19 @@
 import org.openide.nodes.FilterNode;
 import org.openide.nodes.Node;
 import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
+import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
 import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
 import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
 import org.sleuthkit.autopsy.datamodel.FileNode;
 import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
 import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
 import org.sleuthkit.autopsy.datamodel.LocalFileNode;
+import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode;
 import org.sleuthkit.autopsy.datamodel.SlackFileNode;
 import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
 import org.sleuthkit.autopsy.datamodel.VolumeNode;
 import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.Directory;
 import org.sleuthkit.datamodel.LayoutFile;
@@ -198,7 +201,7 @@ private Boolean visitDeep(AbstractAbstractFileNode<? extends AbstractFile> node)
             List<Content> derivedChildren = node.getContentChildren();
             //child of a file, must be a (derived) file too
             for (Content childContent : derivedChildren) {
-                if (((AbstractFile) childContent).isDir()) {
+                if ((childContent instanceof AbstractFile) && ((AbstractFile) childContent).isDir()) {
                     return false;
                 } else {
                     try {
@@ -244,11 +247,26 @@ public Boolean visit(VirtualDirectoryNode vdn) {
             //return ! vdn.hasContentChildren();
         }
 
+        @Override
+        public Boolean visit(LocalDirectoryNode ldn) {
+            return visitDeep(ldn);
+        }
+
         @Override
         public Boolean visit(FileTypesNode ft) {
             return defaultVisit(ft);
         }
 
+        @Override
+        public Boolean visit(BlackboardArtifactNode bbafn) {
+            // Only show Message arttifacts with children
+            if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) ||             
+                 (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) {
+                 return bbafn.hasContentChildren();
+            }
+            
+            return false;
+        }
     }
 
     private static class ShowItemVisitor extends DisplayableItemNodeVisitor.Default<Boolean> {
@@ -292,10 +310,28 @@ public Boolean visit(VirtualDirectoryNode vdn) {
             //return vdn.hasContentChildren();
         }
 
+        
+        @Override
+        public Boolean visit(LocalDirectoryNode ldn) {
+            return true;
+        }
+
         @Override
         public Boolean visit(FileTypesNode fileTypes) {
            return defaultVisit(fileTypes);
         }
+        
+        @Override
+        public Boolean visit(BlackboardArtifactNode bbafn) {
+            
+            // Only show Message arttifacts with children
+            if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) ||             
+                 (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) {
+                 return bbafn.hasContentChildren();
+            }
+            
+            return false;
+        }
 
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
index ed2a2fd3877354d929ff63c9245a932ba2997817..81a704c57a99ad7d65b038edc3af3fe61fd79d34 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java
@@ -25,14 +25,17 @@
 import javax.swing.Action;
 import org.openide.nodes.FilterNode;
 import org.openide.nodes.Node;
+import org.openide.util.Exceptions;
 import org.openide.util.NbBundle;
 import org.openide.util.lookup.Lookups;
 import org.openide.util.lookup.ProxyLookup;
 import org.sleuthkit.autopsy.core.UserPreferences;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.datamodel.AbstractContentNode;
+import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
 import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction;
 import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.Content;
 import org.sleuthkit.datamodel.Directory;
 import org.sleuthkit.datamodel.Image;
@@ -77,7 +80,7 @@ public String getDisplayName() {
         String name = orig.getDisplayName();
         if (orig instanceof AbstractContentNode) {
             AbstractFile file = getLookup().lookup(AbstractFile.class);
-            if (file != null) {
+            if ((file != null) && (false ==  (orig instanceof BlackboardArtifactNode)) ){
                 try {
                     int numVisibleChildren = getVisibleChildCount(file);
 
@@ -92,6 +95,15 @@ public String getDisplayName() {
                     logger.log(Level.SEVERE, "Error getting children count to display for file: " + file, ex); //NON-NLS
                 }
             }
+            else if (orig instanceof BlackboardArtifactNode) {
+                BlackboardArtifact artifact = ((BlackboardArtifactNode) orig).getArtifact();           
+                try {
+                    int numAttachments = artifact.getChildrenCount();
+                    name = name + " \u200E(\u200E" + numAttachments + ")\u200E";  //NON-NLS
+                } catch (TskCoreException ex) {
+                   logger.log(Level.SEVERE, "Error getting chidlren count for atifact: " + artifact, ex); //NON-NLS
+                }
+            }
         }
         return name;
     }
@@ -115,13 +127,17 @@ private int getVisibleChildCount(AbstractFile file) throws TskCoreException {
         if (purgeKnownFiles || purgeSlackFiles) {
             // Purge known and/or slack files from the file count
             for (int i = 0; i < childList.size(); i++) {
-                AbstractFile childFile = (AbstractFile) childList.get(i);
-                if ((purgeKnownFiles && childFile.getKnown() == TskData.FileKnown.KNOWN)
-                        || (purgeSlackFiles && childFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
-                    numVisibleChildren--;
+                Content child = childList.get(i);
+                if (child instanceof AbstractFile) {
+                    AbstractFile childFile = (AbstractFile) child;
+                    if ((purgeKnownFiles && childFile.getKnown() == TskData.FileKnown.KNOWN)
+                            || (purgeSlackFiles && childFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
+                        numVisibleChildren--;
+                    }
                 }
             }
         }
+        
 
         return numVisibleChildren;
     }
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java
index 86c3df1fe3b92fda3b7536fdc24a60c53475bae5..583c2aa1576f78cb2f4c5b570e47abb216eeaec8 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java
@@ -38,6 +38,7 @@
 import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.VirtualDirectory;
 import org.sleuthkit.datamodel.Volume;
 
@@ -119,6 +120,23 @@ public List<? extends Action> visit(final VirtualDirectory d) {
         actionsList.addAll(ContextMenuExtensionPoint.getActions());
         return actionsList;
     }
+    
+    @Override
+    public List<? extends Action> visit(final LocalDirectory d) {
+        List<Action> actionsList = new ArrayList<>();
+        if (!d.isDataSource()) {
+            actionsList.add(AddContentTagAction.getInstance());
+            
+            final Collection<AbstractFile> selectedFilesList =
+                    new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
+            if(selectedFilesList.size() == 1) {
+                actionsList.add(DeleteFileContentTagAction.getInstance());
+            }
+        }
+        actionsList.add(ExtractAction.getInstance());
+        actionsList.addAll(ContextMenuExtensionPoint.getActions());
+        return actionsList;
+    }
 
     @Override
     public List<? extends Action> visit(final DerivedFile d) {
diff --git a/Core/src/org/sleuthkit/autopsy/images/folder-icon-virtual.png b/Core/src/org/sleuthkit/autopsy/images/folder-icon-virtual.png
new file mode 100644
index 0000000000000000000000000000000000000000..88f6dd63b276f96f87333eed0d237fe934f8315c
Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/folder-icon-virtual.png differ
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java
index b45b7b307bbb17b00c8a68cc70eaaca4c465a606..e6b5ee02fa600db3024d7e54dd85ecba4ac18dd6 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/GetFilesContentVisitor.java
@@ -28,6 +28,7 @@
 import org.sleuthkit.datamodel.AbstractFile;
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.VirtualDirectory;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.TskException;
 import org.sleuthkit.datamodel.Volume;
 import org.sleuthkit.datamodel.VolumeSystem;
@@ -43,6 +44,11 @@ abstract class GetFilesContentVisitor implements ContentVisitor<Collection<Abstr
     public Collection<AbstractFile> visit(VirtualDirectory ld) {
         return getAllFromChildren(ld);
     }
+    
+    @Override
+    public Collection<AbstractFile> visit(LocalDirectory ld) {
+        return getAllFromChildren(ld);
+    }
 
     @Override
     public Collection<AbstractFile> visit(Directory drctr) {
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
index 28085cee96ce6f56ca6d9e576ed7b0dbc2c04989..8523fa699f83ce4040ae20065ea69d8c5172bc39 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
@@ -21,12 +21,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
 import org.sleuthkit.datamodel.DerivedFile;
 import org.sleuthkit.datamodel.Directory;
 import org.sleuthkit.datamodel.File;
 import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.LocalFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.VirtualDirectory;
 
@@ -38,12 +40,20 @@ final class GetRootDirectoryVisitor extends GetFilesContentVisitor {
 
     @Override
     public Collection<AbstractFile> visit(VirtualDirectory ld) {
-        //case when we hit a layout directoryor local file container, not under a real FS
+        //case when we hit a layout directory or local file container, not under a real FS
         //or when root virt dir is scheduled
         Collection<AbstractFile> ret = new ArrayList<>();
         ret.add(ld);
         return ret;
     }
+    
+    @Override
+    public Collection<AbstractFile> visit(LocalDirectory ld) {
+        //case when we hit a local directory
+        Collection<AbstractFile> ret = new ArrayList<>();
+        ret.add(ld);
+        return ret;
+    }
 
     @Override
     public Collection<AbstractFile> visit(LayoutFile lf) {
@@ -93,4 +103,8 @@ public Collection<AbstractFile> visit(SlackFile slackFile) {
         return getAllFromChildren(slackFile);
     }
 
+    @Override
+    public Collection<AbstractFile> visit(BlackboardArtifact art) {
+        return getAllFromChildren(art);
+    }
 }
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java
index 6d37ca2a26a479e0e03f34ad7e83c71be0e630c6..ff474230307a8b288d62fd909ebe7573d22e1e63 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCase.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2015 Basis Technology Corp.
+ * Copyright 2015-2017 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -39,7 +39,7 @@ class AutoIngestCase implements Comparable<AutoIngestCase> {
     private final String caseName;
     private final Path metadataFilePath;
     private final Date createDate;
-    private Date lastModfiedDate;
+    private final Date lastAccessedDate;
 
     /**
      * Constructs a representation of case created by automated ingest.
@@ -58,10 +58,10 @@ class AutoIngestCase implements Comparable<AutoIngestCase> {
         }
         if (null != fileAttrs) {
             createDate = new Date(fileAttrs.creationTime().toMillis());
-            lastModfiedDate = new Date(fileAttrs.lastModifiedTime().toMillis());
+            lastAccessedDate = new Date(fileAttrs.lastAccessTime().toMillis());
         } else {
             createDate = new Date();
-            lastModfiedDate = new Date();
+            lastAccessedDate = new Date();
         }
     }
 
@@ -94,19 +94,13 @@ Date getCreationDate() {
     }
 
     /**
-     * Gets the last accessed date for the case, defined as the last modified
+     * Gets the last accessed date for the case, defined as the last accessed
      * time of the case metadata file.
      *
      * @return The last accessed date.
      */
     Date getLastAccessedDate() {
-        try {
-            BasicFileAttributes fileAttrs = Files.readAttributes(metadataFilePath, BasicFileAttributes.class);
-            lastModfiedDate = new Date(fileAttrs.lastModifiedTime().toMillis());
-        } catch (IOException ex) {
-            logger.log(Level.SEVERE, String.format("Error reading file attributes of case metadata file in %s, lastModfiedDate time not updated", caseDirectoryPath), ex);
-        }
-        return lastModfiedDate;
+        return this.lastAccessedDate;
     }
 
     /**
@@ -162,7 +156,7 @@ public int hashCode() {
      */
     @Override
     public int compareTo(AutoIngestCase other) {
-        return -this.lastModfiedDate.compareTo(other.getLastAccessedDate());
+        return -this.lastAccessedDate.compareTo(other.getLastAccessedDate());
     }
 
     /**
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form
index 0c22854c9a257fcdb7260c5e90e2a975616c5b07..cb0f27580926db1724b41c06792d4fdb73ffa683 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.form
@@ -27,44 +27,53 @@
               <EmptySpace max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
                   <Group type="102" attributes="0">
-                      <EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
+                      <EmptySpace min="-2" pref="4" max="-2" attributes="0"/>
                       <Component id="bnOpen" min="-2" pref="80" max="-2" attributes="0"/>
-                      <EmptySpace type="separate" max="-2" attributes="0"/>
-                      <Component id="bnRefresh" min="-2" max="-2" attributes="0"/>
-                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
                       <Component id="bnShowLog" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="32767" attributes="0"/>
-                      <Component id="panelFilter" min="-2" max="-2" attributes="0"/>
-                      <EmptySpace min="-2" pref="20" max="-2" attributes="0"/>
-                  </Group>
-                  <Group type="102" attributes="0">
-                      <Component id="scrollPaneTable" pref="1007" max="32767" attributes="0"/>
+                      <Component id="rbGroupLabel" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="-2" attributes="0"/>
+                      <Component id="rbDays" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="rbWeeks" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="rbMonths" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace min="0" pref="0" max="-2" attributes="0"/>
+                      <Component id="panelFilter" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace type="unrelated" max="-2" attributes="0"/>
+                      <Component id="bnRefresh" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace min="-2" pref="4" max="-2" attributes="0"/>
                   </Group>
+                  <Component id="scrollPaneTable" pref="1007" max="32767" attributes="0"/>
               </Group>
+              <EmptySpace max="-2" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" attributes="0">
-              <EmptySpace min="-2" pref="43" max="-2" attributes="0"/>
+              <EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
               <Component id="scrollPaneTable" min="-2" pref="450" max="-2" attributes="0"/>
-              <EmptySpace type="unrelated" max="32767" attributes="0"/>
-              <Group type="103" groupAlignment="1" attributes="0">
-                  <Group type="102" alignment="1" attributes="0">
-                      <Component id="panelFilter" min="-2" pref="130" max="-2" attributes="0"/>
-                      <EmptySpace max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="103" groupAlignment="3" attributes="0">
+                      <Component id="bnOpen" alignment="3" min="-2" max="-2" attributes="0"/>
+                      <Component id="bnShowLog" alignment="3" min="-2" max="-2" attributes="0"/>
                   </Group>
-                  <Group type="102" alignment="1" attributes="0">
-                      <Group type="103" groupAlignment="3" attributes="0">
-                          <Component id="bnOpen" alignment="3" min="-2" max="-2" attributes="0"/>
-                          <Component id="bnRefresh" alignment="3" min="-2" max="-2" attributes="0"/>
-                          <Component id="bnShowLog" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="bnRefresh" min="-2" max="-2" attributes="0"/>
+                  <Group type="103" groupAlignment="1" attributes="0">
+                      <Group type="103" alignment="1" groupAlignment="3" attributes="0">
+                          <Component id="rbDays" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="rbWeeks" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="rbMonths" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="rbGroupLabel" alignment="3" min="-2" max="-2" attributes="0"/>
                       </Group>
-                      <EmptySpace min="-2" pref="36" max="-2" attributes="0"/>
+                      <Component id="panelFilter" min="-2" max="-2" attributes="0"/>
                   </Group>
               </Group>
+              <EmptySpace min="0" pref="0" max="-2" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -122,32 +131,16 @@
         <DimensionLayout dim="0">
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" alignment="0" attributes="0">
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Group type="103" groupAlignment="0" attributes="0">
-                      <Component id="rbGroupLabel" min="-2" max="-2" attributes="0"/>
-                      <Component id="rbAllCases" alignment="0" min="-2" max="-2" attributes="0"/>
-                      <Component id="rbMonths" alignment="0" min="-2" max="-2" attributes="0"/>
-                      <Component id="rbWeeks" alignment="0" min="-2" max="-2" attributes="0"/>
-                      <Component id="rbDays" alignment="0" min="-2" max="-2" attributes="0"/>
-                  </Group>
-                  <EmptySpace pref="34" max="32767" attributes="0"/>
+                  <Component id="rbAllCases" min="-2" max="-2" attributes="0"/>
+                  <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
         <DimensionLayout dim="1">
           <Group type="103" groupAlignment="0" attributes="0">
               <Group type="102" alignment="1" attributes="0">
-                  <EmptySpace max="-2" attributes="0"/>
-                  <Component id="rbGroupLabel" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="32767" attributes="0"/>
-                  <Component id="rbDays" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace type="unrelated" max="-2" attributes="0"/>
-                  <Component id="rbWeeks" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace type="unrelated" max="-2" attributes="0"/>
-                  <Component id="rbMonths" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="-2" attributes="0"/>
+                  <EmptySpace min="0" pref="0" max="-2" attributes="0"/>
                   <Component id="rbAllCases" min="-2" max="-2" attributes="0"/>
-                  <EmptySpace max="-2" attributes="0"/>
               </Group>
           </Group>
         </DimensionLayout>
@@ -167,56 +160,6 @@
             <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbAllCasesItemStateChanged"/>
           </Events>
         </Component>
-        <Component class="javax.swing.JRadioButton" name="rbMonths">
-          <Properties>
-            <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
-              <ComponentRef name="rbGroupHistoryLength"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbMonths.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-          <Events>
-            <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbMonthsItemStateChanged"/>
-          </Events>
-        </Component>
-        <Component class="javax.swing.JRadioButton" name="rbWeeks">
-          <Properties>
-            <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
-              <ComponentRef name="rbGroupHistoryLength"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbWeeks.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-          <Events>
-            <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbWeeksItemStateChanged"/>
-          </Events>
-        </Component>
-        <Component class="javax.swing.JRadioButton" name="rbDays">
-          <Properties>
-            <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
-              <ComponentRef name="rbGroupHistoryLength"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbDays.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-            <Property name="name" type="java.lang.String" value="" noResource="true"/>
-          </Properties>
-          <Events>
-            <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbDaysItemStateChanged"/>
-          </Events>
-        </Component>
-        <Component class="javax.swing.JLabel" name="rbGroupLabel">
-          <Properties>
-            <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
-              <Font name="Tahoma" size="12" style="0"/>
-            </Property>
-            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
-              <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbGroupLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-        </Component>
       </SubComponents>
     </Container>
     <Component class="javax.swing.JButton" name="bnShowLog">
@@ -233,5 +176,55 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="bnShowLogActionPerformed"/>
       </Events>
     </Component>
+    <Component class="javax.swing.JRadioButton" name="rbDays">
+      <Properties>
+        <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+          <ComponentRef name="rbGroupHistoryLength"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbDays.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+        <Property name="name" type="java.lang.String" value="" noResource="true"/>
+      </Properties>
+      <Events>
+        <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbDaysItemStateChanged"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JRadioButton" name="rbWeeks">
+      <Properties>
+        <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+          <ComponentRef name="rbGroupHistoryLength"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbWeeks.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbWeeksItemStateChanged"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JRadioButton" name="rbMonths">
+      <Properties>
+        <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+          <ComponentRef name="rbGroupHistoryLength"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbMonths.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="rbMonthsItemStateChanged"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JLabel" name="rbGroupLabel">
+      <Properties>
+        <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
+          <Font name="Tahoma" size="12" style="0"/>
+        </Property>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties" key="AutoIngestCasePanel.rbGroupLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
   </SubComponents>
 </Form>
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
index ea98faea18522168bf006641da32c09d7b007392..9b6b3ddefaad64f4d95a36efe152cfba7950073b 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePanel.java
@@ -63,7 +63,7 @@ public final class AutoIngestCasePanel extends JPanel {
     private static final int STATUS_COL_MAX_WIDTH = 250;
     private static final int STATUS_COL_PREFERRED_WIDTH = 60;
     private static final int MILLIS_TO_WAIT_BEFORE_STARTING = 500;
-    private static final int MILLIS_TO_WAIT_BETWEEN_UPDATES = 30000;
+    private static final int MILLIS_TO_WAIT_BETWEEN_UPDATES = 300000;
     private ScheduledThreadPoolExecutor casesTableRefreshExecutor;
 
     /*
@@ -105,6 +105,14 @@ public AutoIngestCasePanel(JDialog parent) {
             public boolean isCellEditable(int row, int column) {
                 return false;
             }
+            @Override
+            public Class<?> getColumnClass(int col) {
+                if (this.getColumnName(col).equals(CREATEDTIME_HEADER) || this.getColumnName(col).equals(COMPLETEDTIME_HEADER)) {
+                    return Date.class;
+                } else {
+                    return super.getColumnClass(col);
+                }
+            }
         };
 
         initComponents();
@@ -384,11 +392,11 @@ private void initComponents() {
         bnRefresh = new javax.swing.JButton();
         panelFilter = new javax.swing.JPanel();
         rbAllCases = new javax.swing.JRadioButton();
-        rbMonths = new javax.swing.JRadioButton();
-        rbWeeks = new javax.swing.JRadioButton();
+        bnShowLog = new javax.swing.JButton();
         rbDays = new javax.swing.JRadioButton();
+        rbWeeks = new javax.swing.JRadioButton();
+        rbMonths = new javax.swing.JRadioButton();
         rbGroupLabel = new javax.swing.JLabel();
-        bnShowLog = new javax.swing.JButton();
 
         setName("Completed Cases"); // NOI18N
 
@@ -428,62 +436,19 @@ public void itemStateChanged(java.awt.event.ItemEvent evt) {
             }
         });
 
-        rbGroupHistoryLength.add(rbMonths);
-        org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbMonths.text")); // NOI18N
-        rbMonths.addItemListener(new java.awt.event.ItemListener() {
-            public void itemStateChanged(java.awt.event.ItemEvent evt) {
-                rbMonthsItemStateChanged(evt);
-            }
-        });
-
-        rbGroupHistoryLength.add(rbWeeks);
-        org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbWeeks.text")); // NOI18N
-        rbWeeks.addItemListener(new java.awt.event.ItemListener() {
-            public void itemStateChanged(java.awt.event.ItemEvent evt) {
-                rbWeeksItemStateChanged(evt);
-            }
-        });
-
-        rbGroupHistoryLength.add(rbDays);
-        org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbDays.text")); // NOI18N
-        rbDays.setName(""); // NOI18N
-        rbDays.addItemListener(new java.awt.event.ItemListener() {
-            public void itemStateChanged(java.awt.event.ItemEvent evt) {
-                rbDaysItemStateChanged(evt);
-            }
-        });
-
-        rbGroupLabel.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N
-        org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbGroupLabel.text")); // NOI18N
-
         javax.swing.GroupLayout panelFilterLayout = new javax.swing.GroupLayout(panelFilter);
         panelFilter.setLayout(panelFilterLayout);
         panelFilterLayout.setHorizontalGroup(
             panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(panelFilterLayout.createSequentialGroup()
-                .addContainerGap()
-                .addGroup(panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(rbGroupLabel)
-                    .addComponent(rbAllCases)
-                    .addComponent(rbMonths)
-                    .addComponent(rbWeeks)
-                    .addComponent(rbDays))
-                .addContainerGap(34, Short.MAX_VALUE))
+                .addComponent(rbAllCases)
+                .addGap(0, 0, Short.MAX_VALUE))
         );
         panelFilterLayout.setVerticalGroup(
             panelFilterLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelFilterLayout.createSequentialGroup()
-                .addContainerGap()
-                .addComponent(rbGroupLabel)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                .addComponent(rbDays)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
-                .addComponent(rbWeeks)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
-                .addComponent(rbMonths)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(rbAllCases)
-                .addContainerGap())
+                .addGap(0, 0, 0)
+                .addComponent(rbAllCases))
         );
 
         org.openide.awt.Mnemonics.setLocalizedText(bnShowLog, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.bnShowLog.text")); // NOI18N
@@ -495,6 +460,34 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
             }
         });
 
+        rbGroupHistoryLength.add(rbDays);
+        org.openide.awt.Mnemonics.setLocalizedText(rbDays, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbDays.text")); // NOI18N
+        rbDays.setName(""); // NOI18N
+        rbDays.addItemListener(new java.awt.event.ItemListener() {
+            public void itemStateChanged(java.awt.event.ItemEvent evt) {
+                rbDaysItemStateChanged(evt);
+            }
+        });
+
+        rbGroupHistoryLength.add(rbWeeks);
+        org.openide.awt.Mnemonics.setLocalizedText(rbWeeks, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbWeeks.text")); // NOI18N
+        rbWeeks.addItemListener(new java.awt.event.ItemListener() {
+            public void itemStateChanged(java.awt.event.ItemEvent evt) {
+                rbWeeksItemStateChanged(evt);
+            }
+        });
+
+        rbGroupHistoryLength.add(rbMonths);
+        org.openide.awt.Mnemonics.setLocalizedText(rbMonths, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbMonths.text")); // NOI18N
+        rbMonths.addItemListener(new java.awt.event.ItemListener() {
+            public void itemStateChanged(java.awt.event.ItemEvent evt) {
+                rbMonthsItemStateChanged(evt);
+            }
+        });
+
+        rbGroupLabel.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N
+        org.openide.awt.Mnemonics.setLocalizedText(rbGroupLabel, org.openide.util.NbBundle.getMessage(AutoIngestCasePanel.class, "AutoIngestCasePanel.rbGroupLabel.text")); // NOI18N
+
         javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
         this.setLayout(layout);
         layout.setHorizontalGroup(
@@ -503,35 +496,45 @@ public void actionPerformed(java.awt.event.ActionEvent evt) {
                 .addContainerGap()
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                     .addGroup(layout.createSequentialGroup()
-                        .addGap(13, 13, 13)
+                        .addGap(4, 4, 4)
                         .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
-                        .addGap(18, 18, 18)
-                        .addComponent(bnRefresh)
-                        .addGap(18, 18, 18)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                         .addComponent(bnShowLog)
                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addComponent(rbGroupLabel)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(rbDays)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(rbWeeks)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(rbMonths)
+                        .addGap(0, 0, 0)
                         .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                        .addGap(20, 20, 20))
-                    .addGroup(layout.createSequentialGroup()
-                        .addComponent(scrollPaneTable, javax.swing.GroupLayout.DEFAULT_SIZE, 1007, Short.MAX_VALUE)
-                        .addContainerGap())))
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                        .addComponent(bnRefresh)
+                        .addGap(4, 4, 4))
+                    .addComponent(scrollPaneTable, javax.swing.GroupLayout.DEFAULT_SIZE, 1007, Short.MAX_VALUE))
+                .addContainerGap())
         );
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
-                .addGap(43, 43, 43)
+                .addGap(6, 6, 6)
                 .addComponent(scrollPaneTable, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
-                    .addGroup(layout.createSequentialGroup()
-                        .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
-                        .addContainerGap())
-                    .addGroup(layout.createSequentialGroup()
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                        .addComponent(bnOpen)
+                        .addComponent(bnShowLog))
+                    .addComponent(bnRefresh)
+                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                            .addComponent(bnOpen)
-                            .addComponent(bnRefresh)
-                            .addComponent(bnShowLog))
-                        .addGap(36, 36, 36))))
+                            .addComponent(rbDays)
+                            .addComponent(rbWeeks)
+                            .addComponent(rbMonths)
+                            .addComponent(rbGroupLabel))
+                        .addComponent(panelFilter, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addGap(0, 0, 0))
         );
     }// </editor-fold>//GEN-END:initComponents
 
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
index 5b755dfb011ebb1d4fe55dc8107020ca07c0b7d6..7e04d4c8e4192646a71df92299c94cc42e8b4d5e 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties
@@ -1,4 +1,11 @@
 CTL_OpenAction=Open Case...
+AutoIngestDashboard.bnRefresh.text=&Refresh
+AutoIngestDashboard.lbCompleted.text=Completed Jobs
+AutoIngestDashboard.lbRunning.text=Running Jobs
+AutoIngestDashboard.lbPending.text=Pending Jobs
+AutoIngestDashboard.bnCancelModule.text=Cancel &Module
+AutoIngestDashboard.bnExit.text=&Exit
+AutoIngestDashboard.bnOptions.text=&Options
 AutoIngestDashboard.JobsTableModel.ColumnHeader.Case=Case
 AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder=Data Source
 AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName=Host Name
@@ -8,12 +15,23 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed
 AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage
 AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status
 AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path
+AutoIngestDashboard.bnShowProgress.text=Ingest Progress
 AutoIngestDashboard.bnResume.text=Resume
+AutoIngestDashboard.bnPause.text=Pause
 AutoIngestDashboard.bnPause.confirmHeader=Are you sure you want to pause?
 AutoIngestDashboard.bnPause.warningText=Pause will occur after the current job completes processing. This could take a long time. Continue?
+AutoIngestDashboard.bnPause.toolTipText=Suspend processing of Pending Jobs
 AutoIngestDashboard.bnPause.toolTipTextResume=Resume processing of Pending Jobs
 AutoIngestDashboard.bnPause.pausing=Pausing after current job completes...
+AutoIngestDashboard.bnRefresh.toolTipText=Refresh displayed tables
+AutoIngestDashboard.bnShowProgress.toolTipText=Show the progress of the currently running Job. This functionality is only available for jobs running on current AIM node.
+AutoIngestDashboard.bnCancelModule.toolTipText=Cancel processing of the current module within the Job and move on to the next module within the Job. This functionality is only available for jobs running on current AIM node.
+AutoIngestDashboard.bnExit.toolTipText=Exit Application
 AutoIngestDashboard.Cancelling=Cancelling...
+AutoIngestDashboard.bnOptions.toolTipText=Display options panel. All processing must be paused to open the options panel.
+AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first
+AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it
+AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
 AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage
 AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case Folder
 AutoIngestDashboard.JobsTableModel.ColumnHeader.LocalJob= Local Job?
@@ -145,12 +163,20 @@ CopyFilesPanel.ConfirmCopyAdd=exists. Do you really want to copy more files to t
 CopyFilesPanel.ConfirmCopyYes=Copy
 CopyFilesPanel.ConfirmCopyNo=Do not copy
 ConfirmationDialog.ConfirmUnlockHeader=Confirm Case Unlock
+AutoIngestDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue.
+AutoIngestDashboard.bnPrioritizeCase.text=Prioriti&ze Case
+AutoIngestDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case
+AutoIngestDashboard.bnShowCaseLog.text=Show Case &Log
 CopyFilesPanel.bnCancelPendingJob.text=Ca&ncel
 CopyFilesPanel.tbDestinationCase.text=
 CopyFilesPanel.cbThrottleNetwork.text=&Throttle Network
 CopyFilesPanel.cbThrottleNetwork.toolTipText=<html>Select this box if a low-bandwidth network connection is involved in this copy job.<br>\nSelecting this box will artificially limit the transfer speed by inserting strategic delays.<br>\nThis helps copy files across low-bandwidth networks where the transfer would<br>\notherwise fail. Only select this if you are having problems copying across the network.</html>
 CopyFilesPanel.bnShowCurrentLog.text=Show &Log
 CopyFilesPanel.bnShowCurrentLog.text=Show &Log
+AutoIngestDashboard.bnCancelJob.toolTipText=Cancel processing of the current Job and move on to the next Job. This functionality is only available for jobs running on current AIM node.
+AutoIngestDashboard.bnCancelJob.text=&Cancel Job
+AutoIngestDashboard.bnDeleteCase.toolTipText=Delete the selected Case in its entirety
+AutoIngestDashboard.bnDeleteCase.text=&Delete Case
 CopyFilesPanel.lbCaseName.text=Case Name
 CaseStatusIconCellRenderer.tooltiptext.ok=Images processed successfully
 CaseStatusIconCellRenderer.tooltiptext.warning=An error occurred or processing was canceled for at least one image - please check the log
@@ -173,6 +199,7 @@ CaseImportPanel.Error=Error
 CaseImportPanel.Complete=Complete
 CaseImportPanel.Blank=
 CaseImportPanel.DeleteWarning=Make sure no important files are in the case source directory
+AutoIngestDashboard.lbStatus.text=Status:
 SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping 
 SingleUserCaseImporter.WillImport=Will import:
 SingleUserCaseImporter.None=None
@@ -195,6 +222,9 @@ ReviewModeCasePanel.StatusIconHeaderText=Status
 ReviewModeCasePanel.OutputFolderHeaderText=Output Folder
 ReviewModeCasePanel.LastAccessedTimeHeaderText=Last Accessed Time
 CopyFilesPanel.bnOptions.text=&Options
+AutoIngestDashboard.lbServicesStatus.text=Services Status:
+AutoIngestDashboard.tbServicesStatusMessage.text=
+AutoIngestDashboard.tbStatusMessage.text=
 FileExporterSettingsPanel.ChooseRootDirectory=Choose a root directory for file output
 FileExporterSettingsPanel.ChooseReportDirectory=Choose a report directory
 FileExporterSettingsPanel.RuleName=Rule Name
@@ -253,7 +283,12 @@ FileExporterSettingsPanel.BrowseReportTooltip_1=Browse for the Reports Folder
 FileExporterSettingsPanel.NewRuleTooltip_1=Clear the rule editor to begin a new rule
 FileExporterSettingsPanel.DeleteTooltip_1=Delete the selected rule
 FileExporterSettingsPanel.SaveTooltip_1=Save the current rule
+AutoIngestDashboard.bnOpenLogDir.text=Open System Logs Directory
+AutoIngestDashboard.bnPrioritizeJob.text=Prioritize Job
+AutoIngestDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of the Pending queue.
+AutoIngestDashboard.bnReprocessJob.text=Reprocess Job
 AutoIngestDashboard.bnPrioritizeFolder.label=<AutoIngestDashboard.bnPrioritizeJob.text>
+AutoIngestDashboard.bnPrioritizeJob.actionCommand=<AutoIngestDashboard.bnPrioritizeJob.text>
 AutoIngestCasePanel.rbDays.text=Days
 AutoIngestCasePanel.rbWeeks.text=Weeks
 AutoIngestCasePanel.rbMonths.text=Months
@@ -262,74 +297,4 @@ AutoIngestCasePanel.bnRefresh.text=&Refresh
 AutoIngestCasePanel.bnOpen.text=&Open
 AutoIngestCasePanel.bnShowLog.toolTipText=Display case log file for selected case
 AutoIngestCasePanel.bnShowLog.text=&Show Log
-AutoIngestCasePanel.rbGroupLabel.text=Show Last 10:
-AutoIngestLegacyDashboard.tbStatusMessage.text=
-AutoIngestLegacyDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case
-AutoIngestLegacyDashboard.bnShowCaseLog.text=Show Case &Log
-AutoIngestLegacyDashboard.bnDeleteCase.toolTipText=Delete the selected Case in its entirety
-AutoIngestLegacyDashboard.bnDeleteCase.text=&Delete Case
-AutoIngestLegacyDashboard.bnCancelJob.toolTipText=Cancel processing of the current Job and move on to the next Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestLegacyDashboard.bnCancelJob.text=&Cancel Job
-AutoIngestLegacyDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
-AutoIngestLegacyDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it
-AutoIngestLegacyDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first
-AutoIngestLegacyDashboard.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue.
-AutoIngestLegacyDashboard.bnPrioritizeCase.text=Prioriti&ze Case
-AutoIngestLegacyDashboard.bnPause.toolTipText=Suspend processing of Pending Jobs
-AutoIngestLegacyDashboard.bnPause.text=Pause
-AutoIngestLegacyDashboard.bnShowProgress.toolTipText=Show the progress of the currently running Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestLegacyDashboard.bnShowProgress.text=Ingest Progress
-AutoIngestLegacyDashboard.bnOptions.toolTipText=Display options panel. All processing must be paused to open the options panel.
-AutoIngestLegacyDashboard.bnOptions.text=&Options
-AutoIngestLegacyDashboard.bnExit.toolTipText=Exit Application
-AutoIngestLegacyDashboard.bnExit.text=&Exit
-AutoIngestLegacyDashboard.bnCancelModule.toolTipText=Cancel processing of the current module within the Job and move on to the next module within the Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestLegacyDashboard.bnCancelModule.text=Cancel &Module
-AutoIngestLegacyDashboard.bnRefresh.toolTipText=Refresh displayed tables
-AutoIngestLegacyDashboard.bnRefresh.text=&Refresh
-AutoIngestLegacyDashboard.lbCompleted.text=Completed Jobs
-AutoIngestLegacyDashboard.lbRunning.text=Running Jobs
-AutoIngestLegacyDashboard.lbPending.text=Pending Jobs
-AutoIngestLegacyDashboard.bnReprocessJob.text=Reprocess Job
-AutoIngestLegacyDashboard.bnOpenLogDir.text=Open System Logs Directory
-AutoIngestLegacyDashboard.tbServicesStatusMessage.text=
-AutoIngestLegacyDashboard.lbServicesStatus.text=Services Status:
-AutoIngestLegacyDashboard.bnPrioritizeJob.actionCommand=<AutoIngestDashboard.bnPrioritizeJob.text>
-AutoIngestLegacyDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of the Pending queue.
-AutoIngestLegacyDashboard.bnPrioritizeJob.text=Prioritize Job
-AutoIngestLegacyDashboard.lbStatus.text=Status:
-AutoIngestDashboard.bnRefresh.text=&Refresh
-AutoIngestDashboard.lbCompleted.text=Completed Jobs
-AutoIngestDashboard.lbRunning.text=Running Jobs
-AutoIngestDashboard.lbPending.text=Pending Jobs
-AutoIngestDashboard.bnDeleteCase.toolTipText=Delete the selected Case in its entirety
-AutoIngestDashboard.bnDeleteCase.text=&Delete Case
-AutoIngestDashboard.bnCancelJob.toolTipText=Cancel processing of the current Job and move on to the next Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestDashboard.bnCancelJob.text=&Cancel Job
-AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already
-AutoIngestDashboard.bnReprocessJob.text=Reprocess Job
-AutoIngestDashboard.bnOpenLogDir.text=Open System Logs Directory
-AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it
-AutoIngestDashboard.tbServicesStatusMessage.text=
-AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first
-AutoIngestDashboard.lbServicesStatus.text=Services Status:
-AutoIngestDashboard.bnPrioritizeJob.actionCommand=<AutoIngestDashboard.bnPrioritizeJob.text>
-AutoIngestDashboard.bnPrioritizeJob.toolTipText=Move this folder to the top of the Pending queue.
-AutoIngestDashboard.bnPrioritizeJob.text=Prioritize Job
-AutoIngestDashboard.lbStatus.text=Status:
-AutoIngestDashboard.tbStatusMessage.text=
-AutoIngestDashboard.bnShowCaseLog.toolTipText=Display case log file for selected case
-AutoIngestDashboard.bnShowCaseLog.text=Show Case &Log
-AutoIngestDashboard.bnPrioritizeCase.toolTipText_1=Move all images associated with a case to top of Pending queue.
-AutoIngestDashboard.bnPrioritizeCase.text_1=Prioriti&ze Case
-AutoIngestDashboard.bnPause.toolTipText=Suspend processing of Pending Jobs
-AutoIngestDashboard.bnPause.text=Pause
-AutoIngestDashboard.bnShowProgress.toolTipText=Show the progress of the currently running Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestDashboard.bnShowProgress.text=Ingest Progress
-AutoIngestDashboard.bnOptions.toolTipText=Display options panel. All processing must be paused to open the options panel.
-AutoIngestDashboard.bnOptions.text=&Options
-AutoIngestDashboard.bnExit.toolTipText=Exit Application
-AutoIngestDashboard.bnExit.text=&Exit
-AutoIngestDashboard.bnCancelModule.toolTipText=Cancel processing of the current module within the Job and move on to the next module within the Job. This functionality is only available for jobs running on current AIM node.
-AutoIngestDashboard.bnCancelModule.text=Cancel &Module
-AutoIngestDashboard.bnRefresh.toolTipText=Refresh displayed tables
+AutoIngestCasePanel.rbGroupLabel.text=Show cases accessed in the last 10:
\ No newline at end of file
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
index 0ec43d4b3eab6d4308ab854f0410767a62a2764f..f8e4d2791f22e2e28075bd5c21ad0c51a5aa75b9 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
@@ -35,6 +35,7 @@
 import org.sleuthkit.datamodel.Directory;
 import org.sleuthkit.datamodel.File;
 import org.sleuthkit.datamodel.LayoutFile;
+import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.LocalFile;
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.SleuthkitItemVisitor;
@@ -206,7 +207,6 @@ private void indexChunk(String chunk, String sourceName, Map<String, String> fie
             // but does this really mean we don't want to index it?
 
             //skip the file, image id unknown
-            //JMTODO: does this need to ne internationalized?
             String msg = NbBundle.getMessage(Ingester.class,
                     "Ingester.ingest.exception.unknownImgId.msg", sourceName); //JMTODO: does this need to ne internationalized?
             logger.log(Level.SEVERE, msg);
@@ -273,6 +273,11 @@ public Map<String, String> visit(Directory d) {
             return getCommonAndMACTimeFields(d);
         }
 
+        @Override
+        public Map<String, String> visit(LocalDirectory ld){
+            return getCommonAndMACTimeFields(ld);
+        }
+        
         @Override
         public Map<String, String> visit(LayoutFile lf) {
             // layout files do not have times
diff --git a/nbproject/project.properties b/nbproject/project.properties
index 03d618afe67b9cb72bc2b772e0c9b5dd8338ffd1..8df7ff47a0eaaef8387722ecfa784a43575068bb 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -10,7 +10,6 @@ app.version=4.4.1
 build.type=DEVELOPMENT
 
 project.org.netbeans.progress=org-netbeans-api-progress
-project.org.sleuthkit.autopsy.centralrepository=CentralRepository
 project.org.sleuthkit.autopsy.experimental=Experimental
 project.org.sleuthkit.autopsy.imagegallery=ImageGallery
 update_versions=false
@@ -32,8 +31,7 @@ modules=\
     ${project.org.sleuthkit.autopsy.core}:\
     ${project.org.sleuthkit.autopsy.corelibs}:\
     ${project.org.sleuthkit.autopsy.imagegallery}:\
-    ${project.org.sleuthkit.autopsy.experimental}:\
-    ${project.org.sleuthkit.autopsy.centralrepository}
+    ${project.org.sleuthkit.autopsy.experimental}
 project.org.sleuthkit.autopsy.core=Core
 project.org.sleuthkit.autopsy.corelibs=CoreLibs
 project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
diff --git a/test/script/config.xml b/test/script/config.xml
index 6762cdd5ce7b311d8751c5abbb5457ff5c7413dd..89dfa9790deec09adf40efb6ff4d621785a5b769 100644
--- a/test/script/config.xml
+++ b/test/script/config.xml
@@ -6,12 +6,25 @@ List of tags:
 image: An image to be ingested
 build: the path to the build.xml file
 indir: the path to input directory
-outdir: the path to output directory
+singleUser_outdir: the path to single-user case output directory
 global_csv: path to global csv file
-golddir: the path to gold directory
+singleUser_golddir: the path to single-user case gold directory
 timing: can be set to True or False. If enabled, record the timing.
-
-NOTE: Make sure to use windows style for paths!
+userCaseType: set this value to do single-user, multi-user or both tests
+
+List of tags for multi-user case:
+multiUser_outdir: a path to multi-user case output directory
+multiUser_golddir: a path to multi-user case gold directory
+dbHost: PostgreSQL database host name
+dbPort: PostgreSQL database port number
+dbUserName: PostgreSQL database username
+dbPassword: PostgreSQL database password
+solrHost: Solr server host name
+solrPort: Solr server port number
+messageServiceHost: ActiveMQ server hostname
+messageServicePort: ActiveMQ server port number
+
+NOTE: Make sure to use UNC path for mutliUser_outdir, use windows style for other paths! 
 
 None of these tags are mandatory, and if nothing is provided the file will be
 looked over and ignored.
diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py
index f55fc0a95d772c51adcbc72767bb2f17ced4f7fc..4f3518f5655d5dbdb941402c3841330662d9c09a 100755
--- a/test/script/tskdbdiff.py
+++ b/test/script/tskdbdiff.py
@@ -300,11 +300,14 @@ def _dump_output_db_nonbb(db_file, dump_file):
         os.chmod (backup_db_file, 0o777)
 
         conn = sqlite3.connect(backup_db_file)
-        id_path_table = build_id_table(conn.cursor())
+        id_files_table = build_id_files_table(conn.cursor())
         id_vs_parts_table = build_id_vs_parts_table(conn.cursor())
         id_vs_info_table = build_id_vs_info_table(conn.cursor())
         id_fs_info_table = build_id_fs_info_table(conn.cursor())
         id_objects_table = build_id_objects_table(conn.cursor())
+        id_artifact_types_table = build_id_artifact_types_table(conn.cursor())
+        id_obj_path_table = build_id_obj_path_table(id_files_table, id_objects_table, id_artifact_types_table)
+
         conn.text_factory = lambda x: x.decode("utf-8", "ignore")
 
         # Delete the blackboard tables
@@ -314,7 +317,7 @@ def _dump_output_db_nonbb(db_file, dump_file):
         # Write to the database dump
         with codecs.open(dump_file, "wb", "utf_8") as db_log:
             for line in conn.iterdump():
-                line = normalize_db_entry(line, id_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table)
+                line = normalize_db_entry(line, id_obj_path_table, id_vs_parts_table, id_vs_info_table, id_fs_info_table, id_objects_table)
                 db_log.write('%s\n' % line)
             # Now sort the file    
             
@@ -346,12 +349,12 @@ def _get_tmp_file(base, ext):
 class TskDbDiffException(Exception):
     pass
 
-def normalize_db_entry(line, table, vs_parts_table, vs_info_table, fs_info_table, objects_table):
+def normalize_db_entry(line, files_table, vs_parts_table, vs_info_table, fs_info_table, objects_table):
     """ Make testing more consistent and reasonable by doctoring certain db entries.
 
     Args:
         line: a String, the line to remove the object id from.
-        table: a map from object ids to file paths.
+        files_table: a map from object ids to file paths.
     """
 
     files_index = line.find('INSERT INTO "tsk_files"')
@@ -361,31 +364,35 @@ def normalize_db_entry(line, table, vs_parts_table, vs_info_table, fs_info_table
     layout_index = line.find('INSERT INTO "tsk_file_layout"')
     data_source_info_index = line.find('INSERT INTO "data_source_info"')
     ingest_job_index = line.find('INSERT INTO "ingest_jobs"')
+
     parens = line[line.find('(') + 1 : line.rfind(')')]
     fields_list = parens.replace(" ", "").split(',')
     
     # remove object ID
     if (files_index != -1):
         obj_id = fields_list[0]
-        path = table[int(obj_id)]
+        path = files_table[int(obj_id)]
         newLine = ('INSERT INTO "tsk_files" VALUES(' + ', '.join(fields_list[1:]) + ');') 
         return newLine
     # remove object ID
     elif (path_index != -1):
-        obj_id = fields_list[0]
-        objValue = table[int(obj_id)]
-        par_obj_id = objects_table[int(obj_id)]
-        par_obj_value = table[par_obj_id]
-        par_obj_name = par_obj_value[par_obj_value.rfind('/')+1:]
-        #check the par_id that we insert to the path name when we create uniqueName
-        pathValue = re.sub(par_obj_name + '_' + str(par_obj_id), par_obj_name, fields_list[1])
-                
+        obj_id = int(fields_list[0])
+        objValue = files_table[obj_id]
+        # remove the obj_id from ModuleOutput/EmbeddedFileExtractor directory
+        idx_pre = fields_list[1].find('EmbeddedFileExtractor') + len('EmbeddedFileExtractor')
+        if idx_pre > -1:
+            idx_pos =  fields_list[1].find('\\', idx_pre + 2)
+            dir_to_replace = fields_list[1][idx_pre + 1 : idx_pos] # +1 to skip the file seperator
+            dir_to_replace = dir_to_replace[0:dir_to_replace.rfind('_')]
+            pathValue = fields_list[1][:idx_pre+1] + dir_to_replace + fields_list[1][idx_pos:]
+        else:
+            pathValue = fields_list[1]
         newLine = ('INSERT INTO "tsk_files_path" VALUES(' + objValue + ', ' + pathValue + ', ' + ', '.join(fields_list[2:]) + ');') 
         return newLine
     # remove object ID
     elif (layout_index != -1):
         obj_id = fields_list[0]
-        path= table[int(obj_id)]
+        path= files_table[int(obj_id)]
         newLine = ('INSERT INTO "tsk_file_layout" VALUES(' + path + ', ' + ', '.join(fields_list[1:]) + ');') 
         return newLine
     # remove object ID
@@ -403,29 +410,29 @@ def normalize_db_entry(line, table, vs_parts_table, vs_info_table, fs_info_table
         except Exception as e:
             return line
 
-        if obj_id in table.keys():
-             path = table[obj_id]
+        if obj_id in files_table.keys():
+            path = files_table[obj_id]
         elif obj_id in vs_parts_table.keys():
-             path = vs_parts_table[obj_id]
+            path = vs_parts_table[obj_id]
         elif obj_id in vs_info_table.keys():
-             path = vs_info_table[obj_id]
+            path = vs_info_table[obj_id]
         elif obj_id in fs_info_table.keys():
-             path = fs_info_table[obj_id]
+            path = fs_info_table[obj_id]
         
-        if parent_id in table.keys():
-             parent_path = table[parent_id]
+        if parent_id in files_table.keys():
+            parent_path = files_table[parent_id]
         elif parent_id in vs_parts_table.keys():
-             parent_path = vs_parts_table[parent_id]
+            parent_path = vs_parts_table[parent_id]
         elif parent_id in vs_info_table.keys():
-             parent_path = vs_info_table[parent_id]
+            parent_path = vs_info_table[parent_id]
         elif parent_id in fs_info_table.keys():
-             parent_path = fs_info_table[parent_id]
+            parent_path = fs_info_table[parent_id]
         
 
         if path and parent_path:
-             return newLine + path + ', ' + parent_path + ', ' + ', '.join(fields_list[2:]) + ');'
+            return newLine + path + ', ' + parent_path + ', ' + ', '.join(fields_list[2:]) + ');'
         else:
-             return line 
+            return line 
     # remove time-based information, ie Test_6/11/14 -> Test    
     elif (report_index != -1):
         fields_list[1] = "AutopsyTestCase"
@@ -467,60 +474,95 @@ def getAssociatedArtifactType(db_file, artifact_id):
 
     return "File path: " + info[0] + " Artifact Type: " + info[1]
 
-def build_id_table(artifact_cursor):
+def build_id_files_table(db_cursor):
     """Build the map of object ids to file paths.
 
     Args:
-        artifact_cursor: the database cursor
+        db_cursor: the database cursor
     """
     # for each row in the db, take the object id, parent path, and name, then create a tuple in the dictionary
     # with the object id as the key and the full file path (parent + name) as the value
-    mapping = dict([(row[0], str(row[1]) + str(row[2])) for row in artifact_cursor.execute("SELECT obj_id, parent_path, name FROM tsk_files")])
+    mapping = dict([(row[0], str(row[1]) + str(row[2])) for row in db_cursor.execute("SELECT obj_id, parent_path, name FROM tsk_files")])
     return mapping
 
-def build_id_vs_parts_table(artifact_cursor):
+def build_id_vs_parts_table(db_cursor):
     """Build the map of object ids to vs_parts.
 
     Args:
-        artifact_cursor: the database cursor
+        db_cursor: the database cursor
     """
     # for each row in the db, take the object id, addr, and start, then create a tuple in the dictionary
     # with the object id as the key and (addr + start) as the value
-    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in artifact_cursor.execute("SELECT obj_id, addr, start FROM tsk_vs_parts")])
+    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in db_cursor.execute("SELECT obj_id, addr, start FROM tsk_vs_parts")])
     return mapping
 
-def build_id_vs_info_table(artifact_cursor):
+def build_id_vs_info_table(db_cursor):
     """Build the map of object ids to vs_info.
 
     Args:
-        artifact_cursor: the database cursor
+        db_cursor: the database cursor
     """
     # for each row in the db, take the object id, vs_type, and img_offset, then create a tuple in the dictionary
     # with the object id as the key and (vs_type + img_offset) as the value
-    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in artifact_cursor.execute("SELECT obj_id, vs_type, img_offset FROM tsk_vs_info")])
+    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in db_cursor.execute("SELECT obj_id, vs_type, img_offset FROM tsk_vs_info")])
     return mapping
 
      
-def build_id_fs_info_table(artifact_cursor):
+def build_id_fs_info_table(db_cursor):
     """Build the map of object ids to fs_info.
 
     Args:
-        artifact_cursor: the database cursor
+        db_cursor: the database cursor
     """
     # for each row in the db, take the object id, img_offset, and fs_type, then create a tuple in the dictionary
     # with the object id as the key and (img_offset + fs_type) as the value
-    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in artifact_cursor.execute("SELECT obj_id, img_offset, fs_type FROM tsk_fs_info")])
+    mapping = dict([(row[0], str(row[1]) + '_' + str(row[2])) for row in db_cursor.execute("SELECT obj_id, img_offset, fs_type FROM tsk_fs_info")])
     return mapping
 
-def build_id_objects_table(artifact_cursor):
+def build_id_objects_table(db_cursor):
     """Build the map of object ids to par_id.
 
     Args:
-        artifact_cursor: the database cursor
+        db_cursor: the database cursor
     """
     # for each row in the db, take the object id, par_obj_id, then create a tuple in the dictionary
-    # with the object id as the key and par_obj_id as the value
-    mapping = dict([(row[0], row[1]) for row in artifact_cursor.execute("SELECT obj_id, par_obj_id FROM tsk_objects")])
+    # with the object id as the key and par_obj_id, type as the value
+    mapping = dict([(row[0], [row[1], row[2]]) for row in db_cursor.execute("SELECT * FROM tsk_objects")])
+    return mapping
+
+def build_id_artifact_types_table(db_cursor):
+    """Build the map of object ids to artifact ids.
+
+    Args:
+        db_cursor: the database cursor
+    """
+    # for each row in the db, take the object id, par_obj_id, then create a tuple in the dictionary
+    # with the object id as the key and artifact type as the value
+    mapping = dict([(row[0], row[1]) for row in db_cursor.execute("SELECT blackboard_artifacts.artifact_obj_id, blackboard_artifact_types.type_name FROM blackboard_artifacts INNER JOIN blackboard_artifact_types ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id ")])
+    return mapping
+
+
+def build_id_obj_path_table(files_table, objects_table, artifacts_table):
+    """Build the map of object ids to artifact ids.
+
+    Args:
+        files_table: obj_id, path
+        objects_table: obj_id, par_obj_id, type
+        artifacts_table: obj_id, artifact_type_name
+    """
+    # make a copy of files_table and updated it with new data from artifats_table
+    mapping = files_table.copy()
+    for k, v in objects_table.items():
+        if k not in mapping.keys(): # If the mapping table doesn't have data for obj_id(k), we use it's par_obj_id's path+name plus it's artifact_type name as path
+            if k in artifacts_table.keys():
+                par_obj_id = v[0]
+                path = mapping[par_obj_id] 
+                mapping[k] = path + "/" + artifacts_table[k]
+        elif v[0] not in mapping.keys():
+            if v[0] in artifacts_table.keys():
+                par_obj_id = objects_table[v[0]]
+                path = mapping[par_obj_id] 
+                mapping[k] = path + "/" + artifacts_table[v[0]]
     return mapping
 
 def main():
diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
index 8c0750bbcd59fb6c6755ebfae47f1af25033ea82..3b7d70d929b74da75a36a01ef8d85c2369021971 100644
--- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
+++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java
@@ -295,11 +295,15 @@ public static String getRelModuleOutputPath() {
      */
     private void processEmails(List<EmailMessage> emails, AbstractFile abstractFile) {
         List<AbstractFile> derivedFiles = new ArrayList<>();
+        
+       
+        
         for (EmailMessage email : emails) {
-            if (email.hasAttachment()) {
-                derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile));
+            BlackboardArtifact msgArtifact = addArtifact(email, abstractFile);
+             
+            if ((msgArtifact != null) && (email.hasAttachment()))  {
+                derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile, msgArtifact ));
             }
-            addArtifact(email, abstractFile);
         }
 
         if (derivedFiles.isEmpty() == false) {
@@ -320,7 +324,7 @@ private void processEmails(List<EmailMessage> emails, AbstractFile abstractFile)
      *
      * @return
      */
-    private List<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile) {
+    private List<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) {
         List<AbstractFile> files = new ArrayList<>();
         for (EmailMessage.Attachment attach : attachments) {
             String filename = attach.getName();
@@ -334,7 +338,7 @@ private List<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attac
 
             try {
                 DerivedFile df = fileManager.addDerivedFile(filename, relPath,
-                        size, cTime, crTime, aTime, mTime, true, abstractFile, "",
+                        size, cTime, crTime, aTime, mTime, true, messageArtifact, "",
                         EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType);
                 files.add(df);
             } catch (TskCoreException ex) {
@@ -356,7 +360,8 @@ private List<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attac
      * @param abstractFile
      */
     @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
-    private void addArtifact(EmailMessage email, AbstractFile abstractFile) {
+    private BlackboardArtifact addArtifact(EmailMessage email, AbstractFile abstractFile) {
+        BlackboardArtifact bbart = null;
         List<BlackboardAttribute> bbattributes = new ArrayList<>();
         String to = email.getRecipients();
         String cc = email.getCc();
@@ -414,12 +419,9 @@ private void addArtifact(EmailMessage email, AbstractFile abstractFile) {
         if (rtf.isEmpty() == false) {
             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, EmailParserModuleFactory.getModuleName(), rtf));
         }
-      
-       
-        
 
         try {
-            BlackboardArtifact bbart;
+            
             bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG);
             bbart.addAttributes(bbattributes);
 
@@ -433,6 +435,8 @@ private void addArtifact(EmailMessage email, AbstractFile abstractFile) {
         } catch (TskCoreException ex) {
             logger.log(Level.WARNING, null, ex);
         }
+
+        return bbart;
     }
 
     void postErrorMessage(String subj, String details) {