diff --git a/.gitignore b/.gitignore
index 9949c8b6fcd8b26768b56289d4aee2d87130d4b7..43671307d1631566631f876f1aa1aa7afa97e872 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 /*/build/
 */nbproject/private/*
 /nbproject/private/*
+/apidiff_output/
 
 /Core/release/
 /Core/src/org/sleuthkit/autopsy/coreutils/Version.properties
diff --git a/Core/build.xml b/Core/build.xml
index 40b8e966a3777859f302ef93dfa348e252a949ca..44b627fb9d64ba632a24208db09219987062d80c 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -31,6 +31,17 @@
     </target>
 
     <target name="get-thirdparty-dependencies" description="get third-party dependencies"> 
+        <!--
+            Copy netbeans localization jars: 
+            This contains jars provided in Netbeans 8 RCP that provide localization bundles.  
+            They do not appear to be included in Netbeans >= 9.  
+            See VIK-7434 for more information.
+        -->
+        <mkdir dir="${modules.dir}/locale"/>
+        <copy todir="${modules.dir}/locale" >
+            <fileset dir="${thirdparty.dir}/NetbeansLocalization"/>
+        </copy>
+
         <!--Copy photorec to release-->
         <copy todir="${basedir}/release/photorec_exec" >
             <fileset dir="${thirdparty.dir}/photorec_exec"/>
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 7dc5c4b32aecbf3b11858a41973288a8cf19c38a..fceb64d40680dd858033f6e728da01ef476df8f5 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -83,7 +83,6 @@ file.reference.jgraphx-4.1.0.jar=release\\modules\\ext\\jgraphx-4.1.0.jar
 file.reference.jline-0.9.94.jar=release\\modules\\ext\\jline-0.9.94.jar
 file.reference.jsoup-1.10.3.jar=release\\modules\\ext\\jsoup-1.10.3.jar
 file.reference.jsr305-3.0.2.jar=release\\modules\\ext\\jsr305-3.0.2.jar
-file.reference.junit-3.8.1.jar=release\\modules\\ext\\junit-3.8.1.jar
 file.reference.jutf7-1.0.0.jar=release\\modules\\ext\\jutf7-1.0.0.jar
 file.reference.jxmapviewer2-2.4.jar=release\\modules\\ext\\jxmapviewer2-2.4.jar
 file.reference.jython-standalone-2.7.0.jar=release\\modules\\ext\\jython-standalone-2.7.0.jar
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index d59036a1f240f2fab155d45408aff05aca29a17f..ab23992798a1a3314cd7faeb8467313845dd3950 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -737,10 +737,6 @@
                 <runtime-relative-path>ext/jai_imageio-1.1.jar</runtime-relative-path>
                 <binary-origin>release\modules\ext\jai_imageio-1.1.jar</binary-origin>
             </class-path-extension>
-            <class-path-extension>
-                <runtime-relative-path>ext/junit-3.8.1.jar</runtime-relative-path>
-                <binary-origin>release\modules\ext\junit-3.8.1.jar</binary-origin>
-            </class-path-extension>
             <class-path-extension>
                 <runtime-relative-path>ext/curator-client-2.8.0.jar</runtime-relative-path>
                 <binary-origin>release\modules\ext\curator-client-2.8.0.jar</binary-origin>
diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
index a2feedc54f53908c91da96431f1022a3a1bad4b6..4ca72069dca76ee239057f94c77353dfc9912505 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties
@@ -45,3 +45,4 @@ OpenPythonModulesFolderAction.actionName.text=Python Plugins
 OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0}
 CTL_OpenPythonModulesFolderAction=Python Plugins
 GetTagNameAndCommentDialog.tagCombo.toolTipText=Select tag to use
+CTL_ExitAction=Exit
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
index 507e079cad813dd625313067fca7fbf51159f4b1..5c9a0ea3ac61cd0bd2a723af69b2b5bc2236013d 100755
--- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED
@@ -96,3 +96,4 @@ OpenPythonModulesFolderAction.actionName.text=Python Plugins
 OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0}
 CTL_OpenPythonModulesFolderAction=Python Plugins
 GetTagNameAndCommentDialog.tagCombo.toolTipText=Select tag to use
+CTL_ExitAction=Exit
diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
index 7240afca92f17a26266d122a46a1d83dab202225..31162cc67bf3da7f76999d1cc76e48c76250c072 100644
--- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
+++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java
@@ -40,7 +40,7 @@
  * The action associated with the Case/Exit menu item. It closes the current
  * case, if any, and shuts down the application.
  */
-@ActionRegistration(displayName = "Exit", iconInMenu = true)
+@ActionRegistration(displayName = "#CTL_ExitAction", iconInMenu = true)
 @ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999)
 @ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case")
 final public class ExitAction implements ActionListener {
diff --git a/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED
index d9811e349af4858c24b21530b1b1c3866badee5d..de7c28c9484b69eb6df5a9c4470c6321e5a45814 100644
--- a/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED
@@ -1,3 +1,4 @@
 CTL_ResetWindowsAction=Reset Windows
-ResetWindowAction.confirm.text=The program will close and restart to perform the resetting of window locations.\n\nAre you sure you want to reset all window locations?
-ResetWindowAction.confirm.title=Reset Windows
+ResetWindowAction.caseCloseFailure.text=Unable to close the current case, the software will restart and the windows locations will reset the next time the software is closed.
+ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, the software will restart and the windows locations will reset but the current case will not be opened upon restart.
+ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. Are you sure you want to restart the software to reset all window locations?
diff --git a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
index 36a9b915df2c36281d8a727414ce08edb4c87505..c5298bc64e848d082a5b4d5dc45c684871dc92b1 100644
--- a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
+++ b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java
@@ -20,8 +20,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.logging.Level;
-import javax.swing.JOptionPane;
 import javax.swing.SwingUtilities;
 import org.apache.commons.io.FileUtils;
 import org.openide.LifecycleManager;
@@ -32,9 +32,10 @@
 import org.openide.util.HelpCtx;
 import org.openide.util.NbBundle;
 import org.openide.util.actions.CallableSystemAction;
-import org.openide.windows.WindowManager;
 import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.CaseActionException;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
 import org.sleuthkit.autopsy.coreutils.PlatformUtil;
 
 /**
@@ -51,39 +52,66 @@ public final class ResetWindowsAction extends CallableSystemAction {
     private static final String DISPLAY_NAME = Bundle.CTL_ResetWindowsAction();
     private static final long serialVersionUID = 1L;
     private final static Logger logger = Logger.getLogger(ResetWindowsAction.class.getName());
+    private final static String WINDOWS2LOCAL = "Windows2Local";
+    private final static String CASE_TO_REOPEN_FILE = "caseToOpen.txt"; 
 
     @Override
     public boolean isEnabled() {
-        return !Case.isCaseOpen();
+        return true;
     }
 
-    @NbBundle.Messages({"ResetWindowAction.confirm.title=Reset Windows",
-        "ResetWindowAction.confirm.text=The program will close and restart to perform the resetting of window locations.\n\nAre you sure you want to reset all window locations?"})
+    @NbBundle.Messages({"ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. "
+        + "If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. "
+        + "Are you sure you want to restart the software to reset all window locations?",
+        "ResetWindowAction.caseCloseFailure.text=Unable to close the current case, "
+        + "the software will restart and the windows locations will reset the next time the software is closed.",
+        "ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, "
+        + "the software will restart and the windows locations will reset but the current case will not be opened upon restart."})
 
     @Override
     public void performAction() {
         SwingUtilities.invokeLater(() -> {
-            int response = JOptionPane.showConfirmDialog(
-                    WindowManager.getDefault().getMainWindow(),
-                    Bundle.ResetWindowAction_confirm_text(),
-                    Bundle.ResetWindowAction_confirm_title(),
-                    JOptionPane.YES_NO_OPTION,  JOptionPane.PLAIN_MESSAGE);
-            if (response == JOptionPane.YES_OPTION) {
+            boolean response = MessageNotifyUtil.Message.confirm(Bundle.ResetWindowAction_confirm_text());
+            if (response) {
+                //adding the shutdown hook, closing the current case, and marking for restart can be re-ordered if slightly different behavior is desired
                 Runtime.getRuntime().addShutdownHook(new Thread() {
                     @Override
                     public void run() {
                         try {
-                            FileUtils.deleteDirectory(new File(PlatformUtil.getUserConfigDirectory() + File.separator + "Windows2Local"));
+                            FileUtils.deleteDirectory(new File(PlatformUtil.getUserConfigDirectory() + File.separator + WINDOWS2LOCAL));
                         } catch (IOException ex) {
-                            logger.log(Level.WARNING, "Unable to delete config directory, window locations will not be reset.", ex);
+                            //While we would like the user to be aware of this in the unlikely event that the directory can not be deleted
+                            //Because our deletion is being attempted in a shutdown hook I don't know that we can pop up UI elements during the shutdown proces
+                            logger.log(Level.SEVERE, "Unable to delete config directory, window locations will not be reset. To manually reset the windows please delete the following directory while the software is closed. " + PlatformUtil.getUserConfigDirectory() + File.separator + "Windows2Local", ex);
                         }
                     }
                 });
-                LifecycleManager.getDefault().markForRestart();
-                LifecycleManager.getDefault().exit();
+                try {
+                    if (Case.isCaseOpen()) {
+                        String caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath().toString();
+                        File caseToOpenFile = new File(ResetWindowsAction.getCaseToReopenFilePath());
+                        Charset encoding = null;  //prevents writeStringToFile from having ambiguous arguments
+                        FileUtils.writeStringToFile(caseToOpenFile, caseMetadataFilePath, encoding);
+                        Case.closeCurrentCase();
+                    }
+                    // The method markForRestart can not be undone once it is called.
+                    LifecycleManager.getDefault().markForRestart();
+                    //we need to call exit last 
+                    LifecycleManager.getDefault().exit();
+                } catch (CaseActionException ex) {
+                    logger.log(Level.WARNING, Bundle.ResetWindowAction_caseCloseFailure_text(), ex);
+                    MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseCloseFailure_text(), MessageNotifyUtil.MessageType.ERROR);
+                } catch (IOException ex) {
+                    logger.log(Level.WARNING, Bundle.ResetWindowAction_caseSaveMetadata_text(), ex);
+                    MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseSaveMetadata_text(), MessageNotifyUtil.MessageType.ERROR);
+                }
             }
         });
     }
+    
+    public static String getCaseToReopenFilePath(){
+        return PlatformUtil.getUserConfigDirectory() + File.separator + CASE_TO_REOPEN_FILE;
+    }
 
     /**
      * Set this action to be enabled/disabled
@@ -91,6 +119,7 @@ public void run() {
      * @param value whether to enable this action or not
      */
     @Override
+
     public void setEnabled(boolean value) {
         super.setEnabled(value);
     }
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index f3dfe72020876c8ab03e4e5a8d04a88087de1471..1a6186b2c23dea93f8ca5ed615d2af5b5c5887f8 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -4,7 +4,6 @@ CTL_CaseCloseAct=Close Case
 CTL_CaseNewAction=New Case
 CTL_CaseDetailsAction=Case Details
 CTL_CaseDeleteAction=Delete Case
-Menu/Case/OpenRecentCase=Open Recent Case
 CTL_CaseDeleteAction=Delete Case
 OpenIDE-Module-Name=Case
 NewCaseVisualPanel1.caseNameLabel.text_1=Case Name:
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
index f731913af50ad9fbf6f9682334b7a1665a0aa480..528d3a50883e09c4b05254b8d14b9e691356d6de 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
@@ -128,6 +128,7 @@ CTL_CaseCloseAct=Close Case
 CTL_CaseNewAction=New Case
 CTL_CaseDetailsAction=Case Details
 CTL_CaseDeleteAction=Delete Case
+CTL_CaseDeleteAction=Delete Case
 CTL_CaseOpenAction=Open Case
 CTL_UnpackagePortableCaseAction=Unpack and Open Portable Case
 DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal.
@@ -186,8 +187,6 @@ LogicalEvidenceFilePanel.pathValidation.getOpenCase.Error=Warning: Exception whi
 LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=Only files with the .l01 file extension are supported here.
 LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01)
 LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders
-Menu/Case/OpenRecentCase=Open Recent Case
-CTL_CaseDeleteAction=Delete Case
 OpenIDE-Module-Name=Case
 NewCaseVisualPanel1.caseNameLabel.text_1=Case Name:
 NewCaseVisualPanel1.caseDirLabel.text=Base Directory:
@@ -346,6 +345,12 @@ RecentCases.getName.text=Clear Recent Cases
 RecentItems.openRecentCase.msgDlg.text=Case {0} no longer exists.
 SelectDataSourceProcessorPanel.name.text=Select Data Source Type
 StartupWindow.title.text=Welcome
+# {0} - autFilePath
+StartupWindowProvider.openCase.cantOpen=Unable to open previously open case with metadata file: {0}
+# {0} - reOpenFilePath
+StartupWindowProvider.openCase.deleteOpenFailure=Unable to open or delete file containing path {0} to previously open case. The previous case will not be opened.
+# {0} - autFilePath
+StartupWindowProvider.openCase.noFile=Unable to open previously open case because metadata file not found at: {0}
 UnpackagePortableCaseDialog.title.text=Unpackage Portable Case
 UnpackagePortableCaseDialog.UnpackagePortableCaseDialog.extensions=Portable case package (.zip, .zip.001)
 UnpackagePortableCaseDialog.validatePaths.badExtension=File extension must be .zip or .zip.001
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 3ffae2bd0f528a14e01a076fe30f66327812fe6f..ddf8cc22b968873031287ee7fa08646b123984af 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -62,6 +62,7 @@
 import javax.annotation.concurrent.ThreadSafe;
 import javax.swing.JOptionPane;
 import javax.swing.SwingUtilities;
+import org.apache.commons.lang3.StringUtils;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle;
 import org.openide.util.NbBundle.Messages;
@@ -157,9 +158,10 @@
  */
 public class Case {
 
-    private static final String CASE_TEMP_DIR = Case.class.getSimpleName();
     private static final int CASE_LOCK_TIMEOUT_MINS = 1;
     private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
+    private static final String APP_NAME = UserPreferences.getAppName();
+    private static final String TEMP_FOLDER = "Temp";
     private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
     private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
     private static final String CACHE_FOLDER = "Cache"; //NON-NLS
@@ -496,35 +498,35 @@ public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
                         event.getArtifacts(artifactType)));
             }
         }
-        
-        @Subscribe 
+
+        @Subscribe
         public void publishOsAccountAddedEvent(TskEvent.OsAccountsAddedTskEvent event) {
-            for(OsAccount account: event.getOsAcounts()) {
+            for (OsAccount account : event.getOsAcounts()) {
                 eventPublisher.publish(new OsAccountAddedEvent(account));
             }
         }
-        
-        @Subscribe 
+
+        @Subscribe
         public void publishOsAccountChangedEvent(TskEvent.OsAccountsChangedTskEvent event) {
-            for(OsAccount account: event.getOsAcounts()) {
+            for (OsAccount account : event.getOsAcounts()) {
                 eventPublisher.publish(new OsAccountChangedEvent(account));
             }
         }
-        
-        @Subscribe 
+
+        @Subscribe
         public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) {
-            for(Long accountId: event.getOsAcountObjectIds()) {
+            for (Long accountId : event.getOsAcountObjectIds()) {
                 eventPublisher.publish(new OsAccountDeletedEvent(accountId));
             }
         }
 
         /**
-         * Publishes an autopsy event from the sleuthkit HostAddedEvent 
+         * Publishes an autopsy event from the sleuthkit HostAddedEvent
          * indicating that hosts have been created.
          *
          * @param event The sleuthkit event for the creation of hosts.
          */
-        @Subscribe 
+        @Subscribe
         public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
             eventPublisher.publish(new HostsAddedEvent(
                     event == null ? Collections.emptyList() : event.getHosts()));
@@ -535,8 +537,8 @@ public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) {
          * indicating that hosts have been updated.
          *
          * @param event The sleuthkit event for the updating of hosts.
-         */        
-        @Subscribe 
+         */
+        @Subscribe
         public void publishHostsChangedEvent(TskEvent.HostsChangedTskEvent event) {
             eventPublisher.publish(new HostsChangedEvent(
                     event == null ? Collections.emptyList() : event.getHosts()));
@@ -547,32 +549,32 @@ public void publishHostsChangedEvent(TskEvent.HostsChangedTskEvent event) {
          * indicating that hosts have been deleted.
          *
          * @param event The sleuthkit event for the deleting of hosts.
-         */    
-        @Subscribe 
+         */
+        @Subscribe
         public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) {
             eventPublisher.publish(new HostsRemovedEvent(
                     event == null ? Collections.emptyList() : event.getHosts()));
         }
 
         /**
-         * Publishes an autopsy event from the sleuthkit PersonAddedEvent 
+         * Publishes an autopsy event from the sleuthkit PersonAddedEvent
          * indicating that persons have been created.
          *
          * @param event The sleuthkit event for the creation of persons.
          */
-        @Subscribe 
+        @Subscribe
         public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) {
             eventPublisher.publish(new PersonsAddedEvent(
                     event == null ? Collections.emptyList() : event.getPersons()));
         }
 
         /**
-         * Publishes an autopsy event from the sleuthkit PersonChangedEvent 
+         * Publishes an autopsy event from the sleuthkit PersonChangedEvent
          * indicating that persons have been updated.
          *
          * @param event The sleuthkit event for the updating of persons.
-         */        
-        @Subscribe 
+         */
+        @Subscribe
         public void publishPersonsChangedEvent(TskEvent.PersonsChangedTskEvent event) {
             eventPublisher.publish(new PersonsChangedEvent(
                     event == null ? Collections.emptyList() : event.getPersons()));
@@ -583,8 +585,8 @@ public void publishPersonsChangedEvent(TskEvent.PersonsChangedTskEvent event) {
          * indicating that persons have been deleted.
          *
          * @param event The sleuthkit event for the deleting of persons.
-         */    
-        @Subscribe 
+         */
+        @Subscribe
         public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) {
             eventPublisher.publish(new PersonsDeletedEvent(
                     event == null ? Collections.emptyList() : event.getPersons()));
@@ -1469,6 +1471,13 @@ public String getOutputDirectory() {
         return hostPath.toString();
     }
 
+    /**
+     * @return A subdirectory of java.io.tmpdir.
+     */
+    private Path getBaseSystemTempPath() {
+        return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName());
+    }
+
     /**
      * Gets the full path to the temp directory for this case, creating it if it
      * does not exist.
@@ -1476,7 +1485,45 @@ public String getOutputDirectory() {
      * @return The temp subdirectory path.
      */
     public String getTempDirectory() {
-        return UserMachinePreferences.getTempDirectory();
+        // NOTE: UserPreferences may also be affected by changes in this method.
+        // See JIRA-7505 for more information.
+        Path basePath = null;
+        // get base temp path for the case based on user preference
+        switch (UserMachinePreferences.getTempDirChoice()) {
+            case CUSTOM:
+                String customDirectory = UserMachinePreferences.getCustomTempDirectory();
+                basePath = (StringUtils.isBlank(customDirectory))
+                        ? null
+                        : Paths.get(customDirectory, APP_NAME, getName());
+                break;
+            case CASE:
+                basePath = Paths.get(getCaseDirectory());
+                break;
+            case SYSTEM:
+            default:
+                // at this level, if the case directory is specified for a temp
+                // directory, return the system temp directory instead.
+                basePath = getBaseSystemTempPath();
+                break;
+        }
+
+        basePath = basePath == null ? getBaseSystemTempPath() : basePath;
+
+        // get sub directories based on multi user vs. single user
+        Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType()))
+                ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER)
+                : Paths.get(TEMP_FOLDER);
+
+        File caseTempDir = basePath
+                .resolve(caseRelPath)
+                .toFile();
+
+        // ensure directory exists
+        if (!caseTempDir.exists()) {
+            caseTempDir.mkdirs();
+        }
+
+        return caseTempDir.getAbsolutePath();
     }
 
     /**
@@ -1923,7 +1970,7 @@ public void deleteReports(Collection<? extends Report> reports) throws TskCoreEx
      *
      * @return A CaseMetaData object.
      */
-    CaseMetadata getMetadata() {
+    public CaseMetadata getMetadata() {
         return metadata;
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
index 5f7ffe9ea7837d82ad9b3f33a2a619604d74dcfb..96f9899dae4273f0e79fe7c96028c7d0cd4ca09f 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -218,7 +218,7 @@ public static Path getCaseMetadataFilePath(Path directoryPath) {
      *
      * @return The path to the metadata file
      */
-    Path getFilePath() {
+    public Path getFilePath() {
         return metadataFilePath;
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
index 07a88ee07f9b7202f70b86f09c9e3c272f6bdee6..9b174f28f73644c2a78929bb1bc8499b57c8a932 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java
@@ -284,10 +284,10 @@ public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourc
             ingestStream = IngestManager.getInstance().openIngestStream(image, settings);
         } catch (TskCoreException ex) {
             logger.log(Level.SEVERE, "Error starting ingest modules", ex);
-            final List<String> errors = new ArrayList<>();
-            errors.add(ex.getMessage());
-            callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
-            return;
+            // There was an error with ingest, but the data source has already been added
+            // so proceed with the defaultIngestStream. Code in openIngestStream
+            // should have caused a dialog to popup with the errors.
+            ingestStream = new DefaultIngestStream();
         }
 
         doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack);
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java
index 9b62bdb0e0dc12cd75387f27ca0a848bac704b97..821beea6b3f975c3c4a064e4f99164115cbd0f5b 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java
@@ -18,12 +18,18 @@
  */
 package org.sleuthkit.autopsy.casemodule;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.logging.Level;
-import org.netbeans.spi.sendopts.OptionProcessor;
+import javax.swing.SwingUtilities;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.apputils.ResetWindowsAction;
 import org.sleuthkit.autopsy.commandlineingest.CommandLineIngestManager;
 import org.sleuthkit.autopsy.commandlineingest.CommandLineOpenCaseManager;
 import org.sleuthkit.autopsy.commandlineingest.CommandLineOptionProcessor;
@@ -61,15 +67,22 @@ public static StartupWindowProvider getInstance() {
         return instance;
     }
 
+    @NbBundle.Messages({
+        "# {0} - autFilePath",
+        "StartupWindowProvider.openCase.noFile=Unable to open previously open case because metadata file not found at: {0}",
+        "# {0} - reOpenFilePath",
+        "StartupWindowProvider.openCase.deleteOpenFailure=Unable to open or delete file containing path {0} to previously open case. The previous case will not be opened.",
+        "# {0} - autFilePath",
+        "StartupWindowProvider.openCase.cantOpen=Unable to open previously open case with metadata file: {0}"})
     private void init() {
         if (startupWindowToUse == null) {
             // first check whether we are running from command line
             if (isRunningFromCommandLine()) {
-                
+
                 String defaultArg = getDefaultArgument();
-                if(defaultArg != null) {
-                   new CommandLineOpenCaseManager(defaultArg).start(); 
-                   return;
+                if (defaultArg != null) {
+                    new CommandLineOpenCaseManager(defaultArg).start();
+                    return;
                 } else {
                     // Autopsy is running from command line
                     logger.log(Level.INFO, "Running from command line"); //NON-NLS
@@ -83,36 +96,41 @@ private void init() {
             if (RuntimeProperties.runningWithGUI()) {
                 checkSolr();
             }
-
             //discover the registered windows
             Collection<? extends StartupWindowInterface> startupWindows
                     = Lookup.getDefault().lookupAll(StartupWindowInterface.class);
 
             int windowsCount = startupWindows.size();
-            if (windowsCount == 1) {
-                startupWindowToUse = startupWindows.iterator().next();
-                logger.log(Level.INFO, "Will use the default startup window: " + startupWindowToUse.toString()); //NON-NLS
-            } else if (windowsCount == 2) {
-                //pick the non default one
-                Iterator<? extends StartupWindowInterface> it = startupWindows.iterator();
-                while (it.hasNext()) {
-                    StartupWindowInterface window = it.next();
-                    if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) {
-                        startupWindowToUse = window;
-                        logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS
-                        break;
+            switch (windowsCount) {
+                case 1:
+                    startupWindowToUse = startupWindows.iterator().next();
+                    logger.log(Level.INFO, "Will use the default startup window: {0}", startupWindowToUse.toString()); //NON-NLS
+                    break;
+                case 2: {
+                    //pick the non default one
+                    Iterator<? extends StartupWindowInterface> it = startupWindows.iterator();
+                    while (it.hasNext()) {
+                        StartupWindowInterface window = it.next();
+                        if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) {
+                            startupWindowToUse = window;
+                            logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS
+                            break;
+                        }
                     }
+                    break;
                 }
-            } else {
-                // select first non-Autopsy start up window
-                Iterator<? extends StartupWindowInterface> it = startupWindows.iterator();
-                while (it.hasNext()) {
-                    StartupWindowInterface window = it.next();
-                    if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) {
-                        startupWindowToUse = window;
-                        logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS
-                        break;
+                default: {
+                    // select first non-Autopsy start up window
+                    Iterator<? extends StartupWindowInterface> it = startupWindows.iterator();
+                    while (it.hasNext()) {
+                        StartupWindowInterface window = it.next();
+                        if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) {
+                            startupWindowToUse = window;
+                            logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS
+                            break;
+                        }
                     }
+                    break;
                 }
             }
 
@@ -121,6 +139,45 @@ private void init() {
                 startupWindowToUse = new org.sleuthkit.autopsy.casemodule.StartupWindow();
             }
         }
+        File openPreviousCaseFile = new File(ResetWindowsAction.getCaseToReopenFilePath());
+
+        if (openPreviousCaseFile.exists()) {
+            //do actual opening on another thread
+            new Thread(() -> {
+                String caseFilePath = "";
+                String unableToOpenMessage = null;
+                try {
+                    //avoid readFileToString having ambiguous arguments 
+                    Charset encoding = null;
+                    caseFilePath = FileUtils.readFileToString(openPreviousCaseFile, encoding);
+                    if (new File(caseFilePath).exists()) {
+                        FileUtils.forceDelete(openPreviousCaseFile);
+                        //close the startup window as we attempt to open the case
+                        close();
+                        Case.openAsCurrentCase(caseFilePath);
+
+                    } else {
+                        unableToOpenMessage = Bundle.StartupWindowProvider_openCase_noFile(caseFilePath);
+                        logger.log(Level.WARNING, unableToOpenMessage);
+                    }
+                } catch (IOException ex) {
+                    unableToOpenMessage = Bundle.StartupWindowProvider_openCase_deleteOpenFailure(ResetWindowsAction.getCaseToReopenFilePath());
+                    logger.log(Level.WARNING, unableToOpenMessage, ex);
+                } catch (CaseActionException ex) {
+                    unableToOpenMessage = Bundle.StartupWindowProvider_openCase_cantOpen(caseFilePath);
+                    logger.log(Level.WARNING, unableToOpenMessage, ex);
+                }
+
+                if (RuntimeProperties.runningWithGUI() && !StringUtils.isBlank(unableToOpenMessage)) {
+                    final String message = unableToOpenMessage;
+                    SwingUtilities.invokeLater(() -> {
+                        MessageNotifyUtil.Message.warn(message);
+                        //the case was not opened restore the startup window
+                        open();
+                    });
+                }
+            }).start();
+        }
     }
 
     private void checkSolr() {
@@ -147,9 +204,9 @@ private void checkSolr() {
      * @return True if running from command line, false otherwise
      */
     private boolean isRunningFromCommandLine() {
-        
+
         CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class);
-         if(processor != null) {
+        if (processor != null) {
             return processor.isRunFromCommandLine();
         }
         return false;
@@ -157,12 +214,12 @@ private boolean isRunningFromCommandLine() {
 
     /**
      * Get the default argument from the CommandLineOptionProcessor.
-     * 
-     * @return If set, the default argument otherwise null. 
+     *
+     * @return If set, the default argument otherwise null.
      */
-    private String getDefaultArgument() {  
+    private String getDefaultArgument() {
         CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class);
-        if(processor != null) {
+        if (processor != null) {
             return processor.getDefaultArgument();
         }
         return null;
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED
index a7b597640e2b90d579d80c798594a4457fe9072d..f43b438b2c3974a017b73da2498650867e2f51fb 100755
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED
@@ -12,7 +12,7 @@ CentralRepoDbChoice.PostgreSQL.Text=Custom PostgreSQL
 CentralRepoDbChoice.PostgreSQL_Multiuser.Text=PostgreSQL using multi-user settings
 CentralRepoDbChoice.Sqlite.Text=SQLite
 CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database.
-CentralRepositoryService.progressMsg.updatingSchema=Updating schema...
+CentralRepositoryService.progressMsg.updatingSchema=Checking for schema updates...
 CentralRepositoryService.progressMsg.waitingForListeners=Finishing adding data to central repository database....
 CentralRepositoryService.serviceName=Central Repository Service
 CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java
old mode 100644
new mode 100755
index 4ed13dc82afdae3423a5cba29bfd5dbeb7d3b125..83dc61d06df60f2f9aa6d616c5b4a72129ca119d
--- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java
+++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java
@@ -47,7 +47,7 @@ public String getServiceName() {
     }
 
     @NbBundle.Messages({
-        "CentralRepositoryService.progressMsg.updatingSchema=Updating schema..."
+        "CentralRepositoryService.progressMsg.updatingSchema=Checking for schema updates..."
     })
     @Override
     public void openCaseResources(CaseContext context) throws AutopsyServiceException {
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java
index 005689d8a87aa593987412f9fec3630828933e0d..1c4c538ea9be8075d8fc0316c26a045038f67706 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageViewer.java
@@ -49,6 +49,7 @@
 import org.openide.nodes.Node;
 import org.openide.nodes.Node.Property;
 import org.openide.nodes.Node.PropertySet;
+import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle.Messages;
 import org.sleuthkit.autopsy.communications.ModifiableProxyLookup;
@@ -146,14 +147,23 @@ public JPanel getPanel() {
 
     @Override
     public void setSelectionInfo(SelectionInfo info) {
-        currentSelectionInfo = info;
+        if(currentSelectionInfo != null && currentSelectionInfo.equals(info)) {
+            try {
+                // Clear the currently selected thread so that clicks can 
+                // be registered.
+                rootTablePane.getExplorerManager().setSelectedNodes(new Node[0]);
+            } catch (PropertyVetoException ex) {
+                logger.log(Level.WARNING, "Error clearing the selected node", ex);
+            }
+        } else {
+            currentSelectionInfo = info;
+            rootMessageFactory.refresh(info);
+        }
 
         currentPanel = rootTablePane;
 
         CardLayout layout = (CardLayout) this.getLayout();
-        layout.show(this, "threads");
-
-        rootMessageFactory.refresh(info);
+        layout.show(this, "threads"); 
     }
 
     @Override
diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
index b106a1a123b8ce1c9087c7e50abefe99f27a75c8..0f6042fc60f6417b46d443821783a0d9b9e7d0fb 100644
--- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties
@@ -27,3 +27,7 @@ ServicesMonitor.remoteKeywordSearch.displayName.text=Multi-user keyword search s
 ServicesMonitor.messaging.displayName.text=Messaging service
 ServicesMonitor.databaseConnectionInfo.error.msg=Error accessing case database connection info
 ServicesMonitor.messagingService.connErr.text=Error accessing messaging service connection info
+Actions/Case=Case
+Menu/Case=Case
+Toolbars/Case=Case
+Menu/Case/OpenRecentCase=Open Recent Case
\ No newline at end of file
diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED
index cd9d1002cd7d3b6cc03aa1a5124a61e42480260a..1d50092e80fda81b0535fbb9f409c862af79e8c0 100755
--- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED
@@ -31,3 +31,7 @@ ServicesMonitor.remoteKeywordSearch.displayName.text=Multi-user keyword search s
 ServicesMonitor.messaging.displayName.text=Messaging service
 ServicesMonitor.databaseConnectionInfo.error.msg=Error accessing case database connection info
 ServicesMonitor.messagingService.connErr.text=Error accessing messaging service connection info
+Actions/Case=Case
+Menu/Case=Case
+Toolbars/Case=Case
+Menu/Case/OpenRecentCase=Open Recent Case
diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
index 395b8cd830114a23661c04010e2152104f468142..85b7b35cbcc0c44b9ff525d186f456d6358160b5 100644
--- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
+++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java
@@ -18,12 +18,14 @@
  */
 package org.sleuthkit.autopsy.core;
 
+import java.io.File;
 import java.nio.file.Paths;
 import org.sleuthkit.autopsy.coreutils.TextConverter;
 import java.util.prefs.BackingStoreException;
 import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo;
 import java.util.prefs.PreferenceChangeListener;
 import java.util.prefs.Preferences;
+import org.apache.commons.lang3.StringUtils;
 import org.openide.util.NbPreferences;
 import org.python.icu.util.TimeZone;
 import org.sleuthkit.autopsy.machinesettings.UserMachinePreferences;
@@ -93,7 +95,8 @@ public final class UserPreferences {
     private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress";
     private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath";
     private static final String HEALTH_MONITOR_REPORT_PATH = "HealthMonitorReportPath";
-    
+    private static final String TEMP_FOLDER = "Temp";
+
     // Prevent instantiation.
     private UserPreferences() {
     }
@@ -348,27 +351,27 @@ public static String getIndexingServerPort() {
     public static void setIndexingServerPort(int port) {
         preferences.putInt(SOLR8_SERVER_PORT, port);
     }
-    
+
     public static String getSolr4ServerHost() {
         return preferences.get(SOLR4_SERVER_HOST, "");
     }
 
     public static void setSolr4ServerHost(String hostName) {
         preferences.put(SOLR4_SERVER_HOST, hostName);
-    }    
-    
+    }
+
     public static String getSolr4ServerPort() {
         return preferences.get(SOLR4_SERVER_PORT, "");
     }
 
     public static void setSolr4ServerPort(String port) {
         preferences.put(SOLR4_SERVER_PORT, port);
-    }    
-    
+    }
+
     public static String getZkServerHost() {
         return preferences.get(ZK_SERVER_HOST, "");
     }
-    
+
     public static void setZkServerHost(String hostName) {
         preferences.put(ZK_SERVER_HOST, hostName);
     }
@@ -380,7 +383,7 @@ public static String getZkServerPort() {
     public static void setZkServerPort(String port) {
         preferences.put(ZK_SERVER_PORT, port);
     }
-    
+
     public static void setTextTranslatorName(String textTranslatorName) {
         preferences.put(TEXT_TRANSLATOR_NAME, textTranslatorName);
     }
@@ -388,14 +391,14 @@ public static void setTextTranslatorName(String textTranslatorName) {
     public static String getTextTranslatorName() {
         return preferences.get(TEXT_TRANSLATOR_NAME, null);
     }
-    
+
     public static void setUseOcrInTranslation(boolean enableOcr) {
         preferences.putBoolean(OCR_TRANSLATION_ENABLED, enableOcr);
     }
 
     public static boolean getUseOcrInTranslation() {
         return preferences.getBoolean(OCR_TRANSLATION_ENABLED, true);
-    }    
+    }
 
     /**
      * Persists message service connection info.
@@ -536,10 +539,11 @@ public static void setLogFileCount(int count) {
     }
 
     /**
-     * Get the maximum JVM heap size (in MB) for the embedded Solr server. The returned value
-     * depends on the platform (64bit vs 32bit).
+     * Get the maximum JVM heap size (in MB) for the embedded Solr server. The
+     * returned value depends on the platform (64bit vs 32bit).
      *
-     * @return Saved value or default (2 GB for 64bit platforms, 512MB for 32bit)
+     * @return Saved value or default (2 GB for 64bit platforms, 512MB for
+     *         32bit)
      */
     public static int getMaxSolrVMSize() {
         if (PlatformUtil.is64BitJVM()) {
@@ -594,20 +598,21 @@ public static void setExternalHexEditorPath(String executablePath) {
     public static String getExternalHexEditorPath() {
         return preferences.get(EXTERNAL_HEX_EDITOR_PATH, Paths.get("C:", "Program Files", "HxD", "HxD.exe").toString());
     }
-    
+
     /**
      * Set the geolocation tile server option.
-     * 
-     * @param option 
+     *
+     * @param option
      */
     public static void setGeolocationTileOption(int option) {
         preferences.putInt(GEO_TILE_OPTION, option);
     }
 
     /**
-     * Retrieves the Geolocation tile option.  If not found, the value will
+     * Retrieves the Geolocation tile option. If not found, the value will
      * default to 0.
-     * @return 
+     *
+     * @return
      */
     public static int getGeolocationtTileOption() {
         return preferences.getInt(GEO_TILE_OPTION, 0);
@@ -615,8 +620,8 @@ public static int getGeolocationtTileOption() {
 
     /**
      * Sets the path to the OSM tile zip file.
-     * 
-     * @param absolutePath 
+     *
+     * @param absolutePath
      */
     public static void setGeolocationOsmZipPath(String absolutePath) {
         preferences.put(GEO_OSM_TILE_ZIP_PATH, absolutePath);
@@ -625,7 +630,7 @@ public static void setGeolocationOsmZipPath(String absolutePath) {
     /**
      * Retrieves the path for the OSM tile zip file or returns empty string if
      * none was found.
-     * 
+     *
      * @return Path to zip file
      */
     public static String getGeolocationOsmZipPath() {
@@ -633,9 +638,10 @@ public static String getGeolocationOsmZipPath() {
     }
 
     /**
-     * Sets the address of geolocation window user defined OSM server data source.
-     * 
-     * @param address 
+     * Sets the address of geolocation window user defined OSM server data
+     * source.
+     *
+     * @param address
      */
     public static void setGeolocationOsmServerAddress(String address) {
         preferences.put(GEO_OSM_SERVER_ADDRESS, address);
@@ -643,40 +649,72 @@ public static void setGeolocationOsmServerAddress(String address) {
 
     /**
      * Retrieves the address to the OSM server or null if one was not found.
-     * 
+     *
      * @return Address of OSM server
      */
     public static String getGeolocationOsmServerAddress() {
         return preferences.get(GEO_OSM_SERVER_ADDRESS, "");
     }
-    
+
     /**
      * Sets the path for Geolocation MBTiles data source file.
-     * 
-     * @param absolutePath 
+     *
+     * @param absolutePath
      */
     public static void setGeolocationMBTilesFilePath(String absolutePath) {
         preferences.put(GEO_MBTILES_FILE_PATH, absolutePath);
     }
-    
+
     /**
      * Retrieves the path for the Geolocation MBTiles data source file.
-     * 
-     * @return Absolute path to  MBTiles file or empty string if none was found.
+     *
+     * @return Absolute path to MBTiles file or empty string if none was found.
      */
     public static String getGeolocationMBTilesFilePath() {
         return preferences.get(GEO_MBTILES_FILE_PATH, "");
     }
-    
+
+    /**
+     * @return A subdirectory of java.io.tmpdir.
+     */
+    private static File getSystemTempDirFile() {
+        return Paths.get(System.getProperty("java.io.tmpdir"), getAppName(), TEMP_FOLDER).toFile();
+    }
+
     /**
-     * Retrieves the root application temp directory.
-     * 
+     * Retrieves the application temp directory and ensures the directory
+     * exists.
+     *
      * @return The absolute path to the application temp directory.
      */
     public static String getAppTempDirectory() {
-        return UserMachinePreferences.getTempDirectory();
+        // NOTE: If this code changes, Case.getTempDirectory() should likely be checked
+        // as well.  See JIRA 7505 for more information.
+        File appTempDir = null;
+        switch (UserMachinePreferences.getTempDirChoice()) {
+            case CUSTOM:
+                String customDirectory = UserMachinePreferences.getCustomTempDirectory();
+                appTempDir = (StringUtils.isBlank(customDirectory))
+                        ? null
+                        : Paths.get(customDirectory, getAppName(), TEMP_FOLDER).toFile();
+                break;
+            case SYSTEM:
+            default:
+                // at this level, if the case directory is specified for a temp
+                // directory, return the system temp directory instead.
+                appTempDir = getSystemTempDirFile();
+                break;
+        }
+
+        appTempDir = appTempDir == null ? getSystemTempDirFile() : appTempDir;
+
+        if (!appTempDir.exists()) {
+            appTempDir.mkdirs();
+        }
+
+        return appTempDir.getAbsolutePath();
     }
-    
+
     /**
      * Set the last used health monitor report path.
      *
@@ -689,9 +727,10 @@ public static void setHealthMonitorReportPath(String reportPath) {
     /**
      * Gets the last used health monitor report path.
      *
-     * @return Last used health monitor report path. Empty string if no value has been recorded.
+     * @return Last used health monitor report path. Empty string if no value
+     *         has been recorded.
      */
     public static String getHealthMonitorReportPath() {
         return preferences.get(HEALTH_MONITOR_REPORT_PATH, "");
     }
-}
\ No newline at end of file
+}
diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml
index 923e8f5c5ea20f5fd0beb8d4662fb00793eb7641..f9f537541da6aeaafaaa7a9687189800cfd2716b 100644
--- a/Core/src/org/sleuthkit/autopsy/core/layer.xml
+++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml
@@ -40,6 +40,7 @@
     ====================================================== -->
     <folder name="Actions">
         <folder name="Case">
+            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.sleuthkit.autopsy.core.Bundle"/>
             <file name="org-sleuthkit-autopsy-casemodule-AddImageAction.instance"/>
             <file name="org-sleuthkit-autopsy-casemodule-CaseCloseAction.instance"/>
             <file name="org-sleuthkit-autopsy-casemodule-CaseNewAction.instance">
@@ -140,13 +141,14 @@
         <file name="Edit_hidden"/>
         <file name="File_hidden"/>
         <folder name="Case">
+            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.sleuthkit.autopsy.core.Bundle"/>
             <file name="org-sleuthkit-autopsy-casemodule-CaseNewAction.shadow">
                 <attr name="originalFile" stringvalue="Actions/Case/org-sleuthkit-autopsy-casemodule-CaseNewAction.instance"/>
                 <attr name="position" intvalue="100"/>
             </file>
-            <folder name="Open Recent Case">
+            <folder name="OpenRecentCase">
+                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.sleuthkit.autopsy.core.Bundle"/>
                 <attr name="position" intvalue="101"/>
-                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.sleuthkit.autopsy.casemodule.Bundle"/>
                 <file name="org-sleuthkit-autopsy-casemodule-RecentCasesAction.shadow">
                     <attr name="originalFile" stringvalue="Actions/Case/org-sleuthkit-autopsy-casemodule-RecentCases.instance"/>
                 </file>
@@ -281,7 +283,6 @@
         <folder name="Help">
             <file name="org-netbeans-core-actions-AboutAction.shadow_hidden"/>
             <file name="org-netbeans-modules-autoupdate-ui-actions-CheckForUpdatesAction.shadow_hidden"/>
-            <attr name="master-help.xml/org-sleuthkit-autopsy-corecomponents-CustomAboutAction.shadow" boolvalue="true"/>
         </folder>
     </folder>
 
@@ -378,6 +379,7 @@
         <file name="UndoRedo_hidden"/>
         <file name="File_hidden"/>
         <folder name="Case">
+            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.sleuthkit.autopsy.core.Bundle"/>
             <attr name="position" intvalue="90"/>
             <!--<file name="org-sleuthkit-autopsy-casemodule-AddImageAction.instance">
                 <attr name="delegate" newvalue="org.sleuthkit.autopsy.casemodule.AddImageAction"/>
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
index 00f0e7217acc56ad02045b8bd03bce8e73316c61..db68527f0e9ff467b0d0c134ea4c6a94c642372e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowAction.java
@@ -24,15 +24,14 @@
 import org.openide.DialogDisplayer;
 import org.openide.awt.ActionID;
 import org.openide.awt.ActionReference;
-import org.openide.awt.ActionRegistration;
 import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
 
 /**
  * Action to open custom implementation of the "About" window from the Help
  * menu.
  */
 @ActionID(id = "org.sleuthkit.autopsy.corecomponents.AboutWindowAction", category = "Help")
-@ActionRegistration(displayName = "#CTL_CustomAboutAction", iconInMenu = true, lazy = false)
 @ActionReference(path = "Menu/Help", position = 3000, separatorBefore = 2999)
 public class AboutWindowAction extends AboutAction {
 
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
index edbef370b2ed5a828299b24c4d402268c743c6b3..56ff4a0993c8b7dc8f1a464808b98be375542b4e 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
@@ -301,7 +301,7 @@ private void writeEtcConfFile() throws IOException {
     
     
     /**
-     * Values for configuration located in the /etc/*.conf file.
+     * Values for configuration located in the /etc/\*.conf file.
      */
     private static class ConfValues {
         private final String XmxVal;
@@ -335,7 +335,7 @@ String getHeapDumpPath() {
     }
     
     /**
-     * Retrieve the /etc/*.conf file values pertinent to settings.
+     * Retrieve the /etc/\*.conf file values pertinent to settings.
      * @return The conf file values.
      * @throws IOException 
      */
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
index 2deaab0d3b835dc7732e03ed492c5d7835a9b571..725d5d83d139332c4526ebbb5fc470e037d14581 100644
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties
@@ -1,6 +1,5 @@
 CTL_DataContentAction=DataContent
 CTL_DataContentTopComponent=Data Content
-CTL_CustomAboutAction=About
 OptionsCategory_Name_General=Application
 OptionsCategory_Keywords_General=Autopsy Options
 HINT_DataContentTopComponent=This is a DataContent window
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
index 16fa8d0e5fada523bc62d1a7af91626adb6dedac..dfe756c2fed5bab5ee67b0946189441e6915db9c 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED
@@ -28,7 +28,6 @@ AutopsyOptionsPanel_tempDirectoryBrowseButtonActionPerformed_onInvalidPath_descr
 AutopsyOptionsPanel_tempDirectoryBrowseButtonActionPerformed_onInvalidPath_title=Path cannot be used
 CTL_DataContentAction=DataContent
 CTL_DataContentTopComponent=Data Content
-CTL_CustomAboutAction=About
 CTL_OfflineHelpAction=Offline Autopsy Documentation
 CTL_OnlineHelpAction=Online Autopsy Documentation
 DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
index 6d7a1dc7ff7303319778c456d2b448420c3fe195..d2a7f32bf7499060696f11cadc1264822c87c390 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED
@@ -376,6 +376,10 @@ TagNode.propertySheet.origNameDisplayName=Original Name
 TagsNode.displayName.text=Tags
 TagsNode.createSheet.name.name=Name
 TagsNode.createSheet.name.displayName=Name
+UnsupportedContentNode.createSheet.name.desc=no description
+UnsupportedContentNode.createSheet.name.displayName=Name
+UnsupportedContentNode.createSheet.name.name=Name
+UnsupportedContentNode.displayName=Unsupported Content
 ViewsNode.name.text=Views
 ViewsNode.createSheet.name.name=Name
 ViewsNode.createSheet.name.displayName=Name
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
index af7eb3a249cebb22659bfd7c927112a0954bc2ee..3f6706952a39127c16053c80a90b5e91bb31d885 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java
@@ -18,6 +18,8 @@
  */
 package org.sleuthkit.autopsy.datamodel;
 
+import org.sleuthkit.autopsy.datamodel.OsAccounts.OsAccountNode;
+
 /**
  * Visitor Pattern interface that goes over Content nodes in the data source
  * area of the tree.
@@ -50,6 +52,9 @@ interface ContentNodeVisitor<T> {
     
     T visit(BlackboardArtifactNode bban);
     
+    T visit(UnsupportedContentNode ucn);
+
+    T visit(OsAccountNode bban);
 
     /**
      * Visitor with an implementable default behavior for all types. Override
@@ -122,5 +127,15 @@ public T visit(SlackFileNode sfn) {
         public T visit(BlackboardArtifactNode bban) {
             return defaultVisit(bban);
         }
+                
+        @Override
+        public T visit(UnsupportedContentNode ucn) {
+            return defaultVisit(ucn);
+        }
+        
+        @Override
+        public T visit(OsAccountNode bban) {
+            return defaultVisit(bban);
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java
index b5d808153d52b6f5eec5d1d503faac8f1b781d99..7601f4a88c0d364abcfa2648a669ca65172e207a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreateSleuthkitNodeVisitor.java
@@ -32,6 +32,7 @@
 import org.sleuthkit.datamodel.SlackFile;
 import org.sleuthkit.datamodel.SleuthkitItemVisitor;
 import org.sleuthkit.datamodel.SleuthkitVisitableItem;
+import org.sleuthkit.datamodel.UnsupportedContent;
 import org.sleuthkit.datamodel.VirtualDirectory;
 import org.sleuthkit.datamodel.Volume;
 
@@ -99,6 +100,11 @@ public AbstractContentNode<? extends Content> visit(SlackFile sf) {
     public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
         return new BlackboardArtifactNode(art);
     }
+    
+    @Override
+    public AbstractContentNode<? extends Content> visit(UnsupportedContent uc) {
+        return new UnsupportedContentNode(uc);
+    }
 
     @Override
     protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
index fa87c8df2e075329ac0fe684429cddd3e7c5c050..61bc401ee50eeca54344c35bc6f0c15c9c875599 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
@@ -202,6 +202,11 @@ public interface DisplayableItemNodeVisitor<T> {
     T visit(HostNode node);
 
     T visit(DataSourcesByTypeNode node);
+    
+    /*
+     * Unsupported node
+     */
+    T visit(UnsupportedContentNode ucn);
 
     /**
      * Visitor with an implementable default behavior for all types. Override
@@ -574,5 +579,10 @@ public T visit(DataSourcesByTypeNode node) {
         public T visit(PersonGroupingNode node) {
             return defaultVisit(node);
         }
+        
+        @Override
+        public T visit(UnsupportedContentNode node) {
+            return defaultVisit(node);
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
index 9b7802bdbc60508fc7151d40b10759395e1fb889..170251bd13258aa025419fb572f375c081728c43 100755
--- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java
@@ -20,6 +20,7 @@
 
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
+import java.lang.ref.WeakReference;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -29,18 +30,26 @@
 import java.util.Optional;
 import java.util.logging.Level;
 import javax.swing.Action;
+import org.apache.commons.lang3.tuple.Pair;
 import org.openide.nodes.ChildFactory;
 import org.openide.nodes.Children;
 import org.openide.nodes.Node;
 import org.openide.nodes.Sheet;
+import org.openide.util.Exceptions;
 import org.openide.util.NbBundle.Messages;
-import org.openide.util.lookup.Lookups;
+import org.openide.util.WeakListeners;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.casemodule.events.OsAccountChangedEvent;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
 import org.sleuthkit.datamodel.Host;
 import org.sleuthkit.datamodel.OsAccount;
+import org.sleuthkit.datamodel.OsAccountRealm;
 import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.Tag;
 import org.sleuthkit.datamodel.TskCoreException;
 import org.sleuthkit.datamodel.TskDataException;
 
@@ -52,6 +61,7 @@ public final class OsAccounts implements AutopsyVisitableItem {
     private static final Logger logger = Logger.getLogger(OsAccounts.class.getName());
     private static final String ICON_PATH = "org/sleuthkit/autopsy/images/os-account.png";
     private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+    private static final String REALM_DATA_AVAILABLE_EVENT = "REALM_DATA_AVAILABLE_EVENT";
 
     private SleuthkitCase skCase;
     private final long filteringDSObjId;
@@ -114,9 +124,9 @@ private final class OsAccountNodeFactory extends ChildFactory.Detachable<OsAccou
             @Override
             public void propertyChange(PropertyChangeEvent evt) {
                 String eventType = evt.getPropertyName();
-                if(eventType.equals(Case.Events.OS_ACCOUNT_ADDED.toString())
+                if (eventType.equals(Case.Events.OS_ACCOUNT_ADDED.toString())
                         || eventType.equals(Case.Events.OS_ACCOUNT_REMOVED.toString())) {
-                     refresh(true);
+                    refresh(true);
                 } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
                     // case was closed. Remove listeners so that we don't get called with a stale case handle
                     if (evt.getNewValue() == null) {
@@ -126,22 +136,22 @@ public void propertyChange(PropertyChangeEvent evt) {
                 }
             }
         };
-        
+
         @Override
         protected void addNotify() {
             Case.addEventTypeSubscriber(EnumSet.of(Case.Events.OS_ACCOUNT_ADDED, Case.Events.OS_ACCOUNT_REMOVED), listener);
             Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener);
         }
-        
+
         @Override
         protected void removeNotify() {
             Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_ADDED), listener);
             Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), listener);
         }
-        
+
         @Override
         protected boolean createKeys(List<OsAccount> list) {
-            if(skCase != null) {
+            if (skCase != null) {
                 try {
                     if (filteringDSObjId == 0) {
                         list.addAll(skCase.getOsAccountManager().getOsAccounts());
@@ -166,35 +176,52 @@ protected Node createNodeForKey(OsAccount key) {
     /**
      * An OsAccount leaf Node.
      */
-    public static final class OsAccountNode extends DisplayableItemNode {
+    public static final class OsAccountNode extends AbstractContentNode<OsAccount> {
 
         private OsAccount account;
-        
+
         private final PropertyChangeListener listener = new PropertyChangeListener() {
             @Override
             public void propertyChange(PropertyChangeEvent evt) {
-                if(((OsAccountChangedEvent)evt).getOsAccount().getId() == account.getId()) {
-                    // Update the account node to the new one
-                    account = ((OsAccountChangedEvent)evt).getOsAccount();
-                    updateSheet();
+                if (evt.getPropertyName().equals(Case.Events.OS_ACCOUNT_CHANGED.name())) {
+                    if (((OsAccountChangedEvent) evt).getOsAccount().getId() == account.getId()) {
+                        // Update the account node to the new one
+                        account = ((OsAccountChangedEvent) evt).getOsAccount();
+                        updateSheet();
+                    }
+                } else if (evt.getPropertyName().equals(REALM_DATA_AVAILABLE_EVENT)) {
+                    OsAccountRealm realm = (OsAccountRealm) evt.getNewValue();
+
+                    // Currently only 0 or 1 names are supported, this will need
+                    // to be modified if that changes.
+                    List<String> realmNames = realm.getRealmNames();
+                    if (!realmNames.isEmpty()) {
+                        updateSheet(new NodeProperty<>(
+                                Bundle.OsAccounts_accountRealmNameProperty_name(),
+                                Bundle.OsAccounts_accountRealmNameProperty_displayName(),
+                                Bundle.OsAccounts_accountRealmNameProperty_desc(),
+                                ""));
+                    }
                 }
             }
         };
 
+        private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null);
+
         /**
          * Constructs a new OsAccountNode.
          *
          * @param account Node object.
          */
         OsAccountNode(OsAccount account) {
-            super(Children.LEAF, Lookups.fixed(account));
+            super(account);
             this.account = account;
 
             setName(account.getName());
             setDisplayName(account.getName());
             setIconBaseWithExtension(ICON_PATH);
-            
-            Case.addEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_CHANGED), listener);
+
+            Case.addEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNT_CHANGED), weakListener);
         }
 
         @Override
@@ -211,7 +238,16 @@ public boolean isLeafTypeNode() {
         public String getItemType() {
             return getClass().getName();
         }
-        
+
+        /**
+         * Returns the OsAccount associated with this node.
+         *
+         * @return
+         */
+        OsAccount getOsAccount() {
+            return account;
+        }
+
         @Messages({
             "OsAccounts_accountNameProperty_name=Name",
             "OsAccounts_accountNameProperty_displayName=Name",
@@ -226,13 +262,13 @@ public String getItemType() {
             "OsAccounts_loginNameProperty_displayName=Login Name",
             "OsAccounts_loginNameProperty_desc=Os Account login name"
         })
-        
+
         /**
-        * Refreshes this node's property sheet.
-        */
-       void updateSheet() {
-           this.setSheet(createSheet());
-       }
+         * Refreshes this node's property sheet.
+         */
+        void updateSheet() {
+            this.setSheet(createSheet());
+        }
 
         @Override
         protected Sheet createSheet() {
@@ -255,9 +291,8 @@ protected Sheet createSheet() {
                     Bundle.OsAccounts_loginNameProperty_displayName(),
                     Bundle.OsAccounts_loginNameProperty_desc(),
                     optional.isPresent() ? optional.get() : ""));
-           // TODO - load realm on background thread
+            // Fill with empty string, fetch on background task.
             String realmName = "";
-            //String realmName = account.getRealm().getRealmNames().isEmpty() ? "" :  account.getRealm().getRealmNames().get(0);
             propertiesSet.put(new NodeProperty<>(
                     Bundle.OsAccounts_accountRealmNameProperty_name(),
                     Bundle.OsAccounts_accountRealmNameProperty_displayName(),
@@ -274,16 +309,91 @@ protected Sheet createSheet() {
                     Bundle.OsAccounts_createdTimeProperty_desc(),
                     timeDisplayStr));
 
+            backgroundTasksPool.submit(new GetOsAccountRealmTask(new WeakReference<>(this), weakListener));
+
             return sheet;
         }
-        
+
         @Override
         public Action[] getActions(boolean popup) {
             List<Action> actionsList = new ArrayList<>();
             actionsList.addAll(Arrays.asList(super.getActions(popup)));
             actionsList.addAll(DataModelActionsFactory.getActions(account));
-            
+
             return actionsList.toArray(new Action[actionsList.size()]);
         }
+
+        @Override
+        protected List<Tag> getAllTagsFromDatabase() {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
+            return null;
+        }
+
+        @Override
+        protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
+            return null;
+        }
+
+        @Override
+        protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
+            return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
+        }
+
+        @Override
+        protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) {
+            return null;
+        }
+
+        @Override
+        public <T> T accept(ContentNodeVisitor<T> visitor) {
+            return visitor.visit(this);
+        }
+
+        /**
+         * Task for grabbing the osAccount realm.
+         */
+        static class GetOsAccountRealmTask implements Runnable {
+
+            private final WeakReference<OsAccountNode> weakNodeRef;
+            private final PropertyChangeListener listener;
+
+            /**
+             * Construct a new task.
+             *
+             * @param weakContentRef
+             * @param listener
+             */
+            GetOsAccountRealmTask(WeakReference<OsAccountNode> weakContentRef, PropertyChangeListener listener) {
+                this.weakNodeRef = weakContentRef;
+                this.listener = listener;
+            }
+
+            @Override
+            public void run() {
+                OsAccountNode node = weakNodeRef.get();
+                if (node == null) {
+                    return;
+                }
+
+                try {
+                    long realmId = node.getOsAccount().getRealmId();
+                    OsAccountRealm realm = Case.getCurrentCase().getSleuthkitCase().getOsAccountRealmManager().getRealmByRealmId(realmId);
+
+                    if (listener != null && realm != null) {
+                        listener.propertyChange(new PropertyChangeEvent(
+                                AutopsyEvent.SourceType.LOCAL.toString(),
+                                REALM_DATA_AVAILABLE_EVENT,
+                                null, realm));
+                    }
+
+                } catch (TskCoreException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            }
+        }
     }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d8a3473e67b0772fd883ce29a9ca737304116d5
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/UnsupportedContentNode.java
@@ -0,0 +1,186 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2021 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.List;
+import javax.swing.Action;
+import org.apache.commons.lang3.tuple.Pair;
+import org.openide.nodes.Sheet;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
+import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
+import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR;
+import org.sleuthkit.datamodel.UnsupportedContent;
+import org.sleuthkit.datamodel.Tag;
+
+/**
+ * This class is used to represent the "Node" for an unsupported content object.
+ */
+public class UnsupportedContentNode extends AbstractContentNode<UnsupportedContent> {
+
+    /**
+     *
+     * @param unsupportedContent underlying Content instance
+     */
+    @NbBundle.Messages({
+        "UnsupportedContentNode.displayName=Unsupported Content",
+    })
+    public UnsupportedContentNode(UnsupportedContent unsupportedContent) {
+        super(unsupportedContent);
+
+        // set name, display name, and icon
+        this.setDisplayName(Bundle.UnsupportedContentNode_displayName());
+
+        this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
+    }
+
+    /**
+     * Right click action for UnsupportedContentNode node
+     *
+     * @param popup
+     *
+     * @return
+     */
+    @Override
+    public Action[] getActions(boolean popup) {
+        List<Action> actionsList = new ArrayList<>();
+
+        for (Action a : super.getActions(true)) {
+            actionsList.add(a);
+        }
+
+        return actionsList.toArray(new Action[actionsList.size()]);
+        
+    }
+
+    @NbBundle.Messages({
+        "UnsupportedContentNode.createSheet.name.name=Name",
+        "UnsupportedContentNode.createSheet.name.displayName=Name",
+        "UnsupportedContentNode.createSheet.name.desc=no description",
+    })
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = super.createSheet();
+        Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
+        if (sheetSet == null) {
+            sheetSet = Sheet.createPropertiesSet();
+            sheet.put(sheetSet);
+        }
+
+        sheetSet.put(new NodeProperty<>(Bundle.UnsupportedContentNode_createSheet_name_name(),
+                Bundle.UnsupportedContentNode_createSheet_name_displayName(),
+                Bundle.UnsupportedContentNode_createSheet_name_desc(),
+                this.getDisplayName()));
+
+        return sheet;
+    }
+
+    @Override
+    public <T> T accept(ContentNodeVisitor<T> visitor) {
+        return visitor.visit(this);
+    }
+
+    @Override
+    public boolean isLeafTypeNode() {
+        return false;
+    }
+
+    @Override
+    public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
+        return visitor.visit(this);
+    }
+
+    @Override
+    public String getItemType() {
+        return getClass().getName();
+    }
+
+    /**
+     * Reads and returns a list of all tags associated with this content node.
+     *
+     * Null implementation of an abstract method.
+     *
+     * @return list of tags associated with the node.
+     */
+    @Override
+    protected List<Tag> getAllTagsFromDatabase() {
+        return new ArrayList<>();
+    }
+
+    /**
+     * Returns correlation attribute instance for the underlying content of the
+     * node.
+     *
+     * Null implementation of an abstract method.
+     *
+     * @return correlation attribute instance for the underlying content of the
+     *         node.
+     */
+    @Override
+    protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
+        return null;
+    }
+
+    /**
+     * Returns Score property for the node.
+     *
+     * Null implementation of an abstract method.
+     *
+     * @param tags list of tags.
+     *
+     * @return Score property for the underlying content of the node.
+     */
+    @Override
+    protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
+        return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR);
+    }
+
+    /**
+     * Returns comment property for the node.
+     *
+     * Null implementation of an abstract method.
+     *
+     * @param tags      list of tags
+     * @param attribute correlation attribute instance
+     *
+     * @return Comment property for the underlying content of the node.
+     */
+    @Override
+    protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
+        return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
+    }
+
+    /**
+     * Returns occurrences/count property for the node.
+     *
+     * Null implementation of an abstract method.
+     *
+     * @param attributeType      the type of the attribute to count
+     * @param attributeValue     the value of the attribute to coun
+     * @param defaultDescription a description to use when none is determined by
+     *                           the getCountPropertyAndDescription method
+     *
+     * @return count property for the underlying content of the node.
+     */
+    @Override
+    protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription) {
+        return Pair.of(-1L, NO_DESCR);
+    }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
index c648387016cfbb40fc33eecd6b4b104724538801..0ab883cbd47a0a01c5f4593705f6b1e4339f8748 100755
--- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED
@@ -59,6 +59,7 @@ SelectionContext.views=Views
 ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.
 ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree.
 ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.
+ViewContextAction.errorMessage.unsupportedParent=Unable to navigate to content not supported in this release.
 VolumeDetailsPanel.volumeIDLabel.text=Volume ID:
 VolumeDetailsPanel.volumeIDValue.text=...
 VolumeDetailsPanel.startValue.text=...
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index a412bb5970b377789d6cc691d1d5a4559ad1aa96..bf99de6183d1c5ceca54056acd41a320af9a5b99 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -304,7 +304,6 @@ public List<Action> visit(BlackboardArtifactNode ban) {
                             NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c));
                 }
                 // action to go to the source file of the artifact
-                // action to go to the source file of the artifact
                 Content fileContent = ban.getLookup().lookup(AbstractFile.class);
                 if (fileContent == null) {
                     Content content = ban.getLookup().lookup(Content.class);
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java
index ca9aa4319876239d1d1e3d3f1e4ed55ea4658963..6fb134ceec9034c1f564942b2dd1778eac3da59a 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java
@@ -63,6 +63,7 @@
 import org.sleuthkit.datamodel.TskCoreException;
 import org.sleuthkit.datamodel.TskData;
 import org.sleuthkit.datamodel.TskDataException;
+import org.sleuthkit.datamodel.UnsupportedContent;
 import org.sleuthkit.datamodel.VolumeSystem;
 
 /**
@@ -161,7 +162,8 @@ public ViewContextAction(String displayName, Content content) {
     @Messages({
         "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.",
         "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.",
-        "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree."
+        "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree.",
+        "ViewContextAction.errorMessage.unsupportedParent=Unable to navigate to content not supported in this release."
     })
     public void actionPerformed(ActionEvent event) {
         EventQueue.invokeLater(() -> {
@@ -181,6 +183,13 @@ public void actionPerformed(ActionEvent event) {
                 logger.log(Level.SEVERE, String.format("Could not get parent of Content object: %s", content), ex); //NON-NLS
                 return;
             }
+            
+            if ((parentContent != null) 
+                    && (parentContent instanceof UnsupportedContent)) {
+                MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_unsupportedParent());
+                logger.log(Level.WARNING, String.format("Could not navigate to unsupported content with id: %d", parentContent.getId())); //NON-NLS
+                return;
+            }
 
             /*
              * Get the "Data Sources" node from the tree view.
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties
index 77a2b221eeff9e15c5519fad5f10f8a9bfa334d9..c6ba196646c74aeac2d91e5f34d965b420d92075 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties
@@ -1,4 +1,3 @@
-CTL_OpenGeolocation=Geolocation
 CTL_GeolocationTopComponentAction=GeolocationTopComponent
 CTL_GeolocationTopComponent=Geolocation
 RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date.
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED
index ddec131b0565b1b3fe7d42da1eced29362bf1a75..578083986f6b7d6038a225b9211c7d1f68a0f94b 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED
@@ -1,6 +1,6 @@
-CTL_OpenGeolocation=Geolocation
 CTL_GeolocationTopComponentAction=GeolocationTopComponent
 CTL_GeolocationTopComponent=Geolocation
+CTL_OpenGeolocation=Geolocation
 GeoFilterPanel_ArtifactType_List_Title=Types
 GeoFilterPanel_DataSource_List_Title=Data Sources
 GeoFilterPanel_empty_artifactType=Unable to apply filter, please select one or more artifact types.
@@ -40,8 +40,6 @@ HidingPane_default_title=Filters
 MapPanel_connection_failure_message=Failed to connect to new geolocation map tile source.
 MapPanel_connection_failure_message_title=Connection Failure
 MayWaypoint_ExternalViewer_label=Open in ExternalViewer
-OpenGeolocationAction_displayName=Geolocation
-OpenGeolocationAction_name=Geolocation
 RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date.
 RefreshPanel.refreshButton.text=Refresh View
 RefreshPanel.closeButton.text=
diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java
index d809f55e042954d454ba373b0c321d9cf70d16b4..1eaac6f1be846af8c48d926f63c6593fc04f9486 100755
--- a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java
+++ b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java
@@ -46,17 +46,13 @@
 @ActionReferences(value = {
     @ActionReference(path = "Menu/Tools", position = 103),
 @ActionReference(path = "Toolbars/Case", position = 103)})
+@Messages({"CTL_OpenGeolocation=Geolocation"})
 public class OpenGeolocationAction extends CallableSystemAction {
     
     private static final long serialVersionUID = 1L;
     private final JButton toolbarButton = new JButton(getName(),
             new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/blueGeo24.png"))); //NON-NLS
     
-    @Messages({
-        "OpenGeolocationAction_name=Geolocation",
-        "OpenGeolocationAction_displayName=Geolocation"
-    })
-
     /**
      * Constructs the new action of opening the Geolocation window.
      */
@@ -98,7 +94,7 @@ public void setEnabled(boolean value) {
 
     @Override
     public String getName() {
-        return Bundle.OpenGeolocationAction_displayName();
+        return Bundle.CTL_OpenGeolocation();
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
index cf02c8e11bf5e7c88fde3adf7ccebdc5a381e702..a50f266c57c44184adfa4d9585353aaf3a3bf7c7 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/GetRootDirectoryVisitor.java
@@ -31,6 +31,7 @@
 import org.sleuthkit.datamodel.LocalDirectory;
 import org.sleuthkit.datamodel.OsAccount;
 import org.sleuthkit.datamodel.SlackFile;
+import org.sleuthkit.datamodel.UnsupportedContent;
 import org.sleuthkit.datamodel.VirtualDirectory;
 
 /**
@@ -113,4 +114,9 @@ public Collection<AbstractFile> visit(BlackboardArtifact art) {
     public Collection<AbstractFile> visit(OsAccount art) {
         return getAllFromChildren(art);
     }
+    
+    @Override
+    public Collection<AbstractFile> visit(UnsupportedContent uc) {
+        return getAllFromChildren(uc);
+    }
 }
diff --git a/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java b/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java
index f0aa9b06d57101ae2076ce2046bbe32073bc7cd0..6594c0f4fdbe516cfab2fa22870d1e1d02b631aa 100644
--- a/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java
+++ b/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java
@@ -21,21 +21,19 @@
 import java.io.File;
 import java.nio.file.Paths;
 import java.util.Optional;
-import java.util.logging.Level;
 import java.util.prefs.Preferences;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
 import org.openide.util.NbBundle;
 import org.openide.util.NbPreferences;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
-import org.sleuthkit.autopsy.core.UserPreferences;
 import org.sleuthkit.autopsy.coreutils.FileUtil;
 import org.sleuthkit.autopsy.coreutils.Logger;
-import org.sleuthkit.autopsy.coreutils.NetworkUtils;
 
 /**
  * Provides case-specific settings like the user-specified temp folder.
+ *
+ * NOTE: The Case class also handles providing a temp directory. When altering
+ * code in this class, also look at the Case class as well.
  */
 public final class UserMachinePreferences {
 
@@ -64,6 +62,7 @@ public enum TempDirChoice {
          * (whitespace and case insensitive).
          *
          * @param val The string value.
+         *
          * @return The choice or empty if not found.
          */
         static Optional<TempDirChoice> getValue(String val) {
@@ -80,98 +79,8 @@ static Optional<TempDirChoice> getValue(String val) {
     private static final String CUSTOM_TEMP_DIR_KEY = "TempDirectory";
     private static final String TEMP_DIR_CHOICE_KEY = "TempDirChoice";
 
-    private static final String AUTOPSY_SUBDIR = UserPreferences.getAppName();
-    private static final String CASE_SUBDIR = "Temp";
-
     private static final TempDirChoice DEFAULT_CHOICE = TempDirChoice.SYSTEM;
 
-    /**
-     * Returns the name of this computer's host name to be used as a directory
-     * in some instances.
-     *
-     * @return The name of this computer's host name to be used as a directory
-     * in some instances.
-     */
-    private static String getHostName() {
-        return NetworkUtils.getLocalHostName();
-    }
-
-    /**
-     * @return A subdirectory of java.io.tmpdir.
-     */
-    private static File getSystemTempDirFile() {
-        return Paths.get(System.getProperty("java.io.tmpdir"), AUTOPSY_SUBDIR).toFile();
-    }
-
-    /**
-     * @return A subdirectory of the open case or getSystemTempDirFile if no
-     * open case.
-     */
-    private static File getCaseTempDirFile() {
-        try {
-            Case autCase = Case.getCurrentCaseThrows();
-            String caseDirStr = autCase.getCaseDirectory();
-            switch (autCase.getCaseType()) {
-                case MULTI_USER_CASE: return Paths.get(caseDirStr, getHostName(), CASE_SUBDIR).toFile();
-                case SINGLE_USER_CASE: return Paths.get(caseDirStr, CASE_SUBDIR).toFile();
-                default: 
-                    logger.log(Level.SEVERE, "Unknown case type: " + autCase.getCaseType());
-                    return getSystemTempDirFile();
-            }
-            
-        } catch (NoCurrentCaseException ex) {
-            return getSystemTempDirFile();
-        }
-    }
-
-    /**
-     * Returns the custom directory subdirectory to be used for temp files
-     * (otherwise java.io.tmpdir subdir).
-     *
-     * @return A subdirectory of the custom user-specified path. If no path is
-     * specified, getSystemTempDirFile() is returned instead.
-     */
-    private static File getCustomTempDirFile() {
-        String customDirectory = getCustomTempDirectory();
-        return (StringUtils.isBlank(customDirectory))
-                ? getSystemTempDirFile() : Paths.get(customDirectory, AUTOPSY_SUBDIR, getHostName()).toFile();
-    }
-
-    /**
-     * Returns the temp directory file to use based on user choice.
-     *
-     * @return The directory.
-     */
-    private static File getTempDirFile() {
-        TempDirChoice choice = getTempDirChoice();
-        switch (choice) {
-            case CASE:
-                return getCaseTempDirFile();
-            case CUSTOM:
-                return getCustomTempDirFile();
-            case SYSTEM:
-            default:
-                return getSystemTempDirFile();
-        }
-    }
-
-    /**
-     * Returns the temp directory to use based on settings. This method also
-     * ensures the temp directory has been created.
-     *
-     * @return The base user-specified temporary directory.
-     */
-    public static String getTempDirectory() {
-        File dir = getTempDirFile();
-        dir = dir == null ? getSystemTempDirFile() : dir;
-
-        if (!dir.exists()) {
-            dir.mkdirs();
-        }
-
-        return dir.getAbsolutePath();
-    }
-
     /**
      * @return The user-specified custom temp directory path or empty string.
      */
@@ -188,7 +97,8 @@ public static String getCustomTempDirectory() {
      * @return True if this is a valid location for a temp directory.
      *
      * @throws UserMachinePreferencesException If path could not be validated
-     * due to mkdirs failure or the directory is not read/write.
+     *                                         due to mkdirs failure or the
+     *                                         directory is not read/write.
      */
     @NbBundle.Messages({
         "# {0} - path",
@@ -219,7 +129,7 @@ private static boolean validateTempDirectory(String path) throws UserMachinePref
      * @param path The path to the directory.
      *
      * @throws UserMachinePreferencesException If the directory cannot be
-     * accessed or created.
+     *                                         accessed or created.
      */
     public static void setCustomTempDirectory(String path) throws UserMachinePreferencesException {
         validateTempDirectory(path);
@@ -228,7 +138,8 @@ public static void setCustomTempDirectory(String path) throws UserMachinePrefere
 
     /**
      * @return The user selection for how the temp directory should be handled
-     * (temp directory in case folder, in java.io.tmpdir, custom path).
+     *         (temp directory in case folder, in java.io.tmpdir, custom path).
+     *         Guaranteed to be non-null.
      */
     public static TempDirChoice getTempDirChoice() {
         return TempDirChoice.getValue(preferences.get(TEMP_DIR_CHOICE_KEY, null))
@@ -239,6 +150,7 @@ public static TempDirChoice getTempDirChoice() {
      * Sets the temp directory choice (i.e. system, case, custom).
      *
      * @param tempDirChoice The choice (must be non-null).
+     *
      * @throws UserMachinePreferencesException
      */
     public static void setTempDirChoice(TempDirChoice tempDirChoice) throws UserMachinePreferencesException {
diff --git a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/Bundle.properties-MERGED
index f46304e2be6e8a6f0e24b0aa28f52a3237695d9f..a07ed802be15c4069d0a5dd10777aa70e52867f9 100644
--- a/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/modules/leappanalyzers/Bundle.properties-MERGED
@@ -34,7 +34,11 @@ ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp
 ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp
 ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices.
 ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)
+LeappFileProcessor.cannot.create.calllog.relationship=Cannot create TSK_CALLLOG Relationship.
+LeappFileProcessor.cannot.create.contact.relationship=Cannot create TSK_CONTACT Relationship.
 LeappFileProcessor.cannot.create.message.relationship=Cannot create TSK_MESSAGE Relationship.
+LeappFileProcessor.cannot.create.trackpoint.relationship=Cannot create TSK_TRACK_POINT artifact.
+LeappFileProcessor.cannot.create.waypoint.relationship=Cannot create TSK_WAYPOINT artifact.
 LeappFileProcessor.cannot.load.artifact.xml=Cannot load xml artifact file.
 LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.
 LeappFileProcessor.completed=Leapp Processing Completed
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java
index f80eef206b4f5b56191c6023926acbef8da19ca7..b85f43bab9e0922b69f5251756acaa63fe78443c 100755
--- a/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java
@@ -55,6 +55,7 @@
 import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
 import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
 import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
+import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.SqlFilterState;
 import org.sleuthkit.autopsy.timeline.utils.CacheLoaderImpl;
 import org.sleuthkit.autopsy.timeline.utils.FilterUtils;
 import org.sleuthkit.autopsy.timeline.zooming.EventsModelParams;
@@ -222,8 +223,7 @@ synchronized private void populateDataSourcesCache() throws TskCoreException {
      * @param rootFilterState A root filter state object.
      */
     synchronized void addDataSourceFilters(RootFilterState rootFilterState) {
-        DataSourcesFilter dataSourcesFilter = rootFilterState.getDataSourcesFilterState().getFilter();
-        datasourceIDsToNamesMap.entrySet().forEach(entry -> dataSourcesFilter.addSubFilter(newDataSourceFilter(entry)));
+        datasourceIDsToNamesMap.entrySet().forEach(entry -> rootFilterState.getDataSourcesFilterState().addSubFilterState(new SqlFilterState<>(newDataSourceFilter(entry))));
     }
 
     /**
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
index 39506554ac9efa73bfceb8e8286d38adc59c0f3d..a834ccd0f6a8633ea5f6bc331602f08d1a9187f8 100755
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.java
@@ -588,7 +588,7 @@ private void refreshTimeUI() {
             long startMillis = filteredEvents.getTimeRange().getStartMillis();
             long endMillis = filteredEvents.getTimeRange().getEndMillis();
 
-            if (minTime > 0 && maxTime > minTime) {
+            if ( maxTime > minTime) {
                 Platform.runLater(() -> {
                     startPicker.localDateTimeProperty().removeListener(startListener);
                     endPicker.localDateTimeProperty().removeListener(endListener);
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/datamodel/CompoundFilterState.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/datamodel/CompoundFilterState.java
index 6884d275a808f0bd5fa867e28bcbf1898838f5a8..f7e628f698ccce475ff102459bde05607006341e 100755
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/datamodel/CompoundFilterState.java
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/filtering/datamodel/CompoundFilterState.java
@@ -110,7 +110,7 @@ private void disableSubFiltersIfNotActive() {
      * @param newSubFilterState The new filter state to be added as a subfilter
      *                          state.
      */
-    protected void addSubFilterState(FilterState< ? extends SubFilterType> newSubFilterState) {
+    public void addSubFilterState(FilterState< ? extends SubFilterType> newSubFilterState) {
         SubFilterType filter = newSubFilterState.getFilter();
         if (getSubFilterStates().stream().map(FilterState::getFilter).noneMatch(filter::equals)) {
 
diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.fxml
index de86817f003a4b789c0298ab4edb319def328be9..7a2e5168b3f48b28889ae5ce89eb01acdcabfacf 100755
--- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.fxml
+++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.fxml
@@ -75,15 +75,15 @@
       <TableView fx:id="table" tableMenuButtonVisible="true" BorderPane.alignment="CENTER">
         <columns>
           <TableColumn fx:id="dateTimeColumn" editable="false" maxWidth="200.0" minWidth="150.0" prefWidth="150.0" resizable="false" sortable="false" text="Date/Time" />
-            <TableColumn fx:id="typeColumn" editable="false" maxWidth="100.0" minWidth="100.0" prefWidth="100.0" sortable="false" text="Event Type" />
+            <TableColumn fx:id="typeColumn" editable="false" minWidth="100.0" maxWidth="500.0" prefWidth="100.0" sortable="false" text="Event Type" resizable="true" />
             <TableColumn fx:id="descriptionColumn" editable="false" maxWidth="3000.0" minWidth="100.0" prefWidth="300.0" sortable="false" text="Description" />
             <TableColumn fx:id="idColumn" editable="false" maxWidth="50.0" minWidth="50.0" prefWidth="50.0" resizable="false" sortable="false" text="ID" />
             <TableColumn fx:id="taggedColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Tagged" />
             <TableColumn fx:id="hashHitColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Hash Hit" />
         </columns>
-         <columnResizePolicy>
+        <columnResizePolicy>
             <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
-         </columnResizePolicy>
+        </columnResizePolicy>
       </TableView>
    </center>
 </fx:root>
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
index 36ad8c6a29d1fa3910d8b285ab82eba413fd7ae8..bc63f4dddf13bd65c1fef36919d4c80b23f2e18b 100644
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java
@@ -1,7 +1,7 @@
 /*
  * Autopsy Forensic Browser
  *
- * Copyright 2011-2020 Basis Technology Corp.
+ * Copyright 2011-2021 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -53,6 +53,8 @@
 import javax.swing.AbstractAction;
 import org.apache.commons.io.FileUtils;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 import static java.util.stream.Collectors.toList;
 import org.apache.solr.client.solrj.SolrQuery;
@@ -85,6 +87,7 @@
 import org.sleuthkit.autopsy.casemodule.Case.CaseType;
 import org.sleuthkit.autopsy.casemodule.CaseMetadata;
 import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.core.RuntimeProperties;
 import org.sleuthkit.autopsy.core.UserPreferences;
 import org.sleuthkit.autopsy.coreutils.FileUtil;
 import org.sleuthkit.autopsy.coreutils.Logger;
@@ -95,7 +98,6 @@
 import org.sleuthkit.autopsy.healthmonitor.HealthMonitor;
 import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
 import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
-import org.sleuthkit.autopsy.report.GeneralReportSettings;
 import org.sleuthkit.autopsy.report.ReportProgressPanel;
 import org.sleuthkit.datamodel.Content;
 
@@ -2030,6 +2032,13 @@ class Collection {
         private final List<SolrInputDocument> buffer;
         private final Object bufferLock;
         
+        /* (JIRA-7521) Sometimes we get into a situation where Solr server is no longer able to index new data. 
+        * Typically main reason for this is Solr running out of memory. In this case we will stop trying to send new 
+        * data to Solr (for this collection) after certain number of consecutive batches have failed. */
+        private static final int MAX_NUM_CONSECUTIVE_FAILURES = 5;
+        private AtomicInteger numConsecutiveFailures = new AtomicInteger(0);
+        private AtomicBoolean skipIndexing = new AtomicBoolean(false);
+        
         private final ScheduledThreadPoolExecutor periodicTasksExecutor;
         private static final long PERIODIC_BATCH_SEND_INTERVAL_MINUTES = 10;
         private static final int NUM_BATCH_UPDATE_RETRIES = 10;
@@ -2076,6 +2085,11 @@ private final class SendBatchedDocumentsTask implements Runnable {
 
             @Override
             public void run() {
+                
+                if (skipIndexing.get()) {
+                    return;
+                }
+                
                 List<SolrInputDocument> clone;
                 synchronized (bufferLock) {
                     
@@ -2242,6 +2256,10 @@ private void extractAllTermsForDataSource(Path outputFile, ReportProgressPanel p
          * @throws KeywordSearchModuleException
          */
         void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
+            
+            if (skipIndexing.get()) {
+                return;
+            }
 
             List<SolrInputDocument> clone;
             synchronized (bufferLock) {
@@ -2268,6 +2286,10 @@ void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
          *
          * @throws KeywordSearchModuleException
          */
+        @NbBundle.Messages({
+            "Collection.unableToIndexData.error=Unable to add data to text index. All future text indexing for the current case will be skipped.",
+            
+        })
         private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordSearchModuleException {
             
             if (docBuffer.isEmpty()) {
@@ -2293,6 +2315,7 @@ private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordS
                         }                        
                     }
                     if (success) {
+                        numConsecutiveFailures.set(0);
                         if (reTryAttempt > 0) {
                             logger.log(Level.INFO, "Batch update suceeded after {0} re-try", reTryAttempt); //NON-NLS
                         }
@@ -2304,10 +2327,29 @@ private void sendBufferedDocs(List<SolrInputDocument> docBuffer) throws KeywordS
                 throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg")); //NON-NLS
             } catch (Exception ex) {
                 // Solr throws a lot of unexpected exception types
+                numConsecutiveFailures.incrementAndGet();
                 logger.log(Level.SEVERE, "Could not add batched documents to index", ex); //NON-NLS
+                
+                // display message to user that that a document batch is missing from the index
+                MessageNotifyUtil.Notify.error(
+                        NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"),
+                        NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"));
                 throw new KeywordSearchModuleException(
                         NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"), ex); //NON-NLS
             } finally {
+                if (numConsecutiveFailures.get() >= MAX_NUM_CONSECUTIVE_FAILURES) {
+                    // skip all future indexing
+                    skipIndexing.set(true);
+                    logger.log(Level.SEVERE, "Unable to add data to text index. All future text indexing for the current case will be skipped!"); //NON-NLS
+
+                    // display message to user that no more data will be added to the index
+                    MessageNotifyUtil.Notify.error(
+                            NbBundle.getMessage(this.getClass(), "Server.addDocBatch.exception.msg"),
+                            Bundle.Collection_unableToIndexData_error());
+                    if (RuntimeProperties.runningWithGUI()) {
+                        MessageNotifyUtil.Message.error(Bundle.Collection_unableToIndexData_error());
+                    }
+                }
                 docBuffer.clear();
             }
         }
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED
index 4538c2836cdbded63b1f4245fc5c69996d6501ce..b796a16d26b5d874e13cc8ce97860aba7a8f068e 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED
@@ -13,6 +13,7 @@ ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries
 DataSourceUsage_AndroidMedia=Android Media Card
 DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card
 DataSourceUsage_FlashDrive=Flash Drive
+# {0} - OS name
 DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
 DataSourceUsageAnalyzer.parentModuleName=Recent Activity
 DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
index 1ddc1ed401e5e6d0bc0b4dfce21ce80ee67c97e0..1acfc4f25382c414580dd6c705db39bdb9fdfd1d 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java
@@ -175,8 +175,8 @@ private void moduleInit() throws IngestModuleException {
             fileManager = currentCase.getServices().getFileManager();
              
             // Create an output folder to save any derived files
-            absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName);
-            relOutputFolderName = Paths.get( RAImageIngestModule.getRelModuleOutputPath(), moduleName).normalize().toString();
+            absOutputFolderName = RAImageIngestModule.getRAOutputPath(currentCase, moduleName, context.getJobId());
+            relOutputFolderName = Paths.get(RAImageIngestModule.getRelModuleOutputPath(currentCase, moduleName, context.getJobId())).normalize().toString();
             
             File dir = new File(absOutputFolderName);
             if (dir.exists() == false) {
@@ -206,7 +206,7 @@ private void resetForNewCacheFolder(String cachePath) throws IngestModuleExcepti
             outDir.mkdirs();
         }
         
-        String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cachePath;
+        String cacheTempPath = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cachePath;
         File tempDir = new File(cacheTempPath);
         if (tempDir.exists() == false) {
             tempDir.mkdirs();
@@ -222,7 +222,7 @@ private void resetForNewCacheFolder(String cachePath) throws IngestModuleExcepti
     private void cleanup () {
         
         for (Entry<String, FileWrapper> entry : this.fileCopyCache.entrySet()) {
-            Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName), entry.getKey() ); 
+            Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()), entry.getKey() ); 
             try {
                 entry.getValue().getFileCopy().getChannel().close();
                 entry.getValue().getFileCopy().close();
@@ -283,7 +283,9 @@ void processCaches() {
                     return;
                 }
                 
-                processCacheFolder(indexFile);
+                if (indexFile.getSize() > 0) {
+                    processCacheFolder(indexFile);
+                }
             }
         
         } catch (TskCoreException ex) {
@@ -652,7 +654,7 @@ private Optional<FileWrapper> findDataOrIndexFile(String cacheFileName, String c
         // write the file to disk so that we can have a memory-mapped ByteBuffer
         AbstractFile cacheFile = abstractFileOptional.get();
         RandomAccessFile randomAccessFile = null;
-        String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName) + cacheFolderName + cacheFile.getName(); //NON-NLS
+        String tempFilePathname = RAImageIngestModule.getRATempPath(currentCase, moduleName, context.getJobId()) + cacheFolderName + cacheFile.getName(); //NON-NLS
         try {
             File newFile = new File(tempFilePathname);
             ContentUtils.writeToFile(cacheFile, newFile, context::dataSourceIngestIsCancelled);
@@ -1039,6 +1041,9 @@ void extract() throws TskCoreException, IngestModuleException {
                 this.data = new byte [length];
                 ByteBuffer buf = cacheFileCopy.getByteBuffer();
                 int dataOffset = DATAFILE_HDR_SIZE + cacheAddress.getStartBlock() * cacheAddress.getBlockSize();
+                if (dataOffset > buf.capacity()) {
+                    return;
+                }
                 buf.position(dataOffset);
                 buf.get(data, 0, length);
                 
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java
index 3570f716221c982e2c1d5b7c548614b1a373ac24..c8a3bb64cd4d8bd300b6b33e4ed3456ed57ce3ff 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chromium.java
@@ -91,6 +91,10 @@ class Chromium extends Extract {
     private static final String LOGIN_DATA_FILE_NAME = "Login Data";
     private static final String WEB_DATA_FILE_NAME = "Web Data";
     private static final String UC_BROWSER_NAME = "UC Browser";
+    private static final String ENCRYPTED_FIELD_MESSAGE = "The data was encrypted.";
+    
+    private Boolean databaseEncrypted = false;
+    private Boolean fieldEncrypted = false;
 
     private final Logger logger = Logger.getLogger(this.getClass().getName());
     private Content dataSource;
@@ -130,42 +134,43 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge
         this.dataSource = dataSource;
         this.context = context;
         dataFound = false;
+        long ingestJobId = context.getJobId();
 
         for (Map.Entry<String, String> browser : BROWSERS_MAP.entrySet()) {
             String browserName = browser.getKey();
             String browserLocation = browser.getValue();
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_History", browserName));
-            this.getHistory(browser.getKey(), browser.getValue());
+            this.getHistory(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
 
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Bookmarks", browserName));
-            this.getBookmark(browser.getKey(), browser.getValue());
+            this.getBookmark(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
 
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Cookies", browserName));
-            this.getCookie(browser.getKey(), browser.getValue());
+            this.getCookie(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
 
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Logins", browserName));
-            this.getLogins(browser.getKey(), browser.getValue());
+            this.getLogins(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
 
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_AutoFill", browserName));
-            this.getAutofill(browser.getKey(), browser.getValue());
+            this.getAutofill(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
 
             progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Downloads", browserName));
-            this.getDownload(browser.getKey(), browser.getValue());
+            this.getDownload(browser.getKey(), browser.getValue(), ingestJobId);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
@@ -179,8 +184,11 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge
 
     /**
      * Query for history databases and add artifacts
+     * @param browser 
+     * @param browserLocation 
+     * @param ingestJobId The ingest job id.
      */
-    private void getHistory(String browser, String browserLocation) {
+    private void getHistory(String browser, String browserLocation, long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> historyFiles;
         String historyFileName = HISTORY_FILE_NAME;
@@ -215,7 +223,7 @@ private void getHistory(String browser, String browserLocation) {
         Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
         int j = 0;
         while (j < allocatedHistoryFiles.size()) {
-            String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS
             final AbstractFile historyFile = allocatedHistoryFiles.get(j++);
             if ((historyFile.getSize() == 0) || (historyFile.getName().toLowerCase().contains("-slack"))
                     || (historyFile.getName().toLowerCase().contains("cache")) || (historyFile.getName().toLowerCase().contains("media"))
@@ -281,8 +289,11 @@ private void getHistory(String browser, String browserLocation) {
 
     /**
      * Search for bookmark files and make artifacts.
+     * @param browser
+     * @param browserLocation 
+     * @param ingestJobId The ingest job id.
      */
-    private void getBookmark(String browser, String browserLocation) {
+    private void getBookmark(String browser, String browserLocation, long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> bookmarkFiles;
         String bookmarkFileName = BOOKMARK_FILE_NAME;
@@ -315,7 +326,7 @@ private void getBookmark(String browser, String browserLocation) {
                     || (bookmarkFile.getName().toLowerCase().contains("bak")) || (bookmarkFile.getParentPath().toLowerCase().contains("backup"))) {
                 continue;
             }
-            String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -423,8 +434,11 @@ private void getBookmark(String browser, String browserLocation) {
 
     /**
      * Queries for cookie files and adds artifacts
+     * @param browser
+     * @param browserLocation
+     * @param ingestJobId The ingest job id.
      */
-    private void getCookie(String browser, String browserLocation) {
+    private void getCookie(String browser, String browserLocation, long ingestJobId) {
 
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> cookiesFiles;
@@ -456,7 +470,7 @@ private void getCookie(String browser, String browserLocation) {
             if ((cookiesFile.getSize() == 0) || (cookiesFile.getName().toLowerCase().contains("-slack"))) {
                 continue;
             }
-            String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -519,8 +533,11 @@ private void getCookie(String browser, String browserLocation) {
 
     /**
      * Queries for download files and adds artifacts
+     * @param browser
+     * @param browserLocation
+     * @param ingestJobId The ingest job id.
      */
-    private void getDownload(String browser, String browserLocation) {
+    private void getDownload(String browser, String browserLocation, long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> downloadFiles;
         String historyFileName = HISTORY_FILE_NAME;
@@ -551,7 +568,7 @@ private void getDownload(String browser, String browserLocation) {
                 continue;
             }
 
-            String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -633,8 +650,11 @@ private void getDownload(String browser, String browserLocation) {
 
     /**
      * Gets user logins from Login Data sqlite database
+     * @param browser
+     * @param browserLocation
+     * @param ingestJobId The ingest job id.
      */
-    private void getLogins(String browser, String browserLocation) {
+    private void getLogins(String browser, String browserLocation, long ingestJobId) {
 
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> loginDataFiles;
@@ -665,7 +685,7 @@ private void getLogins(String browser, String browserLocation) {
             if ((loginDataFile.getSize() == 0) || (loginDataFile.getName().toLowerCase().contains("-slack"))) {
                 continue;
             }
-            String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -736,8 +756,11 @@ private void getLogins(String browser, String browserLocation) {
     /**
      * Gets and parses Autofill data from 'Web Data' database, and creates
      * TSK_WEB_FORM_AUTOFILL, TSK_WEB_FORM_ADDRESS artifacts
+     * @param browser
+     * @param browserLocation
+     * @param ingestJobId The ingest job id.
      */
-    private void getAutofill(String browser, String browserLocation) {
+    private void getAutofill(String browser, String browserLocation, long ingestJobId) {
 
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> webDataFiles;
@@ -764,11 +787,12 @@ private void getAutofill(String browser, String browserLocation) {
         Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
         int j = 0;
         while (j < webDataFiles.size()) {
+            databaseEncrypted = false;
             AbstractFile webDataFile = webDataFiles.get(j++);
             if ((webDataFile.getSize() == 0) || (webDataFile.getName().toLowerCase().contains("-slack"))) {
                 continue;
             }
-            String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
+            String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser, ingestJobId) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -798,11 +822,18 @@ private void getAutofill(String browser, String browserLocation) {
             try {
                 // get form address atifacts
                 getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X);
+                if (databaseEncrypted) {
+                   Collection<BlackboardAttribute> bbattributes = new ArrayList<>(); 
+                   bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT,
+                            RecentActivityExtracterModuleFactory.getModuleName(), 
+                            String.format("%s Autofill Database Encryption Detected", browser)));
+                   bbartifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, webDataFile, bbattributes)); 
+                }
             } catch (NoCurrentCaseException | TskCoreException | Blackboard.BlackboardException ex) {
                 logger.log(Level.SEVERE, String.format("Error adding artifacts to the case database "
                         + "for chrome file %s [objId=%d]", webDataFile.getName(), webDataFile.getId()), ex);
             }
-
+            
             dbFile.delete();
         }
 
@@ -839,9 +870,10 @@ private Collection<BlackboardArtifact> getFormAutofillArtifacts(AbstractFile web
                     NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
                     ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
 
+            fieldEncrypted = false;
             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
                     RecentActivityExtracterModuleFactory.getModuleName(),
-                    ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
+                    processFields(result.get("value")))); //NON-NLS
 
             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
                     RecentActivityExtracterModuleFactory.getModuleName(),
@@ -860,7 +892,11 @@ private Collection<BlackboardArtifact> getFormAutofillArtifacts(AbstractFile web
 
             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
                     RecentActivityExtracterModuleFactory.getModuleName(), browser));
-
+            if (fieldEncrypted) {
+                bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT,
+                        RecentActivityExtracterModuleFactory.getModuleName(), ENCRYPTED_FIELD_MESSAGE));
+            }
+            
             // Add an artifact
             try {
                 bbartifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes));
@@ -902,20 +938,21 @@ private void getFormAddressArtifacts(AbstractFile webDataFile, String dbFilePath
         logger.log(Level.INFO, "{0}- Now getting Web form addresses from {1} with {2} artifacts identified.", new Object[]{getName(), dbFilePath, addresses.size()}); //NON-NLS
         for (HashMap<String, Object> result : addresses) {
 
-            // get name fields
-            String first_name = result.get("first_name").toString() != null ? result.get("first_name").toString() : "";
-            String middle_name = result.get("middle_name").toString() != null ? result.get("middle_name").toString() : "";
-            String last_name = result.get("last_name").toString() != null ? result.get("last_name").toString() : "";
+            fieldEncrypted = false;
+            
+            String first_name = processFields(result.get("first_name"));
+            String middle_name = processFields(result.get("middle_name"));
+            String last_name = processFields(result.get("last_name"));
 
             // get email and phone
-            String email_Addr = result.get("email").toString() != null ? result.get("email").toString() : "";
-            String phone_number = result.get("number").toString() != null ? result.get("number").toString() : "";
+            String email_Addr = processFields(result.get("email"));
+            String phone_number = processFields(result.get("number"));
 
             // Get the address fields
-            String city = result.get("city").toString() != null ? result.get("city").toString() : "";
-            String state = result.get("state").toString() != null ? result.get("state").toString() : "";
-            String zipcode = result.get("zipcode").toString() != null ? result.get("zipcode").toString() : "";
-            String country_code = result.get("country_code").toString() != null ? result.get("country_code").toString() : "";
+            String city = processFields(result.get("city"));
+            String state = processFields(result.get("state"));
+            String zipcode = processFields(result.get("zipcode"));
+            String country_code = processFields(result.get("country_code"));
 
             // schema version specific fields
             String full_name = "";
@@ -925,14 +962,15 @@ private void getFormAddressArtifacts(AbstractFile webDataFile, String dbFilePath
             long use_date = 0;
 
             if (isSchemaV8X) {
-                full_name = result.get("full_name").toString() != null ? result.get("full_name").toString() : "";
-                street_address = result.get("street_address").toString() != null ? result.get("street_address").toString() : "";
+                
+                full_name = processFields(result.get("full_name"));
+                street_address = processFields(result.get("street_address"));
                 date_modified = result.get("date_modified").toString() != null ? Long.valueOf(result.get("date_modified").toString()) : 0;
                 use_count = result.get("use_count").toString() != null ? Integer.valueOf(result.get("use_count").toString()) : 0;
                 use_date = result.get("use_date").toString() != null ? Long.valueOf(result.get("use_date").toString()) : 0;
             } else {
-                String address_line_1 = result.get("address_line_1").toString() != null ? result.get("street_address").toString() : "";
-                String address_line_2 = result.get("address_line_2").toString() != null ? result.get("address_line_2").toString() : "";
+                String address_line_1 = processFields(result.get("address_line_1"));
+                String address_line_2 = processFields(result.get("address_line_2"));
                 street_address = String.join(" ", address_line_1, address_line_2);
             }
 
@@ -948,6 +986,11 @@ private void getFormAddressArtifacts(AbstractFile webDataFile, String dbFilePath
                 otherAttributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED,
                         RecentActivityExtracterModuleFactory.getModuleName(),
                         date_modified)); //NON-NLS
+                if (fieldEncrypted) {
+                    otherAttributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT,
+                            RecentActivityExtracterModuleFactory.getModuleName(), ENCRYPTED_FIELD_MESSAGE)); //NON-NLS
+                    
+                }
             }
 
             helper.addWebFormAddress(
@@ -957,6 +1000,23 @@ private void getFormAddressArtifacts(AbstractFile webDataFile, String dbFilePath
         }
     }
 
+    /**
+     * Check the type of the object and if it is bytes then it is encrypted and return the string and
+     * set flag that field and file are encrypted
+     * @param dataValue Object to be checked, the object is from a database result set
+     * @return the actual string or an empty string
+     */
+    private String processFields(Object dataValue) {
+
+        if (dataValue instanceof byte[]) {
+            fieldEncrypted = true;
+            databaseEncrypted = true;
+        }
+        
+        return dataValue.toString() != null ? dataValue.toString() : "";
+        
+    }
+    
     private boolean isChromePreVersion30(String temps) {
         String query = "PRAGMA table_info(downloads)"; //NON-NLS
         List<HashMap<String, Object>> columns = this.dbConnect(temps, query);
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java
index c2abe0223dbf5ddd8503d95e3bf0f107481c608f..594571201861682abe9c70b1ad0eaeb28f045e48 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java
@@ -520,12 +520,13 @@ protected Collection<BlackboardAttribute> createDownloadSourceAttributes(String
      * 
      * @param context
      * @param file
+     * @param IngestJobId The ingest job id.
      * @return Newly created copy of the AbstractFile
      * @throws IOException 
      */
-    protected File createTemporaryFile(IngestJobContext context, AbstractFile file) throws IOException{
+    protected File createTemporaryFile(IngestJobContext context, AbstractFile file, long ingestJobId) throws IOException{
         Path tempFilePath = Paths.get(RAImageIngestModule.getRATempPath(
-                getCurrentCase(), getName()), file.getName() + file.getId() + file.getNameExtension());
+                getCurrentCase(), getName(), ingestJobId), file.getName() + file.getId() + file.getNameExtension());
         java.io.File tempFile = tempFilePath.toFile();
         
         try {
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java
index 3187955c10c386d4ed6de91b258f1567ebf760d1..68f280a5fa20cd7d6aef5ae9ec0290c5777f81c1 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractEdge.java
@@ -64,7 +64,6 @@
 final class ExtractEdge extends Extract {
 
     private static final Logger LOG = Logger.getLogger(ExtractEdge.class.getName());
-    private final Path moduleTempResultPath;
     private Content dataSource;
     private IngestJobContext context;
     private HashMap<String, ArrayList<String>> containersTable;
@@ -125,9 +124,8 @@ final class ExtractEdge extends Extract {
     /**
     * Extract the bookmarks, cookies, downloads and history from Microsoft Edge
     */
-    ExtractEdge() throws NoCurrentCaseException {
+    ExtractEdge() {
         super(Bundle.ExtractEdge_Module_Name());
-        moduleTempResultPath = Paths.get(RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), EDGE), EDGE_RESULT_FOLDER_NAME);
     }
 
     @Override
@@ -137,6 +135,9 @@ protected String getName() {
 
     @Override
     void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
+        String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), EDGE, context.getJobId());
+        String moduleTempResultDir = Paths.get(moduleTempDir, EDGE_RESULT_FOLDER_NAME).toString();
+        
         this.dataSource = dataSource;
         this.context = context;
         this.setFoundData(false);
@@ -186,7 +187,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
         }
 
         try {
-            this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar);
+            this.processWebCacheDbFile(esedumper, webCacheFiles, progressBar, moduleTempDir, moduleTempResultDir);
         } catch (IOException | TskCoreException ex) {
             LOG.log(Level.SEVERE, "Error processing 'WebCacheV01.dat' files for Microsoft Edge", ex); // NON-NLS
             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_webcacheFail());
@@ -194,7 +195,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
 
         progressBar.progress(Bundle.Progress_Message_Edge_Bookmarks());
         try {
-            this.processSpartanDbFile(esedumper, spartanFiles);
+            this.processSpartanDbFile(esedumper, spartanFiles, moduleTempDir, moduleTempResultDir);
         } catch (IOException | TskCoreException ex) {
             LOG.log(Level.SEVERE, "Error processing 'spartan.edb' files for Microsoft Edge", ex); // NON-NLS
             this.addErrorMessage(Bundle.ExtractEdge_process_errMsg_spartanFail());
@@ -207,10 +208,13 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
      *
      * @param eseDumperPath Path to ESEDatabaseView.exe
      * @param webCacheFiles List of case WebCacheV01.dat files
+     * @param moduleTempDir The temp directory for this module.
+     * @param moduleTempResultDir The temp results directory for this module.
      * @throws IOException
      * @throws TskCoreException
      */
-    void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar) throws IOException, TskCoreException {
+    void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFiles, DataSourceIngestModuleProgress progressBar, 
+            String moduleTempDir, String moduleTempResultDir) throws IOException, TskCoreException {
 
         for (AbstractFile webCacheFile : webCacheFiles) {
 
@@ -223,7 +227,7 @@ void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFile
             //Run the dumper 
             String tempWebCacheFileName = EDGE_WEBCACHE_PREFIX
                     + Integer.toString((int) webCacheFile.getId()) + EDGE_WEBCACHE_EXT; //NON-NLS
-            File tempWebCacheFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempWebCacheFileName);
+            File tempWebCacheFile = new File(moduleTempDir, tempWebCacheFileName);
 
             try {
                 ContentUtils.writeToFile(webCacheFile, tempWebCacheFile,
@@ -232,7 +236,7 @@ void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFile
                 throw new IOException("Error writingToFile: " + webCacheFile, ex); //NON-NLS
             }
 
-            File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) webCacheFile.getId()));
+            File resultsDir = new File(moduleTempDir, Integer.toString((int) webCacheFile.getId()));
             resultsDir.mkdirs();
             try {
                 executeDumper(eseDumperPath, tempWebCacheFile.getAbsolutePath(),
@@ -267,10 +271,13 @@ void processWebCacheDbFile(String eseDumperPath, List<AbstractFile> webCacheFile
      *
      * @param eseDumperPath Path to ESEDatabaseViewer
      * @param spartanFiles List of the case spartan.edb files
+     * @param moduleTempDir The temp directory for this module.
+     * @param moduleTempResultDir The temp results directory for this module.
      * @throws IOException
      * @throws TskCoreException
      */
-    void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles) throws IOException, TskCoreException {
+    void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles, 
+            String moduleTempDir, String moduleTempResultDir) throws IOException, TskCoreException {
 
         for (AbstractFile spartanFile : spartanFiles) {
 
@@ -281,7 +288,7 @@ void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles)
             //Run the dumper 
             String tempSpartanFileName = EDGE_WEBCACHE_PREFIX
                     + Integer.toString((int) spartanFile.getId()) + EDGE_WEBCACHE_EXT; 
-            File tempSpartanFile = new File(RAImageIngestModule.getRATempPath(currentCase, EDGE), tempSpartanFileName);
+            File tempSpartanFile = new File(moduleTempDir, tempSpartanFileName);
 
             try {
                 ContentUtils.writeToFile(spartanFile, tempSpartanFile,
@@ -290,7 +297,7 @@ void processSpartanDbFile(String eseDumperPath, List<AbstractFile> spartanFiles)
                 throw new IOException("Error writingToFile: " + spartanFile, ex); //NON-NLS
             }
 
-            File resultsDir = new File(moduleTempResultPath.toAbsolutePath() + Integer.toString((int) spartanFile.getId()));
+            File resultsDir = new File(moduleTempResultDir, Integer.toString((int) spartanFile.getId()));
             resultsDir.mkdirs();
             try {
                 executeDumper(eseDumperPath, tempSpartanFile.getAbsolutePath(),
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
index 58f46721187b6ff80b7fcf77e787e4c07f81aa38..29754fcdcb93a5e9b8c4e4ea9b5fca52f87918ee 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractIE.java
@@ -31,6 +31,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.file.Paths;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -43,7 +44,6 @@
 import org.openide.modules.InstalledFileLocator;
 import org.openide.util.NbBundle.Messages;
 import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
 import org.sleuthkit.autopsy.casemodule.services.FileManager;
 import org.sleuthkit.autopsy.datamodel.ContentUtils;
 import org.sleuthkit.datamodel.BlackboardArtifact;
@@ -67,7 +67,6 @@
 class ExtractIE extends Extract {
 
     private static final Logger logger = Logger.getLogger(ExtractIE.class.getName());
-    private final String moduleTempResultsDir;
     private String PASCO_LIB_PATH;
     private final String JAVA_PATH;
     private static final String RESOURCE_URL_PREFIX = "res://";
@@ -84,14 +83,16 @@ class ExtractIE extends Extract {
         "Progress_Message_IE_AutoFill=IE Auto Fill",
         "Progress_Message_IE_Logins=IE Logins",})
 
-    ExtractIE() throws NoCurrentCaseException {
+    ExtractIE() {
         super(NbBundle.getMessage(ExtractIE.class, "ExtractIE.moduleName.text"));
-        moduleTempResultsDir = RAImageIngestModule.getRATempPath(Case.getCurrentCaseThrows(), "IE") + File.separator + "results"; //NON-NLS
         JAVA_PATH = PlatformUtil.getJavaPath();
     }
 
     @Override
     public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
+        String moduleTempDir = RAImageIngestModule.getRATempPath(getCurrentCase(), "IE", context.getJobId());
+        String moduleTempResultsDir = Paths.get(moduleTempDir, "results").toString();
+                
         this.dataSource = dataSource;
         this.context = context;
         dataFound = false;
@@ -111,7 +112,7 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge
         }
 
         progressBar.progress(Bundle.Progress_Message_IE_History());
-        this.getHistory();
+        this.getHistory(moduleTempDir, moduleTempResultsDir);
     }
 
     /**
@@ -297,8 +298,10 @@ private void getCookie() {
 
     /**
      * Locates index.dat files, runs Pasco on them, and creates artifacts.
+     * @param moduleTempDir The path to the module temp directory.
+     * @param moduleTempResultsDir The path to the module temp results directory.
      */
-    private void getHistory() {
+    private void getHistory(String moduleTempDir, String moduleTempResultsDir) {
         logger.log(Level.INFO, "Pasco results path: {0}", moduleTempResultsDir); //NON-NLS
         boolean foundHistory = false;
 
@@ -350,7 +353,7 @@ private void getHistory() {
             //BlackboardArtifact bbart = fsc.newArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY);
             indexFileName = "index" + Integer.toString((int) indexFile.getId()) + ".dat"; //NON-NLS
             //indexFileName = "index" + Long.toString(bbart.getArtifactID()) + ".dat";
-            temps = RAImageIngestModule.getRATempPath(currentCase, "IE") + File.separator + indexFileName; //NON-NLS
+            temps = moduleTempDir + File.separator + indexFileName; //NON-NLS
             File datFile = new File(temps);
             if (context.dataSourceIngestIsCancelled()) {
                 break;
@@ -366,7 +369,7 @@ private void getHistory() {
             }
 
             String filename = "pasco2Result." + indexFile.getId() + ".txt"; //NON-NLS
-            boolean bPascProcSuccess = executePasco(temps, filename);
+            boolean bPascProcSuccess = executePasco(temps, filename, moduleTempResultsDir);
             if (context.dataSourceIngestIsCancelled()) {
                 return;
             }
@@ -375,7 +378,7 @@ private void getHistory() {
             //Now fetch the results, parse them and the delete the files.
             if (bPascProcSuccess) {
                 // Don't add TSK_OS_ACCOUNT artifacts to the ModuleDataEvent
-                bbartifacts.addAll(parsePascoOutput(indexFile, filename).stream()
+                bbartifacts.addAll(parsePascoOutput(indexFile, filename, moduleTempResultsDir).stream()
                         .filter(bbart -> bbart.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID())
                         .collect(Collectors.toList()));
                 if (context.dataSourceIngestIsCancelled()) {
@@ -402,6 +405,7 @@ private void getHistory() {
      *
      * @param indexFilePath  Path to local index.dat file to analyze
      * @param outputFileName Name of file to save output to
+     * @param moduleTempResultsDir the path to the module temp directory.
      *
      * @return false on error
      */
@@ -409,7 +413,7 @@ private void getHistory() {
         "# {0} - sub module name", 
         "ExtractIE_executePasco_errMsg_errorRunningPasco={0}: Error analyzing Internet Explorer web history",
     })
-    private boolean executePasco(String indexFilePath, String outputFileName) {
+    private boolean executePasco(String indexFilePath, String outputFileName, String moduleTempResultsDir) {
         boolean success = true;
         try {
             final String outputFileFullPath = moduleTempResultsDir + File.separator + outputFileName;
@@ -451,10 +455,11 @@ private boolean executePasco(String indexFilePath, String outputFileName) {
      * @param origFile            Original index.dat file that was analyzed to
      *                            get this output
      * @param pascoOutputFileName name of pasco output file
+     * @param moduleTempResultsDir the path to the module temp directory.
      *
      * @return A collection of created artifacts
      */
-    private Collection<BlackboardArtifact> parsePascoOutput(AbstractFile origFile, String pascoOutputFileName) {
+    private Collection<BlackboardArtifact> parsePascoOutput(AbstractFile origFile, String pascoOutputFileName, String moduleTempResultsDir) {
 
         Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
         String fnAbs = moduleTempResultsDir + File.separator + pascoOutputFileName;
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
index 32312c2ee1ea28550c188c82fb4dc83dc7522e6b..beba8feb6d79bdf400054efedf69647a8a6c6186 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
@@ -42,6 +42,7 @@
 import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
 import org.sleuthkit.autopsy.casemodule.services.FileManager;
 import org.sleuthkit.autopsy.coreutils.ExecUtil;
+import static org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName;
 import org.sleuthkit.autopsy.coreutils.Logger;
 import org.sleuthkit.autopsy.coreutils.PlatformUtil;
 import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
@@ -91,6 +92,7 @@ final class ExtractPrefetch extends Extract {
     void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
 
         this.context = context;
+        long ingestJobId = context.getJobId();
 
         String modOutPath = Case.getCurrentCase().getModuleDirectory() + File.separator + PREFETCH_DIR_NAME;
         File dir = new File(modOutPath);
@@ -102,7 +104,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
             }
         }
 
-        extractPrefetchFiles(dataSource);
+        extractPrefetchFiles(dataSource, ingestJobId);
 
         final String prefetchDumper = getPathForPrefetchDumper();
         if (prefetchDumper == null) {
@@ -116,9 +118,12 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
 
         String modOutFile = modOutPath + File.separator + dataSource.getName() + "-" + PREFETCH_PARSER_DB_FILE;
         try {
-            String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME);
+            String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME, ingestJobId);
             parsePrefetchFiles(prefetchDumper, tempDirPath, modOutFile, modOutPath);
-            createAppExecArtifacts(modOutFile, dataSource);
+            File prefetchDatabase = new File(modOutFile);
+            if (prefetchDatabase.exists()) {
+                createAppExecArtifacts(modOutFile, dataSource);
+            }
         } catch (IOException ex) {
             logger.log(Level.SEVERE, "Error parsing prefetch files", ex); //NON-NLS 
             addErrorMessage(Bundle.ExtractPrefetch_errMsg_prefetchParsingFailed(Bundle.ExtractPrefetch_module_name()));
@@ -131,7 +136,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
      *
      * @param dataSource - datasource to search for prefetch files
      */
-    void extractPrefetchFiles(Content dataSource) {
+    void extractPrefetchFiles(Content dataSource, long ingestJobId) {
         List<AbstractFile> pFiles;
 
         FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
@@ -153,8 +158,8 @@ void extractPrefetchFiles(Content dataSource) {
                 String origFileName = pFile.getName();
                 String ext = FilenameUtils.getExtension(origFileName);
                 String baseName = FilenameUtils.getBaseName(origFileName);
-                String fileName = String.format("%s_%d.%s", baseName, pFile.getId(), ext);
-                String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME);
+                String fileName = escapeFileName(String.format("%s_%d.%s", baseName, pFile.getId(), ext));
+                String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME, ingestJobId);
                 String prefetchFile =  Paths.get(baseRaTempPath, fileName).toString();
                 try {
                     ContentUtils.writeToFile(pFile, new File(prefetchFile));
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java
index 1ced615661b8948c6a13f772647a136cd33d764e..5d727f5b7d2e02a70c05c6ae429ca35fd4a71020 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java
@@ -136,7 +136,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
             return;  // No need to continue
         }
 
-        String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin"); //NON-NLS
+        String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "recyclebin", context.getJobId()); //NON-NLS
 
         // cycle through the $I files and process each. 
         for (AbstractFile iFile : iFiles) {
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
index a1048d6df67f1e6ba4152ccfd4d0b7d14619754a..c692ff77fb17e51b37eb9ecba4a09767add8402a 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java
@@ -294,14 +294,15 @@ private List<AbstractFile> findRegistryFiles() {
     /**
      * Identifies registry files in the database by mtimeItem, runs regripper on
      * them, and parses the output.
+     * @param ingestJobId The ingest job id.
      */
-    private void analyzeRegistryFiles() {
+    private void analyzeRegistryFiles(long ingestJobId) {
         List<AbstractFile> allRegistryFiles = findRegistryFiles();
 
         // open the log file
         FileWriter logFile = null;
         try {
-            logFile = new FileWriter(RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + "regripper-info.txt"); //NON-NLS
+            logFile = new FileWriter(RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + "regripper-info.txt"); //NON-NLS
         } catch (IOException ex) {
             logger.log(Level.SEVERE, null, ex);
         }
@@ -313,8 +314,8 @@ private void analyzeRegistryFiles() {
             
             String regFileName = regFile.getName();
             long regFileId = regFile.getId();
-            String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + regFileName;
-            String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg") + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS
+            String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg", ingestJobId) + File.separator + regFileName;
+            String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS
             File regFileNameLocalFile = new File(regFileNameLocal);
             try {
                 ContentUtils.writeToFile(regFile, regFileNameLocalFile, context::dataSourceIngestIsCancelled);
@@ -366,7 +367,7 @@ private void analyzeRegistryFiles() {
             // create a report for the full output
             if (!regOutputFiles.fullPlugins.isEmpty()) {
                 //parse the full regripper output from SAM hive files
-                if (regFileNameLocal.toLowerCase().contains("sam") && parseSamPluginOutput(regOutputFiles.fullPlugins, regFile) == false) {
+                if (regFileNameLocal.toLowerCase().contains("sam") && parseSamPluginOutput(regOutputFiles.fullPlugins, regFile, ingestJobId) == false) {
                     this.addErrorMessage(
                             NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
                                     this.getName(), regFileName));     
@@ -806,7 +807,7 @@ private boolean parseAutopsyPluginOutput(String regFilePath, AbstractFile regFil
                                         try {
                                             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, parentModuleName, value));
                                             bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, parentModuleName, itemMtime));
-                                            BlackboardArtifact bbart = regFile.newArtifact(ARTIFACT_TYPE.TSK_INSTALLED_PROG);
+                                            BlackboardArtifact bbart = regFile.newArtifact(ARTIFACT_TYPE.TSK_DELETED_PROG);
                                             bbart.addAttributes(bbattributes);
 
                                             newArtifacts.add(bbart);
@@ -1049,11 +1050,12 @@ private void addBlueToothAttribute(String line, Collection<BlackboardAttribute>
      *
      * @param regFilePath     the path to the registry file being parsed
      * @param regAbstractFile the file to associate newly created artifacts with
+     * @param ingestJobId     The ingest job id.
      *
      * @return true if successful, false if parsing failed at some point
      */
-    private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile) {
-        parseSystemHostDomain();
+    private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile, long ingestJobId) {
+        parseSystemHostDomain(ingestJobId);
         
         File regfile = new File(regFilePath);
         List<BlackboardArtifact> newArtifacts = new ArrayList<>();
@@ -1103,7 +1105,7 @@ private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstrac
             
             //add remaining userinfos as accounts;
             for (Map<String, String> userInfo : userInfoMap.values()) {
-                OsAccount osAccount = accountMgr.newWindowsOsAccount(userInfo.get(SID_KEY), null, domainName, host, domainName != null || !domainName.isEmpty() ? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
+                OsAccount osAccount = accountMgr.newWindowsOsAccount(userInfo.get(SID_KEY), null, domainName, host, domainName != null && !domainName.isEmpty() ? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
                 accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED);
                 updateOsAccount(osAccount, userInfo, groupMap.get(userInfo.get(SID_KEY)), regAbstractFile);
             }
@@ -1139,14 +1141,15 @@ private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstrac
     
     /**
      *  Finds the Host and Domain information from the registry.
+     * @param ingestJobId The ingest job id.
      */
-    private void parseSystemHostDomain() {
+    private void parseSystemHostDomain(long ingestJobId) {
         List<AbstractFile> regFiles = findRegistryFiles();
 
         for (AbstractFile systemHive: regFiles) {
-            if (systemHive.getName().toLowerCase().equals("system")) {
+            if (systemHive.getName().toLowerCase().equals("system")  && systemHive.getSize() > 0) {
                 
-                String systemFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + systemHive.getName();
+                String systemFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg", ingestJobId) + File.separator + "Domain-" + systemHive.getName();
                 File systemFileNameLocalFile = new File(systemFileNameLocal);
         
                 if (!systemFileNameLocalFile.exists()) {
@@ -1989,7 +1992,7 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge
         this.context = context;
 
         progressBar.progress(Bundle.Progress_Message_Analyze_Registry());
-        analyzeRegistryFiles();
+        analyzeRegistryFiles(context.getJobId());
 
     }
 
@@ -2022,7 +2025,7 @@ private void createOrUpdateOsAccount(AbstractFile file, String sid, String userN
         Optional<OsAccount> optional = accountMgr.getWindowsOsAccount(sid, null, null, host);
         OsAccount osAccount;
         if (!optional.isPresent()) {
-            osAccount = accountMgr.newWindowsOsAccount(sid, userName != null && userName.isEmpty() ? null : userName, domainName, host, domainName != null || !domainName.isEmpty()? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
+            osAccount = accountMgr.newWindowsOsAccount(sid, userName != null && userName.isEmpty() ? null : userName, domainName, host, domainName != null && !domainName.isEmpty()? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
             accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED);
         } else {
             osAccount = optional.get();
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
index 15575ab61d0d819603b89c2094ef667ded7a358f..2bf0351542226b12ba9968c2802a364b8c08f894 100755
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSafari.java
@@ -289,7 +289,7 @@ private void getHistory(IngestJobContext context, AbstractFile historyFile) thro
             return;
         }
 
-        File tempHistoryFile = createTemporaryFile(context, historyFile);
+        File tempHistoryFile = createTemporaryFile(context, historyFile, context.getJobId());
 
         try {
             ContentUtils.writeToFile(historyFile, tempHistoryFile, context::dataSourceIngestIsCancelled);
@@ -324,7 +324,7 @@ private void getBookmarks(IngestJobContext context, AbstractFile file) throws Ts
             return;
         }
 
-        File tempFile = createTemporaryFile(context, file);
+        File tempFile = createTemporaryFile(context, file, context.getJobId());
 
         try {
             if(!context.dataSourceIngestIsCancelled()) {
@@ -354,7 +354,7 @@ private void getDownloads(Content dataSource, IngestJobContext context, Abstract
             return;
         }
 
-        File tempFile = createTemporaryFile(context, file);
+        File tempFile = createTemporaryFile(context, file, context.getJobId());
 
         try {
             if(!context.dataSourceIngestIsCancelled()) {
@@ -385,7 +385,7 @@ private void getCookies(IngestJobContext context, AbstractFile file) throws TskC
         File tempFile = null;
 
         try {
-            tempFile = createTemporaryFile(context, file);
+            tempFile = createTemporaryFile(context, file, context.getJobId());
 
             if(!context.dataSourceIngestIsCancelled()) {
                 postArtifacts(getCookieArtifacts(file, tempFile, context));
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java
index 53b65951f9718b4d40f8985c4354fb3a5253c46c..50c2c6afd4b37fa9bd9150f5633e34ac0a5244c2 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java
@@ -100,7 +100,7 @@ void process(Content dataSource, IngestJobContext context, DataSourceIngestModul
             dir.mkdirs();
         }
 
-        String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "sru"); //NON-NLS
+        String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "sru", context.getJobId()); //NON-NLS
         String softwareHiveFileName = getSoftwareHiveFile(dataSource, tempDirPath);
 
         if (softwareHiveFileName == null) {
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
index c4a472a350d103ff703d899023432985c733c67d..6dd30ed4cc673d135b95da5e8bedfd4f0fd68b0f 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
@@ -108,47 +108,52 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge
         this.dataSource = dataSource;
         this.context = context;
         dataFound = false;
+        long ingestJobId = context.getJobId();
         
         progressBar.progress(Bundle.Progress_Message_Firefox_History());
-        this.getHistory();
+        this.getHistory(context.getJobId());
         
         if (context.dataSourceIngestIsCancelled()) {
             return;
         }
         
         progressBar.progress(Bundle.Progress_Message_Firefox_Bookmarks());
-        this.getBookmark();
+        this.getBookmark(ingestJobId);
         
         if (context.dataSourceIngestIsCancelled()) {
             return;
         }
         
         progressBar.progress(Bundle.Progress_Message_Firefox_Downloads());
-        this.getDownload();
+        this.getDownload(ingestJobId);
         
         if (context.dataSourceIngestIsCancelled()) {
             return;
         }
         
         progressBar.progress(Bundle.Progress_Message_Firefox_Cookies());
-        this.getCookie();
+        this.getCookie(ingestJobId);
         
         if (context.dataSourceIngestIsCancelled()) {
             return;
         }
         
         progressBar.progress(Bundle.Progress_Message_Firefox_FormHistory());
-        this.getFormsHistory();
+        this.getFormsHistory(ingestJobId);
         
         if (context.dataSourceIngestIsCancelled()) {
             return;
         }
         
         progressBar.progress(Bundle.Progress_Message_Firefox_AutoFill());
-        this.getAutofillProfiles();
+        this.getAutofillProfiles(ingestJobId);
     }
 
-    private void getHistory() {
+    /**
+     * Get Firefox history.
+     * @param ingestJobId The ingest job id.
+     */
+    private void getHistory(long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> historyFiles;
         try {
@@ -180,7 +185,7 @@ private void getHistory() {
             }
 
             String fileName = historyFile.getName();
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(historyFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -254,8 +259,9 @@ private void getHistory() {
 
     /**
      * Queries for bookmark files and adds artifacts
+     * @param ingestJobId The ingest job id.
      */
-    private void getBookmark() {
+    private void getBookmark(long ingestJobId) {
 
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> bookmarkFiles;
@@ -281,7 +287,7 @@ private void getBookmark() {
                 continue;
             }
             String fileName = bookmarkFile.getName();
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -351,8 +357,9 @@ private void getBookmark() {
 
     /**
      * Queries for cookies file and adds artifacts
+     * @param ingestJobId The ingest job id.
      */
-    private void getCookie() {
+    private void getCookie(long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> cookiesFiles;
         try {
@@ -381,7 +388,7 @@ private void getCookie() {
                 continue;
             }
             String fileName = cookiesFile.getName();
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -468,18 +475,20 @@ private void getCookie() {
 
     /**
      * Queries for downloads files and adds artifacts
+     * @param ingestJobId The ingest job id.
      */
-    private void getDownload() {
-        getDownloadPreVersion24();
-        getDownloadVersion24();
+    private void getDownload(long ingestJobId) {
+        getDownloadPreVersion24(ingestJobId);
+        getDownloadVersion24(ingestJobId);
     }
 
     /**
      * Finds downloads artifacts from Firefox data from versions before 24.0.
      *
      * Downloads were stored in a separate downloads database.
+     * @param ingestJobId The ingest job id.
      */
-    private void getDownloadPreVersion24() {
+    private void getDownloadPreVersion24(long ingestJobId) {
 
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> downloadsFiles;
@@ -505,7 +514,7 @@ private void getDownloadPreVersion24() {
                 continue;
             }
             String fileName = downloadsFile.getName();
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
             int errors = 0;
             try {
                 ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled);
@@ -611,8 +620,9 @@ private void getDownloadPreVersion24() {
      * Gets download artifacts from Firefox data from version 24.
      *
      * Downloads are stored in the places database.
+     * @param ingestJobId The ingest job id.
      */
-    private void getDownloadVersion24() {
+    private void getDownloadVersion24(long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> downloadsFiles;
         try {
@@ -637,7 +647,7 @@ private void getDownloadVersion24() {
                 continue;
             }
             String fileName = downloadsFile.getName();
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + "-downloads" + j + ".db"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + "-downloads" + j + ".db"; //NON-NLS
             int errors = 0;
             try {
                 ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled);
@@ -742,8 +752,9 @@ private void getDownloadVersion24() {
     /**
      * Gets data from formshistory.sqlite database.
      * Parses and creates artifacts.
+     * @param ingestJobId The ingest job id.
      */
-    private void getFormsHistory() {
+    private void getFormsHistory(long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> formHistoryFiles;
        
@@ -777,7 +788,7 @@ private void getFormsHistory() {
             }
 
             String fileName = formHistoryFile.getName();
-            String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "firefox") + File.separator + fileName + j + ".db"; //NON-NLS
+            String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
             try {
                 ContentUtils.writeToFile(formHistoryFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
@@ -864,9 +875,9 @@ private void getFormsHistory() {
     /**
      * Gets data from autofill-profiles.json file. 
      * Parses file and makes artifacts.
-     * 
+     * @param ingestJobId The ingest job id.
      */
-    private void getAutofillProfiles() {
+    private void getAutofillProfiles(long ingestJobId) {
         FileManager fileManager = currentCase.getServices().getFileManager();
         List<AbstractFile> autofillProfilesFiles;
         try {
@@ -891,7 +902,7 @@ private void getAutofillProfiles() {
             if (profileFile.getSize() == 0) {
                 continue;
             }
-            String temps = RAImageIngestModule.getRATempPath(currentCase, "Firefox") + File.separator + profileFile.getName() + j + ".json"; //NON-NLS
+            String temps = RAImageIngestModule.getRATempPath(currentCase, "Firefox", ingestJobId) + File.separator + profileFile.getName() + j + ".json"; //NON-NLS
             try {
                 ContentUtils.writeToFile(profileFile, new File(temps), context::dataSourceIngestIsCancelled);
             } catch (ReadContentInputStreamException ex) {
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java
index 2048313c43f1b514d8b87cb49dc495c122321c4b..b02489112c003b2a478850506a7f0b9a9e431341 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java
@@ -27,6 +27,7 @@
 import java.io.UnsupportedEncodingException;
 import java.util.Calendar;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.logging.Level;
 import org.sleuthkit.autopsy.coreutils.Logger;
 
@@ -116,6 +117,9 @@ private RegistryKey findRegistryKey(RegistryHiveFile registryHiveFile, String re
             }
         } catch (RegistryParseException ex) {
             return null;
+        } catch (NoSuchElementException ex) {
+            logger.log(Level.WARNING, String.format("Cannot find the registry key %s in the registry hive file %s", registryKey, registryHiveFile.toString()));
+            return null;
         }
         return currentKey;   
 
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java
index b92ebffa98f9788fee70347bfe2907216284b2f3..a18ced587e992ece1a51ba731bfe56157dbd46db 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java
@@ -23,15 +23,16 @@
 package org.sleuthkit.autopsy.recentactivity;
 
 import java.io.File;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.logging.Level;
 import org.openide.util.NbBundle;
 import org.sleuthkit.autopsy.casemodule.Case;
 import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
 import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
 import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
 import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
 import org.sleuthkit.autopsy.ingest.IngestServices;
@@ -41,7 +42,6 @@
 import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult;
 import org.sleuthkit.autopsy.ingest.IngestJobContext;
 import org.sleuthkit.datamodel.DataSource;
-import org.sleuthkit.datamodel.OsAccount;
 import org.sleuthkit.datamodel.SleuthkitCase;
 
 /**
@@ -49,6 +49,7 @@
  */
 public final class RAImageIngestModule implements DataSourceIngestModule {
 
+    private static final String RECENT_ACTIVITY_FOLDER = "RecentActivity";
     private static final Logger logger = Logger.getLogger(RAImageIngestModule.class.getName());
     private final List<Extract> extractors = new ArrayList<>();
     private final List<Extract> browserExtractors = new ArrayList<>();
@@ -64,18 +65,11 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
     @Override
     public void startUp(IngestJobContext context) throws IngestModuleException {
         this.context = context;
-        
-        tskCase = Case.getCurrentCase().getSleuthkitCase();
 
-        Extract iexplore;
-        Extract edge;
-        try {
-            iexplore = new ExtractIE();
-            edge = new ExtractEdge();
-        } catch (NoCurrentCaseException ex) {
-            throw new IngestModuleException(ex.getMessage(), ex);
-        }
+        tskCase = Case.getCurrentCase().getSleuthkitCase();
 
+        Extract iexplore = new ExtractIE();
+        Extract edge = new ExtractEdge();
         Extract registry = new ExtractRegistry();
         Extract recentDocuments = new RecentDocumentsByLnk();
         Extract chrome = new Chromium();
@@ -104,10 +98,10 @@ public void startUp(IngestJobContext context) throws IngestModuleException {
         extractors.add(webAccountType); // this needs to run after the web browser modules
         extractors.add(zoneInfo); // this needs to run after the web browser modules
         extractors.add(recycleBin); // this needs to run after ExtractRegistry and ExtractOS
-        extractors.add(sru); 
+        extractors.add(sru);
         extractors.add(prefetch);
         extractors.add(messageDomainType);
-        
+
         browserExtractors.add(chrome);
         browserExtractors.add(firefox);
         browserExtractors.add(iexplore);
@@ -141,8 +135,8 @@ public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress
 
             try {
                 extracter.process(dataSource, context, progressBar, accountCache);
-                if(extracter instanceof ExtractRegistry) {
-                    accountCache.initialize(tskCase, ((DataSource)dataSource).getHost());
+                if (extracter instanceof ExtractRegistry) {
+                    accountCache.initialize(tskCase, ((DataSource) dataSource).getHost());
                 }
             } catch (Exception ex) {
                 logger.log(Level.SEVERE, "Exception occurred in " + extracter.getName(), ex); //NON-NLS
@@ -221,23 +215,39 @@ public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress
         return ProcessResult.OK;
     }
 
+    /**
+     * Makes a path of the format
+     * [basePath]/[RECENT_ACTIVITY_FOLDER]/[module]_[ingest job id] if it does not
+     * already exist and returns the created folder.
+     *
+     * @param basePath The base path (a case-related folder like temp or
+     * output).
+     * @param module The module name to include in the folder name.
+     * @param ingestJobId The id of the ingest job.
+     * @return The path to the folder.
+     */
+    private static String getAndMakeRAPath(String basePath, String module, long ingestJobId) {
+        String moduleFolder = String.format("%s_%d", module, ingestJobId);
+        Path tmpPath = Paths.get(basePath, RECENT_ACTIVITY_FOLDER, moduleFolder);
+        File dir = tmpPath.toFile();
+        if (dir.exists() == false) {
+            dir.mkdirs();
+        }
+        return tmpPath.toString();
+    }
+
     /**
      * Get the temp path for a specific sub-module in recent activity. Will
      * create the dir if it doesn't exist.
      *
      * @param a_case Case that directory is for
-     * @param mod    Module name that will be used for a sub folder in the temp
-     *               folder to prevent name collisions
+     * @param mod Module name that will be used for a sub folder in the temp
+     * folder to prevent name collisions
      *
      * @return Path to directory
      */
-    static String getRATempPath(Case a_case, String mod) {
-        String tmpDir = a_case.getTempDirectory() + File.separator + "RecentActivity" + File.separator + mod; //NON-NLS
-        File dir = new File(tmpDir);
-        if (dir.exists() == false) {
-            dir.mkdirs();
-        }
-        return tmpDir;
+    static String getRATempPath(Case a_case, String mod, long ingestJobId) {
+        return getAndMakeRAPath(a_case.getTempDirectory(), mod, ingestJobId);
     }
 
     /**
@@ -245,28 +255,24 @@ static String getRATempPath(Case a_case, String mod) {
      * create the dir if it doesn't exist.
      *
      * @param a_case Case that directory is for
-     * @param mod    Module name that will be used for a sub folder in the temp
-     *               folder to prevent name collisions
+     * @param mod Module name that will be used for a sub folder in the temp
+     * folder to prevent name collisions
      *
      * @return Path to directory
      */
-    static String getRAOutputPath(Case a_case, String mod) {
-        String tmpDir = a_case.getModuleDirectory() + File.separator + "RecentActivity" + File.separator + mod; //NON-NLS
-        File dir = new File(tmpDir);
-        if (dir.exists() == false) {
-            dir.mkdirs();
-        }
-        return tmpDir;
+    static String getRAOutputPath(Case a_case, String mod, long ingestJobId) {
+        return getAndMakeRAPath(a_case.getModuleDirectory(), mod, ingestJobId);
     }
-    
+
     /**
      * Get relative path for module output folder.
      *
      * @throws NoCurrentCaseException if there is no open case.
      * @return the relative path of the module output folder
      */
-    static String getRelModuleOutputPath() throws NoCurrentCaseException {
-        return Paths.get(Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath(), 
-                            "RecentActivity").normalize().toString() ;  //NON-NLS
+    static String getRelModuleOutputPath(Case autCase, String mod, long ingestJobId) {
+        return Paths.get(getAndMakeRAPath(autCase.getModuleOutputDirectoryRelativePath(), mod, ingestJobId))
+                .normalize()
+                .toString();
     }
 }
diff --git a/apidiff.py b/apidiff.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b69d6f59a30f5917b4d57ead2c29f89e5c34ee7
--- /dev/null
+++ b/apidiff.py
@@ -0,0 +1,295 @@
+"""
+Generates an api diff from one commit to another.  This script relies on gitpython and similarly require git
+installed on the system.  This script also requires python 3.
+
+This script can be called as follows:
+
+python apidiff.py <previous tag id> <latest tag id> -r <repo path> -o <output path>
+
+If the '-o' flag is not specified, this script will create a folder at apidiff_output in the same directory as the
+script.  For full list of options call:
+
+python apidiff.py -h
+"""
+
+import os
+import subprocess
+import sys
+import time
+from pathlib import Path
+from typing import Tuple, Iterator, List
+
+import argparse as argparse
+from git import Repo, Blob, Tree
+
+"""
+These are exit codes for jdiff:
+return code 1   = error in jdiff
+return code 100 = no changes
+return code 101 = compatible changes
+return code 102 = incompatible changes
+"""
+NO_CHANGES = 100
+COMPATIBLE = 101
+NON_COMPATIBLE = 102
+ERROR = 1
+
+
+def compare_xml(jdiff_path: str, root_dir: str, output_folder: str, oldapi_folder: str,
+                newapi_folder: str, api_file_name: str, log_path: str) -> int:
+    """
+    Compares xml generated by jdiff using jdiff.
+    :param jdiff_path: Path to jdiff jar.
+    :param root_dir: directory for output .
+    :param output_folder: Folder for diff output.
+    :param oldapi_folder: Folder name of old api (i.e. release-4.10.2).
+    :param newapi_folder: Folder name of new api (i.e. release-4.10.2).
+    :param api_file_name: Name of xml file name (i.e. if output.xml, just 'output')
+    :param log_path: Path to log file.
+    :return: jdiff exit code.
+    """
+    jdiff_parent = os.path.dirname(jdiff_path)
+
+    null_file = fix_path(os.path.join(jdiff_parent, "lib", "Null.java"))
+
+    # comments are expected in a specific place
+    make_dir(os.path.join(root_dir,
+                          output_folder,
+                          f"user_comments_for_{oldapi_folder}",
+                          f"{api_file_name}_to_{newapi_folder}"))
+
+    log = open(log_path, "w")
+    cmd = ["javadoc",
+           "-doclet", "jdiff.JDiff",
+           "-docletpath", fix_path(jdiff_path),
+           "-d", fix_path(output_folder),
+           "-oldapi", fix_path(os.path.join(oldapi_folder, api_file_name)),
+           "-newapi", fix_path(os.path.join(newapi_folder, api_file_name)),
+           "-script",
+           null_file]
+
+    code = None
+    try:
+        jdiff = subprocess.Popen(cmd, stdout=log, stderr=log, cwd=root_dir)
+        jdiff.wait()
+        code = jdiff.returncode
+    except Exception as e:
+        log_and_print(log, f"Error executing javadoc: {str(e)}\nExiting...")
+        exit(1)
+    log.close()
+
+    print(f"Compared XML for {oldapi_folder} {newapi_folder}")
+    if code == NO_CHANGES:
+        print("  No API changes")
+    elif code == COMPATIBLE:
+        print("  API Changes are backwards compatible")
+    elif code == NON_COMPATIBLE:
+        print("  API Changes are not backwards compatible")
+    else:
+        print("  *Error in XML, most likely an empty module")
+    sys.stdout.flush()
+    return code
+
+
+def gen_xml(jdiff_path: str, output_path: str, log_output_path: str, src: str, packages: List[str]):
+    """
+    Uses jdiff to generate an xml representation of the source code.
+    :param jdiff_path: Path to jdiff jar.
+    :param output_path: Path to output path of diff.
+    :param log_output_path: The log output path.
+    :param src: The path to the source code.
+    :param packages: The packages to process.
+    """
+    make_dir(output_path)
+
+    log = open_log_file(log_output_path)
+    log_and_print(log, f"Generating XML for: {src} outputting to: {output_path}")
+    cmd = ["javadoc",
+           "-doclet", "jdiff.JDiff",
+           "-docletpath", fix_path(jdiff_path),
+           "-apiname", fix_path(output_path),
+           "-sourcepath", fix_path(src)]
+    cmd = cmd + packages
+    try:
+        jdiff = subprocess.Popen(cmd, stdout=log, stderr=log)
+        jdiff.wait()
+    except Exception as e:
+        log_and_print(log, f"Error executing javadoc {str(e)}\nExiting...")
+        exit(1)
+
+    log_and_print(log, f"Generated XML for: " + str(packages))
+    log.close()
+    sys.stdout.flush()
+
+
+def _list_paths(root_tree: Tree, src_folder, path: Path = None) -> Iterator[Tuple[str, Blob]]:
+    """
+    Given the root path to serve as a prefix, walks the tree of a git commit returning all files and blobs.
+    Repurposed from: https://www.enricozini.org/blog/2019/debian/gitpython-list-all-files-in-a-git-commit/
+    Args:
+        root_tree: The tree of the commit to walk.
+        src_folder: relative path in repo to source folder that will be copied.
+        path: The path to use as a prefix.
+    Returns: A tuple iterator where each tuple consists of the path as a string and a blob of the file.
+    """
+    for blob in root_tree.blobs:
+        next_path = Path(path) / blob.name if path else blob.name
+        if Path(src_folder) in Path(next_path).parents:
+            ret_item = (next_path, blob)
+            yield ret_item
+    for tree in root_tree.trees:
+        next_path = Path(path) / tree.name if path else tree.name
+        yield from _list_paths(tree, src_folder, next_path)
+
+
+def _get_tree(repo_path: str, commit_id: str) -> Tree:
+    """
+    Retrieves the git tree that can be walked for files and file content at the specified commit.
+    Args:
+        repo_path: The path to the repo or a child directory of the repo.
+        commit_id: The commit id.
+    Returns: The tree.
+    """
+    repo = Repo(repo_path, search_parent_directories=True)
+    commit = repo.commit(commit_id.strip())
+    return commit.tree
+
+
+def copy_commit_paths(repo_path, commit_id, src_folder, output_folder):
+    """
+    Copies all files located within a repo in the folder 'src_folder' to 'output_folder'.
+    :param repo_path: The path to the repo.
+    :param commit_id: The commit id.
+    :param src_folder: The relative path in the repo to the source folder.
+    :param output_folder: The output folder where the source will be copied.
+    """
+    tree = _get_tree(repo_path, commit_id)
+    for rel_path, blob in _list_paths(tree, src_folder):
+        output_path = os.path.join(output_folder, os.path.relpath(rel_path, src_folder))
+        parent_folder = os.path.dirname(output_path)
+        make_dir(parent_folder)
+        output_file = open(output_path, 'w')
+        output_file.write(blob.data_stream.read().decode('utf-8'))
+        output_file.close()
+
+
+def open_log_file(log_path):
+    """
+    Opens a path to a lof file for appending.  Creating directories and log file as necessary.
+    :param log_path: The path to the log file.
+    :return: The log file opened for writing.
+    """
+    if not os.path.exists(log_path):
+        make_dir(os.path.dirname(log_path))
+        Path(log_path).touch()
+
+    return open(log_path, 'a+')
+
+
+def fix_path(path):
+    """
+    Generates a path that is escaped from cygwin paths if present.
+    :param path: Path (possibly including cygdrive).
+    :return: The normalized path.
+    """
+    if "cygdrive" in path:
+        new_path = path[11:]
+        return "C:/" + new_path
+    else:
+        return path
+
+
+def log_and_print(log, message):
+    """
+    Creates a log entry and prints to stdout.
+    :param log: The log file object.
+    :param message: The string to be printed.
+    """
+    time_stamp = time.strftime('%Y-%m-%d %H:%M:%S')
+    print(f"{time_stamp}: {message}")
+    log.write(f"{time_stamp}: {message}\n")
+
+
+def make_dir(dir_path: str):
+    """
+    Create the given directory, if it doesn't already exist.
+    :param dir_path: The path to the directory.
+    :return: True if created.
+    """
+    try:
+        if not os.path.isdir(dir_path):
+            os.makedirs(dir_path)
+        if os.path.isdir(dir_path):
+            return True
+        return False
+    except IOError:
+        print("Exception thrown when creating directory: " + dir_path)
+        return False
+
+
+def run_compare(output_path: str, jdiff_path: str, repo_path: str, src_rel_path: str, prev_commit_id: str,
+                latest_commit_id: str, packages: List[str]):
+    """
+    Runs a comparison of the api between two different commits/branches/tags of the same repo generating a jdiff diff.
+    :param output_path: The output path for artifacts.
+    :param jdiff_path: The path to the jdiff jar.
+    :param repo_path: The path to the repo.
+    :param src_rel_path: The relative path in the repo to the source directory.
+    :param prev_commit_id: The previous commit/branch/tag id.
+    :param latest_commit_id: The latest commit/branch/tag id.
+    :param packages: The packages to be considered for the api diff.
+    """
+    log_path = os.path.join(output_path, "messages.log")
+    output_file_name = "output"
+    diff_dir = "diff"
+    src_folder = "src"
+
+    for commit_id in [prev_commit_id, latest_commit_id]:
+        src_copy = os.path.join(output_path, src_folder, commit_id)
+        copy_commit_paths(repo_path, commit_id, src_rel_path, src_copy)
+        gen_xml(jdiff_path, os.path.join(output_path, commit_id, output_file_name), log_path, src_copy, packages)
+
+    # compare the two
+    compare_xml(jdiff_path, output_path, os.path.join(output_path, diff_dir),
+                prev_commit_id, latest_commit_id, output_file_name, log_path)
+
+
+def main():
+    parser = argparse.ArgumentParser(description="Generates a jdiff diff of the java api between two commits in a "
+                                                 "repo.",
+                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument(dest='prev_commit', type=str, help=r'The git commit id/branch/tag to be used for the first '
+                                                           r'commit')
+    parser.add_argument(dest='latest_commit', type=str, help=r'The git commit id/branch/tag to be used for the latest '
+                                                             r'commit')
+    parser.add_argument('-r', '--repo', dest='repo_path', type=str, required=True,
+                        help='The path to the repo.  If not specified, path of script is used.')
+
+    parser.add_argument('-o', '--output', dest='output_path', type=str, required=False,
+                        help='The location for output of all artifacts.  Defaults to an output folder in same directory'
+                             'as script')
+    parser.add_argument('-s', '--src', dest='src_rel_folder', type=str, required=False, default="bindings/java/src",
+                        help='The relative path within the repo of the src folder.')
+    # list of packages can be specified like this:
+    # https://stackoverflow.com/questions/15753701/how-can-i-pass-a-list-as-a-command-line-argument-with-argparse
+    parser.add_argument('-p', '--packages', dest='packages', nargs='+', required=False,
+                        default=["org.sleuthkit.datamodel"], help='The packages to consider in api diff.')
+    parser.add_argument('-j', '--jdiff', dest='jdiff_path', type=str, required=False,
+                        help='The packages to consider in api diff.')
+
+    args = parser.parse_args()
+    script_path = os.path.dirname(os.path.realpath(__file__))
+    repo_path = args.repo_path if args.repo_path else script_path
+    output_path = args.output_path if args.output_path else os.path.join(script_path, "apidiff_output")
+    jdiff_path = args.jdiff_path if args.jdiff_path else os.path.join(script_path,
+                                                                      "thirdparty/jdiff/v-custom/jdiff.jar")
+    run_compare(output_path=output_path,
+                jdiff_path=jdiff_path,
+                repo_path=repo_path,
+                packages=args.packages,
+                src_rel_path=args.src_rel_folder,
+                prev_commit_id=args.prev_commit,
+                latest_commit_id=args.latest_commit)
+
+
+main()
diff --git a/docs/doxygen-user/configuration.dox b/docs/doxygen-user/configuration.dox
index e3ef07a2acf9b3641f136b72cea24957ad7d1121..271e3592074bb18ae9ec490085ad3b7fcd9a1ebb 100644
--- a/docs/doxygen-user/configuration.dox
+++ b/docs/doxygen-user/configuration.dox
@@ -14,11 +14,16 @@ The first tab on the options panel is for general application settings.
 
 \image html options_application.png
 
-The top section lets you adjust how much memory is used by Autopsy and how many log files to keep. Generally each Autopsy session generates one log file, though it can generate more if the log file becomes too large. 
+The top section lets you adjust how much memory is used by Autopsy and how many log files to keep. Generally each Autopsy session generates one log file, though it can generate more if the log file becomes too large. You can also specify a custom location to write heap dumps to.
 
-The next section lets you specify where Autopsy should store temporary files. These files will be deleted when a case is closed. 
+The next section lets you specify where Autopsy should store temporary files. These files will be deleted when a case is closed. There are three options:
+<ul>
+<li>Local temp directory - Uses the system temp folder (On Windows, typically C:\\Users\\(user name)\\AppData\\Local\\Temp\\Autopsy)
+<li>Temp folder in case directory - Puts temporariy files in the "temp" directory in the case folder
+<li>Custom - Will use the given folder as a base for the temporary files
+</ul>
 
-The final section lets you set a custom logo. 
+The next section lets you set a custom logo. 
 
 \image html options_logo.png
 
@@ -26,6 +31,8 @@ This logo will be displayed in any generated \ref report_html "HTML reports".
 
 \image html options_logo_report.jpg
 
+The final section lists instructions on how to change scaling for high DPI Windows systems.
+
 \section config_view View Options
 
 See the \ref view_options_page page for a description of how you can customize what data is displayed in Autopsy.
diff --git a/docs/doxygen-user/content_viewer.dox b/docs/doxygen-user/content_viewer.dox
index 8a717e9c2d77b9a507406999e794bc44bc953b90..be5d5b5a337d8cf86adcd4ec56ef54524095e396 100644
--- a/docs/doxygen-user/content_viewer.dox
+++ b/docs/doxygen-user/content_viewer.dox
@@ -75,23 +75,17 @@ Registry hive files can be viewed in a format similar to a registry editor.
 
 \image html content_viewer_registry.png
 
-\section cv_message Message
-
-The Message tab shows details of emails and SMS messages. 
-
-\image html content_viewer_message.png
-
 \section cv_metadata File Metadata
 
 The File Metadata tab displays basic information about the file, such as type, size, and hash. It also displays the output of the Sleuth Kit istat tool.
 
 \image html content_viewer_metadata.png
 
-\section cv_context Context
+\section cv_os_account OS Accounts
 
-The Context tab shows information on where a file came from and allows you to navigate to the original result. For example, it can show the the URL for downloaded files and the email message a file was attached to. In the image below you can see the context for an image that was sent as an email attachment.
+The OS Accounts tab displays information on the OS account associated with a given result, if present. It is also used to give details on accounts listed under the OS Accounts node in the tree.
 
-\image html content_viewer_context.png
+\image html content_viewer_os_account.png
 
 \section cv_results Results
 
@@ -101,6 +95,12 @@ The Results tab is active when selecting items with associated results such as k
 <br>
 \image html content_viewer_results_bookmark.png
 
+\section cv_context Context
+
+The Context tab shows information on where a file came from and allows you to navigate to the original result. For example, it can show the the URL for downloaded files and the email message a file was attached to. In the image below you can see the context for an image that was sent as an email attachment.
+
+\image html content_viewer_context.png
+
 \section cv_annotations Annotations
 
 The Annotations tab shows information added by an analyst about a file or result. It displays any tags and comments associated with the file or result, and if the \ref central_repo_page is enabled it will also display any comments saved to the Central Repository.
diff --git a/docs/doxygen-user/data_source_summary.dox b/docs/doxygen-user/data_source_summary.dox
index 459dd7f6115ac6a5fc24caac3e8279ffa39f27fd..01befa9a2b113b5ec7e2940dc734bea91bae2bc5 100644
--- a/docs/doxygen-user/data_source_summary.dox
+++ b/docs/doxygen-user/data_source_summary.dox
@@ -80,4 +80,10 @@ The Container tab displays information on the data source itself, such as the si
 
 \image html ds_summary_container.png
 
+\subsection ds_summary_export Export
+
+The Export tab allows you to export the contents of the other data source summary tabs to an Excel-formatted file.
+
+\image html ds_summary_export.png
+
 */
\ No newline at end of file
diff --git a/docs/doxygen-user/data_sources.dox b/docs/doxygen-user/data_sources.dox
index f8aefc63c8c98f4d2fa07d3e55afbc98fa9b0649..81336e74ab051fbe8469e0769349a2c14e1101e5 100644
--- a/docs/doxygen-user/data_sources.dox
+++ b/docs/doxygen-user/data_sources.dox
@@ -25,6 +25,18 @@ The data source must remain accessible for the duration of the analysis because
 
 Regardless of the type of data source, there are some common steps in the process:
 <ol>
+
+<li> You will choose the host for the data source you are going to add. See the \ref host_page "hosts page" for more information about hosts.
+
+\image html data_source_host_select.png
+
+There are three options:
+<ul>
+<li> <b>Generate new host based on data source name</b> - this will typically create a host with a name similar to your data source with the ID used in the database appended for uniqueness.
+<li> <b>Specify new host name</b> - this allows you to enter a host name.
+<li> <b>Use existing host</b> - this allows you to choose a host name already in use in the current case.
+</ul>
+
 <li> You will select the type of data source.
 
 \image html select-data-source-type.PNG
diff --git a/docs/doxygen-user/file_discovery.dox b/docs/doxygen-user/file_discovery.dox
index 08ff568d9e237163b353f66212ce99fdc3487d1d..fdf700a7583350cbbd8a38064b495a4a49cfa710 100644
--- a/docs/doxygen-user/file_discovery.dox
+++ b/docs/doxygen-user/file_discovery.dox
@@ -13,7 +13,7 @@ We suggest running all \ref ingest_page "ingest modules" before launching discov
 Required ingest modules:
 <ul>
 <li>\ref file_type_identification_page for image, video, and document searches
-<li>\ref recent_activity_page or \ref ileapp_page for domain searches
+<li>\ref recent_activity_page or one of the mobile parsers (\ref android_analyzer_page, \ref ileapp_page, \ref aleapp_page) for domain searches
 </ul>
 
 Optional ingest modules:
@@ -50,7 +50,7 @@ The first step is choosing whether you want to display images, videos, documents
 
 \subsection file_disc_filtering Filtering
 
-The second step is to select and configure your filters. The available filters will vary depending on the result type. For most filters, you enable them using the checkbox on the left and then select your options. Multiple options can be selected by using CTRL + left click. Results must pass all enabled filters to be displayed.
+The second step is to select and configure your filters. The available filters will vary depending on the result type. For most filters, you enable them using the checkbox on the left and then select the checkboxes next to the options you want to be enabled. The "Check All" and "Uncheck All" buttons can be used to check or uncheck all options in the list. Results must pass all enabled filters to be displayed.
 
 \subsubsection file_disc_size_filter File Size Filter
 
@@ -132,7 +132,7 @@ The previously notable filter is for domain searches only and is used to restric
 
 \subsubsection file_disc_known_account_filter Known Account Type Filter
 
-The previously notable filter is for domain searches only and is used to restrict results to only those domains that have a known account type.
+The known account type filter is for domain searches only and is used to restrict results to only those domains that have a known account type.
 
 \image html FileDiscovery/fd_knownAccountFilter.png
 
diff --git a/docs/doxygen-user/hosts.dox b/docs/doxygen-user/hosts.dox
new file mode 100644
index 0000000000000000000000000000000000000000..1b3ff6f4e2810c9c9579838c59e9610c3d4fa81a
--- /dev/null
+++ b/docs/doxygen-user/hosts.dox
@@ -0,0 +1,50 @@
+/*! \page host_page Hosts
+
+
+[TOC]
+
+\section host_use Using Hosts
+
+\subsection host_wizard Associating a Data Source With a Host
+
+Every data source must be associated with a host. The first step in the \ref ds_add "add data source process" is to select a host for the data source you are about to add to the case. This host can be auto-generated, entered by the user, or selected from the list of hosts already present in the case.
+
+\image html data_source_host_select.png
+
+\subsection host_view Viewing Hosts
+
+Hosts are displayed in the \ref tree_viewer_page. Depending on the \ref view_options_page selected, hosts may be grouped together under persons. 
+
+\image html ui_tree_top_ds.png
+
+\subsection host_os_accounts OS Accounts
+
+OS accounts can be viewed in the OS Accounts node under Results. Each OS account is associated with a host, and the host information is displayed in the OS Account tab of the content viewer.
+
+\image html host_os_accounts.png
+
+\section host_management Managing Hosts
+
+\subsection host_menu Manage Hosts Menu
+
+Go to Case->Manage Hosts to open the host management panel.
+
+\image html manage_hosts.png
+
+Here you can see all hosts in the case, add new hosts, change the name of an existing host, and delete hosts that are not in use. 
+
+\subsection host_merge Merging Hosts
+
+Over the course of processing a case, it may become clear that two (or more) hosts should be combined. Merging one host into another will move all data sources from the source host into the destination host and move or combine any OS accounts found.
+
+
+To merge hosts, right-click on the host you want to merge into another host.
+
+\image html host_merge.png
+
+A confirmation dialog will display stating that this can not be undone. After proceeding, the hosts will be merged together and the tree viewer node will update showing the combined data.
+
+\image html host_merge_result.png
+
+
+*/
\ No newline at end of file
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png
index a1e6bfb84202909dc2eb30aa299a864358560b06..4232c8f434481f36cb1d204d9c16816f00e8cbea 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_analysis.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png
index 3d51efee735083a743421cc5a37c9760da7a9bbf..328b233dc325aa42b909a3d8dce24875f582162b 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_container.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_export.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_export.png
new file mode 100644
index 0000000000000000000000000000000000000000..da7601f32eeacb666a9638b7cd1868abf8edd114
Binary files /dev/null and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_export.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png
index 4ab4440b55035c7033fd0a8db7addef28203a03a..7e49cac8c3ee4fb5cb6cea3a0f3d3859ed7e5439 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_geo.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png
index d3a1a970a9fe7bf04ab7a947bd7a0fc7ea7a0958..6ed19b77c036aec8d22c652ab2b4c2151c5189e2 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_ingest.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png
index 3b47578b0e7b81c25fb6d62c7c803ce354e76f45..4ac2623e6ecc3bcfd52894330a0932c810527aae 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_past_cases.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png
index 3eeaeffd8958165c772410d4f42b858b1b71e146..c9edf412ff3e2480ef5414a26ca4fdd6f0694e27 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_recent_files.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png
index 1a376723d6e0e54d43584d2bf5c991c53e1a2fdf..891802da42acb1ebbd324f753dd495433a653f74 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_timeline.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png
index 2bf93d3f90ac0c60d148fe66e07b20fedefcbb45..b8dca4a984d48d45a8ee8200e494a3a05be20b07 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_types.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png
index 7b6d4d09f55258e998cd7c917beb24ba30493b06..089345f85bed8d19c58495bf1ebb814aee713118 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_user_activity.png differ
diff --git a/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png b/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png
index a9d61e908459e534131ddc52a093040ec640eacd..547f42fab89a084ad8921dfb7a2ec781b6ac045d 100644
Binary files a/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png and b/docs/doxygen-user/images/DataSourceSummary/ds_summary_window.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_dataSourceFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_dataSourceFilter.png
index 9c42198839625b75ebd861fb18826900898be251..fed4932bf087c9300e5c594df4232a8266a9d985 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_dataSourceFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_dataSourceFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_domainResultFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_domainResultFilter.png
index e1227105ee5119b46be2997d23aedbdcb1728a73..34ad45439db163caddff1f35b9225b9fc39aafef 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_domainResultFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_domainResultFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_domains.png b/docs/doxygen-user/images/FileDiscovery/fd_domains.png
index f86e07251c78fae585a9cb1c64eacba201435356..88df1ccb174be2ba9f41099906c9cb9bb4052ffb 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_domains.png and b/docs/doxygen-user/images/FileDiscovery/fd_domains.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_fileSizeFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_fileSizeFilter.png
index 1e2bdb7f849b30ae815da84c3a62a5bdc9325302..d352f941a4c4d8e6c8ffca0cb7c0893a60d043c4 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_fileSizeFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_fileSizeFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_hashSetFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_hashSetFilter.png
index 8c05e725242b069da119f1db2bbeeff3e01b53ca..b8d9a1f3fdac2ff0634b3240042f5c6564f4295b 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_hashSetFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_hashSetFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_interestingItemsFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_interestingItemsFilter.png
index 4362a904d9ccec00b3dcf696a24cadd8802b678c..df3d44357506ba2e2504a3c82cda700e71fe2480 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_interestingItemsFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_interestingItemsFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_objectFilter.png b/docs/doxygen-user/images/FileDiscovery/fd_objectFilter.png
index 42efcee8fa6c717d425c88f11ed363fb9d97a8e7..fae7f9a3ce9b1c2725d8a1e1ba3d176933f13139 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_objectFilter.png and b/docs/doxygen-user/images/FileDiscovery/fd_objectFilter.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_pastOccur.png b/docs/doxygen-user/images/FileDiscovery/fd_pastOccur.png
index 9b04f882c9f3c1508494c90747dee6707df23c44..5f816cc4f89e1c4b693be2d3139b48b9f0f8361b 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_pastOccur.png and b/docs/doxygen-user/images/FileDiscovery/fd_pastOccur.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_setup.png b/docs/doxygen-user/images/FileDiscovery/fd_setup.png
index ee4fe27c1e507c0ae8cfd80d024c4012f73efe22..4b73514a9fba4473fec46b6fb891d21771e0bc14 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_setup.png and b/docs/doxygen-user/images/FileDiscovery/fd_setup.png differ
diff --git a/docs/doxygen-user/images/content_viewer_annotations.png b/docs/doxygen-user/images/content_viewer_annotations.png
index aa375903793678fc7109fe498968dac79aab716c..445b96372e3335ee28cdd4e6d25dd2805f9861a4 100644
Binary files a/docs/doxygen-user/images/content_viewer_annotations.png and b/docs/doxygen-user/images/content_viewer_annotations.png differ
diff --git a/docs/doxygen-user/images/content_viewer_app_image.png b/docs/doxygen-user/images/content_viewer_app_image.png
index c3ba7a00529117105d374b2d75fb5d1bff4bf2d5..50bfe7473dd044ce4f2f25cb3049465e039a1ee6 100644
Binary files a/docs/doxygen-user/images/content_viewer_app_image.png and b/docs/doxygen-user/images/content_viewer_app_image.png differ
diff --git a/docs/doxygen-user/images/content_viewer_app_plist.png b/docs/doxygen-user/images/content_viewer_app_plist.png
index 815b5d2ed94cce81e59e582b151f4fa28fa48342..749a5d4f93ad2caed8332198c7a95c930cbe8e08 100644
Binary files a/docs/doxygen-user/images/content_viewer_app_plist.png and b/docs/doxygen-user/images/content_viewer_app_plist.png differ
diff --git a/docs/doxygen-user/images/content_viewer_app_sqlite.png b/docs/doxygen-user/images/content_viewer_app_sqlite.png
index 9bc708593f6dd5a9233cdd1fe18e8261dc89a572..b377395cf113b834db96202c1203343807a8e72a 100644
Binary files a/docs/doxygen-user/images/content_viewer_app_sqlite.png and b/docs/doxygen-user/images/content_viewer_app_sqlite.png differ
diff --git a/docs/doxygen-user/images/content_viewer_context.png b/docs/doxygen-user/images/content_viewer_context.png
index 50953c6d56c626b9a603db50a22ff90759319c86..758d1f2aed19e3259fc37ecf276c22380160f77e 100644
Binary files a/docs/doxygen-user/images/content_viewer_context.png and b/docs/doxygen-user/images/content_viewer_context.png differ
diff --git a/docs/doxygen-user/images/content_viewer_hex.png b/docs/doxygen-user/images/content_viewer_hex.png
index 0843548450943cad458ec821124fcf55ed41e3ac..ec68e2a521a59e50daf70845353cb7295d0eb08e 100644
Binary files a/docs/doxygen-user/images/content_viewer_hex.png and b/docs/doxygen-user/images/content_viewer_hex.png differ
diff --git a/docs/doxygen-user/images/content_viewer_html.png b/docs/doxygen-user/images/content_viewer_html.png
index 4a24941951984d4b2d1d171dccc7ad7a977497ca..f42feba21d6b42d4e14597c387768a723eb9cfa7 100644
Binary files a/docs/doxygen-user/images/content_viewer_html.png and b/docs/doxygen-user/images/content_viewer_html.png differ
diff --git a/docs/doxygen-user/images/content_viewer_indexed_text.png b/docs/doxygen-user/images/content_viewer_indexed_text.png
index e25cbee5c51efad0da71cdc33e30040d29292872..11e4da475f0ac5d5eb06ec09d627c914300e3a75 100644
Binary files a/docs/doxygen-user/images/content_viewer_indexed_text.png and b/docs/doxygen-user/images/content_viewer_indexed_text.png differ
diff --git a/docs/doxygen-user/images/content_viewer_metadata.png b/docs/doxygen-user/images/content_viewer_metadata.png
index fd38248776777c53bfa4236a0090160b5898b9ca..67ed3e01863c4be126e472bd19bfb82575237345 100644
Binary files a/docs/doxygen-user/images/content_viewer_metadata.png and b/docs/doxygen-user/images/content_viewer_metadata.png differ
diff --git a/docs/doxygen-user/images/content_viewer_os_account.png b/docs/doxygen-user/images/content_viewer_os_account.png
new file mode 100644
index 0000000000000000000000000000000000000000..74bb53c3666eacefd6b250888c57cd6301b30535
Binary files /dev/null and b/docs/doxygen-user/images/content_viewer_os_account.png differ
diff --git a/docs/doxygen-user/images/content_viewer_other_occurrences.png b/docs/doxygen-user/images/content_viewer_other_occurrences.png
index c8a69e15d28f921abacfd462e59737b46f980de3..cb23535502a59257376843a4e6ac8804c22be176 100644
Binary files a/docs/doxygen-user/images/content_viewer_other_occurrences.png and b/docs/doxygen-user/images/content_viewer_other_occurrences.png differ
diff --git a/docs/doxygen-user/images/content_viewer_registry.png b/docs/doxygen-user/images/content_viewer_registry.png
index ee45181c853682b4c3083274fb75338c905af924..1d48467916280a0a2f00599b94e293288b758044 100644
Binary files a/docs/doxygen-user/images/content_viewer_registry.png and b/docs/doxygen-user/images/content_viewer_registry.png differ
diff --git a/docs/doxygen-user/images/content_viewer_results_bookmark.png b/docs/doxygen-user/images/content_viewer_results_bookmark.png
index 7dbc61bc7a8cea5def55c47cfcc9e4976f782abb..2c0d3cb73658ff0966a0d2b92f7ea7dd90242d58 100644
Binary files a/docs/doxygen-user/images/content_viewer_results_bookmark.png and b/docs/doxygen-user/images/content_viewer_results_bookmark.png differ
diff --git a/docs/doxygen-user/images/content_viewer_results_call.png b/docs/doxygen-user/images/content_viewer_results_call.png
index fa554e873a989a9a930d15f4467f217a8f0b0753..3bc104af0a4c9cc9fcf566712416db76107c5301 100644
Binary files a/docs/doxygen-user/images/content_viewer_results_call.png and b/docs/doxygen-user/images/content_viewer_results_call.png differ
diff --git a/docs/doxygen-user/images/content_viewer_strings_cyrillic.png b/docs/doxygen-user/images/content_viewer_strings_cyrillic.png
index 0318a383d6a5906953fe6caa9831f93a1f43d68f..e9c1945e7f53cb1b39ee852e540c854cf0e55059 100644
Binary files a/docs/doxygen-user/images/content_viewer_strings_cyrillic.png and b/docs/doxygen-user/images/content_viewer_strings_cyrillic.png differ
diff --git a/docs/doxygen-user/images/content_viewer_strings_latin.png b/docs/doxygen-user/images/content_viewer_strings_latin.png
index ba4403e518a2ef94be19c3c8a0fe4b6d8ea8db9f..4fbf4dd576735854612cc2f3664ff50720781cf2 100644
Binary files a/docs/doxygen-user/images/content_viewer_strings_latin.png and b/docs/doxygen-user/images/content_viewer_strings_latin.png differ
diff --git a/docs/doxygen-user/images/content_viewer_video.png b/docs/doxygen-user/images/content_viewer_video.png
index f3a26ef8f6ffb6f7b7403cf13ff6ff56ac17243c..09d19bdfded30bfc3cb1f3a35e74d2d6cac19ca3 100644
Binary files a/docs/doxygen-user/images/content_viewer_video.png and b/docs/doxygen-user/images/content_viewer_video.png differ
diff --git a/docs/doxygen-user/images/custom_web_categories.png b/docs/doxygen-user/images/custom_web_categories.png
new file mode 100644
index 0000000000000000000000000000000000000000..a49a1d2852f94124c7ad2e5242211cb9fe91e44b
Binary files /dev/null and b/docs/doxygen-user/images/custom_web_categories.png differ
diff --git a/docs/doxygen-user/images/custom_web_categories_results.png b/docs/doxygen-user/images/custom_web_categories_results.png
new file mode 100644
index 0000000000000000000000000000000000000000..68cc44bbfd67d08276b888d2c892aaa3553e75a2
Binary files /dev/null and b/docs/doxygen-user/images/custom_web_categories_results.png differ
diff --git a/docs/doxygen-user/images/data_source_host_select.png b/docs/doxygen-user/images/data_source_host_select.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9275d93876c191742cfd04ce1a94b419a72e8bd
Binary files /dev/null and b/docs/doxygen-user/images/data_source_host_select.png differ
diff --git a/docs/doxygen-user/images/host_merge.png b/docs/doxygen-user/images/host_merge.png
new file mode 100644
index 0000000000000000000000000000000000000000..b5235eb8d4a6834341647e4de4f2976e68f0a63e
Binary files /dev/null and b/docs/doxygen-user/images/host_merge.png differ
diff --git a/docs/doxygen-user/images/host_merge_result.png b/docs/doxygen-user/images/host_merge_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb22063c3dad7757cd0f76019d524d7ebc1461a8
Binary files /dev/null and b/docs/doxygen-user/images/host_merge_result.png differ
diff --git a/docs/doxygen-user/images/host_os_accounts.png b/docs/doxygen-user/images/host_os_accounts.png
new file mode 100644
index 0000000000000000000000000000000000000000..d73505fa5ca89882120765bc9be49c3b395c9101
Binary files /dev/null and b/docs/doxygen-user/images/host_os_accounts.png differ
diff --git a/docs/doxygen-user/images/keyword-search-configuration-dialog-general.PNG b/docs/doxygen-user/images/keyword-search-configuration-dialog-general.PNG
index 360ec860b9ff2ebd45297ddb0967246a34a2cf54..4dbb566faacecef40a2eb2d188227eaa1e822102 100644
Binary files a/docs/doxygen-user/images/keyword-search-configuration-dialog-general.PNG and b/docs/doxygen-user/images/keyword-search-configuration-dialog-general.PNG differ
diff --git a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG
index 5dcc5e3d03ad11ab39a235b93fc81ab9ab503746..51df8bd5bd79cb87ba8afd09d459812742d9edee 100644
Binary files a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG and b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG differ
diff --git a/docs/doxygen-user/images/manage_hosts.png b/docs/doxygen-user/images/manage_hosts.png
new file mode 100644
index 0000000000000000000000000000000000000000..14f4805027258548ffdaf40d446853dbb46801af
Binary files /dev/null and b/docs/doxygen-user/images/manage_hosts.png differ
diff --git a/docs/doxygen-user/images/options_application.png b/docs/doxygen-user/images/options_application.png
index 977c63e77a757210e075cf325198e591d27e47d3..3823eb7a2e49871a805e943c1108b76b0c0e6db0 100644
Binary files a/docs/doxygen-user/images/options_application.png and b/docs/doxygen-user/images/options_application.png differ
diff --git a/docs/doxygen-user/images/reports_select.png b/docs/doxygen-user/images/reports_select.png
index fe4f1f10bd7f8622ca31f334fba3c323488db5d7..97e440adeb26758c799a187952b2964d56b7f8c9 100644
Binary files a/docs/doxygen-user/images/reports_select.png and b/docs/doxygen-user/images/reports_select.png differ
diff --git a/docs/doxygen-user/images/reset_windows.png b/docs/doxygen-user/images/reset_windows.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b1a7ef4112447a5f66c9759108da06c3070b67b
Binary files /dev/null and b/docs/doxygen-user/images/reset_windows.png differ
diff --git a/docs/doxygen-user/images/solr/solr_disable_periodic_search.png b/docs/doxygen-user/images/solr/solr_disable_periodic_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6b4242c68dacc74bd89d194be9e4e87bcc6ec3e
Binary files /dev/null and b/docs/doxygen-user/images/solr/solr_disable_periodic_search.png differ
diff --git a/docs/doxygen-user/images/solr/solr_jvm.png b/docs/doxygen-user/images/solr/solr_jvm.png
new file mode 100644
index 0000000000000000000000000000000000000000..1285110183e9705781124feb945886550cd70abf
Binary files /dev/null and b/docs/doxygen-user/images/solr/solr_jvm.png differ
diff --git a/docs/doxygen-user/images/ui_person_select.png b/docs/doxygen-user/images/ui_person_select.png
new file mode 100644
index 0000000000000000000000000000000000000000..e82a5d0c3016e074e9331359c53837278f74d2f7
Binary files /dev/null and b/docs/doxygen-user/images/ui_person_select.png differ
diff --git a/docs/doxygen-user/images/ui_tree_top_ds.png b/docs/doxygen-user/images/ui_tree_top_ds.png
new file mode 100644
index 0000000000000000000000000000000000000000..7870ae8d0c9ad09016c716d2b589e9f1e46c6ded
Binary files /dev/null and b/docs/doxygen-user/images/ui_tree_top_ds.png differ
diff --git a/docs/doxygen-user/images/ui_tree_top_persons.png b/docs/doxygen-user/images/ui_tree_top_persons.png
new file mode 100644
index 0000000000000000000000000000000000000000..56b3c43f56633c224c8ce5c0119b3a4c2b3d18c7
Binary files /dev/null and b/docs/doxygen-user/images/ui_tree_top_persons.png differ
diff --git a/docs/doxygen-user/images/view_options_options_panel.png b/docs/doxygen-user/images/view_options_options_panel.png
index 40b5f6cf8a0a0393b104ff3966c2eec2b954a196..168ce70cc4d0bd4432f96974aab50da023d4aa1a 100644
Binary files a/docs/doxygen-user/images/view_options_options_panel.png and b/docs/doxygen-user/images/view_options_options_panel.png differ
diff --git a/docs/doxygen-user/images/views_grouped_tree.png b/docs/doxygen-user/images/views_grouped_tree.png
new file mode 100644
index 0000000000000000000000000000000000000000..d76b991a5219a1e18e96a2158b8a6ea95ad3fd19
Binary files /dev/null and b/docs/doxygen-user/images/views_grouped_tree.png differ
diff --git a/docs/doxygen-user/images/views_standard_tree.png b/docs/doxygen-user/images/views_standard_tree.png
new file mode 100644
index 0000000000000000000000000000000000000000..80eddc68a9ef5beb3b0da05fbc84a28de5f44c87
Binary files /dev/null and b/docs/doxygen-user/images/views_standard_tree.png differ
diff --git a/docs/doxygen-user/keyword_search.dox b/docs/doxygen-user/keyword_search.dox
index fd207a6de29a721b11acd235df9a6351e712901c..45ef1c0dfa978c9220d0c74e5579e94f0ccdd5bd 100644
--- a/docs/doxygen-user/keyword_search.dox
+++ b/docs/doxygen-user/keyword_search.dox
@@ -44,12 +44,28 @@ Under the Keyword list is the option to send ingest inbox messages for each hit.
 The string extraction setting defines how strings are extracted from files from which text cannot be extracted normally because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files.
 When we extract strings from binary files we need to interpret sequences of bytes as text differently, depending on the possible text encoding and script/language used. In many cases we don't know in advance what the specific encoding/language the text is encoded in. However, it helps if the investigator is looking for a specific language, because by selecting less languages the indexing performance will be improved and the number of false positives will be reduced.
 
+\image html keyword-search-configuration-dialog-string-extraction.PNG
+
 The default setting is to search for English strings only, encoded as either UTF8 or UTF16. This setting has the best performance (shortest ingest time).
 The user can also use the String Viewer first and try different script/language settings, and see which settings give satisfactory results for the type of text relevant to the investigation. Then the same setting that works for the investigation can be applied to the keyword search ingest.
 
-\image html keyword-search-configuration-dialog-string-extraction.PNG
 
-There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The following shows a sample image containing text:
+## General Settings tab {#generalSettingsTab}
+
+\image html keyword-search-configuration-dialog-general.PNG
+
+### NIST NSRL Support
+The hash lookup ingest service can be configured to use the NIST NSRL hash set of known files. The keyword search advanced configuration dialog "General" tab contains an option to skip keyword indexing and search on files that have previously marked as "known" and uninteresting files. Selecting this option can greatly reduce size of the index and improve ingest performance. In most cases, user does not need to keyword search for "known" files.
+
+### Result update frequency during ingest
+To control how frequently searches are executed during ingest, the user can adjust the timing setting available in the keyword search advanced configuration dialog "General" tab. Setting the number of minutes lower will result in more frequent index updates and searches being executed and the user will be able to see results more in real-time. However, more frequent updates can affect the overall performance, especially on lower-end systems, and can potentially lengthen the overall time needed for the ingest to complete.
+
+One can also choose to have no periodic searches. This will speed up the ingest. Users choosing this option can run their keyword searches once the entire keyword search index is complete.
+
+### Optical Character Recognition
+There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The secondary checkbox can make OCR run faster by only processing large images and images extracted from documents. 
+
+The following shows a sample image containing text:
 
 \image html keyword-search-ocr-image.png
 
@@ -72,19 +88,6 @@ and move them to the right location. The following steps breakdown this process
 
 The language files will now be supported when OCR is enabled in the Keyword Search Settings.
 
-## General Settings tab {#generalSettingsTab}
-
-\image html keyword-search-configuration-dialog-general.PNG
-
-### NIST NSRL Support
-The hash lookup ingest service can be configured to use the NIST NSRL hash set of known files. The keyword search advanced configuration dialog "General" tab contains an option to skip keyword indexing and search on files that have previously marked as "known" and uninteresting files. Selecting this option can greatly reduce size of the index and improve ingest performance. In most cases, user does not need to keyword search for "known" files.
-
-### Result update frequency during ingest
-To control how frequently searches are executed during ingest, the user can adjust the timing setting available in the keyword search advanced configuration dialog "General" tab. Setting the number of minutes lower will result in more frequent index updates and searches being executed and the user will be able to see results more in real-time. However, more frequent updates can affect the overall performance, especially on lower-end systems, and can potentially lengthen the overall time needed for the ingest to complete.
-
-One can also choose to have no periodic searches. This will speed up the ingest. Users choosing this option can run their keyword searches once the entire keyword search index is complete.
-
-
 <!----------------------------------------->
 
 <br>
diff --git a/docs/doxygen-user/multi-user/installSolr.dox b/docs/doxygen-user/multi-user/installSolr.dox
index 8e52b5ba1192d615dc2f2fec79ab14094b769740..d8a4690d58ebc5edd8f3eac0e8c995ecc0524ca1 100644
--- a/docs/doxygen-user/multi-user/installSolr.dox
+++ b/docs/doxygen-user/multi-user/installSolr.dox
@@ -259,5 +259,32 @@ However, the dashboard does not show enough detail to know when Solr is out of h
 Solr heap and other performance tuning is described in the following article:
 <ul><li>https://cwiki.apache.org/confluence/display/SOLR/SolrPerformanceProblems</ul>
 
+\subsubsection install_solr_performance_tuning Notes on Solr Performance Tuning
+
+If you are going to work with large images (TBs) and KWS performance is important, the best approach is to use a network (Multi-User) Solr server. 
+
+Some notes:
+<ul>
+<li>A single Solr server works well for data sources up to 1TB; after that the performance starts to slow down. The performance doesn't "drop off the cliff," but it keeps slowing down as you add more data to the index. After 3TBs of input data the Solr performance takes a significant decline.
+
+<li>A single Multi-User Solr server may not perform much better than a Single-User Autopsy case. However, in Multi-User mode you can add additional Solr servers and create a Solr cluster. See the \ref install_sorl_adding_nodes section in the above documentation. These additional nodes are where the performance gains come from, especially for large input data sources. Apache Solr documentation calls this "SolrCloud" mode and each Solr server is called a "shard". The more Solr servers/shards you have, the better performance you will have for large data sets. On our test and production clusters, we are using 4-6 Solr servers to handle data sets of up to 10TB, which seems to be the upper limit. After that, you are better off breaking your Autopsy case into multiple cases, thus creating a separate Solr index for each case.
+
+<li>In our testing, a 3-node SolrCloud indexes data roughly twice as fast as single Solr node. A 6-node SolrCloud indexes data almost twice as fast as 3-node SolrCloud. After that we did not see much performance gain. These performance figures are heavily dependent on network throughput, machine resources, disk access speeds, and the type of data that is being indexed.
+
+<li>Exact match searches are much faster than substring or regex searches.
+
+<li>Regex searches tend to use a lot of RAM on the Solr server.
+
+<li>Indexing/searching of unallocated space really slows everything down because it is mostly binary or garbled data.
+
+<li>If you are not going to look at the search results until ingest is over then you should disable the periodic keyword searches. They will start taking longer as your input data grows. This can be done in Tools->Options->Keyword Search tab:
+
+\image html solr_disable_periodic_search.png
+
+<li>In Single-User mode, if you are ingesting and indexing data sources that are multiple TBs in size, then both Autopsy memory and especially the Solr JVM memory needs to be increased from their default settings. This can be done in Tools->Options->Application tab. We would recommend at least 10GB heap size for Autopsy and at least 6-8GB heap size for Solr. Note that these are "maximum" values that the process will be allowed to use/request. The operating system will not allocate more heap than the process actually needs.
+
+\image html solr_jvm.png
+
+</ul>
 
 */
diff --git a/docs/doxygen-user/recent_activity.dox b/docs/doxygen-user/recent_activity.dox
index ba2f94deefd747dbb5054c208760ea20a1fdaf87..094d43b0775f2e5053b3808c7bd3592d00711944 100644
--- a/docs/doxygen-user/recent_activity.dox
+++ b/docs/doxygen-user/recent_activity.dox
@@ -13,7 +13,17 @@ This allows you to see what activity has occured in the last seven days of usage
 Configuration
 =======
 
-There is nothing to configure for this module.
+Configuring Custom Web Categories
+------
+
+The Recent Activity module will create "Web Categories" results for domains that match a list of categories. There are some built-in categories, but custom categories can also be entered through the "Custom Web Categories" tab on the main options panel. These custom categories will override any matching built-in category.
+
+\image html custom_web_categories.png
+
+The buttons below the list of categories allow you to enter new categories, edit existing categories, and delete categories. You can also export your list of categories and import a set of categories that was previously exported from this panel. Importing a set will add its categories to the current list (existing categories will not be deleted).
+
+The category match for each domain will be listed in the "Name" column in the result viewer.
+\image html custom_web_categories_results.png
 
 
 Using the Module
@@ -23,6 +33,7 @@ Ingest Settings
 ------
 There are no run-time settings for this module.
 
+
 Seeing Results
 ------
 Results show up in the tree under "Extracted Content".
diff --git a/docs/doxygen-user/reporting.dox b/docs/doxygen-user/reporting.dox
index 86a5e9df6166460805de6289fad266e86e73bd68..dbdf63a4c8196bd98263d69ad37d4630754599de 100644
--- a/docs/doxygen-user/reporting.dox
+++ b/docs/doxygen-user/reporting.dox
@@ -58,7 +58,7 @@ Generating an Excel report is very similar to an \ref report_html. You select wh
 
 \image html reports_excel.png
 
-\subsection report_tagged_hashes Add Tagged Hashes
+\subsection report_tagged_hashes Save Tagged Hashes
 
 This is one of the report modules that doesn't generate an actual report. The purpose of this module is to easily add the hashes
 of some/all tagged files to an Autopsy hash set that can be used by the \ref hash_db_page. You can use the "Configure Hash Sets" button to create a new
@@ -69,6 +69,10 @@ hash set to write to, or use an existing hash set.
 After running this module, if you use the same hash set on future cases then everything that was tagged with one of the selected tags in this case will
 show up as Hashset Hits.
 
+\subsection reports_unique_words Extract Unique Words
+
+This report module allows you to export all unique "words" found in a case. These words come from the Solr index that was created by the \ref keyword_search_page.
+
 \subsection report_case_uco CASE-UCO
 
 This module creates a JSON output file in <a href="https://github.com/ucoProject/CASE/wiki">CASE-UCO</a> format for a single data source. 
diff --git a/docs/doxygen-user/tree_viewer.dox b/docs/doxygen-user/tree_viewer.dox
index 9ca6ce49ceb981391132dcf53f2fef95ee0fdae0..d3d6651fcee86cbd6ebe0928562e192c31464d6f 100644
--- a/docs/doxygen-user/tree_viewer.dox
+++ b/docs/doxygen-user/tree_viewer.dox
@@ -4,20 +4,37 @@
 
 
 The tree on the left-hand side of the main window is where you can browse the files in the data sources in the case and find saved results from automated analyis (ingest). The tree has five main areas:
-- <b>Data Sources:</b> This shows the directory tree hierarchy of the data sources. You can navigate to a specific file or directory here. Each data source added to the case is represented as a distinct sub tree. If you add a data source multiple times, it shows up multiple times.
+- <b>Persons / Hosts / Data Sources:</b> This shows the directory tree hierarchy of the data sources. You can navigate to a specific file or directory here. Each data source added to the case is represented as a distinct sub tree. If you add a data source multiple times, it shows up multiple times.
 - <b>Views:</b> Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source.
 - <b>Results:</b> This is where you can see the results from both the automated analysis (ingest) running in the background and your search results.
 - <b>Tags:</b> This is where files and results that have been \ref tagging_page "tagged" are shown.
 - <b>Reports:</b> Reports that you have generated, or that ingest modules have created, show up here.
 
-You can also use the "Group by data source" option available through the \ref view_options_page to move the Views, Results, and Tags tree nodes under their corresponding data sources. This can be helpful on very large cases to reduce the size of each sub tree. For example:
+You can also use the "Group by Person/Host" option available through the \ref view_options_page to move the Views, Results, and Tags tree nodes under their corresponding person and host. This can be helpful on very large cases to reduce the size of each sub tree.
 
-\image html ui_layout_group_tree.PNG
+\section ui_tree_ds Persons / Hosts / Data Sources
+By default, the top node of the tree viewer will contain all data sources in the case. The Data Sources node is organized by host and then the data source itself. Right clicking on the various nodes in the Data Sources area of the tree will allow you to get more options for each data source and its contents. 
 
-\section ui_tree_ds Data Sources
+\image html ui_tree_top_ds.png
 
-The Data Sources area shows each data source that has been added to the case, in order added (top one is first).
-Right clicking on the various nodes in the Data Sources area of the tree will allow you to get more options for each data source and its contents. 
+If the "Group by Person/Host" option has been selected in the \ref view_options_group "View Options", the hosts and data sources will be organized under any persons that have been associated with the hosts. Additionally, the rest of the nodes (Views, Results, etc) will be found under each data source.
+
+\image html ui_tree_top_persons.png
+
+\subsection ui_tree_persons Persons
+
+If the "Group by Person/Host" option in the \ref view_options_group "View Options" has been set, the top level nodes will display persons. Persons are manually created and can be associated with one or more hosts. To add or remove a person from a host, right-click on the host and select the appropriate option.
+
+\image html ui_person_select.png
+
+You can edit and delete persons by right-clicking on the node.
+
+\subsection ui_tree_hosts Hosts
+
+All data sources are organized under host nodes. See the \ref host_page "hosts page" for more information on using hosts.
+
+\subsection ui_tree_ds_node Data Sources
+Under the hosts are the nodes for each data source. 
 
 Unallocated space is the chunks of a file system that are currently not being used for anything. Unallocated space can hold deleted files and other interesting artifacts. In an image data source, unallocated space is stored in blocks with distinct locations in the file system. However, because of the way carving tools work, it is better to feed these tools a single, large unallocated space file. Autopsy provides access to both methods of looking at unallocated space.
 \li <b>Individual blocks in a volume</b>  For each volume, there is a "virtual" folder named "$Unalloc". This folder contains all the individual unallocated blocks in contiguous runs (unallocated space files) as the image is storing them. You can right click and extract any unallocated space file the same way you can extract any other type of file in the Data Sources area.
diff --git a/docs/doxygen-user/troubleshooting.dox b/docs/doxygen-user/troubleshooting.dox
index 666b86f10b723470949535a9f92741922904f405..c08ebf50c4c6518894a191ef4cef8550fc6bb51e 100644
--- a/docs/doxygen-user/troubleshooting.dox
+++ b/docs/doxygen-user/troubleshooting.dox
@@ -9,18 +9,19 @@ If you are experiencing an error, we encourage you to post on the forum (https:/
 <li>What led to the error. For example:
 <ul>
 <li>What type of data source was being processed?
-<li>Which ingest modules were running?
+<li>Which ingest modules were running? You can generate an \ref ingest_monitoring "ingest snapshot" to view the current ingest state.
 <li>Which specialized viewer were you using?
 </ul>
 <li>The error being displayed on screen (if applicable)
+<li>A \ref troubleshooting_stack "thread dump" or screenshot of the \ref ingest_monitoring "ingest snapshot" if Autopsy seems stuck
 <li>If there were any errors in the \ref troubleshooting_logs "logs"
 </ul>
 
 \section troubleshooting_specific_issues Specific Issues
 
-\subsection troubleshooting_fond_size Font Size Too Small in Windows
+\subsection troubleshooting_fond_size Font Size Too Small
 
-Make the following changes if the application is hard to navigate in High DPI systems:
+In Windows, you can make the following changes if the application is hard to navigate in High DPI systems:
 
 <ol>
 <li>Right-click on the application icon on your Desktop, Start Menu, etc.
@@ -32,8 +33,18 @@ Make the following changes if the application is hard to navigate in High DPI sy
 <li>Restart Autopsy.
 </ol>
 
+In Linux, you can supply the font size with "--fontsize XX" command line argument, but not all of the dialogs are correctly responsive and some of the text will get cut off.
+
 \section troubleshooting_general General Troubleshooting
 
+\subsection troubleshooting_reset_ui Resetting the UI
+
+If the Autopsy window no longer looks like the default \ref uilayout_page (for example, if a viewer has disappeared or there is a strange empty space), you can reset it. To do this, go to Window->Reset Windows. This will cause Autopsy to restart. If you have a case open, it will reopen after the reset.
+
+\image html reset_windows.png
+
+If resetting the windows does not fix the problem, you may need to delete your user folder as described in the next section.
+
 \subsection troubleshooting_user_folder Deleting the Autopsy User Folder
 
 If Autopsy starts behaving strangely, stops loading entirely, or menu items go missing, you probably need to delete your user folder. Doing so essenitally gives you a fresh installation. On Windows the user folder is located in "C:\Users\(user name)\AppData\Roaming\autopsy". 
diff --git a/docs/doxygen-user/view_options.dox b/docs/doxygen-user/view_options.dox
index 1ff398da81850828d4caef0069d756f9ff378ed6..270d91950799a2b4eae84a2946e69f7ab939ff0b 100644
--- a/docs/doxygen-user/view_options.dox
+++ b/docs/doxygen-user/view_options.dox
@@ -66,11 +66,15 @@ If you have a \ref machine_translation_page module installed, this option will a
 
 The settings in this section only apply to the current case.
 
-\subsection view_options_group Group by data source
+\subsection view_options_group Data Source Grouping
 
-The "Group by data source" option allows you to separate all elements in the \ref ui_tree by data source. This can help nodes load faster on large cases.
+The options here allow you to choose how to display data in the \ref ui_tree. The top option ("Group by Data Type") displays combined results for all data sources. All nodes on the tree will contain combined results for all data sources in the case.
 
-\image html ui_layout_group_tree.PNG
+\image html views_standard_tree.png
+
+The second option ("Group by Person/Host") separates the results for each data source, and organizes the data sources by \ref ui_tree_persons "person" and \ref ui_tree_hosts "host". 
+
+\image html views_grouped_tree.png
 
 \section view_options_session Current Session Settings
 
diff --git a/thirdparty/NetbeansLocalization/README.txt b/thirdparty/NetbeansLocalization/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c5751c99f0df738339209d6edcb77561fd62bd01
--- /dev/null
+++ b/thirdparty/NetbeansLocalization/README.txt
@@ -0,0 +1 @@
+This contains jars provided in Netbeans 8 RCP that provide localization bundles.  They do not appear to be included in Netbeans >= 9.  See Jira 7434 for more information.
diff --git a/thirdparty/NetbeansLocalization/org-jdesktop-layout_ja.jar b/thirdparty/NetbeansLocalization/org-jdesktop-layout_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..070f7febd534f508c1ff5c2fae422d085e4b564f
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-jdesktop-layout_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-annotations-common_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-annotations-common_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..edb9a78b04e0b566bc2461845d183c66c1ca75bd
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-annotations-common_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-htmlui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-htmlui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..935bd841007f307e8c588421b93e0433b8459425
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-htmlui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-intent_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-intent_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..4f476868ae064cb8124311e8e2a12e56531096dc
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-intent_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-io_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-io_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d115ccc52d8d199ff3180161822ee0fa8a1b859a
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-io_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-progress-compat8_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-progress-compat8_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..902ee4b55e06d007331415d7f879de0a73820d90
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-progress-compat8_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-progress-nb_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-progress-nb_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..3cd2c2c517880c85c98987c08d6bb8ac792dbee4
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-progress-nb_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-progress_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-progress_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..7ae7ceb95d6993a4a7f4540cce1ebeb68073c0e5
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-progress_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-search_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-search_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a77ec100a2ca2752ee37aad5ce79bd3c240e0e75
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-search_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-templates_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-templates_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a0db573a65dd84943642ac36c5ac573b78080b6f
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-templates_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-api-visual_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-api-visual_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..305048fb1338b68544d89627920581f9629c86be
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-api-visual_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-execution_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-execution_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..22e24e7de82525c6e1bc37891312739f5ab9c8c0
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-execution_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-io-ui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-io-ui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2096ba4174605ef7706b89169185f6ad7d71e652
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-io-ui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-multitabs_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-multitabs_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a86f711f83b89373792c1a08f4494ebb9edfebe3
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-multitabs_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-multiview_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-multiview_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bd6f67dff954a607f78f27b5c405f3519246ee38
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-multiview_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-nativeaccess_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-nativeaccess_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..dc386bac99113499f266508cc2ecc3436aaaeee6
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-nativeaccess_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-netigso_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-netigso_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..57429511f7f8ceabbed1414a499a287af39bde94
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-netigso_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-network_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-network_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b7d257b02ce7bdba789b62149a4b5755334a69e9
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-network_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-osgi_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-osgi_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..1fc1980df714dde6b98e8dfe1b7d80b67b0ebc48
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-osgi_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-output2_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-output2_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a024cec8d811615553b3f45c7cc0ea04988b7b58
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-output2_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-ui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-ui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..583bed5055a6699338f4a81815f92879f6edc1c5
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-ui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core-windows_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core-windows_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..22ebd5d849da3de13f293199644116f5e57b7646
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core-windows_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-core_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-core_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ee88e3d0b28d87c8eafe42b2952d09da8a8292b6
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-core_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-lib-uihandler_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-lib-uihandler_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..12964589fee9412a3d7d5b718e5ee9f81820c5e8
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-lib-uihandler_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-felix_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-felix_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2237f74e641ddf2c05ea9f36718a439c957915e9
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-felix_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-javafx_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-javafx_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e27934543a652f19b7524b2c036440d3e444d5d4
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-javafx_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-jna-platform_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-jna-platform_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bfb0273184469e11fb893be6c817971ca0bd1fe7
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-jna-platform_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-jna_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-jna_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f6a70fdef9e2702d65f1197d99b78e146654dbcf
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-jna_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-jsr223_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-jsr223_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..aaaeae743970e2f77636d71a120cdf43954f37d1
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-jsr223_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-junit4_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-junit4_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d2504a6e016759a88edc71e71bbe47b7d9fa982d
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-junit4_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-osgi_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-osgi_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..46c049df50a7030475eee9c2f410d062f6b85cd9
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-osgi_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-libs-testng_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-libs-testng_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..5e6a42ced297ad6d1aa52b0822c58b9db37eef4f
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-libs-testng_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-applemenu_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-applemenu_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a87cab81b9d35997905f2b06e65d179e52e20acc
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-applemenu_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-cli_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-cli_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a485b6f5efdb9de8a2c9d9035dd477cd712038bc
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-cli_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-services_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-services_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..696c3cf0bcba095f2bf1207039e683a4f33bf459
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-services_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-ui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-ui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2944325af7a616ba488153e2bb708850475ee2c2
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-autoupdate-ui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-core-kit_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-core-kit_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..7fb1cf061c66e95fc1130f942484c59dd2896e64
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-core-kit_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup-impl_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup-impl_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..1bed7e0b388aff8bccf880081632e50880146783
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup-impl_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ab36fa091a655d005a208e86687e082476b1eb08
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-editor-mimelookup_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-favorites_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-favorites_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b9829e54c0f312f7656a353f0a7e8d81f6096782
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-favorites_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-javahelp_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-javahelp_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e80bbd607a2219caa0fcc77bf8288037ca19caf0
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-javahelp_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-junitlib_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-junitlib_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a9d8bce0ee3249e234d6bc4ac9e7c1afa86971f5
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-junitlib_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-fallback_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-fallback_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6fa12e67da0540fd56260b823b552f9cf95e55bf
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-fallback_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-impl_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-impl_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..cdd341da238a51836c99de9a2e207bad0a0d3276
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring-impl_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..733a9b20e5bdc2f7dfaa15995ad92ea3d5abd79a
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-keyring_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-linux_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-linux_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..4b493caa9822b2c4557fa8e4e0b8546baed8bbf3
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-linux_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-macosx_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-macosx_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2ffd6623884013252720a8449e0944970b55a8de
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-macosx_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-nio2_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-nio2_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d1eb2762b99691586e1b0ce417868ed8593dd388
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-nio2_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-ui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-ui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..9eae950a754f65a140600c643919aa6b8f1ee83f
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-ui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-windows_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-windows_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b31b2bd76b9419ee140944bfc58e7e2792e30f15
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs-windows_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..fdbe8874ac2d48e8626c60e2b9fa6b5ffe6afa4a
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-masterfs_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-netbinox_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-netbinox_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..81d02839f5d3564a983d71f728e66a40051c5b43
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-netbinox_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-options-api_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-options-api_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d7fc6636a444516036e49396b01f4a2bf263fc7e
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-options-api_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-options-keymap_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-options-keymap_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..aa05ab94344a4fb21ca004c8fa4c1eb976572028
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-options-keymap_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-print_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-print_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..17333fcb6913f4f17ee761e4a90180890c17cbc1
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-print_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-progress-ui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-progress-ui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..71b4f6c62ca7867e56463dc3bd670bf8b644bf7e
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-progress-ui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-queries_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-queries_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..fb46c44bbfa1cc563e5faac4126f14449202b6ac
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-queries_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-sampler_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-sampler_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..7b8d70e76af0fdaf4ab259584e78fc6d2b70f072
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-sampler_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-sendopts_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-sendopts_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..8bb8725873fa7f2fc8f92e1aabb73abcaa061d14
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-sendopts_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-settings_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-settings_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..4edce51acd6e3d7127a7d6f8db313f187319b6bb
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-settings_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-spi-actions_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-spi-actions_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..34340d9419e89c4268fb88cab92a55c61fc92ae1
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-spi-actions_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-templates_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-templates_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..67dec8d2995aa81de3437e638e97a9cac4aa7b1d
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-templates_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-templatesui_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-templatesui_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c58fabf436be0cdfaf263a20b45e5d8e0dc41df1
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-templatesui_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-modules-uihandler_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-modules-uihandler_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..94401aa524db4601ca05fde9e214406194ee8758
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-modules-uihandler_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-spi-quicksearch_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-spi-quicksearch_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..0356025fdf9cc3758550b3377436fcfe4cc84a02
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-spi-quicksearch_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-swing-outline_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-swing-outline_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6fa52f1e3e7de6b5bb3a8325ce67ae49478d6b08
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-swing-outline_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-swing-plaf_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-swing-plaf_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6007a388a6bd0f88bd30944b578cd73499f96040
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-swing-plaf_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-netbeans-swing-tabcontrol_ja.jar b/thirdparty/NetbeansLocalization/org-netbeans-swing-tabcontrol_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..75357b99f1e617ce41a31eea631e475758e0449f
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-netbeans-swing-tabcontrol_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-actions_ja.jar b/thirdparty/NetbeansLocalization/org-openide-actions_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f393db11706aec080e4b4f37ad0c9a163887134c
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-actions_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-awt_ja.jar b/thirdparty/NetbeansLocalization/org-openide-awt_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f35574dd868d7e4ebf4fd3cef043d9c408c792e8
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-awt_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-compat_ja.jar b/thirdparty/NetbeansLocalization/org-openide-compat_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bba222edfabc958263eee0aba647eaf06513d3e5
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-compat_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-dialogs_ja.jar b/thirdparty/NetbeansLocalization/org-openide-dialogs_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..fdd75a84d58c9f1fce7a658169eb4bbad438f8b7
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-dialogs_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-execution-compat8_ja.jar b/thirdparty/NetbeansLocalization/org-openide-execution-compat8_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..11c1df1ea10a5cca6a4218a665afa219ffda5254
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-execution-compat8_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-execution_ja.jar b/thirdparty/NetbeansLocalization/org-openide-execution_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e1b84b35bcba5e5d572a2d618666bcd691709df8
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-execution_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-explorer_ja.jar b/thirdparty/NetbeansLocalization/org-openide-explorer_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c8a06bbd708751be20db4e6433db2bf360e9f301
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-explorer_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-filesystems-nb_ja.jar b/thirdparty/NetbeansLocalization/org-openide-filesystems-nb_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..55b02fc68ad5da5970bbfb702c4f2128cd4b72c3
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-filesystems-nb_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-io_ja.jar b/thirdparty/NetbeansLocalization/org-openide-io_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..50e19b97786cebbd97fa5a74ce0c137b7c936562
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-io_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-loaders_ja.jar b/thirdparty/NetbeansLocalization/org-openide-loaders_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c06d6e17122311b4a50cfbbee41130581b0b3321
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-loaders_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-nodes_ja.jar b/thirdparty/NetbeansLocalization/org-openide-nodes_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ed84a00886a4e26470833d73869cc9bf490933be
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-nodes_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-options_ja.jar b/thirdparty/NetbeansLocalization/org-openide-options_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b306b0a028e0882db7a0a58aa801453c2f047515
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-options_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-text_ja.jar b/thirdparty/NetbeansLocalization/org-openide-text_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..3e7e07b0598b91fae3f1030e9e968a67955f2634
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-text_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-util-enumerations_ja.jar b/thirdparty/NetbeansLocalization/org-openide-util-enumerations_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..841b9d755108fad83ed24c80c84d04830fdd98de
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-util-enumerations_ja.jar differ
diff --git a/thirdparty/NetbeansLocalization/org-openide-windows_ja.jar b/thirdparty/NetbeansLocalization/org-openide-windows_ja.jar
new file mode 100644
index 0000000000000000000000000000000000000000..92fc28e2d83f37858422683d1f6d4f9911f0a103
Binary files /dev/null and b/thirdparty/NetbeansLocalization/org-openide-windows_ja.jar differ