diff --git a/src/HASS.Agent.Staging/HASS.Agent.Shared/HomeAssistant/Commands/PowershellCommand.cs b/src/HASS.Agent.Staging/HASS.Agent.Shared/HomeAssistant/Commands/PowershellCommand.cs
index e9076055..187cade1 100644
--- a/src/HASS.Agent.Staging/HASS.Agent.Shared/HomeAssistant/Commands/PowershellCommand.cs
+++ b/src/HASS.Agent.Staging/HASS.Agent.Shared/HomeAssistant/Commands/PowershellCommand.cs
@@ -45,7 +45,7 @@ public override void TurnOn()
}
var executed = _isScript
- ? PowershellManager.ExecuteScriptHeadless(Command)
+ ? PowershellManager.ExecuteScriptHeadless(Command, string.Empty)
: PowershellManager.ExecuteCommandHeadless(Command);
if (!executed) Log.Error("[POWERSHELL] [{name}] Executing {descriptor} failed", Name, _descriptor, Name);
@@ -57,12 +57,9 @@ public override void TurnOnWithAction(string action)
{
State = "ON";
- // prepare command
- var command = string.IsNullOrWhiteSpace(Command) ? action : $"{Command} {action}";
-
var executed = _isScript
- ? PowershellManager.ExecuteScriptHeadless(command)
- : PowershellManager.ExecuteCommandHeadless(command);
+ ? PowershellManager.ExecuteScriptHeadless(Command, action)
+ : PowershellManager.ExecuteCommandHeadless(Command);
if (!executed) Log.Error("[POWERSHELL] [{name}] Launching PS {descriptor} with action '{action}' failed", Name, _descriptor, action);
diff --git a/src/HASS.Agent.Staging/HASS.Agent.Shared/Managers/PowershellManager.cs b/src/HASS.Agent.Staging/HASS.Agent.Shared/Managers/PowershellManager.cs
index 869873a8..576823dc 100644
--- a/src/HASS.Agent.Staging/HASS.Agent.Shared/Managers/PowershellManager.cs
+++ b/src/HASS.Agent.Staging/HASS.Agent.Shared/Managers/PowershellManager.cs
@@ -3,273 +3,321 @@
using System.Globalization;
using System.IO;
using System.Text;
+using CliWrap;
+using Newtonsoft.Json;
using Serilog;
namespace HASS.Agent.Shared.Managers
{
- ///
- /// Performs powershell-related actions
- ///
- public static class PowershellManager
- {
- ///
- /// Execute a Powershell command without waiting for or checking results
- ///
- ///
- ///
- public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, false);
-
- ///
- /// Executes a Powershell script without waiting for or checking results
- ///
- ///
- ///
- public static bool ExecuteScriptHeadless(string script) => ExecuteHeadless(script, true);
-
- private static bool ExecuteHeadless(string command, bool isScript)
- {
- var descriptor = isScript ? "script" : "command";
-
- try
- {
- var workingDir = string.Empty;
- if (isScript)
- {
- // try to get the script's startup path
- var scriptDir = Path.GetDirectoryName(command);
- workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
- }
-
- // find the powershell executable
- var psExec = GetPsExecutable();
- if (string.IsNullOrEmpty(psExec)) return false;
-
- // prepare the executing process
- var processInfo = new ProcessStartInfo
- {
- WindowStyle = ProcessWindowStyle.Hidden,
- CreateNoWindow = true,
- FileName = psExec,
- WorkingDirectory = workingDir
- };
-
- // set the right type of arguments
- processInfo.Arguments = isScript ?
- $@"& '{command}'"
- : $@"& {{{command}}}";
-
- // launch
- using var process = new Process();
- process.StartInfo = processInfo;
- var start = process.Start();
-
- if (!start)
- {
- Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command);
- return false;
- }
-
- // done
- return true;
- }
- catch (Exception ex)
- {
- Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command);
- return false;
- }
- }
-
- ///
- /// Execute a Powershell command, logs the output if it fails
- ///
- ///
- ///
- ///
- public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, false, timeout);
-
- ///
- /// Executes a Powershell script, logs the output if it fails
- ///
- ///
- ///
- ///
- public static bool ExecuteScript(string script, TimeSpan timeout) => Execute(script, true, timeout);
-
- private static bool Execute(string command, bool isScript, TimeSpan timeout)
- {
- var descriptor = isScript ? "script" : "command";
-
- try
- {
- var workingDir = string.Empty;
- if (isScript)
- {
- // try to get the script's startup path
- var scriptDir = Path.GetDirectoryName(command);
- workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
- }
-
- // find the powershell executable
- var psExec = GetPsExecutable();
- if (string.IsNullOrEmpty(psExec)) return false;
-
- // prepare the executing process
- var processInfo = new ProcessStartInfo
- {
- FileName = psExec,
- RedirectStandardError = true,
- RedirectStandardOutput = true,
- UseShellExecute = false,
- WorkingDirectory = workingDir,
- // set the right type of arguments
- Arguments = isScript
- ? $@"& '{command}'"
- : $@"& {{{command}}}"
- };
-
- // launch
- using var process = new Process();
- process.StartInfo = processInfo;
- var start = process.Start();
-
- if (!start)
- {
- Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command);
- return false;
- }
-
- // execute and wait
- process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds));
-
- if (process.ExitCode == 0)
- {
- // done, all good
- return true;
- }
-
- // non-zero exitcode, process as failed
- Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode);
-
- var errors = process.StandardError.ReadToEnd().Trim();
- if (!string.IsNullOrEmpty(errors)) Log.Error("[POWERSHELL] Error output:\r\n{output}", errors);
- else
- {
- var console = process.StandardOutput.ReadToEnd().Trim();
- if (!string.IsNullOrEmpty(console)) Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors);
- else Log.Error("[POWERSHELL] No error and no console output");
- }
-
- // done
- return false;
- }
- catch (Exception ex)
- {
- Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command);
- return false;
- }
- }
-
- ///
- /// Executes the command or script, and returns the standard and error output
- ///
- ///
- ///
- ///
- ///
- ///
- internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors)
- {
- output = string.Empty;
- errors = string.Empty;
-
- try
- {
- // check whether we're executing a script
- var isScript = command.ToLower().EndsWith(".ps1");
-
- var workingDir = string.Empty;
- if (isScript)
- {
- // try to get the script's startup path
- var scriptDir = Path.GetDirectoryName(command);
- workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
- }
-
- // find the powershell executable
- var psExec = GetPsExecutable();
- if (string.IsNullOrEmpty(psExec)) return false;
-
- // prepare the executing process
- var processInfo = new ProcessStartInfo
- {
- FileName = psExec,
- RedirectStandardError = true,
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- WorkingDirectory = workingDir,
- // attempt to set the right encoding
- StandardOutputEncoding = Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage),
- StandardErrorEncoding = Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage),
- // set the right type of arguments
- Arguments = isScript
- ? $@"& '{command}'"
- : $@"& {{{command}}}"
- };
-
- // execute and wait
- using var process = new Process();
- process.StartInfo = processInfo;
-
- var start = process.Start();
- if (!start)
- {
- Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command);
- return false;
- }
-
- // wait for completion
- var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds));
- if (!completed) Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command);
-
- // read the streams
- output = process.StandardOutput.ReadToEnd().Trim();
- errors = process.StandardError.ReadToEnd().Trim();
-
- // dispose of them
- process.StandardOutput.Dispose();
- process.StandardError.Dispose();
-
- // make sure the process ends
- process.Kill();
-
- // done
- return completed;
- }
- catch (Exception ex)
- {
- Log.Fatal(ex, ex.Message);
- return false;
- }
- }
-
- ///
- /// Attempt to locate powershell.exe
- ///
- ///
- public static string GetPsExecutable()
- {
- // try regular location
- var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe");
- if (File.Exists(psExec)) return psExec;
-
- // try specific
- psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe");
- if (File.Exists(psExec)) return psExec;
-
- // not found
- Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system");
- return string.Empty;
- }
- }
+ ///
+ /// Performs Powershell-related actions
+ ///
+ public static class PowershellManager
+ {
+ ///
+ /// Execute a Powershell command without waiting for or checking results
+ ///
+ ///
+ ///
+ public static bool ExecuteCommandHeadless(string command) => ExecuteHeadless(command, string.Empty, false);
+
+ ///
+ /// Executes a Powershell script without waiting for or checking results
+ ///
+ ///
+ ///
+ ///
+ public static bool ExecuteScriptHeadless(string script, string parameters) => ExecuteHeadless(script, parameters, true);
+
+ private static string GetProcessArguments(string command, string parameters, bool isScript)
+ {
+ if (isScript)
+ {
+ return string.IsNullOrWhiteSpace(parameters)
+ ? $"-File \"{command}\""
+ : $"-File \"{command}\" \"{parameters}\"";
+ }
+ else
+ {
+ return $@"& {{{command}}}"; //NOTE: place to fix any potential future issues with "command part of the command"
+ }
+ }
+
+ private static bool ExecuteHeadless(string command, string parameters, bool isScript)
+ {
+ var descriptor = isScript ? "script" : "command";
+
+ try
+ {
+ var workingDir = string.Empty;
+ if (isScript)
+ {
+ // try to get the script's startup path
+ var scriptDir = Path.GetDirectoryName(command);
+ workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
+ }
+
+ var psExec = GetPsExecutable();
+ if (string.IsNullOrEmpty(psExec))
+ return false;
+
+ var processInfo = new ProcessStartInfo
+ {
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+ FileName = psExec,
+ WorkingDirectory = workingDir,
+ Arguments = GetProcessArguments(command, parameters, isScript)
+ };
+
+ using var process = new Process();
+ process.StartInfo = processInfo;
+ var start = process.Start();
+
+ if (!start)
+ {
+ Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {command}", descriptor, command);
+
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command);
+
+ return false;
+ }
+ }
+
+ ///
+ /// Execute a Powershell command, logs the output if it fails
+ ///
+ ///
+ ///
+ ///
+ public static bool ExecuteCommand(string command, TimeSpan timeout) => Execute(command, string.Empty, false, timeout);
+
+ ///
+ /// Executes a Powershell script, logs the output if it fails
+ ///
+ ///
+ ///
+ ///
+ public static bool ExecuteScript(string script, string parameters, TimeSpan timeout) => Execute(script, parameters, true, timeout);
+
+ private static bool Execute(string command, string parameters, bool isScript, TimeSpan timeout)
+ {
+ var descriptor = isScript ? "script" : "command";
+
+ try
+ {
+ var workingDir = string.Empty;
+ if (isScript)
+ {
+ // try to get the script's startup path
+ var scriptDir = Path.GetDirectoryName(command);
+ workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
+ }
+
+ var psExec = GetPsExecutable();
+ if (string.IsNullOrEmpty(psExec)) return false;
+
+ var processInfo = new ProcessStartInfo
+ {
+ FileName = psExec,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ WorkingDirectory = workingDir,
+ Arguments = GetProcessArguments(command, parameters, isScript)
+ };
+
+ using var process = new Process();
+ process.StartInfo = processInfo;
+ var start = process.Start();
+
+ if (!start)
+ {
+ Log.Error("[POWERSHELL] Unable to start processing {descriptor}: {script}", descriptor, command);
+
+ return false;
+ }
+
+ process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds));
+
+ if (process.ExitCode == 0)
+ return true;
+
+ // non-zero exitcode, process as failed
+ Log.Error("[POWERSHELL] The {descriptor} returned non-zero exitcode: {code}", descriptor, process.ExitCode);
+
+ var errors = process.StandardError.ReadToEnd().Trim();
+ if (!string.IsNullOrEmpty(errors))
+ {
+ Log.Error("[POWERSHELL] Error output:\r\n{output}", errors);
+ }
+ else
+ {
+ var console = process.StandardOutput.ReadToEnd().Trim();
+ if (!string.IsNullOrEmpty(console))
+ Log.Error("[POWERSHELL] No error output, console output:\r\n{output}", errors);
+ else
+ Log.Error("[POWERSHELL] No error and no console output");
+ }
+
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "[POWERSHELL] Fatal error when executing {descriptor}: {command}", descriptor, command);
+
+ return false;
+ }
+ }
+
+ private static Encoding TryParseCodePage(int codePage)
+ {
+ Encoding encoding = null;
+ try
+ {
+ encoding = Encoding.GetEncoding(codePage);
+ }
+ catch
+ {
+ // best effort
+ }
+
+ return encoding;
+ }
+
+ private static Encoding GetEncoding()
+ {
+ var encoding = TryParseCodePage(CultureInfo.InstalledUICulture.TextInfo.OEMCodePage);
+ if (encoding != null)
+ return encoding;
+
+ encoding = TryParseCodePage(CultureInfo.CurrentCulture.TextInfo.OEMCodePage);
+ if (encoding != null)
+ return encoding;
+
+ encoding = TryParseCodePage(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage);
+ if (encoding != null)
+ return encoding;
+
+ encoding = TryParseCodePage(CultureInfo.InvariantCulture.TextInfo.OEMCodePage);
+ if (encoding != null)
+ return encoding;
+
+ Log.Warning("[POWERSHELL] Cannot parse system text culture to encoding, returning UTF-8 as a fallback, please report this as a GitHub issue");
+
+ Log.Debug("[POWERSHELL] currentInstalledUICulture {c}", JsonConvert.SerializeObject(CultureInfo.InstalledUICulture.TextInfo));
+ Log.Debug("[POWERSHELL] currentCulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentCulture.TextInfo));
+ Log.Debug("[POWERSHELL] currentUICulture {c}", JsonConvert.SerializeObject(CultureInfo.CurrentUICulture.TextInfo));
+ Log.Debug("[POWERSHELL] invariantCulture {c}", JsonConvert.SerializeObject(CultureInfo.InvariantCulture.TextInfo));
+
+ return Encoding.UTF8;
+ }
+
+ ///
+ /// Executes the command or script, and returns the standard and error output
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static bool ExecuteWithOutput(string command, TimeSpan timeout, out string output, out string errors)
+ {
+ output = string.Empty;
+ errors = string.Empty;
+
+ try
+ {
+ var isScript = command.ToLower().EndsWith(".ps1");
+
+ var workingDir = string.Empty;
+ if (isScript)
+ {
+ // try to get the script's startup path
+ var scriptDir = Path.GetDirectoryName(command);
+ workingDir = !string.IsNullOrEmpty(scriptDir) ? scriptDir : string.Empty;
+ }
+
+ var psExec = GetPsExecutable();
+ if (string.IsNullOrEmpty(psExec))
+ return false;
+
+ var encoding = GetEncoding();
+
+ var processInfo = new ProcessStartInfo
+ {
+ FileName = psExec,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WorkingDirectory = workingDir,
+ StandardOutputEncoding = encoding,
+ StandardErrorEncoding = encoding,
+ // set the right type of arguments
+ Arguments = isScript
+ ? $@"& '{command}'"
+ : $@"& {{{command}}}"
+ };
+
+ using var process = new Process();
+ process.StartInfo = processInfo;
+
+ var start = process.Start();
+ if (!start)
+ {
+ Log.Error("[POWERSHELL] Unable to begin executing the {type}: {cmd}", isScript ? "script" : "command", command);
+
+ return false;
+ }
+
+ var completed = process.WaitForExit(Convert.ToInt32(timeout.TotalMilliseconds));
+ if (!completed)
+ Log.Error("[POWERSHELL] Timeout executing the {type}: {cmd}", isScript ? "script" : "command", command);
+
+ output = process.StandardOutput.ReadToEnd().Trim();
+ errors = process.StandardError.ReadToEnd().Trim();
+
+ process.StandardOutput.Dispose();
+ process.StandardError.Dispose();
+
+ process.Kill();
+
+ return completed;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, ex.Message);
+
+ return false;
+ }
+ }
+
+ ///
+ /// Attempt to locate powershell.exe
+ ///
+ ///
+ public static string GetPsExecutable()
+ {
+ // try regular location
+ var psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell\\v1.0\\powershell.exe");
+ if (File.Exists(psExec))
+ return psExec;
+
+ // try specific
+ psExec = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "WindowsPowerShell\\v1.0\\powershell.exe");
+ if (File.Exists(psExec))
+ return psExec;
+
+ Log.Error("[POWERSHELL] PS executable not found, make sure you have powershell installed on your system");
+ return string.Empty;
+ }
+ }
}
diff --git a/src/HASS.Agent.Staging/HASS.Agent/MQTT/MqttManager.cs b/src/HASS.Agent.Staging/HASS.Agent/MQTT/MqttManager.cs
index d4f08916..875a46de 100644
--- a/src/HASS.Agent.Staging/HASS.Agent/MQTT/MqttManager.cs
+++ b/src/HASS.Agent.Staging/HASS.Agent/MQTT/MqttManager.cs
@@ -37,7 +37,7 @@ public class MqttManager : IMqttManager
private bool _disconnectionNotified = false;
private bool _connectingFailureNotified = false;
-
+
private MqttStatus _status = MqttStatus.Connecting;
///
@@ -82,7 +82,7 @@ public void Initialize()
// create our device's config model
if (Variables.DeviceConfig == null) CreateDeviceConfigModel();
-
+
// create a new mqtt client
_mqttClient = Variables.MqttFactory.CreateManagedMqttClient();
@@ -348,7 +348,7 @@ public async Task PublishAsync(MqttApplicationMessage message)
if (Variables.ExtendedLogging) Log.Warning("[MQTT] Not connected, message dropped (won't report again for 5 minutes)");
return false;
}
-
+
// publish away
var published = await _mqttClient.PublishAsync(message);
if (published.ReasonCode == MqttClientPublishReasonCode.Success) return true;
@@ -390,12 +390,12 @@ public async Task AnnounceAutoDiscoveryConfigAsync(AbstractDiscoverable discover
// prepare topic
var topic = $"{Variables.AppSettings.MqttDiscoveryPrefix}/{domain}/{Variables.DeviceConfig.Name}/{discoverable.ObjectId}/config";
-
+
// build config message
var messageBuilder = new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag);
-
+
// add payload
if (clearConfig) messageBuilder.WithPayload(Array.Empty());
else messageBuilder.WithPayload(JsonSerializer.Serialize(discoverable.GetAutoDiscoveryConfig(), discoverable.GetAutoDiscoveryConfig().GetType(), JsonSerializerOptions));
@@ -420,7 +420,7 @@ public async Task AnnounceAutoDiscoveryConfigAsync(AbstractDiscoverable discover
///
private DateTime _lastAvailableAnnouncement = DateTime.MinValue;
private DateTime _lastAvailableAnnouncementFailedLogged = DateTime.MinValue;
-
+
///
/// JSON serializer options (camelcase, casing, ignore condition, converters)
///
@@ -516,7 +516,7 @@ public async Task ClearDeviceConfigAsync()
.WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/sensor/{Variables.DeviceConfig.Name}/availability")
.WithPayload(Array.Empty())
.WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag);
-
+
// publish
await _mqttClient.PublishAsync(messageBuilder.Build());
}
@@ -600,20 +600,20 @@ public async Task UnubscribeAsync(AbstractCommand command)
private static ManagedMqttClientOptions GetOptions()
{
if (string.IsNullOrEmpty(Variables.AppSettings.MqttAddress)) return null;
-
+
// id can be random, but we'll store it for consistency (unless user-defined)
if (string.IsNullOrEmpty(Variables.AppSettings.MqttClientId))
{
Variables.AppSettings.MqttClientId = Guid.NewGuid().ToString()[..8];
SettingsManager.StoreAppSettings();
}
-
+
// configure last will message
var lastWillMessageBuilder = new MqttApplicationMessageBuilder()
.WithTopic($"{Variables.AppSettings.MqttDiscoveryPrefix}/sensor/{Variables.DeviceConfig.Name}/availability")
.WithPayload("offline")
.WithRetainFlag(Variables.AppSettings.MqttUseRetainFlag);
-
+
// prepare message
var lastWillMessage = lastWillMessageBuilder.Build();
@@ -687,7 +687,7 @@ private static void HandleMessageReceived(MqttApplicationMessage applicationMess
var notification = JsonSerializer.Deserialize(applicationMessage.Payload, JsonSerializerOptions)!;
_ = Task.Run(() => NotificationManager.ShowNotification(notification));
return;
- }
+ }
if (applicationMessage.Topic == $"hass.agent/media_player/{HelperFunctions.GetConfiguredDeviceName()}/cmd")
{
@@ -745,12 +745,12 @@ private static void HandleCommandReceived(MqttApplicationMessage applicationMess
if (payload.Contains("on")) command.TurnOn();
else if (payload.Contains("off")) command.TurnOff();
else switch (payload)
- {
- case "press":
- case "lock":
- command.TurnOn();
- break;
- }
+ {
+ case "press":
+ case "lock":
+ command.TurnOn();
+ break;
+ }
}
///
@@ -760,8 +760,12 @@ private static void HandleCommandReceived(MqttApplicationMessage applicationMess
///
private static void HandleActionReceived(MqttApplicationMessage applicationMessage, AbstractCommand command)
{
+ if (applicationMessage.Payload == null)
+ return;
+
var payload = Encoding.UTF8.GetString(applicationMessage.Payload);
- if (string.IsNullOrWhiteSpace(payload)) return;
+ if (string.IsNullOrWhiteSpace(payload))
+ return;
command.TurnOnWithAction(payload);
}