diff --git a/SyncMLViewer/Helper.cs b/SyncMLViewer/Helper.cs index 88d3347..9e80f8e 100644 --- a/SyncMLViewer/Helper.cs +++ b/SyncMLViewer/Helper.cs @@ -18,12 +18,57 @@ namespace SyncMLViewer { internal static class Helper { + // The tree root node shown by regedit.exe comes from a localized string resource in + // regedit.exe.mui, it reads "Computer" on en-US and "Ordinateur" on fr-FR for example. + // Everything below the root node (the hive names) is not localized. + private static readonly string[] RegistryHiveNames = + { + "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER", "HKEY_CLASSES_ROOT", "HKEY_USERS", + "HKEY_CURRENT_CONFIG", "HKEY_PERFORMANCE_DATA", "HKEY_DYN_DATA", + "HKLM", "HKCU", "HKCR", "HKU", "HKCC" + }; + + /// + /// Removes the tree root node from a registry path so that the path becomes independent of + /// the Windows display language. + /// + /// + /// regedit.exe matches the first segment of its LastKey value against the localized name of + /// its tree root node. A hard coded English "Computer\" therefore only resolves on an + /// English Windows, on every other display language regedit silently falls back to the root + /// node instead of navigating to the requested key. regedit happily accepts a LastKey value + /// without any root node prefix and prepends the correct localized name itself, so dropping + /// the prefix works on all languages. + /// + internal static string NormalizeRegistryPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return string.Empty; + } + + var normalized = path.Trim().Trim('\\'); + var separatorIndex = normalized.IndexOf('\\'); + var firstSegment = separatorIndex < 0 ? normalized : normalized.Substring(0, separatorIndex); + + // already rooted at a hive, nothing to strip + if (RegistryHiveNames.Contains(firstSegment, StringComparer.OrdinalIgnoreCase)) + { + return normalized; + } + + // the first segment is the localized root node, drop it + return separatorIndex < 0 ? string.Empty : normalized.Substring(separatorIndex + 1); + } + public static void OpenRegistry(string path) { + // CreateSubKey instead of OpenSubKey, the Regedit key does not exist yet on a profile + // where regedit.exe has never been started using (var registryKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default) - .OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", true)) + .CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Applets\Regedit")) { - registryKey?.SetValue("LastKey", path); + registryKey?.SetValue("LastKey", NormalizeRegistryPath(path)); } var processes = Process.GetProcessesByName("regedit"); diff --git a/SyncMLViewer/MainWindow.xaml b/SyncMLViewer/MainWindow.xaml index df065d2..c0ad2cc 100644 --- a/SyncMLViewer/MainWindow.xaml +++ b/SyncMLViewer/MainWindow.xaml @@ -158,6 +158,7 @@ + diff --git a/SyncMLViewer/MainWindow.xaml.cs b/SyncMLViewer/MainWindow.xaml.cs index 24255f2..8f18e39 100644 --- a/SyncMLViewer/MainWindow.xaml.cs +++ b/SyncMLViewer/MainWindow.xaml.cs @@ -1962,37 +1962,37 @@ private async void MenuItemCheckUpdate_OnClick(object sender, RoutedEventArgs e) private void MenuItemRegistryEnrollments_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments"); } private void MenuItemRegistryProvisioning_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning"); } private void MenuItemRegistryPolicyManager_OnClick(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager"); } private void MenuItemRegistryRebootRequiredUris_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\SyncML\RebootRequiredURIs"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\SyncML\RebootRequiredURIs"); } private void MenuItemRegistryDeclaredConfiguration_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DeclaredConfiguration"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DeclaredConfiguration"); } private void MenuItemRegistryEnterpriseDesktopAppManagement_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseDesktopAppManagement"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseDesktopAppManagement"); } private void MenuItemRegistryIntuneManagementExtension_Click(object sender, RoutedEventArgs e) { - Helper.OpenRegistry(@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension"); + Helper.OpenRegistry(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension"); } private async void MenuItemMdmDiagnostics_OnClick(object sender, RoutedEventArgs e) @@ -2351,6 +2351,16 @@ private void MenuItemOpenDeclaredConfigurationHostOSFolder_Click(object sender, Helper.OpenFolder(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Microsoft\DC\HostOS")); } + private void MenuItemOpenDeviceInventoryAgentFolder_Click(object sender, RoutedEventArgs e) + { + // ProgramW6432 always points to the native Program Files, SpecialFolder.ProgramFiles + // would resolve to Program Files (x86) when running as a 32-bit process + var programFiles = Environment.GetEnvironmentVariable("ProgramW6432") + ?? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + + Helper.OpenFolder(Path.Combine(programFiles, "Microsoft Device Inventory Agent")); + } + private void MenuItemClearHistoryItems_Click(object sender, RoutedEventArgs e) { resultStack.Children.Clear(); @@ -2732,7 +2742,7 @@ private void MenuItemLookupNodeCache_Click(object sender, RoutedEventArgs e) if (int.TryParse(text, out _)) { - Helper.OpenRegistry($@"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\NodeCache\CSP\Device\MS DM Server\Nodes\{text}"); + Helper.OpenRegistry($@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\NodeCache\CSP\Device\MS DM Server\Nodes\{text}"); } }