diff --git a/Help/command/install.rst b/Help/command/install.rst
index 2259176d746ac454b79d4631ea225c5f39d4cbba..2865e1df49c0e9d7a21dde13b420a5d7b598ab9f 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -9,6 +9,7 @@ Synopsis
 .. parsed-literal::
 
   install(`TARGETS`_ <target>... [...])
+  install(`IMPORTED_RUNTIME_ARTIFACTS`_ <target>... [...])
   install({`FILES`_ | `PROGRAMS`_} <file>... [...])
   install(`DIRECTORY`_ <dir>... [...])
   install(`SCRIPT`_ <file> [...])
@@ -382,6 +383,38 @@ set to ``TRUE`` has undefined behavior.
   to ensure that such out-of-directory targets are built before the
   subdirectory-specific install rules are run.
 
+Installing Imported Runtime Artifacts
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. _`install(IMPORTED_RUNTIME_ARTIFACTS)`:
+.. _IMPORTED_RUNTIME_ARTIFACTS:
+
+.. versionadded:: 3.21
+
+.. code-block:: cmake
+
+  install(IMPORTED_RUNTIME_ARTIFACTS targets...
+          [[LIBRARY|RUNTIME|FRAMEWORK|BUNDLE]
+           [DESTINATION <dir>]
+           [PERMISSIONS permissions...]
+           [CONFIGURATIONS [Debug|Release|...]]
+           [COMPONENT <component>]
+           [OPTIONAL] [EXCLUDE_FROM_ALL]
+          ] [...]
+          )
+
+The ``IMPORTED_RUNTIME_ARTIFACTS`` form specifies rules for installing the
+runtime artifacts of imported targets. Projects may do this if they want to
+bundle outside executables or modules inside their installation. The
+``LIBRARY``, ``RUNTIME``, ``FRAMEWORK``, and ``BUNDLE`` arguments have the
+same semantics that they do in the `TARGETS`_ mode. Only the runtime artifacts
+of imported targets are installed (except in the case of :prop_tgt:`FRAMEWORK`
+libraries, :prop_tgt:`MACOSX_BUNDLE` executables, and :prop_tgt:`BUNDLE`
+CFBundles.) For example, headers and import libraries associated with DLLs are
+not installed. In the case of :prop_tgt:`FRAMEWORK` libraries,
+:prop_tgt:`MACOSX_BUNDLE` executables, and :prop_tgt:`BUNDLE` CFBundles, the
+entire directory is installed.
+
 Installing Files
 ^^^^^^^^^^^^^^^^
 
diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst
index 0e530bc0bd25f6381459024b58f3c4c9533e6811..e7d78c3a8627d3a4902421956f6dfc3ca06f6294 100644
--- a/Help/manual/cmake-file-api.7.rst
+++ b/Help/manual/cmake-file-api.7.rst
@@ -747,6 +747,11 @@ with members:
       An :command:`install(CODE)` call.
       This type has no additional members.
 
+    ``importedRuntimeArtifacts``
+      An :command:`install(IMPORTED_RUNTIME_ARTIFACTS)` call.
+      The ``destination`` member is populated. The ``isOptional`` member may
+      exist. This type has no additional members.
+
   ``isExcludeFromAll``
     Optional member that is present with boolean value ``true`` when
     :command:`install` is called with the ``EXCLUDE_FROM_ALL`` option.
diff --git a/Help/release/dev/install-imported-runtime-artifacts.rst b/Help/release/dev/install-imported-runtime-artifacts.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e2821c15c12725313bc0e08cd4ff8837c6dab109
--- /dev/null
+++ b/Help/release/dev/install-imported-runtime-artifacts.rst
@@ -0,0 +1,5 @@
+install-imported-runtime-artifacts
+----------------------------------
+
+* The :command:`install` command gained a new ``IMPORTED_RUNTIME_ARTIFACTS``
+  mode, which can be used to install the runtime artifacts of imported targets.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index bd9e4c2d824f33707c058237662532e3ef5d72a2..844a2ee87fef4c2aff89a6cc5a99c2cbf3818090 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -350,6 +350,8 @@ set(SRCS
   cmInstalledFile.cxx
   cmInstallFilesGenerator.h
   cmInstallFilesGenerator.cxx
+  cmInstallImportedRuntimeArtifactsGenerator.h
+  cmInstallImportedRuntimeArtifactsGenerator.cxx
   cmInstallScriptGenerator.h
   cmInstallScriptGenerator.cxx
   cmInstallSubdirectoryGenerator.h
diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx
index 945b5475ca4a09e2d505250ae9d50552af935bd4..ff11f4a797b10c51525c3c24363ccfa228238889 100644
--- a/Source/cmFileAPICodemodel.cxx
+++ b/Source/cmFileAPICodemodel.cxx
@@ -29,6 +29,7 @@
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFilesGenerator.h"
 #include "cmInstallGenerator.h"
+#include "cmInstallImportedRuntimeArtifactsGenerator.h"
 #include "cmInstallScriptGenerator.h"
 #include "cmInstallSubdirectoryGenerator.h"
 #include "cmInstallTargetGenerator.h"
@@ -1009,6 +1010,15 @@ Json::Value DirectoryObject::DumpInstaller(cmInstallGenerator* gen)
       installer["scriptFile"] = RelativeIfUnder(
         this->TopSource, installScript->GetScript(this->Config));
     }
+  } else if (auto* installImportedRuntimeArtifacts =
+               dynamic_cast<cmInstallImportedRuntimeArtifactsGenerator*>(
+                 gen)) {
+    installer["type"] = "importedRuntimeArtifacts";
+    installer["destination"] =
+      installImportedRuntimeArtifacts->GetDestination(this->Config);
+    if (installImportedRuntimeArtifacts->GetOptional()) {
+      installer["isOptional"] = true;
+    }
   }
 
   // Add fields common to all install generators.
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index e973764814fb11f54d82e0b5041ee9e3fa2202ca..c9bb467b36ce2ae7dc25b3c6275b4c0d36b7a055 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmInstallCommand.h"
 
+#include <cassert>
 #include <cstddef>
 #include <set>
 #include <sstream>
@@ -22,6 +23,7 @@
 #include "cmInstallExportGenerator.h"
 #include "cmInstallFilesGenerator.h"
 #include "cmInstallGenerator.h"
+#include "cmInstallImportedRuntimeArtifactsGenerator.h"
 #include "cmInstallScriptGenerator.h"
 #include "cmInstallTargetGenerator.h"
 #include "cmListFileCache.h"
@@ -865,6 +867,227 @@ bool HandleTargetsMode(std::vector<std::string> const& args,
   return true;
 }
 
+bool HandleImportedRuntimeArtifactsMode(std::vector<std::string> const& args,
+                                        cmExecutionStatus& status)
+{
+  Helper helper(status);
+
+  // This is the IMPORTED_RUNTIME_ARTIFACTS mode.
+  std::vector<cmTarget*> targets;
+
+  struct ArgVectors
+  {
+    std::vector<std::string> Library;
+    std::vector<std::string> Runtime;
+    std::vector<std::string> Framework;
+    std::vector<std::string> Bundle;
+  };
+
+  static auto const argHelper = cmArgumentParser<ArgVectors>{}
+                                  .Bind("LIBRARY"_s, &ArgVectors::Library)
+                                  .Bind("RUNTIME"_s, &ArgVectors::Runtime)
+                                  .Bind("FRAMEWORK"_s, &ArgVectors::Framework)
+                                  .Bind("BUNDLE"_s, &ArgVectors::Bundle);
+
+  std::vector<std::string> genericArgVector;
+  ArgVectors const argVectors = argHelper.Parse(args, &genericArgVector);
+
+  // now parse the generic args (i.e. the ones not specialized on LIBRARY,
+  // RUNTIME etc. (see above)
+  std::vector<std::string> targetList;
+  std::vector<std::string> unknownArgs;
+  cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
+  genericArgs.Bind("IMPORTED_RUNTIME_ARTIFACTS"_s, targetList);
+  genericArgs.Parse(genericArgVector, &unknownArgs);
+  bool success = genericArgs.Finalize();
+
+  cmInstallCommandArguments libraryArgs(helper.DefaultComponentName);
+  cmInstallCommandArguments runtimeArgs(helper.DefaultComponentName);
+  cmInstallCommandArguments frameworkArgs(helper.DefaultComponentName);
+  cmInstallCommandArguments bundleArgs(helper.DefaultComponentName);
+
+  // now parse the args for specific parts of the target (e.g. LIBRARY,
+  // RUNTIME etc.
+  libraryArgs.Parse(argVectors.Library, &unknownArgs);
+  runtimeArgs.Parse(argVectors.Runtime, &unknownArgs);
+  frameworkArgs.Parse(argVectors.Framework, &unknownArgs);
+  bundleArgs.Parse(argVectors.Bundle, &unknownArgs);
+
+  if (!unknownArgs.empty()) {
+    // Unknown argument.
+    status.SetError(
+      cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given unknown argument \"",
+               unknownArgs[0], "\"."));
+    return false;
+  }
+
+  // apply generic args
+  libraryArgs.SetGenericArguments(&genericArgs);
+  runtimeArgs.SetGenericArguments(&genericArgs);
+  frameworkArgs.SetGenericArguments(&genericArgs);
+  bundleArgs.SetGenericArguments(&genericArgs);
+
+  success = success && libraryArgs.Finalize();
+  success = success && runtimeArgs.Finalize();
+  success = success && frameworkArgs.Finalize();
+  success = success && bundleArgs.Finalize();
+
+  if (!success) {
+    return false;
+  }
+
+  // Check if there is something to do.
+  if (targetList.empty()) {
+    return true;
+  }
+
+  for (std::string const& tgt : targetList) {
+    if (helper.Makefile->IsAlias(tgt)) {
+      status.SetError(cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given target \"",
+                               tgt, "\" which is an alias."));
+      return false;
+    }
+    // Lookup this target in the current directory.
+    cmTarget* target = helper.Makefile->FindTargetToUse(tgt);
+    if (!target || !target->IsImported()) {
+      // If no local target has been found, find it in the global scope.
+      cmTarget* const global_target =
+        helper.Makefile->GetGlobalGenerator()->FindTarget(tgt, true);
+      if (global_target && global_target->IsImported()) {
+        target = global_target;
+      }
+    }
+    if (target) {
+      // Found the target.  Check its type.
+      if (target->GetType() != cmStateEnums::EXECUTABLE &&
+          target->GetType() != cmStateEnums::SHARED_LIBRARY &&
+          target->GetType() != cmStateEnums::MODULE_LIBRARY) {
+        status.SetError(
+          cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given target \"", tgt,
+                   "\" which is not an executable, library, or module."));
+        return false;
+      }
+      // Store the target in the list to be installed.
+      targets.push_back(target);
+    } else {
+      // Did not find the target.
+      status.SetError(cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given target \"",
+                               tgt, "\" which does not exist."));
+      return false;
+    }
+  }
+
+  // Keep track of whether we will be performing an installation of
+  // any files of the given type.
+  bool installsLibrary = false;
+  bool installsRuntime = false;
+  bool installsFramework = false;
+  bool installsBundle = false;
+
+  auto const createInstallGenerator =
+    [helper](cmTarget& target, const cmInstallCommandArguments& typeArgs,
+             const std::string& destination)
+    -> std::unique_ptr<cmInstallImportedRuntimeArtifactsGenerator> {
+    return cm::make_unique<cmInstallImportedRuntimeArtifactsGenerator>(
+      target.GetName(), destination, typeArgs.GetPermissions(),
+      typeArgs.GetConfigurations(), typeArgs.GetComponent(),
+      cmInstallGenerator::SelectMessageLevel(helper.Makefile),
+      typeArgs.GetExcludeFromAll(), typeArgs.GetOptional(),
+      helper.Makefile->GetBacktrace());
+  };
+
+  // Generate install script code to install the given targets.
+  for (cmTarget* ti : targets) {
+    // Handle each target type.
+    cmTarget& target = *ti;
+    std::unique_ptr<cmInstallImportedRuntimeArtifactsGenerator>
+      libraryGenerator;
+    std::unique_ptr<cmInstallImportedRuntimeArtifactsGenerator>
+      runtimeGenerator;
+    std::unique_ptr<cmInstallImportedRuntimeArtifactsGenerator>
+      frameworkGenerator;
+    std::unique_ptr<cmInstallImportedRuntimeArtifactsGenerator>
+      bundleGenerator;
+
+    switch (target.GetType()) {
+      case cmStateEnums::SHARED_LIBRARY:
+        if (target.IsDLLPlatform()) {
+          runtimeGenerator = createInstallGenerator(
+            target, runtimeArgs, helper.GetRuntimeDestination(&runtimeArgs));
+        } else if (target.IsFrameworkOnApple()) {
+          if (frameworkArgs.GetDestination().empty()) {
+            status.SetError(cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given no "
+                                     "FRAMEWORK DESTINATION for shared "
+                                     "library FRAMEWORK target \"",
+                                     target.GetName(), "\"."));
+            return false;
+          }
+          frameworkGenerator = createInstallGenerator(
+            target, frameworkArgs, frameworkArgs.GetDestination());
+        } else {
+          libraryGenerator = createInstallGenerator(
+            target, libraryArgs, helper.GetLibraryDestination(&libraryArgs));
+        }
+        break;
+      case cmStateEnums::MODULE_LIBRARY:
+        libraryGenerator = createInstallGenerator(
+          target, libraryArgs, helper.GetLibraryDestination(&libraryArgs));
+        break;
+      case cmStateEnums::EXECUTABLE:
+        if (target.IsAppBundleOnApple()) {
+          if (bundleArgs.GetDestination().empty()) {
+            status.SetError(
+              cmStrCat("IMPORTED_RUNTIME_ARTIFACTS given no BUNDLE "
+                       "DESTINATION for MACOSX_BUNDLE executable target \"",
+                       target.GetName(), "\"."));
+            return false;
+          }
+          bundleGenerator = createInstallGenerator(
+            target, bundleArgs, bundleArgs.GetDestination());
+        } else {
+          runtimeGenerator = createInstallGenerator(
+            target, runtimeArgs, helper.GetRuntimeDestination(&runtimeArgs));
+        }
+        break;
+      default:
+        assert(false && "This should never happen");
+        break;
+    }
+
+    // Keep track of whether we're installing anything in each category
+    installsLibrary = installsLibrary || libraryGenerator;
+    installsRuntime = installsRuntime || runtimeGenerator;
+    installsFramework = installsFramework || frameworkGenerator;
+    installsBundle = installsBundle || bundleGenerator;
+
+    helper.Makefile->AddInstallGenerator(std::move(libraryGenerator));
+    helper.Makefile->AddInstallGenerator(std::move(runtimeGenerator));
+    helper.Makefile->AddInstallGenerator(std::move(frameworkGenerator));
+    helper.Makefile->AddInstallGenerator(std::move(bundleGenerator));
+  }
+
+  // Tell the global generator about any installation component names
+  // specified
+  if (installsLibrary) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      libraryArgs.GetComponent());
+  }
+  if (installsRuntime) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      runtimeArgs.GetComponent());
+  }
+  if (installsFramework) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      frameworkArgs.GetComponent());
+  }
+  if (installsBundle) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      bundleArgs.GetComponent());
+  }
+
+  return true;
+}
+
 bool HandleFilesMode(std::vector<std::string> const& args,
                      cmExecutionStatus& status)
 {
@@ -1705,6 +1928,7 @@ bool cmInstallCommand(std::vector<std::string> const& args,
     { "SCRIPT"_s, HandleScriptMode },
     { "CODE"_s, HandleScriptMode },
     { "TARGETS"_s, HandleTargetsMode },
+    { "IMPORTED_RUNTIME_ARTIFACTS"_s, HandleImportedRuntimeArtifactsMode },
     { "FILES"_s, HandleFilesMode },
     { "PROGRAMS"_s, HandleFilesMode },
     { "DIRECTORY"_s, HandleDirectoryMode },
diff --git a/Source/cmInstallExportGenerator.cxx b/Source/cmInstallExportGenerator.cxx
index ccefd92c2208fc69f0e645af1cd1eeb0d84bc5e4..d932fd9e6f421f327fcaa037da52bf33e39802e3 100644
--- a/Source/cmInstallExportGenerator.cxx
+++ b/Source/cmInstallExportGenerator.cxx
@@ -184,9 +184,8 @@ void cmInstallExportGenerator::GenerateScriptActions(std::ostream& os,
                                                      Indent indent)
 {
   // Remove old per-configuration export files if the main changes.
-  std::string installedDir =
-    cmStrCat("$ENV{DESTDIR}",
-             this->ConvertToAbsoluteDestination(this->Destination), '/');
+  std::string installedDir = cmStrCat(
+    "$ENV{DESTDIR}", ConvertToAbsoluteDestination(this->Destination), '/');
   std::string installedFile = cmStrCat(installedDir, this->FileName);
   os << indent << "if(EXISTS \"" << installedFile << "\")\n";
   Indent indentN = indent.Next();
diff --git a/Source/cmInstallGenerator.cxx b/Source/cmInstallGenerator.cxx
index cf5f45e3db4a0882cb5663ed16d519799133cba3..9f0d119e4cf8ba162122a8266f88148e2f923f52 100644
--- a/Source/cmInstallGenerator.cxx
+++ b/Source/cmInstallGenerator.cxx
@@ -2,10 +2,11 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmInstallGenerator.h"
 
-#include <ostream>
+#include <sstream>
 #include <utility>
 
 #include "cmMakefile.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 cmInstallGenerator::cmInstallGenerator(
@@ -98,7 +99,7 @@ void cmInstallGenerator::AddInstallRule(
        << "${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n";
     os << indent << "endif()\n";
   }
-  std::string absDest = this->ConvertToAbsoluteDestination(dest);
+  std::string absDest = ConvertToAbsoluteDestination(dest);
   os << "file(INSTALL DESTINATION \"" << absDest << "\" TYPE " << stype;
   if (optional) {
     os << " OPTIONAL";
@@ -183,7 +184,7 @@ bool cmInstallGenerator::InstallsForConfig(const std::string& config)
 }
 
 std::string cmInstallGenerator::ConvertToAbsoluteDestination(
-  std::string const& dest) const
+  std::string const& dest)
 {
   std::string result;
   if (!dest.empty() && !cmSystemTools::FileIsFullPath(dest)) {
@@ -211,3 +212,59 @@ cmInstallGenerator::MessageLevel cmInstallGenerator::SelectMessageLevel(
   }
   return MessageDefault;
 }
+
+std::string cmInstallGenerator::GetDestDirPath(std::string const& file)
+{
+  // Construct the path of the file on disk after installation on
+  // which tweaks may be performed.
+  std::string toDestDirPath = "$ENV{DESTDIR}";
+  if (file[0] != '/' && file[0] != '$') {
+    toDestDirPath += "/";
+  }
+  toDestDirPath += file;
+  return toDestDirPath;
+}
+
+void cmInstallGenerator::AddTweak(std::ostream& os, Indent indent,
+                                  const std::string& config,
+                                  std::string const& file,
+                                  const TweakMethod& tweak)
+{
+  std::ostringstream tw;
+  tweak(tw, indent.Next(), config, file);
+  std::string tws = tw.str();
+  if (!tws.empty()) {
+    os << indent << "if(EXISTS \"" << file << "\" AND\n"
+       << indent << "   NOT IS_SYMLINK \"" << file << "\")\n";
+    os << tws;
+    os << indent << "endif()\n";
+  }
+}
+
+void cmInstallGenerator::AddTweak(std::ostream& os, Indent indent,
+                                  const std::string& config,
+                                  std::string const& dir,
+                                  std::vector<std::string> const& files,
+                                  const TweakMethod& tweak)
+{
+  if (files.size() == 1) {
+    // Tweak a single file.
+    AddTweak(os, indent, config, GetDestDirPath(cmStrCat(dir, files[0])),
+             tweak);
+  } else {
+    // Generate a foreach loop to tweak multiple files.
+    std::ostringstream tw;
+    AddTweak(tw, indent.Next(), config, "${file}", tweak);
+    std::string tws = tw.str();
+    if (!tws.empty()) {
+      Indent indent2 = indent.Next().Next();
+      os << indent << "foreach(file\n";
+      for (std::string const& f : files) {
+        os << indent2 << "\"" << GetDestDirPath(cmStrCat(dir, f)) << "\"\n";
+      }
+      os << indent2 << ")\n";
+      os << tws;
+      os << indent << "endforeach()\n";
+    }
+  }
+}
diff --git a/Source/cmInstallGenerator.h b/Source/cmInstallGenerator.h
index 0117617cdbf936451096b0187079b1a3f82f231a..97acb88e8382dde62e08bd4eec7d6d15f8c80049 100644
--- a/Source/cmInstallGenerator.h
+++ b/Source/cmInstallGenerator.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <functional>
 #include <iosfwd>
 #include <string>
 #include <vector>
@@ -53,7 +54,7 @@ public:
 
   /** Get the install destination as it should appear in the
       installation script.  */
-  std::string ConvertToAbsoluteDestination(std::string const& dest) const;
+  static std::string ConvertToAbsoluteDestination(std::string const& dest);
 
   /** Test if this generator installs something for a given configuration.  */
   bool InstallsForConfig(const std::string& config);
@@ -70,12 +71,25 @@ public:
 
   cmListFileBacktrace const& GetBacktrace() const { return this->Backtrace; }
 
+  static std::string GetDestDirPath(std::string const& file);
+
 protected:
   void GenerateScript(std::ostream& os) override;
 
   std::string CreateComponentTest(const std::string& component,
                                   bool exclude_from_all);
 
+  using TweakMethod =
+    std::function<void(std::ostream& os, Indent indent,
+                       const std::string& config, const std::string& file)>;
+  static void AddTweak(std::ostream& os, Indent indent,
+                       const std::string& config, std::string const& file,
+                       const TweakMethod& tweak);
+  static void AddTweak(std::ostream& os, Indent indent,
+                       const std::string& config, std::string const& dir,
+                       std::vector<std::string> const& files,
+                       const TweakMethod& tweak);
+
   // Information shared by most generator types.
   std::string const Destination;
   std::string const Component;
diff --git a/Source/cmInstallImportedRuntimeArtifactsGenerator.cxx b/Source/cmInstallImportedRuntimeArtifactsGenerator.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..01980ac1a23b788022c8331c1fde9030a27120d4
--- /dev/null
+++ b/Source/cmInstallImportedRuntimeArtifactsGenerator.cxx
@@ -0,0 +1,146 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmInstallImportedRuntimeArtifactsGenerator.h"
+
+#include <cassert>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallType.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
+
+namespace {
+const cmsys::RegularExpression FrameworkRegularExpression(
+  "^(.*/)?([^/]*)\\.framework/(.*)$");
+
+const cmsys::RegularExpression BundleRegularExpression(
+  "^(.*/)?([^/]*)\\.app/(.*)$");
+
+const cmsys::RegularExpression CFBundleRegularExpression(
+  "^(.*/)?([^/]*)\\.bundle/(.*)$");
+}
+
+cmInstallImportedRuntimeArtifactsGenerator::
+  cmInstallImportedRuntimeArtifactsGenerator(
+    std::string targetName, std::string const& dest,
+    std::string file_permissions,
+    std::vector<std::string> const& configurations,
+    std::string const& component, MessageLevel message, bool exclude_from_all,
+    bool optional, cmListFileBacktrace backtrace)
+  : cmInstallGenerator(dest, configurations, component, message,
+                       exclude_from_all, false, std::move(backtrace))
+  , TargetName(std::move(targetName))
+  , FilePermissions(std::move(file_permissions))
+  , Optional(optional)
+{
+  this->ActionsPerConfig = true;
+}
+
+bool cmInstallImportedRuntimeArtifactsGenerator::Compute(cmLocalGenerator* lg)
+{
+  // Lookup this target in the current directory.
+  this->Target = lg->FindGeneratorTargetToUse(this->TargetName);
+  if (!this->Target || !this->Target->IsImported()) {
+    // If no local target has been found, find it in the global scope.
+    this->Target =
+      lg->GetGlobalGenerator()->FindGeneratorTarget(this->TargetName);
+  }
+
+  return true;
+}
+
+std::string cmInstallImportedRuntimeArtifactsGenerator::GetDestination(
+  std::string const& config) const
+{
+  return cmGeneratorExpression::Evaluate(
+    this->Destination, this->Target->GetLocalGenerator(), config);
+}
+
+void cmInstallImportedRuntimeArtifactsGenerator::GenerateScriptForConfig(
+  std::ostream& os, const std::string& config, Indent indent)
+{
+  auto location = this->Target->GetFullPath(config);
+
+  switch (this->Target->GetType()) {
+    case cmStateEnums::EXECUTABLE:
+      if (this->Target->IsBundleOnApple()) {
+        cmsys::RegularExpressionMatch match;
+        if (BundleRegularExpression.find(location.c_str(), match)) {
+          auto bundleDir = match.match(1);
+          auto bundleName = match.match(2);
+          auto bundlePath = cmStrCat(bundleDir, bundleName, ".app");
+          this->AddInstallRule(os, this->GetDestination(config),
+                               cmInstallType_DIRECTORY, { bundlePath },
+                               this->Optional, nullptr,
+                               this->FilePermissions.c_str(), nullptr,
+                               " USE_SOURCE_PERMISSIONS", indent);
+        }
+      } else {
+        this->AddInstallRule(os, this->GetDestination(config),
+                             cmInstallType_EXECUTABLE, { location },
+                             this->Optional, this->FilePermissions.c_str(),
+                             nullptr, nullptr, nullptr, indent);
+      }
+      break;
+    case cmStateEnums::SHARED_LIBRARY:
+      if (this->Target->IsFrameworkOnApple()) {
+        cmsys::RegularExpressionMatch match;
+        if (FrameworkRegularExpression.find(location.c_str(), match)) {
+          auto frameworkDir = match.match(1);
+          auto frameworkName = match.match(2);
+          auto frameworkPath =
+            cmStrCat(frameworkDir, frameworkName, ".framework");
+          this->AddInstallRule(os, this->GetDestination(config),
+                               cmInstallType_DIRECTORY, { frameworkPath },
+                               this->Optional, nullptr,
+                               this->FilePermissions.c_str(), nullptr,
+                               " USE_SOURCE_PERMISSIONS", indent);
+        }
+      } else {
+        std::vector<std::string> files{ location };
+        auto soName = this->Target->GetSOName(config);
+        auto soNameFile =
+          cmStrCat(this->Target->GetDirectory(config), '/', soName);
+        if (!soName.empty() && soNameFile != location) {
+          files.push_back(soNameFile);
+        }
+        this->AddInstallRule(os, this->GetDestination(config),
+                             cmInstallType_SHARED_LIBRARY, files,
+                             this->Optional, this->FilePermissions.c_str(),
+                             nullptr, nullptr, nullptr, indent);
+      }
+      break;
+    case cmStateEnums::MODULE_LIBRARY:
+      if (this->Target->IsCFBundleOnApple()) {
+        cmsys::RegularExpressionMatch match;
+        if (CFBundleRegularExpression.find(location.c_str(), match)) {
+          auto bundleDir = match.match(1);
+          auto bundleName = match.match(2);
+          auto bundlePath = cmStrCat(bundleDir, bundleName, ".bundle");
+          this->AddInstallRule(os, this->GetDestination(config),
+                               cmInstallType_DIRECTORY, { bundlePath },
+                               this->Optional, nullptr,
+                               this->FilePermissions.c_str(), nullptr,
+                               " USE_SOURCE_PERMISSIONS", indent);
+        }
+      } else {
+        this->AddInstallRule(os, this->GetDestination(config),
+                             cmInstallType_MODULE_LIBRARY, { location },
+                             this->Optional, this->FilePermissions.c_str(),
+                             nullptr, nullptr, nullptr, indent);
+      }
+      break;
+    default:
+      assert(false && "This should never happen");
+      break;
+  }
+}
diff --git a/Source/cmInstallImportedRuntimeArtifactsGenerator.h b/Source/cmInstallImportedRuntimeArtifactsGenerator.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e045ee725d58a9c5b2642c980c774fa5babf8cb
--- /dev/null
+++ b/Source/cmInstallImportedRuntimeArtifactsGenerator.h
@@ -0,0 +1,44 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmInstallGenerator.h"
+#include "cmListFileCache.h"
+#include "cmScriptGenerator.h"
+
+class cmGeneratorTarget;
+class cmLocalGenerator;
+
+class cmInstallImportedRuntimeArtifactsGenerator : public cmInstallGenerator
+{
+public:
+  cmInstallImportedRuntimeArtifactsGenerator(
+    std::string targetName, std::string const& dest,
+    std::string file_permissions,
+    std::vector<std::string> const& configurations,
+    std::string const& component, MessageLevel message, bool exclude_from_all,
+    bool optional, cmListFileBacktrace backtrace = cmListFileBacktrace());
+  ~cmInstallImportedRuntimeArtifactsGenerator() override = default;
+
+  bool Compute(cmLocalGenerator* lg) override;
+
+  cmGeneratorTarget* GetTarget() const { return this->Target; }
+
+  bool GetOptional() const { return this->Optional; }
+
+  std::string GetDestination(std::string const& config) const;
+
+protected:
+  void GenerateScriptForConfig(std::ostream& os, const std::string& config,
+                               Indent indent) override;
+
+private:
+  std::string const TargetName;
+  cmGeneratorTarget* Target;
+  std::string const FilePermissions;
+  bool const Optional;
+};
diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx
index 3e79ad8b09619dd052fa7768d0d1cf0ad7fced37..35165cf72109d0da57641ddf02b0dad5fd4ea142 100644
--- a/Source/cmInstallTargetGenerator.cxx
+++ b/Source/cmInstallTargetGenerator.cxx
@@ -77,12 +77,15 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(
   }
 
   // Tweak files located in the destination directory.
-  std::string toDir = cmStrCat(this->ConvertToAbsoluteDestination(dest), '/');
+  std::string toDir = cmStrCat(ConvertToAbsoluteDestination(dest), '/');
 
   // Add pre-installation tweaks.
   if (!files.NoTweak) {
-    this->AddTweak(os, indent, config, toDir, files.To,
-                   &cmInstallTargetGenerator::PreReplacementTweaks);
+    AddTweak(os, indent, config, toDir, files.To,
+             [this](std::ostream& o, Indent i, const std::string& c,
+                    const std::string& f) {
+               this->PreReplacementTweaks(o, i, c, f);
+             });
   }
 
   // Write code to install the target file.
@@ -102,8 +105,11 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(
 
   // Add post-installation tweaks.
   if (!files.NoTweak) {
-    this->AddTweak(os, indent, config, toDir, files.To,
-                   &cmInstallTargetGenerator::PostReplacementTweaks);
+    AddTweak(os, indent, config, toDir, files.To,
+             [this](std::ostream& o, Indent i, const std::string& c,
+                    const std::string& f) {
+               this->PostReplacementTweaks(o, i, c, f);
+             });
   }
 }
 
@@ -461,63 +467,6 @@ bool cmInstallTargetGenerator::Compute(cmLocalGenerator* lg)
   return true;
 }
 
-void cmInstallTargetGenerator::AddTweak(std::ostream& os, Indent indent,
-                                        const std::string& config,
-                                        std::string const& file,
-                                        TweakMethod tweak)
-{
-  std::ostringstream tw;
-  (this->*tweak)(tw, indent.Next(), config, file);
-  std::string tws = tw.str();
-  if (!tws.empty()) {
-    os << indent << "if(EXISTS \"" << file << "\" AND\n"
-       << indent << "   NOT IS_SYMLINK \"" << file << "\")\n";
-    os << tws;
-    os << indent << "endif()\n";
-  }
-}
-
-void cmInstallTargetGenerator::AddTweak(std::ostream& os, Indent indent,
-                                        const std::string& config,
-                                        std::string const& dir,
-                                        std::vector<std::string> const& files,
-                                        TweakMethod tweak)
-{
-  if (files.size() == 1) {
-    // Tweak a single file.
-    this->AddTweak(os, indent, config,
-                   this->GetDestDirPath(cmStrCat(dir, files[0])), tweak);
-  } else {
-    // Generate a foreach loop to tweak multiple files.
-    std::ostringstream tw;
-    this->AddTweak(tw, indent.Next(), config, "${file}", tweak);
-    std::string tws = tw.str();
-    if (!tws.empty()) {
-      Indent indent2 = indent.Next().Next();
-      os << indent << "foreach(file\n";
-      for (std::string const& f : files) {
-        os << indent2 << "\"" << this->GetDestDirPath(cmStrCat(dir, f))
-           << "\"\n";
-      }
-      os << indent2 << ")\n";
-      os << tws;
-      os << indent << "endforeach()\n";
-    }
-  }
-}
-
-std::string cmInstallTargetGenerator::GetDestDirPath(std::string const& file)
-{
-  // Construct the path of the file on disk after installation on
-  // which tweaks may be performed.
-  std::string toDestDirPath = "$ENV{DESTDIR}";
-  if (file[0] != '/' && file[0] != '$') {
-    toDestDirPath += "/";
-  }
-  toDestDirPath += file;
-  return toDestDirPath;
-}
-
 void cmInstallTargetGenerator::PreReplacementTweaks(std::ostream& os,
                                                     Indent indent,
                                                     const std::string& config,
diff --git a/Source/cmInstallTargetGenerator.h b/Source/cmInstallTargetGenerator.h
index 84fce4231b3bd3ed0c3d2d9a480b9be99816f53a..6173f2c3e32165d15a7ece122103f052abc3720c 100644
--- a/Source/cmInstallTargetGenerator.h
+++ b/Source/cmInstallTargetGenerator.h
@@ -93,15 +93,6 @@ public:
 protected:
   void GenerateScriptForConfig(std::ostream& os, const std::string& config,
                                Indent indent) override;
-  using TweakMethod = void (cmInstallTargetGenerator::*)(std::ostream&, Indent,
-                                                         const std::string&,
-                                                         const std::string&);
-  void AddTweak(std::ostream& os, Indent indent, const std::string& config,
-                std::string const& file, TweakMethod tweak);
-  void AddTweak(std::ostream& os, Indent indent, const std::string& config,
-                std::string const& dir, std::vector<std::string> const& files,
-                TweakMethod tweak);
-  std::string GetDestDirPath(std::string const& file);
   void PreReplacementTweaks(std::ostream& os, Indent indent,
                             const std::string& config,
                             std::string const& file);
diff --git a/Tests/ExportImport/CMakeLists.txt b/Tests/ExportImport/CMakeLists.txt
index d88eb11bbf019e8d0b145803e78347e7ee8c15d1..4999612ff2d0b4c703d4c2d0b779554ceff0961b 100644
--- a/Tests/ExportImport/CMakeLists.txt
+++ b/Tests/ExportImport/CMakeLists.txt
@@ -16,14 +16,12 @@ set_property(
   )
 
 get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
-if(_isMultiConfig)
-  set(NESTED_CONFIG_TYPE -C "${CMAKE_CFG_INTDIR}")
+if(_isMultiConfig OR CMAKE_BUILD_TYPE)
+  set(NESTED_CONFIG_TYPE -C "$<CONFIG>")
+  set(NESTED_CONFIG_INSTALL_TYPE --config "$<CONFIG>")
 else()
-  if(CMAKE_BUILD_TYPE)
-    set(NESTED_CONFIG_TYPE -C "${CMAKE_BUILD_TYPE}")
-  else()
-    set(NESTED_CONFIG_TYPE)
-  endif()
+  set(NESTED_CONFIG_TYPE)
+  set(NESTED_CONFIG_INSTALL_TYPE)
 endif()
 
 if(MINGW OR MSYS)
@@ -79,5 +77,21 @@ set_property(
   PROPERTY SYMBOLIC 1
   )
 
+# Install the imported targets.
+add_custom_command(
+  OUTPUT ${ExportImport_BINARY_DIR}/ImportInstall
+  COMMAND ${CMAKE_COMMAND} -E rm -rf ${ExportImport_BINARY_DIR}/Import/install
+  COMMAND ${CMAKE_COMMAND}
+    --install ${ExportImport_BINARY_DIR}/Import
+    --prefix ${ExportImport_BINARY_DIR}/Import/install
+    ${NESTED_CONFIG_INSTALL_TYPE}
+  )
+add_custom_target(ImportInstallTarget ALL DEPENDS ${ExportImport_BINARY_DIR}/ImportInstall)
+add_dependencies(ImportInstallTarget ImportTarget)
+set_property(
+  SOURCE ${ExportImport_BINARY_DIR}/ImportInstall
+  PROPERTY SYMBOLIC 1
+  )
+
 add_executable(ExportImport main.c)
 add_dependencies(ExportImport ImportTarget)
diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt
index 6d9b4ab4f57f90cec11a1e8e48e959862b1d82b1..fa0016b2447495f3580552b58ccf2a9ab6fbee49 100644
--- a/Tests/ExportImport/Export/CMakeLists.txt
+++ b/Tests/ExportImport/Export/CMakeLists.txt
@@ -395,6 +395,10 @@ target_include_directories(systemlib
     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
 )
 
+add_library(testMod1 MODULE empty.cpp)
+add_library(testMod2 MODULE empty.cpp)
+set_property(TARGET testMod2 PROPERTY BUNDLE 1)
+
 install(TARGETS testLibRequired
         EXPORT RequiredExp DESTINATION lib
         INCLUDES DESTINATION
@@ -523,6 +527,7 @@ install(
   testLibDeprecation
   testLibCycleA testLibCycleB
   testLibNoSONAME
+  testMod1 testMod2
   cmp0022NEW cmp0022OLD
   TopDirLib SubDirLinkA
   systemlib
@@ -595,6 +600,7 @@ export(TARGETS testExe2 testLib4 testLib5 testLib6 testLib7 testExe3 testExe4 te
   testLib4lib testLib4libdbg testLib4libopt
   testLibCycleA testLibCycleB
   testLibNoSONAME
+  testMod1 testMod2
   testLibPerConfigDest
   NAMESPACE bld_
   APPEND FILE ExportBuildTree.cmake
diff --git a/Tests/ExportImport/Import/CMakeLists.txt b/Tests/ExportImport/Import/CMakeLists.txt
index a8a98fcb62631c24eec9a893c4bea17735db1853..e64c6b40789a6f1c44399bfc2243fe187632fdce 100644
--- a/Tests/ExportImport/Import/CMakeLists.txt
+++ b/Tests/ExportImport/Import/CMakeLists.txt
@@ -26,3 +26,6 @@ add_subdirectory(Interface)
 
 # Test package version range
 add_subdirectory(version_range)
+
+# Test install(IMPORTED_RUNTIME_ARTIFACTS)
+add_subdirectory(install-IMPORTED_RUNTIME_ARTIFACTS)
diff --git a/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/CMakeLists.txt b/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..76efe2f509ccb52e140204d4bbe35fe6804e0320
--- /dev/null
+++ b/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Import targets from the exported build tree.
+include(${Import_BINARY_DIR}/../Export/ExportBuildTree.cmake)
+
+set(_tgts
+  bld_testExe1 # Ordinary executable
+  bld_testExe2lib # Ordinary shared library
+  bld_testLib3 # Shared library with version and soversion
+  bld_testLib4 # Framework library
+  bld_testExe3 # Bundle executable
+  bld_testMod1 # Module library
+  bld_testMod2 # CFBundle
+  )
+
+install(IMPORTED_RUNTIME_ARTIFACTS ${_tgts}
+  RUNTIME DESTINATION aaa/executables
+  LIBRARY DESTINATION aaa/libraries
+  BUNDLE DESTINATION aaa/bundles
+  FRAMEWORK DESTINATION aaa/frameworks
+  )
+
+install(IMPORTED_RUNTIME_ARTIFACTS ${_tgts}
+  DESTINATION bbb
+  )
+
+install(IMPORTED_RUNTIME_ARTIFACTS ${_tgts}
+  BUNDLE DESTINATION zzz/bundles
+  FRAMEWORK DESTINATION zzz/frameworks
+  )
+
+install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/check_installed.cmake")
diff --git a/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/check_installed.cmake b/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/check_installed.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..29b298fbf393c99328ca4d83684956f0ef8625ea
--- /dev/null
+++ b/Tests/ExportImport/Import/install-IMPORTED_RUNTIME_ARTIFACTS/check_installed.cmake
@@ -0,0 +1,57 @@
+cmake_minimum_required(VERSION 3.20)
+
+function(check_installed expect)
+  file(GLOB_RECURSE actual
+    LIST_DIRECTORIES TRUE
+    RELATIVE ${CMAKE_INSTALL_PREFIX}
+    ${CMAKE_INSTALL_PREFIX}/*
+    )
+  if(actual)
+    list(SORT actual)
+  endif()
+  if(NOT "${actual}" MATCHES "${expect}")
+    message(FATAL_ERROR "Installed files:
+  ${actual}
+do not match what we expected:
+  ${expect}
+in directory:
+  ${CMAKE_INSTALL_PREFIX}")
+  endif()
+endfunction()
+
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
+  set(_dirs [[aaa;aaa/executables;aaa/executables/testExe1-4;aaa/executables/testExe3;aaa/libraries;aaa/libraries/libtestExe2lib\.so;aaa/libraries/libtestLib3lib(-d|-r)?\.so\.1\.2;aaa/libraries/libtestLib3lib(-d|-r)?\.so\.3;aaa/libraries/libtestLib4\.so;aaa/libraries/libtestMod1\.so;aaa/libraries/libtestMod2\.so]])
+  set(_alldest [[bbb;bbb/libtestExe2lib\.so;bbb/libtestLib3lib(-d|-r)?\.so\.1\.2;bbb/libtestLib3lib(-d|-r)?\.so\.3;bbb/libtestLib4\.so;bbb/libtestMod1\.so;bbb/libtestMod2\.so;bbb/testExe1-4;bbb/testExe3]])
+  set(_default [[bin;bin/testExe1-4;bin/testExe3;lib;lib/libtestExe2lib\.so;lib/libtestLib3lib(-d|-r)?\.so\.1\.2;lib/libtestLib3lib(-d|-r)?\.so\.3;lib/libtestLib4\.so;lib/libtestMod1\.so;lib/libtestMod2\.so]])
+  check_installed("^${_dirs};${_alldest};${_default}$")
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+  set(_dirs_msvc [[aaa;aaa/executables;aaa/executables/testExe1\.exe;aaa/executables/testExe2lib\.dll;aaa/executables/testExe3\.exe;aaa/executables/testLib3dll(-d|-r)?\.dll;aaa/executables/testLib4\.dll;aaa/libraries;aaa/libraries/testMod1\.dll;aaa/libraries/testMod2\.dll]])
+  set(_dirs_mingw [[aaa;aaa/executables;aaa/executables/libtestExe2lib\.dll;aaa/executables/libtestLib3dll(-d|-r)?\.dll;aaa/executables/libtestLib4\.dll;aaa/executables/testExe1\.exe;aaa/executables/testExe3\.exe;aaa/libraries;aaa/libraries/libtestMod1\.dll;aaa/libraries/libtestMod2\.dll]])
+
+  set(_alldest_msvc [[bbb;bbb/testExe1\.exe;bbb/testExe2lib\.dll;bbb/testExe3\.exe;bbb/testLib3dll(-d|-r)?\.dll;bbb/testLib4\.dll;bbb/testMod1\.dll;bbb/testMod2\.dll]])
+  set(_alldest_mingw [[bbb;bbb/libtestExe2lib\.dll;bbb/libtestLib3dll(-d|-r)?\.dll;bbb/libtestLib4\.dll;bbb/libtestMod1\.dll;bbb/libtestMod2\.dll;bbb/testExe1\.exe;bbb/testExe3\.exe]])
+
+  set(_default_msvc [[bin;bin/testExe1\.exe;bin/testExe2lib\.dll;bin/testExe3\.exe;bin/testLib3dll(-d|-r)?\.dll;bin/testLib4\.dll;lib;lib/testMod1\.dll;lib/testMod2\.dll]])
+  set(_default_mingw [[bin;bin/libtestExe2lib\.dll;bin/libtestLib3dll(-d|-r)?\.dll;bin/libtestLib4\.dll;bin/testExe1\.exe;bin/testExe3\.exe;lib;lib/libtestMod1\.dll;lib/libtestMod2\.dll]])
+
+  check_installed("^(${_dirs_msvc};${_alldest_msvc};${_default_msvc}|${_dirs_mingw};${_alldest_mingw};${_default_mingw})$")
+elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
+  set(_bundles [[aaa/bundles;aaa/bundles/testExe3\.app;aaa/bundles/testExe3\.app/Contents;aaa/bundles/testExe3\.app/Contents/Info\.plist;aaa/bundles/testExe3\.app/Contents/MacOS;aaa/bundles/testExe3\.app/Contents/MacOS/testExe3(;aaa/bundles/testExe3\.app/Contents/PkgInfo)?(;aaa/bundles/testExe3\.app/Contents/_CodeSignature;aaa/bundles/testExe3\.app/Contents/_CodeSignature/CodeResources)?]])
+  set(_executables [[aaa/executables;aaa/executables/testExe1(-4)?]])
+  set(_frameworks [[aaa/frameworks;aaa/frameworks/testLib4\.framework;aaa/frameworks/testLib4\.framework/Headers;aaa/frameworks/testLib4\.framework/Headers/testLib4\.h;aaa/frameworks/testLib4\.framework/Resources;aaa/frameworks/testLib4\.framework/Versions;aaa/frameworks/testLib4\.framework/Versions/A;aaa/frameworks/testLib4\.framework/Versions/A/Resources;aaa/frameworks/testLib4\.framework/Versions/A/Resources/Info\.plist(;aaa/frameworks/testLib4\.framework/Versions/A/_CodeSignature;aaa/frameworks/testLib4\.framework/Versions/A/_CodeSignature/CodeResources)?;aaa/frameworks/testLib4\.framework/Versions/A/testLib4;aaa/frameworks/testLib4\.framework/Versions/Current;aaa/frameworks/testLib4\.framework/testLib4]])
+  set(_libraries [[aaa/libraries;aaa/libraries/libtestExe2lib\.dylib;aaa/libraries/libtestLib3lib(-d|-r)?\.1\.2\.dylib;aaa/libraries/libtestLib3lib(-d|-r)?\.3\.dylib;aaa/libraries/libtestMod1\.so;aaa/libraries/testMod2\.bundle;aaa/libraries/testMod2\.bundle/Contents;aaa/libraries/testMod2\.bundle/Contents/Info\.plist;aaa/libraries/testMod2\.bundle/Contents/MacOS;aaa/libraries/testMod2\.bundle/Contents/MacOS/testMod2(;aaa/libraries/testMod2\.bundle/Contents/_CodeSignature;aaa/libraries/testMod2\.bundle/Contents/_CodeSignature/CodeResources)?]])
+  set(_dirs "aaa;${_bundles};${_executables};${_frameworks};${_libraries}")
+
+  set(_alldest [[bbb;bbb/libtestExe2lib\.dylib;bbb/libtestLib3lib(-d|-r)?\.1\.2\.dylib;bbb/libtestLib3lib(-d|-r)?\.3\.dylib;bbb/libtestMod1\.so;bbb/testExe1(-4)?;bbb/testExe3\.app;bbb/testExe3\.app/Contents;bbb/testExe3\.app/Contents/Info\.plist;bbb/testExe3\.app/Contents/MacOS;bbb/testExe3\.app/Contents/MacOS/testExe3(;bbb/testExe3\.app/Contents/PkgInfo)?(;bbb/testExe3\.app/Contents/_CodeSignature;bbb/testExe3\.app/Contents/_CodeSignature/CodeResources)?;bbb/testLib4\.framework;bbb/testLib4\.framework/Headers;bbb/testLib4\.framework/Headers/testLib4\.h;bbb/testLib4\.framework/Resources;bbb/testLib4\.framework/Versions;bbb/testLib4\.framework/Versions/A;bbb/testLib4\.framework/Versions/A/Resources;bbb/testLib4\.framework/Versions/A/Resources/Info\.plist(;bbb/testLib4\.framework/Versions/A/_CodeSignature;bbb/testLib4\.framework/Versions/A/_CodeSignature/CodeResources)?;bbb/testLib4\.framework/Versions/A/testLib4;bbb/testLib4\.framework/Versions/Current;bbb/testLib4\.framework/testLib4;bbb/testMod2\.bundle;bbb/testMod2\.bundle/Contents;bbb/testMod2\.bundle/Contents/Info\.plist;bbb/testMod2\.bundle/Contents/MacOS;bbb/testMod2\.bundle/Contents/MacOS/testMod2(;bbb/testMod2\.bundle/Contents/_CodeSignature;bbb/testMod2\.bundle/Contents/_CodeSignature/CodeResources)?]])
+
+  set(_default_bin [[bin;bin/testExe1(-4)?]])
+  set(_default_lib [[lib;lib/libtestExe2lib\.dylib;lib/libtestLib3lib(-d|-r)?\.1\.2\.dylib;lib/libtestLib3lib(-d|-r)?\.3\.dylib;lib/libtestMod1\.so;lib/testMod2\.bundle;lib/testMod2\.bundle/Contents;lib/testMod2\.bundle/Contents/Info\.plist;lib/testMod2\.bundle/Contents/MacOS;lib/testMod2\.bundle/Contents/MacOS/testMod2(;lib/testMod2\.bundle/Contents/_CodeSignature;lib/testMod2\.bundle/Contents/_CodeSignature/CodeResources)?]])
+  set(_default_bundles [[zzz/bundles;zzz/bundles/testExe3\.app;zzz/bundles/testExe3\.app/Contents;zzz/bundles/testExe3\.app/Contents/Info\.plist;zzz/bundles/testExe3\.app/Contents/MacOS;zzz/bundles/testExe3\.app/Contents/MacOS/testExe3(;zzz/bundles/testExe3\.app/Contents/PkgInfo)?(;zzz/bundles/testExe3\.app/Contents/_CodeSignature;zzz/bundles/testExe3\.app/Contents/_CodeSignature/CodeResources)?]])
+  set(_default_frameworks [[zzz/frameworks;zzz/frameworks/testLib4\.framework;zzz/frameworks/testLib4\.framework/Headers;zzz/frameworks/testLib4\.framework/Headers/testLib4\.h;zzz/frameworks/testLib4\.framework/Resources;zzz/frameworks/testLib4\.framework/Versions;zzz/frameworks/testLib4\.framework/Versions/A;zzz/frameworks/testLib4\.framework/Versions/A/Resources;zzz/frameworks/testLib4\.framework/Versions/A/Resources/Info\.plist(;zzz/frameworks/testLib4\.framework/Versions/A/_CodeSignature;zzz/frameworks/testLib4\.framework/Versions/A/_CodeSignature/CodeResources)?;zzz/frameworks/testLib4\.framework/Versions/A/testLib4;zzz/frameworks/testLib4\.framework/Versions/Current;zzz/frameworks/testLib4\.framework/testLib4]])
+  set(_default "${_default_bin};${_default_lib};zzz;${_default_bundles};${_default_frameworks}")
+
+  # Need to break this up due to too many pairs of parentheses
+  check_installed("^${_dirs};bbb;")
+  check_installed(";${_alldest};bin;")
+  check_installed(";${_default}$")
+endif()
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
index b9a1fbfc6a275e54d660a678522f34aa1f92d9dd..9911ad5be3ee8025ee350746491554333507418d 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-check.py
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
@@ -644,7 +644,7 @@ def gen_check_directories(c, g):
 
     if sys.platform not in ("win32", "cygwin", "msys"):
         for e in expected:
-            e["installers"] = list(filter(lambda i: "_dllExtra" not in i or not i["_dllExtra"], e["installers"]))
+            e["installers"] = list(filter(lambda i: not i.get("_dllExtra", False), e["installers"]))
             if "aix" not in sys.platform:
                 for i in e["installers"]:
                     if "pathsNamelink" in i:
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/imported.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/imported.json
index d1272740974559110c813803e5c4c14194bfc731..92b9526405001f34a23004d357e0786001bafc74 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/imported.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/imported.json
@@ -14,6 +14,67 @@
     ],
     "projectName": "Imported",
     "minimumCMakeVersion": "3.12",
-    "hasInstallRule": null,
-    "installers": []
+    "hasInstallRule": true,
+    "installers": [
+        {
+            "component": "Unspecified",
+            "type": "importedRuntimeArtifacts",
+            "destination": "lib",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": null,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "backtrace": [
+                {
+                    "file": "^imported/CMakeLists\\.txt$",
+                    "line": 32,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^imported/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "component": "Unspecified",
+            "type": "importedRuntimeArtifacts",
+            "destination": "lib2",
+            "paths": null,
+            "isExcludeFromAll": null,
+            "isForAllComponents": null,
+            "isOptional": true,
+            "targetId": null,
+            "targetIndex": null,
+            "targetIsImportLibrary": null,
+            "targetInstallNamelink": null,
+            "exportName": null,
+            "exportTargets": null,
+            "scriptFile": null,
+            "backtrace": [
+                {
+                    "file": "^imported/CMakeLists\\.txt$",
+                    "line": 35,
+                    "command": "install",
+                    "hasParent": true
+                },
+                {
+                    "file": "^imported/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ]
 }
diff --git a/Tests/RunCMake/FileAPI/imported/CMakeLists.txt b/Tests/RunCMake/FileAPI/imported/CMakeLists.txt
index f79d87c823b87c3762a0807de5a6f295739f4807..770838b6a722bbc8fe100fc33872b80c21733a4d 100644
--- a/Tests/RunCMake/FileAPI/imported/CMakeLists.txt
+++ b/Tests/RunCMake/FileAPI/imported/CMakeLists.txt
@@ -28,3 +28,10 @@ endif()
 add_library(imported_interface_lib INTERFACE IMPORTED)
 add_executable(link_imported_interface_exe ../empty.c)
 target_link_libraries(link_imported_interface_exe PRIVATE imported_interface_lib)
+
+install(IMPORTED_RUNTIME_ARTIFACTS imported_shared_lib
+  DESTINATION lib
+)
+install(IMPORTED_RUNTIME_ARTIFACTS imported_shared_lib
+  DESTINATION lib2 OPTIONAL
+)
diff --git a/bootstrap b/bootstrap
index aefd210e7ca2ce10750c0b2be0575316b1622705..911558da062db47684845097b157c8b4879997bd 100755
--- a/bootstrap
+++ b/bootstrap
@@ -387,6 +387,7 @@ CMAKE_CXX_SOURCES="\
   cmInstallFilesCommand \
   cmInstallFilesGenerator \
   cmInstallGenerator \
+  cmInstallImportedRuntimeArtifactsGenerator \
   cmInstallScriptGenerator \
   cmInstallSubdirectoryGenerator \
   cmInstallTargetGenerator \