diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs new file mode 100644 index 0000000000..5e39e9d6f2 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -0,0 +1,769 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Microsoft.Win32; +using Shell32; +using System; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Collections.Generic; +using Dbg = System.Management.Automation.Diagnostics; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Represent a control panel item + /// + public sealed class ControlPanelItem + { + /// + /// Control panel applet name + /// + public string Name + { + get { return _name; } + } + private string _name; + + /// + /// Control panel applet canonical name + /// + public string CanonicalName + { + get { return _canonicalName; } + } + private string _canonicalName; + + /// + /// Control panel applet category + /// + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Category + { + get { return _category; } + } + private string[] _category; + + /// + /// Control panel applet description + /// + public string Description + { + get { return _description; } + } + private string _description; + + /// + /// Control panel applet path + /// + internal string Path + { + get { return _path; } + } + private string _path; + + /// + /// Internal constructor for ControlPanelItem + /// + /// + /// + /// + /// + /// + internal ControlPanelItem(string name, string canonicalName, string[] category, string description, string path) + { + _name = name; + _path = path; + _canonicalName = canonicalName; + _category = category; + _description = description; + } + + /// + /// ToString method + /// + /// + public override string ToString() + { + return this.Name; + } + } + + /// + /// This class implements the base for ControlPanelItem commands + /// + public abstract class ControlPanelItemBaseCommand : PSCmdlet + { + /// + /// Locale specific verb action Open string exposed by the control panel item. + /// + private static string VerbActionOpenName = null; + + /// + /// Canonical name of the control panel item used as a refernece to fetch the verb + /// action Open string. This control panel item exists on all SKU's. + /// + private const string RegionCanonicalName = "Microsoft.RegionAndLanguage"; + + private const string ControlPanelShellFolder = "shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}"; + private static readonly string[] ControlPanelItemFilterList = new string[] { "Folder Options", "Taskbar and Start Menu" }; + private const string TestHeadlessServerScript = @" +$result = $false +$serverManagerModule = Get-Module -ListAvailable | ? {$_.Name -eq 'ServerManager'} +if ($serverManagerModule -ne $null) +{ + Import-Module ServerManager + $Gui = (Get-WindowsFeature Server-Gui-Shell).Installed + if ($Gui -eq $false) + { + $result = $true + } +} +$result +"; + internal readonly Dictionary CategoryMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + internal string[] CategoryNames = { "*" }; + internal string[] RegularNames = { "*" }; + internal string[] CanonicalNames = { "*" }; + internal ControlPanelItem[] ControlPanelItems = new ControlPanelItem[0]; + + /// + /// Get all executable control panel items + /// + internal List AllControlPanelItems + { + get + { + if (_allControlPanelItems == null) + { + _allControlPanelItems = new List(); + string allItemFolderPath = ControlPanelShellFolder + "\\0"; + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 allItemFolder = (Folder2)shell2.NameSpace(allItemFolderPath); + FolderItems3 allItems = (FolderItems3)allItemFolder.Items(); + + bool applyControlPanelItemFilterList = IsServerCoreOrHeadLessServer(); + + foreach (ShellFolderItem item in allItems) + { + if (applyControlPanelItemFilterList) + { + bool match = false; + foreach (string name in ControlPanelItemFilterList) + { + if (name.Equals(item.Name, StringComparison.OrdinalIgnoreCase)) + { + match = true; + break; + } + } + if (match) + continue; + } + + if (ContainVerbOpen(item)) + _allControlPanelItems.Add(item); + } + } + return _allControlPanelItems; + } + } + private List _allControlPanelItems; + + #region Cmdlet Overrides + + /// + /// Does the preprocessing for ControlPanelItem cmdlets + /// + protected override void BeginProcessing() + { + System.OperatingSystem osInfo = System.Environment.OSVersion; + PlatformID platform = osInfo.Platform; + Version version = osInfo.Version; + + if (platform.Equals(PlatformID.Win32NT) && + ((version.Major < 6) || + ((version.Major == 6) && (version.Minor < 2)) + )) + { + // Below Win8, this cmdlet is not supported because of Win8:794135 + // throw terminating + string message = string.Format(CultureInfo.InvariantCulture, + ControlPanelResources.ControlPanelItemCmdletNotSupported, + this.CommandInfo.Name); + throw new PSNotSupportedException(message); + } + } + + #endregion + + /// + /// Test if an item can be invoked + /// + /// + /// + private bool ContainVerbOpen(ShellFolderItem item) + { + bool result = false; + FolderItemVerbs verbs = item.Verbs(); + foreach (FolderItemVerb verb in verbs) + { + if (!String.IsNullOrEmpty(verb.Name) && + (verb.Name.Equals(ControlPanelResources.VerbActionOpen, StringComparison.OrdinalIgnoreCase) || + CompareVerbActionOpen(verb.Name))) + { + result = true; + break; + } + } + return result; + } + + /// + /// CompareVerbActionOpen is a helper function used to perform locale specific + /// comparision of the verb action Open exposed by various control panel items. + /// + /// Locale spcific verb action exposed by the control panel item. + /// True if the control panel item supports verb action open or else returns false. + private static bool CompareVerbActionOpen(string verbActionName) + { + if (VerbActionOpenName == null) + { + const string allItemFolderPath = ControlPanelShellFolder + "\\0"; + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 allItemFolder = (Folder2)shell2.NameSpace(allItemFolderPath); + FolderItems3 allItems = (FolderItems3)allItemFolder.Items(); + + foreach (ShellFolderItem item in allItems) + { + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = !String.IsNullOrEmpty(canonicalName) + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + + if (canonicalName != null && canonicalName.Equals(RegionCanonicalName, StringComparison.OrdinalIgnoreCase)) + { + // The 'Region' control panel item always has '&Open' (english or other locale) as the first verb name + VerbActionOpenName = item.Verbs().Item(0).Name; + break; + } + } + + Dbg.Assert(VerbActionOpenName != null, "The 'Region' control panel item is available on all SKUs and it always " + + "has '&Open' as the first verb item, so VerbActionOpenName should never be null at this point"); + } + + return VerbActionOpenName.Equals(verbActionName, StringComparison.OrdinalIgnoreCase); + } + + /// + /// IsServerCoreORHeadLessServer is a helper function that checks if the current SKU is a + /// Server Core machine or if the Server-GUI-Shell feature is removed on the machine. + /// + /// True if the current SKU is a Server Core machine or if the Server-GUI-Shell + /// feature is removed on the machine or else returns false. + private bool IsServerCoreOrHeadLessServer() + { + bool result = false; + + using (RegistryKey installation = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")) + { + Dbg.Assert(installation != null, "the CurrentVersion subkey should exist"); + + string installationType = (string)installation.GetValue("InstallationType", ""); + + if (installationType.Equals("Server Core")) + { + result = true; + } + else if (installationType.Equals("Server")) + { + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + ps.AddScript(TestHeadlessServerScript); + Collection psObjectCollection = ps.Invoke(new object[0]); + Dbg.Assert(psObjectCollection != null && psObjectCollection.Count == 1, "invoke should never return null, there should be only one return item"); + if (LanguagePrimitives.IsTrue(PSObject.Base(psObjectCollection[0]))) + { + result = true; + } + } + } + } + + return result; + } + + /// + /// Get the category number and name map + /// + internal void GetCategoryMap() + { + if (CategoryMap.Count != 0) + { + return; + } + + IShellDispatch4 shell2 = (IShellDispatch4)new Shell(); + Folder2 categoryFolder = (Folder2)shell2.NameSpace(ControlPanelShellFolder); + FolderItems3 catItems = (FolderItems3)categoryFolder.Items(); + + foreach (ShellFolderItem category in catItems) + { + string path = category.Path; + string catNum = path.Substring(path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase) + 1); + + CategoryMap.Add(catNum, category.Name); + } + } + + /// + /// Get control panel item by the category + /// + /// + /// + internal List GetControlPanelItemByCategory(List controlPanelItems) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string pattern in CategoryNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + int[] categories = (int[])item.ExtendedProperty("System.ControlPanel.Category"); + foreach (int cat in categories) + { + string catStr = (string)LanguagePrimitives.ConvertTo(cat, typeof (string), CultureInfo.InvariantCulture); + Dbg.Assert(CategoryMap.ContainsKey(catStr), "the category should be contained in _categoryMap"); + string catName = CategoryMap[catStr]; + + if (!wildcard.IsMatch(catName)) + continue; + if (itemSet.Contains(path)) + { + found = true; + break; + } + + found = true; + itemSet.Add(path); + list.Add(item); + break; + } + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string errMsg = StringUtil.Format(ControlPanelResources.NoControlPanelItemFoundForGivenCategory, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenCategory", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the regular name + /// + /// + /// + /// + internal List GetControlPanelItemByName(List controlPanelItems, bool withCategoryFilter) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string pattern in RegularNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string name = item.Name; + string path = item.Path; + if (!wildcard.IsMatch(name)) + continue; + if (itemSet.Contains(path)) + { + found = true; + continue; + } + + found = true; + itemSet.Add(path); + list.Add(item); + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string formatString = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundForGivenNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundForGivenName; + string errMsg = StringUtil.Format(formatString, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenName", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the canonical name + /// + /// + /// + /// + internal List GetControlPanelItemByCanonicalName(List controlPanelItems, bool withCategoryFilter) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (CanonicalNames == null) + { + bool found = false; + foreach (ShellFolderItem item in controlPanelItems) + { + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + if (canonicalName == null) + { + found = true; + list.Add(item); + } + } + + if (!found) + { + string errMsg = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundWithNullCanonicalNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundWithNullCanonicalName; + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "", + ErrorCategory.InvalidArgument, CanonicalNames); + WriteError(error); + } + return list; + } + + foreach (string pattern in CanonicalNames) + { + bool found = false; + WildcardPattern wildcard = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = canonicalName != null + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + + if (canonicalName == null) + { + if (pattern.Equals("*", StringComparison.OrdinalIgnoreCase)) + { + found = true; + if (!itemSet.Contains(path)) + { + itemSet.Add(path); + list.Add(item); + } + } + } + else + { + if (!wildcard.IsMatch(canonicalName)) + continue; + if (itemSet.Contains(path)) + { + found = true; + continue; + } + + found = true; + itemSet.Add(path); + list.Add(item); + } + } + + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) + { + string formatString = withCategoryFilter + ? ControlPanelResources.NoControlPanelItemFoundForGivenCanonicalNameWithCategory + : ControlPanelResources.NoControlPanelItemFoundForGivenCanonicalName; + string errMsg = StringUtil.Format(formatString, pattern); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenCanonicalName", + ErrorCategory.InvalidArgument, pattern); + WriteError(error); + } + } + + return list; + } + + /// + /// Get control panel item by the ControlPanelItem instances + /// + /// + /// + internal List GetControlPanelItemsByInstance(List controlPanelItems) + { + List list = new List(); + HashSet itemSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (ControlPanelItem controlPanelItem in ControlPanelItems) + { + bool found = false; + + foreach (ShellFolderItem item in controlPanelItems) + { + string path = item.Path; + if (!controlPanelItem.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) + continue; + if (itemSet.Contains(path)) + { + found = true; + break; + } + + found = true; + itemSet.Add(path); + list.Add(item); + break; + } + + if (!found) + { + string errMsg = StringUtil.Format(ControlPanelResources.NoControlPanelItemFoundForGivenInstance, + controlPanelItem.GetType().Name); + ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), + "NoControlPanelItemFoundForGivenInstance", + ErrorCategory.InvalidArgument, controlPanelItem); + WriteError(error); + } + } + + return list; + } + } + + /// + /// Get all control panel items that is available in the "All Control Panel Items" category + /// + [Cmdlet(VerbsCommon.Get, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=219982")] + [OutputType(typeof(ControlPanelItem))] + public sealed class GetControlPanelItemCommand : ControlPanelItemBaseCommand + { + private const string RegularNameParameterSet = "RegularName"; + private const string CanonicalNameParameterSet = "CanonicalName"; + + #region "Parameters" + + /// + /// Control panel item names + /// + [Parameter(Position = 0, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Name + { + get { return RegularNames; } + set + { + RegularNames = value; + _nameSpecified = true; + } + } + private bool _nameSpecified = false; + + /// + /// Canonical names of control panel items + /// + [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] + [AllowNull] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] CanonicalName + { + get { return CanonicalNames; } + set + { + CanonicalNames = value; + _canonicalNameSpecified = true; + } + } + private bool _canonicalNameSpecified = false; + + /// + /// Category of control panel items + /// + [Parameter] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Category + { + get { return CategoryNames; } + set + { + CategoryNames = value; + _categorySpecified = true; + } + } + private bool _categorySpecified = false; + + #endregion "Parameters" + + /// + /// + /// + protected override void ProcessRecord() + { + GetCategoryMap(); + List items = GetControlPanelItemByCategory(AllControlPanelItems); + + if (_nameSpecified) + { + items = GetControlPanelItemByName(items, _categorySpecified); + } + else if (_canonicalNameSpecified) + { + items = GetControlPanelItemByCanonicalName(items, _categorySpecified); + } + + List results = new List(); + foreach (ShellFolderItem item in items) + { + string name = item.Name; + string path = item.Path; + string description = (string)item.ExtendedProperty("InfoTip"); + string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); + canonicalName = canonicalName != null + ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + : null; + int[] categories = (int[])item.ExtendedProperty("System.ControlPanel.Category"); + string[] cateStrings = new string[categories.Length]; + for (int i = 0; i < categories.Length; i++) + { + string catStr = (string)LanguagePrimitives.ConvertTo(categories[i], typeof(string), CultureInfo.InvariantCulture); + Dbg.Assert(CategoryMap.ContainsKey(catStr), "the category should be contained in CategoryMap"); + cateStrings[i] = CategoryMap[catStr]; + } + + ControlPanelItem controlPanelItem = new ControlPanelItem(name, canonicalName, cateStrings, description, path); + results.Add(controlPanelItem); + } + + // Sort the reuslts by Canonical Name + results.Sort(CompareControlPanelItems); + foreach (ControlPanelItem controlPanelItem in results) + { + WriteObject(controlPanelItem); + } + } + + #region "Private Methods" + + private static int CompareControlPanelItems(ControlPanelItem x, ControlPanelItem y) + { + // In the case that at least one of them is null + if (x.CanonicalName == null && y.CanonicalName == null) + return 0; + if (x.CanonicalName == null) + return 1; + if (y.CanonicalName == null) + return -1; + + // In the case that both are not null + return string.Compare(x.CanonicalName, y.CanonicalName, StringComparison.OrdinalIgnoreCase); + } + + #endregion "Private Methods" + } + + /// + /// Show the specified control panel applet + /// + [Cmdlet(VerbsCommon.Show, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "http://go.microsoft.com/fwlink/?LinkID=219983")] + public sealed class ShowControlPanelItemCommand : ControlPanelItemBaseCommand + { + private const string RegularNameParameterSet = "RegularName"; + private const string CanonicalNameParameterSet = "CanonicalName"; + private const string ControlPanelItemParameterSet = "ControlPanelItem"; + + #region "Parameters" + + /// + /// Control panel item names + /// + [Parameter(Position = 0, Mandatory = true, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] Name + { + get { return RegularNames; } + set { RegularNames = value; } + } + + /// + /// Canonical names of control panel items + /// + [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] + [AllowNull] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public string[] CanonicalName + { + get { return CanonicalNames; } + set { CanonicalNames = value; } + } + + /// + /// Control panel items returned by Get-ControlPanelItem + /// + [Parameter(Position = 0, ParameterSetName = ControlPanelItemParameterSet, ValueFromPipeline = true)] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public ControlPanelItem[] InputObject + { + get { return ControlPanelItems; } + set { ControlPanelItems = value; } + } + + #endregion "Parameters" + + /// + /// + /// + protected override void ProcessRecord() + { + List items; + if (ParameterSetName == RegularNameParameterSet) + { + items = GetControlPanelItemByName(AllControlPanelItems, false); + } + else if (ParameterSetName == CanonicalNameParameterSet) + { + items = GetControlPanelItemByCanonicalName(AllControlPanelItems, false); + } + else + { + items = GetControlPanelItemsByInstance(AllControlPanelItems); + } + + foreach (ShellFolderItem item in items) + { + item.InvokeVerb(); + } + } + } +}