├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── enhancement.yml │ ├── translation_issue.yml │ └── bug_report.yml └── dependabot.yml ├── global.json ├── Images ├── MyScheduledTasks.png └── MyScheduledTasks_v6.png ├── MyScheduledTasks ├── Images │ ├── MST.ico │ ├── MST.png │ └── InvertedMST.png ├── MainWindow.xaml.cs ├── Views │ ├── AboutPage.xaml.cs │ ├── SettingsPage.xaml.cs │ ├── AddTasks.xaml.cs │ └── MainPage.xaml.cs ├── Configuration │ ├── ConfigManager.cs │ ├── TempSettings.cs │ ├── SettingChange.cs │ ├── UserSettings.cs │ └── ConfigHelpers.cs ├── Models │ ├── AllTasks.cs │ ├── NavigationItem.cs │ ├── MyTasks.cs │ ├── UILanguage.cs │ ├── Enums.cs │ └── ScheduledTask.cs ├── Converters │ ├── BooleanInverter.cs │ ├── RefreshVisibilityConverter.cs │ ├── RootConverter.cs │ ├── MenuItemEnabledConverter.cs │ ├── FontSizeConverter.cs │ ├── InvertibleBooleanToVisibilityConverter.cs │ ├── LocalizedDescriptionAttribute.cs │ ├── SpacingConverter.cs │ ├── EnumDescriptionTypeConverter.cs │ └── EnumBindingSourceExtension.cs ├── Dialogs │ ├── DeleteTasksDialog.xaml.cs │ ├── ImportTaskDialog.xaml.cs │ ├── EditNote.xaml.cs │ ├── SnackbarMsg.cs │ ├── MDCustMsgBox.xaml.cs │ └── EditNote.xaml ├── Helpers │ ├── EnumHelpers.cs │ ├── TaskInfo.cs │ ├── ClipboardHelper.cs │ ├── ScreenHelpers.cs │ ├── DialogHelpers.cs │ ├── CommandLineHelpers.cs │ ├── TextFileViewer.cs │ ├── LocalizationHelpers.cs │ ├── NLogHelpers.cs │ ├── AppInfo.cs │ ├── TaskFileHelpers.cs │ └── GitHubHelpers.cs ├── AssemblyInfo.cs ├── Constants │ └── AppConstStrings.cs ├── Styles │ ├── SnackbarStyle.xaml │ ├── ScrollBarStyle.xaml │ ├── TextBoxStyles.xaml │ ├── ListBoxStyles.xaml │ ├── DataTemplates.xaml │ ├── MenuStyles.xaml │ ├── NavigationStyles.xaml │ ├── ButtonStyles.xaml │ ├── ExpanderStyles.xaml │ └── DataGridStyles.xaml ├── Strings.test.xaml ├── License.txt ├── PowerShell │ └── GenBuildInfo.ps1 ├── GlobalUsings.cs ├── App.xaml ├── Languages │ └── Strings.en-GB.xaml ├── ViewModels │ ├── AboutViewModel.cs │ ├── MainViewModel.cs │ ├── SettingsViewModel.cs │ └── AddTasksViewModel.cs ├── app.manifest ├── CommonCompletionCodes.txt ├── MyScheduledTasks.csproj ├── Inno_Setup │ └── MyScheduledTasksLocalization.iss └── App.xaml.cs ├── CONTRIBUTING.md ├── version.json ├── SECURITY.md ├── LICENSE ├── MyScheduledTasks.sln ├── .gitattributes ├── README.md ├── CODE_OF_CONDUCT.md └── .gitignore /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.400" 4 | } 5 | } -------------------------------------------------------------------------------- /Images/MyScheduledTasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/MyScheduledTasks/HEAD/Images/MyScheduledTasks.png -------------------------------------------------------------------------------- /Images/MyScheduledTasks_v6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/MyScheduledTasks/HEAD/Images/MyScheduledTasks_v6.png -------------------------------------------------------------------------------- /MyScheduledTasks/Images/MST.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/MyScheduledTasks/HEAD/MyScheduledTasks/Images/MST.ico -------------------------------------------------------------------------------- /MyScheduledTasks/Images/MST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/MyScheduledTasks/HEAD/MyScheduledTasks/Images/MST.png -------------------------------------------------------------------------------- /MyScheduledTasks/Images/InvertedMST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/MyScheduledTasks/HEAD/MyScheduledTasks/Images/InvertedMST.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Pull Requests are welcome for adding a new language or for making updates to an existing one. The PR should be for the language file only. 2 | 3 | See the [wiki page](https://github.com/Timthreetwelve/MyScheduledTasks/wiki/Contribute-a-Translation) for details. 4 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "0.8.0.0", 4 | "assemblyVersion": { 5 | "precision": "build" 6 | }, 7 | "gitCommitIdShortFixedLength": 7, 8 | "publicReleaseRefSpec": [ 9 | "^refs/heads/main$" 10 | ] 11 | } -------------------------------------------------------------------------------- /MyScheduledTasks/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks; 4 | 5 | public partial class MainWindow : Window 6 | { 7 | public MainWindow() 8 | { 9 | InitializeComponent(); 10 | 11 | MainWindowHelpers.MyScheduledTasksStartUp(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MyScheduledTasks/Views/AboutPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Views; 4 | /// 5 | /// Interaction logic for AboutPage.xaml 6 | /// 7 | public partial class AboutPage : UserControl 8 | { 9 | public AboutPage() 10 | { 11 | InitializeComponent(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MyScheduledTasks/Views/SettingsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Views; 4 | /// 5 | /// Interaction logic for SettingsPage.xaml 6 | /// 7 | public partial class SettingsPage : UserControl 8 | { 9 | public SettingsPage() 10 | { 11 | InitializeComponent(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ### Reporting a Vulnerability 4 | 5 | To report a vulnerability, please **do not** open an issue. Instead, send an email to: 6 | 7 | `timthreetwelve@outlook.com` 8 | 9 | If you discover a vulnerability in a third-party module, please report it to the person or team maintaining the module. 10 | 11 | 12 | ### Preferred Language 13 | 14 | Please use English for all communications. 15 | -------------------------------------------------------------------------------- /MyScheduledTasks/Configuration/ConfigManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Configuration; 4 | 5 | /// 6 | /// Class for the static Setting property 7 | /// 8 | /// Class name of user settings 9 | public abstract class ConfigManager where T : ConfigManager, new() 10 | { 11 | public static T? Setting { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/MyScheduledTasks" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | open-pull-requests-limit: 5 13 | 14 | -------------------------------------------------------------------------------- /MyScheduledTasks/Models/AllTasks.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Models; 4 | 5 | internal sealed partial class AllTasks : ObservableObject 6 | { 7 | public static ObservableCollection All_TasksCollection { get; } = []; 8 | public static ObservableCollection Non_MS_TasksCollection { get; } = []; 9 | 10 | [ObservableProperty] 11 | private string? _taskPath; 12 | 13 | [ObservableProperty] 14 | private string? _taskName; 15 | 16 | [ObservableProperty] 17 | private string? _taskFolder; 18 | } 19 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/BooleanInverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Inverts a boolean value. True becomes False, False becomes True. 7 | /// 8 | public sealed class BooleanInverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | return !(bool)value!; 13 | } 14 | 15 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 16 | { 17 | return !(bool)value!; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/DeleteTasksDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Dialogs; 4 | 5 | /// 6 | /// Interaction logic for DeleteTasksDialog.xaml 7 | /// 8 | public partial class DeleteTasksDialog : UserControl 9 | { 10 | public DeleteTasksDialog() 11 | { 12 | InitializeComponent(); 13 | } 14 | 15 | private void UserControl_KeyDown(object sender, KeyEventArgs e) 16 | { 17 | if (e.Key == Key.Escape) 18 | { 19 | DialogHost.Close("MainDialogHost"); 20 | e.Handled = true; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/EnumHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class EnumHelpers 6 | { 7 | internal static string GetEnumDescription(Enum enumObj) 8 | { 9 | FieldInfo? field = enumObj.GetType().GetField(enumObj.ToString()); 10 | object[] attrArray = field!.GetCustomAttributes(false); 11 | 12 | if (attrArray.Length > 0) 13 | { 14 | DescriptionAttribute? attribute = attrArray[0] as DescriptionAttribute; 15 | return attribute!.Description; 16 | } 17 | 18 | return enumObj.ToString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/TaskInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class TaskInfo 6 | { 7 | #region Get scheduled task info for one task 8 | /// 9 | /// Get scheduled task info for one task 10 | /// 11 | /// Name (including folder name) of scheduled task 12 | /// Scheduled task object as Task 13 | public static Task? GetTaskInfo(string name) 14 | { 15 | using TaskService ts = TaskService.Instance; 16 | 17 | return ts.GetTask(name); 18 | } 19 | #endregion Get scheduled task info for one task 20 | } 21 | -------------------------------------------------------------------------------- /MyScheduledTasks/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | using System.Windows; 4 | 5 | [assembly: ThemeInfo( 6 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 7 | //(used if a resource is not found in the page, 8 | // or application resource dictionaries) 9 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 10 | //(used if a resource is not found in the page, 11 | // app, or any theme specific resource dictionaries) 12 | )] 13 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/RefreshVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Show refresh button only on main page. 7 | /// 8 | internal sealed class RefreshVisibilityConverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | return value is MainViewModel ? Visibility.Visible : (object)Visibility.Collapsed; 13 | } 14 | 15 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 16 | { 17 | return Binding.DoNothing; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /MyScheduledTasks/Constants/AppConstStrings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Constants; 4 | 5 | /// 6 | /// Class for constant/static strings 7 | /// 8 | internal static class AppConstString 9 | { 10 | /// 11 | /// Gets the GitHub repository owner. 12 | /// 13 | /// 14 | /// The repository owner. 15 | /// 16 | public static string RepoOwner => "TimThreeTwelve"; 17 | 18 | /// 19 | /// Gets the name of the GitHub repository. 20 | /// 21 | /// 22 | /// The name of the repository. 23 | /// 24 | public static string RepoName => "MyScheduledTasks"; 25 | } 26 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/RootConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Converter to annotate root folder 7 | /// 8 | internal sealed class RootConverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | string root = GetStringResource("SettingsItem_Root"); 13 | return value?.ToString() == "\\" && UserSettings.Setting!.AnnotateRoot ? $"\\ [{root}]" : value!; 14 | } 15 | 16 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 17 | { 18 | return Binding.DoNothing; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/SnackbarStyle.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyScheduledTasks/Strings.test.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | Settings - Language Test 13 | 14 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/ImportTaskDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Dialogs; 4 | 5 | public partial class ImportTaskDialog : UserControl 6 | { 7 | public ImportTaskDialog() 8 | { 9 | InitializeComponent(); 10 | } 11 | 12 | internal static void FilePicker() 13 | { 14 | OpenFileDialog dlgOpen = new() 15 | { 16 | Title = GetStringResource("ImportTask_FilePickerTitle"), 17 | Multiselect = false, 18 | CheckFileExists = true, 19 | CheckPathExists = true, 20 | Filter = "XML files (*.xml)|*.xml" 21 | }; 22 | bool? result = dlgOpen.ShowDialog(); 23 | if (result == true) 24 | { 25 | TempSettings.Setting!.ImportXMLFile = dlgOpen.FileName; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/ScrollBarStyle.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/TextBoxStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 18 | 19 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/ClipboardHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | using static Vanara.PInvoke.User32; 4 | 5 | namespace MyScheduledTasks.Helpers; 6 | 7 | internal static class ClipboardHelper 8 | { 9 | #region Copy text to clipboard 10 | private const uint _const_CF_UNICODETEXT = 13; 11 | 12 | /// 13 | /// Copies text to clipboard using Vanara PInvoke instead of DllImport 14 | /// 15 | /// Text to be placed in the Windows clipboard 16 | public static bool CopyTextToClipboard(string text) 17 | { 18 | if (!OpenClipboard(IntPtr.Zero) || text.Length < 1) 19 | { 20 | return false; 21 | } 22 | 23 | IntPtr global = Marshal.StringToHGlobalUni(text); 24 | 25 | _ = SetClipboardData(_const_CF_UNICODETEXT, global); 26 | _ = CloseClipboard(); 27 | 28 | return true; 29 | } 30 | #endregion Copy text to clipboard 31 | } 32 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/MenuItemEnabledConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | /// 5 | /// This converter is used to enable/disable menu items 6 | /// depending on the number of items selected in the DataGrid 7 | /// 8 | internal sealed class MenuItemEnabledConverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | if (value is int selected && parameter is string desired) 13 | { 14 | return desired.ToLowerInvariant() switch 15 | { 16 | "one" => selected == 1, 17 | "oneormore" => selected >= 1, 18 | _ => (object)false, 19 | }; 20 | } 21 | return true; 22 | } 23 | 24 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 25 | { 26 | return Binding.DoNothing; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/FontSizeConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | /// 5 | /// Converter to increase or decrease font size. 6 | /// Amount of increase or decrease is passed as a parameter. 7 | /// Increase by passing a positive number, decrease by passing a negative number. 8 | /// 9 | internal sealed class FontSizeConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | if (value is not null 14 | && targetType == typeof(double) 15 | && parameter is string parm 16 | && double.TryParse(parm, out double newFontSize)) 17 | { 18 | return (double)value + newFontSize; 19 | } 20 | return value!; 21 | } 22 | 23 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 24 | { 25 | return Binding.DoNothing; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/InvertibleBooleanToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | /// 5 | /// Converts a boolean value to a Visibility value. 6 | /// Inspired by https://stackoverflow.com/a/2427307/15237757 7 | /// 8 | public class InvertibleBooleanToVisibilityConverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | if (value is not bool val) 13 | { 14 | return Binding.DoNothing; 15 | } 16 | return string.Equals(parameter?.ToString(), "Inverted", StringComparison.OrdinalIgnoreCase) 17 | ? !val ? Visibility.Visible : Visibility.Collapsed 18 | : (val ? Visibility.Visible : Visibility.Collapsed); 19 | } 20 | 21 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 22 | { 23 | return Binding.DoNothing; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/ListBoxStyles.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 19 | 20 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/DataTemplates.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/LocalizedDescriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Allows a Description Attribute in an Enum to be localized 7 | /// 8 | /// 9 | /// Based on https://brianlagunas.com/localize-enum-descriptions-in-wpf/ 10 | /// 11 | /// 12 | internal sealed class LocalizedDescriptionAttribute(string resourceKey) : DescriptionAttribute 13 | { 14 | public override string Description 15 | { 16 | get 17 | { 18 | object description; 19 | try 20 | { 21 | description = Application.Current.TryFindResource(resourceKey); 22 | } 23 | catch (Exception) 24 | { 25 | return $"{resourceKey} value is null"; 26 | } 27 | 28 | return description is null ? $"{resourceKey} resource not found" : description.ToString()!; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.yml: -------------------------------------------------------------------------------- 1 | name: Enhancement / New Feature Request 💡 2 | description: Suggest an enhancement or new feature 3 | title: "Enhancement Request" 4 | labels: ["enhancement", "needs triage"] 5 | assignees: Timthreetwelve 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: "### If you have an idea for a feature or enhancement that would make the app better, tell me about it ###" 10 | - type: input 11 | id: summary 12 | attributes: 13 | label: Brief summary of your request 14 | description: Enter a short description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: request 19 | attributes: 20 | label: Request details 21 | description: Tell me what you would like to see. Feel free to attach an image file if it helps describe your idea. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: anything-else 26 | attributes: 27 | label: Anything else? 28 | description: Is there an example that you can point me to? Provide a link here. 29 | validations: 30 | required: false 31 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/SpacingConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Converter that changes Spacing to Thickness 7 | /// 8 | internal sealed class SpacingConverter : IValueConverter 9 | { 10 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | if (value is Spacing spacing) 13 | { 14 | switch (spacing) 15 | { 16 | case Spacing.Compact: 17 | return new Thickness(15, 2, 15, 2); 18 | case Spacing.Comfortable: 19 | return new Thickness(15, 5, 15, 5); 20 | case Spacing.Wide: 21 | return new Thickness(15, 7, 15, 7); 22 | } 23 | } 24 | return new Thickness(15, 10, 15, 10); 25 | } 26 | 27 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 28 | { 29 | return Binding.DoNothing; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2024 Tim Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MyScheduledTasks/Models/NavigationItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Models; 4 | 5 | /// 6 | /// Class defining properties of a Navigation Item 7 | /// 8 | public partial class NavigationItem : ObservableObject 9 | { 10 | [ObservableProperty] 11 | private PackIconKind _iconKind = PackIconKind.QuestionMark; 12 | 13 | [ObservableProperty] 14 | private bool _isExit; 15 | 16 | [ObservableProperty] 17 | private bool _isLaunch; 18 | 19 | [ObservableProperty] 20 | private bool _isRestart; 21 | 22 | [ObservableProperty] 23 | private string? _launchArguments; 24 | 25 | [ObservableProperty] 26 | private string? _launchFile; 27 | 28 | [ObservableProperty] 29 | private bool _launchShellExecute; 30 | 31 | [ObservableProperty] 32 | private string? _name; 33 | 34 | [ObservableProperty] 35 | private NavPage _navPage; 36 | 37 | [ObservableProperty] 38 | private string? _pageTitle = "Page Title Goes Here"; 39 | 40 | [ObservableProperty] 41 | private object? _viewModelType; 42 | } 43 | -------------------------------------------------------------------------------- /MyScheduledTasks/License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - 2024 Tim Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MyScheduledTasks/Models/MyTasks.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Models; 4 | 5 | public partial class MyTasks : ObservableObject 6 | { 7 | #region Constructor 8 | public MyTasks(string taskPath, bool alert, string taskNote) 9 | { 10 | TaskPath = taskPath; 11 | Alert = alert; 12 | TaskNote = taskNote; 13 | } 14 | #endregion Constructor 15 | 16 | #region Properties 17 | [ObservableProperty] 18 | private string _taskPath; 19 | 20 | [ObservableProperty] 21 | private bool _alert; 22 | 23 | [ObservableProperty] 24 | private string _taskNote; 25 | 26 | private static bool _isDirty; 27 | public static bool IsDirty 28 | { 29 | get => _isDirty; 30 | set 31 | { 32 | _isDirty = value; 33 | TaskHelpers.IsDirtyChanged(); 34 | } 35 | } 36 | 37 | public static bool IgnoreChanges { get; set; } 38 | #endregion Properties 39 | 40 | #region Observable collection 41 | public static ObservableCollection? MyTasksCollection { get; set; } 42 | #endregion Observable collection 43 | } 44 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/EditNote.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Dialogs; 4 | 5 | /// 6 | /// Dialog used to edit the TaskNote property 7 | /// 8 | public partial class EditNote : UserControl 9 | { 10 | public EditNote(ScheduledTask task) 11 | { 12 | InitializeComponent(); 13 | 14 | DataContext = task; 15 | 16 | Loaded += (_, _) => 17 | { 18 | NoteTextBox.CaretIndex = NoteTextBox.Text.Length; 19 | NoteTextBox.ScrollToEnd(); 20 | NoteTextBox.Focus(); 21 | }; 22 | } 23 | 24 | #region TextBox key down event 25 | /// Handles the KeyDown event of the TextBox control. 26 | private void TextBox_KeyDown(object sender, KeyEventArgs e) 27 | { 28 | // Update property when enter is pressed 29 | if (e.Key == Key.Enter) 30 | { 31 | // https://stackoverflow.com/a/13289118 32 | TextBox tBox = (TextBox)sender; 33 | DependencyProperty prop = TextBox.TextProperty; 34 | BindingExpression? binding = BindingOperations.GetBindingExpression(tBox, prop); 35 | binding?.UpdateSource(); 36 | } 37 | } 38 | #endregion 39 | } 40 | -------------------------------------------------------------------------------- /MyScheduledTasks/PowerShell/GenBuildInfo.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory = $true)] [string] $assemblyName, 3 | [Parameter(Mandatory = $false)] [string] $outputFile="BuildInfo.cs" 4 | ) 5 | 6 | $nowUTC = (Get-Date).ToUniversalTime().ToString('yyyy/MM/dd HH:mm:ss') 7 | 8 | $class = 9 | "// Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 10 | 11 | // This file is generated during a pre-build event by PowerShell\GenBuildInfo.ps1. 12 | // Any edits to this file will be overwritten during the next build! 13 | 14 | namespace $assemblyName; 15 | 16 | public static class BuildInfo 17 | { 18 | public static readonly string CommitIDString = ThisAssembly.GitCommitId[..7]; 19 | 20 | public static readonly string CommitIDFullString = ThisAssembly.GitCommitId; 21 | 22 | public const string BuildDateString = `"$nowUTC`"; 23 | 24 | public static readonly DateTime BuildDateUtc = 25 | DateTime.SpecifyKind( 26 | DateTime.ParseExact(BuildDateString, `"yyyy/MM/dd HH:mm:ss`", CultureInfo.InvariantCulture), 27 | DateTimeKind.Utc); 28 | 29 | public static readonly string BuildDateStringUtc = $`"{BuildDateUtc:f} (UTC)`"; 30 | }" 31 | 32 | $outputPath = Join-Path -Path $(Get-Location).Path -ChildPath $outputFile 33 | Set-Content -Path "$outputPath" -Value $class -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/EnumDescriptionTypeConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Enum description converter. 7 | /// 8 | /// 9 | /// Based on https://brianlagunas.com/localize-enum-descriptions-in-wpf/ 10 | /// 11 | internal sealed class EnumDescriptionTypeConverter(Type type) : EnumConverter(type) 12 | { 13 | public override object ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) 14 | { 15 | if (destinationType == typeof(string)) 16 | { 17 | if (value != null) 18 | { 19 | FieldInfo? fi = value.GetType().GetField(value.ToString()!); 20 | if (fi != null) 21 | { 22 | DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); 23 | if ((attributes.Length > 0) && (!string.IsNullOrEmpty(attributes[0].Description))) 24 | { 25 | return attributes[0].Description; 26 | } 27 | return value.ToString()!; 28 | } 29 | } 30 | return string.Empty; 31 | } 32 | return base.ConvertTo(context, culture, value, destinationType)!; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MyScheduledTasks/Configuration/TempSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Configuration; 4 | 5 | /// 6 | /// Class for non-persistent settings. 7 | /// 8 | [INotifyPropertyChanged] 9 | internal sealed partial class TempSettings : ConfigManager 10 | { 11 | [ObservableProperty] 12 | private static bool _appExpanderOpen; 13 | 14 | [ObservableProperty] 15 | private static bool _columnsExpanderOpen; 16 | 17 | [ObservableProperty] 18 | private static bool _langExpanderOpen; 19 | 20 | [ObservableProperty] 21 | private static bool _uIExpanderOpen; 22 | 23 | [ObservableProperty] 24 | private static bool _importAddToMyTasks = true; 25 | 26 | [ObservableProperty] 27 | private static string? _importXMLFile; 28 | 29 | [ObservableProperty] 30 | private static string? _importTaskName; 31 | 32 | [ObservableProperty] 33 | private static bool _importOverwrite; 34 | 35 | [ObservableProperty] 36 | private static bool _importRunOnlyLoggedOn = true; 37 | 38 | [ObservableProperty] 39 | private static bool _importResetCreationDate = true; 40 | 41 | [ObservableProperty] 42 | private static bool _backupExpanderOpen; 43 | 44 | [ObservableProperty] 45 | private static bool _checkedForNewRelease; 46 | 47 | [ObservableProperty] 48 | private static bool _newReleaseAvailable; 49 | 50 | [ObservableProperty] 51 | private static string _gitHubRelease = string.Empty; 52 | } 53 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/ScreenHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class ScreenHelpers 6 | { 7 | #region Reposition off-screen window back to the desktop 8 | /// 9 | /// Keep the window on the screen. 10 | /// 11 | public static void KeepWindowOnScreen(Window? window) 12 | { 13 | if (window is null || UserSettings.Setting!.StartCentered) 14 | { 15 | return; 16 | } 17 | 18 | // the SystemParameters properties work better for this method than Screen properties. 19 | if (window.Top < SystemParameters.VirtualScreenTop) 20 | { 21 | window.Top = SystemParameters.VirtualScreenTop; 22 | } 23 | 24 | if (window.Left < SystemParameters.VirtualScreenLeft) 25 | { 26 | window.Left = SystemParameters.VirtualScreenLeft; 27 | } 28 | 29 | if (window.Left + window.Width > SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth) 30 | { 31 | window.Left = SystemParameters.VirtualScreenWidth + SystemParameters.VirtualScreenLeft - window.Width; 32 | } 33 | 34 | if (window.Top + window.Height > SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight) 35 | { 36 | window.Top = SystemParameters.WorkArea.Size.Height + SystemParameters.VirtualScreenTop - window.Height; 37 | } 38 | } 39 | #endregion Reposition off-screen window back to the desktop 40 | } 41 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/DialogHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class DialogHelpers 6 | { 7 | #region Import dialog 8 | /// 9 | /// Shows the Import Tasks dialog. 10 | /// 11 | internal static async System.Threading.Tasks.Task ShowImportTaskDialog() 12 | { 13 | MainWindowHelpers.MainWindowNotAllowedPointer(); 14 | ImportTaskDialog importDialog = new(); 15 | _ = await DialogHost.Show(importDialog, "MainDialogHost"); 16 | MainWindowHelpers.MainWindowNormalPointer(); 17 | } 18 | #endregion Import dialog 19 | 20 | #region Delete dialog 21 | /// 22 | /// Shows the Delete Tasks dialog. 23 | /// 24 | internal static async System.Threading.Tasks.Task ShowDeleteTasksDialog() 25 | { 26 | MainWindowHelpers.MainWindowNotAllowedPointer(); 27 | DeleteTasksDialog deleteDialog = new(); 28 | _ = await DialogHost.Show(deleteDialog, "MainDialogHost"); 29 | MainWindowHelpers.MainWindowNormalPointer(); 30 | } 31 | #endregion Delete dialog 32 | 33 | #region Edit note dialog 34 | /// 35 | /// Shows the Edit Note dialog 36 | /// 37 | /// task to edit note property 38 | internal static async System.Threading.Tasks.Task ShowEditNoteDialog(ScheduledTask task) 39 | { 40 | MainWindowHelpers.MainWindowNotAllowedPointer(); 41 | EditNote en = new(task); 42 | _ = await DialogHost.Show(en, "MainDialogHost"); 43 | MainWindowHelpers.MainWindowNormalPointer(); 44 | } 45 | #endregion Edit note dialog 46 | } 47 | -------------------------------------------------------------------------------- /MyScheduledTasks/Converters/EnumBindingSourceExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | #nullable disable 3 | namespace MyScheduledTasks.Converters; 4 | 5 | /// 6 | /// Markup Extension used to allow an Enum to be used as an ItemsSource 7 | /// 8 | /// 9 | /// Based on https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/ 10 | /// 11 | /// 12 | internal sealed class EnumBindingSourceExtension : MarkupExtension 13 | { 14 | private Type _enumType; 15 | public Type EnumType 16 | { 17 | set 18 | { 19 | if (value == _enumType) return; 20 | if (value != null) 21 | { 22 | Type enumType = Nullable.GetUnderlyingType(value) ?? value; 23 | if (!enumType.IsEnum) 24 | throw new ArgumentException("Type must be for an Enum."); 25 | } 26 | _enumType = value; 27 | } 28 | } 29 | 30 | public EnumBindingSourceExtension(Type enumType) 31 | { 32 | EnumType = enumType; 33 | } 34 | 35 | public override object ProvideValue(IServiceProvider serviceProvider) 36 | { 37 | if (_enumType == null) 38 | throw new InvalidOperationException("The EnumType must be specified."); 39 | 40 | Type actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; 41 | Array enumValues = Enum.GetValues(actualEnumType); 42 | 43 | if (actualEnumType == _enumType) 44 | return enumValues; 45 | 46 | Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); 47 | enumValues.CopyTo(tempArray, 1); 48 | return tempArray; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/CommandLineHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class CommandLineHelpers 6 | { 7 | #region Properties 8 | public static string? CommandLineParserError { get; private set; } 9 | public static bool Administrator { get; private set; } 10 | public static bool Hide { get; private set; } 11 | #endregion Properties 12 | 13 | #region Command line parameters 14 | public static void ProcessCommandLine() 15 | { 16 | using CommandLineParser.CommandLineParser parser = new(); 17 | SwitchArgument hideArgument = new('h', 18 | "hide", 19 | string.Empty, 20 | false); 21 | SwitchArgument adminArgument = new('a', 22 | "administrator", 23 | string.Empty, 24 | false); 25 | parser.Arguments.Add(hideArgument); 26 | parser.Arguments.Add(adminArgument); 27 | parser.AcceptSlash = true; 28 | parser.IgnoreCase = true; 29 | try 30 | { 31 | parser.ParseCommandLine(App.Args); 32 | Hide = hideArgument.Value; 33 | Administrator = adminArgument.Value; 34 | } 35 | catch (UnknownArgumentException e) 36 | { 37 | CommandLineParserError = "Command line argument: " + e.Message; 38 | } 39 | catch (Exception e) 40 | { 41 | CommandLineParserError = "Command line argument: " + e.Message + e.StackTrace; 42 | } 43 | } 44 | #endregion Command line parameters 45 | } 46 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/SnackbarMsg.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Dialogs; 4 | 5 | /// 6 | /// Methods for displaying snackbar messages 7 | /// 8 | public static class SnackbarMsg 9 | { 10 | #region Clear message queue then queue a message (default duration) 11 | public static void ClearAndQueueMessage(string message) 12 | { 13 | (Application.Current.MainWindow as MainWindow)?.SnackBar1.MessageQueue!.Clear(); 14 | (Application.Current.MainWindow as MainWindow)?.SnackBar1.MessageQueue!.Enqueue(message); 15 | } 16 | #endregion Clear message queue then queue a message (default duration) 17 | 18 | #region Clear message queue then queue a message and set duration 19 | public static void ClearAndQueueMessage(string message, int duration) 20 | { 21 | (Application.Current.MainWindow as MainWindow)?.SnackBar1.MessageQueue!.Clear(); 22 | (Application.Current.MainWindow as MainWindow)?.SnackBar1.MessageQueue!.Enqueue(message, 23 | null, 24 | null, 25 | null, 26 | false, 27 | true, 28 | TimeSpan.FromMilliseconds(duration)); 29 | } 30 | #endregion Clear message queue then queue a message and set duration 31 | 32 | #region Queue a message and set duration (without clearing first) 33 | public static void QueueMessage(string message, int duration) 34 | { 35 | (Application.Current.MainWindow as MainWindow)?.SnackBar1.MessageQueue!.Enqueue(message, 36 | null, 37 | null, 38 | null, 39 | false, 40 | true, 41 | TimeSpan.FromMilliseconds(duration)); 42 | } 43 | #endregion Queue a message and set duration (without clearing first) 44 | } 45 | -------------------------------------------------------------------------------- /MyScheduledTasks/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | // Like it says on the tin, this is a global using file 4 | 5 | global using System; 6 | global using System.Collections; 7 | global using System.Collections.Generic; 8 | global using System.Collections.ObjectModel; 9 | global using System.ComponentModel; 10 | global using System.Diagnostics; 11 | global using System.Globalization; 12 | global using System.IO; 13 | global using System.Linq; 14 | global using System.Media; 15 | global using System.Reflection; 16 | global using System.Runtime.InteropServices; 17 | global using System.Runtime.Versioning; 18 | global using System.Security.Principal; 19 | global using System.Text; 20 | global using System.Text.Json; 21 | global using System.Threading; 22 | global using System.Windows; 23 | global using System.Windows.Controls; 24 | global using System.Windows.Data; 25 | global using System.Windows.Input; 26 | global using System.Windows.Markup; 27 | global using System.Windows.Media; 28 | 29 | global using CommandLineParser.Arguments; 30 | global using CommandLineParser.Exceptions; 31 | 32 | global using CommunityToolkit.Mvvm.ComponentModel; 33 | global using CommunityToolkit.Mvvm.Input; 34 | 35 | global using MaterialDesignColors; 36 | global using MaterialDesignThemes.Wpf; 37 | 38 | global using Microsoft.Win32; 39 | global using Microsoft.Win32.TaskScheduler; 40 | 41 | global using MyScheduledTasks.Configuration; 42 | global using MyScheduledTasks.Constants; 43 | global using MyScheduledTasks.Converters; 44 | global using MyScheduledTasks.Dialogs; 45 | global using MyScheduledTasks.Helpers; 46 | global using MyScheduledTasks.Models; 47 | global using MyScheduledTasks.ViewModels; 48 | global using MyScheduledTasks.Views; 49 | 50 | global using static MyScheduledTasks.Helpers.NLogHelpers; 51 | global using static MyScheduledTasks.Helpers.ResourceHelpers; 52 | 53 | global using NLog; 54 | global using NLog.Config; 55 | global using NLog.Targets; 56 | -------------------------------------------------------------------------------- /MyScheduledTasks/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /MyScheduledTasks/Languages/Strings.en-GB.xaml: -------------------------------------------------------------------------------- 1 | 4 | 28 | 29 | 30 | Accent colour set to {0} 31 | 32 | Theme, Accent Colour, UI Size, Row Height, Start Position and more 33 | 34 | Accent Colour 35 | Start with window centred on screen 36 | 37 | Blue Grey 38 | Grey 39 | 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/translation_issue.yml: -------------------------------------------------------------------------------- 1 | name: Translation Issues ✏️ 2 | description: Contribute a translation or report a problem 3 | title: "Translation: " 4 | labels: ["Translation"] 5 | assignees: Timthreetwelve 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: "## Contribute a translation or report a problem ##" 10 | - type: markdown 11 | attributes: 12 | value: "*Please add the language name to the subject line above*" 13 | - type: markdown 14 | attributes: 15 | value: "### Please refer to the Contribute a Translation page in the Wiki before contributing ###" 16 | - type: textarea 17 | id: problem 18 | attributes: 19 | label: Report a problem with a translation 20 | description: Tell me what is wrong, alert me to a missing translation or suggest a better translation 21 | validations: 22 | required: false 23 | - type: textarea 24 | id: contribution 25 | attributes: 26 | label: Contribute a translation 27 | description: Describe the translation and attach the file here 28 | placeholder: This is my translation of ... 29 | render: shell 30 | validations: 31 | required: false 32 | - type: input 33 | id: language 34 | attributes: 35 | label: If this is a contribution, enter the language name and code 36 | description: For example, English (en-US). The code is important. 37 | placeholder: English (en-US) 38 | validations: 39 | required: false 40 | - type: input 41 | id: local 42 | attributes: 43 | label: If this is a contribution, enter the language name as it is spelled locally 44 | description: For example, if the language was Spanish enter Español. 45 | placeholder: Español 46 | validations: 47 | required: false 48 | - type: input 49 | id: acknowledge 50 | attributes: 51 | label: If you wish to have your contribution acknowledged 52 | description: Enter your GitHub ID, a name or a short phrase that will appear on the About page 53 | validations: 54 | required: false 55 | -------------------------------------------------------------------------------- /MyScheduledTasks/Views/AddTasks.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Views; 4 | 5 | public partial class AddTasks : UserControl 6 | { 7 | public static AddTasks? Instance { get; private set; } 8 | 9 | public AddTasks() 10 | { 11 | InitializeComponent(); 12 | 13 | Instance = this; 14 | 15 | AddTasksViewModel.HideTasks(AllTasksGrid); 16 | AddTasksViewModel.StaticPropertyChanged += AddTasksViewModel_StaticPropertyChanged!; 17 | } 18 | 19 | private void AddTasksViewModel_StaticPropertyChanged(object sender, PropertyChangedEventArgs e) 20 | { 21 | FilterTheGrid(AddTasksViewModel.FilterText); 22 | } 23 | 24 | #region Filter the datagrid 25 | /// 26 | /// Filters the grid. 27 | /// 28 | private void FilterTheGrid(string filterText) 29 | { 30 | if (string.IsNullOrEmpty(filterText)) 31 | { 32 | AllTasksGrid.Items.Filter = _ => true; 33 | } 34 | else if (filterText.StartsWith('!')) 35 | { 36 | filterText = filterText[1..].TrimStart(' '); 37 | AllTasksGrid.Items.Filter = o => 38 | { 39 | AllTasks? tasks = o as AllTasks; 40 | return !tasks!.TaskName!.Contains(filterText, StringComparison.CurrentCultureIgnoreCase); 41 | }; 42 | } 43 | else 44 | { 45 | AllTasksGrid.Items.Filter = o => 46 | { 47 | AllTasks? tasks = o as AllTasks; 48 | return tasks!.TaskName!.Contains(filterText, StringComparison.CurrentCultureIgnoreCase); 49 | }; 50 | } 51 | 52 | SnackbarMsg.ClearAndQueueMessage( 53 | AllTasksGrid.Items.Count == 1 54 | ? GetStringResource("MsgText_FilterOneRowShown") 55 | : string.Format(CultureInfo.InvariantCulture, MsgTextFilterRowsShown, AllTasksGrid.Items.Count), 2000); 56 | } 57 | #endregion Filter the datagrid 58 | } 59 | -------------------------------------------------------------------------------- /MyScheduledTasks/Views/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Views; 4 | 5 | public partial class MainPage : UserControl 6 | { 7 | #region MainPage Instance 8 | public static MainPage? Instance { get; private set; } 9 | #endregion MainPage Instance 10 | 11 | #region Constructor 12 | public MainPage() 13 | { 14 | InitializeComponent(); 15 | 16 | Instance = this; 17 | 18 | // The following fixes binding ElementName in the Context Menu. 19 | // Credit: https://stackoverflow.com/a/1066009 20 | NameScope.SetNameScope(DgContextMenu, NameScope.GetNameScope(this)); 21 | 22 | // Details pane size 23 | DetailsRow.Height = !UserSettings.Setting!.ShowDetails 24 | ? new GridLength(1) 25 | : new GridLength(UserSettings.Setting.DetailsHeight); 26 | } 27 | #endregion Constructor 28 | 29 | #region Clear column sort 30 | /// 31 | /// Clears any sorts that may have been applied to columns in the data grid 32 | /// 33 | internal void ClearColumnSort() 34 | { 35 | foreach (DataGridColumn column in DataGridTasks.Columns) 36 | { 37 | column.SortDirection = null; 38 | } 39 | DataGridTasks.Items.SortDescriptions.Clear(); 40 | 41 | SnackbarMsg.ClearAndQueueMessage(GetStringResource("MsgText_ColumnSortCleared")); 42 | } 43 | #endregion Clear column sort 44 | 45 | #region GridSplitter drag completed 46 | private void GridSplitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) 47 | { 48 | UserSettings.Setting!.DetailsHeight = DetailsRow.Height.Value; 49 | } 50 | #endregion GridSplitter drag completed 51 | 52 | #region DataGrid row drag & drop 53 | private void DataGridTasksDrop(object sender, DragEventArgs e) 54 | { 55 | if (Equals(e.Source, DataGridTasks)) 56 | { 57 | _ = System.Threading.Tasks.Task.Run(TaskHelpers.UpdateMyTasksAfterDrop); 58 | } 59 | } 60 | #endregion DataGrid row drag & drop 61 | } 62 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/MenuStyles.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 22 | 23 | 34 | 35 | 46 | 47 | -------------------------------------------------------------------------------- /MyScheduledTasks.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32228.430 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyScheduledTasks", "MyScheduledTasks\MyScheduledTasks.csproj", "{A6785345-1687-4C21-81AA-D0B3626B71AC}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0C8B71D0-B5B6-45A3-94D0-8C6DDA62D8DF}" 9 | ProjectSection(SolutionItems) = preProject 10 | .github\ISSUE_TEMPLATE\bug_report.yml = .github\ISSUE_TEMPLATE\bug_report.yml 11 | .github\ISSUE_TEMPLATE\config.yml = .github\ISSUE_TEMPLATE\config.yml 12 | .github\dependabot.yml = .github\dependabot.yml 13 | .github\ISSUE_TEMPLATE\enhancement.yml = .github\ISSUE_TEMPLATE\enhancement.yml 14 | NewReleaseTemplate.md = NewReleaseTemplate.md 15 | README.md = README.md 16 | .github\ISSUE_TEMPLATE\translation_issue.yml = .github\ISSUE_TEMPLATE\translation_issue.yml 17 | version.json = version.json 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|x64 = Debug|x64 24 | Release|Any CPU = Release|Any CPU 25 | Release|x64 = Release|x64 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Debug|x64.ActiveCfg = Debug|x64 31 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Debug|x64.Build.0 = Debug|x64 32 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Release|x64.ActiveCfg = Release|x64 35 | {A6785345-1687-4C21-81AA-D0B3626B71AC}.Release|x64.Build.0 = Release|x64 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {E47F7C54-2EED-4181-A4BC-7FBE5EC22B42} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/TextFileViewer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | // Comment out the following if MessageBox is not to be used 4 | #define messagebox 5 | 6 | namespace MyScheduledTasks.Helpers; 7 | 8 | /// 9 | /// Class for viewing text files. If the file extension is not associated 10 | /// with an application, notepad.exe will be attempted. 11 | /// 12 | public static class TextFileViewer 13 | { 14 | #region Text file viewer 15 | /// 16 | /// Opens specified text file 17 | /// 18 | /// Full path for text file 19 | /// 20 | public static void ViewTextFile(string textFile) 21 | { 22 | try 23 | { 24 | using Process p = new(); 25 | p.StartInfo.FileName = textFile; 26 | p.StartInfo.UseShellExecute = true; 27 | p.StartInfo.ErrorDialog = false; 28 | _ = p.Start(); 29 | } 30 | catch (Win32Exception ex) 31 | { 32 | if (ex.NativeErrorCode == 1155) 33 | { 34 | using Process p = new(); 35 | p.StartInfo.FileName = "notepad.exe"; 36 | p.StartInfo.Arguments = textFile; 37 | p.StartInfo.UseShellExecute = true; 38 | p.StartInfo.ErrorDialog = false; 39 | _ = p.Start(); 40 | _log.Debug($"Opening {textFile} in Notepad.exe"); 41 | } 42 | else 43 | { 44 | #if messagebox 45 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextErrorReadingFile, textFile); 46 | _ = MessageBox.Show($"{msg}\n{ex.Message}", 47 | GetStringResource("MsgText_ErrorCaption"), 48 | MessageBoxButton.OK, 49 | MessageBoxImage.Error); 50 | #endif 51 | _log.Error(ex, $"Unable to open {textFile}"); 52 | } 53 | } 54 | catch (Exception ex) 55 | { 56 | #if messagebox 57 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextErrorOpeningFile, textFile); 58 | _ = MessageBox.Show($"{msg}\n{ex.Message}", 59 | GetStringResource("MsgText_ErrorCaption"), 60 | MessageBoxButton.OK, 61 | MessageBoxImage.Error); 62 | #endif 63 | _log.Error(ex, $"Unable to open {textFile}"); 64 | } 65 | } 66 | #endregion Text file viewer 67 | } 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 🐛 2 | description: Find a bug? File a bug report! 3 | title: "Bug Report:" 4 | labels: ["bug", "needs triage"] 5 | assignees: Timthreetwelve 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | - type: checkboxes 12 | attributes: 13 | label: Is there an existing issue for this? 14 | description: Please search to see if an issue already exists for the bug you encountered. 15 | options: 16 | - label: I have searched the existing issues 17 | required: true 18 | - type: textarea 19 | id: what-happened 20 | attributes: 21 | label: What happened or didn't happen 22 | description: Describe what happened. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: steps_to_reproduce 27 | attributes: 28 | label: Steps To Reproduce 29 | description: Steps to reproduce the behavior. 30 | placeholder: | 31 | 1. In this environment... 32 | 2. With this config... 33 | 3. Run '...' 34 | 4. See error... 35 | validations: 36 | required: false 37 | - type: input 38 | id: version 39 | attributes: 40 | label: Version 41 | description: What version the app are you running? 42 | placeholder: ex. 1.0.0 43 | validations: 44 | required: true 45 | - type: input 46 | id: windows-version 47 | attributes: 48 | label: Windows Version 49 | description: What version of Windows are you running? Include the build number if possible. 50 | placeholder: ex. Windows 11 22H2 build 22621.1702 51 | validations: 52 | required: true 53 | - type: textarea 54 | id: logs 55 | attributes: 56 | label: Relevant log output 57 | description: | 58 | Please copy and paste (or attach) any relevant log output. 59 | 60 | You can open the application log file by clicking on the three dot menu in the upper right and selecting View Log File. 61 | If the error occurred during installation, paste the setup log (found in the %Temp% directory). The file name will look like *Setup Log date sequence#.txt* 62 | validations: 63 | required: false 64 | - type: textarea 65 | id: anything-else 66 | attributes: 67 | label: Anything else? 68 | description: | 69 | Links? References? Anything that will give us more context about the issue you are encountering! 70 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 71 | validations: 72 | required: false 73 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /MyScheduledTasks/ViewModels/AboutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | using System.Threading.Tasks; 4 | 5 | namespace MyScheduledTasks.ViewModels; 6 | 7 | public partial class AboutViewModel : ObservableObject 8 | { 9 | [ObservableProperty] 10 | private bool _isNewReleaseAvailable; 11 | 12 | [ObservableProperty] 13 | private bool _releaseCheckDone; 14 | 15 | #region Constructor 16 | public AboutViewModel() 17 | { 18 | if (AnnotatedLanguageList.Count == 0) 19 | { 20 | AddNote(); 21 | } 22 | _ = CheckForNewReleaseOnLoadAsync(); 23 | } 24 | #endregion Constructor 25 | 26 | private static async Task CheckForNewReleaseOnLoadAsync() 27 | { 28 | if (TempSettings.Setting!.CheckedForNewRelease && UserSettings.Setting!.AutoCheckForUpdates) 29 | { 30 | return true; 31 | } 32 | TempSettings.Setting.CheckedForNewRelease = true; 33 | TempSettings.Setting.NewReleaseAvailable = await GitHubHelpers.CheckForNewReleaseAsync(); 34 | TempSettings.Setting.GitHubRelease = GitHubHelpers.GitHubVersion?.ToString() ?? string.Empty; 35 | Debug.WriteLine($"IsNewReleaseAvailable: {TempSettings.Setting.NewReleaseAvailable}, NewReleaseVersion: {TempSettings.Setting.GitHubRelease}"); // For testing 36 | return true; 37 | } 38 | 39 | #region Relay Commands 40 | [RelayCommand] 41 | private static void ViewLicense() 42 | { 43 | string dir = AppInfo.AppDirectory; 44 | TextFileViewer.ViewTextFile(Path.Combine(dir, "License.txt")); 45 | } 46 | 47 | [RelayCommand] 48 | private static void ViewReadMe() 49 | { 50 | string dir = AppInfo.AppDirectory; 51 | TextFileViewer.ViewTextFile(Path.Combine(dir, "ReadMe.txt")); 52 | } 53 | 54 | [RelayCommand] 55 | private static void GoToWebPage(string url) 56 | { 57 | Process p = new(); 58 | p.StartInfo.FileName = url; 59 | p.StartInfo.UseShellExecute = true; 60 | p.Start(); 61 | } 62 | 63 | [RelayCommand] 64 | private static async System.Threading.Tasks.Task CheckReleaseAsync() 65 | { 66 | await GitHubHelpers.CheckRelease(); 67 | } 68 | #endregion Relay Commands 69 | 70 | #region Annotated Language translation list 71 | public List AnnotatedLanguageList { get; } = []; 72 | #endregion Annotated Language translation list 73 | 74 | #region Add note to list of languages 75 | private void AddNote() 76 | { 77 | foreach (UILanguage item in UILanguage.DefinedLanguages.Where(item => item.LanguageCode is not "en-GB")) 78 | { 79 | item.Note = GetLanguagePercent(item.LanguageCode!); 80 | AnnotatedLanguageList.Add(item); 81 | } 82 | } 83 | #endregion Add note to list of languages 84 | } 85 | -------------------------------------------------------------------------------- /MyScheduledTasks/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.ViewModels; 4 | 5 | internal sealed partial class MainViewModel : ObservableObject 6 | { 7 | public static List TasksToDelete { get; } = []; 8 | 9 | #region Load the task list 10 | /// 11 | /// Load the task list from MyTasksCollection 12 | /// 13 | public static void LoadData() 14 | { 15 | CheckTaskFile(MyTasks.MyTasksCollection!); 16 | 17 | MyTasks.IgnoreChanges = true; 18 | 19 | int added = 0; 20 | ScheduledTask.TaskList.Clear(); 21 | for (int i = 0; i < MyTasks.MyTasksCollection!.Count; i++) 22 | { 23 | MyTasks item = MyTasks.MyTasksCollection[i]; 24 | Task? task = TaskInfo.GetTaskInfo(item.TaskPath); 25 | if (task is not null) 26 | { 27 | ScheduledTask scheduledTask = ScheduledTask.BuildScheduledTask(task, item); 28 | ScheduledTask.TaskList.Add(scheduledTask); 29 | _log.Debug($"Added {i + 1,3}: \"{item.TaskPath}\""); 30 | } 31 | else 32 | { 33 | _log.Warn($"No information found for \"{item.TaskPath}\""); 34 | string msg = $"{GetStringResource("MsgText_NoInformationFound")} {item.TaskPath}."; 35 | SnackbarMsg.QueueMessage(msg, 5000); 36 | 37 | ScheduledTask emptyTask = new() 38 | { 39 | TaskName = item.TaskPath, 40 | TaskDescription = GetStringResource("MsgText_ErrorTaskNull"), 41 | TaskStatus = GetStringResource("TaskResult_Null") 42 | }; 43 | 44 | ScheduledTask.TaskList.Add(emptyTask); 45 | added++; 46 | _log.Debug($"Added {added,3}: \"{item.TaskPath}\""); 47 | } 48 | } 49 | MyTasks.IgnoreChanges = false; 50 | } 51 | #endregion Load the task list 52 | 53 | #region Check tasks file 54 | /// 55 | /// Checks TaskPath for each entry in the tasks file for null or empty values. 56 | /// If found they will be removed from the collection and the tasks file will be rewritten. 57 | /// 58 | /// MyTasks Collection 59 | private static void CheckTaskFile(ObservableCollection tasks) 60 | { 61 | int removed = 0; 62 | for (int i = 0; i < tasks.Count; i++) 63 | { 64 | if (string.IsNullOrEmpty(tasks[i].TaskPath)) 65 | { 66 | _log.Warn($"Null or empty item found while reading {TaskFileHelpers.TasksFile}. Item will be removed. "); 67 | MyTasks.MyTasksCollection!.RemoveAt(i); 68 | removed++; 69 | } 70 | } 71 | if (removed > 0) 72 | { 73 | TaskFileHelpers.WriteTasksToFile(); 74 | } 75 | } 76 | #endregion Check tasks file 77 | } 78 | -------------------------------------------------------------------------------- /MyScheduledTasks/Configuration/SettingChange.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Configuration; 4 | 5 | /// 6 | /// Class to handle certain changes in user settings. 7 | /// 8 | public static class SettingChange 9 | { 10 | #region User Setting change 11 | /// 12 | /// Handle changes in UserSettings 13 | /// 14 | public static void UserSettingChanged(object sender, PropertyChangedEventArgs e) 15 | { 16 | object? newValue = GetPropertyValue(sender, e); 17 | _log.Debug($"Setting change: {e.PropertyName} New Value: {newValue}"); 18 | 19 | switch (e.PropertyName) 20 | { 21 | case nameof(UserSettings.Setting.IncludeDebug): 22 | SetLogLevel((bool)newValue!); 23 | break; 24 | 25 | case nameof(UserSettings.Setting.UITheme): 26 | MainWindowHelpers.SetBaseTheme((ThemeType)newValue!); 27 | break; 28 | 29 | case nameof(UserSettings.Setting.PrimaryColor): 30 | MainWindowHelpers.SetPrimaryColor((AccentColor)newValue!); 31 | break; 32 | 33 | case nameof(UserSettings.Setting.UISize): 34 | MainWindowHelpers.UIScale(UserSettings.Setting!.UISize); 35 | break; 36 | 37 | case nameof(UserSettings.Setting.LanguageTesting): 38 | case nameof(UserSettings.Setting.UILanguage): 39 | case nameof(UserSettings.Setting.UseOSLanguage): 40 | LocalizationHelpers.SaveAndRestart(); 41 | break; 42 | } 43 | } 44 | #endregion User Setting change 45 | 46 | #region Temp setting change 47 | /// 48 | /// Handle changes in TempSettings 49 | /// 50 | internal static void TempSettingChanged(object sender, PropertyChangedEventArgs e) 51 | { 52 | object? newValue = GetPropertyValue(sender, e); 53 | // Write to trace level to avoid unnecessary message in log file 54 | _log.Trace($"Temp Setting change: {e.PropertyName} New Value: {newValue}"); 55 | 56 | switch (e.PropertyName) 57 | { 58 | case nameof(TempSettings.Setting.ImportRunOnlyLoggedOn): 59 | if (!(bool)newValue!) 60 | { 61 | TaskHelpers.ImportCaution(); 62 | } 63 | break; 64 | } 65 | } 66 | #endregion Temp setting change 67 | 68 | #region Get property value 69 | /// 70 | /// Gets the value of the property 71 | /// 72 | /// 73 | /// 74 | /// An object containing the value of the property 75 | private static object? GetPropertyValue(object sender, PropertyChangedEventArgs e) 76 | { 77 | PropertyInfo? prop = sender.GetType().GetProperty(e.PropertyName!); 78 | return prop?.GetValue(sender, null); 79 | } 80 | #endregion Get property value 81 | } 82 | -------------------------------------------------------------------------------- /MyScheduledTasks/Models/UILanguage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Models; 4 | 5 | /// 6 | /// Class for language properties. 7 | /// 8 | public partial class UILanguage : ObservableObject 9 | { 10 | #region Properties 11 | /// 12 | /// The name of the contributor. Can be any string chosen by the contributor. 13 | /// 14 | [ObservableProperty] 15 | private string? _contributor; 16 | 17 | /// 18 | /// Total number of strings in the language resource dictionary. 19 | /// 20 | [ObservableProperty] 21 | private int? _currentLanguageStringCount = LocalizationHelpers.LanguageStrings; 22 | 23 | /// 24 | /// Total number of strings in the (en-US) language resource dictionary. 25 | /// 26 | [ObservableProperty] 27 | private int? _defaultStringCount = App.DefaultLanguageStrings; 28 | 29 | /// 30 | /// English spelling of the language name. 31 | /// 32 | [ObservableProperty] 33 | private string? _language; 34 | 35 | /// 36 | /// Language code in the form xx-XX 37 | /// 38 | [ObservableProperty] 39 | private string? _languageCode; 40 | 41 | /// 42 | /// Native spelling of the language name. 43 | /// 44 | [ObservableProperty] 45 | private string? _languageNative; 46 | 47 | /// 48 | /// Note field. 49 | /// 50 | [ObservableProperty] 51 | private string _note = string.Empty; 52 | #endregion Properties 53 | 54 | #region Override ToString 55 | /// 56 | /// Overrides the ToString method. 57 | /// 58 | /// 59 | /// Used to write language code to user settings file. 60 | /// 61 | /// 62 | /// The language code as a string. 63 | /// 64 | public override string? ToString() => LanguageCode; 65 | #endregion Override ToString 66 | 67 | #region List of languages 68 | /// 69 | /// List of languages with language code 70 | /// 71 | private static List LanguageList { get; } = 72 | [ 73 | new () {Language = "English", LanguageCode = "en-US", LanguageNative = "English", Contributor = "Timthreetwelve", Note="Default"}, 74 | new () {Language = "English", LanguageCode = "en-GB", LanguageNative = "English", Contributor = "Timthreetwelve"}, 75 | new () {Language = "Spanish", LanguageCode = "es-ES", LanguageNative = "Español", Contributor = "Timthreetwelve & Google Translate"}, 76 | new () {Language = "Korean", LanguageCode = "ko-KR", LanguageNative = "한국어", Contributor = "VenusGirl (비너스걸)"}, 77 | new () {Language = "Italian", LanguageCode = "it-IT", LanguageNative = "Italiano", Contributor = "bovirus"}, 78 | new () {Language = "Dutch", LanguageCode = "nl-NL", LanguageNative = "Nederlands", Contributor = "CMTRiX"}, 79 | ]; 80 | 81 | /// 82 | /// List of defined languages ordered by LanguageCode. 83 | /// 84 | public static List DefinedLanguages => [.. LanguageList.OrderBy(x => x.LanguageCode)]; 85 | #endregion List of languages 86 | } 87 | -------------------------------------------------------------------------------- /MyScheduledTasks/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.ViewModels; 4 | 5 | internal sealed partial class SettingsViewModel : ObservableObject 6 | { 7 | #region MainWindow Instance 8 | private static readonly MainWindow? _mainWindow = Application.Current.MainWindow as MainWindow; 9 | #endregion MainWindow Instance 10 | 11 | #region Properties 12 | public static List? FontList { get; private set; } 13 | #endregion Properties 14 | 15 | #region Constructor 16 | public SettingsViewModel() 17 | { 18 | FontList ??= [.. Fonts.SystemFontFamilies.OrderBy(x => x.Source)]; 19 | } 20 | #endregion Constructor 21 | 22 | #region Open App folder 23 | [RelayCommand] 24 | private static void OpenAppFolder() 25 | { 26 | string filePath = string.Empty; 27 | try 28 | { 29 | filePath = Path.Combine(AppInfo.AppDirectory, "Strings.test.xaml"); 30 | if (File.Exists(filePath)) 31 | { 32 | _ = Process.Start("explorer.exe", $"/select,\"{filePath}\""); 33 | } 34 | else 35 | { 36 | using Process p = new(); 37 | p.StartInfo.FileName = AppInfo.AppDirectory; 38 | p.StartInfo.UseShellExecute = true; 39 | p.StartInfo.ErrorDialog = false; 40 | _ = p.Start(); 41 | } 42 | } 43 | catch (Exception ex) 44 | { 45 | _log.Error(ex, $"Error trying to open {filePath}: {ex.Message}"); 46 | _ = new MDCustMsgBox(GetStringResource("MsgText_Error_FileExplorer"), 47 | "My Scheduled Tasks ERROR", 48 | ButtonType.Ok, 49 | false, 50 | true, 51 | _mainWindow, 52 | true).ShowDialog(); 53 | } 54 | } 55 | #endregion Open App folder 56 | 57 | #region Open settings 58 | [RelayCommand] 59 | private static void OpenSettings() 60 | { 61 | ConfigHelpers.SaveSettings(); 62 | TextFileViewer.ViewTextFile(ConfigHelpers.SettingsFileName!); 63 | } 64 | #endregion Open settings 65 | 66 | #region Export settings 67 | [RelayCommand] 68 | private static void ExportSettings() 69 | { 70 | ConfigHelpers.ExportSettings(); 71 | } 72 | #endregion Export settings 73 | 74 | #region Import settings 75 | [RelayCommand] 76 | private static void ImportSettings() 77 | { 78 | ConfigHelpers.ImportSettings(); 79 | } 80 | #endregion Import settings 81 | 82 | #region List (dump) settings to log file 83 | [RelayCommand] 84 | private static void DumpSettings() 85 | { 86 | ConfigHelpers.DumpSettings(); 87 | NavigationViewModel.ViewLogFile(); 88 | } 89 | #endregion List (dump) settings to log file 90 | 91 | #region Compare languages 92 | [RelayCommand] 93 | private static void CompareLanguageKeys() 94 | { 95 | CompareLanguageDictionaries(); 96 | TextFileViewer.ViewTextFile(GetLogfileName()); 97 | } 98 | #endregion Compare languages 99 | } 100 | -------------------------------------------------------------------------------- /MyScheduledTasks/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 62 | 63 | 64 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/NavigationStyles.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 55 | 56 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/ButtonStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 29 | 30 | 31 | 32 | 49 | 50 | 51 | 52 | 69 | 70 | -------------------------------------------------------------------------------- /MyScheduledTasks/CommonCompletionCodes.txt: -------------------------------------------------------------------------------- 1 | Common completion (Last Result) codes for scheduled tasks are: 2 | 3 | 0 or 0x0: The operation completed successfully. 4 | 1 or 0x1: Incorrect function called or unknown function called. 5 | 2 or 0x2: File not found. 6 | 10 or 0xa: The environment is incorrect. 7 | 0x00041300: Task is ready to run at its next scheduled time. 8 | 0x00041301: The task is currently running. 9 | 0x00041302: The task has been disabled. 10 | 0x00041303: The task has not yet run. 11 | 0x00041304: There are no more runs scheduled for this task. 12 | 0x00041305: One or more of the properties that are needed to run this task have not been set. 13 | 0x00041306: The last run of the task was terminated by the user. 14 | 0x00041307: Either the task has no triggers or the existing triggers are disabled or not set. 15 | 0x00041308: Event triggers do not have set run times. 16 | 0x80010002: Call was canceled by the message filter 17 | 0x80041309: A task's trigger is not found. 18 | 0x8004130A: One or more of the properties required to run this task have not been set. 19 | 0x8004130B: There is no running instance of the task. 20 | 0x8004130C: The Task Scheduler service is not installed on this computer. 21 | 0x8004130D: The task object could not be opened. 22 | 0x8004130E: The object is either an invalid task object or is not a task object. 23 | 0x8004130F: No account information could be found in the Task Scheduler security database for the task indicated. 24 | 0x80041310: Unable to establish existence of the account specified. 25 | 0x80041311: Corruption was detected in the Task Scheduler security database 26 | 0x80041312: Task Scheduler security services are available only on Windows NT. 27 | 0x80041313: The task object version is either unsupported or invalid. 28 | 0x80041314: The task has been configured with an unsupported combination of account settings and run time options. 29 | 0x80041315: The Task Scheduler Service is not running. 30 | 0x80041316: The task XML contains an unexpected node. 31 | 0x80041317: The task XML contains an element or attribute from an unexpected namespace. 32 | 0x80041318: The task XML contains a value which is incorrectly formatted or out of range. 33 | 0x80041319: The task XML is missing a required element or attribute. 34 | 0x8004131A: The task XML is malformed. 35 | 0x0004131B: The task is registered, but not all specified triggers will start the task. 36 | 0x0004131C: The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal. 37 | 0x8004131D: The task XML contains too many nodes of the same type. 38 | 0x8004131E: The task cannot be started after the trigger end boundary. 39 | 0x8004131F: An instance of this task is already running. 40 | 0x80041320: The task will not run because the user is not logged on. 41 | 0x80041321: The task image is corrupt or has been tampered with. 42 | 0x80041322: The Task Scheduler service is not available. 43 | 0x80041323: The Task Scheduler service is too busy to handle your request. Please try again later. 44 | 0x80041324: The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition. 45 | 0x00041325: The Task Scheduler service has asked the task to run. 46 | 0x80041326: The task is disabled. 47 | 0x80041327: The task has properties that are not compatible with earlier versions of Windows. 48 | 0x80041328: The task settings do not allow the task to start on demand. 49 | 0x80070002: The Task Scheduler cannot find the file. 50 | 0x800710E0: The operator or administrator has refused the request. 51 | 0xC000013A: The application terminated as a result of a CTRL+C. 52 | 0xC0000142: The application failed to initialize properly. 53 | 54 | Source: Wikipedia contributors. (2023b, December 22). Windows Task Scheduler. Wikipedia. https://en.wikipedia.org/wiki/Windows_Task_Scheduler#Column_'Last_Result' -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/LocalizationHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | /// 6 | /// Class for localization and culture helper methods. 7 | /// 8 | internal static class LocalizationHelpers 9 | { 10 | #region Properties 11 | /// 12 | /// Uri of the resource dictionary 13 | /// 14 | private static string? LanguageFile { get; set; } 15 | 16 | /// 17 | /// Number of language strings in a resource dictionary 18 | /// 19 | public static int LanguageStrings { get; set; } 20 | #endregion Properties 21 | 22 | #region Return the current culture (language) 23 | /// 24 | /// Gets the current culture. 25 | /// 26 | /// Current culture name 27 | public static string GetCurrentCulture() 28 | { 29 | return CultureInfo.CurrentCulture.Name; 30 | } 31 | #endregion Return the current culture (language) 32 | 33 | #region Return the current UI culture 34 | /// 35 | /// Gets the current UI culture. 36 | /// 37 | /// Current UI culture name 38 | public static string GetCurrentUICulture() 39 | { 40 | return CultureInfo.CurrentUICulture.Name; 41 | } 42 | #endregion Return the current UI culture 43 | 44 | #region Apply language settings 45 | /// 46 | /// Apply language settings. 47 | /// 48 | /// The resource dictionary corresponding to the selected language. 49 | public static void ApplyLanguageSettings(ResourceDictionary LanguageDictionary) 50 | { 51 | LanguageStrings = LanguageDictionary.Count; 52 | LanguageFile = LanguageDictionary.Source.OriginalString; 53 | if (LanguageStrings == 0) 54 | { 55 | _log.Warn($"No strings loaded from {LanguageFile}"); 56 | } 57 | _log.Debug($"Current culture: {GetCurrentCulture()} UI: {GetCurrentUICulture()}"); 58 | _log.Debug($"{LanguageStrings} strings loaded from {LanguageFile}"); 59 | } 60 | #endregion Apply language settings 61 | 62 | #region Check if Use OS Language is set 63 | /// 64 | /// Check if the option to use the OS language is set and if the language is defined. 65 | /// 66 | /// The language code to check. 67 | /// True if the language is defined and the language exists. Otherwise return false. 68 | public static bool CheckUseOsLanguage(string language) 69 | { 70 | if (UserSettings.Setting!.UseOSLanguage) 71 | { 72 | if (UILanguage.DefinedLanguages.Exists(x => x.LanguageCode == language)) 73 | { 74 | return true; 75 | } 76 | _log.Warn($"Language \"{language}\" has not been defined in this application. Defaulting to en-US and setting \"Use OS Language\" to false."); 77 | UserSettings.Setting.UseOSLanguage = false; 78 | ConfigHelpers.SaveSettings(); 79 | } 80 | return false; 81 | } 82 | #endregion Check if Use OS Language is set 83 | 84 | #region Save settings and restart (after language change) 85 | /// 86 | /// Saves settings and restarts the application. Invoked when language is changed. 87 | /// 88 | public static void SaveAndRestart() 89 | { 90 | ConfigHelpers.SaveSettings(); 91 | using Process p = new(); 92 | p.StartInfo.FileName = AppInfo.AppPath; 93 | p.StartInfo.UseShellExecute = true; 94 | _ = p.Start(); 95 | _log.Debug("Restarting for language change."); 96 | Application.Current.Shutdown(); 97 | } 98 | #endregion Save settings and restart (after language change) 99 | } 100 | -------------------------------------------------------------------------------- /MyScheduledTasks/MyScheduledTasks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MyScheduledTasks 5 | WinExe 6 | net8.0-windows 7 | 10.0.19041.41 8 | enable 9 | true 10 | My Scheduled Tasks 11 | © 2020-$([System.DateTime]::UtcNow.Year) Tim Kennedy 12 | Tim Kennedy 13 | T_K 14 | My Scheduled Tasks 15 | en-US 16 | Images\MST.ico 17 | AnyCPU;x64 18 | 19 | 20 | 21 | 22 | app.manifest 23 | 24 | 25 | 26 | 27 | Recommended 28 | 8.0 29 | 30 | 31 | 32 | 33 | en 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | all 53 | 3.9.50 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -File "$(ProjectDir)PowerShell\GenBuildInfo.ps1" 80 | -assemblyName $(AssemblyName) 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | My Scheduled Tasks Logo 4 | 5 |

6 |

7 | My Scheduled Tasks 8 |

9 |
10 | 11 | [![GitHub](https://img.shields.io/github/license/Timthreetwelve/MyScheduledTasks?style=plastic)](https://github.com/Timthreetwelve/MyScheduledTasks/blob/main/LICENSE) 12 | [![NET8win](https://img.shields.io/badge/.NET-8.0--Windows-blueviolet?style=plastic)](https://dotnet.microsoft.com/en-us/download) 13 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/Timthreetwelve/MyScheduledTasks?style=plastic)](https://github.com/Timthreetwelve/MyScheduledTasks/releases/latest) 14 | [![GitHub Release Date](https://img.shields.io/github/release-date/timthreetwelve/MyScheduledTasks?style=plastic&color=orange)](https://github.com/Timthreetwelve/MyScheduledTasks/releases/latest) 15 | [![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/timthreetwelve/MyScheduledTasks/latest?style=plastic)](https://github.com/Timthreetwelve/MyScheduledTasks/commits/main) 16 | [![GitHub last commit](https://img.shields.io/github/last-commit/timthreetwelve/MyScheduledTasks?style=plastic)](https://github.com/Timthreetwelve/MyScheduledTasks/commits/main) 17 | [![GitHub commits](https://img.shields.io/github/commit-activity/m/timthreetwelve/MyScheduledTasks?style=plastic)](https://github.com/Timthreetwelve/MyScheduledTasks/commits/main) 18 | [![GitHub Stars](https://img.shields.io/github/stars/timthreetwelve/MyScheduledTasks?style=plastic&color=goldenrod&logo=github)](https://docs.github.com/en/get-started/exploring-projects-on-github/saving-repositories-with-stars) 19 | [![GitHub all releases](https://img.shields.io/github/downloads/Timthreetwelve/MyScheduledTasks/total?style=plastic&label=total%20downloads)](https://github.com/Timthreetwelve/MyScheduledTasks/releases) 20 | [![GitHub release (by tag)](https://img.shields.io/github/downloads/timthreetwelve/MyScheduledTasks/latest/total?style=plastic&color=2196F3&label=downloads%20latest%20version)](https://github.com/Timthreetwelve/MyScheduledTasks/releases/latest) 21 | [![GitHub Issues](https://img.shields.io/github/issues/timthreetwelve/MyScheduledTasks?style=plastic&color=orangered)](https://github.com/Timthreetwelve/MyScheduledTasks/issues) 22 | [![GitHub Issues](https://img.shields.io/github/issues-closed/timthreetwelve/MyScheduledTasks?style=plastic&color=slateblue)](https://github.com/Timthreetwelve/MyScheduledTasks/issues) 23 | 24 |
25 | 26 | ### My Scheduled Tasks is an application that lets you keep track of the tasks in Windows Task Scheduler that you care about. 27 | 28 | #### My Scheduled Task is now Multilingual! 29 | Please see [Contribute a Translation](https://github.com/Timthreetwelve/MyScheduledTasks/wiki/Contribute-a-Translation) topic in the Wiki if you would like to contribute a translation. 30 | You will also find a topic on [Testing a Language File](https://github.com/Timthreetwelve/MyScheduledTasks/wiki/Testing-a-Language-File) in the wiki. 31 | 32 | #### My Scheduled Tasks runs on .NET 8 33 | Self-contained versions are available if .NET 8 isn't installed. Portable versions are also available. See the [releases page](https://github.com/Timthreetwelve/MyScheduledTasks/releases). 34 | 35 | #### Features 36 | 37 | * See the tasks that you have chosen in one window, no digging around in folders. 38 | * Easily add scheduled tasks to your list. 39 | * Optionally hide Microsoft tasks from the Add Tasks list. 40 | * Easily check the last result of the tasks in your list. 41 | * Run, Disable, Enable, or Delete individual tasks. 42 | * Export tasks to XML file. 43 | * Import tasks from XML file. 44 | * Add a note to individual tasks. 45 | * Select any task to see more details. 46 | * Optionally hide the details pane. 47 | * Optionally run My Scheduled Tasks as a scheduled task and get notification of tasks that have a non-zero result code. 48 | * Choose which columns are visible. 49 | 50 | #### Screenshot 51 | ![Screenshot](https://github.com/Timthreetwelve/MyScheduledTasks/blob/main/Images/MyScheduledTasks_v6.png) 52 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/NLogHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | /// 6 | /// Class for NLog helper methods 7 | /// 8 | internal static class NLogHelpers 9 | { 10 | /// 11 | /// Static instance for NLog Logger. 12 | /// 13 | /// 14 | /// Used with a "static using" in GlobalUsings.cs to avoid creating an instance in every class. 15 | /// 16 | internal static readonly Logger _log = LogManager.GetLogger("logTemp"); 17 | 18 | #region Create the NLog configuration 19 | /// 20 | /// Configure NLog 21 | /// 22 | /// True to start with new log file. False to append to current file. 23 | public static void NLogConfig(bool newFile) 24 | { 25 | LoggingConfiguration config = new(); 26 | 27 | // create log file Target for NLog 28 | FileTarget logfile = new("logfile") 29 | { 30 | // new file on startup 31 | DeleteOldFileOnStartup = newFile, 32 | 33 | // create the file if needed 34 | FileName = CreateFilename(), 35 | 36 | // message and footer layouts 37 | Footer = "${date:format=yyyy/MM/dd HH\\:mm\\:ss}", 38 | Layout = "${date:format=yyyy/MM/dd HH\\:mm\\:ss} " + 39 | "${pad:padding=-5:inner=${level:uppercase=true}} " + 40 | "${message}${onexception:${newline}${exception:format=tostring}}" 41 | }; 42 | 43 | // add the log file target 44 | config.AddTarget(logfile); 45 | 46 | // add the rule for the log file 47 | LoggingRule file = new("*", LogLevel.Debug, logfile) 48 | { 49 | RuleName = "LogToFile" 50 | }; 51 | config.LoggingRules.Add(file); 52 | 53 | // create debugger target 54 | DebuggerTarget debugger = new("debugger") 55 | { 56 | Layout = "${processtime} >>> ${message} " 57 | }; 58 | 59 | // add the target 60 | config.AddTarget(debugger); 61 | 62 | // add the rule 63 | LoggingRule bug = new("*", LogLevel.Trace, debugger); 64 | config.LoggingRules.Add(bug); 65 | 66 | // add the configuration to NLog 67 | LogManager.Configuration = config; 68 | 69 | // Lastly, set the logging level based on setting 70 | SetLogLevel(UserSettings.Setting!.IncludeDebug); 71 | } 72 | #endregion Create the NLog configuration 73 | 74 | #region Create a filename in the temp folder 75 | private static string CreateFilename() 76 | { 77 | // create filename string 78 | string myName = AppInfo.AppName; 79 | string today = DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); 80 | string filename = Debugger.IsAttached ? $"{myName}.{today}.debug.log" : $"{myName}.{today}.log"; 81 | 82 | // combine temp folder with filename 83 | string tempDir = Path.GetTempPath(); 84 | return Path.Combine(tempDir, "T_K", filename); 85 | } 86 | #endregion Create a filename in the temp folder 87 | 88 | #region Set NLog logging level 89 | /// 90 | /// Set the NLog logging level to Debug or Info 91 | /// 92 | /// If true set level to Debug, otherwise set to Info 93 | public static void SetLogLevel(bool debug) 94 | { 95 | LoggingConfiguration config = LogManager.Configuration; 96 | 97 | LoggingRule rule = config.FindRuleByName("LogToFile"); 98 | if (rule != null) 99 | { 100 | LogLevel level = debug ? LogLevel.Debug : LogLevel.Info; 101 | rule.SetLoggingLevels(level, LogLevel.Fatal); 102 | LogManager.ReconfigExistingLoggers(); 103 | } 104 | } 105 | #endregion Set NLog logging level 106 | 107 | #region Get the log file name 108 | /// 109 | /// Gets the filename for the NLog log fie 110 | /// 111 | /// 112 | public static string GetLogfileName() 113 | { 114 | LoggingConfiguration config = LogManager.Configuration; 115 | return (config.FindTargetByName("logfile") 116 | as FileTarget)?.FileName.Render(new LogEventInfo { TimeStamp = DateTime.Now })!; 117 | } 118 | #endregion Get the log file name 119 | } 120 | -------------------------------------------------------------------------------- /MyScheduledTasks/Models/Enums.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Models; 4 | 5 | /// 6 | /// Navigation Page 7 | /// 8 | public enum NavPage 9 | { 10 | [Description("The Main Page")] 11 | Main = 0, 12 | [LocalizedDescription("SettingsEnum_Navigation_AddTasks")] 13 | AddTasks, 14 | [LocalizedDescription("SettingsEnum_Navigation_Settings")] 15 | Settings, 16 | [LocalizedDescription("SettingsEnum_Navigation_About")] 17 | About 18 | } 19 | 20 | #region Theme 21 | /// 22 | /// Theme type 23 | /// 24 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 25 | public enum ThemeType 26 | { 27 | [LocalizedDescription("SettingsEnum_Theme_Light")] 28 | Light = 0, 29 | [LocalizedDescription("SettingsEnum_Theme_Dark")] 30 | Dark = 1, 31 | [LocalizedDescription("SettingsEnum_Theme_Darker")] 32 | Darker = 2, 33 | [LocalizedDescription("SettingsEnum_Theme_System")] 34 | System = 3, 35 | [LocalizedDescription("SettingsEnum_Theme_DarkBlue")] 36 | DarkBlue = 4 37 | } 38 | #endregion Theme 39 | 40 | #region UI size 41 | /// 42 | /// Size of the UI 43 | /// 44 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 45 | public enum MySize 46 | { 47 | [LocalizedDescription("SettingsEnum_Size_Smallest")] 48 | Smallest = 0, 49 | [LocalizedDescription("SettingsEnum_Size_Smaller")] 50 | Smaller = 1, 51 | [LocalizedDescription("SettingsEnum_Size_Small")] 52 | Small = 2, 53 | [LocalizedDescription("SettingsEnum_Size_Default")] 54 | Default = 3, 55 | [LocalizedDescription("SettingsEnum_Size_Large")] 56 | Large = 4, 57 | [LocalizedDescription("SettingsEnum_Size_Larger")] 58 | Larger = 5, 59 | [LocalizedDescription("SettingsEnum_Size_Largest")] 60 | Largest = 6 61 | } 62 | #endregion UI size 63 | 64 | #region Accent color 65 | /// 66 | /// One of the 19 predefined Material Design in XAML colors plus Black & White 67 | /// 68 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 69 | public enum AccentColor 70 | { 71 | [LocalizedDescription("SettingsEnum_AccentColor_Red")] 72 | Red = 0, 73 | [LocalizedDescription("SettingsEnum_AccentColor_Pink")] 74 | Pink = 1, 75 | [LocalizedDescription("SettingsEnum_AccentColor_Purple")] 76 | Purple = 2, 77 | [LocalizedDescription("SettingsEnum_AccentColor_DeepPurple")] 78 | DeepPurple = 3, 79 | [LocalizedDescription("SettingsEnum_AccentColor_Indigo")] 80 | Indigo = 4, 81 | [LocalizedDescription("SettingsEnum_AccentColor_Blue")] 82 | Blue = 5, 83 | [LocalizedDescription("SettingsEnum_AccentColor_LightBlue")] 84 | LightBlue = 6, 85 | [LocalizedDescription("SettingsEnum_AccentColor_Cyan")] 86 | Cyan = 7, 87 | [LocalizedDescription("SettingsEnum_AccentColor_Teal")] 88 | Teal = 8, 89 | [LocalizedDescription("SettingsEnum_AccentColor_Green")] 90 | Green = 9, 91 | [LocalizedDescription("SettingsEnum_AccentColor_LightGreen")] 92 | LightGreen = 10, 93 | [LocalizedDescription("SettingsEnum_AccentColor_Lime")] 94 | Lime = 11, 95 | [LocalizedDescription("SettingsEnum_AccentColor_Yellow")] 96 | Yellow = 12, 97 | [LocalizedDescription("SettingsEnum_AccentColor_Amber")] 98 | Amber = 13, 99 | [LocalizedDescription("SettingsEnum_AccentColor_Orange")] 100 | Orange = 14, 101 | [LocalizedDescription("SettingsEnum_AccentColor_DeepOrange")] 102 | DeepOrange = 15, 103 | [LocalizedDescription("SettingsEnum_AccentColor_Brown")] 104 | Brown = 16, 105 | [LocalizedDescription("SettingsEnum_AccentColor_Gray")] 106 | Gray = 17, 107 | [LocalizedDescription("SettingsEnum_AccentColor_BlueGray")] 108 | BlueGray = 18, 109 | [LocalizedDescription("SettingsEnum_AccentColor_Black")] 110 | Black = 19, 111 | [LocalizedDescription("SettingsEnum_AccentColor_White")] 112 | White = 20, 113 | } 114 | #endregion Accent color 115 | 116 | #region Spacing 117 | /// 118 | /// Space between rows in the data grids 119 | /// 120 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 121 | public enum Spacing 122 | { 123 | [LocalizedDescription("SettingsEnum_Spacing_Compact")] 124 | Compact = 0, 125 | [LocalizedDescription("SettingsEnum_Spacing_Comfortable")] 126 | Comfortable = 1, 127 | [LocalizedDescription("SettingsEnum_Spacing_Wide")] 128 | Wide = 2 129 | } 130 | #endregion Spacing 131 | -------------------------------------------------------------------------------- /MyScheduledTasks/Styles/ExpanderStyles.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 39 | 40 | 41 | 42 | 58 | 59 | 60 | 61 | 80 | 81 | 82 | 83 | 84 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /MyScheduledTasks/ViewModels/AddTasksViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.ViewModels; 4 | 5 | internal sealed partial class AddTasksViewModel 6 | { 7 | #region Private field 8 | private static int _itemsAdded; 9 | #endregion Private field 10 | 11 | #region Add selected items to TaskList 12 | /// 13 | /// Add all selected items 14 | /// 15 | /// Name of the DataGrid 16 | private static void AddSelectedItems(DataGrid grid) 17 | { 18 | if (grid.SelectedItems.Count > 0) 19 | { 20 | _itemsAdded = 0; 21 | foreach (AllTasks item in grid.SelectedItems) 22 | { 23 | AddToMyTasks(item); 24 | } 25 | if (_itemsAdded > 0) 26 | { 27 | _log.Info($"{_itemsAdded} task(s) added"); 28 | string msg = string.Format(CultureInfo.CurrentCulture, AddTasksTasksAdded, _itemsAdded); 29 | SnackbarMsg.QueueMessage(msg, 3000); 30 | TaskFileHelpers.WriteTasksToFile(); 31 | } 32 | 33 | grid.UnselectAll(); 34 | } 35 | } 36 | #endregion Add selected items to TaskList 37 | 38 | #region Add a single item 39 | /// 40 | /// Adds a single item to the tasks list 41 | /// 42 | /// Path of the item to be added 43 | /// True if the task was added. Otherwise returns false. 44 | internal static bool AddToMyTasks(AllTasks item) 45 | { 46 | Task? task = GetTaskInfo(item.TaskPath!); 47 | if (task == null) 48 | { 49 | string msg = string.Format(CultureInfo.InvariantCulture, AddTasksNotFound, item.TaskName); 50 | _log.Error($"The Scheduled Task \"{item.TaskPath}\" was not found."); 51 | _ = new MDCustMsgBox(msg, 52 | GetStringResource("AddTasks_Error"), 53 | ButtonType.Ok, 54 | false, 55 | true, 56 | null, 57 | true).ShowDialog(); 58 | return false; 59 | } 60 | 61 | if (ScheduledTask.TaskList.Any(p => p.TaskPath == task.Path)) 62 | { 63 | int pos = ScheduledTask.TaskList.IndexOf(ScheduledTask.TaskList.FirstOrDefault(x => x.TaskPath == task.Path)!); 64 | _log.Warn($"{task.Path} is already present in the list in position {pos + 1}"); 65 | string msg = string.Format(CultureInfo.InvariantCulture, AddTasksTaskAlreadyAdded, task.Path); 66 | SnackbarMsg.QueueMessage(msg, 3000); 67 | return false; 68 | } 69 | ScheduledTask schedTask = ScheduledTask.BuildScheduledTask(task, null); 70 | ScheduledTask.TaskList.Add(schedTask); 71 | 72 | MyTasks newTask = new(task.Path, false, string.Empty); 73 | MyTasks.MyTasksCollection!.Add(newTask); 74 | 75 | _log.Info($"Added: \"{task.Path}\""); 76 | _itemsAdded++; 77 | return true; 78 | } 79 | #endregion Add a single item 80 | 81 | #region Include or exclude Microsoft tasks 82 | /// 83 | /// Determine source of add tasks list 84 | /// 85 | /// Name of the DataGrid 86 | private static void DetermineSource(DataGrid grid) 87 | { 88 | grid.ItemsSource = UserSettings.Setting!.HideMicrosoftFolder ? AllTasks.Non_MS_TasksCollection : AllTasks.All_TasksCollection; 89 | } 90 | #endregion Include or exclude Microsoft tasks 91 | 92 | #region Get info for a task 93 | 94 | private static Task? GetTaskInfo(string name) 95 | { 96 | using TaskService ts = new(); 97 | return ts.GetTask(name); 98 | } 99 | #endregion Get info for a task 100 | 101 | #region Relay commands 102 | [RelayCommand] 103 | public static void HideTasks(DataGrid grid) 104 | { 105 | DetermineSource(grid); 106 | } 107 | 108 | [RelayCommand] 109 | private static void RefreshTasks() 110 | { 111 | TaskHelpers.GetAllTasks(); 112 | } 113 | 114 | [RelayCommand] 115 | private static void AddTasks(DataGrid grid) 116 | { 117 | AddSelectedItems(grid); 118 | } 119 | #endregion Relay commands 120 | 121 | #region Static property and change event handler for filter text 122 | public static event EventHandler? StaticPropertyChanged; 123 | 124 | private static string? _filterText; 125 | public static string FilterText 126 | { 127 | get => _filterText!; 128 | set 129 | { 130 | if (_filterText != value) 131 | { 132 | _filterText = value; 133 | StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(FilterText)); 134 | } 135 | } 136 | } 137 | #endregion Static property and change event handler for filter text 138 | } 139 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/MDCustMsgBox.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | // Inspired by https://stackoverflow.com/a/60302166 4 | 5 | namespace MyScheduledTasks.Dialogs; 6 | 7 | /// 8 | /// Custom message box that works well with Material Design in XAML. 9 | /// 10 | public partial class MDCustMsgBox : Window 11 | { 12 | #region Public Property 13 | public static CustResultType CustResult { get; private set; } 14 | #endregion 15 | 16 | /// 17 | /// Custom message box for MDIX 18 | /// 19 | /// Text of the message 20 | /// Text that goes in the title bar 21 | /// OK, OKCancel, YesNoCancel or YesNo 22 | /// True to hide close button 23 | /// True to make window topmost 24 | /// Owner of the window 25 | /// True will set accent color to OrangeRed 26 | public MDCustMsgBox(string Message, 27 | string Title, 28 | ButtonType Buttons, 29 | bool HideClose = false, 30 | bool OnTop = true, 31 | Window? MsgBoxOwner = null, 32 | bool IsError = false) 33 | { 34 | InitializeComponent(); 35 | 36 | DataContext = this; 37 | 38 | #region Topmost 39 | if (OnTop) 40 | { 41 | Topmost = true; 42 | } 43 | #endregion 44 | 45 | #region Message text 46 | TxtMessage.Text = Message; 47 | #endregion Message text 48 | 49 | #region Message box title 50 | TxtTitle.Text = string.IsNullOrEmpty(Title) ? Application.Current!.MainWindow!.Title : Title; 51 | #endregion Message box title 52 | 53 | #region Button visibility 54 | switch (Buttons) 55 | { 56 | case ButtonType.Ok: 57 | BtnCancel.Visibility = Visibility.Collapsed; 58 | BtnYes.Visibility = Visibility.Collapsed; 59 | BtnNo.Visibility = Visibility.Collapsed; 60 | _ = BtnOk.Focus(); 61 | break; 62 | 63 | case ButtonType.OkCancel: 64 | BtnYes.Visibility = Visibility.Collapsed; 65 | BtnNo.Visibility = Visibility.Collapsed; 66 | _ = BtnOk.Focus(); 67 | break; 68 | 69 | case ButtonType.YesNo: 70 | BtnOk.Visibility = Visibility.Collapsed; 71 | BtnCancel.Visibility = Visibility.Collapsed; 72 | _ = BtnYes.Focus(); 73 | break; 74 | 75 | case ButtonType.YesNoCancel: 76 | BtnOk.Visibility = Visibility.Collapsed; 77 | _ = BtnYes.Focus(); 78 | break; 79 | } 80 | if (HideClose) 81 | { 82 | BtnClose.Visibility = Visibility.Collapsed; 83 | } 84 | #endregion Button visibility 85 | 86 | #region Window position 87 | if (MsgBoxOwner != null) 88 | { 89 | Owner = MsgBoxOwner; 90 | WindowStartupLocation = Owner.IsVisible ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen; 91 | } 92 | else 93 | { 94 | WindowStartupLocation = WindowStartupLocation.CenterScreen; 95 | } 96 | #endregion Window position 97 | 98 | #region Error message 99 | if (IsError) 100 | { 101 | BorderBrush = Brushes.OrangeRed; 102 | BorderThickness = new Thickness(2); 103 | CardHeader.Background = BorderBrush; 104 | CardHeader.FontWeight = FontWeights.Bold; 105 | } 106 | #endregion Error message 107 | } 108 | 109 | #region Mouse event 110 | private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 111 | { 112 | DragMove(); 113 | } 114 | #endregion Mouse event 115 | 116 | #region Button commands 117 | [RelayCommand] 118 | private void CancelButton() 119 | { 120 | Close(); 121 | CustResult = CustResultType.Cancel; 122 | } 123 | 124 | [RelayCommand] 125 | private void OKButton() 126 | { 127 | Close(); 128 | CustResult = CustResultType.Ok; 129 | } 130 | 131 | [RelayCommand] 132 | private void YesButton() 133 | { 134 | Close(); 135 | CustResult = CustResultType.Yes; 136 | } 137 | 138 | [RelayCommand] 139 | private void NoButton() 140 | { 141 | Close(); 142 | CustResult = CustResultType.No; 143 | } 144 | #endregion Button commands 145 | } 146 | 147 | #region Button type enumeration 148 | public enum ButtonType 149 | { 150 | OkCancel, 151 | YesNo, 152 | YesNoCancel, 153 | Ok, 154 | } 155 | #endregion Button type enumeration 156 | 157 | #region Result type enumeration 158 | public enum CustResultType 159 | { 160 | Ok, 161 | Yes, 162 | No, 163 | Cancel 164 | } 165 | #endregion Result type enumeration 166 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | timthreetwelve@outlook.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/AppInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | /// 6 | /// Class to return information about the current application 7 | /// 8 | public static class AppInfo 9 | { 10 | /// 11 | /// Returns the operating system description e.g. Microsoft Windows 10.0.19044 12 | /// 13 | public static string OsPlatform => RuntimeInformation.OSDescription; 14 | 15 | /// 16 | /// Returns the framework name 17 | /// 18 | public static string? Framework => Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName; 19 | 20 | /// 21 | /// Returns the framework description 22 | /// 23 | public static string RuntimeVersion => RuntimeInformation.FrameworkDescription; 24 | 25 | /// 26 | /// Returns the version number in Major.Minor.Build format 27 | /// 28 | private static string TitleVersion => Assembly.GetEntryAssembly()!.GetName().Version!.ToString().Remove(Assembly.GetEntryAssembly()!.GetName().Version!.ToString().LastIndexOf('.')); 29 | 30 | /// 31 | /// Returns the file version 32 | /// 33 | public static string AppFileVersion => Assembly.GetEntryAssembly()!.GetCustomAttribute()?.Version ?? "missing"; 34 | 35 | /// 36 | /// Returns the full version number as String 37 | /// 38 | public static string AppVersion => Assembly.GetEntryAssembly()!.GetName().Version!.ToString(); 39 | 40 | /// 41 | /// Returns the full version number as Version 42 | /// 43 | public static Version AppVersionVer => Assembly.GetEntryAssembly()!.GetName().Version!; 44 | 45 | /// 46 | /// Returns the app's full path including the EXE name 47 | /// 48 | public static string AppPath => Environment.ProcessPath!; 49 | 50 | /// 51 | /// Returns the app's full path excluding the EXE name 52 | /// 53 | public static string AppDirectory => Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location) ?? "missing"; 54 | 55 | /// 56 | /// Returns the app's name without the extension 57 | /// 58 | public static string AppName => Assembly.GetEntryAssembly()!.GetName().Name ?? "missing"; 59 | 60 | /// 61 | /// Returns the app's name with the extension 62 | /// 63 | public static string AppExeName => Path.GetFileName(AppPath); 64 | 65 | /// 66 | /// Returns the app's full name (name, version, culture, etc.) 67 | /// 68 | public static string AppFullName => Assembly.GetEntryAssembly()!.GetName().FullName; 69 | 70 | /// 71 | /// Returns the Company Name from the Assembly info 72 | /// 73 | public static string AppCompany => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).CompanyName ?? "missing"; 74 | 75 | /// 76 | /// Returns the Author from the Assembly info 77 | /// 78 | public static string AppDescription => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).FileDescription ?? "missing"; 79 | 80 | /// 81 | /// Returns the product version from the Assembly info 82 | /// 83 | public static string AppProductVersion => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).ProductVersion ?? "missing"; 84 | 85 | /// 86 | /// Returns the Copyright info from the Assembly info 87 | /// 88 | public static string AppCopyright => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).LegalCopyright ?? "missing"; 89 | 90 | /// 91 | /// Returns the Product Name from the Assembly info 92 | /// 93 | public static string AppProduct => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).ProductName ?? "missing"; 94 | 95 | /// 96 | /// Returns the File Name from the Assembly info 97 | /// 98 | public static string AppFileName => FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).FileName; 99 | 100 | /// 101 | /// Combines the product name with the title version. 102 | /// 103 | /// 104 | /// String in the format: AppName - 0.0.1 105 | /// 106 | public static string ToolTipVersion => $"{AppProduct} - {TitleVersion}"; 107 | 108 | /// 109 | /// Returns the Process Name 110 | /// 111 | public static string AppProcessName => Process.GetCurrentProcess().ProcessName; 112 | 113 | /// 114 | /// Returns the Process ID as Int 115 | /// 116 | public static int AppProcessID => Environment.ProcessId; 117 | 118 | /// 119 | /// Returns the Process Start Time as DateTime 120 | /// 121 | public static DateTime AppProcessStart => Process.GetCurrentProcess().StartTime; 122 | 123 | /// 124 | /// Returns the Process MainModule 125 | /// 126 | public static string AppProcessMainModule => Process.GetCurrentProcess().MainModule!.ModuleName; 127 | 128 | /// 129 | /// The CLR version 130 | /// 131 | public static string CLRVersion => Environment.Version.ToString(); 132 | 133 | /// 134 | /// True if running as administrator 135 | /// 136 | public static bool IsAdmin => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); 137 | } 138 | -------------------------------------------------------------------------------- /MyScheduledTasks/Configuration/UserSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Configuration; 4 | 5 | [INotifyPropertyChanged] 6 | public partial class UserSettings : ConfigManager 7 | { 8 | #region Properties 9 | /// 10 | /// Add text to root folder. 11 | /// 12 | [ObservableProperty] 13 | private bool _annotateRoot = true; 14 | 15 | /// 16 | /// Check for updates automatically when About page is opened. 17 | /// 18 | [ObservableProperty] 19 | private bool _autoCheckForUpdates = true; 20 | 21 | /// 22 | /// Height of the details pane. 23 | /// 24 | [ObservableProperty] 25 | private double _detailsHeight = 520; 26 | 27 | /// 28 | /// Used to determine used to determine scaling of dialogs. 29 | /// 30 | [ObservableProperty] 31 | private static double _dialogScale = 1; 32 | 33 | /// 34 | /// Don't display Microsoft folder. 35 | /// 36 | [ObservableProperty] 37 | private bool _hideMicrosoftFolder; 38 | 39 | /// 40 | /// Include debug level messages in the log file. 41 | /// 42 | [ObservableProperty] 43 | private bool _includeDebug = true; 44 | 45 | /// 46 | /// Keep window topmost. 47 | /// 48 | [ObservableProperty] 49 | private bool _keepOnTop; 50 | 51 | /// 52 | /// Enable language testing. 53 | /// 54 | [ObservableProperty] 55 | private bool _languageTesting; 56 | 57 | /// 58 | /// Accent color. 59 | /// 60 | [ObservableProperty] 61 | private AccentColor _primaryColor = AccentColor.Blue; 62 | 63 | /// 64 | /// Vertical spacing in the data grids. 65 | /// 66 | [ObservableProperty] 67 | private Spacing _rowSpacing = Spacing.Comfortable; 68 | 69 | /// 70 | /// Font used in datagrids. 71 | /// 72 | [ObservableProperty] 73 | private string? _selectedFont = "Segoe UI"; 74 | 75 | /// 76 | /// Font size used throughout the application. 77 | /// Defaults to 14 which was the original size. 78 | /// 79 | [ObservableProperty] 80 | private double _selectedFontSize = 14; 81 | 82 | /// 83 | /// Show the advanced menu. 84 | /// 85 | [ObservableProperty] 86 | private bool _showAdvancedMenu; 87 | 88 | /// 89 | /// Show the alert column in the grid. 90 | /// 91 | [ObservableProperty] 92 | private bool _showAlertCol = true; 93 | 94 | /// 95 | /// Show the details pane at the bottom. 96 | /// 97 | [ObservableProperty] 98 | private bool _showDetails = true; 99 | 100 | /// 101 | /// Show Exit in the navigation menu. 102 | /// 103 | [ObservableProperty] 104 | private bool _showExitInNav = true; 105 | 106 | /// 107 | /// Show the folder column. 108 | /// 109 | [ObservableProperty] 110 | private bool _showFolderCol = true; 111 | 112 | /// 113 | /// Show the last run column. 114 | /// 115 | [ObservableProperty] 116 | private bool _showLastRunCol = true; 117 | 118 | /// 119 | /// Show the next run column. 120 | /// 121 | [ObservableProperty] 122 | private bool _showNextRunCol; 123 | 124 | /// 125 | /// Show the note column. 126 | /// 127 | [ObservableProperty] 128 | private bool _showNoteCol = true; 129 | 130 | /// 131 | /// Show the Result column. 132 | /// 133 | [ObservableProperty] 134 | private bool _showResultCol = true; 135 | 136 | /// 137 | /// Show the Status column. 138 | /// 139 | [ObservableProperty] 140 | private bool _showStatusCol = true; 141 | 142 | /// 143 | /// Use sound. 144 | /// 145 | [ObservableProperty] 146 | private bool _sound = true; 147 | 148 | /// 149 | /// Option start with window centered on screen. 150 | /// 151 | [ObservableProperty] 152 | private bool _startCentered = true; 153 | 154 | /// 155 | /// Defined language to use in the UI. 156 | /// 157 | [ObservableProperty] 158 | private string _uILanguage = "en-US"; 159 | 160 | /// 161 | /// Amount of UI zoom. 162 | /// 163 | [ObservableProperty] 164 | private MySize _uISize = MySize.Default; 165 | 166 | /// 167 | /// Theme type. 168 | /// 169 | [ObservableProperty] 170 | private ThemeType _uITheme = ThemeType.System; 171 | 172 | /// 173 | /// Use the operating system language (if one has been provided). 174 | /// 175 | [ObservableProperty] 176 | private bool _useOSLanguage; 177 | 178 | /// 179 | /// Height of the window. 180 | /// 181 | [ObservableProperty] 182 | private double _windowHeight = 800; 183 | 184 | /// 185 | /// Position of left side of the window. 186 | /// 187 | [ObservableProperty] 188 | private double _windowLeft = 100; 189 | 190 | /// 191 | /// Position of the top side of the window. 192 | /// 193 | [ObservableProperty] 194 | private double _windowTop = 100; 195 | 196 | /// 197 | /// Width of the window. 198 | /// 199 | [ObservableProperty] 200 | private double _windowWidth = 1400; 201 | #endregion Properties 202 | } 203 | -------------------------------------------------------------------------------- /MyScheduledTasks/Helpers/TaskFileHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace MyScheduledTasks.Helpers; 4 | 5 | internal static class TaskFileHelpers 6 | { 7 | #region Tasks filename 8 | /// 9 | /// Name of the tasks file. 10 | /// 11 | public static string TasksFile { get; } = Path.Combine(AppInfo.AppDirectory, "MyTasks.json"); 12 | #endregion Tasks filename 13 | 14 | #region JSON serializer options 15 | /// 16 | /// JSON serializer options 17 | /// 18 | private static readonly JsonSerializerOptions _options = new() 19 | { 20 | WriteIndented = true 21 | }; 22 | #endregion JSON serializer options 23 | 24 | #region MainWindow Instance 25 | private static readonly MainWindow? _mainWindow = Application.Current.MainWindow as MainWindow; 26 | #endregion MainWindow Instance 27 | 28 | #region Read the tasks JSON file 29 | /// 30 | /// Read the tasks file. 31 | /// 32 | public static void ReadMyTasks() 33 | { 34 | // If the file doesn't exist, create a minimal JSON file 35 | if (!File.Exists(TasksFile)) 36 | { 37 | CreateEmptyFile(); 38 | } 39 | // Read the JSON file 40 | try 41 | { 42 | string json = File.ReadAllText(TasksFile); 43 | MyTasks.MyTasksCollection = JsonSerializer.Deserialize>(json)!; 44 | _log.Info($"Read {MyTasks.MyTasksCollection.Count} items from {TasksFile} "); 45 | } 46 | // Can't really do much if the file is not readable 47 | catch (Exception ex) 48 | { 49 | _log.Fatal(ex, $"Error reading {TasksFile}"); 50 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextErrorReadingFile, TasksFile); 51 | msg += $"\n\n{ex.Message}\n\n{GetStringResource("MsgText_ErrorFatal")}"; 52 | _ = new MDCustMsgBox(msg, 53 | GetStringResource("MsgText_ErrorCaption"), 54 | ButtonType.Ok, 55 | true, 56 | true, 57 | null!, 58 | true).ShowDialog(); 59 | 60 | // Quit via Environment.Exit so that normal shutdown processing doesn't run 61 | Environment.Exit(1); 62 | } 63 | } 64 | 65 | /// 66 | /// Create an empty JSON file if one doesn't exist. 67 | /// 68 | private static void CreateEmptyFile() 69 | { 70 | const string x = "[]"; 71 | try 72 | { 73 | File.WriteAllText(TasksFile, x); 74 | } 75 | catch (Exception ex) 76 | { 77 | _log.Fatal(ex, $"Error creating {TasksFile}"); 78 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextErrorCreatingFile, TasksFile); 79 | msg += $"\n\n{ex.Message}\n\n{GetStringResource("MsgText_ErrorFatal")}"; 80 | _ = new MDCustMsgBox(msg, 81 | GetStringResource("MsgText_ErrorCaption"), 82 | ButtonType.Ok, 83 | true, 84 | true, 85 | null!, 86 | true).ShowDialog(); 87 | 88 | // Quit via Environment.Exit so that normal shutdown processing doesn't run 89 | Environment.Exit(1); 90 | } 91 | } 92 | #endregion Read the tasks JSON file 93 | 94 | #region Write the tasks JSON file 95 | /// 96 | /// Convert MyTasksCollection to JSON and save it to a file 97 | /// 98 | /// Setting quiet to true will not display snack bar message 99 | public static void WriteTasksToFile(bool quiet = true) 100 | { 101 | try 102 | { 103 | string tasks = JsonSerializer.Serialize(MyTasks.MyTasksCollection, _options); 104 | File.WriteAllText(TasksFile, tasks); 105 | _log.Info($"Saving {MyTasks.MyTasksCollection!.Count} tasks to {TasksFile} "); 106 | if (!quiet) 107 | { 108 | SnackbarMsg.QueueMessage(GetStringResource("MsgText_FileSaved"), 3000); 109 | } 110 | MyTasks.IsDirty = false; 111 | } 112 | catch (Exception ex) 113 | { 114 | _log.Error(ex, $"Error saving {TasksFile}"); 115 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextErrorSavingFile, TasksFile); 116 | msg += $"\n\n{ex.Message}"; 117 | _ = new MDCustMsgBox(msg, 118 | GetStringResource("MsgText_ErrorCaption"), 119 | ButtonType.Ok, 120 | true, 121 | true, 122 | _mainWindow!, 123 | true).ShowDialog(); 124 | } 125 | } 126 | #endregion Write the tasks JSON file 127 | 128 | #region Check for empty task list 129 | /// 130 | /// Display a message box if the tasks list is empty. 131 | /// 132 | public static void CheckEmptyList() 133 | { 134 | if (ScheduledTask.TaskList.Count == 0) 135 | { 136 | _ = new MDCustMsgBox(GetStringResource("MsgText_ErrorEmptyTaskList"), 137 | GetStringResource("MsgText_ErrorEmptyTaskListCaption"), 138 | ButtonType.YesNo, 139 | true, 140 | true, 141 | _mainWindow!) 142 | .ShowDialog(); 143 | 144 | if (MDCustMsgBox.CustResult == CustResultType.Yes) 145 | { 146 | _mainWindow!.NavigationListBox.SelectedValue = NavigationViewModel.FindNavPage(NavPage.AddTasks); 147 | } 148 | } 149 | } 150 | #endregion Check for empty task list 151 | } 152 | -------------------------------------------------------------------------------- /MyScheduledTasks/Inno_Setup/MyScheduledTasksLocalization.iss: -------------------------------------------------------------------------------- 1 | [LangOptions] 2 | DialogFontSize=10 3 | DialogFontName="Segoe UI" 4 | WelcomeFontSize=12 5 | WelcomeFontName="Verdana" 6 | 7 | [Languages] 8 | Name: "en"; MessagesFile: "compiler:Default.isl" 9 | Name: "ko"; MessagesFile: "compiler:Languages\Korean.isl" 10 | Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl" 11 | Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" 12 | Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl" 13 | Name: "sk"; MessagesFile: "compiler:Languages\Slovak.isl" 14 | 15 | [Messages] 16 | en.SetupWindowTitle = Setup - {#MyAppName} {#MyAppVersion} 17 | ko.SetupWindowTitle = 설치 - {#MyAppName} {#MyAppVersion} 18 | es.SetupWindowTitle = Instalar - {#MyAppName} {#MyAppVersion} 19 | it.SetupWindowTitle = Installazione di {#MyAppName} {#MyAppVersion} 20 | nl.SetupWindowTitle = Setup - {#MyAppName} {#MyAppVersion} 21 | sk.SetupWindowTitle = Sprievodca inštaláciou - {#MyAppName} {#MyAppVersion} 22 | 23 | [CustomMessages] 24 | en.NotSelfContained=This will install the standard version of %1 version %2.%n%nThis version requires an existing installation of .NET 8 Desktop Runtime and is compatible with both x64 and x86 systems.%n%nIt Is recommended that you close all other applications before continuing.%n%nClick 'Next' to continue, or 'Cancel' to exit Setup. 25 | en.SelfContainedx86=This will install the self-contained x86 (32-bit) version of %1 version %2.%n%nIt Is recommended that you close all other applications before continuing.%n%nClick 'Next' to continue, or 'Cancel' to exit Setup. 26 | en.SelfContainedx64=This will install the self-contained x64 (64-bit) version of %1 version %2.%n%nIt Is recommended that you close all other applications before continuing.%n%nClick 'Next' to continue, or 'Cancel' to exit Setup. 27 | en.ViewReadme=View the ReadMe file 28 | en.AppIsRunning=is running, please close it to continue with the installation. 29 | en.ClearSettings=Do you want to remove the settings files and registry entries?%n%nSelect 'No' if you plan to reinstall the program. 30 | 31 | ko.NotSelfContained=이렇게 하면 %1 버전 %2.%n%n이 버전의 표준 버전이 설치됩니다. 이 버전은 .NET 8 데스크톱 런타임을 기존에 설치해야 하며 x64 및 x86 시스템과 모두 호환됩니다.%n%n계속하기 전에 다른 모든 응용 프로그램을 닫는 것이 좋습니다.%n%n계속하려면 '다음'을 클릭하거나 '취소'를 클릭하여 설치를 종료하는 것이 좋습니다. 32 | ko.SelfContainedx86=이렇게 하면 %1 버전의 자체 포함 x86 (32비트) 버전 %2.%n%n계속하기 전에 다른 모든 응용 프로그램을 닫는 것이 좋습니다.%n%n계속하려면 '다음'을 클릭하거나 '취소'를 클릭하여 설치를 종료합니다. 33 | ko.SelfContainedx64=이렇게 하면 %1 버전의 자체 포함 x64 (64비트) 버전 %2.%n%n계속하기 전에 다른 모든 응용 프로그램을 닫는 것이 좋습니다.%n%n계속하려면 '다음'을 클릭하거나 '취소'를 클릭하여 설치를 종료합니다. 34 | ko.ViewReadme=ReadMe 파일 보기 35 | ko.AppIsRunning=가 실행 중입니다. 설치를 계속하려면 닫으세요. 36 | ko.ClearSettings=설정 파일과 레지스트리 항목을 제거하시겠습니까? %n%n프로그램을 다시 설치하려면 '아니오'를 선택합니다. 37 | 38 | es.NotSelfContained=Esto instalará la versión estándar de la versión %2 de %1.%n%nEsta versión requiere una instalación existente de .NET 8 Desktop Runtime y es compatible con los sistemas x64 y x86.%n%nSe recomienda cerrar todas las demás aplicaciones antes de continuar.%n%nHaga clic en 'Siguiente' para continuar o en 'Cancelar' para salir de la configuración. 39 | es.SelfContainedx86=Esto instalará la versión x86 (32 bits) independiente de la versión %2 de %1.%n%nSe recomienda cerrar todas las demás aplicaciones antes de continuar.%n%nHaga clic en 'Siguiente' para continuar o en 'Cancelar' para salir de la configuración. 40 | es.SelfContainedx64=Esto instalará la versión x64 (64 bits) independiente de la versión %2 de %1.%n%nSe recomienda cerrar todas las demás aplicaciones antes de continuar.%n%nHaga clic en 'Siguiente' para continuar o en 'Cancelar' para salir de la configuración. 41 | es.ViewReadme=Abrir el archivo Léame 42 | es.AppIsRunning=se está ejecutando, por favor ciérrelo para continuar con la instalación. 43 | es.ClearSettings=¿Desea eliminar la configuración y los archivos de datos?%n%nSeleccione 'No' si planea reinstalar. 44 | 45 | it.NotSelfContained=Verrà installata la versione standard di %1 versione %2.%n%nQuesta versione richiede che sia già installato .NET 8 Desktop Runtime ed è compatibile con i sistemi 32bit e 64bit.%n%nPrima di continuare l'instalalzione ti consigliamo di chiudere tutte le altre applicazioni.%n%nSeleziona 'Avanti' per continuare o 'Annulla' per uscire dall'installazione. 46 | it.SelfContainedx86=Verrà installata la versione standalone di %1 versione %2 32 bit.%n%nPrima di continuare l'instalalzione ti consigliamo di chiudere tutte le altre applicazioni.%n%nSelziona 'Avanti' per continuare o 'Annulla' per uscire dall'installazione. 47 | it.SelfContainedx64=Verrà installata la versione standalone di %1 versione %2 64 bit.%n%nPrima di continuare l'instalalzione ti consigliamo di chiudere tutte le altre applicazioni.%n%nSeleziona 'Avanti' per continuare o 'Annulla' per uscire dall'installazione. 48 | it.ViewReadme=Visualizza file ReadMe 49 | it.AppIsRunning=è in esecuzione.%nChiudilo per poter continuare l'installazione. 50 | it.ClearSettings=Vuoi rimuovere le impostazioni, i file cronologia e le voci del registro?%n%nSeleziona "No" se hai intenzione di reinstallare il programma. 51 | 52 | nl.NotSelfContained=Hiermee wordt de standaardversie van %1 versie %2 geïnstalleerd.%n%n%nDeze versie vereist een bestaande installatie van .NET 8 Desktop Runtime en is compatibel met zowel x64- als x86-systemen.%n%nHet wordt aanbevolen om alle andere toepassingen te sluiten voordat u doorgaat.%n%nKlik op 'volgende' om door te gaan of op 'annuleren' om de installatie af te sluiten. 53 | nl.SelfContainedx86=Hiermee wordt de portable x86 (32-bits) versie van %1 versie %2 geïnstalleerd.%n%n%nHet wordt aanbevolen om alle andere toepassingen te sluiten voordat u doorgaat.%n%nKlik op 'volgende' om door te gaan of op 'annuleren' om de installatie af te sluiten. 54 | nl.SelfContainedx64= Hiermee wordt de portable x64 (64-bits) versie van %1 versie %2 geïnstalleerd.%n%n%nHet wordt aanbevolen om alle andere toepassingen te sluiten voordat u doorgaat.%n%nKlik op 'volgende' om door te gaan of op 'annuleren' om de installatie af te sluiten. 55 | nl.ViewReadme=Open de ReadMe file 56 | nl.AppIsRunning=wordt uitgevoerd, sluit deze dan af om door te gaan met de installatie. 57 | nl.ClearSettings=Wilt u de instellingen en gegevensbestanden verwijderen?%n%nKies 'NEE' als u het programma opnieuw wilt installeren. 58 | 59 | sk.ViewReadme=Zobraziť súbor ReadMe 60 | sk.AppIsRunning=beží, zatvorte ho, aby ste mohli pokračovať v inštalácii. 61 | sk.ClearSettings=Chcete odstrániť súbory nastavení?%n%nAk plánujete preinštalovať, vyberte 'Nie'. 62 | -------------------------------------------------------------------------------- /MyScheduledTasks/Dialogs/EditNote.xaml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 47 | 55 | 56 | 57 | 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 97 |