]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
mount_image.c: add fallback definitions of RENAME_* constants
[wimlib] / programs / imagex.c
index d567371ae1227a549a0f4233aab14de458a1a071..54e50bfcdd06fe68015b8a23c2ac81c36be80097 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 /*
- * Copyright (C) 2012, 2013, 2014 Eric Biggers
+ * Copyright 2012-2023 Eric Biggers
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 
 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
 
-#ifdef __WIN32__
+#ifdef _WIN32
 #  include "imagex-win32.h"
 #  define print_security_descriptor     win32_print_security_descriptor
-#else /* __WIN32__ */
+#else /* _WIN32 */
 #  include <getopt.h>
 #  include <langinfo.h>
 #  define print_security_descriptor    default_print_security_descriptor
 static inline void set_fd_to_binary_mode(int fd)
 {
 }
-#endif /* !__WIN32 */
+/* NetBSD is missing getopt_long_only() but has getopt_long() */
+#ifndef HAVE_GETOPT_LONG_ONLY
+#  define getopt_long_only getopt_long
+#endif
+#endif /* !_WIN32 */
 
 /* Don't confuse the user by presenting the mounting commands on Windows when
  * they will never work.  However on UNIX-like systems we always present them,
  * even if WITH_FUSE is not defined at this point, as to not tie the build of
  * wimlib-imagex to a specific build of wimlib.  */
-#ifdef __WIN32__
+#ifdef _WIN32
 #  define WIM_MOUNTING_SUPPORTED 0
 #else
 #  define WIM_MOUNTING_SUPPORTED 1
@@ -131,24 +135,43 @@ enum {
 static void usage(int cmd, FILE *fp);
 static void usage_all(FILE *fp);
 static void recommend_man_page(int cmd, FILE *fp);
-static const tchar *get_cmd_string(int cmd, bool nospace);
+static const tchar *get_cmd_string(int cmd, bool only_short_form);
 
-static bool imagex_be_quiet = false;
 static FILE *imagex_info_file;
 
-#define imagex_printf(format, ...) \
+#define imagex_printf(format, ...)     \
+       if (imagex_info_file)           \
                tfprintf(imagex_info_file, format, ##__VA_ARGS__)
 
+static void imagex_suppress_output(void)
+{
+       imagex_info_file = NULL;
+}
+
+static void imagex_output_to_stderr(void)
+{
+       if (imagex_info_file)
+               imagex_info_file = stderr;
+}
+
+static void imagex_flush_output(void)
+{
+       if (imagex_info_file)
+               fflush(imagex_info_file);
+}
+
 enum {
        IMAGEX_ALLOW_OTHER_OPTION,
+       IMAGEX_BLOBS_OPTION,
        IMAGEX_BOOT_OPTION,
        IMAGEX_CHECK_OPTION,
        IMAGEX_CHUNK_SIZE_OPTION,
        IMAGEX_COMMAND_OPTION,
        IMAGEX_COMMIT_OPTION,
+       IMAGEX_COMPACT_OPTION,
        IMAGEX_COMPRESS_OPTION,
-       IMAGEX_COMPRESS_SLOW_OPTION,
        IMAGEX_CONFIG_OPTION,
+       IMAGEX_CREATE_OPTION,
        IMAGEX_DEBUG_OPTION,
        IMAGEX_DELTA_FROM_OPTION,
        IMAGEX_DEREFERENCE_OPTION,
@@ -158,9 +181,10 @@ enum {
        IMAGEX_FLAGS_OPTION,
        IMAGEX_FORCE_OPTION,
        IMAGEX_HEADER_OPTION,
+       IMAGEX_IMAGE_PROPERTY_OPTION,
+       IMAGEX_INCLUDE_INTEGRITY_OPTION,
        IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
        IMAGEX_LAZY_OPTION,
-       IMAGEX_LOOKUP_TABLE_OPTION,
        IMAGEX_METADATA_OPTION,
        IMAGEX_NEW_IMAGE_OPTION,
        IMAGEX_NOCHECK_OPTION,
@@ -168,8 +192,9 @@ enum {
        IMAGEX_NOT_PIPABLE_OPTION,
        IMAGEX_NO_ACLS_OPTION,
        IMAGEX_NO_ATTRIBUTES_OPTION,
-       IMAGEX_NO_REPLACE_OPTION,
        IMAGEX_NO_GLOBS_OPTION,
+       IMAGEX_NO_REPLACE_OPTION,
+       IMAGEX_NO_SOLID_SORT_OPTION,
        IMAGEX_NULLGLOB_OPTION,
        IMAGEX_ONE_FILE_ONLY_OPTION,
        IMAGEX_PATH_OPTION,
@@ -177,14 +202,15 @@ enum {
        IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
        IMAGEX_REBUILD_OPTION,
        IMAGEX_RECOMPRESS_OPTION,
+       IMAGEX_RECOVER_DATA_OPTION,
        IMAGEX_RECURSIVE_OPTION,
        IMAGEX_REF_OPTION,
-       IMAGEX_RESUME_OPTION,
        IMAGEX_RPFIX_OPTION,
+       IMAGEX_SNAPSHOT_OPTION,
        IMAGEX_SOFT_OPTION,
-       IMAGEX_SOLID_OPTION,
        IMAGEX_SOLID_CHUNK_SIZE_OPTION,
        IMAGEX_SOLID_COMPRESS_OPTION,
+       IMAGEX_SOLID_OPTION,
        IMAGEX_SOURCE_LIST_OPTION,
        IMAGEX_STAGING_DIR_OPTION,
        IMAGEX_STREAMS_INTERFACE_OPTION,
@@ -192,10 +218,11 @@ enum {
        IMAGEX_THREADS_OPTION,
        IMAGEX_TO_STDOUT_OPTION,
        IMAGEX_UNIX_DATA_OPTION,
+       IMAGEX_UNSAFE_COMPACT_OPTION,
        IMAGEX_UPDATE_OF_OPTION,
        IMAGEX_VERBOSE_OPTION,
-       IMAGEX_WIMBOOT_OPTION,
        IMAGEX_WIMBOOT_CONFIG_OPTION,
+       IMAGEX_WIMBOOT_OPTION,
        IMAGEX_XML_OPTION,
 };
 
@@ -211,10 +238,9 @@ static const struct option apply_options[] = {
        {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
        {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
        {T("include-invalid-names"), no_argument,       NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
-
-       /* --resume is undocumented for now as it needs improvement.  */
-       {T("resume"),      no_argument,       NULL, IMAGEX_RESUME_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
+       {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
+       {T("recover-data"), no_argument,      NULL, IMAGEX_RECOVER_DATA_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -223,18 +249,17 @@ static const struct option capture_or_append_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
-       {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,      NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"), no_argument,      NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
+       {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
        {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
        {T("flags"),       required_argument, NULL, IMAGEX_FLAGS_OPTION},
+       {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
        {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
@@ -250,12 +275,17 @@ static const struct option capture_or_append_options[] = {
        {T("update-of"),   required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
        {T("delta-from"),  required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
+       {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
+       {T("snapshot"),    no_argument,       NULL, IMAGEX_SNAPSHOT_OPTION},
+       {T("create"),      no_argument,       NULL, IMAGEX_CREATE_OPTION},
        {NULL, 0, NULL, 0},
 };
 
 static const struct option delete_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("soft"),  no_argument, NULL, IMAGEX_SOFT_OPTION},
+       {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -263,6 +293,7 @@ static const struct option dir_options[] = {
        {T("path"),     required_argument, NULL, IMAGEX_PATH_OPTION},
        {T("detailed"), no_argument,       NULL, IMAGEX_DETAILED_OPTION},
        {T("one-file-only"), no_argument,  NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
+       {T("ref"),      required_argument, NULL, IMAGEX_REF_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -271,22 +302,21 @@ static const struct option export_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
-       {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"),no_argument,       NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
+       {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
        {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
        {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
+       {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -307,6 +337,8 @@ static const struct option extract_options[] = {
        {T("nullglob"),     no_argument,      NULL, IMAGEX_NULLGLOB_OPTION},
        {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
+       {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
+       {T("recover-data"), no_argument,      NULL, IMAGEX_RECOVER_DATA_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -315,19 +347,23 @@ static const struct option info_options[] = {
        {T("check"),        no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),      no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument,  NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("extract-xml"),  required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
        {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
-       {T("lookup-table"), no_argument,       NULL, IMAGEX_LOOKUP_TABLE_OPTION},
-       {T("metadata"),     no_argument,       NULL, IMAGEX_METADATA_OPTION},
+       {T("lookup-table"), no_argument,       NULL, IMAGEX_BLOBS_OPTION},
+       {T("blobs"),        no_argument,       NULL, IMAGEX_BLOBS_OPTION},
        {T("xml"),          no_argument,       NULL, IMAGEX_XML_OPTION},
+       {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
 static const struct option join_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
+#if WIM_MOUNTING_SUPPORTED
 static const struct option mount_options[] = {
        {T("check"),             no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("debug"),             no_argument,       NULL, IMAGEX_DEBUG_OPTION},
@@ -338,33 +374,34 @@ static const struct option mount_options[] = {
        {T("allow-other"),       no_argument,       NULL, IMAGEX_ALLOW_OTHER_OPTION},
        {NULL, 0, NULL, 0},
 };
+#endif
 
 static const struct option optimize_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
-       {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
-       {T("recompress-slow"), no_argument,   NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"),no_argument,       NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
+       {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
        {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
+       {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
 static const struct option split_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
+#if WIM_MOUNTING_SUPPORTED
 static const struct option unmount_options[] = {
        {T("commit"),  no_argument, NULL, IMAGEX_COMMIT_OPTION},
        {T("check"),   no_argument, NULL, IMAGEX_CHECK_OPTION},
@@ -374,17 +411,19 @@ static const struct option unmount_options[] = {
        {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
        {NULL, 0, NULL, 0},
 };
+#endif
 
 static const struct option update_options[] = {
        /* Careful: some of the options here set the defaults for update
         * commands, but the flags given to an actual update command (and not to
-        * `imagex update' itself are also handled in
-        * update_command_add_option().  */
+        * wimupdate itself) are also handled in update_command_add_option(). */
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
        {T("command"),     required_argument, NULL, IMAGEX_COMMAND_OPTION},
        {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
+       {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
 
        /* Default delete options */
        {T("force"),       no_argument,       NULL, IMAGEX_FORCE_OPTION},
@@ -401,6 +440,7 @@ static const struct option update_options[] = {
        {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
        {T("no-replace"),  no_argument,       NULL, IMAGEX_NO_REPLACE_OPTION},
+       {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
 
        {NULL, 0, NULL, 0},
 };
@@ -485,7 +525,7 @@ verify_image_exists_and_is_single(int image, const tchar *image_name,
 static void
 print_available_compression_types(FILE *fp)
 {
-       static const tchar *s =
+       static const tchar * const s =
        T(
        "Available compression types:\n"
        "\n"
@@ -498,9 +538,9 @@ print_available_compression_types(FILE *fp)
        tfputs(s, fp);
 }
 
-/* Parse the argument to --compress */
+/* Parse the argument to --compress or --solid-compress  */
 static int
-get_compression_type(tchar *optarg)
+get_compression_type(tchar *optarg, bool solid)
 {
        int ctype;
        unsigned int compression_level = 0;
@@ -523,15 +563,27 @@ get_compression_type(tchar *optarg)
 
        if (!tstrcasecmp(optarg, T("maximum")) ||
            !tstrcasecmp(optarg, T("lzx")) ||
-           !tstrcasecmp(optarg, T("max")))
+           !tstrcasecmp(optarg, T("max"))) {
                ctype = WIMLIB_COMPRESSION_TYPE_LZX;
-       else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
+       } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
                ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
-       else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
+       } else if (!tstrcasecmp(optarg, T("recovery"))) {
+               if (!solid) {
+                       tfprintf(stderr,
+T(
+"Warning: use of '--compress=recovery' is discouraged because it behaves\n"
+"   differently from DISM.  Instead, you typically want to use '--solid' to\n"
+"   create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
+"   /compress:recovery.  But if you really want *non-solid* LZMS compression,\n"
+"   then you may suppress this warning by specifying '--compress=lzms' instead\n"
+"   of '--compress=recovery'.\n"));
+               }
                ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
-       else if (!tstrcasecmp(optarg, T("none")))
+       } else if (!tstrcasecmp(optarg, T("lzms"))) {
+               ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
+       } else if (!tstrcasecmp(optarg, T("none"))) {
                ctype = WIMLIB_COMPRESSION_TYPE_NONE;
-       else {
+       else {
                imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
                print_available_compression_types(stderr);
                return WIMLIB_COMPRESSION_TYPE_INVALID;
@@ -542,69 +594,135 @@ get_compression_type(tchar *optarg)
        return ctype;
 }
 
-static void
-set_compress_slow(void)
+/* Parse the argument to --compact */
+static int
+set_compact_mode(const tchar *arg, int *extract_flags)
 {
-#if 0
-       fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
-                       "         Use the '--compress=TYPE:LEVEL' option instead.\n");
-#endif
-       wimlib_set_default_compression_level(-1, 100);
+       int flag = 0;
+       if (!tstrcasecmp(arg, T("xpress4k")))
+               flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
+       else if (!tstrcasecmp(arg, T("xpress8k")))
+               flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
+       else if (!tstrcasecmp(arg, T("xpress16k")))
+               flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
+       else if (!tstrcasecmp(arg, T("lzx")))
+               flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
+
+       if (flag) {
+               *extract_flags |= flag;
+               return 0;
+       }
+
+       imagex_error(T(
+"\"%"TS"\" is not a recognized System Compression format.  The options are:"
+"\n"
+"    --compact=xpress4k\n"
+"    --compact=xpress8k\n"
+"    --compact=xpress16k\n"
+"    --compact=lzx\n"
+       ), arg);
+       return -1;
 }
 
-struct string_set {
-       const tchar **strings;
+
+struct string_list {
+       tchar **strings;
        unsigned num_strings;
        unsigned num_alloc_strings;
 };
 
-#define STRING_SET_INITIALIZER \
+#define STRING_LIST_INITIALIZER \
        { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
 
-#define STRING_SET(_strings) \
-       struct string_set _strings = STRING_SET_INITIALIZER
+#define STRING_LIST(_strings) \
+       struct string_list _strings = STRING_LIST_INITIALIZER
 
 static int
-string_set_append(struct string_set *set, const tchar *glob)
+string_list_append(struct string_list *list, tchar *glob)
 {
-       unsigned num_alloc_strings = set->num_alloc_strings;
+       unsigned num_alloc_strings = list->num_alloc_strings;
 
-       if (set->num_strings == num_alloc_strings) {
-               const tchar **new_strings;
+       if (list->num_strings == num_alloc_strings) {
+               tchar **new_strings;
 
                num_alloc_strings += 4;
-               new_strings = realloc(set->strings,
-                                     sizeof(set->strings[0]) * num_alloc_strings);
+               new_strings = realloc(list->strings,
+                                     sizeof(list->strings[0]) * num_alloc_strings);
                if (!new_strings) {
                        imagex_error(T("Out of memory!"));
                        return -1;
                }
-               set->strings = new_strings;
-               set->num_alloc_strings = num_alloc_strings;
+               list->strings = new_strings;
+               list->num_alloc_strings = num_alloc_strings;
        }
-       set->strings[set->num_strings++] = glob;
+       list->strings[list->num_strings++] = glob;
        return 0;
 }
 
 static void
-string_set_destroy(struct string_set *set)
+string_list_destroy(struct string_list *list)
 {
-       free(set->strings);
+       free(list->strings);
 }
 
 static int
-wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
+wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
 {
-       return wimlib_reference_resource_files(wim, set->strings,
-                                              set->num_strings,
+       return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
+                                              list->num_strings,
                                               WIMLIB_REF_FLAG_GLOB_ENABLE,
                                               open_flags);
 }
 
+static int
+append_image_property_argument(struct string_list *image_properties)
+{
+       if (!tstrchr(optarg, '=')) {
+               imagex_error(T("'--image-property' argument "
+                              "must be in the form NAME=VALUE"));
+               return -1;
+       }
+       return string_list_append(image_properties, optarg);
+}
+
+static int
+apply_image_properties(struct string_list *image_properties,
+                      WIMStruct *wim, int image, bool *any_changes_ret)
+{
+       bool any_changes = false;
+       for (unsigned i = 0; i < image_properties->num_strings; i++) {
+               tchar *name, *value;
+               const tchar *current_value;
+               int ret;
+
+               name = image_properties->strings[i];
+               value = tstrchr(name, '=');
+               *value++ = '\0';
+
+               current_value = wimlib_get_image_property(wim, image, name);
+               if (current_value && !tstrcmp(current_value, value)) {
+                       imagex_printf(T("The %"TS" property of image %d "
+                                       "already has value \"%"TS"\".\n"),
+                                     name, image, value);
+               } else {
+                       imagex_printf(T("Setting the %"TS" property of image "
+                                       "%d to \"%"TS"\".\n"),
+                                     name, image, value);
+                       ret = wimlib_set_image_property(wim, image, name, value);
+                       if (ret)
+                               return ret;
+                       any_changes = true;
+               }
+       }
+       if (any_changes_ret)
+               *any_changes_ret = any_changes;
+       return 0;
+}
+
 static void
 do_resource_not_found_warning(const tchar *wimfile,
                              const struct wimlib_wim_info *info,
-                             const struct string_set *refglobs)
+                             const struct string_list *refglobs)
 {
        if (info->total_parts > 1) {
                if (refglobs->num_strings == 0) {
@@ -622,6 +740,17 @@ do_resource_not_found_warning(const tchar *wimfile,
        }
 }
 
+static void
+do_metadata_not_found_warning(const tchar *wimfile,
+                             const struct wimlib_wim_info *info)
+{
+       if (info->part_number != 1) {
+               imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
+                              "       You must specify the first part."),
+                              wimfile);
+       }
+}
+
 /* Returns the size of a file given its name, or -1 if the file does not exist
  * or its size cannot be determined.  */
 static off_t
@@ -839,152 +968,6 @@ parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
        return sources;
 }
 
-/* Reads the contents of a file into memory. */
-static char *
-file_get_contents(const tchar *filename, size_t *len_ret)
-{
-       struct stat stbuf;
-       void *buf = NULL;
-       size_t len;
-       FILE *fp;
-
-       if (tstat(filename, &stbuf) != 0) {
-               imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
-               goto out;
-       }
-       len = stbuf.st_size;
-
-       fp = tfopen(filename, T("rb"));
-       if (!fp) {
-               imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
-               goto out;
-       }
-
-       buf = malloc(len ? len : 1);
-       if (!buf) {
-               imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
-                              "contents of file \"%"TS"\""), len, filename);
-               goto out_fclose;
-       }
-       if (fread(buf, 1, len, fp) != len) {
-               imagex_error_with_errno(T("Failed to read %zu bytes from the "
-                                         "file \"%"TS"\""), len, filename);
-               goto out_free_buf;
-       }
-       *len_ret = len;
-       goto out_fclose;
-out_free_buf:
-       free(buf);
-       buf = NULL;
-out_fclose:
-       fclose(fp);
-out:
-       return buf;
-}
-
-/* Read standard input until EOF and return the full contents in a malloc()ed
- * buffer and the number of bytes of data in @len_ret.  Returns NULL on read
- * error. */
-static char *
-stdin_get_contents(size_t *len_ret)
-{
-       /* stdin can, of course, be a pipe or other non-seekable file, so the
-        * total length of the data cannot be pre-determined */
-       char *buf = NULL;
-       size_t newlen = 1024;
-       size_t pos = 0;
-       size_t inc = 1024;
-       for (;;) {
-               char *p = realloc(buf, newlen);
-               size_t bytes_read, bytes_to_read;
-               if (!p) {
-                       imagex_error(T("out of memory while reading stdin"));
-                       break;
-               }
-               buf = p;
-               bytes_to_read = newlen - pos;
-               bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
-               pos += bytes_read;
-               if (bytes_read != bytes_to_read) {
-                       if (feof(stdin)) {
-                               *len_ret = pos;
-                               return buf;
-                       } else {
-                               imagex_error_with_errno(T("error reading stdin"));
-                               break;
-                       }
-               }
-               newlen += inc;
-               inc *= 3;
-               inc /= 2;
-       }
-       free(buf);
-       return NULL;
-}
-
-
-static tchar *
-translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
-{
-#ifndef __WIN32__
-       /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
-        * */
-       *num_tchars_ret = num_bytes;
-       return text;
-#else /* !__WIN32__ */
-       /* On Windows, translate the text to UTF-16LE */
-       wchar_t *text_wstr;
-       size_t num_wchars;
-
-       if (num_bytes >= 2 &&
-           (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
-            ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
-       {
-               /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
-                * with something that looks like an ASCII character encoded as
-                * a UTF-16LE code unit.  Assume the file is encoded as
-                * UTF-16LE.  This is not a 100% reliable check. */
-               num_wchars = num_bytes / 2;
-               text_wstr = (wchar_t*)text;
-       } else {
-               /* File does not look like UTF-16LE.  Assume it is encoded in
-                * the current Windows code page.  I think these are always
-                * ASCII-compatible, so any so-called "plain-text" (ASCII) files
-                * should work as expected. */
-               text_wstr = win32_mbs_to_wcs(text,
-                                            num_bytes,
-                                            &num_wchars);
-               free(text);
-       }
-       *num_tchars_ret = num_wchars;
-       return text_wstr;
-#endif /* __WIN32__ */
-}
-
-static tchar *
-file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
-{
-       char *contents;
-       size_t num_bytes;
-
-       contents = file_get_contents(filename, &num_bytes);
-       if (!contents)
-               return NULL;
-       return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
-}
-
-static tchar *
-stdin_get_text_contents(size_t *num_tchars_ret)
-{
-       char *contents;
-       size_t num_bytes;
-
-       contents = stdin_get_contents(&num_bytes);
-       if (!contents)
-               return NULL;
-       return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
-}
-
 #define TO_PERCENT(numerator, denominator) \
        (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
 
@@ -1037,6 +1020,31 @@ report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
                last_scan_progress = *scan;
        }
 }
+
+static struct wimlib_progress_info_split last_split_progress;
+
+static void
+report_split_progress(uint64_t bytes_completed_in_part)
+{
+       uint64_t completed_bytes = last_split_progress.completed_bytes +
+                                  bytes_completed_in_part;
+       unsigned percent_done = TO_PERCENT(completed_bytes,
+                                          last_split_progress.total_bytes);
+       unsigned unit_shift;
+       const tchar *unit_name;
+
+       unit_shift = get_unit(last_split_progress.total_bytes, &unit_name);
+       imagex_printf(T("\rSplitting WIM: %"PRIu64" %"TS" of "
+                       "%"PRIu64" %"TS" (%u%%) written, part %u of %u"),
+                     completed_bytes >> unit_shift,
+                     unit_name,
+                     last_split_progress.total_bytes >> unit_shift,
+                     unit_name,
+                     percent_done,
+                     last_split_progress.cur_part_number,
+                     last_split_progress.total_parts);
+}
+
 /* Progress callback function passed to various wimlib functions. */
 static enum wimlib_progress_status
 imagex_progress_func(enum wimlib_progress_msg msg,
@@ -1047,28 +1055,33 @@ imagex_progress_func(enum wimlib_progress_msg msg,
        unsigned unit_shift;
        const tchar *unit_name;
 
-       if (imagex_be_quiet)
-               return WIMLIB_PROGRESS_STATUS_CONTINUE;
        switch (msg) {
        case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
+               if (last_split_progress.total_bytes != 0) {
+                       /* wimlib_split() in progress; use the split-specific
+                        * progress message.  */
+                       report_split_progress(info->write_streams.completed_compressed_bytes);
+                       break;
+               }
                {
-                       static bool first = true;
-                       if (first) {
-                               imagex_printf(T("Writing %"TS"-compressed data "
-                                               "using %u thread%"TS"\n"),
-                                             wimlib_get_compression_type_string(
-                                                       info->write_streams.compression_type),
-                                       info->write_streams.num_threads,
-                                       (info->write_streams.num_threads == 1) ? T("") : T("s"));
-                               first = false;
+                       static bool started;
+                       if (!started) {
+                               if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
+                                       imagex_printf(T("Using %"TS" compression "
+                                                       "with %u thread%"TS"\n"),
+                                                     wimlib_get_compression_type_string(
+                                                               info->write_streams.compression_type),
+                                               info->write_streams.num_threads,
+                                               (info->write_streams.num_threads == 1) ? T("") : T("s"));
+                               }
+                               started = true;
                        }
                }
                unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->write_streams.completed_bytes,
                                          info->write_streams.total_bytes);
 
-               imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
-                       "written (%u%% done)"),
+               imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
                        info->write_streams.completed_bytes >> unit_shift,
                        unit_name,
                        info->write_streams.total_bytes >> unit_shift,
@@ -1106,7 +1119,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                         * default installation.  On UNIX-like systems, warn the
                         * user when fixing the target of an absolute symbolic
                         * link, so they know to disable this if they want.  */
-               #ifndef __WIN32__
+               #ifndef _WIN32
                        imagex_printf(T("\nWARNING: Adjusted target of "
                                        "absolute symbolic link \"%"TS"\"\n"
                                        "           (Use --norpfix to capture "
@@ -1176,7 +1189,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                percent_done = TO_PERCENT(info->extract.completed_bytes,
                                          info->extract.total_bytes);
                unit_shift = get_unit(info->extract.total_bytes, &unit_name);
-               imagex_printf(T("\rExtracting files: "
+               imagex_printf(T("\rExtracting file data: "
                          "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
                        info->extract.completed_bytes >> unit_shift,
                        unit_name,
@@ -1205,26 +1218,9 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                }
                break;
        case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
-               percent_done = TO_PERCENT(info->split.completed_bytes,
-                                         info->split.total_bytes);
-               unit_shift = get_unit(info->split.total_bytes, &unit_name);
-               imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
-                         "%"PRIu64" %"TS" (%u%%) written\n"),
-                       info->split.part_name,
-                       info->split.cur_part_number,
-                       info->split.total_parts,
-                       info->split.completed_bytes >> unit_shift,
-                       unit_name,
-                       info->split.total_bytes >> unit_shift,
-                       unit_name,
-                       percent_done);
-               break;
        case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
-               if (info->split.completed_bytes == info->split.total_bytes) {
-                       imagex_printf(T("Finished writing split WIM part %u of %u\n"),
-                               info->split.cur_part_number,
-                               info->split.total_parts);
-               }
+               last_split_progress = info->split;
+               report_split_progress(0);
                break;
        case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
                switch (info->update.command->op) {
@@ -1273,7 +1269,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
                                          info->verify_streams.total_bytes);
                unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
-               imagex_printf(T("\rVerifying streams: "
+               imagex_printf(T("\rVerifying file data: "
                          "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
                        info->verify_streams.completed_bytes >> unit_shift,
                        unit_name,
@@ -1286,7 +1282,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
        default:
                break;
        }
-       fflush(imagex_info_file);
+       imagex_flush_output();
        return WIMLIB_PROGRESS_STATUS_CONTINUE;
 }
 
@@ -1303,16 +1299,37 @@ parse_num_threads(const tchar *optarg)
        }
 }
 
-static uint32_t parse_chunk_size(const tchar *optarg)
+static uint32_t
+parse_chunk_size(const tchar *optarg)
 {
-       tchar *tmp;
-       unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
-       if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
-               imagex_error(T("Chunk size must be a non-negative integer!"));
-               return UINT32_MAX;
-       } else {
-               return chunk_size;
-       }
+       tchar *tmp;
+       uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
+       if (chunk_size == 0) {
+               imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
+                              "       with optional K, M, or G suffix"));
+               return UINT32_MAX;
+       }
+       if (*tmp) {
+               if (*tmp == T('k') || *tmp == T('K')) {
+                       chunk_size <<= 10;
+                       tmp++;
+               } else if (*tmp == T('m') || *tmp == T('M')) {
+                       chunk_size <<= 20;
+                       tmp++;
+               } else if (*tmp == T('g') || *tmp == T('G')) {
+                       chunk_size <<= 30;
+                       tmp++;
+               }
+               if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
+                       imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
+                       return UINT32_MAX;
+               }
+       }
+       if (chunk_size >= UINT32_MAX) {
+               imagex_error(T("Invalid chunk size specification; the value is too large!"));
+               return UINT32_MAX;
+       }
+       return chunk_size;
 }
 
 
@@ -1366,7 +1383,7 @@ update_command_add_option(int op, const tchar *option,
        return recognized;
 }
 
-/* How many nonoption arguments each `imagex update' command expects */
+/* How many nonoption arguments each wimupdate command expects */
 static const unsigned update_command_num_nonoptions[] = {
        [WIMLIB_UPDATE_OP_ADD] = 2,
        [WIMLIB_UPDATE_OP_DELETE] = 1,
@@ -1398,7 +1415,7 @@ update_command_add_nonoption(int op, const tchar *nonoption,
 }
 
 /*
- * Parse a command passed on stdin to `imagex update'.
+ * Parse a command passed on stdin to wimupdate.
  *
  * @line:      Text of the command.
  * @len:       Length of the line, including a null terminator
@@ -1542,7 +1559,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
        const tchar *image_num_or_name = NULL;
        int extract_flags = 0;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        for_opt(c, apply_options) {
                switch (c) {
@@ -1553,7 +1570,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -1579,12 +1596,17 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
                        extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
                        break;
-               case IMAGEX_RESUME_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
-                       break;
                case IMAGEX_WIMBOOT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
                        break;
+               case IMAGEX_COMPACT_OPTION:
+                       ret = set_compact_mode(optarg, &extract_flags);
+                       if (ret)
+                               goto out_free_refglobs;
+                       break;
+               case IMAGEX_RECOVER_DATA_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1649,7 +1671,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        goto out_wimlib_free;
        }
 
-#ifndef __WIN32__
+#ifndef _WIN32
        {
                /* Interpret a regular file or block device target as an NTFS
                 * volume.  */
@@ -1692,11 +1714,13 @@ imagex_apply(int argc, tchar **argv, int cmd)
                                       "       make sure you have "
                                       "concatenated together all parts."));
                }
+       } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
+               do_metadata_not_found_warning(wimfile, &info);
        }
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -1705,18 +1729,23 @@ out_usage:
        goto out_free_refglobs;
 }
 
-/* Create a WIM image from a directory tree, NTFS volume, or multiple files or
- * directory trees.  'wimlib-imagex capture': create a new WIM file containing
- * the desired image.  'wimlib-imagex append': add a new image to an existing
- * WIM file. */
+/*
+ * Create a WIM image from a directory tree, NTFS volume, or multiple files or
+ * directory trees.  'wimcapture': create a new WIM file containing the desired
+ * image.  'wimappend': add a new image to an existing WIM file; or, with
+ * '--create' behave like 'wimcapture' if the WIM file doesn't exist.
+ */
 static int
 imagex_capture_or_append(int argc, tchar **argv, int cmd)
 {
        int c;
+       bool create = false;
+       bool appending = (cmd == CMD_APPEND);
        int open_flags = 0;
        int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
                        WIMLIB_ADD_FLAG_WINCONFIG |
-                       WIMLIB_ADD_FLAG_VERBOSE;
+                       WIMLIB_ADD_FLAG_VERBOSE |
+                       WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
        int write_flags = 0;
        int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
        uint32_t chunk_size = UINT32_MAX;
@@ -1725,14 +1754,13 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        const tchar *wimfile;
        int wim_fd;
        const tchar *name;
-       const tchar *desc;
-       const tchar *flags_element = NULL;
+       STRING_LIST(image_properties);
 
        WIMStruct *wim;
-       STRING_SET(base_wimfiles);
+       STRING_LIST(base_wimfiles);
        WIMStruct **base_wims;
 
-       WIMStruct *template_wim;
+       WIMStruct *template_wim = NULL;
        const tchar *template_wimfile = NULL;
        const tchar *template_image_name_or_num = NULL;
        int template_image = WIMLIB_NO_IMAGE;
@@ -1760,6 +1788,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
@@ -1770,13 +1800,10 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
                        break;
                case IMAGEX_COMPRESS_OPTION:
-                       compression_type = get_compression_type(optarg);
+                       compression_type = get_compression_type(optarg, false);
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       break;
                case IMAGEX_CHUNK_SIZE_OPTION:
                        chunk_size = parse_chunk_size(optarg);
                        if (chunk_size == UINT32_MAX)
@@ -1788,15 +1815,28 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                goto out_err;
                        break;
                case IMAGEX_SOLID_COMPRESS_OPTION:
-                       solid_ctype = get_compression_type(optarg);
+                       solid_ctype = get_compression_type(optarg, true);
                        if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
                case IMAGEX_SOLID_OPTION:
-                       write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+                       write_flags |= WIMLIB_WRITE_FLAG_SOLID;
+                       break;
+               case IMAGEX_NO_SOLID_SORT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
                        break;
-               case IMAGEX_FLAGS_OPTION:
-                       flags_element = optarg;
+               case IMAGEX_FLAGS_OPTION: {
+                       tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
+                       tsprintf(p, T("FLAGS=%"TS), optarg);
+                       ret = string_list_append(&image_properties, p);
+                       if (ret)
+                               goto out;
+                       break;
+               }
+               case IMAGEX_IMAGE_PROPERTY_OPTION:
+                       ret = append_image_property_argument(&image_properties);
+                       if (ret)
+                               goto out;
                        break;
                case IMAGEX_DEREFERENCE_OPTION:
                        add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
@@ -1854,21 +1894,32 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                        template_image_name_or_num = optarg;
                                }
                        }
+               #ifdef _WIN32
+                       imagex_printf(T("[WARNING] '--update-of' is unreliable on Windows!\n"));
+               #endif
                        break;
                case IMAGEX_DELTA_FROM_OPTION:
-                       if (cmd != CMD_CAPTURE) {
-                               imagex_error(T("'--delta-from' is only "
-                                              "valid for capture!"));
-                               goto out_usage;
-                       }
-                       ret = string_set_append(&base_wimfiles, optarg);
+                       ret = string_list_append(&base_wimfiles, optarg);
                        if (ret)
-                               goto out_free_base_wimfiles;
+                               goto out;
                        write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
                        break;
                case IMAGEX_WIMBOOT_OPTION:
                        add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
                        break;
+               case IMAGEX_UNSAFE_COMPACT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+                       break;
+               case IMAGEX_SNAPSHOT_OPTION:
+                       add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
+                       break;
+               case IMAGEX_CREATE_OPTION:
+                       if (cmd == CMD_CAPTURE) {
+                               imagex_error(T("'--create' is only valid for 'wimappend', not 'wimcapture'"));
+                               goto out_err;
+                       }
+                       create = true;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1891,9 +1942,9 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
                        /* With --wimboot, default to XPRESS compression.  */
                        compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
-               } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
+               } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
                        /* With --solid, default to LZMS compression.  (However,
-                        * this will not affect solid blocks!)  */
+                        * this will not affect solid resources!)  */
                        compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
                } else {
                        /* Otherwise, default to LZX compression.  */
@@ -1903,6 +1954,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
        if (!tstrcmp(wimfile, T("-"))) {
                /* Writing captured WIM to standard output.  */
+               if (create)
+                       appending = false;
        #if 0
                if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
                        imagex_error("Can't write a non-pipable WIM to "
@@ -1914,15 +1967,37 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        #else
                write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
        #endif
-               if (cmd == CMD_APPEND) {
+               if (appending) {
                        imagex_error(T("Using standard output for append does "
                                       "not make sense."));
                        goto out_err;
                }
                wim_fd = STDOUT_FILENO;
                wimfile = NULL;
-               imagex_info_file = stderr;
+               imagex_output_to_stderr();
                set_fd_to_binary_mode(wim_fd);
+       } else {
+               struct stat stbuf;
+
+               /* Check for 'wimappend --create' acting as wimcapture */
+               if (create && tstat(wimfile, &stbuf) != 0 && errno == ENOENT) {
+
+                       appending = false;
+
+                       /* Ignore '--update-of' for the target WIMFILE */
+                       if (template_image_name_or_num &&
+                           (!template_wimfile ||
+                            !tstrcmp(template_wimfile, wimfile)))
+                       {
+                               template_image_name_or_num = NULL;
+                               template_wimfile = NULL;
+                       }
+               }
+       }
+
+       if ((write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) && !appending) {
+               imagex_error(T("'--unsafe-compact' is only valid for append!"));
+               goto out_err;
        }
 
        /* If template image was specified using --update-of=IMAGE rather
@@ -1932,7 +2007,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        /* Capturing delta WIM based on single WIM:  default to
                         * base WIM.  */
                        template_wimfile = base_wimfiles.strings[0];
-               } else if (cmd == CMD_APPEND) {
+               } else if (appending) {
                        /* Appending to WIM:  default to WIM being appended to.
                         */
                        template_wimfile = wimfile;
@@ -1969,21 +2044,20 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                name = tbasename(tstrcpy(source_copy, source));
                name_defaulted = true;
        }
-       /* Image description defaults to NULL if not given. */
-       if (argc >= 4)
-               desc = argv[3];
-       else
-               desc = NULL;
+
+       /* Image description (if given). */
+       if (argc >= 4) {
+               tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
+               tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
+               ret = string_list_append(&image_properties, p);
+               if (ret)
+                       goto out;
+       }
 
        if (source_list) {
                /* Set up capture sources in source list mode */
-               if (source[0] == T('-') && source[1] == T('\0')) {
-                       source_list_contents = stdin_get_text_contents(&source_list_nchars);
-               } else {
-                       source_list_contents = file_get_text_contents(source,
-                                                                     &source_list_nchars);
-               }
-               if (!source_list_contents)
+               if (wimlib_load_text_file(source, &source_list_contents,
+                                         &source_list_nchars) != 0)
                        goto out_err;
 
                capture_sources = parse_source_list(&source_list_contents,
@@ -2006,7 +2080,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        }
 
        /* Open the existing WIM, or create a new one.  */
-       if (cmd == CMD_APPEND) {
+       if (appending) {
                ret = wimlib_open_wim_with_progress(wimfile,
                                                    open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
                                                    &wim,
@@ -2026,11 +2100,21 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                ret = wimlib_set_output_chunk_size(wim, chunk_size);
                if (ret)
                        goto out_free_wim;
-       } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
-                  compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
-               ret = wimlib_set_output_chunk_size(wim, 4096);
-               if (ret)
-                       goto out_free_wim;
+       } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
+
+               int ctype = compression_type;
+
+               if (appending) {
+                       struct wimlib_wim_info info;
+                       wimlib_get_wim_info(wim, &info);
+                       ctype = info.compression_type;
+               }
+
+               if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
+                       ret = wimlib_set_output_chunk_size(wim, 4096);
+                       if (ret)
+                               goto out_free_wim;
+               }
        }
        if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
                ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
@@ -2043,7 +2127,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        goto out_free_wim;
        }
 
-#ifndef __WIN32__
+#ifndef _WIN32
        /* Detect if source is regular file or block device and set NTFS volume
         * capture mode.  */
        if (!source_list) {
@@ -2069,7 +2153,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        /* If the user did not specify an image name, and the basename of the
         * source already exists as an image name in the WIM file, append a
         * suffix to make it unique. */
-       if (cmd == CMD_APPEND && name_defaulted) {
+       if (appending && name_defaulted) {
                unsigned long conflict_idx;
                tchar *name_end = tstrchr(name, T('\0'));
                for (conflict_idx = 1;
@@ -2121,13 +2205,19 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
         * open the WIM if needed and parse the image index.  */
        if (template_image_name_or_num) {
 
-
-               if (base_wimfiles.num_strings == 1 &&
-                   template_wimfile == base_wimfiles.strings[0]) {
-                       template_wim = base_wims[0];
-               } else if (template_wimfile == wimfile) {
+               if (appending && !tstrcmp(template_wimfile, wimfile)) {
                        template_wim = wim;
                } else {
+                       for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
+                               if (!tstrcmp(template_wimfile,
+                                            base_wimfiles.strings[i])) {
+                                       template_wim = base_wims[i];
+                                       break;
+                               }
+                       }
+               }
+
+               if (!template_wim) {
                        ret = wimlib_open_wim_with_progress(template_wimfile,
                                                            open_flags,
                                                            &template_wim,
@@ -2159,8 +2249,6 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                                        template_wimfile);
                if (ret)
                        goto out_free_template_wim;
-       } else {
-               template_wim = NULL;
        }
 
        ret = wimlib_add_image_multisource(wim,
@@ -2172,29 +2260,18 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        if (ret)
                goto out_free_template_wim;
 
-       if (desc || flags_element || template_image_name_or_num) {
-               /* User provided <DESCRIPTION> or <FLAGS> element, or an image
-                * on which the added one is to be based has been specified with
-                * --update-of.  Get the index of the image we just
-                *  added, then use it to call the appropriate functions.  */
+       if (image_properties.num_strings || template_image_name_or_num) {
+               /* User asked to set additional image properties, or an image on
+                * which the added one is to be based has been specified with
+                * --update-of.  */
                struct wimlib_wim_info info;
 
                wimlib_get_wim_info(wim, &info);
 
-               if (desc) {
-                       ret = wimlib_set_image_descripton(wim,
-                                                         info.image_count,
-                                                         desc);
-                       if (ret)
-                               goto out_free_template_wim;
-               }
-
-               if (flags_element) {
-                       ret = wimlib_set_image_flags(wim, info.image_count,
-                                                    flags_element);
-                       if (ret)
-                               goto out_free_template_wim;
-               }
+               ret = apply_image_properties(&image_properties, wim,
+                                            info.image_count, NULL);
+               if (ret)
+                       goto out_free_template_wim;
 
                /* Reference template image if the user provided one.  */
                if (template_image_name_or_num) {
@@ -2213,7 +2290,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
        /* Write the new WIM or overwrite the existing WIM with the new image
         * appended.  */
-       if (cmd == CMD_APPEND) {
+       if (appending) {
                ret = wimlib_overwrite(wim, write_flags, num_threads);
        } else if (wimfile) {
                ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
@@ -2223,10 +2300,13 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                         write_flags, num_threads);
        }
 out_free_template_wim:
-       /* template_wim may alias base_wims[0] or wim.  */
-       if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
-           template_wim != wim)
-               wimlib_free(template_wim);
+       /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
+       if (template_wim == wim)
+               goto out_free_base_wims;
+       for (size_t i = 0; i < base_wimfiles.num_strings; i++)
+               if (template_wim == base_wims[i])
+                       goto out_free_base_wims;
+       wimlib_free(template_wim);
 out_free_base_wims:
        for (size_t i = 0; i < base_wimfiles.num_strings; i++)
                wimlib_free(base_wims[i]);
@@ -2238,15 +2318,16 @@ out_free_capture_sources:
                free(capture_sources);
 out_free_source_list_contents:
        free(source_list_contents);
-out_free_base_wimfiles:
-       string_set_destroy(&base_wimfiles);
+out:
+       string_list_destroy(&image_properties);
+       string_list_destroy(&base_wimfiles);
        return ret;
 
 out_usage:
        usage(cmd, stderr);
 out_err:
        ret = -1;
-       goto out_free_base_wimfiles;
+       goto out;
 }
 
 /* Remove image(s) from a WIM. */
@@ -2266,11 +2347,16 @@ imagex_delete(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_SOFT_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
                        break;
+               case IMAGEX_UNSAFE_COMPACT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2356,21 +2442,21 @@ static const struct {
 #define TIMESTR_MAX 100
 
 static void
-timespec_to_string(const struct timespec *spec, tchar *buf)
+print_time(const tchar *type, const struct wimlib_timespec *wts,
+          int32_t high_part)
 {
-       time_t t = spec->tv_sec;
+       tchar timestr[TIMESTR_MAX];
+       time_t t;
        struct tm tm;
-       gmtime_r(&t, &tm);
-       tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
-       buf[TIMESTR_MAX - 1] = '\0';
-}
 
-static void
-print_time(const tchar *type, const struct timespec *spec)
-{
-       tchar timestr[TIMESTR_MAX];
+       if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
+               t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
+       else
+               t = wts->tv_sec;
 
-       timespec_to_string(spec, timestr);
+       gmtime_r(&t, &tm);
+       tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
+       timestr[TIMESTR_MAX - 1] = '\0';
 
        tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
 }
@@ -2384,6 +2470,9 @@ static void print_byte_field(const uint8_t field[], size_t len)
 static void
 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
 {
+       tchar attr_string[256];
+       tchar *p;
+
        tputs(T("WIM Information:"));
        tputs(T("----------------"));
        tprintf(T("Path:           %"TS"\n"), wimfile);
@@ -2399,47 +2488,66 @@ print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
        tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
        tprintf(T("Boot Index:     %d\n"), info->boot_index);
        tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
-       tprintf(T("Integrity Info: %"TS"\n"),
-               info->has_integrity_table ? T("yes") : T("no"));
-       tprintf(T("Relative path junction: %"TS"\n"),
-               info->has_rpfix ? T("yes") : T("no"));
-       tprintf(T("Pipable:        %"TS"\n"),
-               info->pipable ? T("yes") : T("no"));
-       tputchar(T('\n'));
+
+       attr_string[0] = T('\0');
+
+       if (info->pipable)
+               tstrcat(attr_string, T("Pipable, "));
+
+       if (info->has_integrity_table)
+               tstrcat(attr_string, T("Integrity info, "));
+
+       if (info->has_rpfix)
+               tstrcat(attr_string, T("Relative path junction, "));
+
+       if (info->resource_only)
+               tstrcat(attr_string, T("Resource only, "));
+
+       if (info->metadata_only)
+               tstrcat(attr_string, T("Metadata only, "));
+
+       if (info->is_marked_readonly)
+               tstrcat(attr_string, T("Readonly, "));
+
+       p = tstrchr(attr_string, T('\0'));
+       if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
+               p[-2] = T('\0');
+
+       tprintf(T("Attributes:     %"TS"\n\n"), attr_string);
 }
 
 static int
 print_resource(const struct wimlib_resource_entry *resource,
               void *_ignore)
 {
-       tprintf(T("Hash                = 0x"));
+       tprintf(T("Hash              = 0x"));
        print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
        tputchar(T('\n'));
 
        if (!resource->is_missing) {
-               tprintf(T("Uncompressed size   = %"PRIu64" bytes\n"),
+               tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
                        resource->uncompressed_size);
                if (resource->packed) {
-                       tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
-                               resource->raw_resource_compressed_size);
-
-                       tprintf(T("Raw offset in WIM   = %"PRIu64" bytes\n"),
+                       tprintf(T("Solid resource    = %"PRIu64" => %"PRIu64" "
+                                 "bytes @ offset %"PRIu64"\n"),
+                               resource->raw_resource_uncompressed_size,
+                               resource->raw_resource_compressed_size,
                                resource->raw_resource_offset_in_wim);
 
-                       tprintf(T("Offset in raw       = %"PRIu64" bytes\n"),
+                       tprintf(T("Solid offset      = %"PRIu64" bytes\n"),
                                resource->offset);
                } else {
-                       tprintf(T("Compressed size     = %"PRIu64" bytes\n"),
+                       tprintf(T("Compressed size   = %"PRIu64" bytes\n"),
                                resource->compressed_size);
 
-                       tprintf(T("Offset in WIM       = %"PRIu64" bytes\n"),
+                       tprintf(T("Offset in WIM     = %"PRIu64" bytes\n"),
                                resource->offset);
                }
 
-               tprintf(T("Part Number         = %u\n"), resource->part_number);
-               tprintf(T("Reference Count     = %u\n"), resource->reference_count);
+               tprintf(T("Part Number       = %u\n"), resource->part_number);
+               tprintf(T("Reference Count   = %u\n"), resource->reference_count);
 
-               tprintf(T("Flags               = "));
+               tprintf(T("Flags             = "));
                if (resource->is_compressed)
                        tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
                if (resource->is_metadata)
@@ -2449,7 +2557,7 @@ print_resource(const struct wimlib_resource_entry *resource,
                if (resource->is_spanned)
                        tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
                if (resource->packed)
-                       tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS  "));
+                       tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
                tputchar(T('\n'));
        }
        tputchar(T('\n'));
@@ -2457,11 +2565,12 @@ print_resource(const struct wimlib_resource_entry *resource,
 }
 
 static void
-print_lookup_table(WIMStruct *wim)
+print_blobs(WIMStruct *wim)
 {
        wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
 }
 
+#ifndef _WIN32
 static void
 default_print_security_descriptor(const uint8_t *sd, size_t size)
 {
@@ -2469,11 +2578,29 @@ default_print_security_descriptor(const uint8_t *sd, size_t size)
        print_byte_field(sd, size);
        tputchar(T('\n'));
 }
+#endif
+
+static bool
+is_null_guid(const uint8_t *guid)
+{
+       static const uint8_t null_guid[WIMLIB_GUID_LEN];
+
+       return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
+}
+
+static void
+print_guid(const tchar *label, const uint8_t *guid)
+{
+       if (is_null_guid(guid))
+               return;
+       tprintf(T("%-20"TS"= 0x"), label);
+       print_byte_field(guid, WIMLIB_GUID_LEN);
+       tputchar(T('\n'));
+}
 
 static void
 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
 {
-
        tprintf(T(
 "----------------------------------------------------------------------------\n"));
        tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
@@ -2490,9 +2617,12 @@ print_dentry_detailed(const struct wimlib_dir_entry *dentry)
                                          dentry->security_descriptor_size);
        }
 
-       print_time(T("Creation Time"), &dentry->creation_time);
-       print_time(T("Last Write Time"), &dentry->last_write_time);
-       print_time(T("Last Access Time"), &dentry->last_access_time);
+       print_time(T("Creation Time"),
+                  &dentry->creation_time, dentry->creation_time_high);
+       print_time(T("Last Write Time"),
+                  &dentry->last_write_time, dentry->last_write_time_high);
+       print_time(T("Last Access Time"),
+                  &dentry->last_access_time, dentry->last_access_time_high);
 
 
        if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
@@ -2508,10 +2638,21 @@ print_dentry_detailed(const struct wimlib_dir_entry *dentry)
                        dentry->unix_mode, dentry->unix_rdev);
        }
 
+       if (!is_null_guid(dentry->object_id.object_id)) {
+               print_guid(T("Object ID"), dentry->object_id.object_id);
+               print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
+               print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
+               print_guid(T("Domain ID"), dentry->object_id.domain_id);
+       }
+
        for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
                if (dentry->streams[i].stream_name) {
-                       tprintf(T("\tData stream \"%"TS"\":\n"),
+                       tprintf(T("\tNamed data stream \"%"TS"\":\n"),
                                dentry->streams[i].stream_name);
+               } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
+                       tprintf(T("\tRaw encrypted data stream:\n"));
+               } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
+                       tprintf(T("\tReparse point stream:\n"));
                } else {
                        tprintf(T("\tUnnamed data stream:\n"));
                }
@@ -2545,6 +2686,8 @@ imagex_dir(int argc, tchar **argv, int cmd)
        };
        int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
 
+       STRING_LIST(refglobs);
+
        for_opt(c, dir_options) {
                switch (c) {
                case IMAGEX_PATH_OPTION:
@@ -2556,6 +2699,11 @@ imagex_dir(int argc, tchar **argv, int cmd)
                case IMAGEX_ONE_FILE_ONLY_OPTION:
                        iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
                        break;
+               case IMAGEX_REF_OPTION:
+                       ret = string_list_append(&refglobs, optarg);
+                       if (ret)
+                               goto out_free_refglobs;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2576,7 +2724,7 @@ imagex_dir(int argc, tchar **argv, int cmd)
        ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
                                            imagex_progress_func, NULL);
        if (ret)
-               goto out;
+               goto out_free_refglobs;
 
        if (argc >= 2) {
                image = wimlib_resolve_image(wim, argv[1]);
@@ -2600,17 +2748,30 @@ imagex_dir(int argc, tchar **argv, int cmd)
                image = 1;
        }
 
+       if (refglobs.num_strings) {
+               ret = wim_reference_globs(wim, &refglobs, 0);
+               if (ret)
+                       goto out_wimlib_free;
+       }
+
        ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
                                      print_dentry, &options);
+       if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
+               struct wimlib_wim_info info;
+
+               wimlib_get_wim_info(wim, &info);
+               do_metadata_not_found_warning(wimfile, &info);
+       }
 out_wimlib_free:
        wimlib_free(wim);
-out:
+out_free_refglobs:
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
        usage(CMD_DIR, stderr);
        ret = -1;
-       goto out;
+       goto out_free_refglobs;
 }
 
 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
@@ -2636,7 +2797,7 @@ imagex_export(int argc, tchar **argv, int cmd)
        int image;
        struct stat stbuf;
        bool wim_is_new;
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
        unsigned num_threads = 0;
        uint32_t chunk_size = UINT32_MAX;
        uint32_t solid_chunk_size = UINT32_MAX;
@@ -2649,25 +2810,26 @@ imagex_export(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        break;
                case IMAGEX_COMPRESS_OPTION:
-                       compression_type = get_compression_type(optarg);
+                       compression_type = get_compression_type(optarg, false);
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       break;
                case IMAGEX_RECOMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
                        break;
                case IMAGEX_SOLID_OPTION:
-                       write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+                       write_flags |= WIMLIB_WRITE_FLAG_SOLID;
+                       break;
+               case IMAGEX_NO_SOLID_SORT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
                        break;
                case IMAGEX_CHUNK_SIZE_OPTION:
                        chunk_size = parse_chunk_size(optarg);
@@ -2680,12 +2842,12 @@ imagex_export(int argc, tchar **argv, int cmd)
                                goto out_err;
                        break;
                case IMAGEX_SOLID_COMPRESS_OPTION:
-                       solid_ctype = get_compression_type(optarg);
+                       solid_ctype = get_compression_type(optarg, true);
                        if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -2706,6 +2868,9 @@ imagex_export(int argc, tchar **argv, int cmd)
                case IMAGEX_WIMBOOT_OPTION:
                        export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
                        break;
+               case IMAGEX_UNSAFE_COMPACT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2746,7 +2911,7 @@ imagex_export(int argc, tchar **argv, int cmd)
        #endif
                dest_wimfile = NULL;
                dest_wim_fd = STDOUT_FILENO;
-               imagex_info_file = stderr;
+               imagex_output_to_stderr();
                set_fd_to_binary_mode(dest_wim_fd);
        }
        errno = ENOENT;
@@ -2754,9 +2919,9 @@ imagex_export(int argc, tchar **argv, int cmd)
                wim_is_new = false;
                /* Destination file exists. */
 
-               if (!S_ISREG(stbuf.st_mode)) {
-                       imagex_error(T("\"%"TS"\" is not a regular file"),
-                                    dest_wimfile);
+               if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
+                       imagex_error(T("\"%"TS"\" is not a regular file "
+                                      "or block device"), dest_wimfile);
                        ret = -1;
                        goto out_free_src_wim;
                }
@@ -2795,6 +2960,13 @@ imagex_export(int argc, tchar **argv, int cmd)
                        goto out_free_src_wim;
                }
 
+               if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
+                       imagex_error(T("'--unsafe-compact' is only valid when "
+                                      "exporting to an existing WIM file!"));
+                       ret = -1;
+                       goto out_free_src_wim;
+               }
+
                /* dest_wimfile is not an existing file, so create a new WIM. */
 
                if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
@@ -2802,7 +2974,7 @@ imagex_export(int argc, tchar **argv, int cmd)
                         * to that of the source WIM, unless --solid or
                         * --wimboot was specified.   */
 
-                       if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
+                       if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
                                compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
                        else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
                                compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
@@ -2872,6 +3044,8 @@ imagex_export(int argc, tchar **argv, int cmd)
                if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
                        do_resource_not_found_warning(src_wimfile,
                                                      &src_info, &refglobs);
+               } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
+                       do_metadata_not_found_warning(src_wimfile, &src_info);
                }
                goto out_free_dest_wim;
        }
@@ -2890,7 +3064,7 @@ out_free_dest_wim:
 out_free_src_wim:
        wimlib_free(src_wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -2917,7 +3091,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                            WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
        int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        tchar *root_path = WIMLIB_WIM_ROOT_PATH;
 
@@ -2930,7 +3104,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -2951,8 +3125,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_TO_STDOUT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
-                       imagex_info_file = stderr;
-                       imagex_be_quiet = true;
+                       imagex_suppress_output();
                        set_fd_to_binary_mode(STDOUT_FILENO);
                        break;
                case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
@@ -2971,6 +3144,14 @@ imagex_extract(int argc, tchar **argv, int cmd)
                case IMAGEX_WIMBOOT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
                        break;
+               case IMAGEX_COMPACT_OPTION:
+                       ret = set_compact_mode(optarg, &extract_flags);
+                       if (ret)
+                               goto out_free_refglobs;
+                       break;
+               case IMAGEX_RECOVER_DATA_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3034,17 +3215,22 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        argc -= num_paths;
                        argv += num_paths;
                } else {
+                       const tchar *listfile = argv[0] + 1;
+
+                       if (!tstrcmp(listfile, T("-"))) {
+                               tputs(T("Reading pathlist file from standard input..."));
+                               listfile = NULL;
+                       }
+
                        ret = wimlib_extract_pathlist(wim, image, dest_dir,
-                                                     argv[0] + 1,
-                                                     extract_flags);
+                                                     listfile, extract_flags);
                        argc--;
                        argv++;
                }
        }
 
        if (ret == 0) {
-               if (!imagex_be_quiet)
-                       imagex_printf(T("Done extracting files.\n"));
+               imagex_printf(T("Done extracting files.\n"));
        } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
                if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
                                      WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
@@ -3064,11 +3250,16 @@ imagex_extract(int argc, tchar **argv, int cmd)
 
                wimlib_get_wim_info(wim, &info);
                do_resource_not_found_warning(wimfile, &info, &refglobs);
+       } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
+               struct wimlib_wim_info info;
+
+               wimlib_get_wim_info(wim, &info);
+               do_metadata_not_found_warning(wimfile, &info);
        }
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -3085,21 +3276,19 @@ imagex_info(int argc, tchar **argv, int cmd)
 {
        int c;
        bool boot         = false;
-       bool check        = false;
-       bool nocheck      = false;
        bool header       = false;
-       bool lookup_table = false;
+       bool blobs        = false;
        bool xml          = false;
        bool short_header = true;
        const tchar *xml_out_file = NULL;
        const tchar *wimfile;
        const tchar *image_num_or_name;
-       const tchar *new_name;
-       const tchar *new_desc;
+       STRING_LIST(image_properties);
        WIMStruct *wim;
        int image;
        int ret;
        int open_flags = 0;
+       int write_flags = 0;
        struct wimlib_wim_info info;
 
        for_opt(c, info_options) {
@@ -3108,17 +3297,20 @@ imagex_info(int argc, tchar **argv, int cmd)
                        boot = true;
                        break;
                case IMAGEX_CHECK_OPTION:
-                       check = true;
+                       open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
-                       nocheck = true;
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        break;
                case IMAGEX_HEADER_OPTION:
                        header = true;
                        short_header = false;
                        break;
-               case IMAGEX_LOOKUP_TABLE_OPTION:
-                       lookup_table = true;
+               case IMAGEX_BLOBS_OPTION:
+                       blobs = true;
                        short_header = false;
                        break;
                case IMAGEX_XML_OPTION:
@@ -3129,10 +3321,11 @@ imagex_info(int argc, tchar **argv, int cmd)
                        xml_out_file = optarg;
                        short_header = false;
                        break;
-               case IMAGEX_METADATA_OPTION:
-                       imagex_error(T("The --metadata option has been removed. "
-                                      "Use 'wimdir --detail' instead."));
-                       goto out_err;
+               case IMAGEX_IMAGE_PROPERTY_OPTION:
+                       ret = append_image_property_argument(&image_properties);
+                       if (ret)
+                               goto out;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3145,16 +3338,24 @@ imagex_info(int argc, tchar **argv, int cmd)
 
        wimfile           = argv[0];
        image_num_or_name = (argc >= 2) ? argv[1] : T("all");
-       new_name          = (argc >= 3) ? argv[2] : NULL;
-       new_desc          = (argc >= 4) ? argv[3] : NULL;
 
-       if (check && nocheck) {
-               imagex_error(T("Can't specify both --check and --nocheck"));
-               goto out_err;
+       if (argc >= 3) {
+               /* NEW_NAME */
+               tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
+               tsprintf(p, T("NAME=%"TS), argv[2]);
+               ret = string_list_append(&image_properties, p);
+               if (ret)
+                       goto out;
        }
 
-       if (check)
-               open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+       if (argc >= 4) {
+               /* NEW_DESC */
+               tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
+               tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
+               ret = string_list_append(&image_properties, p);
+               if (ret)
+                       goto out;
+       }
 
        ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
                                            imagex_progress_func, NULL);
@@ -3187,17 +3388,17 @@ imagex_info(int argc, tchar **argv, int cmd)
                                       "image in a multi-image WIM"));
                        goto out_wimlib_free;
                }
-               if (new_name) {
-                       imagex_error(T("Cannot specify the NEW_NAME "
-                                      "without specifying a specific "
-                                      "image in a multi-image WIM"));
+               if (image_properties.num_strings) {
+                       imagex_error(T("Can't change image properties without "
+                                      "specifying a specific image in a "
+                                      "multi-image WIM"));
                        goto out_wimlib_free;
                }
        }
 
        /* Operations that print information are separated from operations that
         * recreate the WIM file. */
-       if (!new_name && !boot) {
+       if (!image_properties.num_strings && !boot) {
 
                /* Read-only operations */
 
@@ -3213,13 +3414,13 @@ imagex_info(int argc, tchar **argv, int cmd)
                if (header)
                        wimlib_print_header(wim);
 
-               if (lookup_table) {
+               if (blobs) {
                        if (info.total_parts != 1) {
-                               tfprintf(stderr, T("Warning: Only showing the lookup table "
+                               tfprintf(stderr, T("Warning: Only showing the blobs "
                                                   "for part %d of a %d-part WIM.\n"),
                                         info.part_number, info.total_parts);
                        }
-                       print_lookup_table(wim);
+                       print_blobs(wim);
                }
 
                if (xml) {
@@ -3256,15 +3457,15 @@ imagex_info(int argc, tchar **argv, int cmd)
 
                ret = 0;
        } else {
-
                /* Modification operations */
+               bool any_property_changes;
 
                if (image == WIMLIB_ALL_IMAGES)
                        image = 1;
 
-               if (image == WIMLIB_NO_IMAGE && new_name) {
-                       imagex_error(T("Cannot specify new_name (\"%"TS"\") "
-                                      "when using image 0"), new_name);
+               if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
+                       imagex_error(T("Cannot change image properties "
+                                      "when using image 0"));
                        ret = -1;
                        goto out_wimlib_free;
                }
@@ -3284,49 +3485,20 @@ imagex_info(int argc, tchar **argv, int cmd)
                                        goto out_wimlib_free;
                        }
                }
-               if (new_name) {
-                       if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
-                       {
-                               imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
-                                       image, new_name);
-                               new_name = NULL;
-                       } else {
-                               imagex_printf(T("Changing the name of image %d to "
-                                         "\"%"TS"\".\n"), image, new_name);
-                               ret = wimlib_set_image_name(wim, image, new_name);
-                               if (ret)
-                                       goto out_wimlib_free;
-                       }
-               }
-               if (new_desc) {
-                       const tchar *old_desc;
-                       old_desc = wimlib_get_image_description(wim, image);
-                       if (old_desc && !tstrcmp(old_desc, new_desc)) {
-                               imagex_printf(T("The description of image %d is already "
-                                         "\"%"TS"\".\n"), image, new_desc);
-                               new_desc = NULL;
-                       } else {
-                               imagex_printf(T("Changing the description of image %d "
-                                         "to \"%"TS"\".\n"), image, new_desc);
-                               ret = wimlib_set_image_descripton(wim, image,
-                                                                 new_desc);
-                               if (ret)
-                                       goto out_wimlib_free;
-                       }
-               }
+
+               ret = apply_image_properties(&image_properties, wim, image,
+                                            &any_property_changes);
+               if (ret)
+                       goto out_wimlib_free;
 
                /* Only call wimlib_overwrite() if something actually needs to
                 * be changed.  */
-               if (boot || new_name || new_desc ||
-                   (check && !info.has_integrity_table) ||
-                   (nocheck && info.has_integrity_table))
+               if (boot || any_property_changes ||
+                   ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
+                    !info.has_integrity_table) ||
+                   ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
+                    info.has_integrity_table))
                {
-                       int write_flags = 0;
-
-                       if (check)
-                               write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
-                       if (nocheck)
-                               write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        ret = wimlib_overwrite(wim, write_flags, 1);
                } else {
                        imagex_printf(T("The file \"%"TS"\" was not modified "
@@ -3338,11 +3510,11 @@ imagex_info(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out:
+       string_list_destroy(&image_properties);
        return ret;
 
 out_usage:
        usage(CMD_INFO, stderr);
-out_err:
        ret = -1;
        goto out;
 }
@@ -3361,6 +3533,8 @@ imagex_join(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                default:
@@ -3409,7 +3583,7 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
        int image;
        int ret;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        if (cmd == CMD_MOUNTRW) {
                mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
@@ -3441,7 +3615,7 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
                        }
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -3498,14 +3672,18 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
 
        ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
        if (ret) {
-               imagex_error(T("Failed to mount image %d from \"%"TS"\" "
-                              "on \"%"TS"\""),
-                            image, wimfile, dir);
+               if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
+                       do_metadata_not_found_warning(wimfile, &info);
+               } else {
+                       imagex_error(T("Failed to mount image %d from \"%"TS"\" "
+                                      "on \"%"TS"\""),
+                                    image, wimfile, dir);
+               }
        }
 out_free_wim:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -3528,6 +3706,7 @@ imagex_optimize(int argc, tchar **argv, int cmd)
        int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
        int ret;
        WIMStruct *wim;
+       struct wimlib_wim_info info;
        const tchar *wimfile;
        off_t old_size;
        off_t new_size;
@@ -3537,6 +3716,8 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
@@ -3544,14 +3725,10 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_COMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       compression_type = get_compression_type(optarg);
+                       compression_type = get_compression_type(optarg, false);
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       break;
                case IMAGEX_RECOMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
                        break;
@@ -3566,13 +3743,19 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                                goto out_err;
                        break;
                case IMAGEX_SOLID_COMPRESS_OPTION:
-                       solid_ctype = get_compression_type(optarg);
+                       solid_ctype = get_compression_type(optarg, true);
                        if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
                case IMAGEX_SOLID_OPTION:
-                       write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+                       write_flags |= WIMLIB_WRITE_FLAG_SOLID;
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
+                       /* Reset the non-solid compression type to LZMS. */
+                       if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
+                               compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
+                       break;
+               case IMAGEX_NO_SOLID_SORT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
                        break;
                case IMAGEX_THREADS_OPTION:
                        num_threads = parse_num_threads(optarg);
@@ -3585,6 +3768,9 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                case IMAGEX_NOT_PIPABLE_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
                        break;
+               case IMAGEX_UNSAFE_COMPACT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3602,11 +3788,18 @@ imagex_optimize(int argc, tchar **argv, int cmd)
        if (ret)
                goto out;
 
-       if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
+       wimlib_get_wim_info(wim, &info);
+
+       if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID &&
+           compression_type != info.compression_type) {
                /* Change compression type.  */
                ret = wimlib_set_output_compression_type(wim, compression_type);
                if (ret)
                        goto out_wimlib_free;
+
+               /* Reset the chunk size. */
+               if (chunk_size == UINT32_MAX)
+                       chunk_size = 0;
        }
 
        if (chunk_size != UINT32_MAX) {
@@ -3673,7 +3866,7 @@ imagex_split(int argc, tchar **argv, int cmd)
        int c;
        int open_flags = 0;
        int write_flags = 0;
-       unsigned long part_size;
+       uint64_t part_size;
        tchar *tmp;
        int ret;
        WIMStruct *wim;
@@ -3682,6 +3875,8 @@ imagex_split(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                default:
@@ -3707,6 +3902,8 @@ imagex_split(int argc, tchar **argv, int cmd)
                goto out;
 
        ret = wimlib_split(wim, argv[1], part_size, write_flags);
+       if (ret == 0)
+               tprintf(T("\nFinished splitting \"%"TS"\"\n"), argv[0]);
        wimlib_free(wim);
 out:
        return ret;
@@ -3808,6 +4005,7 @@ imagex_update(int argc, tchar **argv, int cmd)
                                WIMLIB_ADD_FLAG_WINCONFIG;
        int default_delete_flags = 0;
        unsigned num_threads = 0;
+       STRING_LIST(refglobs);
        int c;
        tchar *cmd_file_contents;
        size_t cmd_file_nchars;
@@ -3827,6 +4025,8 @@ imagex_update(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_REBUILD_OPTION:
@@ -3849,6 +4049,13 @@ imagex_update(int argc, tchar **argv, int cmd)
                case IMAGEX_WIMBOOT_CONFIG_OPTION:
                        wimboot_config = optarg;
                        break;
+               case IMAGEX_REF_OPTION:
+                       ret = string_list_append(&refglobs, optarg);
+                       if (ret)
+                               goto out;
+                       /* assume delta WIM */
+                       write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
+                       break;
                /* Default delete options */
                case IMAGEX_FORCE_OPTION:
                        default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
@@ -3882,6 +4089,9 @@ imagex_update(int argc, tchar **argv, int cmd)
                case IMAGEX_NO_REPLACE_OPTION:
                        default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
                        break;
+               case IMAGEX_UNSAFE_COMPACT_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3896,7 +4106,7 @@ imagex_update(int argc, tchar **argv, int cmd)
        ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
                                            imagex_progress_func, NULL);
        if (ret)
-               goto out_free_command_str;
+               goto out;
 
        if (argc >= 2) {
                /* Image explicitly specified.  */
@@ -3920,6 +4130,10 @@ imagex_update(int argc, tchar **argv, int cmd)
                image = 1;
        }
 
+       ret = wim_reference_globs(wim, &refglobs, open_flags);
+       if (ret)
+               goto out_wimlib_free;
+
        /* Read update commands from standard input, or the command string if
         * specified.  */
        if (command_str) {
@@ -3935,8 +4149,8 @@ imagex_update(int argc, tchar **argv, int cmd)
                        tputs(T("Reading update commands from standard input..."));
                        recommend_man_page(CMD_UPDATE, stdout);
                }
-               cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
-               if (!cmd_file_contents) {
+               if (wimlib_load_text_file(NULL, &cmd_file_contents,
+                                         &cmd_file_nchars) != 0) {
                        ret = -1;
                        goto out_wimlib_free;
                }
@@ -3999,15 +4213,16 @@ out_free_cmd_file_contents:
        free(cmd_file_contents);
 out_wimlib_free:
        wimlib_free(wim);
-out_free_command_str:
+out:
        free(command_str);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
        usage(CMD_UPDATE, stderr);
 out_err:
        ret = -1;
-       goto out_free_command_str;
+       goto out;
 }
 
 /* Verify a WIM file.  */
@@ -4019,13 +4234,13 @@ imagex_verify(int argc, tchar **argv, int cmd)
        WIMStruct *wim;
        int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
        int verify_flags = 0;
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
        int c;
 
        for_opt(c, verify_options) {
                switch (c) {
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -4081,7 +4296,7 @@ imagex_verify(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -4118,7 +4333,7 @@ static const struct imagex_command imagex_commands[] = {
        [CMD_VERIFY]   = {T("verify"),   imagex_verify},
 };
 
-#ifdef __WIN32__
+#ifdef _WIN32
 
    /* Can be a directory or source list file.  But source list file is probably
     * a rare use case, so just say directory.  */
@@ -4136,14 +4351,15 @@ static const struct imagex_command imagex_commands[] = {
 
 #endif
 
-static const tchar *usage_strings[] = {
+static const tchar * const usage_strings[] = {
 [CMD_APPEND] =
 T(
 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
-"                    [--wimboot] [--unix-data] [--dereference]\n"
+"                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
+"                    [--dereference] [--snapshot] [--create]\n"
 ),
 [CMD_APPLY] =
 T(
@@ -4151,6 +4367,7 @@ T(
 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
+"                    [--compact=FORMAT] [--recover-data]\n"
 ),
 [CMD_CAPTURE] =
 T(
@@ -4160,6 +4377,7 @@ T(
 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
+"                    [--snapshot]\n"
 ),
 [CMD_DELETE] =
 T(
@@ -4167,7 +4385,7 @@ T(
 ),
 [CMD_DIR] =
 T(
-"    %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
+"    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
 ),
 [CMD_EXPORT] =
 T(
@@ -4175,21 +4393,22 @@ T(
 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
-"                    [--wimboot]\n"
+"                    [--wimboot] [--solid]\n"
 ),
 [CMD_EXTRACT] =
 T(
 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
-"                    [--no-attributes] [--include-invalid-names]\n"
-"                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
+"                    [--no-attributes] [--include-invalid-names] [--no-globs]\n"
+"                    [--nullglob] [--preserve-dir-structure] [--recover-data]\n"
 ),
 [CMD_INFO] =
 T(
 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
 "                    [--boot] [--check] [--nocheck] [--xml]\n"
-"                    [--extract-xml FILE] [--header] [--lookup-table]\n"
+"                    [--extract-xml FILE] [--header] [--blobs]\n"
+"                    [--image-property NAME=VALUE]\n"
 ),
 [CMD_JOIN] =
 T(
@@ -4212,8 +4431,8 @@ T(
 [CMD_OPTIMIZE] =
 T(
 "    %"TS" WIMFILE\n"
-"                    [--recompress] [--compress=TYPE]\n"
-"                    [--threads=NUM_THREADS] [--check] [--nocheck]\n"
+"                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
+"                    [--check] [--nocheck] [--solid]\n"
 "\n"
 ),
 [CMD_SPLIT] =
@@ -4244,21 +4463,18 @@ T(
 static const tchar *invocation_name;
 static int invocation_cmd = CMD_NONE;
 
-static const tchar *get_cmd_string(int cmd, bool nospace)
+static const tchar *get_cmd_string(int cmd, bool only_short_form)
 {
        static tchar buf[50];
-       if (cmd == CMD_NONE) {
+
+       if (cmd == CMD_NONE)
                return T("wimlib-imagex");
-       } else if (invocation_cmd != CMD_NONE) {
+
+       if (only_short_form || invocation_cmd != CMD_NONE) {
                tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
        } else {
-               const tchar *format;
-
-               if (nospace)
-                       format = T("%"TS"-%"TS"");
-               else
-                       format = T("%"TS" %"TS"");
-               tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
+               tsprintf(buf, T("%"TS" %"TS), invocation_name,
+                        imagex_commands[cmd].name);
        }
        return buf;
 }
@@ -4266,23 +4482,23 @@ static const tchar *get_cmd_string(int cmd, bool nospace)
 static void
 version(void)
 {
-       static const tchar *s =
+       static const tchar * const fmt =
        T(
-"wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
-"Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
-"License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
+"wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
+"Copyright 2012-2023 Eric Biggers\n"
+"License GPLv3+; GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.\n"
 "This is free software: you are free to change and redistribute it.\n"
 "There is NO WARRANTY, to the extent permitted by law.\n"
 "\n"
 "Report bugs to "PACKAGE_BUGREPORT".\n"
        );
-       tfputs(s, stdout);
+       tfprintf(stdout, fmt, wimlib_get_version_string());
 }
 
-
 static void
-help_or_version(int argc, tchar **argv, int cmd)
+do_common_options(int *argc_p, tchar **argv, int cmd)
 {
+       int argc = *argc_p;
        int i;
        const tchar *p;
 
@@ -4299,9 +4515,18 @@ help_or_version(int argc, tchar **argv, int cmd)
                        } else if (!tstrcmp(p, T("version"))) {
                                version();
                                exit(0);
-                       }
+                       } else if (!tstrcmp(p, T("quiet"))) {
+                               imagex_suppress_output();
+                               memmove(&argv[i], &argv[i + 1],
+                                       (argc - i) * sizeof(argv[i]));
+                               argc--;
+                               i--;
+                       } else if (!*p) /* reached "--", no more options */
+                               break;
                }
        }
+
+       *argc_p = argc;
 }
 
 static void
@@ -4314,12 +4539,11 @@ static void
 recommend_man_page(int cmd, FILE *fp)
 {
        const tchar *format_str;
-#ifdef __WIN32__
+#ifdef _WIN32
        format_str = T("Some uncommon options are not listed;\n"
                       "See %"TS".pdf in the doc directory for more details.\n");
 #else
-       format_str = T("Some uncommon options are not listed;\n"
-                      "Try `man %"TS"' for more details.\n");
+       format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
 #endif
        tfprintf(fp, format_str, get_cmd_string(cmd, true));
 }
@@ -4341,7 +4565,7 @@ usage_all(FILE *fp)
                print_usage_string(cmd, fp);
                tfprintf(fp, T("\n"));
        }
-       static const tchar *extra =
+       static const tchar * const extra =
        T(
 "    %"TS" --help\n"
 "    %"TS" --version\n"
@@ -4356,16 +4580,17 @@ usage_all(FILE *fp)
        recommend_man_page(CMD_NONE, fp);
 }
 
+#ifdef _WIN32
+int wmain(int argc, wchar_t **argv);
+#define main wmain
+#endif
+
 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
  * something else), while on Windows the command arguments will be UTF-16LE
  * encoded 'wchar_t' strings. */
 int
-#ifdef __WIN32__
-wmain(int argc, wchar_t **argv, wchar_t **envp)
-#else
-main(int argc, char **argv)
-#endif
+main(int argc, tchar **argv)
 {
        int ret;
        int init_flags = 0;
@@ -4374,31 +4599,6 @@ main(int argc, char **argv)
        imagex_info_file = stdout;
        invocation_name = tbasename(argv[0]);
 
-#ifndef __WIN32__
-       if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
-               init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
-       } else {
-               char *codeset;
-
-               setlocale(LC_ALL, "");
-               codeset = nl_langinfo(CODESET);
-               if (!strstr(codeset, "UTF-8") &&
-                   !strstr(codeset, "UTF8") &&
-                   !strstr(codeset, "utf-8") &&
-                   !strstr(codeset, "utf8"))
-               {
-                       fprintf(stderr,
-"WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
-"         Maybe try: `export LANG=en_US.UTF-8'?\n"
-"         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
-"         to any value to force wimlib to use UTF-8.\n",
-                       invocation_name);
-
-               }
-       }
-
-#endif /* !__WIN32__ */
-
        {
                tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
                if (igcase != NULL) {
@@ -4451,11 +4651,8 @@ main(int argc, char **argv)
                }
        }
 
-       /* Handle --help and --version.  --help can be either for the program as
-        * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
-        * CMD_NONE).  Note: help_or_version() will not return if a --help or
-        * --version argument was found.  */
-       help_or_version(argc, argv, cmd);
+       /* Handle common options.  May exit early (for --help or --version).  */
+       do_common_options(&argc, argv, cmd);
 
        /* Bail if a valid command was not specified.  */
        if (cmd == CMD_NONE) {