void BatteryCheck(object state) { SYSTEM_POWER_STATUS_EX status = new SYSTEM_POWER_STATUS_EX(); if (GetSystemPowerStatusEx(status, true)) { if (status.ACLineStatus != oldStatus) { OnPowerMessage("power source: " + status.ACLineStatus.ToString(), status.ACLineStatus == ACLineStatus.AC_LINE_ONLINE ? true : false); oldStatus = status.ACLineStatus; } } }
private void PowerEventArrive(object sender, EventArrivedEventArgs e) { // Credit: https://stackoverflow.com/questions/3948884/detect-power-state-change bool exitLoop = false; Dictionary <string, string> powerValues = new Dictionary <string, string> { { "4", "Entering Suspend" }, { "7", "Resume from Suspend" }, { "10", "Power Status Change" }, { "11", "OEM Event" }, { "18", "Resume Automatic" } }; foreach (PropertyData pd in e.NewEvent.Properties) { if (pd != null && pd.Value != null && !exitLoop) { if (pd.Value.ToString() == "10") // Power Status Change event = 10 { List <string> executeCommands = null; bool cancellingPowerOff = false; int taskDelay = 0; var powerStatus = Win32PowerManager.GetSystemPowerStatus(); ACLineStatus newACLineStatus = powerStatus.ACLineStatus; // We'll be done with the event after this exitLoop = true; if (newACLineStatus == ACLineStatus.Online) { // Power was off but came back on executeCommands = _appSettings.PowerOnCommands; // Cancel any running tasks and refresh cancellation token source // TODO: Code below should only run if there is a currently running power off task if (_powerOffDelayed) { _powerOffDelayed = false; _runningTasksCancellationTokenSource.Cancel(); cancellingPowerOff = true; _logger.LogInformation($"Power back on within ({_appSettings.SecondsBeforePowerOffExecution}) seconds of power off. Skipping execution of power on script(s)."); } else { _logger.LogInformation($"Power back on. Power on script(s) execution beginning..."); } } else if (newACLineStatus == ACLineStatus.Offline) { // Power was on but then went off executeCommands = _appSettings.PowerOffCommands; taskDelay = _appSettings.SecondsBeforePowerOffExecution * 1000; _powerOffDelayed = true; _logger.LogInformation($"Power is off. Waiting ({_appSettings.SecondsBeforePowerOffExecution}) seconds to begin execution of script(s)..."); } // Execute the relevant commands if (!cancellingPowerOff) { Task.Run(() => { // Only reference this copy of the token inside of here var localCancelToken = _runningTasksCancellationTokenSource; try { _logger.LogDebug($"Task ID#{Task.CurrentId} started."); _logger.LogInformation($"Executing command(s): {String.Join(", ", executeCommands)}"); if (taskDelay > 0) { var delayTask = Task.Delay(taskDelay, localCancelToken.Token); _logger.LogDebug($"Task ID#{delayTask.Id} (delay task) started. Waiting ({taskDelay / 1000}) seconds..."); // If the task is cancelled here, it will throw an AggregateException and be caught below. try { delayTask.Wait(localCancelToken.Token); } catch (OperationCanceledException) { _logger.LogDebug($"Task ID#{delayTask.Id} (delay task) cancelled."); } _powerOffDelayed = false; // Cancel the outer task if the inner one was cancelled localCancelToken.Token.ThrowIfCancellationRequested(); _logger.LogDebug($"Task ID#{delayTask.Id} (delay task) completed."); } for (int x = 0; x < executeCommands.Count && !localCancelToken.Token.IsCancellationRequested; x++) { string command = executeCommands[x]; using (var process = new Process()) { bool procKilled = false; // Create a new process for each command in the list process.StartInfo.FileName = command; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = true; _logger.LogInformation($"Starting child process ({command})..."); try { process.Start(); // Check for task cancellation once a second. If task was cancelled, kill the process const int PROC_POLL_INTERVAL = 1000; while (!process.WaitForExit(PROC_POLL_INTERVAL)) { if (localCancelToken.Token.IsCancellationRequested) { procKilled = true; process.Kill(true); } } _logger.LogInformation($"Child process {(procKilled ? "killed" : "ended")} ({command})."); } catch (Exception ex) { _logger.LogError(ex, "Unable to start process."); } } } } catch (AggregateException ae) { ae.Handle(ex => { if (ex is TaskCanceledException) { _logger.LogDebug($"Task ID#{(ex as TaskCanceledException).Task.Id} cancelled."); } return(ex is TaskCanceledException); }); } catch (OperationCanceledException ex) { _logger.LogDebug($"Task ID#{Task.CurrentId} cancelled."); } }, _runningTasksCancellationTokenSource.Token) .ContinueWith((t) => { // If a cancellation is requested, it will most likely be caught in the power off delay wait and caught in the AE handler just above. // Because the exception was handled, the outer task will complete successfully despite being cancelled. Thus we'll need to handle this situation here // by reporting the task as being cancelled and resetting the cancellation token source if (t.IsCanceled) { _logger.LogDebug($"Task ID#{t.Id} cancelled."); } else { _logger.LogDebug($"Task ID#{t.Id} completed with status ({t.Status})"); } }, _runningTasksCancellationTokenSource.Token); } else { _runningTasksCancellationTokenSource = new CancellationTokenSource(); } } } /* * var name = powerValues.ContainsKey(pd.Value.ToString()) * ? powerValues[pd.Value.ToString()] * : pd.Value.ToString(); * _logger.LogInformation($"POWER EVENT: {name}"); */ } }