diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e83c96b99..f3d41c1f9e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1895,12 +1895,14 @@ if(ENABLE_PLUGIN_APPS)
             src/collectors/apps.plugin/apps_os_macos.c
             src/collectors/apps.plugin/apps_os_windows.c
             src/collectors/apps.plugin/apps_incremental_collection.c
+            src/collectors/apps.plugin/apps_os_windows_nt.c
     )
 
     add_executable(apps.plugin ${APPS_PLUGIN_FILES})
 
     target_link_libraries(apps.plugin libnetdata ${CAP_LIBRARIES}
-            "$<$<BOOL:${OS_WINDOWS}>:Version>")
+            "$<$<BOOL:${OS_WINDOWS}>:Version>"
+            "$<$<BOOL:${OS_WINDOWS}>:ntdll>")
 
     target_include_directories(apps.plugin PRIVATE ${CAP_INCLUDE_DIRS})
     target_compile_options(apps.plugin PRIVATE ${CAP_CFLAGS_OTHER})
diff --git a/src/collectors/apps.plugin/README.md b/src/collectors/apps.plugin/README.md
index 7960cd8101..0ed4c68fdf 100644
--- a/src/collectors/apps.plugin/README.md
+++ b/src/collectors/apps.plugin/README.md
@@ -132,6 +132,26 @@ its CPU resources will be cut in half, and data collection will be once every 2
 
 The configuration file is `/etc/netdata/apps_groups.conf`. You can edit this file using our [`edit-config`](docs/netdata-agent/configuration/README.md) script.
 
+### Configuring process managers
+
+`apps.plugin` needs to know the common process managers, meaning the names of the processes
+which spawn other processes. Process managers are used so that `apps.plugin` will automatically
+consider all their sub-processes important to monitor.
+
+Process managers are configured in `apps_groups.conf` with the prefix `managers:`, like this:
+
+```
+managers: process1 process2 process3
+```
+
+Multiple lines may exist, all starting with `managers:`.
+
+The process names given here should be exactly as the operating system sets them. In Linux these
+process names are limited to 15 characters. Usually the command `ps -e` or `cat /proc/{PID}/stat`
+states the names needed here.
+
+### Configuring process groups and renaming processes
+
 The configuration file works accepts multiple lines, each having this format:
 
 ```txt
@@ -140,48 +160,39 @@ group: process1 process2 ...
 
 Each group can be given multiple times, to add more processes to it.
 
-For the **Applications** section, only groups configured in this file are reported.
-All other processes will be reported as `other`.
-
-For each process given, its whole process tree will be grouped, not just the process matched.
-The plugin will include both parents and children. If including the parents into the group is
-undesirable, the line `other: *` should be appended to the `apps_groups.conf`.
+For each process given, all of its sub-processes will be grouped, not just the matched process.
 
 The process names are the ones returned by:
 
--   `ps -e` or `cat /proc/PID/stat`
--   in case of substring mode (see below): `/proc/PID/cmdline`
+-   **comm**: `ps -e` or `cat /proc/{PID}/stat`
+-   **cmdline**: in case of substring mode (see below): `/proc/{PID}/cmdline`
+
+On Linux **comm** is limited to just a few characters. `apps.plugin` attempts to find the entire
+**comm** name by looking for it at the **cmdline**. When this is successful, the entire process name
+is available, otherwise the shortened one is used.
 
 To add process names with spaces, enclose them in quotes (single or double)
 example: `'Plex Media Serv'` or `"my other process"`.
 
-You can add an asterisk `*` at the beginning and/or the end of a process:
+You can add asterisks (`*`) to provide a pattern:
 
--   `*name` _suffix_ mode: will search for processes ending with `name` (at `/proc/PID/stat`)
--   `name*` _prefix_ mode: will search for processes beginning with `name` (at `/proc/PID/stat`)
--   `*name*` _substring_ mode: will search for `name` in the whole command line (at `/proc/PID/cmdline`)
+-   `*name` _suffix_ mode: will match a **comm** ending with `name`.
+-   `name*` _prefix_ mode: will match a **comm** beginning with `name`.
+-   `*name*` _substring_ mode: will search for `name` in **cmdline**.
 
-If you enter even just one _name_ (substring), `apps.plugin` will process
-`/proc/PID/cmdline` for all processes (of course only once per process: when they are first seen).
+Asterisks may appear in the middle of `name` (like `na*me`), without affecting what is being
+matched (**comm** or **cmdline**). 
 
 To add processes with single quotes, enclose them in double quotes: `"process with this ' single quote"`
 
 To add processes with double quotes, enclose them in single quotes: `'process with this " double quote'`
 
-If a group or process name starts with a `-`, the dimension will be hidden from the chart (cpu chart only).
+The order of the entries in this list is important: the first one that matches a process is used, so follow a top-down hierarchy.
+Processes not matched by any row, will inherit it from their parents.
 
-If a process starts with a `+`, debugging will be enabled for it (debugging produces a lot of output - do not enable it in production systems).
-
-You can add any number of groups. Only the ones found running will affect the charts generated.
-However, producing charts with hundreds of dimensions may slow down your web browser.
-
-The order of the entries in this list is important: the first that matches a process is used, so put important
-ones at the top. Processes not matched by any row, will inherit it from their parents or children.
-
-The order also controls the order of the dimensions on the generated charts (although applications started
-after apps.plugin is started, will be appended to the existing list of dimensions the `netdata` daemon maintains).
-
-There are a few command line options you can pass to `apps.plugin`. The list of available options can be acquired with the `--help` flag. The options can be set in the `netdata.conf` file. For example, to disable user and user group charts you should set
+There are a few command line options you can pass to `apps.plugin`. The list of available
+options can be acquired with the `--help` flag. The options can be set in the `netdata.conf` using the [`edit-config` script](/docs/netdata-agent/configuration/README.md).
+For example, to disable user and user group charts you would set:
 
 ```
 [plugin:apps]
diff --git a/src/collectors/apps.plugin/apps_aggregations.c b/src/collectors/apps.plugin/apps_aggregations.c
index 8c7ebb6899..289d874b97 100644
--- a/src/collectors/apps.plugin/apps_aggregations.c
+++ b/src/collectors/apps.plugin/apps_aggregations.c
@@ -74,7 +74,7 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p,
     if(!w->uptime_min || p->values[PDF_UPTIME] < w->uptime_min) w->uptime_min = p->values[PDF_UPTIME];
     if(!w->uptime_max || w->uptime_max < p->values[PDF_UPTIME]) w->uptime_max = p->values[PDF_UPTIME];
 
-    if(unlikely(debug_enabled || w->debug_enabled)) {
+    if(unlikely(debug_enabled)) {
         struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
         pid_on_target->pid = p->pid;
         pid_on_target->next = w->root_pid;
@@ -110,27 +110,61 @@ static inline void cleanup_exited_pids(void) {
     }
 }
 
-static struct target *get_app_group_target_for_pid(struct pid_stat *p) {
+static struct target *matched_apps_groups_target(struct pid_stat *p, struct target *w) {
+    if(is_process_manager(p))
+        return NULL;
+
+    p->matched_by_config = true;
+    return w->target ? w->target : w;
+}
+
+static struct target *get_apps_groups_target_for_pid(struct pid_stat *p) {
     targets_assignment_counter++;
 
     for(struct target *w = apps_groups_root_target; w ; w = w->next) {
         if(w->type != TARGET_TYPE_APP_GROUP) continue;
 
-        // find it - 4 cases:
-        // 1. the target is not a pattern
-        // 2. the target has the prefix
-        // 3. the target has the suffix
-        // 4. the target is something inside cmdline
-
-        if(unlikely(( (!w->starts_with && !w->ends_with && w->compare == p->comm)
-                      || (w->starts_with && !w->ends_with && string_starts_with_string(p->comm, w->compare))
-                      || (!w->starts_with && w->ends_with && string_ends_with_string(p->comm, w->compare))
-                      || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(pid_stat_cmdline(p), string2str(w->compare)))
-                          ))) {
-
-            p->matched_by_config = true;
-            if(w->target) return w->target;
-            else return w;
+        if(!w->starts_with && !w->ends_with) {
+            if(w->ag.pattern) {
+                if(simple_pattern_matches_string(w->ag.pattern, p->comm))
+                    return matched_apps_groups_target(p, w);
+            }
+            else {
+                if(w->ag.compare == p->comm || w->ag.compare == p->comm_orig)
+                    return matched_apps_groups_target(p, w);
+            }
+        }
+        else if(w->starts_with && !w->ends_with) {
+            if(w->ag.pattern) {
+                if(simple_pattern_matches_string(w->ag.pattern, p->comm))
+                    return matched_apps_groups_target(p, w);
+            }
+            else {
+                if(string_starts_with_string(p->comm, w->ag.compare) ||
+                    (p->comm != p->comm_orig && string_starts_with_string(p->comm, w->ag.compare)))
+                    return matched_apps_groups_target(p, w);
+            }
+        }
+        else if(!w->starts_with && w->ends_with) {
+            if(w->ag.pattern) {
+                if(simple_pattern_matches_string(w->ag.pattern, p->comm))
+                    return matched_apps_groups_target(p, w);
+            }
+            else {
+                if(string_ends_with_string(p->comm, w->ag.compare) ||
+                    (p->comm != p->comm_orig && string_ends_with_string(p->comm, w->ag.compare)))
+                    return matched_apps_groups_target(p, w);
+            }
+        }
+        else if(w->starts_with && w->ends_with && p->cmdline) {
+            if(w->ag.pattern) {
+                if(simple_pattern_matches_string(w->ag.pattern, p->cmdline))
+                    return matched_apps_groups_target(p, w);
+            }
+            else {
+                if(strstr(string2str(p->cmdline), string2str(w->ag.compare)))
+                    return matched_apps_groups_target(p, w);
+            }
         }
     }
 
@@ -141,19 +175,23 @@ static void assign_a_target_to_all_processes(void) {
     // assign targets from app_groups.conf
     for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
         if(!p->target)
-            p->target = get_app_group_target_for_pid(p);
+            p->target = get_apps_groups_target_for_pid(p);
     }
 
     // assign targets from their parents, if they have
     for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
         if(!p->target) {
-            for(struct pid_stat *pp = p->parent ; pp ; pp = pp->parent) {
-                if(pp->target) {
-                    if(pp->matched_by_config) {
-                        // we are only interested about app_groups.conf matches
-                        p->target = pp->target;
+            if(!p->is_manager) {
+                for (struct pid_stat *pp = p->parent; pp; pp = pp->parent) {
+                    if(pp->is_manager) break;
+
+                    if (pp->target) {
+                        if (pp->matched_by_config) {
+                            // we are only interested about app_groups.conf matches
+                            p->target = pp->target;
+                        }
+                        break;
                     }
-                    break;
                 }
             }
 
@@ -180,6 +218,7 @@ void aggregate_processes_to_targets(void) {
 
     // this has to be done, before the cleanup
     struct target *w = NULL, *o = NULL;
+    (void)w; (void)o;
 
     // concentrate everything on the targets
     for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
diff --git a/src/collectors/apps.plugin/apps_groups.conf b/src/collectors/apps.plugin/apps_groups.conf
index df01c4b458..6b446361c4 100644
--- a/src/collectors/apps.plugin/apps_groups.conf
+++ b/src/collectors/apps.plugin/apps_groups.conf
@@ -4,19 +4,21 @@
 ## Documentation at:
 ## https://github.com/netdata/netdata/blob/master/src/collectors/apps.plugin/README.md
 ##
-## The list of process managers can be configured here (uncomment and edit):
+## Subprocesses of process managers are monitored.
+## (uncomment to edit - the default is also hardcoded into the plugin)
 
-## Linux
-#managers: init systemd containerd-shim dumb-init gnome-shell docker-init
+## Linux process managers
+#managers: init systemd containerd-shim-runc-v2 dumb-init gnome-shell docker-init
+#managers: openrc-run.sh crond plasmashell xfwm4
 
-## FreeBSD
+## FreeBSD process managers
 #managers: init
 
-## MacOS
+## MacOS process managers
 #managers: launchd
 
-## Windows
-#managers: System services wininit
+## Windows process managers
+#managers: wininit services explorer System
 
 ## -----------------------------------------------------------------------------
 ## Processes of interest
@@ -26,23 +28,23 @@ netdata: netdata
 ## netdata known plugins
 ## plugins not defined here will be accumulated into netdata, above
 apps.plugin: *apps.plugin*
-freeipmi.plugin: *freeipmi.plugin*
-nfacct.plugin: *nfacct.plugin*
-cups.plugin: *cups.plugin*
-xenstat.plugin: *xenstat.plugin*
-perf.plugin: *perf.plugin*
-charts.d.plugin: *charts.d.plugin*
-python.d.plugin: *python.d.plugin*
+go.d.plugin: *go.d.plugin*
 systemd-journal.plugin: *systemd-journal.plugin*
 network-viewer.plugin: *network-viewer.plugin*
 windows-events.plugin: *windows-events.plugin*
-tc-qos-helper: *tc-qos-helper.sh*
-fping: fping
-ioping: ioping
-go.d.plugin: *go.d.plugin*
+cups.plugin: *cups.plugin*
+perf.plugin: *perf.plugin*
+nfacct.plugin: *nfacct.plugin*
+xenstat.plugin: *xenstat.plugin*
+freeipmi.plugin: *freeipmi.plugin*
+charts.d.plugin: *charts.d.plugin*
+python.d.plugin: *python.d.plugin*
 slabinfo.plugin: *slabinfo.plugin*
 ebpf.plugin: *ebpf.plugin*
 debugfs.plugin: *debugfs.plugin*
+tc-qos-helper: *tc-qos-helper.sh*
+fping: fping
+ioping: ioping
 
 ## agent-service-discovery
 agent_sd: agent_sd
@@ -65,32 +67,26 @@ azure: mdsd *waagent* *omiserver* *omiagent* hv_kvp_daemon hv_vss_daemon *auoms*
 datadog: *datadog*
 newrelic: newrelic*
 google-agent: *google_guest_agent* *google_osconfig_agent*
-ceph: ceph-* ceph_* radosgw* rbd-* cephfs-* osdmaptool crushtool
-samba: smbd nmbd winbindd ctdbd ctdb-* ctdb_*
-nfs: rpcbind rpc.* nfs*
-zfs: spl_* z_* txg_* zil_* arc_* l2arc*
-iscsi: iscsid iscsi_eh
-afp: netatalk afpd cnid_dbd cnid_metad
 aws-s3: '*aws s3*' s3cmd s5cmd
 proxmox-ve: pve* spiceproxy
 libvirt: virtlogd virtqemud virtstoraged virtnetworkd virtlockd virtinterfaced
 libvirt: virtnodedevd virtproxyd virtsecretd libvirtd
 guest-agent: qemu-ga spice-vdagent cloud-init*
-dhcp: *dhcp* dhclient
+dhcp: dhcp* dhclient
 
 build: cc1 cc1plus as gcc* cppcheck ld make cmake automake autoconf autoreconf
 build: cargo rustc bazel buck git gdb valgrind* rpmbuild dpkg-buildpackage
-packagemanager: apt* dpkg* dselect dnf yum rpm zypp* yast* pacman xbps* swupd* emerge*
-packagemanager: packagekitd pkgin pkg apk snapd slackpkg slapt-get
+packagemanager: apt* dpkg* dselect dnf yum rpm zypp* yast* pacman xbps* swupd*
+packagemanager: packagekitd pkgin pkg apk snapd slackpkg slapt-get emerge*
 clam: clam* *clam
 backup: rsync lsyncd bacula* borg rclone
 cron: cron* atd anacron *systemd-cron* incrond
 ups: upsmon upsd */nut/* apcupsd
-audio: pulse* pipewire wireplumber jack*
 
 rabbitmq: *rabbitmq*
 sidekiq: *sidekiq*
 erlang: beam.smp
+postfix: *postfix*
 
 ## -----------------------------------------------------------------------------
 ## java applications
@@ -117,12 +113,134 @@ kafka: *kafka.Kafka*
 
 ## -----------------------------------------------------------------------------
 ## Kernel / System
+## The following are interesting kernel threads and related processes to
+## monitor individually, mainly for their CPU utilization.
 
+## These kernel threads switch tasks all the time, so they should never be
+## categorized as anything specific.
+kernel: kworker/*
+
+## Kernel Samepage Merging (KSM) daemon that looks for identical memory pages
+## across processes and merges them to save memory.
 ksmd: ksmd
-khugepaged: khugepaged
+
+## Handles migration of processes between CPU cores to balance load.
+kmigration: migration/*
+
+## Manages memory compaction, moving memory pages around to reduce
+## fragmentation.
+kcompactd: kcompactd*
+
+## Responsible for freeing up memory by swapping pages to disk when needed.
+kswapd: kswapd*
+
+## DAMON is a mechanism designed to efficiently monitor the memory access
+## patterns of running processes or the system itself.
 kdamond: kdamond
-kswapd: kswapd
-zswap: zswap
-kcompactd: kcompactd
-ipvs: ipvs_*
+
+## Manages ballooning in virtualized environments.
+vballoon: vballoon*
+
+## virtio - Handles or I/O (storage and network) on virtual machines.
+kvirtio: virtio-* vhost-*
+
+## Layer 4 (transport layer) load balancing
+ipvs: ipvsd ipvs_* ip_vs_*
+
+## Hugepages
+## Scans memory regions and tries to promote regular-sized pages (4KB) into
+## hugepages (2MB) where possible. Merge smaller contiguous 4KB pages into 2MB
+## pages. Hugepages also use: kswapd, kcompactd, and migration.
+khugepaged: khugepaged
+
+## Note about zswap:
+## zswap does not introduce its own dedicated kernel threads. Instead, it
+## operates within the existing memory management and swapping framework of the
+## kernel:
+## - kswapd: swaps pages in/out of memory, using compression in the process.
+## - kcompactd: compacts memory when pages are compressed or moved around.
+
+## -----------------------------------------------------------------------------
+## Block Devices
+
+## Handles deferred block I/O operations for block devices.
+kblockd: kblockd
+
+## Device Mapper (DM)
+device-mapper: kcopyd/* kcryptd/* kdmflush/* dm_bufio_cache
+device-mapper: raid1/* raid5/* raid10/* multipathd bioset/*
+
+## Software RAID (MD)
+md-raid: md*_raid* md*_resync md*_reshape md*_recovery md_thread
+md-raid: flush_md* raid*_sync
+
+## iSCSI
+iscsi: iscsid iscsiadm iscsi_eh/* iscsi_xmit/* iscsi_ttx/* iscsi_rx/* iscsi_trx/*
+
+## SCSI
+scsi: scsi_eh/* scsi_tmf/* scsi_wq/*
+
+## BCACHE
+bcache: bcache* bch_btree_io bch_journal
+
+## SAS
+sas: sas_task/* mpt*
+
+## Fibre Channel (FC)
+fc: fc_transport qla2xxx*
+
+## loop devices
+loop: loop* flush-loop*
+
+## -----------------------------------------------------------------------------
+## Filesystems
+
+## Ext4
+ext4: ext4-* jbd2/*
+
+## XFS
+xfs: xfs*
+
+## BTRFS
 btrfs: btrfs*
+
+## NFS
+nfs: rpcbind rpc.* nfs* rpciod
+
+## ZFS
+zfs: spl_* z_* txg_* zil_* arc_* l2arc* zfs* zed zdb zpool*
+
+## CEPH
+ceph: ceph-* ceph_* radosgw* rbd-* cephfs-*
+ceph: ceph cephadm osdmaptool crushtool rados rbd
+
+## CIFS & Samba
+cifs: smbd nmbd winbindd ctdbd ctdb-* ctdb_*
+cifs: cifsd cifscreds cifs.upcall
+
+## Apple Filling Protocol (AFP)
+afp: netatalk afpd cnid_dbd cnid_metad
+
+## -----------------------------------------------------------------------------
+## Desktops
+
+systemd-journald: *systemd-journal*
+systemd: systemd systemd-*
+
+## GNOME
+desktop: gnome-* gsd-* gjs goa-* gcr-* gvfs-* *xdg-*-gnome* passimd gvfsd*
+desktop: at-spi-* at-spi2-* dconf-service gcr-*
+
+## KDE
+desktop: plasmashell kwin-* kde* *-kde-* klauncher kactivitymanagerd krunner
+desktop: kdeconnectd ksmserver kglobalaccel5 plasma-* *org.kde.*
+desktop: sddm* kwalletd5 knotify5 kmix kscreen kwayland-*
+
+## XFCE4
+desktop: xfce4-* xfwm4 xfdesktop xfce4-panel xfsettingsd xfconfd
+desktop: lightdm lightdm-*
+
+## Generic tools related to desktop
+desktop: gdm gdm-* dbus-* xdg-* ibus-* evolution-* accounts-daemon colord
+desktop: geoclue pulse* pipewire* wireplumber jack* touchegg pulseaudio
+desktop: Xwayland Xorg
diff --git a/src/collectors/apps.plugin/apps_os_freebsd.c b/src/collectors/apps.plugin/apps_os_freebsd.c
index f2480acfa0..265cda8b67 100644
--- a/src/collectors/apps.plugin/apps_os_freebsd.c
+++ b/src/collectors/apps.plugin/apps_os_freebsd.c
@@ -291,7 +291,7 @@ bool apps_os_read_pid_stat_freebsd(struct pid_stat *p, void *ptr) {
     usec_t started_ut = timeval_usec(&proc_info->ki_start);
     p->values[PDF_UPTIME] = (system_current_time_ut > started_ut) ? (system_current_time_ut - started_ut) / USEC_PER_SEC : 0;
 
-    if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
+    if(unlikely(debug_enabled || p->target))
         debug_log_int("READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=%d",
                       netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET",
                       p->stat_collected_usec - p->last_stat_collected_usec,
diff --git a/src/collectors/apps.plugin/apps_os_linux.c b/src/collectors/apps.plugin/apps_os_linux.c
index bda64c1a6c..8ba1771072 100644
--- a/src/collectors/apps.plugin/apps_os_linux.c
+++ b/src/collectors/apps.plugin/apps_os_linux.c
@@ -93,7 +93,7 @@ bool apps_os_read_pid_fds_linux(struct pid_stat *p, void *ptr __maybe_unused) {
         if(unlikely(l == -1)) {
             // cannot read the link
 
-            if(debug_enabled || (p->target && p->target->debug_enabled))
+            if(debug_enabled)
                 netdata_log_error("Cannot read link %s", p->fds[fdid].filename);
 
             if(unlikely(p->fds[fdid].fd < 0)) {
@@ -689,7 +689,7 @@ bool apps_os_read_pid_stat_linux(struct pid_stat *p, void *ptr __maybe_unused) {
         }
     }
 
-    if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
+    if(unlikely(debug_enabled))
         debug_log_int("READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=" KERNEL_UINT_FORMAT,
                       netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET", p->stat_collected_usec - p->last_stat_collected_usec,
                       p->values[PDF_UTIME],
diff --git a/src/collectors/apps.plugin/apps_os_macos.c b/src/collectors/apps.plugin/apps_os_macos.c
index 746153ba5a..9e50246ab8 100644
--- a/src/collectors/apps.plugin/apps_os_macos.c
+++ b/src/collectors/apps.plugin/apps_os_macos.c
@@ -242,7 +242,7 @@ bool apps_os_read_pid_stat_macos(struct pid_stat *p, void *ptr) {
     // Note: Some values such as guest time, cutime, cstime, etc., are not directly available in MacOS.
     // You might need to approximate or leave them unset depending on your needs.
 
-    if(unlikely(debug_enabled || (p->target && p->target->debug_enabled))) {
+    if(unlikely(debug_enabled || p->target)) {
         debug_log_int("READ PROC/PID/STAT for MacOS: process: '%s' on target '%s' VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", threads=%d",
                       pid_stat_comm(p), (p->target) ? string2str(p->target->name) : "UNSET",
                       p->values[PDF_UTIME],
diff --git a/src/collectors/apps.plugin/apps_os_windows.c b/src/collectors/apps.plugin/apps_os_windows.c
index 65784682ee..38a552bb0d 100644
--- a/src/collectors/apps.plugin/apps_os_windows.c
+++ b/src/collectors/apps.plugin/apps_os_windows.c
@@ -451,6 +451,8 @@
 #include <tchar.h>
 #include <strsafe.h>
 
+WCHAR* GetProcessCommandLine(HANDLE hProcess);
+
 struct perflib_data {
     PERF_DATA_BLOCK *pDataBlock;
     PERF_OBJECT_TYPE *pObjectType;
@@ -458,34 +460,17 @@ struct perflib_data {
     DWORD pid;
 };
 
-BOOL EnableDebugPrivilege() {
-    HANDLE hToken;
-    LUID luid;
-    TOKEN_PRIVILEGES tkp;
-
-    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
-        return FALSE;
-
-    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
-        return FALSE;
-
-    tkp.PrivilegeCount = 1;
-    tkp.Privileges[0].Luid = luid;
-    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
-
-    if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
-        return FALSE;
-
-    CloseHandle(hToken);
-
-    return TRUE;
-}
-
 void apps_os_init_windows(void) {
     PerflibNamesRegistryInitialize();
 
-    if(!EnableDebugPrivilege())
-        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable debug privilege");
+    if(!EnableWindowsPrivilege(SE_DEBUG_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_DEBUG_NAME);
+
+    if(!EnableWindowsPrivilege(SE_SYSTEM_PROFILE_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SYSTEM_PROFILE_NAME);
+
+    if(!EnableWindowsPrivilege(SE_PROF_SINGLE_PROCESS_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_PROF_SINGLE_PROCESS_NAME);
 }
 
 uint64_t apps_os_get_total_memory_windows(void) {
@@ -500,10 +485,31 @@ uint64_t apps_os_get_total_memory_windows(void) {
     return memStat.ullTotalPhys;
 }
 
-static __thread wchar_t unicode[PATH_MAX];
+// remove the PID suffix and .exe suffix, if any
+static void fix_windows_comm(struct pid_stat *p, char *comm) {
+    char pid[UINT64_MAX_LENGTH + 1]; // +1 for the underscore
+    pid[0] = '_';
+    print_uint64(&pid[1], p->pid);
+    size_t pid_len = strlen(pid);
+    size_t comm_len = strlen(comm);
+    if (pid_len < comm_len) {
+        char *compare = &comm[comm_len - pid_len];
+        if (strcmp(pid, compare) == 0)
+            *compare = '\0';
+    }
+
+    // remove the .exe suffix, if any
+    comm_len = strlen(comm);
+    size_t exe_len = strlen(".exe");
+    if(exe_len < comm_len) {
+        char *compare = &comm[comm_len - exe_len];
+        if (strcmp(".exe", compare) == 0)
+            *compare = '\0';
+    }
+}
 
 // Convert wide string to UTF-8
-static STRING *wchar_to_string(WCHAR *s) {
+static char *wchar_to_utf8(WCHAR *s) {
     static __thread char utf8[PATH_MAX];
     static __thread int utf8_size = sizeof(utf8);
 
@@ -512,33 +518,152 @@ static STRING *wchar_to_string(WCHAR *s) {
         return NULL;
 
     WideCharToMultiByte(CP_UTF8, 0, s, -1, utf8, utf8_size, NULL, NULL);
-    return string_strdupz(utf8);
+    return utf8;
 }
 
-STRING *GetProcessFriendlyName(WCHAR *path) {
+// Convert wide string to UTF-8
+static STRING *wchar_to_string(WCHAR *s) {
+    return string_strdupz(wchar_to_utf8(s));
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// return a sanitized name for the process
+STRING *GetProcessFriendlyNameSanitized(WCHAR *path) {
     static __thread uint8_t void_buf[1024 * 1024];
+    static __thread DWORD void_buf_size = sizeof(void_buf);
+    static __thread wchar_t unicode[PATH_MAX];
+    static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
 
     DWORD handle;
     DWORD size = GetFileVersionInfoSizeW(path, &handle);
-    if (size == 0 || size > sizeof(void_buf))
+    if (size == 0 || size > void_buf_size)
         return FALSE;
 
     if (GetFileVersionInfoW(path, handle, size, void_buf)) {
         LPWSTR value = NULL;
         UINT len = 0;
-        DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
         if (VerQueryValueW(void_buf, L"\\StringFileInfo\\040904B0\\FileDescription", (LPVOID*)&value, &len) &&
             len > 0 && len < unicode_size) {
             wcsncpy(unicode, value, unicode_size - 1);
             unicode[unicode_size - 1] = L'\0';
-            return wchar_to_string(unicode);
+            char *name = wchar_to_utf8(unicode);
+            sanitize_chart_meta(name);
+            return string_strdupz(name);
         }
     }
 
     return NULL;
 }
 
+#define SERVICE_PREFIX "Service "
+// return a sanitized name for the process
+static STRING *GetNameFromCmdlineSanitized(struct pid_stat *p) {
+    if(!p->cmdline) return NULL;
+
+    char buf[string_strlen(p->cmdline) + 1];
+    memcpy(buf, string2str(p->cmdline), sizeof(buf));
+    char *words[100];
+    size_t num_words = quoted_strings_splitter(buf, words, 100, isspace_map_pluginsd);
+
+    if(string_strcmp(p->comm, "svchost") == 0) {
+        // find -s SERVICE in the command line
+        for(size_t i = 0; i < num_words ;i++) {
+            if(strcmp(words[i], "-s") == 0 && i + 1 < num_words) {
+                char service[strlen(words[i + 1]) + sizeof(SERVICE_PREFIX)]; // sizeof() includes a null
+                strcpy(service, SERVICE_PREFIX);
+                strcpy(&service[sizeof(SERVICE_PREFIX) - 1], words[i + 1]);
+                sanitize_chart_meta(service);
+                return string_strdupz(service);
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static void GetServiceNames(void) {
+    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
+    if (hSCManager == NULL) return;
+
+    DWORD dwBytesNeeded = 0, dwServicesReturned = 0, dwResumeHandle = 0;
+    ENUM_SERVICE_STATUS_PROCESS *pServiceStatus = NULL;
+
+    // First, query the required buffer size
+    EnumServicesStatusEx(
+            hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
+            NULL, 0, &dwBytesNeeded, &dwServicesReturned, &dwResumeHandle, NULL);
+
+    if (dwBytesNeeded == 0) {
+        CloseServiceHandle(hSCManager);
+        return;
+    }
+
+    // Allocate memory to hold the services
+    pServiceStatus = mallocz(dwBytesNeeded);
+
+    // Now, retrieve the list of services
+    if (!EnumServicesStatusEx(
+            hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
+            (LPBYTE)pServiceStatus, dwBytesNeeded, &dwBytesNeeded, &dwServicesReturned,
+            &dwResumeHandle, NULL)) {
+        freez(pServiceStatus);
+        CloseServiceHandle(hSCManager);
+        return;
+    }
+
+    // Loop through the services
+    for (DWORD i = 0; i < dwServicesReturned; i++) {
+        if(!pServiceStatus[i].lpDisplayName || !*pServiceStatus[i].lpDisplayName)
+            continue;
+
+        struct pid_stat *p = find_pid_entry((pid_t)pServiceStatus[i].ServiceStatusProcess.dwProcessId);
+        if(p && !p->got_service) {
+            p->got_service = true;
+
+            size_t len = strlen(pServiceStatus[i].lpDisplayName);
+            char buf[len + 1];
+            memcpy(buf, pServiceStatus[i].lpDisplayName, sizeof(buf));
+            sanitize_chart_meta(buf);
+
+            string_freez(p->name);
+            p->name = string_strdupz(buf);
+        }
+    }
+
+    free(pServiceStatus);
+    CloseServiceHandle(hSCManager);
+}
+
+static WCHAR *executable_path_from_cmdline(WCHAR *cmdline) {
+    if (!cmdline || !*cmdline) return NULL;
+
+    WCHAR *exe_path_start = cmdline;
+    WCHAR *exe_path_end = NULL;
+
+    if (cmdline[0] == L'"') {
+        // Command line starts with a double quote
+        exe_path_start++;  // Move past the first double quote
+        exe_path_end = wcschr(exe_path_start, L'"');  // Find the next quote
+    }
+    else {
+        // Command line does not start with a double quote
+        exe_path_end = wcschr(exe_path_start, L' ');  // Find the first space
+    }
+
+    if (exe_path_end) {
+        // Null-terminate the string at the end of the executable path
+        *exe_path_end = L'\0';
+        return exe_path_start;
+    }
+
+    return NULL;
+}
+
 void GetAllProcessesInfo(void) {
+    static __thread wchar_t unicode[PATH_MAX];
+    static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
+
     calls_counter++;
 
     HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@@ -552,45 +677,70 @@ void GetAllProcessesInfo(void) {
         return;
     }
 
+    bool need_service_names = false;
+
     do {
+        if(!pe32.th32ProcessID) continue;
+
         struct pid_stat *p = get_or_allocate_pid_entry((pid_t)pe32.th32ProcessID);
         p->ppid = (pid_t)pe32.th32ParentProcessID;
         if(p->got_info) continue;
         p->got_info = true;
 
-        if(!p->initialized) {
-            string_freez(p->comm);
-            p->comm = wchar_to_string(pe32.szExeFile);
-            p->assigned_to_target = false;
+        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, p->pid);
+        if (hProcess == NULL)
+            continue;
+
+        // Get the full command line, if possible
+        {
+            WCHAR *cmdline = GetProcessCommandLine(hProcess); // returns malloc'd buffer
+            if (cmdline) {
+                string_freez(p->cmdline);
+                p->cmdline = wchar_to_string(cmdline);
+
+                // extract the process full path from the command line
+                WCHAR *path = executable_path_from_cmdline(cmdline);
+                if(path) {
+                    string_freez(p->name);
+                    p->name = GetProcessFriendlyNameSanitized(path);
+                }
+
+                free(cmdline); // free(), not freez()
+            }
         }
 
-        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, p->pid);
-        if (hProcess == NULL) continue;
+        if(!p->cmdline || !p->name) {
+            if (QueryFullProcessImageNameW(hProcess, 0, unicode, &unicode_size)) {
+                // put the full path name to the command into cmdline
+                if(!p->cmdline)
+                    p->cmdline = wchar_to_string(unicode);
 
-        STRING *full_path = NULL;
-        STRING *friendly_name = NULL;
-
-        DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
-        if(QueryFullProcessImageNameW(hProcess, 0, unicode, &unicode_size)) {
-            full_path = wchar_to_string(unicode);
-            friendly_name = GetProcessFriendlyName(unicode);
+                if(!p->name)
+                    p->name = GetProcessFriendlyNameSanitized(unicode);
+            }
         }
 
         CloseHandle(hProcess);
 
-        if(full_path) {
-            string_freez(p->cmdline);
-            p->cmdline = full_path;
+        char *comm = wchar_to_utf8(pe32.szExeFile);
+        fix_windows_comm(p, comm);
+        update_pid_comm(p, comm); // will sanitize p->comm
+
+        if(!need_service_names && string_strcmp(p->comm, "svchost") == 0)
+            need_service_names = true;
+
+        STRING *better_name = GetNameFromCmdlineSanitized(p);
+        if(better_name) {
+            string_freez(p->name);
+            p->name = better_name;
         }
 
-        if(friendly_name) {
-            string_freez(p->name);
-            p->name = friendly_name;
-            p->assigned_to_target = false;
-        }
     } while (Process32NextW(hSnapshot, &pe32));
 
     CloseHandle(hSnapshot);
+
+    if(need_service_names)
+        GetServiceNames();
 }
 
 static inline kernel_uint_t perflib_cpu_utilization(COUNTER_DATA *d) {
@@ -692,40 +842,17 @@ bool apps_os_collect_all_pids_windows(void) {
             // a new pid
             p->initialized = true;
 
-            static __thread char name[MAX_PATH];
+            static __thread char comm[MAX_PATH];
 
-            if (getInstanceName(d.pDataBlock, d.pObjectType, d.pi, name, sizeof(name))) {
-                // remove the PID suffix, if any
-                char pid[UINT64_MAX_LENGTH + 1]; // +1 for the underscore
-                pid[0] = '_';
-                print_uint64(&pid[1], p->pid);
-                size_t pid_len = strlen(pid);
-                size_t name_len = strlen(name);
-                if (pid_len < name_len) {
-                    char *compare = &name[name_len - pid_len];
-                    if (strcmp(pid, compare) == 0)
-                        *compare = '\0';
-                }
-
-                // remove the .exe suffix, if any
-                name_len = strlen(name);
-                size_t exe_len = strlen(".exe");
-                if(exe_len < name_len) {
-                    char *compare = &name[name_len - exe_len];
-                    if (strcmp(".exe", compare) == 0)
-                        *compare = '\0';
-                }
-            }
+            if (getInstanceName(d.pDataBlock, d.pObjectType, d.pi, comm, sizeof(comm)))
+                fix_windows_comm(p, comm);
             else
-                strncpyz(name, "unknown", sizeof(name) - 1);
+                strncpyz(comm, "unknown", sizeof(comm) - 1);
 
-            if(strcmp(name, "wininit") == 0)
+            if(strcmp(comm, "wininit") == 0)
                 INIT_PID = p->pid;
 
-            string_freez(p->comm); // it may be detected in a previous run via GetAllProcessesInfo()
-            p->comm = string_strdupz(name);
-            p->got_info = false;
-            p->assigned_to_target = false;
+            update_pid_comm(p, comm); // will sanitize p->comm
             added++;
 
             COUNTER_DATA ppid = {.key = "Creating Process ID"};
diff --git a/src/collectors/apps.plugin/apps_os_windows_nt.c b/src/collectors/apps.plugin/apps_os_windows_nt.c
new file mode 100644
index 0000000000..6a853d65b2
--- /dev/null
+++ b/src/collectors/apps.plugin/apps_os_windows_nt.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// this must not include libnetdata.h because STRING is defined in winternl.h
+
+#include "config.h"
+#if defined(OS_WINDOWS)
+
+#include <windows.h>
+#include <winternl.h>
+#include <psapi.h>
+#include <stdint.h>
+
+// --------------------------------------------------------------------------------------------------------------------
+// Get the full windows command line
+
+WCHAR* GetProcessCommandLine(HANDLE hProcess) {
+    PROCESS_BASIC_INFORMATION pbi;
+    ULONG len;
+    NTSTATUS status = NtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), &len);
+    if (status != 0)
+        return NULL;
+
+    // The rest of the function remains the same as before
+    PEB peb;
+    if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL))
+        return NULL;
+
+    RTL_USER_PROCESS_PARAMETERS procParams;
+    if (!ReadProcessMemory(hProcess, peb.ProcessParameters, &procParams, sizeof(procParams), NULL))
+        return NULL;
+
+    WCHAR* commandLine = (WCHAR*)malloc(procParams.CommandLine.MaximumLength);
+    if (!commandLine)
+        return NULL;
+
+    if (!ReadProcessMemory(hProcess, procParams.CommandLine.Buffer, commandLine, procParams.CommandLine.MaximumLength, NULL)) {
+        free(commandLine);
+        return NULL;
+    }
+
+    return commandLine;
+}
+
+#endif
diff --git a/src/collectors/apps.plugin/apps_pid.c b/src/collectors/apps.plugin/apps_pid.c
index 03841cd800..768087cc16 100644
--- a/src/collectors/apps.plugin/apps_pid.c
+++ b/src/collectors/apps.plugin/apps_pid.c
@@ -134,6 +134,7 @@ void del_pid_entry(pid_t pid) {
     freez(p->fds);
 #endif
 
+    string_freez(p->comm_orig);
     string_freez(p->comm);
     string_freez(p->cmdline);
     aral_freez(pids.all_pids.aral, p);
@@ -316,7 +317,49 @@ static inline void link_all_processes_to_their_parents(void) {
 
 // --------------------------------------------------------------------------------------------------------------------
 
+static inline STRING *comm_from_cmdline_sanitized(char *comm, STRING *cmdline) {
+    if(!cmdline) {
+        sanitize_chart_meta(comm);
+        return string_strdupz(comm);
+    }
+
+    const char *cl = string2str(cmdline);
+    size_t len = string_strlen(cmdline);
+
+    char buf_cmd[len + 1];
+    // if it is enclosed in (), remove the parenthesis
+    if(cl[0] == '(' && cl[len - 1] == ')') {
+        memcpy(buf_cmd, &cl[1], len - 2);
+        buf_cmd[len - 2] = '\0';
+    }
+    else
+        memcpy(buf_cmd, cl, sizeof(buf_cmd));
+
+    size_t comm_len = strlen(comm);
+    char *start = strstr(buf_cmd, comm);
+    if(start) {
+        char *end = start + comm_len;
+        while(*end && !isspace((uint8_t)*end) && *end != '/' && *end != '\\' && *end != '"') end++;
+        *end = '\0';
+
+        sanitize_chart_meta(start);
+        return string_strdupz(start);
+    }
+
+    sanitize_chart_meta(comm);
+    return string_strdupz(comm);
+}
+
 void update_pid_comm(struct pid_stat *p, const char *comm) {
+    if(p->comm_orig && string_strcmp(p->comm_orig, comm) == 0)
+        // no change
+        return;
+
+#if (PROCESSES_HAVE_CMDLINE == 1)
+    if(likely(proc_pid_cmdline_is_needed && !p->cmdline))
+        managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));
+#endif
+
     // some process names have ( and ), remove the parenthesis
     size_t len = strlen(comm);
     char buf[len + 1];
@@ -327,22 +370,18 @@ void update_pid_comm(struct pid_stat *p, const char *comm) {
     else
         memcpy(buf, comm, sizeof(buf));
 
-    // check if the comm is changed
-    if(!p->comm || strcmp(pid_stat_comm(p), buf) != 0) {
-        // it is changed
+    string_freez(p->comm_orig);
+    p->comm_orig = string_strdupz(comm);
 
-        string_freez(p->comm);
-        p->comm = string_strdupz(buf);
+    string_freez(p->comm);
+    p->comm = comm_from_cmdline_sanitized(buf, p->cmdline);
 
-#if (PROCESSES_HAVE_CMDLINE == 1)
-        if(likely(proc_pid_cmdline_is_needed))
-            managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));
-#endif
+    p->is_manager = is_process_manager(p);
+    p->is_aggregator = is_process_aggregator(p);
 
-        // the process changes comm, we may have to reassign it to
-        // an apps_groups.conf target.
-        p->target = NULL;
-    }
+    // the process changed comm, we may have to reassign it to
+    // an apps_groups.conf target.
+    p->target = NULL;
 }
 
 // --------------------------------------------------------------------------------------------------------------------
diff --git a/src/collectors/apps.plugin/apps_plugin.c b/src/collectors/apps.plugin/apps_plugin.c
index 60f80c3c9b..8d4815be1e 100644
--- a/src/collectors/apps.plugin/apps_plugin.c
+++ b/src/collectors/apps.plugin/apps_plugin.c
@@ -121,6 +121,84 @@ size_t pagesize;
 // ----------------------------------------------------------------------------
 // update chart dimensions
 
+// Helper function to count the number of processes in the linked list
+int count_processes(struct pid_stat *root) {
+    int count = 0;
+
+    for(struct pid_stat *p = root; p ; p = p->next)
+        if(p->updated) count++;
+
+    return count;
+}
+
+// Comparator function to sort by pid
+int compare_by_pid(const void *a, const void *b) {
+    struct pid_stat *pa = *(struct pid_stat **)a;
+    struct pid_stat *pb = *(struct pid_stat **)b;
+    return ((int)pa->pid - (int)pb->pid);
+}
+
+// Function to print a process and its children recursively
+void print_process_tree(struct pid_stat *root, struct pid_stat *parent, int depth, int total_processes) {
+    // Allocate an array of pointers for processes with the given parent
+    struct pid_stat **children = (struct pid_stat **)malloc(total_processes * sizeof(struct pid_stat *));
+    int children_count = 0;
+
+    // Populate the array with processes that have the given parent
+    struct pid_stat *p = root;
+    while (p != NULL) {
+        if (p->updated && p->parent == parent) {
+            children[children_count++] = p;
+        }
+        p = p->next;
+    }
+
+    // Sort the children array by pid
+    qsort(children, children_count, sizeof(struct pid_stat *), compare_by_pid);
+
+    // Print each child and recurse
+    for (int i = 0; i < children_count; i++) {
+        // Print the current process with indentation based on depth
+        if (depth > 0) {
+            for (int j = 0; j < (depth - 1) * 4; j++) {
+                printf(" ");
+            }
+            printf(" \\_ ");
+        }
+
+#if (PROCESSES_HAVE_COMM_AND_NAME == 1)
+        printf("[%d] %s (name: %s) [%s]: %s\n", children[i]->pid,
+               string2str(children[i]->comm),
+               string2str(children[i]->name),
+               string2str(children[i]->target->name),
+               string2str(children[i]->cmdline));
+#else
+        printf("[%d] %s [%s]: %s\n", children[i]->pid,
+               string2str(children[i]->comm),
+               string2str(children[i]->target->name),
+               string2str(children[i]->cmdline));
+#endif
+
+        // Recurse to print this child's children
+        print_process_tree(root, children[i], depth + 1, total_processes);
+    }
+
+    // Free the allocated array
+    free(children);
+}
+
+// Function to print the full hierarchy
+void print_hierarchy(struct pid_stat *root) {
+    // Count the total number of processes
+    int total_processes = count_processes(root);
+
+    // Start printing from processes with parent = NULL (i.e., root processes)
+    print_process_tree(root, NULL, 0, total_processes);
+}
+
+// ----------------------------------------------------------------------------
+// update chart dimensions
+
 #if (ALL_PIDS_ARE_READ_INSTANTLY == 0)
 static void normalize_utilization(struct target *root) {
     struct target *w;
@@ -297,6 +375,7 @@ cleanup:
 }
 
 static bool profile_speed = false;
+static bool print_tree_and_exit = false;
 
 static void parse_args(int argc, char **argv)
 {
@@ -316,6 +395,11 @@ static void parse_args(int argc, char **argv)
             exit(0);
         }
 
+        if(strcmp("print", argv[i]) == 0 || strcmp("-print", argv[i]) == 0 || strcmp("--print", argv[i]) == 0) {
+            print_tree_and_exit = true;
+            continue;
+        }
+
 #if defined(OS_LINUX)
         if(strcmp("test-permissions", argv[i]) == 0 || strcmp("-t", argv[i]) == 0) {
             if(!check_proc_1_io()) {
@@ -618,7 +702,7 @@ int main(int argc, char **argv) {
     procfile_adaptive_initial_allocation = 1;
     os_get_system_HZ();
     os_get_system_cpus_uncached();
-    apps_orchestrators_and_aggregators_init(); // before parsing args!
+    apps_managers_and_aggregators_init(); // before parsing args!
     parse_args(argc, argv);
 
 #if !defined(OS_WINDOWS)
@@ -702,6 +786,11 @@ int main(int argc, char **argv) {
         normalize_utilization(apps_groups_root_target);
 #endif
 
+        if(unlikely(print_tree_and_exit)) {
+            print_hierarchy(root_of_pids());
+            exit(0);
+        }
+
         if(send_resource_usage)
             send_resource_usage_to_netdata(dt);
 
diff --git a/src/collectors/apps.plugin/apps_plugin.h b/src/collectors/apps.plugin/apps_plugin.h
index cc131c08fa..26f166b5a5 100644
--- a/src/collectors/apps.plugin/apps_plugin.h
+++ b/src/collectors/apps.plugin/apps_plugin.h
@@ -371,7 +371,10 @@ struct target {
 
     TARGET_TYPE type;
     union {
-        STRING *compare;
+        struct {
+            SIMPLE_PATTERN *pattern;
+            STRING *compare;
+        } ag;
 #if (PROCESSES_HAVE_UID == 1)
         uid_t uid;
 #endif
@@ -393,11 +396,8 @@ struct target {
 #endif
 
     bool exposed:1;         // if set, we have sent this to netdata
-    bool hidden:1;          // if set, we set the hidden flag on the dimension
-    bool debug_enabled:1;
-    bool ends_with:1;
-    bool starts_with:1;     // if set, the compare string matches only the
-                            // beginning of the command
+    bool ends_with:1;       // if set, the compare string matches the end of the command
+    bool starts_with:1;     // if set, the compare string matches the start of the command
 
     struct pid_on_target *root_pid; // list of aggregated pids for target debugging
 
@@ -476,7 +476,7 @@ struct pid_stat {
     struct pid_stat *next;
     struct pid_stat *prev;
 
-    struct target *target;          // app_groups.conf targets
+    struct target *target;          // app_groups.conf/tree targets
 
 #if (PROCESSES_HAVE_UID == 1)
     struct target *uid_target;      // uid based targets
@@ -485,9 +485,10 @@ struct pid_stat {
     struct target *gid_target;      // gid based targets
 #endif
 
-    STRING *comm;                   // the command name (short version)
-    STRING *name;                   // a better name, or NULL
-    STRING *cmdline;                // the full command line (or on windows, the full pathname of the program)
+    STRING *comm_orig;              // the command, as-collected
+    STRING *comm;                   // the command, sanitized
+    STRING *name;                   // the command name if any, sanitized
+    STRING *cmdline;                // the full command line of the program
 
 #if defined(OS_WINDOWS)
     COUNTER_DATA perflib[PDF_MAX];
@@ -531,6 +532,8 @@ struct pid_stat {
     bool updated:1;                 // true when the process is currently running
     bool merged:1;                  // true when it has been merged to its parent
     bool keep:1;                    // true when we need to keep this process in memory even after it exited
+    bool is_manager:1;              // true when this pid is a process manager
+    bool is_aggregator:1;           // true when this pid is a process aggregator
 
     bool matched_by_config:1;
 
@@ -540,7 +543,7 @@ struct pid_stat {
 
 #if defined(OS_WINDOWS)
     bool got_info:1;
-    bool assigned_to_target:1;
+    bool got_service:1;
     bool initialized:1;
 #endif
 
@@ -631,7 +634,7 @@ bool managed_log(struct pid_stat *p, PID_LOG log, bool status);
 #define pid_incremental_cpu(type, idx, value) \
     incremental_rate(p->values[idx], p->raw[idx], value, p->type##_collected_usec, p->last_##type##_collected_usec, CPU_TO_NANOSECONDCORES)
 
-void apps_orchestrators_and_aggregators_init(void);
+void apps_managers_and_aggregators_init(void);
 void apps_users_and_groups_init(void);
 void apps_pids_init(void);
 
@@ -675,6 +678,8 @@ struct pid_stat *find_pid_entry(pid_t pid);
 void del_pid_entry(pid_t pid);
 void update_pid_comm(struct pid_stat *p, const char *comm);
 
+bool is_process_manager(struct pid_stat *p);
+bool is_process_aggregator(struct pid_stat *p);
 
 // --------------------------------------------------------------------------------------------------------------------
 // targets management
diff --git a/src/collectors/apps.plugin/apps_targets.c b/src/collectors/apps.plugin/apps_targets.c
index c35eb1119e..6a680ad1f9 100644
--- a/src/collectors/apps.plugin/apps_targets.c
+++ b/src/collectors/apps.plugin/apps_targets.c
@@ -31,35 +31,7 @@ struct target *find_target_by_name(struct target *base, const char *name) {
 }
 
 // --------------------------------------------------------------------------------------------------------------------
-// Tree
-
-static inline STRING *comm_from_cmdline(STRING *comm, STRING *cmdline) {
-    if(!cmdline) return sanitize_chart_meta_string(comm);
-
-    const char *cl = string2str(cmdline);
-    size_t len = string_strlen(cmdline);
-
-    char buf_cmd[len + 1];
-    // if it is enclosed in (), remove the parenthesis
-    if(cl[0] == '(' && cl[len - 1] == ')') {
-        memcpy(buf_cmd, &cl[1], len - 2);
-        buf_cmd[len - 2] = '\0';
-    }
-    else
-        memcpy(buf_cmd, cl, sizeof(buf_cmd));
-
-    char *start = strstr(buf_cmd, string2str(comm));
-    if(start) {
-        char *end = start + string_strlen(comm);
-        while(*end && !isspace((uint8_t)*end) && *end != '/' && *end != '\\') end++;
-        *end = '\0';
-
-        sanitize_chart_meta(start);
-        return string_strdupz(start);
-    }
-
-    return sanitize_chart_meta_string(comm);
-}
+// Process managers and aggregators
 
 struct comm_list {
     STRING *comm;
@@ -111,21 +83,26 @@ static void managed_list_add(struct managed_list *list, const char *s) {
 
 static STRING *KernelAggregator = NULL;
 
-void apps_orchestrators_and_aggregators_init(void) {
+void apps_managers_and_aggregators_init(void) {
     KernelAggregator = string_strdupz("kernel");
 
     managed_list_clear(&tree.managers);
 #if defined(OS_LINUX)
-    managed_list_add(&tree.managers, "init");               // linux systems
-    managed_list_add(&tree.managers, "systemd");            // lxc containers and host systems (this also catches "systemd --user")
-    managed_list_add(&tree.managers, "containerd-shim");    // docker containers
-    managed_list_add(&tree.managers, "docker-init");        // docker containers
-    managed_list_add(&tree.managers, "dumb-init");          // some docker containers use this
-    managed_list_add(&tree.managers, "gnome-shell");        // gnome user applications
+    managed_list_add(&tree.managers, "init");                       // linux systems
+    managed_list_add(&tree.managers, "systemd");                    // lxc containers and host systems (this also catches "systemd --user")
+    managed_list_add(&tree.managers, "containerd-shim-runc-v2");    // docker containers
+    managed_list_add(&tree.managers, "docker-init");                // docker containers
+    managed_list_add(&tree.managers, "dumb-init");                  // some docker containers use this
+    managed_list_add(&tree.managers, "openrc-run.sh");              // openrc
+    managed_list_add(&tree.managers, "crond");                      // linux crond
+    managed_list_add(&tree.managers, "gnome-shell");                // gnome user applications
+    managed_list_add(&tree.managers, "plasmashell");                // kde user applications
+    managed_list_add(&tree.managers, "xfwm4");                      // xfce4 user applications
 #elif defined(OS_WINDOWS)
-    managed_list_add(&tree.managers, "System");
-    managed_list_add(&tree.managers, "services");
     managed_list_add(&tree.managers, "wininit");
+    managed_list_add(&tree.managers, "services");
+    managed_list_add(&tree.managers, "explorer");
+    managed_list_add(&tree.managers, "System");
 #elif defined(OS_FREEBSD)
     managed_list_add(&tree.managers, "init");
 #elif defined(OS_MACOS)
@@ -142,49 +119,52 @@ void apps_orchestrators_and_aggregators_init(void) {
 #endif
 }
 
-static inline bool is_orchestrator(struct pid_stat *p) {
+bool is_process_manager(struct pid_stat *p) {
     for(size_t c = 0; c < tree.managers.used ; c++) {
-        if(p->comm == tree.managers.array[c].comm)
+        if(p->comm == tree.managers.array[c].comm ||
+            p->comm_orig == tree.managers.array[c].comm)
             return true;
     }
 
     return false;
 }
 
-static inline bool is_aggregator(struct pid_stat *p) {
+bool is_process_aggregator(struct pid_stat *p) {
     for(size_t c = 0; c < tree.aggregators.used ; c++) {
-        if(p->comm == tree.aggregators.array[c].comm)
+        if(p->comm == tree.aggregators.array[c].comm ||
+            p->comm_orig == tree.aggregators.array[c].comm)
             return true;
     }
 
     return false;
 }
 
+// --------------------------------------------------------------------------------------------------------------------
+// Tree
+
 struct target *get_tree_target(struct pid_stat *p) {
 //    // skip fast all the children that are more than 3 levels down
 //    while(p->parent && p->parent->pid != INIT_PID && p->parent->parent && p->parent->parent->parent)
 //        p = p->parent;
 
     // keep the children of INIT_PID, and process orchestrators
-    while(p->parent && p->parent->pid != INIT_PID && p->parent->pid != 0 && !is_orchestrator(p->parent))
+    while(p->parent && p->parent->pid != INIT_PID && p->parent->pid != 0 && !p->parent->is_manager)
         p = p->parent;
 
     // merge all processes into process aggregators
-    STRING *search_for = string_dup(p->comm);
-    bool aggregator = false;
-    if((p->ppid == 0 && p->pid != INIT_PID) || (p->parent && is_aggregator(p->parent))) {
-        aggregator = true;
+    STRING *search_for = NULL;
+    if((p->ppid == 0 && p->pid != INIT_PID) || (p->parent && p->parent->is_aggregator)) {
         search_for = string_dup(KernelAggregator);
     }
-
-    if(!aggregator) {
+    else {
 #if (PROCESSES_HAVE_COMM_AND_NAME == 1)
-        search_for = sanitize_chart_meta_string(p->name ? p->name : p->comm);
+        search_for = string_dup(p->name ? p->name : p->comm);
 #else
-        search_for = comm_from_cmdline(p->comm, p->cmdline);
+        search_for = string_dup(p->comm);
 #endif
     }
 
+    // find an existing target with the required name
     struct target *w;
     for(w = apps_groups_root_target; w ; w = w->next) {
         if (w->name == search_for) {
@@ -196,7 +176,7 @@ struct target *get_tree_target(struct pid_stat *p) {
     w = callocz(sizeof(struct target), 1);
     w->type = TARGET_TYPE_TREE;
     w->starts_with = w->ends_with = false;
-    w->compare = string_dup(p->comm);
+    w->ag.compare = string_dup(search_for);
     w->id = search_for;
     w->name = string_dup(search_for);
     w->clean_name = get_clean_name(w->name);
@@ -302,17 +282,17 @@ struct target *apps_groups_root_target = NULL;
 
 // find or create a new target
 // there are targets that are just aggregated to other target (the second argument)
-static struct target *get_apps_groups_target(const char *id, struct target *target, const char *name) {
-    bool tdebug = false, thidden = target ? target->hidden : false, ends_with = false, starts_with = false;
+static struct target *get_apps_groups_target(const char *comm, struct target *target, const char *name) {
+    bool ends_with = false, starts_with = false, has_asterisk_inside = false;
 
-    STRING *id_lookup = NULL;
+    STRING *comm_lookup = NULL;
     STRING *name_lookup = NULL;
 
     // extract the options from the id
     {
-        size_t len = strlen(id);
+        size_t len = strlen(comm);
         char buf[len + 1];
-        memcpy(buf, id, sizeof(buf));
+        memcpy(buf, comm, sizeof(buf));
 
         if(buf[len - 1] == '*') {
             buf[--len] = '\0';
@@ -320,37 +300,25 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
         }
 
         const char *nid = buf;
-        while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') {
-            if (nid[0] == '-') thidden = true;
-            if (nid[0] == '+') tdebug = true;
-            if (nid[0] == '*') ends_with = true;
+        if (nid[0] == '*') {
+            ends_with = true;
             nid++;
         }
 
-        id_lookup = string_strdupz(nid);
+        if(strchr(nid, '*'))
+            has_asterisk_inside = true;
+
+        comm_lookup = string_strdupz(nid);
     }
 
     // extract the options from the name
-    {
-        size_t len = strlen(name);
-        char buf[len + 1];
-        memcpy(buf, name, sizeof(buf));
-
-        const char *nn = buf;
-        while (nn[0] == '-' || nn[0] == '+') {
-            if (nn[0] == '-') thidden = true;
-            if (nn[0] == '+') tdebug = true;
-            nn++;
-        }
-
-        name_lookup = string_strdupz(nn);
-    }
+    name_lookup = string_strdupz(name);
 
     // find if it already exists
     struct target *w, *last = apps_groups_root_target;
     for(w = apps_groups_root_target ; w ; w = w->next) {
-        if(w->id == id_lookup) {
-            string_freez(id_lookup);
+        if(w->id == comm_lookup) {
+            string_freez(comm_lookup);
             string_freez(name_lookup);
             return w;
         }
@@ -368,19 +336,22 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
 
     if(target && target->target)
         fatal("Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'",
-              id, string2str(target->id), string2str(target->target->id));
+            comm, string2str(target->id), string2str(target->target->id));
 
     w = callocz(sizeof(struct target), 1);
     w->type = TARGET_TYPE_APP_GROUP;
-    w->compare = string_dup(id_lookup);
+    w->ag.compare = string_dup(comm_lookup);
     w->starts_with = starts_with;
     w->ends_with = ends_with;
-    w->id = string_dup(id_lookup);
+    w->id = string_dup(comm_lookup);
+
+    if(has_asterisk_inside)
+        w->ag.pattern = simple_pattern_create(comm, " ", SIMPLE_PATTERN_EXACT, true);
 
     if(unlikely(!target))
         w->name = string_dup(name_lookup); // copy the name
     else
-        w->name = string_dup(id_lookup); // copy the id
+        w->name = string_dup(comm_lookup); // copy the id
 
     // dots are used to distinguish chart type and id in streaming, so we should replace them
     w->clean_name = get_clean_name(w->name);
@@ -388,29 +359,20 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
     if(w->starts_with && w->ends_with)
         proc_pid_cmdline_is_needed = true;
 
-    w->hidden = thidden;
-#ifdef NETDATA_INTERNAL_CHECKS
-    w->debug_enabled = tdebug;
-#else
-    if(tdebug)
-        fprintf(stderr, "apps.plugin has been compiled without debugging\n");
-#endif
     w->target = target;
 
     // append it, to maintain the order in apps_groups.conf
     if(last) last->next = w;
     else apps_groups_root_target = w;
 
-    debug_log("ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s"
+    debug_log("ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s'"
               , string2str(w->id)
-              , string2str(w->compare)
+              , string2str(w->ag.compare)
               , (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
               , w->target?w->target->name:w->name
-              , (w->hidden)?"hidden":"-"
-              , (w->debug_enabled)?"debug":"-"
     );
 
-    string_freez(id_lookup);
+    string_freez(comm_lookup);
     string_freez(name_lookup);
 
     return w;
diff --git a/src/collectors/windows-events.plugin/windows-events.c b/src/collectors/windows-events.plugin/windows-events.c
index 29881c039f..24f8f59a59 100644
--- a/src/collectors/windows-events.plugin/windows-events.c
+++ b/src/collectors/windows-events.plugin/windows-events.c
@@ -1139,6 +1139,15 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
     sid_cache_init();
     field_cache_init();
 
+    if(!EnableWindowsPrivilege(SE_SECURITY_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SECURITY_NAME);
+
+    if(!EnableWindowsPrivilege(SE_BACKUP_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_BACKUP_NAME);
+
+    if(!EnableWindowsPrivilege(SE_AUDIT_NAME))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_AUDIT_NAME);
+
     // ------------------------------------------------------------------------
     // debug
 
diff --git a/src/health/health_notifications.c b/src/health/health_notifications.c
index 85dd2d0d8b..443c0246f0 100644
--- a/src/health/health_notifications.c
+++ b/src/health/health_notifications.c
@@ -20,17 +20,27 @@ struct health_raised_summary {
 };
 
 void health_alarm_wait_for_execution(ALARM_ENTRY *ae) {
-    if (!(ae->flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS))
-        return;
+    // this has to ALWAYS remove the given alarm entry from the queue
 
-    if(!ae->popen_instance) {
-        // nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not spawn a notification");
-        return;
+    int code = 0;
+
+    if (!(ae->flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS)) {
+        nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not an execution in progress");
+        code = 128;
+        goto cleanup;
     }
 
-    ae->exec_code = spawn_popen_wait(ae->popen_instance);
+    if(!ae->popen_instance) {
+        nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not spawn a notification");
+        code = 128;
+        goto cleanup;
+    }
 
+    code = spawn_popen_wait(ae->popen_instance);
     netdata_log_debug(D_HEALTH, "done executing command - returned with code %d", ae->exec_code);
+
+cleanup:
+    ae->exec_code = code;
     ae->flags &= ~HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
 
     if(ae->exec_code != 0)
@@ -466,13 +476,18 @@ void health_send_notification(RRDHOST *host, ALARM_ENTRY *ae, struct health_rais
         ae->exec_run_timestamp = now_realtime_sec(); /* will be updated by real time after spawning */
 
         netdata_log_debug(D_HEALTH, "executing command '%s'", command_to_run);
-        ae->flags |= HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
         ae->popen_instance = spawn_popen_run(command_to_run);
-        enqueue_alarm_notify_in_progress(ae);
+        if(ae->popen_instance) {
+            ae->flags |= HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
+            enqueue_alarm_notify_in_progress(ae);
+        }
+        else
+            netdata_log_error("Failed to execute alarm notification");
+
         health_alarm_log_save(host, ae);
-    } else {
-        netdata_log_error("Failed to format command arguments");
     }
+    else
+        netdata_log_error("Failed to format command arguments");
 
     buffer_free(warn_alarms);
     buffer_free(crit_alarms);
diff --git a/src/libnetdata/os/os-windows-wrappers.c b/src/libnetdata/os/os-windows-wrappers.c
index 64076eae28..161f2aefd0 100644
--- a/src/libnetdata/os/os-windows-wrappers.c
+++ b/src/libnetdata/os/os-windows-wrappers.c
@@ -58,4 +58,42 @@ bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, cha
     return status;
 }
 
+bool EnableWindowsPrivilege(const char *privilegeName) {
+    HANDLE hToken;
+    LUID luid;
+    TOKEN_PRIVILEGES tkp;
+
+    // Open the process token with appropriate access rights
+    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
+        return false;
+
+    // Lookup the LUID for the specified privilege
+    if (!LookupPrivilegeValue(NULL, privilegeName, &luid)) {
+        CloseHandle(hToken);  // Close the token handle before returning
+        return false;
+    }
+
+    // Set up the TOKEN_PRIVILEGES structure
+    tkp.PrivilegeCount = 1;
+    tkp.Privileges[0].Luid = luid;
+    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+    // Adjust the token's privileges
+    if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
+        CloseHandle(hToken);  // Close the token handle before returning
+        return false;
+    }
+
+    // Check if AdjustTokenPrivileges succeeded
+    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
+        CloseHandle(hToken);  // Close the token handle before returning
+        return false;
+    }
+
+    // Close the handle to the token after success
+    CloseHandle(hToken);
+
+    return true;
+}
+
 #endif
diff --git a/src/libnetdata/os/os-windows-wrappers.h b/src/libnetdata/os/os-windows-wrappers.h
index 5ae73043a3..30e1fc50d2 100644
--- a/src/libnetdata/os/os-windows-wrappers.h
+++ b/src/libnetdata/os/os-windows-wrappers.h
@@ -14,5 +14,7 @@ bool netdata_registry_get_dword(unsigned int *out, void *hKey, char *subKey, cha
 long netdata_registry_get_string_from_open_key(char *out, unsigned int length, void *lKey, char *name);
 bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, char *subKey, char *name);
 
+bool EnableWindowsPrivilege(const char *privilegeName);
+
 #endif // OS_WINDOWS
 #endif //NETDATA_OS_WINDOWS_WRAPPERS_H
diff --git a/src/libnetdata/spawn_server/spawn_server_windows.c b/src/libnetdata/spawn_server/spawn_server_windows.c
index 09218568c7..8c7d76cd24 100644
--- a/src/libnetdata/spawn_server/spawn_server_windows.c
+++ b/src/libnetdata/spawn_server/spawn_server_windows.c
@@ -54,7 +54,7 @@ static BUFFER *argv_to_windows(const char **argv) {
     BUFFER *wb = buffer_create(0, NULL);
 
     // argv[0] is the path
-    char b[strlen(argv[0]) * 2 + 1024];
+    char b[strlen(argv[0]) * 2 + FILENAME_MAX];
     cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
 
     for(size_t i = 0; argv[i] ;i++) {
@@ -84,6 +84,8 @@ static BUFFER *argv_to_windows(const char **argv) {
             else
                 buffer_putc(wb, ' ');
         }
+        else if (needs_quotes)
+            buffer_putc(wb, '"');
 
         for(const char *c = s; *c ; c++) {
             switch(*c) {