/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs
644 строки
24 KB
xtqqczze
Enable IDE0049 `PreferBuiltInOrFrameworkType` (#14491)
28 июл 2021, 22:39
Не верифицирован
28 июл 2021, 22:39
0f2f23f
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Management.Automation; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.PowerShell.Commands.ShowCommandExtension; namespace Microsoft.PowerShell.Commands { /// <summary> /// Show-Command displays a GUI for a cmdlet, or for all cmdlets if no specific cmdlet is specified. /// </summary> [Cmdlet(VerbsCommon.Show, "Command", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109589")] public class ShowCommandCommand : PSCmdlet, IDisposable { #region Private Fields /// <summary> /// Set to true when ProcessRecord is reached, since it will always open a window. /// </summary> private bool _hasOpenedWindow; /// <summary> /// Determines if the command should be sent to the pipeline as a string instead of run. /// </summary> private bool _passThrough; /// <summary> /// Uses ShowCommandProxy to invoke WPF GUI object. /// </summary> private ShowCommandProxy _showCommandProxy; /// <summary> /// Data container for all cmdlets. This is populated when show-command is called with no command name. /// </summary> private List<ShowCommandCommandInfo> _commands; /// <summary> /// List of modules that have been loaded indexed by module name. /// </summary> private Dictionary<string, ShowCommandModuleInfo> _importedModules; /// <summary> /// Record the EndProcessing error. /// </summary> private PSDataCollection<ErrorRecord> _errors = new(); /// <summary> /// Field used for the NoCommonParameter parameter. /// </summary> private SwitchParameter _noCommonParameter; /// <summary> /// Object used for ShowCommand with a command name that holds the view model created for the command. /// </summary> private object _commandViewModelObj; #endregion #region Input Cmdlet Parameter /// <summary> /// Gets or sets the command name. /// </summary> [Parameter(Position = 0)] [Alias("CommandName")] public string Name { get; set; } /// <summary> /// Gets or sets the Width. /// </summary> [Parameter] [ValidateRange(300, int.MaxValue)] public double Height { get; set; } /// <summary> /// Gets or sets the Width. /// </summary> [Parameter] [ValidateRange(300, int.MaxValue)] public double Width { get; set; } /// <summary> /// Gets or sets a value indicating Common Parameters should not be displayed. /// </summary> [Parameter] public SwitchParameter NoCommonParameter { get { return _noCommonParameter; } set { _noCommonParameter = value; } } /// <summary> /// Gets or sets a value indicating errors should not cause a message window to be displayed. /// </summary> [Parameter] public SwitchParameter ErrorPopup { get; set; } /// <summary> /// Gets or sets a value indicating the command should be sent to the pipeline as a string instead of run. /// </summary> [Parameter] public SwitchParameter PassThru { get { return _passThrough; } set { _passThrough = value; } } #endregion #region Public and Protected Methods /// <summary> /// Executes a PowerShell script, writing the output objects to the pipeline. /// </summary> /// <param name="script">Script to execute.</param> public void RunScript(string script) { if (_showCommandProxy == null || string.IsNullOrEmpty(script)) { return; } if (_passThrough) { this.WriteObject(script); return; } if (ErrorPopup) { this.RunScriptSilentlyAndWithErrorHookup(script); return; } if (_showCommandProxy.HasHostWindow) { if (!_showCommandProxy.SetPendingISECommand(script)) { this.RunScriptSilentlyAndWithErrorHookup(script); } return; } // Don't send newline at end as PSReadLine shows it rather than executing if (!ConsoleInputWithNativeMethods.AddToConsoleInputBuffer(script, newLine: false)) { this.WriteDebug(FormatAndOut_out_gridview.CannotWriteToConsoleInputBuffer); this.RunScriptSilentlyAndWithErrorHookup(script); } } /// <summary> /// Dispose method in IDisposable. /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Initialize a proxy instance for show-command. /// </summary> protected override void BeginProcessing() { _showCommandProxy = new ShowCommandProxy(this); if (_showCommandProxy.ScreenHeight < this.Height) { ErrorRecord error = new( new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Height", _showCommandProxy.ScreenHeight)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(error); } if (_showCommandProxy.ScreenWidth < this.Width) { ErrorRecord error = new( new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Width", _showCommandProxy.ScreenWidth)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(error); } } /// <summary> /// ProcessRecord with or without CommandName. /// </summary> protected override void ProcessRecord() { if (Name == null) { _hasOpenedWindow = this.CanProcessRecordForAllCommands(); } else { _hasOpenedWindow = this.CanProcessRecordForOneCommand(); } } /// <summary> /// Optionally displays errors in a message. /// </summary> protected override void EndProcessing() { if (!_hasOpenedWindow) { return; } // We wait until the window is loaded and then activate it // to work around the console window gaining activation somewhere // in the end of ProcessRecord, which causes the keyboard focus // (and use oif tab key to focus controls) to go away from the window _showCommandProxy.WindowLoaded.WaitOne(); _showCommandProxy.ActivateWindow(); this.WaitForWindowClosedOrHelpNeeded(); this.RunScript(_showCommandProxy.GetScript()); if (_errors.Count == 0 || !ErrorPopup) { return; } StringBuilder errorString = new(); for (int i = 0; i < _errors.Count; i++) { if (i != 0) { errorString.AppendLine(); } ErrorRecord error = _errors[i]; errorString.Append(error.Exception.Message); } _showCommandProxy.ShowErrorString(errorString.ToString()); } /// <summary> /// StopProcessing is called close the window when user press Ctrl+C in the command prompt. /// </summary> protected override void StopProcessing() { _showCommandProxy.CloseWindow(); } #endregion #region Private Methods /// <summary> /// Runs the script in a new PowerShell instance and hooks up error stream to potentially display error popup. /// This method has the inconvenience of not showing to the console user the script being executed. /// </summary> /// <param name="script">Script to be run.</param> private void RunScriptSilentlyAndWithErrorHookup(string script) { // errors are not created here, because there is a field for it used in the final pop up PSDataCollection<object> output = new(); output.DataAdded += this.Output_DataAdded; _errors.DataAdded += this.Error_DataAdded; System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); ps.Streams.Error = _errors; ps.Commands.AddScript(script); ps.Invoke(null, output, null); } /// <summary> /// Issues an error when this.commandName was not found. /// </summary> private void IssueErrorForNoCommand() { InvalidOperationException errorException = new( string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.CommandNotFound, Name)); this.ThrowTerminatingError(new ErrorRecord(errorException, "NoCommand", ErrorCategory.InvalidOperation, Name)); } /// <summary> /// Issues an error when there is more than one command matching this.commandName. /// </summary> private void IssueErrorForMoreThanOneCommand() { InvalidOperationException errorException = new( string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.MoreThanOneCommand, Name, "Show-Command")); this.ThrowTerminatingError(new ErrorRecord(errorException, "MoreThanOneCommand", ErrorCategory.InvalidOperation, Name)); } /// <summary> /// Called from CommandProcessRecord to run the command that will get the CommandInfo and list of modules. /// </summary> /// <param name="command">Command to be retrieved.</param> /// <param name="modules">List of loaded modules.</param> private void GetCommandInfoAndModules(out CommandInfo command, out Dictionary<string, ShowCommandModuleInfo> modules) { command = null; modules = null; string commandText = _showCommandProxy.GetShowCommandCommand(Name, true); Collection<PSObject> commandResults = this.InvokeCommand.InvokeScript(commandText); object[] commandObjects = (object[])commandResults[0].BaseObject; object[] moduleObjects = (object[])commandResults[1].BaseObject; if (commandResults == null || moduleObjects == null || commandObjects.Length == 0) { this.IssueErrorForNoCommand(); return; } if (commandObjects.Length > 1) { this.IssueErrorForMoreThanOneCommand(); } command = ((PSObject)commandObjects[0]).BaseObject as CommandInfo; if (command == null) { this.IssueErrorForNoCommand(); return; } if (command.CommandType == CommandTypes.Alias) { commandText = _showCommandProxy.GetShowCommandCommand(command.Definition, false); commandResults = this.InvokeCommand.InvokeScript(commandText); if (commandResults == null || commandResults.Count != 1) { this.IssueErrorForNoCommand(); return; } command = (CommandInfo)commandResults[0].BaseObject; } modules = _showCommandProxy.GetImportedModulesDictionary(moduleObjects); } /// <summary> /// ProcessRecord when a command name is specified. /// </summary> /// <returns>True if there was no exception processing this record.</returns> private bool CanProcessRecordForOneCommand() { CommandInfo commandInfo; this.GetCommandInfoAndModules(out commandInfo, out _importedModules); Diagnostics.Assert(commandInfo != null, "GetCommandInfoAndModules would throw a terminating error/exception"); try { _commandViewModelObj = _showCommandProxy.GetCommandViewModel(new ShowCommandCommandInfo(commandInfo), _noCommonParameter.ToBool(), _importedModules, this.Name.Contains('\\')); _showCommandProxy.ShowCommandWindow(_commandViewModelObj, _passThrough); } catch (TargetInvocationException ti) { this.WriteError(new ErrorRecord(ti.InnerException, "CannotProcessRecordForOneCommand", ErrorCategory.InvalidOperation, Name)); return false; } return true; } /// <summary> /// ProcessRecord when a command name is not specified. /// </summary> /// <returns>True if there was no exception processing this record.</returns> private bool CanProcessRecordForAllCommands() { Collection<PSObject> rawCommands = this.InvokeCommand.InvokeScript(_showCommandProxy.GetShowAllModulesCommand()); _commands = _showCommandProxy.GetCommandList((object[])rawCommands[0].BaseObject); _importedModules = _showCommandProxy.GetImportedModulesDictionary((object[])rawCommands[1].BaseObject); try { _showCommandProxy.ShowAllModulesWindow(_importedModules, _commands, _noCommonParameter.ToBool(), _passThrough); } catch (TargetInvocationException ti) { this.WriteError(new ErrorRecord(ti.InnerException, "CannotProcessRecordForAllCommands", ErrorCategory.InvalidOperation, Name)); return false; } return true; } /// <summary> /// Waits until the window has been closed answering HelpNeeded events. /// </summary> private void WaitForWindowClosedOrHelpNeeded() { while (true) { int which = WaitHandle.WaitAny(new WaitHandle[] { _showCommandProxy.WindowClosed, _showCommandProxy.HelpNeeded, _showCommandProxy.ImportModuleNeeded }); if (which == 0) { break; } if (which == 1) { Collection<PSObject> helpResults = this.InvokeCommand.InvokeScript(_showCommandProxy.GetHelpCommand(_showCommandProxy.CommandNeedingHelp)); _showCommandProxy.DisplayHelp(helpResults); continue; } Diagnostics.Assert(which == 2, "which is 0,1 or 2 and 0 and 1 have been eliminated in the ifs above"); string commandToRun = _showCommandProxy.GetImportModuleCommand(_showCommandProxy.ParentModuleNeedingImportModule); Collection<PSObject> rawCommands; try { rawCommands = this.InvokeCommand.InvokeScript(commandToRun); } catch (RuntimeException e) { _showCommandProxy.ImportModuleFailed(e); continue; } _commands = _showCommandProxy.GetCommandList((object[])rawCommands[0].BaseObject); _importedModules = _showCommandProxy.GetImportedModulesDictionary((object[])rawCommands[1].BaseObject); _showCommandProxy.ImportModuleDone(_importedModules, _commands); continue; } } /// <summary> /// Writes the output of a script being run into the pipeline. /// </summary> /// <param name="sender">Output collection.</param> /// <param name="e">Output event.</param> private void Output_DataAdded(object sender, DataAddedEventArgs e) { this.WriteObject(((PSDataCollection<object>)sender)[e.Index]); } /// <summary> /// Writes the errors of a script being run into the pipeline. /// </summary> /// <param name="sender">Error collection.</param> /// <param name="e">Error event.</param> private void Error_DataAdded(object sender, DataAddedEventArgs e) { this.WriteError(((PSDataCollection<ErrorRecord>)sender)[e.Index]); } /// <summary> /// Implements IDisposable logic. /// </summary> /// <param name="isDisposing">True if being called from Dispose.</param> private void Dispose(bool isDisposing) { if (isDisposing) { if (_errors != null) { _errors.Dispose(); _errors = null; } } } #endregion /// <summary> /// Wraps interop code for console input buffer. /// </summary> internal static class ConsoleInputWithNativeMethods { /// <summary> /// Constant used in calls to GetStdHandle. /// </summary> internal const int STD_INPUT_HANDLE = -10; /// <summary> /// Adds a string to the console input buffer. /// </summary> /// <param name="str">String to add to console input buffer.</param> /// <param name="newLine">True to add Enter after the string.</param> /// <returns>True if it was successful in adding all characters to console input buffer.</returns> internal static bool AddToConsoleInputBuffer(string str, bool newLine) { IntPtr handle = ConsoleInputWithNativeMethods.GetStdHandle(ConsoleInputWithNativeMethods.STD_INPUT_HANDLE); if (handle == IntPtr.Zero) { return false; } uint strLen = (uint)str.Length; ConsoleInputWithNativeMethods.INPUT_RECORD[] records = new ConsoleInputWithNativeMethods.INPUT_RECORD[strLen + (newLine ? 1 : 0)]; for (int i = 0; i < strLen; i++) { ConsoleInputWithNativeMethods.INPUT_RECORD.SetInputRecord(ref records[i], str[i]); } uint written; if (!ConsoleInputWithNativeMethods.WriteConsoleInput(handle, records, strLen, out written) || written != strLen) { // I do not know of a case where written is not going to be strlen. Maybe for some character that // is not supported in the console. The API suggests this can happen, // so we handle it by returning false return false; } // Enter is written separately, because if this is a command, and one of the characters in the command was not written // (written != strLen) it is desireable to fail (return false) before typing enter and running the command if (newLine) { ConsoleInputWithNativeMethods.INPUT_RECORD[] enterArray = new ConsoleInputWithNativeMethods.INPUT_RECORD[1]; ConsoleInputWithNativeMethods.INPUT_RECORD.SetInputRecord(ref enterArray[0], (char)13); written = 0; if (!ConsoleInputWithNativeMethods.WriteConsoleInput(handle, enterArray, 1, out written)) { // I don't think this will happen return false; } Diagnostics.Assert(written == 1, "only Enter is being added and it is a supported character"); } return true; } /// <summary> /// Gets the console handle. /// </summary> /// <param name="nStdHandle">Which console handle to get.</param> /// <returns>The console handle.</returns> [DllImport("kernel32.dll", SetLastError = true)] internal static extern IntPtr GetStdHandle(int nStdHandle); /// <summary> /// Writes to the console input buffer. /// </summary> /// <param name="hConsoleInput">Console handle.</param> /// <param name="lpBuffer">Inputs to be written.</param> /// <param name="nLength">Number of inputs to be written.</param> /// <param name="lpNumberOfEventsWritten">Returned number of inputs actually written.</param> /// <returns>0 if the function fails.</returns> [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool WriteConsoleInput( IntPtr hConsoleInput, INPUT_RECORD[] lpBuffer, uint nLength, out uint lpNumberOfEventsWritten); /// <summary> /// A record to be added to the console buffer. /// </summary> internal struct INPUT_RECORD { /// <summary> /// The proper event type for a KeyEvent KEY_EVENT_RECORD. /// </summary> internal const int KEY_EVENT = 0x0001; /// <summary> /// Input buffer event type. /// </summary> internal ushort EventType; /// <summary> /// The actual event. The original structure is a union of many others, but this is the largest of them. /// And we don't need other kinds of events. /// </summary> internal KEY_EVENT_RECORD KeyEvent; /// <summary> /// Sets the necessary fields of <paramref name="inputRecord"/> for a KeyDown event for the <paramref name="character"/> /// </summary> /// <param name="inputRecord">Input record to be set.</param> /// <param name="character">Character to set the record with.</param> internal static void SetInputRecord(ref INPUT_RECORD inputRecord, char character) { inputRecord.EventType = INPUT_RECORD.KEY_EVENT; inputRecord.KeyEvent.bKeyDown = true; inputRecord.KeyEvent.UnicodeChar = character; } } /// <summary> /// Type of INPUT_RECORD which is a key. /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct KEY_EVENT_RECORD { /// <summary> /// True for key down and false for key up, but only needed if wVirtualKeyCode is used. /// </summary> internal bool bKeyDown; /// <summary> /// Repeat count. /// </summary> internal ushort wRepeatCount; /// <summary> /// Virtual key code. /// </summary> internal ushort wVirtualKeyCode; /// <summary> /// Virtual key scan code. /// </summary> internal ushort wVirtualScanCode; /// <summary> /// Character in input. If this is specified, wVirtualKeyCode, and others don't need to be. /// </summary> internal char UnicodeChar; /// <summary> /// State of keys like Shift and control. /// </summary> internal uint dwControlKeyState; } } } }