diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 33ae3aef9fca..91beed1130eb 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -4,6 +4,7 @@ use strict;
 use warnings;
 use File::Basename;
 use File::Slurp;
+use Net::DBus;
 use Sys::Syslog qw(:standard :macros);
 use Cwd 'abs_path';
 
@@ -59,17 +60,18 @@ syslog(LOG_NOTICE, "switching to system configuration $out");
 # virtual console 1 and we restart the "tty1" unit.
 $SIG{PIPE} = "IGNORE";
 
+my $dbus = Net::DBus->find;
+my $systemdService = $dbus->get_service('org.freedesktop.systemd1');
+my $systemdManager = $systemdService->get_object('/org/freedesktop/systemd1');
+
 sub getActiveUnits {
-    # FIXME: use D-Bus or whatever to query this, since parsing the
-    # output of list-units is likely to break.
-    my $lines = `@systemd@/bin/systemctl list-units --full`;
     my $res = {};
-    foreach my $line (split '\n', $lines) {
-        chomp $line;
-        last if $line eq "";
-        $line =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s/ or next;
-        next if $1 eq "UNIT";
-        $res->{$1} = { load => $2, state => $3, substate => $4 };
+    foreach my $unit (@{ $systemdManager->ListUnits() }) {
+        $res->{$unit->[0]} = {
+            load => $unit->[2],
+            state => $unit->[3],
+            substate => $unit->[4]
+        };
     }
     return $res;
 }
@@ -277,7 +279,7 @@ foreach my $device (keys %$prevSwaps) {
 if (scalar @unitsToStop > 0) {
     @unitsToStop = unique(@unitsToStop);
     print STDERR "stopping the following units: ", join(", ", sort(@unitsToStop)), "\n";
-    system("@systemd@/bin/systemctl", "stop", "--", @unitsToStop); # FIXME: ignore errors?
+    $systemdManager->StopUnit($_, "replace") for @unitsToStop;
 }
 
 print STDERR "NOT restarting the following units: ", join(", ", sort(@unitsToSkip)), "\n"
@@ -292,21 +294,22 @@ system("$out/activate", "$out") == 0 or $res = 2;
 # Restart systemd if necessary.
 if (abs_path("/proc/1/exe") ne abs_path("@systemd@/lib/systemd/systemd")) {
     print STDERR "restarting systemd...\n";
-    system("@systemd@/bin/systemctl", "daemon-reexec") == 0 or $res = 2;
+
+    $systemdManager->Reexecute();
 }
 
 # Forget about previously failed services.
-system("@systemd@/bin/systemctl", "reset-failed");
+$systemdManager->ResetFailed();
 
-# Make systemd reload its units.
-system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
+# Make systemd reload its units
+$systemdManager->Reload();
 
 # Restart changed services (those that have to be restarted rather
 # than stopped and started).
 my @restart = unique(split('\n', read_file($restartListFile, err_mode => 'quiet') // ""));
 if (scalar @restart > 0) {
     print STDERR "restarting the following units: ", join(", ", sort(@restart)), "\n";
-    system("@systemd@/bin/systemctl", "restart", "--", @restart) == 0 or $res = 4;
+    $systemdManager->Restart($_, "replace") for @restart;
     unlink($restartListFile);
 }
 
@@ -318,7 +321,7 @@ if (scalar @restart > 0) {
 # systemd.
 my @start = unique("default.target", "timers.target", split('\n', read_file($startListFile, err_mode => 'quiet') // ""));
 print STDERR "starting the following units: ", join(", ", sort(@start)), "\n";
-system("@systemd@/bin/systemctl", "start", "--", @start) == 0 or $res = 4;
+$systemdManager->StartUnit($_, "replace") for @start;
 unlink($startListFile);
 
 # Reload units that need it.  This includes remounting changed mount
@@ -326,19 +329,31 @@ unlink($startListFile);
 my @reload = unique(split '\n', read_file($reloadListFile, err_mode => 'quiet') // "");
 if (scalar @reload > 0) {
     print STDERR "reloading the following units: ", join(", ", sort(@reload)), "\n";
-    system("@systemd@/bin/systemctl", "reload", "--", @reload) == 0 or $res = 4;
+    $systemdManager->ReloadUnit($_, "replace") for @reload;
     unlink($reloadListFile);
 }
 
 # Signal dbus to reload its configuration.
-system("@systemd@/bin/systemctl", "reload", "dbus.service");
+$systemdManager->ReloadUnit("dbus.service", "replace");
 
 # Print failed and new units.
 my (@failed, @new, @restarting);
 my $activeNew = getActiveUnits;
 while (my ($unit, $state) = each %{$activeNew}) {
-    push @failed, $unit if $state->{state} eq "failed" || $state->{substate} eq "auto-restart";
-    push @new, $unit if $state->{state} ne "failed" && !defined $activePrev->{$unit};
+    if ($state->{state} eq "failed") {
+        push @failed, $unit;
+    }
+    elsif ($state->{state} eq "auto-restart") {
+        # A unit in auto-restart state is a failure *if* it previously failed to start
+        my $unit = $systemdManager->GetUnit($unit);
+
+        if ($unit->ExecMainStatus ne '0') {
+            push @failed, $unit;
+        }
+    }
+    elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit}) {
+        push @new, $unit;
+    }
 }
 
 print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n"
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index d9891f434cc8..30a529988a96 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -108,7 +108,7 @@ let
     configurationName = config.boot.loader.grub.configurationName;
 
     # Needed by switch-to-configuration.
-    perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl";
+    perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.XMLTwig}/lib/perl5/site_perl -I${pkgs.perlPackages.XMLParser}/lib/perl5/site_perl -I${pkgs.perlPackages.NetDBus}/lib/perl5/site_perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl";
   };