diff --git a/CMakeLists.txt b/CMakeLists.txt
index f7fd193ca3beb458675cc23b5f23620743eb8526..e9cac8f50dc6d98c4911f4ca7d9cf4ad7b1b35f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -556,47 +556,6 @@ if(MSVC)
     target_sources(${PROJECT_NAME} PRIVATE ${QM_FILES})
   endif()
 
-  # POST_BUILD steps
-
-  # check time stamp
-  set(TIME_STAMP_FILE ".deploy.stamp")
-  add_custom_command(
-    TARGET ${PROJECT_NAME}
-    POST_BUILD
-    WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
-    COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
-    -P ${EXTRAS_DIR}/build/cmake/time_stamp_check.cmake)
-
-  # copy runtime files and run windeployqt on target and deploy Qt libs
-  add_custom_command(
-    TARGET ${PROJECT_NAME}
-    POST_BUILD
-    WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
-    COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
-    -DCOPY_TO_PATH=$<TARGET_FILE_DIR:${PROJECT_NAME}>
-    -DDRING_PATH=${DAEMON_DIR}
-    -DPROJECT_ROOT_DIR=${PROJECT_SOURCE_DIR}
-    -DPACKAGING_DIR=${PACKAGING_DIR}
-    -P ${EXTRAS_DIR}/build/cmake/windows_daemon_deploy.cmake)
-
-  add_custom_command(
-    TARGET ${PROJECT_NAME}
-    POST_BUILD
-    WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
-    COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
-    -DWIN_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
-    -DQML_SRC_DIR=${APP_SRC_DIR}
-    -DEXE_NAME=$<TARGET_FILE:${PROJECT_NAME}>
-    -P ${EXTRAS_DIR}/build/cmake/windows_qt_deploy.cmake)
-
-  # create time stamp
-  add_custom_command(
-    TARGET ${PROJECT_NAME}
-    POST_BUILD
-    WORKING_DIRECTORY "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
-    COMMAND ${CMAKE_COMMAND} -DTIME_STAMP_FILE=${TIME_STAMP_FILE}
-    -P ${EXTRAS_DIR}/build/cmake/time_stamp_create.cmake)
-
   # executable name
   set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "Jami")
 elseif (NOT APPLE)
diff --git a/JamiInstaller/HarvestFilter.xslt b/JamiInstaller/HarvestFilter.xslt
index 4b78b7c76bf71f7a92955a8cf0810be6f0406217..0a043ec8700bef6469c6557d14d758791a294a82 100644
--- a/JamiInstaller/HarvestFilter.xslt
+++ b/JamiInstaller/HarvestFilter.xslt
@@ -12,33 +12,12 @@ xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
     </xsl:template>
 
     <xsl:key name="service-search" match="wix:Component[contains(wix:File/@Source, 'Jami.exe')]" use="@Id" />
-    <xsl:key name="vc-service-search" match="wix:Component[contains(wix:File/@Source, 'redist') and contains(wix:File/@Source, 'vc')]" use="@Id" />
     <xsl:key name="pdb-search" match="wix:Component[contains(wix:File/@Source, '.pdb')]" use="@Id" />
-    <xsl:key name="lib-search" match="wix:Component[contains(wix:File/@Source, 'Jami.lib')]" use="@Id" />
-    <xsl:key name="exp-search" match="wix:Component[contains(wix:File/@Source, 'Jami.exp')]" use="@Id" />
-    <xsl:key name="qmake-search" match="wix:Component[contains(wix:File/@Source, 'qmake')]" use="@Id" />
-    <xsl:key name="obj-search" match="wix:Component[contains(wix:File/@Source, '.obj')]" use="@Id" />
-    <xsl:key name="tlog-search" match="wix:Component[contains(wix:File/@Source, '.tlog')]" use="@Id" />
-    <xsl:key name="log-search" match="wix:Component[contains(wix:File/@Source, '.log')]" use="@Id" />
 
     <xsl:template match="wix:Component[key('service-search', @Id)]" />
-    <xsl:template match="wix:Component[key('vc-service-search', @Id)]" />
     <xsl:template match="wix:Component[key('pdb-search', @Id)]" />
-    <xsl:template match="wix:Component[key('lib-search', @Id)]" />
-    <xsl:template match="wix:Component[key('exp-search', @Id)]" />
-    <xsl:template match="wix:Component[key('qmake-search', @Id)]" />
-    <xsl:template match="wix:Component[key('obj-search', @Id)]" />
-    <xsl:template match="wix:Component[key('tlog-search', @Id)]" />
-    <xsl:template match="wix:Component[key('log-search', @Id)]" />
 
     <xsl:template match="wix:ComponentRef[key('service-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('vc-service-search', @Id)]" />
     <xsl:template match="wix:ComponentRef[key('pdb-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('lib-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('exp-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('qmake-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('obj-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('tlog-search', @Id)]" />
-    <xsl:template match="wix:ComponentRef[key('log-search', @Id)]" />
 
 </xsl:stylesheet>
\ No newline at end of file
diff --git a/extras/build/cmake/time_stamp_check.cmake b/extras/build/cmake/time_stamp_check.cmake
deleted file mode 100644
index b0f44b7d91931214597e60db7d36baf37314615c..0000000000000000000000000000000000000000
--- a/extras/build/cmake/time_stamp_check.cmake
+++ /dev/null
@@ -1,13 +0,0 @@
-execute_process(COMMAND git rev-parse HEAD
-                OUTPUT_VARIABLE VERSION_PATCH)
-
-# remove leading and trailing spaces
-string(STRIP "${VERSION_PATCH}" VERSION_PATCH)
-
-message("Checking time stamp ...")
-if(EXISTS ${TIME_STAMP_FILE})
-    file (STRINGS ${TIME_STAMP_FILE} VERSION_IN_FILE)
-    if(NOT "${VERSION_IN_FILE}" STREQUAL "${VERSION_PATCH}")
-        file (REMOVE "${TIME_STAMP_FILE}")
-    endif()
-endif()
\ No newline at end of file
diff --git a/extras/build/cmake/time_stamp_create.cmake b/extras/build/cmake/time_stamp_create.cmake
deleted file mode 100644
index 7b8aff2b1a794382154d1e59d4414c69ed082508..0000000000000000000000000000000000000000
--- a/extras/build/cmake/time_stamp_create.cmake
+++ /dev/null
@@ -1,8 +0,0 @@
-execute_process(COMMAND git rev-parse HEAD
-                OUTPUT_VARIABLE VERSION_PATCH)
-if (EXISTS ${TIME_STAMP_FILE})
-    message("Keep the old time stamp")
-else()
-    message("Creating time stamp ...")
-    file(WRITE ${TIME_STAMP_FILE} "${VERSION_PATCH}")
-endif()
\ No newline at end of file
diff --git a/extras/build/cmake/windows_daemon_deploy.cmake b/extras/build/cmake/windows_daemon_deploy.cmake
deleted file mode 100644
index 25ff6a3c062388f17c8a1a299cf7bf2e3d10f336..0000000000000000000000000000000000000000
--- a/extras/build/cmake/windows_daemon_deploy.cmake
+++ /dev/null
@@ -1,21 +0,0 @@
-if (EXISTS ${TIME_STAMP_FILE})
-    message("No need for daemon deployment")
-else()
-    message("Daemon deploying ...")
-    file(COPY "${DRING_PATH}/contrib/build/openssl/libcrypto-1_1-x64.dll"
-              "${DRING_PATH}/contrib/build/openssl/libssl-1_1-x64.dll"
-              "${PACKAGING_DIR}/wix/qt.conf"
-              "${PROJECT_ROOT_DIR}/resources/images/jami.ico"
-              "${PACKAGING_DIR}/wix/License.rtf"
-         DESTINATION ${COPY_TO_PATH})
-    # Cannot copy symbolic link using file COPY, create insread.
-    file(GLOB_RECURSE RingTones "${DRING_PATH}/ringtones/*.ul"
-                                "${DRING_PATH}/ringtones/*.ogg"
-                                "${DRING_PATH}/ringtones/*.wav"
-                                "${DRING_PATH}/ringtones/*.opus")
-    list(REMOVE_ITEM RingTones "${DRING_PATH}/ringtones/default.opus")
-    file(COPY ${RingTones}
-         DESTINATION ${COPY_TO_PATH}/ringtones)
-    file(CREATE_LINK "${COPY_TO_PATH}/ringtones/01_AfroNigeria.opus"
-                     "${COPY_TO_PATH}/ringtones/default.opus")
-endif()
diff --git a/extras/build/cmake/windows_qt_deploy.cmake b/extras/build/cmake/windows_qt_deploy.cmake
deleted file mode 100644
index 8957d1bdb16a345ba375be981328624a7bcb5d7b..0000000000000000000000000000000000000000
--- a/extras/build/cmake/windows_qt_deploy.cmake
+++ /dev/null
@@ -1,14 +0,0 @@
-if (EXISTS ${TIME_STAMP_FILE})
-    message("No need for Qt deployment in dir " ${QML_SRC_DIR})
-else()
-    message("Qt deploying in dir " ${QML_SRC_DIR})
-    execute_process(COMMAND "${WIN_DEPLOY_QT_PATH}/windeployqt.exe"
-                            --verbose 1
-                            --qmldir ${QML_SRC_DIR}
-                            --release ${EXE_NAME})
-    if (DEFINED OFF_SCREEN_PLUGIN_REQUESTED)
-        # for not showing window when testing
-        file(COPY "${OFF_SCREEN_PLUGIN_PATH}/qoffscreen.dll"
-             DESTINATION ${COPY_TO_PATH})
-    endif()
-endif()
\ No newline at end of file
diff --git a/extras/scripts/build-windows.py b/extras/scripts/build-windows.py
index b259ed817cbeebd85277ebbb23cbb993ef282310..5ba8ae05962289ca940aa9683dff5f3bf01d50ac 100644
--- a/extras/scripts/build-windows.py
+++ b/extras/scripts/build-windows.py
@@ -41,6 +41,8 @@ import subprocess
 import platform
 import argparse
 import multiprocessing
+import shutil
+import time
 
 
 # Visual Studio helpers
@@ -93,43 +95,26 @@ def execute_cmd(cmd, with_shell=False, env_vars=None, cmd_dir=repo_root_dir):
     return proc.returncode
 
 
-def get_latest_vs_version():
-    """Find the latest visual c++ compiler tools version."""
+def get_vs_prop(prop):
+    """Get a visual studio property."""
     args = [
         "-latest",
         "-products *",
         "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
-        "-property installationVersion",
+        "-property " + prop,
     ]
     cmd = [VS_WHERE_PATH] + args
     output = subprocess.check_output(" ".join(cmd)).decode("utf-8")
-    if output:
-        return output.splitlines()[0].split(".")[0]
-    else:
-        return
-
-
-def find_latest_vs_dir():
-    """Find the latest visual c++ compiler tools path."""
-    args = [
-        "-latest",
-        "-products *",
-        "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
-        "-property installationPath",
-    ]
-    cmd = [VS_WHERE_PATH] + args
-    output = subprocess.check_output(
-        " ".join(cmd)).decode("utf-8", errors="ignore")
     if output:
         return output.splitlines()[0]
     else:
-        return
+        return None
 
 
 def find_ms_build():
     """Find the latest msbuild executable."""
     filename = "MSBuild.exe"
-    vs_path = find_latest_vs_dir()
+    vs_path = get_vs_prop("installationPath")
     if vs_path is None:
         return
     for root, _, files in os.walk(os.path.join(vs_path, "MSBuild")):
@@ -169,7 +154,7 @@ def get_vs_env(arch="x64", _platform="", version=""):
 
 def get_vs_env_cmd(arch="x64", _platform="", version=""):
     """Get the vcvarsall.bat command."""
-    vs_path = find_latest_vs_dir()
+    vs_path = get_vs_prop("installationPath")
     if vs_path is None:
         return
     vc_env_init = [os.path.join(
@@ -286,6 +271,7 @@ def build(config_str, qt_dir, tests):
     cmake_options = [
         "-DWITH_DAEMON_SUBMODULE=ON",
         "-DCMAKE_PREFIX_PATH=" + qt_dir,
+        "-DCMAKE_MSVCIDE_RUN_PATH=" + qt_dir + "\\bin",
         "-DCMAKE_INSTALL_PREFIX=" + daemon_bin_dir,
         "-DLIBJAMI_INCLUDE_DIR=" + daemon_dir + "\\src\\jami",
         "-DCMAKE_SYSTEM_VERSION=" + WIN_SDK_VERSION,
@@ -307,11 +293,73 @@ def build(config_str, qt_dir, tests):
         sys.exit(1)
 
 
+def deploy_runtimes(qt_dir):
+    """Deploy the dependencies to the runtime directory."""
+    print("Deploying runtime dependencies")
+
+    runtime_dir = os.path.join(repo_root_dir, "x64", "Release")
+    stamp_file = os.path.join(runtime_dir, ".deploy.stamp")
+    if os.path.exists(stamp_file):
+        return
+
+    daemon_dir = os.path.join(repo_root_dir, "daemon")
+    ringtone_dir = os.path.join(daemon_dir, "ringtones")
+    packaging_dir = os.path.join(repo_root_dir, "extras", "packaging")
+
+    def install_file(src, rel_path):
+        shutil.copy(os.path.join(rel_path, src), runtime_dir)
+
+    print("Copying libjami dependencies")
+    install_file("contrib/build/openssl/libcrypto-1_1-x64.dll", daemon_dir)
+    install_file("contrib/build/openssl/libssl-1_1-x64.dll", daemon_dir)
+    # Ringtone files (ul,ogg,wav,opus files in the daemon ringtone dir).
+
+    print("Copying ringtones")
+    ringtone_dir = os.path.join(daemon_dir, "ringtones")
+    ringtone_files = [f for f in os.listdir(ringtone_dir) if f.endswith(
+        (".ul", ".ogg", ".wav", ".opus"))]
+    ringtone_files = [os.path.join(ringtone_dir, f) for f in ringtone_files]
+    default_ringtone = os.path.join(ringtone_dir, "default.opus")
+    ringtone_files.remove(default_ringtone)
+    ringtone_dir_out = os.path.join(runtime_dir, "ringtones")
+    if os.path.exists(ringtone_dir_out):
+        shutil.rmtree(ringtone_dir_out)
+    os.makedirs(ringtone_dir_out, exist_ok=True)
+    for ringtone in ringtone_files:
+        shutil.copy(ringtone, os.path.join(runtime_dir, "ringtones"))
+    # Create a hard link to the default ringtone (Windows).
+    os.link(os.path.join(runtime_dir, "ringtones", "01_AfroNigeria.opus"),
+            os.path.join(runtime_dir, "ringtones", "default.opus"))
+
+    print("Copying misc. client configuration files")
+    install_file("wix/qt.conf", packaging_dir)
+    install_file("wix/License.rtf", packaging_dir)
+    install_file("resources/images/jami.ico", repo_root_dir)
+
+    # windeployqt
+    print("Running windeployqt (this may take a while)...")
+    win_deploy_qt = os.path.join(qt_dir, "bin", "windeployqt.exe")
+    qml_src_dir = os.path.join(repo_root_dir, "src", "app")
+    os.environ["VCINSTALLDIR"] = os.path.join(
+        get_vs_prop("installationPath"), "VC")
+    executable = os.path.join(runtime_dir, "Jami.exe")
+    execute_cmd([win_deploy_qt, "--verbose", "1", "--no-compiler-runtime",
+                 "--qmldir", qml_src_dir, "--release", executable],
+                False, cmd_dir=runtime_dir)
+
+    with open(stamp_file, "w", encoding="utf-8") as file:
+        # Write the current time to the file.
+        file.write(str(time.time()))
+
+
 def run_tests(config_str, qt_dir):
     """Run tests."""
     print("Running client tests")
 
     os.environ["PATH"] += os.pathsep + os.path.join(qt_dir, 'bin')
+    daemon_dir = os.path.join(repo_root_dir, "daemon")
+    os.environ["PATH"] += os.pathsep + \
+        os.path.join(daemon_dir, "contrib", "build", "openssl")
     os.environ["QT_QPA_PLATFORM"] = "offscreen"
     os.environ["QT_QUICK_BACKEND"] = "software"
     os.environ['QT_QPA_FONTDIR'] = os.path.join(
@@ -319,6 +367,9 @@ def run_tests(config_str, qt_dir):
     os.environ['QT_PLUGIN_PATH'] = os.path.join(qt_dir, 'plugins')
     os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(
         qt_dir, 'plugins', 'platforms')
+    os.environ['QTWEBENGINEPROCESS_PATH'] = os.path.join(
+        qt_dir, 'bin', 'QtWebEngineProcess.exe')
+    os.environ["QML2_IMPORT_PATH"] = os.path.join(qt_dir, "qml")
 
     tests_dir = os.path.join(build_dir, "tests")
     if execute_cmd(["ctest", "-V", "-C", config_str],
@@ -357,7 +408,7 @@ def generate_zip(version):
     app_output_dir = os.path.join(repo_root_dir, 'x64', 'Release')
     app_files = os.path.join(app_output_dir, '*')
 
-    # TODO: exclude Jami.PDB, .deploy.stamp, and vc_redist.x64.exe
+    # TODO: exclude Jami.PDB, .deploy.stamp
 
     artifacts_dir = os.path.join(build_dir, 'artifacts')
     if not os.path.exists(artifacts_dir):
@@ -404,7 +455,13 @@ def parse_args():
     parser.add_argument(
         "-i", "--init", action="store_true", help="Initialize submodules")
     parser.add_argument(
-        "-s",
+        '-sd',
+        '--skip-deploy',
+        action='store_true',
+        default=False,
+        help='Force skip deployment of runtime files needed for packaging')
+    parser.add_argument(
+        "-sb",
         "--skip-build",
         action="store_true",
         default=False,
@@ -426,7 +483,7 @@ def main():
         print("These scripts will only run on a 64-bit system for now.")
         sys.exit(1)
     if sys.platform == "win32":
-        vs_version = get_latest_vs_version()
+        vs_version = get_vs_prop("installationVersion").split(".")[0]
         if vs_version is None or int(vs_version) < 15:
             print("Visual Studio 2017 or later is required.")
             sys.exit(1)
@@ -454,19 +511,21 @@ def main():
         sys.exit(0)
 
     config_str = ('Release', 'Beta')[parsed_args.beta]
-    skip_build = parsed_args.skip_build
+
+    def do_build(do_tests):
+        if not parsed_args.skip_build:
+            build(config_str, parsed_args.qt, do_tests)
+        if not parsed_args.skip_deploy:
+            deploy_runtimes(parsed_args.qt)
 
     if parsed_args.subcommand == "pack":
-        if not skip_build:
-            build(config_str, parsed_args.qt, False)
-        elif parsed_args.msi:
+        do_build(False)
+        if parsed_args.msi:
             generate_msi(get_version())
         elif parsed_args.zip:
             generate_zip(get_version())
     else:
-        if not skip_build:
-            build(config_str, parsed_args.qt,
-                  parsed_args.tests)
+        do_build(parsed_args.tests)
         if parsed_args.tests:
             run_tests(config_str, parsed_args.qt)