changeset 29:f6235dc0a8ec

Add ability to play a sound on check failure.
author Brad Greco <brad@bgreco.net>
date Fri, 14 Jun 2019 21:01:55 -0400
parents 437442cd8090
children b0af6b4bed4d
files ServerMonitor/App.config ServerMonitor/Forms/AboutForm.Designer.cs ServerMonitor/Forms/AboutForm.cs ServerMonitor/Forms/SettingsForm.Designer.cs ServerMonitor/Forms/SettingsForm.cs ServerMonitor/Objects/CheckResult.cs ServerMonitor/Objects/ServerMonitor.cs ServerMonitor/Properties/Settings.Designer.cs ServerMonitor/Properties/Settings.settings ServerMonitor/ServerMonitor.csproj ServerMonitor/packages.config
diffstat 11 files changed, 332 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/ServerMonitor/App.config	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/App.config	Fri Jun 14 21:01:55 2019 -0400
@@ -38,6 +38,15 @@
             <setting name="HideToNotificationArea" serializeAs="String">
                 <value>True</value>
             </setting>
+            <setting name="ErrorSound" serializeAs="String">
+                <value>0</value>
+            </setting>
+            <setting name="WarningSound" serializeAs="String">
+                <value>0</value>
+            </setting>
+            <setting name="InformationSound" serializeAs="String">
+                <value>0</value>
+            </setting>
         </ServerMonitorApp.Properties.Settings>
         <ServerMonitor.Properties.Settings>
             <setting name="ConfirmDeleteCheck" serializeAs="String">
--- a/ServerMonitor/Forms/AboutForm.Designer.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Forms/AboutForm.Designer.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -35,6 +35,7 @@
             this.LicenseLabel = new System.Windows.Forms.Label();
             this.SshNetLinkLabel = new System.Windows.Forms.LinkLabel();
             this.NAppUpdateLinkLabel = new System.Windows.Forms.LinkLabel();
+            this.NAudioLinkLabel = new System.Windows.Forms.LinkLabel();
             ((System.ComponentModel.ISupportInitialize)(this.IconPictureBox)).BeginInit();
             this.SuspendLayout();
             // 
@@ -115,11 +116,25 @@
             this.NAppUpdateLinkLabel.UseCompatibleTextRendering = true;
             this.NAppUpdateLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.NAppUpdateLinkLabel_LinkClicked);
             // 
+            // NAudioLinkLabel
+            // 
+            this.NAudioLinkLabel.LinkArea = new System.Windows.Forms.LinkArea(25, 6);
+            this.NAudioLinkLabel.Location = new System.Drawing.Point(12, 262);
+            this.NAudioLinkLabel.Name = "NAudioLinkLabel";
+            this.NAudioLinkLabel.Size = new System.Drawing.Size(300, 18);
+            this.NAudioLinkLabel.TabIndex = 8;
+            this.NAudioLinkLabel.TabStop = true;
+            this.NAudioLinkLabel.Text = "Audio is provided by the NAudio library.";
+            this.NAudioLinkLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter;
+            this.NAudioLinkLabel.UseCompatibleTextRendering = true;
+            this.NAudioLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.NAudioLinkLabel_LinkClicked);
+            // 
             // AboutForm
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
-            this.ClientSize = new System.Drawing.Size(324, 273);
+            this.ClientSize = new System.Drawing.Size(324, 289);
+            this.Controls.Add(this.NAudioLinkLabel);
             this.Controls.Add(this.NAppUpdateLinkLabel);
             this.Controls.Add(this.SshNetLinkLabel);
             this.Controls.Add(this.LicenseLabel);
@@ -147,5 +162,6 @@
         private System.Windows.Forms.Label LicenseLabel;
         private System.Windows.Forms.LinkLabel SshNetLinkLabel;
         private System.Windows.Forms.LinkLabel NAppUpdateLinkLabel;
+        private System.Windows.Forms.LinkLabel NAudioLinkLabel;
     }
 }
\ No newline at end of file
--- a/ServerMonitor/Forms/AboutForm.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Forms/AboutForm.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -47,5 +47,10 @@
         {
             Process.Start("https://github.com/synhershko/NAppUpdate");
         }
+
+        private void NAudioLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+        {
+            Process.Start("https://github.com/naudio/NAudio");
+        }
     }
 }
--- a/ServerMonitor/Forms/SettingsForm.Designer.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Forms/SettingsForm.Designer.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -29,6 +29,12 @@
         private void InitializeComponent()
         {
             this.SeverityOptionsGroupBox = new System.Windows.Forms.GroupBox();
+            this.InformationSoundComboBox = new System.Windows.Forms.ComboBox();
+            this.WarningSoundComboBox = new System.Windows.Forms.ComboBox();
+            this.InformationSoundLabel = new System.Windows.Forms.Label();
+            this.WarningSoundLabel = new System.Windows.Forms.Label();
+            this.ErrorSoundLabel = new System.Windows.Forms.Label();
+            this.ErrorSoundComboBox = new System.Windows.Forms.ComboBox();
             this.ErrorComboBox = new System.Windows.Forms.ComboBox();
             this.WarningComboBox = new System.Windows.Forms.ComboBox();
             this.InformationComboBox = new System.Windows.Forms.ComboBox();
@@ -38,7 +44,7 @@
             this.WarningIcon = new System.Windows.Forms.PictureBox();
             this.ErrorLabel = new System.Windows.Forms.Label();
             this.ErrorIcon = new System.Windows.Forms.PictureBox();
-            this.label1 = new System.Windows.Forms.Label();
+            this.OptionsLabel = new System.Windows.Forms.Label();
             this.OkButton = new System.Windows.Forms.Button();
             this.CancelSettingsButton = new System.Windows.Forms.Button();
             this.KeepLogDaysInput = new System.Windows.Forms.NumericUpDown();
@@ -57,6 +63,12 @@
             // 
             this.SeverityOptionsGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
+            this.SeverityOptionsGroupBox.Controls.Add(this.InformationSoundComboBox);
+            this.SeverityOptionsGroupBox.Controls.Add(this.WarningSoundComboBox);
+            this.SeverityOptionsGroupBox.Controls.Add(this.InformationSoundLabel);
+            this.SeverityOptionsGroupBox.Controls.Add(this.WarningSoundLabel);
+            this.SeverityOptionsGroupBox.Controls.Add(this.ErrorSoundLabel);
+            this.SeverityOptionsGroupBox.Controls.Add(this.ErrorSoundComboBox);
             this.SeverityOptionsGroupBox.Controls.Add(this.ErrorComboBox);
             this.SeverityOptionsGroupBox.Controls.Add(this.WarningComboBox);
             this.SeverityOptionsGroupBox.Controls.Add(this.InformationComboBox);
@@ -68,11 +80,71 @@
             this.SeverityOptionsGroupBox.Controls.Add(this.ErrorIcon);
             this.SeverityOptionsGroupBox.Location = new System.Drawing.Point(12, 109);
             this.SeverityOptionsGroupBox.Name = "SeverityOptionsGroupBox";
-            this.SeverityOptionsGroupBox.Size = new System.Drawing.Size(337, 104);
+            this.SeverityOptionsGroupBox.Size = new System.Drawing.Size(435, 104);
             this.SeverityOptionsGroupBox.TabIndex = 8;
             this.SeverityOptionsGroupBox.TabStop = false;
             this.SeverityOptionsGroupBox.Text = "Severity settings";
             // 
+            // InformationSoundComboBox
+            // 
+            this.InformationSoundComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.InformationSoundComboBox.FormattingEnabled = true;
+            this.InformationSoundComboBox.Location = new System.Drawing.Point(299, 71);
+            this.InformationSoundComboBox.Name = "InformationSoundComboBox";
+            this.InformationSoundComboBox.Size = new System.Drawing.Size(130, 21);
+            this.InformationSoundComboBox.TabIndex = 58;
+            this.InformationSoundComboBox.SelectedIndexChanged += new System.EventHandler(this.SoundComboBox_SelectedIndexChanged);
+            this.InformationSoundComboBox.Format += new System.Windows.Forms.ListControlConvertEventHandler(this.SoundComboBox_Format);
+            // 
+            // WarningSoundComboBox
+            // 
+            this.WarningSoundComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.WarningSoundComboBox.FormattingEnabled = true;
+            this.WarningSoundComboBox.Location = new System.Drawing.Point(299, 44);
+            this.WarningSoundComboBox.Name = "WarningSoundComboBox";
+            this.WarningSoundComboBox.Size = new System.Drawing.Size(130, 21);
+            this.WarningSoundComboBox.TabIndex = 57;
+            this.WarningSoundComboBox.SelectedIndexChanged += new System.EventHandler(this.SoundComboBox_SelectedIndexChanged);
+            this.WarningSoundComboBox.Format += new System.Windows.Forms.ListControlConvertEventHandler(this.SoundComboBox_Format);
+            // 
+            // InformationSoundLabel
+            // 
+            this.InformationSoundLabel.AutoSize = true;
+            this.InformationSoundLabel.Location = new System.Drawing.Point(252, 74);
+            this.InformationSoundLabel.Name = "InformationSoundLabel";
+            this.InformationSoundLabel.Size = new System.Drawing.Size(41, 13);
+            this.InformationSoundLabel.TabIndex = 56;
+            this.InformationSoundLabel.Text = "Sound:";
+            // 
+            // WarningSoundLabel
+            // 
+            this.WarningSoundLabel.AutoSize = true;
+            this.WarningSoundLabel.Location = new System.Drawing.Point(252, 47);
+            this.WarningSoundLabel.Name = "WarningSoundLabel";
+            this.WarningSoundLabel.Size = new System.Drawing.Size(41, 13);
+            this.WarningSoundLabel.TabIndex = 55;
+            this.WarningSoundLabel.Text = "Sound:";
+            // 
+            // ErrorSoundLabel
+            // 
+            this.ErrorSoundLabel.AutoSize = true;
+            this.ErrorSoundLabel.Location = new System.Drawing.Point(252, 20);
+            this.ErrorSoundLabel.Name = "ErrorSoundLabel";
+            this.ErrorSoundLabel.Size = new System.Drawing.Size(41, 13);
+            this.ErrorSoundLabel.TabIndex = 54;
+            this.ErrorSoundLabel.Text = "Sound:";
+            // 
+            // ErrorSoundComboBox
+            // 
+            this.ErrorSoundComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.ErrorSoundComboBox.FormattingEnabled = true;
+            this.ErrorSoundComboBox.Location = new System.Drawing.Point(299, 17);
+            this.ErrorSoundComboBox.Name = "ErrorSoundComboBox";
+            this.ErrorSoundComboBox.Size = new System.Drawing.Size(130, 21);
+            this.ErrorSoundComboBox.TabIndex = 53;
+            this.ErrorSoundComboBox.SelectedIndexChanged += new System.EventHandler(this.SoundComboBox_SelectedIndexChanged);
+            this.ErrorSoundComboBox.Format += new System.Windows.Forms.ListControlConvertEventHandler(this.SoundComboBox_Format);
+            // 
             // ErrorComboBox
             // 
             this.ErrorComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
@@ -157,20 +229,20 @@
             this.ErrorIcon.TabIndex = 0;
             this.ErrorIcon.TabStop = false;
             // 
-            // label1
+            // OptionsLabel
             // 
-            this.label1.AutoSize = true;
-            this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.label1.Location = new System.Drawing.Point(9, 9);
-            this.label1.Name = "label1";
-            this.label1.Size = new System.Drawing.Size(50, 13);
-            this.label1.TabIndex = 1;
-            this.label1.Text = "Options";
+            this.OptionsLabel.AutoSize = true;
+            this.OptionsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.OptionsLabel.Location = new System.Drawing.Point(9, 9);
+            this.OptionsLabel.Name = "OptionsLabel";
+            this.OptionsLabel.Size = new System.Drawing.Size(50, 13);
+            this.OptionsLabel.TabIndex = 1;
+            this.OptionsLabel.Text = "Options";
             // 
             // OkButton
             // 
             this.OkButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.OkButton.Location = new System.Drawing.Point(193, 224);
+            this.OkButton.Location = new System.Drawing.Point(291, 224);
             this.OkButton.Name = "OkButton";
             this.OkButton.Size = new System.Drawing.Size(75, 23);
             this.OkButton.TabIndex = 50;
@@ -182,7 +254,7 @@
             // 
             this.CancelSettingsButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
             this.CancelSettingsButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-            this.CancelSettingsButton.Location = new System.Drawing.Point(274, 224);
+            this.CancelSettingsButton.Location = new System.Drawing.Point(372, 224);
             this.CancelSettingsButton.Name = "CancelSettingsButton";
             this.CancelSettingsButton.Size = new System.Drawing.Size(75, 23);
             this.CancelSettingsButton.TabIndex = 51;
@@ -246,7 +318,7 @@
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.CancelButton = this.CancelSettingsButton;
-            this.ClientSize = new System.Drawing.Size(361, 259);
+            this.ClientSize = new System.Drawing.Size(459, 259);
             this.Controls.Add(this.KeepRunningCheckBox);
             this.Controls.Add(this.AutorunCheckBox);
             this.Controls.Add(this.DaysLabel);
@@ -254,7 +326,7 @@
             this.Controls.Add(this.KeepLogDaysInput);
             this.Controls.Add(this.CancelSettingsButton);
             this.Controls.Add(this.OkButton);
-            this.Controls.Add(this.label1);
+            this.Controls.Add(this.OptionsLabel);
             this.Controls.Add(this.SeverityOptionsGroupBox);
             this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
             this.MaximizeBox = false;
@@ -276,7 +348,7 @@
 
         private System.Windows.Forms.GroupBox SeverityOptionsGroupBox;
         private System.Windows.Forms.PictureBox ErrorIcon;
-        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.Label OptionsLabel;
         private System.Windows.Forms.Label InformationLabel;
         private System.Windows.Forms.PictureBox InformationIcon;
         private System.Windows.Forms.Label WarningLabel;
@@ -292,5 +364,11 @@
         private System.Windows.Forms.Label DaysLabel;
         private System.Windows.Forms.CheckBox AutorunCheckBox;
         private System.Windows.Forms.CheckBox KeepRunningCheckBox;
+        private System.Windows.Forms.Label ErrorSoundLabel;
+        private System.Windows.Forms.ComboBox ErrorSoundComboBox;
+        private System.Windows.Forms.Label WarningSoundLabel;
+        private System.Windows.Forms.Label InformationSoundLabel;
+        private System.Windows.Forms.ComboBox InformationSoundComboBox;
+        private System.Windows.Forms.ComboBox WarningSoundComboBox;
     }
 }
\ No newline at end of file
--- a/ServerMonitor/Forms/SettingsForm.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Forms/SettingsForm.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -1,6 +1,8 @@
 using Microsoft.Win32;
 using ServerMonitorApp.Properties;
 using System;
+using System.IO;
+using System.Linq;
 using System.Text.RegularExpressions;
 using System.Windows.Forms;
 
@@ -11,6 +13,8 @@
     {
         private readonly string autorunKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
         private readonly string autorunName = "ServerMonitor";
+        private readonly string[] soundOptions = { "None", "Windows error", "Custom sound..." };
+        private readonly int customSoundIndex = 2;
 
         public SettingsForm()
         {
@@ -26,6 +30,14 @@
                 comboBox.DataSource = Enum.GetValues(typeof(FailAction));
                 comboBox.Format += FailActionComboBox_Format;
             }
+            // Populate the sound options combo boxes.
+            foreach (ComboBox comboBox in new object[] { ErrorSoundComboBox, WarningSoundComboBox, InformationSoundComboBox })
+            {
+                comboBox.Items.AddRange(soundOptions);
+                // Add the already-selected custom sound, if any.
+                if (getSoundSettingIndex(comboBox) == customSoundIndex)
+                    comboBox.Items.Insert(customSoundIndex, getSoundSetting(comboBox));
+            }
             // Initialize controls with current settings.
             AutorunCheckBox.Checked = GetAutorun();
             KeepRunningCheckBox.Checked = Settings.Default.HideToNotificationArea;
@@ -33,6 +45,9 @@
             ErrorComboBox.SelectedItem = Settings.Default.ErrorAction;
             WarningComboBox.SelectedItem = Settings.Default.WarningAction;
             InformationComboBox.SelectedItem = Settings.Default.InformationAction;
+            ErrorSoundComboBox.SelectedIndex = getSoundSettingIndex(ErrorSoundComboBox);
+            WarningSoundComboBox.SelectedIndex = getSoundSettingIndex(WarningSoundComboBox);
+            InformationSoundComboBox.SelectedIndex = getSoundSettingIndex(InformationSoundComboBox);
         }
 
         /// <summary>Gets whether an autorun registry key for this program exists.</summary>
@@ -71,6 +86,9 @@
             Settings.Default.ErrorAction = (FailAction)ErrorComboBox.SelectedItem;
             Settings.Default.WarningAction = (FailAction)WarningComboBox.SelectedItem;
             Settings.Default.InformationAction = (FailAction)InformationComboBox.SelectedItem;
+            Settings.Default.ErrorSound = getSelectedSound(ErrorSoundComboBox);
+            Settings.Default.WarningSound = getSelectedSound(WarningSoundComboBox);
+            Settings.Default.InformationSound = getSelectedSound(InformationSoundComboBox);
             Settings.Default.Save();
             SetAutorun(AutorunCheckBox.Checked);
             Close();
@@ -81,5 +99,81 @@
         {
             Close();
         }
+
+        /// <summary>Shows a file browser when "Custom cound..." is selected.</summary>
+        private void SoundComboBox_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            ComboBox comboBox = (ComboBox)sender;
+            // Show the dialog if the last item is selected.
+            if (comboBox.SelectedIndex == comboBox.Items.Count - 1)
+            {
+                OpenFileDialog dialog = new OpenFileDialog()
+                {
+                    Title = "Select audio file",
+                    Filter = "Audio files (*.wav;*.mp3;*.wma,*.aif,*.aiff,*.asf,*.aac,*.m4a)|*.wav;*.mp3;*.wma,*.aif,*.aiff,*.asf,*.aac,*.m4a|All files (*.*)|*.*"
+                };
+                if (dialog.ShowDialog() == DialogResult.OK)
+                {
+                    // Remove the previous custom sound, if any.
+                    if (comboBox.Items.Count > soundOptions.Length)
+                        comboBox.Items.RemoveAt(customSoundIndex);
+                    // Add the custom sound and mark it as selected.
+                    comboBox.Items.Insert(customSoundIndex, dialog.FileName);
+                    comboBox.SelectedIndex = customSoundIndex;
+                }
+                else
+                {
+                    // Restore the existing setting if the dialog was cancelled.
+                    comboBox.SelectedIndex = getSoundSettingIndex(comboBox);
+                }
+            }
+        }
+
+        /// <summary>Shows only the filename of the custom sound.</summary>
+        private void SoundComboBox_Format(object sender, ListControlConvertEventArgs e)
+        {
+            ComboBox comboBox = (ComboBox)sender;
+            if (!soundOptions.Contains(e.Value))
+                e.Value = Path.GetFileName(e.Value.ToString());
+        }
+
+        /// <summary>Returns the selected sound for a combo box as stored in program settings.</summary>
+        /// <param name="comboBox">The combo box to read from.</param>
+        /// <returns>
+        /// The index ("0" or "1") of the selected option, or the
+        /// path to the custom sound if a custom sound is selected.
+        /// </returns>
+        private string getSelectedSound(ComboBox comboBox)
+        {
+            return comboBox.SelectedIndex == customSoundIndex ? comboBox.SelectedItem.ToString() : comboBox.SelectedIndex.ToString();
+        }
+
+        /// <summary>Reads the sound setting for a combobox from program settings.</summary>
+        /// <param name="comboBox">The combo box to read data for.</param>
+        /// <returns>
+        /// The index ("0" or "1") of the selected option, or the
+        /// path to the custom sound if a custom sound is selected.
+        /// </returns>
+        private string getSoundSetting(ComboBox comboBox)
+        {
+            string setting = string.Empty;
+            if (comboBox == ErrorSoundComboBox)
+                setting = Settings.Default.ErrorSound;
+            if (comboBox == WarningSoundComboBox)
+                setting = Settings.Default.WarningSound;
+            if (comboBox == InformationSoundComboBox)
+                setting = Settings.Default.InformationSound;
+            return setting;
+        }
+
+        /// <summary>Gets the index of the combo box item that corresponds to its program sound setting.</summary>
+        /// <param name="comboBox">The combo box to read data for.</param>
+        /// <returns>The index of the combo box item that corresponds to its program sound setting.</returns>
+        private int getSoundSettingIndex(ComboBox comboBox)
+        {
+            // If the string is not numeric, it contains the path to the custom sound.
+            return int.TryParse(getSoundSetting(comboBox), out int index) ? index : customSoundIndex;
+        }
+
     }
 }
--- a/ServerMonitor/Objects/CheckResult.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Objects/CheckResult.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -47,6 +47,32 @@
             }
         }
 
+        /// <summary>Sound to play when the check fails.</summary>
+        /// <remarks>
+        /// Returns null if no sound should be played.
+        /// Returns string.Empty if the Windows default error sound should be played.
+        /// Otherwise, returns the path to the custom sound file that should be played.
+        /// </remarks>
+        public string FailSound
+        {
+            get
+            {
+                string setting = null;
+                // Use the global preferences for each status to determine the action to take.
+                switch (CheckStatus)
+                {
+                    case CheckStatus.Error: setting = Settings.Default.ErrorSound; break;
+                    case CheckStatus.Warning: setting = Settings.Default.WarningSound; break;
+                    case CheckStatus.Information: setting = Settings.Default.InformationSound; break;
+                    // On success (or any other status), do nothing.
+                }
+                if (int.TryParse(setting, out int index)) {
+                    return index == 0 ? null : string.Empty;
+                }
+                return setting;
+            }
+        }
+
         /// <summary>CheckResult constructor.</summary>
         /// <param name="check">The originating check of this check result.</param>
         /// <param name="status">The result status of the check execution.</param>
--- a/ServerMonitor/Objects/ServerMonitor.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Objects/ServerMonitor.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -1,4 +1,5 @@
 using Microsoft.Win32;
+using NAudio.Wave;
 using Renci.SshNet;
 using Renci.SshNet.Common;
 using ServerMonitorApp.Properties;
@@ -6,6 +7,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Media;
 using System.Net.NetworkInformation;
 using System.Threading;
 using System.Threading.Tasks;
@@ -34,6 +36,8 @@
         // then executes.
         private Dictionary<Task<CheckResult>, int> tasks;
         private ServerSummaryForm mainForm;
+        private WaveOut waveOut = new WaveOut();
+        MediaFoundationReader mediaReader;
 
         /// <summary>Fires when the status of a check changes.</summary>
         public event EventHandler<CheckStatusChangedEventArgs> CheckStatusChanged;
@@ -90,7 +94,7 @@
         {
             bool triedBackup = false;
 
-            Read:
+        Read:
             TextReader reader = null;
             try
             {
@@ -322,9 +326,43 @@
                     mainForm.AlertServerForm(result.Check);
                 if (result.FailAction.In(FailAction.FlashTaskbar, FailAction.NotificationBalloon))
                     mainForm.ShowBalloon(result);
+                PlaySound(result.FailSound);
             }
         }
 
+        /// <summary>Plays a sound.</summary>
+        /// <param name="sound">
+        /// If null, no sound is played.
+        /// If string.Empty, the Windows default error sound is played.
+        /// Otherwise, plays the sound at the given path.
+        /// </param>
+        private void PlaySound(string sound)
+        {
+            if (sound == string.Empty)
+                SystemSounds.Asterisk.Play();
+            else if (sound != null)
+            {
+                try
+                {
+                    mediaReader = new MediaFoundationReader(sound);
+                    waveOut.Init(mediaReader);
+                    waveOut.PlaybackStopped += WaveOut_PlaybackStopped;
+                    waveOut.Play();
+                }
+                catch
+                {
+                    // Play the default sound if something went wrong.
+                    SystemSounds.Asterisk.Play();
+                }
+            }
+        }
+
+        /// <summary>Disposes the media reader after sound playback.</summary>
+        private void WaveOut_PlaybackStopped(object sender, StoppedEventArgs e)
+        {
+            mediaReader.Dispose();
+        }
+
         /// <summary>Reads all check results from the log for a server.</summary>
         /// <param name="server">The server whose check results should be read.</param>
         /// <returns>A list of all check results found in the log file for the given server.</returns>
--- a/ServerMonitor/Properties/Settings.Designer.cs	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Properties/Settings.Designer.cs	Fri Jun 14 21:01:55 2019 -0400
@@ -130,5 +130,41 @@
                 this["HideToNotificationArea"] = value;
             }
         }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("0")]
+        public string ErrorSound {
+            get {
+                return ((string)(this["ErrorSound"]));
+            }
+            set {
+                this["ErrorSound"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("0")]
+        public string WarningSound {
+            get {
+                return ((string)(this["WarningSound"]));
+            }
+            set {
+                this["WarningSound"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("0")]
+        public string InformationSound {
+            get {
+                return ((string)(this["InformationSound"]));
+            }
+            set {
+                this["InformationSound"] = value;
+            }
+        }
     }
 }
--- a/ServerMonitor/Properties/Settings.settings	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/Properties/Settings.settings	Fri Jun 14 21:01:55 2019 -0400
@@ -29,5 +29,14 @@
     <Setting Name="HideToNotificationArea" Type="System.Boolean" Scope="User">
       <Value Profile="(Default)">True</Value>
     </Setting>
+    <Setting Name="ErrorSound" Type="System.String" Scope="User">
+      <Value Profile="(Default)">0</Value>
+    </Setting>
+    <Setting Name="WarningSound" Type="System.String" Scope="User">
+      <Value Profile="(Default)">0</Value>
+    </Setting>
+    <Setting Name="InformationSound" Type="System.String" Scope="User">
+      <Value Profile="(Default)">0</Value>
+    </Setting>
   </Settings>
 </SettingsFile>
\ No newline at end of file
--- a/ServerMonitor/ServerMonitor.csproj	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/ServerMonitor.csproj	Fri Jun 14 21:01:55 2019 -0400
@@ -64,6 +64,9 @@
     <Reference Include="NAppUpdate.Framework, Version=0.5.1.0, Culture=neutral, PublicKeyToken=d1f1d1f19f9e5a56, processorArchitecture=MSIL">
       <HintPath>..\packages\NAppUpdate.Framework.0.5.1.0\lib\net40\NAppUpdate.Framework.dll</HintPath>
     </Reference>
+    <Reference Include="NAudio, Version=1.9.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\NAudio.1.9.0\lib\net35\NAudio.dll</HintPath>
+    </Reference>
     <Reference Include="Renci.SshNet, Version=2016.1.0.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db106, processorArchitecture=MSIL">
       <HintPath>..\packages\SSH.NET.2016.1.0\lib\net40\Renci.SshNet.dll</HintPath>
     </Reference>
--- a/ServerMonitor/packages.config	Sun Jun 02 17:55:38 2019 -0400
+++ b/ServerMonitor/packages.config	Fri Jun 14 21:01:55 2019 -0400
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="NAppUpdate.Framework" version="0.5.1.0" targetFramework="net45" />
+  <package id="NAudio" version="1.9.0" targetFramework="net45" />
   <package id="SSH.NET" version="2016.1.0" targetFramework="net45" />
 </packages>
\ No newline at end of file