├── global.json ├── Images ├── WUView.png ├── WUView550.png └── WUView_2024-04-04_17-11-03.png ├── WUView ├── Images │ ├── UV.ico │ ├── UV.png │ └── InvertedUV.png ├── Views │ ├── AboutPage.xaml.cs │ └── SettingsPage.xaml.cs ├── MainWindow.xaml.cs ├── Configuration │ ├── ConfigManager.cs │ ├── TempSettings.cs │ ├── SettingChange.cs │ ├── UserSettings.cs │ └── ConfigHelpers.cs ├── Dialogs │ ├── ExcludesEditor.xaml.cs │ ├── MDCustMsgBox.xaml.cs │ ├── ExcludesEditor.xaml │ └── MDCustMsgBox.xaml ├── Properties │ └── PublishProfiles │ │ ├── Framework_Dependent.pubxml │ │ ├── Self_Contained_x64.pubxml │ │ └── Self_Contained_x86.pubxml ├── Styles │ ├── HyperlinkStyles.xaml │ ├── ScrollBarStyle.xaml │ ├── TextBoxStyles.xaml │ ├── NavigationStyles.xaml │ ├── DataGridStyles.xaml │ ├── SnackbarStyle.xaml │ ├── ButtonStyles.xaml │ └── ExpanderStyles.xaml ├── Helpers │ ├── DialogHelpers.cs │ ├── OperationHelper.cs │ ├── ClipboardHelper.cs │ ├── ResultCodeHelper.cs │ ├── SnackbarMsg.cs │ ├── ScreenHelpers.cs │ ├── WUApiHelpers.cs │ ├── TextFileViewer.cs │ ├── SingleInstance.cs │ ├── LocalizationHelpers.cs │ ├── NLogHelpers.cs │ ├── GitHubHelpers.cs │ ├── AppInfo.cs │ └── FileHelpers.cs ├── Converters │ ├── BooleanInverter.cs │ ├── TodayConverter.cs │ ├── FontSizeConverter.cs │ ├── ResultsConverter.cs │ ├── SpacingConverter.cs │ ├── LocalizedDescriptionAttribute.cs │ ├── EnumDescriptionTypeConverter.cs │ ├── ItemsSourceConverter.cs │ ├── ExcludedItemsConverter.cs │ ├── EnumBindingSourceExtension.cs │ ├── EnumDescConverter.cs │ └── DateFormatConverter.cs ├── AssemblyInfo.cs ├── Constants │ ├── AppConstString.cs │ └── AppConstUri.cs ├── Models │ ├── ExcludedItems.cs │ ├── NavigationItem.cs │ ├── WuEventRecord.cs │ ├── WUpdate.cs │ ├── UILanguage.cs │ └── Enums.cs ├── Strings.test.xaml ├── License.txt ├── PowerShell │ └── GenBuildInfo.ps1 ├── GlobalUsings.cs ├── ViewModels │ ├── AboutViewModel.cs │ └── SettingsViewModel.cs ├── App.xaml ├── Languages │ └── Strings.en-GB.xaml ├── WUView.csproj ├── ReadMe.txt ├── App.xaml.cs └── Inno_Setup │ └── WUViewEx.iss ├── .github ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── enhancement.yml │ ├── translation_issue.yml │ └── bug_report.yml ├── version.json ├── SECURITY.md ├── LICENSE ├── WUView.sln ├── .gitattributes ├── README.md ├── CODE_OF_CONDUCT.md └── .gitignore /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.400" 4 | } 5 | } -------------------------------------------------------------------------------- /Images/WUView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/Images/WUView.png -------------------------------------------------------------------------------- /Images/WUView550.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/Images/WUView550.png -------------------------------------------------------------------------------- /WUView/Images/UV.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/WUView/Images/UV.ico -------------------------------------------------------------------------------- /WUView/Images/UV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/WUView/Images/UV.png -------------------------------------------------------------------------------- /WUView/Images/InvertedUV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/WUView/Images/InvertedUV.png -------------------------------------------------------------------------------- /Images/WUView_2024-04-04_17-11-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timthreetwelve/WUView/HEAD/Images/WUView_2024-04-04_17-11-03.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/WUView" 5 | schedule: 6 | interval: "daily" 7 | open-pull-requests-limit: 5 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Windows Update Viewer Discussions 🗣️ 4 | url: https://github.com/Timthreetwelve/WUView/discussions 5 | about: If you have a question or some feedback, start a discussion. 6 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "0.7.0", 4 | "assemblyVersion": { 5 | "precision": "build" 6 | }, 7 | "gitCommitIdShortFixedLength": 7, 8 | "publicReleaseRefSpec": [ 9 | "^refs/heads/main$" 10 | ] 11 | } -------------------------------------------------------------------------------- /WUView/Views/AboutPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/Views/SettingsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView; 4 | 5 | public partial class MainWindow : Window 6 | { 7 | public MainWindow() 8 | { 9 | SingleInstance.Create(AppInfo.AppName); 10 | 11 | InitializeComponent(); 12 | 13 | MainWindowHelpers.WUVStartup(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /WUView/Configuration/ConfigManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/Dialogs/ExcludesEditor.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Dialogs; 4 | 5 | /// 6 | /// Dialog to edit the exclude list 7 | /// 8 | public partial class ExcludesEditor : UserControl 9 | { 10 | public ExcludesEditor() 11 | { 12 | InitializeComponent(); 13 | } 14 | 15 | private void UserControl_Loaded(object sender, RoutedEventArgs e) 16 | { 17 | TextBox1.CaretIndex = TextBox1.Text.Length; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WUView/Properties/PublishProfiles/Framework_Dependent.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Framework_Dependent 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0-windows10.0.19041.0 13 | false 14 | 15 | -------------------------------------------------------------------------------- /WUView/Styles/HyperlinkStyles.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /WUView/Configuration/TempSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 _uIExpanderOpen; 16 | 17 | [ObservableProperty] 18 | private static bool _langExpanderOpen; 19 | 20 | [ObservableProperty] 21 | private static bool _backupExpanderOpen; 22 | } 23 | -------------------------------------------------------------------------------- /WUView/Helpers/DialogHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Helpers; 4 | 5 | internal static class DialogHelpers 6 | { 7 | /// 8 | /// Shows the dialog used to view and edit the excludes file 9 | /// 10 | /// Returns if the user clicked OK, otherwise. 11 | internal static async Task ShowEditExcludesDialog() 12 | { 13 | ExcludesEditor ee = new(); 14 | object? value = await DialogHost.Show(ee, "MainDialogHost"); 15 | return value != null && (bool)value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WUView/Converters/BooleanInverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Inverts a boolean value. True becomes False. False becomes True. 7 | /// 8 | /// 9 | internal sealed class BooleanInverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | return !(bool)value!; 14 | } 15 | 16 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 17 | { 18 | return !(bool)value!; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WUView/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 | -------------------------------------------------------------------------------- /WUView/Constants/AppConstString.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Constants; 4 | 5 | /// 6 | /// Class for constant strings 7 | /// 8 | public static class AppConstString 9 | { 10 | /// 11 | /// Gets the GitHub repository owner. 12 | /// 13 | /// 14 | /// The repository owner. 15 | /// 16 | public static string RepoOwner { get; } = "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 { get; } = "WUView"; 25 | } 26 | -------------------------------------------------------------------------------- /WUView/Models/ExcludedItems.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Models; 4 | 5 | /// 6 | /// Class for the excluded items 7 | /// 8 | public class ExcludedItems : ObservableObject 9 | { 10 | /// 11 | /// Gets or sets the excluded string. 12 | /// 13 | /// 14 | /// The excluded string. 15 | /// 16 | public string? ExcludedString { get; init; } 17 | 18 | /// 19 | /// Collection of excluded strings 20 | /// 21 | /// 22 | /// The excluded strings. 23 | /// 24 | public static ObservableCollection ExcludedStrings { get; set; } = []; 25 | } 26 | -------------------------------------------------------------------------------- /WUView/Models/NavigationItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 string? _name; 18 | 19 | [ObservableProperty] 20 | private NavPage _navPage; 21 | 22 | [ObservableProperty] 23 | private string _pageTitle = "Page Title Goes Here"; 24 | 25 | [ObservableProperty] 26 | private object? _viewModelType; 27 | } 28 | -------------------------------------------------------------------------------- /WUView/Properties/PublishProfiles/Self_Contained_x64.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Self_Contained_x64 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0-windows10.0.19041.0 13 | win-x64 14 | true 15 | false 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /WUView/Properties/PublishProfiles/Self_Contained_x86.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Self_Contained_x86 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0-windows10.0.19041.0 13 | win-x86 14 | true 15 | false 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /WUView/Converters/TodayConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Converter that determines if the update occurred today. 7 | /// 8 | /// Returns if date is today. 9 | internal sealed class TodayConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | return UserSettings.Setting!.BoldToday ? value is DateTime date && date.Date == DateTime.Today : (object)false; 14 | } 15 | 16 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 17 | { 18 | return Binding.DoNothing; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WUView/Constants/AppConstUri.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Constants; 4 | 5 | /// 6 | /// Class for constant Uris 7 | /// 8 | internal static class AppConstUri 9 | { 10 | /// 11 | /// Gets the HResult URL. 12 | /// 13 | /// 14 | /// The HResult URL as Uri. 15 | /// 16 | public static Uri HResultCodeUrl { get; } = new("https://docs.microsoft.com/en-us/windows/deployment/update/windows-update-error-reference"); 17 | 18 | /// 19 | /// Gets the Result code URL. 20 | /// 21 | /// 22 | /// The result code URL as Uri. 23 | /// 24 | public static Uri ResultCodeUrl { get; } = new("https://github.com/Timthreetwelve/WUView/wiki/Result-Codes"); 25 | } 26 | -------------------------------------------------------------------------------- /WUView/Strings.test.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | Settings - Language Test 13 | 14 | -------------------------------------------------------------------------------- /WUView/Helpers/OperationHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Helpers; 4 | /// 5 | /// Class to localize Operation string. 6 | /// 7 | public static class OperationHelper 8 | { 9 | /// 10 | /// Localizes the Operation string. Also removes the unwanted "uo" prefix. 11 | /// 12 | /// Operation type from update history. 13 | /// Operation type as localized string. 14 | public static string TranslateOperation(UpdateOperation operation) 15 | { 16 | return operation switch 17 | { 18 | UpdateOperation.uoInstallation => GetStringResource("OperationType_Installation"), 19 | UpdateOperation.uoUninstallation => GetStringResource("OperationType_Uninstallation"), 20 | _ => "unknown", 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WUView/Styles/ScrollBarStyle.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /WUView/Models/WuEventRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Models; 4 | 5 | /// 6 | /// Class to hold Setup event log information. 7 | /// 8 | internal sealed class WuEventRecord 9 | { 10 | /// 11 | /// The Knowledge Base (KB) number. 12 | /// 13 | public string? KayBee { get; init; } 14 | 15 | /// 16 | /// The time the event record was created. 17 | /// 18 | public DateTime TimeCreated { get; init; } 19 | 20 | /// 21 | /// The event ID number. 22 | /// 23 | public int EventId { get; init; } 24 | 25 | /// 26 | /// The description text. 27 | /// 28 | public string? Description { get; init; } 29 | 30 | /// 31 | /// The record ID number. 32 | /// 33 | public long? RecordId { get; init; } 34 | } 35 | -------------------------------------------------------------------------------- /WUView/Styles/TextBoxStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 18 | 19 | -------------------------------------------------------------------------------- /WUView/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 WUView.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 | -------------------------------------------------------------------------------- /WUView/Converters/FontSizeConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Converter to increase or decrease font size. 7 | /// Amount of increase or decrease is passed as a parameter. 8 | /// Increase by passing a positive number, decrease by passing a negative number. 9 | /// 10 | internal sealed class FontSizeConverter : IValueConverter 11 | { 12 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 13 | { 14 | if (value is not null 15 | && targetType == typeof(double) 16 | && parameter is string parm 17 | && double.TryParse(parm, out double newFontSize)) 18 | { 19 | return (double)value + newFontSize; 20 | } 21 | return value!; 22 | } 23 | 24 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 25 | { 26 | return Binding.DoNothing; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tim Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /WUView/License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2024 Tim Kennedy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /WUView/Converters/ResultsConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Updates strings to the desired format. 7 | /// 8 | /// 9 | internal sealed class ResultsConverter : IValueConverter 10 | { 11 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | if (value is null || parameter is null) 14 | { 15 | return string.Empty; 16 | } 17 | 18 | try 19 | { 20 | return parameter is "HResult" && value is string hrString 21 | ? $"0x{int.Parse(hrString, CultureInfo.InvariantCulture):X8}" 22 | : value.ToString(); 23 | } 24 | catch (Exception) 25 | { 26 | return value.ToString(); 27 | } 28 | } 29 | 30 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 31 | { 32 | return Binding.DoNothing; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WUView/Converters/SpacingConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Converter that changes Spacing to Thickness 7 | /// 8 | /// 9 | internal sealed class SpacingConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | if (value is Spacing spacing) 14 | { 15 | switch (spacing) 16 | { 17 | case Spacing.Compact: 18 | return new Thickness(15, 2, 15, 2); 19 | case Spacing.Comfortable: 20 | return new Thickness(15, 5, 15, 5); 21 | case Spacing.Wide: 22 | return new Thickness(15, 7, 15, 7); 23 | } 24 | } 25 | 26 | return new Thickness(15, 10, 15, 10); 27 | } 28 | 29 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 30 | { 31 | return Binding.DoNothing; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WUView/Converters/LocalizedDescriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | if (description is null) 29 | { 30 | return $"{resourceKey} resource not found"; 31 | } 32 | 33 | return description.ToString() ?? string.Empty; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.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: "*Feel free to change the issue title above*" 10 | - type: markdown 11 | attributes: 12 | value: "### If you have an idea for a feature or enhancement that would make the app better, tell me about it ###" 13 | - type: input 14 | id: summary 15 | attributes: 16 | label: Brief summary of your request 17 | description: Enter a short description 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: request 22 | attributes: 23 | label: Request details 24 | description: Tell me what you would like to see. Feel free to attach an image file if it helps describe your idea. 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: anything-else 29 | attributes: 30 | label: Anything else? 31 | description: Is there an example that you can point me to? Provide a link here. 32 | validations: 33 | required: false 34 | -------------------------------------------------------------------------------- /WUView/Helpers/ResultCodeHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Helpers; 4 | /// 5 | /// Class to localize Result Codes. 6 | /// 7 | internal static class ResultCodeHelper 8 | { 9 | /// 10 | /// Localizes the Result Code. Also removes the unwanted "orc" prefix. 11 | /// 12 | /// Result code from update history. 13 | /// Result code as localized string. 14 | public static string TranslateResultCode(OperationResultCode resultCode) 15 | { 16 | return resultCode switch 17 | { 18 | OperationResultCode.orcNotStarted => GetStringResource("ResultCode_NotStarted"), 19 | OperationResultCode.orcInProgress => GetStringResource("ResultCode_InProgress"), 20 | OperationResultCode.orcSucceeded => GetStringResource("ResultCode_Succeeded"), 21 | OperationResultCode.orcSucceededWithErrors => GetStringResource("ResultCode_SucceededWithErrors"), 22 | OperationResultCode.orcFailed => GetStringResource("ResultCode_Failed"), 23 | OperationResultCode.orcAborted => GetStringResource("ResultCode_Aborted"), 24 | _ => "unknown", 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WUView/Helpers/SnackbarMsg.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Helpers; 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 | } 33 | -------------------------------------------------------------------------------- /WUView/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 | namespace $assemblyName; 12 | 13 | /// 14 | /// Class containing git commit id and actual build date. 15 | /// This file is generated during a pre-build event by PowerShell\GenBuildInfo.ps1. 16 | /// Any edits to this file will be overwritten during the next build! 17 | /// 18 | public static class BuildInfo 19 | { 20 | public static readonly string CommitIDString = ThisAssembly.GitCommitId[..7]; 21 | 22 | public static readonly string CommitIDFullString = ThisAssembly.GitCommitId; 23 | 24 | public const string BuildDateString = `"$nowUTC`"; 25 | 26 | public static readonly DateTime BuildDateUtc = 27 | DateTime.SpecifyKind( 28 | DateTime.ParseExact(BuildDateString, `"yyyy/MM/dd HH:mm:ss`", CultureInfo.InvariantCulture), 29 | DateTimeKind.Utc); 30 | 31 | public static readonly string BuildDateStringUtc = $`"{BuildDateUtc:f} (UTC)`"; 32 | }" 33 | 34 | $outputPath = Join-Path -Path $(Get-Location).Path -ChildPath $outputFile 35 | Set-Content -Path "$outputPath" -Value $class -Encoding utf8BOM -------------------------------------------------------------------------------- /WUView/Converters/EnumDescriptionTypeConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | #nullable disable 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/Models/WUpdate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Models; 4 | 5 | /// 6 | /// Defines properties for Windows Update and Event Log 7 | /// 8 | public partial class WUpdate : ObservableObject 9 | { 10 | #region Properties from WUApi 11 | 12 | [ObservableProperty] 13 | private string? _title; 14 | 15 | [ObservableProperty] 16 | private string? _kBNum; 17 | 18 | [ObservableProperty] 19 | private string? _description; 20 | 21 | [ObservableProperty] 22 | private string? _updateID; 23 | 24 | [ObservableProperty] 25 | private DateTime _date; 26 | 27 | [ObservableProperty] 28 | private string? _resultCode; 29 | 30 | [ObservableProperty] 31 | private string? _hResult; 32 | 33 | [ObservableProperty] 34 | private string? _operation; 35 | 36 | [ObservableProperty] 37 | private string? _supportURL; 38 | #endregion Properties from WUApi 39 | 40 | #region Properties from Event Log 41 | [ObservableProperty] 42 | private int? _eLEventID; 43 | 44 | [ObservableProperty] 45 | private string? _eLDescription; 46 | 47 | [ObservableProperty] 48 | private string? _eLProvider; 49 | 50 | [ObservableProperty] 51 | private DateTime? _eLDate; 52 | #endregion Properties from Event Log 53 | 54 | #region GetClone Method 55 | internal WUpdate GetClone() 56 | { 57 | return (WUpdate)MemberwiseClone(); 58 | } 59 | #endregion GetClone Method 60 | } 61 | -------------------------------------------------------------------------------- /WUView/Converters/ItemsSourceConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Sets the desired of updates. 7 | /// 8 | /// 9 | internal sealed class ItemsSourceConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | int fullCount = MainViewModel.UpdatesFullList.Count; 14 | int withoutCount = MainViewModel.UpdatesWithoutExcludedItems.Count; 15 | 16 | if (UserSettings.Setting!.HideExcluded) 17 | { 18 | if (MainPage.Instance is not null) 19 | { 20 | SnackbarMsg.ClearAndQueueMessage(string.Format(CultureInfo.InvariantCulture, 21 | MsgTextDisplayedUpdates, withoutCount, fullCount)); 22 | } 23 | return MainViewModel.UpdatesWithoutExcludedItems; 24 | } 25 | else 26 | { 27 | if (MainPage.Instance is not null) 28 | { 29 | SnackbarMsg.ClearAndQueueMessage(string.Format(CultureInfo.InvariantCulture, 30 | MsgTextDisplayedAllUpdates, fullCount)); 31 | } 32 | return MainViewModel.UpdatesFullList; 33 | } 34 | } 35 | 36 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 37 | { 38 | return Binding.DoNothing; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WUView/Helpers/ScreenHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/Converters/ExcludedItemsConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Converts excluded items in the observable collection to strings for use in a textbox 7 | /// 8 | /// 9 | internal sealed class ExcludedItemsConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | if (value is not null) 14 | { 15 | StringBuilder sb = new(); 16 | foreach (ExcludedItems item in (ObservableCollection)value) 17 | { 18 | _ = sb.AppendLine(item.ExcludedString); 19 | } 20 | return sb.ToString(); 21 | } 22 | return string.Empty; 23 | } 24 | 25 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 26 | { 27 | if (value is not null) 28 | { 29 | ObservableCollection excludedItems = []; 30 | foreach (string item in value.ToString()!.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)) 31 | { 32 | ExcludedItems excluded = new() 33 | { 34 | ExcludedString = item 35 | }; 36 | excludedItems.Add(excluded); 37 | } 38 | return excludedItems; 39 | } 40 | return string.Empty; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.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: acknowledge 42 | attributes: 43 | label: If you wish to have your contribution acknowledged 44 | description: Enter your GitHub ID, a name or a short phrase that will appear on the About page 45 | validations: 46 | required: false 47 | -------------------------------------------------------------------------------- /WUView/Helpers/WUApiHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Helpers; 4 | 5 | internal static class WUApiHelpers 6 | { 7 | #region Log Windows Update Agent info 8 | public static void LogWUAInfo() 9 | { 10 | try 11 | { 12 | _log.Debug($"Windows Update Agent product version: {GetWUAInfo("ProductVersionString")}"); 13 | _log.Debug($"Windows Update Agent major version: {GetWUAInfo("ApiMajorVersion")} minor version: {GetWUAInfo("ApiMinorVersion")}"); 14 | } 15 | catch (Exception ex) 16 | { 17 | _log.Error(ex, $"Error attempting to get Windows Update Agent info. {ex.Message}"); 18 | } 19 | } 20 | #endregion Log Windows Update Agent info 21 | 22 | #region Get Windows Update Agent info 23 | private static string GetWUAInfo(string wuaObj) 24 | { 25 | IWindowsUpdateAgentInfo updateAgentInfo = new(); 26 | return updateAgentInfo.GetInfo(wuaObj).ToString()!; 27 | } 28 | #endregion Get Windows Update Agent info 29 | 30 | #region Log Windows Update Service status 31 | public static void LogWUEnabled() 32 | { 33 | try 34 | { 35 | IAutomaticUpdates automaticUpdates = new(); 36 | if (automaticUpdates.ServiceEnabled) 37 | { 38 | _log.Info("Windows Update Service is enabled."); 39 | } 40 | else 41 | { 42 | _log.Warn("Windows Update Service is not enabled."); 43 | } 44 | } 45 | catch (Exception ex) 46 | { 47 | _log.Error(ex, "Error checking Windows Update service status"); 48 | } 49 | } 50 | #endregion Log Windows Update Service status 51 | } 52 | -------------------------------------------------------------------------------- /WUView/Styles/NavigationStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /WUView/Converters/EnumBindingSourceExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | #nullable disable 3 | namespace WUView.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) 20 | { 21 | if (value != null) 22 | { 23 | Type enumType = Nullable.GetUnderlyingType(value) ?? value; 24 | if (!enumType.IsEnum) 25 | throw new ArgumentException("Type must be for an Enum."); 26 | } 27 | 28 | _enumType = value; 29 | } 30 | } 31 | } 32 | 33 | public EnumBindingSourceExtension(Type enumType) 34 | { 35 | EnumType = enumType; 36 | } 37 | 38 | public override object ProvideValue(IServiceProvider serviceProvider) 39 | { 40 | if (_enumType == null) 41 | throw new InvalidOperationException("The EnumType must be specified."); 42 | 43 | Type actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; 44 | Array enumValues = Enum.GetValues(actualEnumType); 45 | 46 | if (actualEnumType == _enumType) 47 | return enumValues; 48 | 49 | Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); 50 | enumValues.CopyTo(tempArray, 1); 51 | return tempArray; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /WUView/Styles/DataGridStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /WUView.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33516.290 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WUView", "WUView\WUView.csproj", "{2E751468-2744-4042-81F5-853FBE67C098}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4F173CCE-2C97-49E9-B081-05348897E99C}" 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\ISSUE_TEMPLATE\enhancement.yml = .github\ISSUE_TEMPLATE\enhancement.yml 13 | NewReleaseTemplate.md = NewReleaseTemplate.md 14 | README.md = README.md 15 | .github\ISSUE_TEMPLATE\translation_issue.yml = .github\ISSUE_TEMPLATE\translation_issue.yml 16 | version.json = version.json 17 | EndProjectSection 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {2E751468-2744-4042-81F5-853FBE67C098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {2E751468-2744-4042-81F5-853FBE67C098}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {2E751468-2744-4042-81F5-853FBE67C098}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {2E751468-2744-4042-81F5-853FBE67C098}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | RESX_ConfirmAddLanguageFile = True 35 | RESX_AutoCreateNewLanguageFiles = True 36 | RESX_RemoveEmptyEntries = False 37 | SolutionGuid = {79767ED0-375D-4153-9F6A-DAE1A0B29ACF} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /WUView/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | global using System; 4 | global using System.Collections; 5 | global using System.Collections.Generic; 6 | global using System.Collections.ObjectModel; 7 | global using System.ComponentModel; 8 | global using System.Diagnostics; 9 | global using System.Diagnostics.Eventing.Reader; 10 | global using System.Globalization; 11 | global using System.IO; 12 | global using System.Linq; 13 | global using System.Reflection; 14 | global using System.Runtime.InteropServices; 15 | global using System.Runtime.Versioning; 16 | global using System.Security.Principal; 17 | global using System.Text; 18 | global using System.Text.Json; 19 | global using System.Text.RegularExpressions; 20 | global using System.Threading; 21 | global using System.Threading.Tasks; 22 | global using System.Windows; 23 | global using System.Windows.Controls; 24 | global using System.Windows.Controls.Primitives; 25 | global using System.Windows.Data; 26 | global using System.Windows.Documents; 27 | global using System.Windows.Input; 28 | global using System.Windows.Media; 29 | global using System.Windows.Navigation; 30 | global using System.Windows.Markup; 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 | 40 | global using NLog; 41 | global using NLog.Config; 42 | global using NLog.Targets; 43 | 44 | global using WUView.Configuration; 45 | global using WUView.Constants; 46 | global using WUView.Converters; 47 | global using WUView.Dialogs; 48 | global using WUView.Helpers; 49 | global using WUView.Models; 50 | global using WUView.ViewModels; 51 | global using WUView.Views; 52 | 53 | global using static WUView.Helpers.NLogHelpers; 54 | global using static WUView.Helpers.ResourceHelpers; 55 | 56 | global using static Vanara.PInvoke.WUApi; 57 | -------------------------------------------------------------------------------- /WUView/ViewModels/AboutViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.ViewModels; 4 | 5 | public partial class AboutViewModel 6 | { 7 | #region Constructor 8 | public AboutViewModel() 9 | { 10 | if (AnnotatedLanguageList.Count == 0) 11 | { 12 | AddNote(); 13 | } 14 | } 15 | #endregion Constructor 16 | 17 | #region Relay Commands 18 | [RelayCommand] 19 | private static void ViewLicense() 20 | { 21 | string dir = AppInfo.AppDirectory; 22 | TextFileViewer.ViewTextFile(Path.Combine(dir, "License.txt")); 23 | } 24 | 25 | [RelayCommand] 26 | private static void ViewReadMe() 27 | { 28 | string dir = AppInfo.AppDirectory; 29 | TextFileViewer.ViewTextFile(Path.Combine(dir, "ReadMe.txt")); 30 | } 31 | 32 | [RelayCommand] 33 | private static void GoToGitHub(string url) 34 | { 35 | Process p = new(); 36 | p.StartInfo.FileName = url; 37 | p.StartInfo.UseShellExecute = true; 38 | p.Start(); 39 | } 40 | 41 | [RelayCommand] 42 | private static async Task CheckReleaseAsync() 43 | { 44 | await GitHubHelpers.CheckRelease(); 45 | } 46 | #endregion Relay Commands 47 | 48 | #region Annotated Language translation list 49 | public List AnnotatedLanguageList { get; } = []; 50 | #endregion Annotated Language translation list 51 | 52 | #region Add note to list of languages 53 | private void AddNote() 54 | { 55 | foreach (UILanguage item in UILanguage.DefinedLanguages) 56 | { 57 | // en-GB is a special case and therefore should not be listed. 58 | // See the comments in Languages\Strings.en-GB.xaml. 59 | if (item.LanguageCode is not "en-GB") 60 | { 61 | item.Note = GetLanguagePercent(item.LanguageCode!); 62 | AnnotatedLanguageList.Add(item); 63 | } 64 | } 65 | } 66 | #endregion Add note to list of languages 67 | } 68 | -------------------------------------------------------------------------------- /WUView/Styles/SnackbarStyle.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 37 | 38 | -------------------------------------------------------------------------------- /WUView/Converters/EnumDescConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Enum description converter. 7 | /// 8 | /// 9 | /// Allows use of "Light Blue" instead of LightBlue or Light_Blue. 10 | /// 11 | internal abstract class EnumDescConverter : IValueConverter 12 | { 13 | /// 14 | /// Converts a value. 15 | /// 16 | /// The value produced by the binding source. 17 | /// The type of the binding target property. 18 | /// The converter parameter to use. 19 | /// The culture to use in the converter. 20 | /// 21 | /// A converted value. If the method returns , the valid null value is used. 22 | /// 23 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 24 | { 25 | if (value is not Enum myEnum) 26 | { 27 | return null; 28 | } 29 | string description = GetEnumDescription(myEnum); 30 | return !string.IsNullOrEmpty(description) ? description : myEnum.ToString(); 31 | } 32 | 33 | /// 34 | /// Gets the enum description. 35 | /// 36 | /// The enum 37 | /// The description 38 | public static string GetEnumDescription(Enum enumObj) 39 | { 40 | FieldInfo? field = enumObj.GetType().GetField(enumObj.ToString()); 41 | object[] attrArray = field!.GetCustomAttributes(false); 42 | 43 | if (attrArray.Length > 0) 44 | { 45 | DescriptionAttribute? attribute = attrArray[0] as DescriptionAttribute; 46 | return attribute!.Description; 47 | } 48 | else 49 | { 50 | return enumObj.ToString(); 51 | } 52 | } 53 | 54 | /// 55 | /// Not used. 56 | /// 57 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 58 | { 59 | return string.Empty; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /WUView/Converters/DateFormatConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Converters; 4 | 5 | /// 6 | /// Sets the date format. 7 | /// 8 | /// 9 | internal sealed class DateFormatConverter : IValueConverter 10 | { 11 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 12 | { 13 | if (value is DateTime dateTime) 14 | { 15 | DateTime item = dateTime; 16 | 17 | CultureInfo cult = CultureInfo.CurrentCulture; 18 | switch (UserSettings.Setting!.DateFormat) 19 | { 20 | case 1: 21 | return item.ToString("yyyy/MM/dd HH:mm", CultureInfo.InvariantCulture); 22 | case 2: 23 | return item.ToString("MM/dd/yyyy hh:mm tt", CultureInfo.InvariantCulture); 24 | case 3: 25 | return item.ToString("dd-MMM-yyyy HH:mm", CultureInfo.InvariantCulture); 26 | case 4: 27 | return item.ToUniversalTime().ToString("yyyy-MM-dd HH:mm UTC", CultureInfo.InvariantCulture); 28 | case 5: 29 | return item.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture); 30 | case 6: 31 | return item.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture); 32 | case 7: 33 | return item.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture); 34 | case 8: 35 | string cultDateOnly = cult.DateTimeFormat.ShortDatePattern; 36 | return item.ToString(cultDateOnly, CultureInfo.CurrentCulture); 37 | case 9: 38 | string cultDate = cult.DateTimeFormat.ShortDatePattern; 39 | string cultTime = cult.DateTimeFormat.ShortTimePattern; 40 | return item.ToString($"{cultDate} {cultTime}", CultureInfo.CurrentCulture); 41 | 42 | default: 43 | return item.ToString("MM/dd/yyyy", CultureInfo.InvariantCulture); 44 | } 45 | } 46 | return string.Empty; 47 | } 48 | 49 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 50 | { 51 | return Binding.DoNothing; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /WUView/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 WUView.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 | -------------------------------------------------------------------------------- /WUView/App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /WUView/Configuration/SettingChange.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | NLogHelpers.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.MaxUpdates): 38 | case nameof(UserSettings.Setting.ExcludeKBandResult): 39 | MainPage.RefreshAll(); 40 | break; 41 | 42 | case nameof(UserSettings.Setting.UILanguage): 43 | case nameof(UserSettings.Setting.LanguageTesting): 44 | case nameof(UserSettings.Setting.UseOSLanguage): 45 | LocalizationHelpers.SaveAndRestart(); 46 | break; 47 | } 48 | } 49 | #endregion User Setting change 50 | 51 | #region Temp Setting change 52 | /// 53 | /// Handle changes in TempSettings 54 | /// 55 | internal static void TempSettingChanged(object sender, PropertyChangedEventArgs e) 56 | { 57 | object? newValue = GetPropertyValue(sender, e); 58 | // Write to trace level to avoid unnecessary message in log file 59 | _log.Trace($"Temp Setting change: {e.PropertyName} New Value: {newValue}"); 60 | } 61 | #endregion Setting change 62 | 63 | #region Get property value 64 | /// 65 | /// Gets the value of the property 66 | /// 67 | /// An object containing the value of the property 68 | private static object? GetPropertyValue(object sender, PropertyChangedEventArgs e) 69 | { 70 | PropertyInfo? prop = sender.GetType().GetProperty(e.PropertyName!); 71 | return prop?.GetValue(sender, null); 72 | } 73 | #endregion Get property value 74 | } 75 | -------------------------------------------------------------------------------- /WUView/Languages/Strings.en-GB.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 29 | 30 | 31 | Accent colour set to {0} 32 | 33 | Theme, Accent Colour, UI Size, Row Height, Start Position and more 34 | 35 | Accent Colour 36 | Use accent colour for message background 37 | Start with window centred on screen 38 | 39 | Blue Grey 40 | Grey 41 | 42 | 43 | 44 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /WUView/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.ViewModels; 4 | 5 | public partial class SettingsViewModel : ObservableObject 6 | { 7 | #region Properties 8 | public static List? FontList { get; private set; } 9 | #endregion Properties 10 | 11 | #region Constructor 12 | public SettingsViewModel() 13 | { 14 | FontList ??= [.. Fonts.SystemFontFamilies.OrderBy(x => x.Source)]; 15 | } 16 | #endregion Constructor 17 | 18 | #region Relay commands 19 | 20 | #region Open app folder 21 | [RelayCommand] 22 | private static void OpenAppFolder() 23 | { 24 | string filePath = string.Empty; 25 | try 26 | { 27 | filePath = Path.Combine(AppInfo.AppDirectory, "Strings.test.xaml"); 28 | if (File.Exists(filePath)) 29 | { 30 | _ = Process.Start("explorer.exe", string.Format(CultureInfo.InvariantCulture, "/select,\"{0}\"", filePath)); 31 | } 32 | else 33 | { 34 | using Process p = new(); 35 | p.StartInfo.FileName = AppInfo.AppDirectory; 36 | p.StartInfo.UseShellExecute = true; 37 | p.StartInfo.ErrorDialog = false; 38 | _ = p.Start(); 39 | } 40 | } 41 | catch (Exception ex) 42 | { 43 | _log.Error(ex, $"Error trying to open {filePath}: {ex.Message}"); 44 | _ = new MDCustMsgBox(GetStringResource("MsgText_Error_FileExplorer"), 45 | GetStringResource("MsgText_ErrorCaption"), 46 | ButtonType.Ok, 47 | false, 48 | true, 49 | null, 50 | true).ShowDialog(); 51 | } 52 | } 53 | #endregion Open app folder 54 | 55 | #region Open settings 56 | [RelayCommand] 57 | private static void OpenSettings() 58 | { 59 | ConfigHelpers.SaveSettings(); 60 | TextFileViewer.ViewTextFile(ConfigHelpers.SettingsFileName); 61 | } 62 | #endregion Open settings 63 | 64 | #region Export settings 65 | [RelayCommand] 66 | private static void ExportSettings() 67 | { 68 | ConfigHelpers.ExportSettings(); 69 | } 70 | #endregion Export settings 71 | 72 | #region Import settings 73 | [RelayCommand] 74 | private static void ImportSettings() 75 | { 76 | ConfigHelpers.ImportSettings(); 77 | } 78 | #endregion Import settings 79 | 80 | #region List (dump) settings to log file 81 | [RelayCommand] 82 | private static void DumpSettings() 83 | { 84 | ConfigHelpers.DumpSettings(); 85 | NavigationViewModel.ViewLogFile(); 86 | } 87 | #endregion List (dump) settings to log file 88 | 89 | #region Compare languages 90 | [RelayCommand] 91 | private static void CompareLanguageKeys() 92 | { 93 | CompareLanguageDictionaries(); 94 | TextFileViewer.ViewTextFile(GetLogfileName()!); 95 | } 96 | #endregion Compare languages 97 | 98 | #endregion Relay commands 99 | } 100 | -------------------------------------------------------------------------------- /.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: "*Feel free to change the issue title above*" 10 | - type: markdown 11 | attributes: 12 | value: | 13 | Thanks for taking the time to fill out this bug report! 14 | - type: markdown 15 | attributes: 16 | value: | 17 | ### Please read the Known Issues and Troubleshooting topics in the Wiki before opening a Bug Report. 18 | - type: checkboxes 19 | attributes: 20 | label: Is there an existing issue for this? 21 | description: Please search to see if an issue already exists for the bug you encountered. 22 | options: 23 | - label: I have searched the existing issues and have read the Known Issues and Troubleshooting topics in the Wiki 24 | required: true 25 | - type: textarea 26 | id: what-happened 27 | attributes: 28 | label: What happened or didn't happen 29 | description: Describe what happened. 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: steps_to_reproduce 34 | attributes: 35 | label: Steps To Reproduce 36 | description: Steps to reproduce the behavior. 37 | placeholder: | 38 | 1. In this environment... 39 | 2. With this config... 40 | 3. Run '...' 41 | 4. See error... 42 | validations: 43 | required: false 44 | - type: dropdown 45 | id: installtype 46 | attributes: 47 | label: Installation or Update Method 48 | description: How was the version that is the subject of this issue installed or updated? 49 | options: 50 | - Installer (exe) 51 | - Portable (zip) 52 | - Winget 53 | validations: 54 | required: true 55 | - type: input 56 | id: version 57 | attributes: 58 | label: Version 59 | description: What version the app are you running? 60 | placeholder: ex. 1.0.0 61 | validations: 62 | required: true 63 | - type: input 64 | id: windows-version 65 | attributes: 66 | label: Windows Version 67 | description: What version of Windows are you running? Include the build number if possible. 68 | placeholder: ex. Windows 11 22H2 build 22621.1702 69 | validations: 70 | required: true 71 | - type: textarea 72 | id: logs 73 | attributes: 74 | label: Relevant log output 75 | description: | 76 | Please copy and paste (or attach) any relevant log output. 77 | 78 | You can open the application log file by clicking on the three dot menu in the upper right and selecting View Log File. 79 | 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* 80 | render: shell 81 | validations: 82 | required: false 83 | - type: textarea 84 | id: anything-else 85 | attributes: 86 | label: Anything else? 87 | description: | 88 | Links? References? Anything that will give us more context about the issue you are encountering! 89 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 90 | validations: 91 | required: false 92 | -------------------------------------------------------------------------------- /WUView/WUView.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WUView 5 | net8.0-windows10.0.19041.0 6 | 10.0.19041.0 7 | WinExe 8 | enable 9 | true 10 | Windows Update Viewer 11 | © 2020-$([System.DateTime]::UtcNow.Year) Tim Kennedy 12 | Tim Kennedy 13 | T_K 14 | Windows Update Viewer 15 | Images\UV.ico 16 | en-US 17 | false 18 | 19 | 20 | 21 | 22 | Recommended 23 | 8.0 24 | 25 | 26 | 27 | 28 | en 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -File "$(ProjectDir)PowerShell\GenBuildInfo.ps1" 61 | -assemblyName $(AssemblyName) 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /WUView/Styles/ButtonStyles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 23 | 24 | 25 | 26 | 43 | 44 | 45 | 46 | 63 | 64 | -------------------------------------------------------------------------------- /WUView/Helpers/SingleInstance.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | // Based on https://github.com/it3xl/WPF-app-Single-Instance-in-one-line-of-code 4 | 5 | namespace WUView.Helpers; 6 | 7 | /// 8 | /// Class to only allow a single instance of the application to run. 9 | /// 10 | /// 11 | /// If another instance of the application is started, it will activate 12 | /// the first instance and then shut down. 13 | /// 14 | public static class SingleInstance 15 | { 16 | #region Private fields 17 | private static bool _alreadyProcessedOnThisInstance; 18 | #endregion Private fields 19 | 20 | #region Create the application or exit if application exists 21 | /// Creates a single instance of the application. 22 | /// Name of the application. 23 | /// if set to true unique per user. 24 | internal static void Create(string appName, bool uniquePerUser = true) 25 | { 26 | if (_alreadyProcessedOnThisInstance) 27 | { 28 | return; 29 | } 30 | _alreadyProcessedOnThisInstance = true; 31 | 32 | Application app = Application.Current; 33 | 34 | string debugger = string.Empty; 35 | if (Debugger.IsAttached) 36 | { 37 | debugger = "-Debug"; 38 | } 39 | 40 | const string uniqueID = "{ABFC758B-06B6-4379-A712-835EB42D230F}"; 41 | string eventName = uniquePerUser ? $"{appName}-{uniqueID}-{Environment.UserName}{debugger}" : $"{appName}-{uniqueID}"; 42 | 43 | if (EventWaitHandle.TryOpenExisting(eventName, out EventWaitHandle? eventWaitHandle)) 44 | { 45 | ActivateFirstInstanceWindow(eventWaitHandle); 46 | 47 | Environment.Exit(0); 48 | } 49 | 50 | RegisterFirstInstanceWindowActivation(app, eventName); 51 | } 52 | #endregion Create the application or exit if application exists 53 | 54 | #region Set the event 55 | /// Sets the event 56 | /// The event wait handle. 57 | private static void ActivateFirstInstanceWindow(EventWaitHandle eventWaitHandle) 58 | { 59 | _ = eventWaitHandle.Set(); 60 | } 61 | #endregion Set the event 62 | 63 | #region Create the event handle and register the instance 64 | /// Registers the first instance window activation. 65 | /// The application. 66 | /// Name of the event. 67 | private static void RegisterFirstInstanceWindowActivation(Application app, string eventName) 68 | { 69 | EventWaitHandle eventWaitHandle = new( 70 | false, 71 | EventResetMode.AutoReset, 72 | eventName); 73 | 74 | _ = ThreadPool.RegisterWaitForSingleObject( 75 | eventWaitHandle, 76 | WaitOrTimerCallback, 77 | app, 78 | Timeout.Infinite, false); 79 | eventWaitHandle.Close(); 80 | } 81 | #endregion Create the event handle and register the instance 82 | 83 | #region Show the main window 84 | /// Shows the main window of the original instance 85 | private static void WaitOrTimerCallback(object? state, bool timedOut) 86 | { 87 | Application? app = (Application?)state; 88 | _ = app!.Dispatcher.BeginInvoke(new Action(MainWindowHelpers.ShowMainWindow)); 89 | } 90 | #endregion Show the main window 91 | } 92 | -------------------------------------------------------------------------------- /WUView/Helpers/LocalizationHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 Get current culture 23 | /// 24 | /// Gets the current culture. 25 | /// 26 | /// Current culture name 27 | public static string GetCurrentCulture() => CultureInfo.CurrentCulture.Name; 28 | 29 | /// 30 | /// Gets the current UI culture. 31 | /// 32 | /// Current UI culture name 33 | public static string GetCurrentUICulture() => CultureInfo.CurrentUICulture.Name; 34 | #endregion Get current culture 35 | 36 | #region Apply language settings 37 | /// 38 | /// Apply language settings. 39 | /// 40 | /// The resource dictionary corresponding to the selected language. 41 | public static void ApplyLanguageSettings(ResourceDictionary LanguageDictionary) 42 | { 43 | LanguageStrings = LanguageDictionary.Count; 44 | LanguageFile = LanguageDictionary.Source.OriginalString; 45 | if (LanguageStrings == 0) 46 | { 47 | _log.Warn($"No strings loaded from {LanguageFile}"); 48 | } 49 | _log.Debug($"Current culture: {GetCurrentCulture()} UI: {GetCurrentUICulture()}"); 50 | _log.Debug($"{LanguageStrings} strings loaded from {LanguageFile}"); 51 | } 52 | #endregion Apply language settings 53 | 54 | #region Check if Use OS Language is set 55 | /// 56 | /// Check if the option to use the OS language is set and if the language is defined. 57 | /// 58 | /// The language code to check. 59 | /// True if the language is defined and the language exists. Otherwise return false. 60 | public static bool CheckUseOsLanguage(string language) 61 | { 62 | if (UserSettings.Setting!.UseOSLanguage) 63 | { 64 | if (UILanguage.DefinedLanguages.Exists(x => x.LanguageCode == language)) 65 | { 66 | return true; 67 | } 68 | _log.Warn($"Language \"{language}\" has not been defined in this application. Defaulting to en-US and setting \"Use OS Language\" to false."); 69 | UserSettings.Setting.UseOSLanguage = false; 70 | ConfigHelpers.SaveSettings(); 71 | } 72 | return false; 73 | } 74 | #endregion Check if Use OS Language is set 75 | 76 | #region Save settings and restart app on language change 77 | /// 78 | /// Saves settings and restarts the application. Invoked when language is changed. 79 | /// 80 | public static void SaveAndRestart() 81 | { 82 | ConfigHelpers.SaveSettings(); 83 | using Process p = new(); 84 | p.StartInfo.FileName = AppInfo.AppPath; 85 | p.StartInfo.UseShellExecute = true; 86 | _ = p.Start(); 87 | _log.Debug("Restarting for language change."); 88 | Application.Current.Shutdown(); 89 | } 90 | #endregion Save settings and restart app on language change 91 | } 92 | -------------------------------------------------------------------------------- /WUView/Models/UILanguage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Models; 4 | 5 | /// 6 | /// Class for language properties. 7 | /// 8 | /// 9 | public partial class UILanguage : ObservableObject 10 | { 11 | #region Properties 12 | /// 13 | /// The name of the contributor. Can be any string chosen by the contributor. 14 | /// 15 | [ObservableProperty] 16 | private string? _contributor; 17 | 18 | /// 19 | /// Total number of strings in the language resource dictionary. 20 | /// 21 | [ObservableProperty] 22 | private int? _currentLanguageStringCount = LocalizationHelpers.LanguageStrings; 23 | 24 | /// 25 | /// Total number of strings in the (en-US) language resource dictionary. 26 | /// 27 | [ObservableProperty] 28 | private int? _defaultStringCount = App.DefaultLanguageStrings; 29 | 30 | /// 31 | /// English spelling of the language name. 32 | /// 33 | [ObservableProperty] 34 | private string? _language; 35 | 36 | /// 37 | /// Language code in the form xx-XX 38 | /// 39 | [ObservableProperty] 40 | private string? _languageCode; 41 | 42 | /// 43 | /// Native spelling of the language name. 44 | /// 45 | [ObservableProperty] 46 | private string? _languageNative; 47 | 48 | /// 49 | /// Note field. Currently unused. 50 | /// 51 | [ObservableProperty] 52 | private string? _note = string.Empty; 53 | #endregion Properties 54 | 55 | #region Override ToString 56 | /// 57 | /// Overrides the ToString method. 58 | /// 59 | /// 60 | /// The language code as a string. 61 | /// 62 | public override string? ToString() => LanguageCode; 63 | #endregion Override ToString 64 | 65 | #region List of languages 66 | /// 67 | /// List of languages with language code 68 | /// 69 | /// 70 | /// Please add new entries to the bottom. The languages will be sorted by language code. 71 | /// 72 | private static List LanguageList { get; } = 73 | [ 74 | new () {Language = "English", LanguageCode = "en-US", LanguageNative = "English", Contributor = "Timthreetwelve", Note="Default"}, 75 | new () {Language = "English", LanguageCode = "en-GB", LanguageNative = "English", Contributor = "Timthreetwelve"}, 76 | new () {Language = "Spanish", LanguageCode = "es-ES", LanguageNative = "Español", Contributor = "My AWESOME brother Steve and me"}, 77 | new () {Language = "Italian", LanguageCode = "it-IT", LanguageNative = "Italiano", Contributor = "RB"}, 78 | new () {Language = "Dutch", LanguageCode = "nl-NL", LanguageNative = "Nederlands", Contributor = "Tim"}, 79 | new () {Language = "German", LanguageCode = "de-DE", LanguageNative = "Deutsch", Contributor = "Timthreetwelve & Henry2o1o"}, 80 | new () {Language = "French", LanguageCode = "fr-FR", LanguageNative = "Français", Contributor = "Timthreetwelve"}, 81 | new () {Language = "Catalan", LanguageCode = "ca-ES", LanguageNative = "Català", Contributor = "Timthreetwelve"}, 82 | new () {Language = "Polish", LanguageCode = "pl-PL", LanguageNative = "Polski", Contributor = "FadeMind"}, 83 | new () {Language = "Slovak", LanguageCode = "sk-SK", LanguageNative = "Slovenčina", Contributor = "VAIO"}, 84 | new () {Language = "Slovenian", LanguageCode = "sl-SL", LanguageNative = "Slovenščina", Contributor = "Jadran Rudec"}, 85 | new () {Language = "Portuguese (Brazil)", LanguageCode = "pt-BR", LanguageNative = "Português (Brasil)", Contributor = "igorruckert"}, 86 | new () {Language = "Korean", LanguageCode = "ko-KR", LanguageNative = "한국어", Contributor = "VenusGirl💗 (비너스걸)"}, 87 | new () {Language = "Japanese", LanguageCode = "ja-JP", LanguageNative = "日本語", Contributor = "coolvitto"}, 88 | ]; 89 | 90 | /// 91 | /// List of defined languages ordered by LanguageCode. 92 | /// 93 | public static List DefinedLanguages => [.. LanguageList.OrderBy(x => x.LanguageCode)]; 94 | #endregion List of languages 95 | } 96 | -------------------------------------------------------------------------------- /WUView/Helpers/NLogHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | // Throw exception if there are error in configuration 26 | LogManager.ThrowConfigExceptions = true; 27 | 28 | // New NLog configuration 29 | LoggingConfiguration config = new(); 30 | 31 | // create log file Target for NLog 32 | FileTarget logfile = new("logfile") 33 | { 34 | // new file on startup 35 | DeleteOldFileOnStartup = newFile, 36 | 37 | // create the file if needed 38 | FileName = CreateFilename(), 39 | 40 | // message and footer layouts 41 | Footer = "${date:format=yyyy/MM/dd HH\\:mm\\:ss}", 42 | Layout = "${date:format=yyyy/MM/dd HH\\:mm\\:ss} " + 43 | "${pad:padding=-5:inner=${level:uppercase=true}} " + 44 | "${message}${onexception:${newline}${exception:format=tostring}}" 45 | }; 46 | 47 | // add the log file target 48 | config.AddTarget(logfile); 49 | 50 | // add the rule for the log file 51 | LoggingRule file = new("*", LogLevel.Debug, logfile) 52 | { 53 | RuleName = "LogToFile" 54 | }; 55 | config.LoggingRules.Add(file); 56 | 57 | // create debugger target 58 | DebuggerTarget debugger = new("debugger") 59 | { 60 | Layout = "${processtime} >>> ${message} " 61 | }; 62 | 63 | // add the target 64 | config.AddTarget(debugger); 65 | 66 | // add the rule 67 | LoggingRule bug = new("*", LogLevel.Trace, debugger); 68 | config.LoggingRules.Add(bug); 69 | 70 | // add the configuration to NLog 71 | LogManager.Configuration = config; 72 | 73 | // Lastly, set the logging level based on setting 74 | SetLogLevel(UserSettings.Setting!.IncludeDebug); 75 | } 76 | #endregion Create the NLog configuration 77 | 78 | #region Create a filename in the temp folder 79 | private static string CreateFilename() 80 | { 81 | // create filename string 82 | string appName = AppInfo.AppName; 83 | string today = DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture); 84 | string filename = Debugger.IsAttached ? $"{appName}.{today}.debug.log" : $"{appName}.{today}.log"; 85 | 86 | // combine temp folder with filename 87 | string tempDir = Path.GetTempPath(); 88 | return Path.Combine(tempDir, "T_K", filename); 89 | } 90 | #endregion Create a filename in the temp folder 91 | 92 | #region Set NLog logging level 93 | /// 94 | /// Set the NLog logging level to Debug or Info 95 | /// 96 | /// If true set level to Debug, otherwise set to Info 97 | public static void SetLogLevel(bool debug) 98 | { 99 | LoggingConfiguration config = LogManager.Configuration; 100 | 101 | LoggingRule rule = config.FindRuleByName("LogToFile"); 102 | if (rule != null) 103 | { 104 | LogLevel level = debug ? LogLevel.Debug : LogLevel.Info; 105 | rule.SetLoggingLevels(level, LogLevel.Fatal); 106 | LogManager.ReconfigExistingLoggers(); 107 | } 108 | } 109 | #endregion Set NLog logging level 110 | 111 | #region Get the log file name 112 | /// 113 | /// Gets the filename for the NLog log fie 114 | /// 115 | /// Name of the log file. 116 | public static string? GetLogfileName() 117 | { 118 | LoggingConfiguration config = LogManager.Configuration; 119 | return (config.FindTargetByName("logfile") 120 | as FileTarget)?.FileName.Render(new LogEventInfo { TimeStamp = DateTime.Now }); 121 | } 122 | #endregion Get the log file name 123 | } 124 | -------------------------------------------------------------------------------- /WUView/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Windows Updaet Viewer Logo 4 | 5 |

6 |

7 | Windows Update Viewer 8 |

9 | 10 |
11 | 12 | [![GitHub](https://img.shields.io/github/license/Timthreetwelve/WUView?style=plastic&color=seagreen)](https://github.com/Timthreetwelve/WUView/blob/main/LICENSE) 13 | [![NET6win](https://img.shields.io/badge/.NET-8.0--Windows-blueviolet?style=plastic)](https://dotnet.microsoft.com/en-us/download) 14 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/Timthreetwelve/WUView?style=plastic)](https://github.com/Timthreetwelve/WUView/releases/latest) 15 | [![GitHub Release Date](https://img.shields.io/github/release-date/timthreetwelve/WUView?style=plastic&color=orange)](https://github.com/Timthreetwelve/WUView/releases/latest) 16 | [![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/timthreetwelve/WUView/latest?style=plastic)](https://github.com/Timthreetwelve/WUView/commits/main) 17 | [![GitHub last commit](https://img.shields.io/github/last-commit/timthreetwelve/WUView?style=plastic)](https://github.com/Timthreetwelve/WUView/commits/main) 18 | [![GitHub commits](https://img.shields.io/github/commit-activity/m/timthreetwelve/WUView?style=plastic)](https://github.com/Timthreetwelve/WUView/commits/main) 19 | [![GitHub Stars](https://img.shields.io/github/stars/timthreetwelve/wuview?style=plastic&color=goldenrod&logo=github)](https://docs.github.com/en/get-started/exploring-projects-on-github/saving-repositories-with-stars) 20 | [![GitHub all releases](https://img.shields.io/github/downloads/Timthreetwelve/WUView/total?style=plastic&label=total%20downloads&color=teal)](https://github.com/Timthreetwelve/WUView/releases) 21 | [![GitHub release (by tag)](https://img.shields.io/github/downloads/timthreetwelve/wuview/latest/total?style=plastic&color=2196F3&label=downloads%20latest%20version)](https://github.com/Timthreetwelve/WUView/releases/latest) 22 | [![GitHub Issues](https://img.shields.io/github/issues/timthreetwelve/wuview?style=plastic&color=orangered)](https://github.com/Timthreetwelve/WUView/issues) 23 | [![GitHub Issues](https://img.shields.io/github/issues-closed/timthreetwelve/wuview?style=plastic&color=slateblue)](https://github.com/Timthreetwelve/WUView/issues) 24 | 25 |
26 | 27 | ### What is Windows Update Viewer? 28 | Windows Update Viewer (WUView) is an application that displays Windows Update history. It is meant to be a lightweight application that is easy to use. There aren't any confusing categories; every update is listed in one place. Updates that you don't want to see can be permanently excluded or temporarily filtered. 29 | 30 | WUView uses the Windows Update Agent API and Windows event logs to display details of installed updates. Event log entries are associated with individual updates by using the "KB" number. If an update does not use a KB number or isn't presented in a consistent format, no event log entries will be displayed. 31 | 32 | Please be aware that Windows Update Viewer can only display updates provided by the [Windows Update Agent API](https://learn.microsoft.com/en-us/windows/win32/wua_sdk/portal-client). If you have reason to suspect that the application isn't returning correct or complete information, see the [Troubleshooting](https://github.com/Timthreetwelve/WUView/wiki/Troubleshooting) and [Known Issues](https://github.com/Timthreetwelve/WUView/wiki/Known-Issues) topics in the Wiki. 33 | 34 | If you are using Windows 10 please see [Known Issues](https://github.com/Timthreetwelve/WUView/wiki/Known-Issues). 35 | 36 | See the [Wiki](https://github.com/Timthreetwelve/WUView/wiki) for additional information. 37 | 38 | ### Windows Update Viewer is multilingual! 39 | Languages are being added as of version 0.5.21. Please see [Contribute a Translation](https://github.com/Timthreetwelve/WUView/wiki/Contribute-a-Translation) topic in the Wiki if you would like to contribute a translation. 40 | 41 | ### Windows Update Viewer uses .NET 8 42 | Self-contained versions are available if .NET 8 isn't installed. See the [releases page](https://github.com/Timthreetwelve/WUView/releases). 43 | 44 | ### Download Windows Update Viewer 45 | You can always download the latest release from the [releases page](https://github.com/Timthreetwelve/WUView/releases). Note that "portable" releases are provided as well as the traditional installers. 46 | 47 | ### Features 48 | * View details for each update, including Event Log entries, if available. 49 | * Easily exclude entries, such as Defender. 50 | * Temporarily filter entries. 51 | * Link to the Support URL. 52 | * Link to HResult explanation (the HResult is placed in the clipboard). 53 | * Toggle the visibility of the details pane. 54 | * Save to a text or CSV file. 55 | * Open Windows Update from the app. 56 | * Choose accent color and one of three themes. 57 | * Adjust app size and row spacing. (Helpful for us users that don't see as well as we used to.) 58 | * Select the interface language. 59 | 60 | ### The Main Window 61 | ![WUView screenshot](https://github.com/Timthreetwelve/WUView/blob/main/Images/WUView_2024-04-04_17-11-03.png) 62 | 63 | 64 | -------------------------------------------------------------------------------- /WUView/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 WUView.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 red 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 | -------------------------------------------------------------------------------- /WUView/Helpers/GitHubHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | using Octokit; 4 | 5 | namespace WUView.Helpers; 6 | 7 | /// 8 | /// Class for methods that check GitHub for releases 9 | /// 10 | internal static class GitHubHelpers 11 | { 12 | #region MainWindow Instance 13 | private static readonly MainWindow? _mainWindow = System.Windows.Application.Current.MainWindow as MainWindow; 14 | #endregion MainWindow Instance 15 | 16 | #region Check for newer release 17 | /// 18 | /// Checks to see if a newer release is available. 19 | /// 20 | /// 21 | /// If the release version is greater than the current version 22 | /// a message box will be shown asking to go to the releases page. 23 | /// 24 | public static async Task CheckRelease() 25 | { 26 | try 27 | { 28 | SnackbarMsg.ClearAndQueueMessage(GetStringResource("MsgText_AppUpdateChecking")); 29 | Release? release = await GetLatestReleaseAsync(AppConstString.RepoOwner, AppConstString.RepoName); 30 | if (release == null) 31 | { 32 | CheckFailed(); 33 | return; 34 | } 35 | 36 | string tag = release.TagName; 37 | if (string.IsNullOrEmpty(tag)) 38 | { 39 | CheckFailed(); 40 | return; 41 | } 42 | 43 | if (tag.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)) 44 | { 45 | tag = tag.ToLower(CultureInfo.InvariantCulture).TrimStart('v'); 46 | } 47 | 48 | Version latestVersion = new(tag); 49 | 50 | _log.Debug($"Latest version is {latestVersion} released on {release.PublishedAt!.Value.UtcDateTime} UTC"); 51 | 52 | if (latestVersion <= AppInfo.AppVersionVer) 53 | { 54 | string msg = GetStringResource("MsgText_AppUpdateNoneFound"); 55 | _log.Debug(msg); 56 | _ = new MDCustMsgBox(msg, 57 | "Windows Update Viewer", 58 | ButtonType.Ok, 59 | false, 60 | true, 61 | _mainWindow).ShowDialog(); 62 | } 63 | else 64 | { 65 | _log.Debug($"A newer release ({latestVersion}) has been found."); 66 | string msg = string.Format(CultureInfo.InvariantCulture, MsgTextAppUpdateNewerFound, latestVersion); 67 | _ = new MDCustMsgBox($"{msg}\n\n" + 68 | $"{GetStringResource("MsgText_AppUpdateGoToRelease")}\n\n" + 69 | $"{GetStringResource("MsgText_AppUpdateClose")}", 70 | "Windows Update Viewer", 71 | ButtonType.YesNo, 72 | false, 73 | true, 74 | _mainWindow).ShowDialog(); 75 | 76 | if (MDCustMsgBox.CustResult == CustResultType.Yes) 77 | { 78 | string opening = GetStringResource("MsgText_Opening"); 79 | _log.Debug($"{opening} {release.HtmlUrl}"); 80 | string url = release.HtmlUrl; 81 | Process p = new(); 82 | p.StartInfo.FileName = url; 83 | p.StartInfo.UseShellExecute = true; 84 | p.Start(); 85 | System.Windows.Application.Current.Shutdown(); 86 | } 87 | } 88 | } 89 | catch (Exception ex) 90 | { 91 | _log.Error(ex, "Error encountered while checking version"); 92 | CheckFailed(); 93 | } 94 | } 95 | #endregion Check for newer release 96 | 97 | #region Get latest release 98 | /// 99 | /// Gets the latest release. 100 | /// 101 | /// The repository owner. 102 | /// Name of the repository. 103 | /// Release object 104 | private static async Task GetLatestReleaseAsync(string repoOwner, string repoName) 105 | { 106 | try 107 | { 108 | GitHubClient client = new(new ProductHeaderValue(repoName)); 109 | _log.Debug("Checking GitHub for latest release."); 110 | 111 | return await client.Repository.Release.GetLatest(repoOwner, repoName); 112 | } 113 | catch (Exception ex) 114 | { 115 | _log.Error(ex, "Get latest release from GitHub failed."); 116 | return null!; 117 | } 118 | } 119 | #endregion Get latest release 120 | 121 | #region Check failed message 122 | /// 123 | /// Display a message box stating that the release check failed. 124 | /// 125 | private static void CheckFailed() 126 | { 127 | _ = new MDCustMsgBox(GetStringResource("MsgText_AppUpdateCheckFailed"), 128 | "Windows Update Viewer", 129 | ButtonType.Ok, 130 | false, 131 | true, 132 | _mainWindow, 133 | true).ShowDialog(); 134 | } 135 | #endregion Check failed message 136 | } 137 | -------------------------------------------------------------------------------- /WUView/Models/Enums.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Models; 4 | 5 | /// 6 | /// Navigation Page 7 | /// 8 | public enum NavPage 9 | { 10 | Viewer = 0, 11 | Settings = 1, 12 | About = 2, 13 | Exit = 3 14 | } 15 | 16 | /// 17 | /// Theme type, Light, Dark, or System 18 | /// 19 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 20 | public enum ThemeType 21 | { 22 | [LocalizedDescription("SettingsEnum_Theme_Light")] 23 | Light = 0, 24 | [LocalizedDescription("SettingsEnum_Theme_Dark")] 25 | Dark = 1, 26 | [LocalizedDescription("SettingsEnum_Theme_Darker")] 27 | Darker = 2, 28 | [LocalizedDescription("SettingsEnum_Theme_System")] 29 | System = 3, 30 | [LocalizedDescription("SettingsEnum_Theme_DarkBlue")] 31 | DarkBlue = 4 32 | } 33 | 34 | /// 35 | /// Size of the UI, Smallest, Smaller, Small, Default, Large, Larger, or Largest 36 | /// 37 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 38 | public enum MySize 39 | { 40 | [LocalizedDescription("SettingsEnum_Size_Smallest")] 41 | Smallest = 0, 42 | [LocalizedDescription("SettingsEnum_Size_Smaller")] 43 | Smaller = 1, 44 | [LocalizedDescription("SettingsEnum_Size_Small")] 45 | Small = 2, 46 | [LocalizedDescription("SettingsEnum_Size_Default")] 47 | Default = 3, 48 | [LocalizedDescription("SettingsEnum_Size_Large")] 49 | Large = 4, 50 | [LocalizedDescription("SettingsEnum_Size_Larger")] 51 | Larger = 5, 52 | [LocalizedDescription("SettingsEnum_Size_Largest")] 53 | Largest = 6 54 | } 55 | 56 | /// 57 | /// One of the 19 predefined Material Design in XAML colors 58 | /// 59 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 60 | public enum AccentColor 61 | { 62 | [LocalizedDescription("SettingsEnum_AccentColor_Red")] 63 | Red = 0, 64 | [LocalizedDescription("SettingsEnum_AccentColor_Pink")] 65 | Pink = 1, 66 | [LocalizedDescription("SettingsEnum_AccentColor_Purple")] 67 | Purple = 2, 68 | [LocalizedDescription("SettingsEnum_AccentColor_DeepPurple")] 69 | DeepPurple = 3, 70 | [LocalizedDescription("SettingsEnum_AccentColor_Indigo")] 71 | Indigo = 4, 72 | [LocalizedDescription("SettingsEnum_AccentColor_Blue")] 73 | Blue = 5, 74 | [LocalizedDescription("SettingsEnum_AccentColor_LightBlue")] 75 | LightBlue = 6, 76 | [LocalizedDescription("SettingsEnum_AccentColor_Cyan")] 77 | Cyan = 7, 78 | [LocalizedDescription("SettingsEnum_AccentColor_Teal")] 79 | Teal = 8, 80 | [LocalizedDescription("SettingsEnum_AccentColor_Green")] 81 | Green = 9, 82 | [LocalizedDescription("SettingsEnum_AccentColor_LightGreen")] 83 | LightGreen = 10, 84 | [LocalizedDescription("SettingsEnum_AccentColor_Lime")] 85 | Lime = 11, 86 | [LocalizedDescription("SettingsEnum_AccentColor_Yellow")] 87 | Yellow = 12, 88 | [LocalizedDescription("SettingsEnum_AccentColor_Amber")] 89 | Amber = 13, 90 | [LocalizedDescription("SettingsEnum_AccentColor_Orange")] 91 | Orange = 14, 92 | [LocalizedDescription("SettingsEnum_AccentColor_DeepOrange")] 93 | DeepOrange = 15, 94 | [LocalizedDescription("SettingsEnum_AccentColor_Brown")] 95 | Brown = 16, 96 | [LocalizedDescription("SettingsEnum_AccentColor_Gray")] 97 | Gray = 17, 98 | [LocalizedDescription("SettingsEnum_AccentColor_BlueGray")] 99 | BlueGray = 18, 100 | [LocalizedDescription("SettingsEnum_AccentColor_Black")] 101 | Black = 19, 102 | [LocalizedDescription("SettingsEnum_AccentColor_White")] 103 | White = 20, 104 | } 105 | 106 | /// 107 | /// Space between rows in the DataGrid 108 | /// 109 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 110 | public enum Spacing 111 | { 112 | [LocalizedDescription("SettingsEnum_Spacing_Compact")] 113 | Compact = 0, 114 | [LocalizedDescription("SettingsEnum_Spacing_Comfortable")] 115 | Comfortable = 1, 116 | [LocalizedDescription("SettingsEnum_Spacing_Wide")] 117 | Wide = 2 118 | } 119 | 120 | /// 121 | /// Maximum number of updates to get 122 | /// 123 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 124 | public enum MaxUpdates 125 | { 126 | [LocalizedDescription("SettingsEnum_MaxUpdates_All")] 127 | All = 0, 128 | [LocalizedDescription("SettingsEnum_MaxUpdates_50")] 129 | Max50 = 1, 130 | [LocalizedDescription("SettingsEnum_MaxUpdates_100")] 131 | Max100 = 2, 132 | [LocalizedDescription("SettingsEnum_MaxUpdates_250")] 133 | Max250 = 3, 134 | [LocalizedDescription("SettingsEnum_MaxUpdates_500")] 135 | Max500 = 4 136 | } 137 | 138 | /// 139 | /// Date format choices 140 | /// 141 | /// 142 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 143 | public enum DateFormat 144 | { 145 | [Description("MM/dd/yyyy")] 146 | MMddyyyy = 0, 147 | [Description("yyyy/MM/dd HH:mm")] 148 | yyyyMMddHHmm = 1, 149 | [Description("MM/dd/yyyy hh:mm tt")] 150 | MMddyyyhhmmtt = 2, 151 | [Description("d-MMM-yyyy H:mm")] 152 | dMMMyyyyHmm = 3, 153 | [Description("yyyy-MM-dd HH:mm UTC")] 154 | yyyyMMddHHmmUTC = 4, 155 | [Description("dd/MM/yyyy")] 156 | ddMMyyyy = 5, 157 | [Description("dd/MM/yyyy HH:mm")] 158 | ddMMyyyyHHmm = 6, 159 | [Description("yyyy/MM/dd")] 160 | ddMMyy = 7, 161 | [LocalizedDescription("SettingsEnum_DateFormat_RegionalDate")] 162 | RegionalDateOnly = 8, 163 | [LocalizedDescription("SettingsEnum_DateFormat_RegionalDateTime")] 164 | RegionalDateTime = 9 165 | } 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 | -------------------------------------------------------------------------------- /WUView/Helpers/AppInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.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 | -------------------------------------------------------------------------------- /WUView/Dialogs/ExcludesEditor.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | 63 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 89 | 90 | 98 | 99 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /WUView/Configuration/UserSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Configuration; 4 | 5 | [INotifyPropertyChanged] 6 | public partial class UserSettings : ConfigManager 7 | { 8 | #region Properties 9 | /// 10 | /// Select the first row of the data grid. 11 | /// 12 | [ObservableProperty] 13 | private bool _autoSelectFirstRow; 14 | 15 | /// 16 | /// Show updates with the current date in bold. 17 | /// 18 | [ObservableProperty] 19 | private static bool _boldToday = true; 20 | 21 | /// 22 | /// Used to persist the column order if the user changes it. 23 | /// 24 | [ObservableProperty] 25 | private int _columnDate = 1; 26 | 27 | /// 28 | /// Used to persist the column order if the user changes it. 29 | /// 30 | [ObservableProperty] 31 | private static int _columnKB; 32 | 33 | /// 34 | /// Used to persist the column order if the user changes it. 35 | /// 36 | [ObservableProperty] 37 | private int _columnResult = 3; 38 | 39 | /// 40 | /// Used to persist the column order if the user changes it. 41 | /// 42 | [ObservableProperty] 43 | private int _columnTitle = 2; 44 | 45 | /// 46 | /// Date format. 47 | /// 48 | [ObservableProperty] 49 | private int _dateFormat = 9; 50 | 51 | /// 52 | /// Height of the details pane. 53 | /// 54 | [ObservableProperty] 55 | private double _detailsHeight = 300; 56 | 57 | /// 58 | /// Used to determine used to determine scaling of dialogs. 59 | /// 60 | [ObservableProperty] 61 | private static double _dialogScale = 1; 62 | 63 | /// 64 | /// Toggle inclusion of KB number and result when excluding updates. 65 | /// 66 | [ObservableProperty] 67 | private bool _excludeKBandResult; 68 | 69 | /// 70 | /// Toggle hiding excluded updates. 71 | /// 72 | [ObservableProperty] 73 | private bool _hideExcluded = true; 74 | 75 | /// 76 | /// Include debug level messages in the log file. 77 | /// 78 | [ObservableProperty] 79 | private bool _includeDebug = true; 80 | 81 | /// 82 | /// Keep window topmost. 83 | /// 84 | [ObservableProperty] 85 | private bool _keepOnTop; 86 | 87 | /// 88 | /// Enable language testing. 89 | /// 90 | [ObservableProperty] 91 | private bool _languageTesting; 92 | 93 | /// 94 | /// Maximum number of updates to get. 95 | /// 96 | [ObservableProperty] 97 | private MaxUpdates _maxUpdates = MaxUpdates.All; 98 | 99 | /// 100 | /// Accent color. 101 | /// 102 | [ObservableProperty] 103 | private AccentColor _primaryColor = AccentColor.Blue; 104 | 105 | /// 106 | /// Vertical spacing in the data grids. 107 | /// 108 | [ObservableProperty] 109 | private Spacing _rowSpacing = Spacing.Comfortable; 110 | 111 | /// 112 | /// Font used in datagrids. 113 | /// 114 | [ObservableProperty] 115 | private string? _selectedFont = "Segoe UI"; 116 | 117 | /// 118 | /// Font size used throughout the application. 119 | /// Defaults to 14 which was the original size. 120 | /// 121 | [ObservableProperty] 122 | private double _selectedFontSize = 14; 123 | 124 | /// 125 | /// Show the details pane at the bottom. 126 | /// 127 | [ObservableProperty] 128 | private bool _showDetails = true; 129 | 130 | /// 131 | /// Show Exit in the navigation menu. 132 | /// 133 | [ObservableProperty] 134 | private bool _showExitInNav = true; 135 | 136 | /// 137 | /// Show messages in log for updates with non-zero result code. 138 | /// 139 | [ObservableProperty] 140 | private bool _showLogWarnings = true; 141 | 142 | /// 143 | /// Option start with window centered on screen. 144 | /// 145 | [ObservableProperty] 146 | private bool _startCentered = true; 147 | 148 | /// 149 | /// Defined language to use in the UI. 150 | /// 151 | [ObservableProperty] 152 | private string _uILanguage = "en-US"; 153 | 154 | /// 155 | /// Amount of UI zoom. 156 | /// 157 | [ObservableProperty] 158 | private MySize _uISize = MySize.Default; 159 | 160 | /// 161 | /// Theme type. 162 | /// 163 | [ObservableProperty] 164 | private ThemeType _uITheme = ThemeType.System; 165 | 166 | /// 167 | /// Use accent color for snack bar message background. 168 | /// 169 | [ObservableProperty] 170 | private bool _useAccentColorOnSnackbar; 171 | 172 | /// 173 | /// Use the operating system language (if one has been provided). 174 | /// 175 | [ObservableProperty] 176 | private bool _useOSLanguage = true; 177 | 178 | /// 179 | /// Height of the window. 180 | /// 181 | [ObservableProperty] 182 | private double _windowHeight = 650; 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 = 1200; 201 | #endregion Properties 202 | } 203 | -------------------------------------------------------------------------------- /WUView/Configuration/ConfigHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tim Kennedy. All Rights Reserved. Licensed under the MIT License. 2 | 3 | namespace WUView.Configuration; 4 | 5 | /// 6 | /// Class for methods used for creating, reading and saving settings. 7 | /// 8 | public static class ConfigHelpers 9 | { 10 | #region Properties 11 | public static string SettingsFileName { get; private set; } = null!; 12 | 13 | private static JsonSerializerOptions JsonOptions { get; } = new() 14 | { 15 | WriteIndented = true 16 | }; 17 | #endregion Properties 18 | 19 | #region MainWindow Instance 20 | private static readonly MainWindow? _mainWindow = Application.Current.MainWindow as MainWindow; 21 | #endregion MainWindow Instance 22 | 23 | #region Initialize settings 24 | /// 25 | /// Initialization method. Gets the file name for settings file and creates it if it 26 | /// doesn't exist. 27 | /// 28 | /// Option name of settings file 29 | public static void InitializeSettings(string settingsFile = "usersettings.json") 30 | { 31 | string? settingsDir = Path.GetDirectoryName(AppContext.BaseDirectory); 32 | SettingsFileName = Path.Combine(settingsDir!, settingsFile); 33 | 34 | if (!File.Exists(SettingsFileName)) 35 | { 36 | UserSettings.Setting = new UserSettings(); 37 | SaveSettings(); 38 | } 39 | ConfigManager.Setting = ReadConfiguration(); 40 | 41 | ConfigManager.Setting = new TempSettings(); 42 | } 43 | #endregion Initialize settings 44 | 45 | #region Read setting from file 46 | /// 47 | /// Read settings from JSON file. 48 | /// 49 | /// UserSettings 50 | private static UserSettings ReadConfiguration() 51 | { 52 | try 53 | { 54 | string json = File.ReadAllText(SettingsFileName); 55 | UserSettings? settings = JsonSerializer.Deserialize(json); 56 | return settings!; 57 | } 58 | catch (Exception ex) 59 | { 60 | _ = MessageBox.Show($"Error reading settings file.\n{ex.Message}", 61 | "Error", 62 | MessageBoxButton.OK, 63 | MessageBoxImage.Error); 64 | return new UserSettings(); 65 | } 66 | } 67 | #endregion Read setting from file 68 | 69 | #region Save settings to JSON file 70 | /// 71 | /// Write settings to JSON file. 72 | /// 73 | public static void SaveSettings() 74 | { 75 | try 76 | { 77 | string json = JsonSerializer.Serialize(UserSettings.Setting, JsonOptions); 78 | File.WriteAllText(SettingsFileName, json); 79 | } 80 | catch (Exception ex) 81 | { 82 | _ = MessageBox.Show($"{GetStringResource("MsgText_ErrorSavingSettings")}\n{ex.Message}", 83 | GetStringResource("MsgText_ErrorCaption"), 84 | MessageBoxButton.OK, 85 | MessageBoxImage.Error); 86 | } 87 | } 88 | #endregion Save settings to JSON file 89 | 90 | #region Export settings 91 | /// 92 | /// Exports the current settings to a JSON file. 93 | /// 94 | public static void ExportSettings() 95 | { 96 | try 97 | { 98 | string appPart = AppInfo.AppProduct.Replace(" ", ""); 99 | string settingsPart = GetStringResource("NavItem_Settings"); 100 | string datePart = DateTime.Now.ToString("yyyyMMdd", CultureInfo.CurrentCulture); 101 | SaveFileDialog saveFile = new() 102 | { 103 | CheckPathExists = true, 104 | Filter = "JSON File|*.json|All Files|*.*", 105 | FileName = $"{appPart}_{settingsPart}_{datePart}.json" 106 | }; 107 | 108 | if (saveFile.ShowDialog() == true) 109 | { 110 | _log.Debug($"Exporting settings file to {saveFile.FileName}."); 111 | string json = JsonSerializer.Serialize(UserSettings.Setting, JsonOptions); 112 | File.WriteAllText(saveFile.FileName, json); 113 | } 114 | } 115 | catch (Exception ex) 116 | { 117 | _log.Debug(ex, "Error exporting settings file."); 118 | _ = MessageBox.Show($"{GetStringResource("MsgText_ErrorExportingSettings")}\n{ex.Message}", 119 | GetStringResource("MsgText_ErrorCaption"), 120 | MessageBoxButton.OK, 121 | MessageBoxImage.Error); 122 | } 123 | } 124 | #endregion Export settings 125 | 126 | #region Import settings 127 | /// 128 | /// Imports settings from a previously exported file. 129 | /// 130 | public static void ImportSettings() 131 | { 132 | try 133 | { 134 | OpenFileDialog importFile = new() 135 | { 136 | CheckPathExists = true, 137 | CheckFileExists = true, 138 | Filter = "JSON File|*.json", 139 | InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) 140 | }; 141 | 142 | if (importFile.ShowDialog() == true) 143 | { 144 | _log.Debug($"Importing settings file from {importFile.FileName}."); 145 | ConfigManager.Setting = JsonSerializer.Deserialize(File.ReadAllText(importFile.FileName))!; 146 | SaveSettings(); 147 | 148 | _ = new MDCustMsgBox($"{GetStringResource("MsgText_ImportSettingsRestart")}", 149 | "Windows Update Viewer", 150 | ButtonType.Ok, 151 | false, 152 | true, 153 | _mainWindow).ShowDialog(); 154 | } 155 | } 156 | catch (Exception ex) 157 | { 158 | _log.Debug(ex, "Error importing settings file."); 159 | _ = MessageBox.Show($"{GetStringResource("MsgText_ErrorImportingSettings")}\n{ex.Message}", 160 | GetStringResource("MsgText_ErrorCaption"), 161 | MessageBoxButton.OK, 162 | MessageBoxImage.Error); 163 | } 164 | } 165 | #endregion Import settings 166 | 167 | #region Dump settings into the log 168 | /// 169 | /// Dumps (writes) current settings to the log file. 170 | /// 171 | public static void DumpSettings() 172 | { 173 | string dashes = new('-', 25); 174 | string header = $"{dashes} Begin Settings {dashes}"; 175 | string trailer = $"{dashes} End Settings {dashes}"; 176 | _log.Debug(header); 177 | PropertyInfo[] properties = typeof(UserSettings).GetProperties(); 178 | int maxLength = properties.Max(s => s.Name.Length); 179 | foreach (PropertyInfo property in properties) 180 | { 181 | string? value = property.GetValue(UserSettings.Setting, [])!.ToString(); 182 | _log.Debug($"{property.Name.PadRight(maxLength)} : {value}"); 183 | } 184 | _log.Debug(trailer); 185 | } 186 | #endregion Dump settings into the log 187 | } 188 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | fileeditor 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | [Aa][Rr][Mm]/ 26 | [Aa][Rr][Mm]64/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015/2017 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # Visual Studio 2017 auto generated files 38 | Generated\ Files/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | # Benchmark Results 54 | BenchmarkDotNet.Artifacts/ 55 | 56 | # .NET Core 57 | project.lock.json 58 | project.fragment.lock.json 59 | artifacts/ 60 | 61 | # StyleCop 62 | StyleCopReport.xml 63 | 64 | # Files built by Visual Studio 65 | *_i.c 66 | *_p.c 67 | *_h.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.iobj 72 | *.pch 73 | *.pdb 74 | *.ipdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *_wpftmp.csproj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.svclog 91 | *.scc 92 | 93 | # Chutzpah Test files 94 | _Chutzpah* 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opendb 101 | *.opensdf 102 | *.sdf 103 | *.cachefile 104 | *.VC.db 105 | *.VC.VC.opendb 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | *.vspx 111 | *.sap 112 | 113 | # Visual Studio Trace Files 114 | *.e2e 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # The packages folder can be ignored because of Package Restore 188 | **/[Pp]ackages/* 189 | # except build/, which is used as an MSBuild target. 190 | !**/[Pp]ackages/build/ 191 | # Uncomment if necessary however generally it will be regenerated when needed 192 | #!**/[Pp]ackages/repositories.config 193 | # NuGet v3's project.json files produces more ignorable files 194 | *.nuget.props 195 | *.nuget.targets 196 | 197 | # Microsoft Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Microsoft Azure Emulator 202 | ecf/ 203 | rcf/ 204 | 205 | # Windows Store app package directories and files 206 | AppPackages/ 207 | BundleArtifacts/ 208 | Package.StoreAssociation.xml 209 | _pkginfo.txt 210 | *.appx 211 | 212 | # Visual Studio cache files 213 | # files ending in .cache can be ignored 214 | *.[Cc]ache 215 | # but keep track of directories ending in .cache 216 | !?*.[Cc]ache/ 217 | 218 | # Others 219 | ClientBin/ 220 | ~$* 221 | *~ 222 | *.dbmdl 223 | *.dbproj.schemaview 224 | *.jfm 225 | *.pfx 226 | *.publishsettings 227 | orleans.codegen.cs 228 | 229 | # Including strong name files can present a security risk 230 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 231 | #*.snk 232 | 233 | # Since there are multiple workflows, uncomment next line to ignore bower_components 234 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 235 | #bower_components/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | *- Backup*.rdl 261 | 262 | # Microsoft Fakes 263 | FakesAssemblies/ 264 | 265 | # GhostDoc plugin setting file 266 | *.GhostDoc.xml 267 | 268 | # Node.js Tools for Visual Studio 269 | .ntvs_analysis.dat 270 | node_modules/ 271 | 272 | # Visual Studio 6 build log 273 | *.plg 274 | 275 | # Visual Studio 6 workspace options file 276 | *.opt 277 | 278 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 279 | *.vbw 280 | 281 | # Visual Studio LightSwitch build output 282 | **/*.HTMLClient/GeneratedArtifacts 283 | **/*.DesktopClient/GeneratedArtifacts 284 | **/*.DesktopClient/ModelManifest.xml 285 | **/*.Server/GeneratedArtifacts 286 | **/*.Server/ModelManifest.xml 287 | _Pvt_Extensions 288 | 289 | # Paket dependency manager 290 | .paket/paket.exe 291 | paket-files/ 292 | 293 | # FAKE - F# Make 294 | .fake/ 295 | 296 | # JetBrains Rider 297 | .idea/ 298 | *.sln.iml 299 | 300 | # CodeRush personal settings 301 | .cr/personal 302 | 303 | # Python Tools for Visual Studio (PTVS) 304 | __pycache__/ 305 | *.pyc 306 | 307 | # Cake - Uncomment if you are using it 308 | # tools/** 309 | # !tools/packages.config 310 | 311 | # Tabs Studio 312 | *.tss 313 | 314 | # Telerik's JustMock configuration file 315 | *.jmconfig 316 | 317 | # BizTalk build output 318 | *.btp.cs 319 | *.btm.cs 320 | *.odx.cs 321 | *.xsd.cs 322 | 323 | # OpenCover UI analysis results 324 | OpenCover/ 325 | 326 | # Azure Stream Analytics local run output 327 | ASALocalRun/ 328 | 329 | # MSBuild Binary and Structured Log 330 | *.binlog 331 | 332 | # NVidia Nsight GPU debugger configuration file 333 | *.nvuser 334 | 335 | # MFractors (Xamarin productivity tool) working folder 336 | .mfractor/ 337 | 338 | # Local History for Visual Studio 339 | .localhistory/ 340 | 341 | # BeatPulse healthcheck temp database 342 | healthchecksdb 343 | 344 | # BuildInfo 345 | WUView/BuildInfo.cs 346 | 347 | # Inno Setup backup 348 | *.~is -------------------------------------------------------------------------------- /WUView/Dialogs/MDCustMsgBox.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 31 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 89 | 90 | 91 | 92 | 93 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 |