/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings namespace Microsoft.PowerShell.Commands { #region PSSnapInCommandBase /// /// Base class for all the pssnapin related cmdlets. /// public abstract class PSSnapInCommandBase : PSCmdlet, IDisposable { #region IDisposable Members /// /// Set to true when object is disposed /// /// private bool _disposed; /// /// Dispose method unloads the app domain and the /// resource reader if it was created. /// /// public void Dispose() { if (_disposed == false) { if (_resourceReader != null) { _resourceReader.Dispose(); _resourceReader = null; } GC.SuppressFinalize(this); } _disposed = true; } #endregion IDisposable Members #region Cmdlet Methods /// /// Disposes the resource reader. /// /// protected override void EndProcessing() { if (_resourceReader != null) { _resourceReader.Dispose(); _resourceReader = null; } } #endregion CmdletMethods #region Internal Methods /// /// Runspace configuration for the current engine /// /// /// PSSnapIn cmdlets need object to work with. /// internal RunspaceConfigForSingleShell Runspace { get { RunspaceConfigForSingleShell runSpace = Context.RunspaceConfiguration as RunspaceConfigForSingleShell; if (runSpace == null) { return null; } return runSpace; } } /// /// Writes a non-terminating error onto the pipeline. /// /// Object which caused this exception. /// ErrorId for this error. /// Complete exception object. /// ErrorCategory for this exception. internal void WriteNonTerminatingError( Object targetObject, string errorId, Exception innerException, ErrorCategory category) { WriteError(new ErrorRecord(innerException, errorId, category, targetObject)); } /// /// Searches the input list for the pattern supplied. /// /// Input list /// pattern with wildcards /// /// A collection of string objects (representing PSSnapIn name) /// that match the pattern. /// /// /// Please note that this method will use WildcardPattern class. /// So it wont support all the 'regex' patterns /// internal Collection SearchListForPattern(Collection searchList, string pattern) { Collection listToReturn = new Collection(); if (null == searchList) { // return an empty list return listToReturn; } WildcardPattern matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); // We are doing WildCard search foreach (PSSnapInInfo psSnapIn in searchList) { if (matcher.IsMatch(psSnapIn.Name)) { listToReturn.Add(psSnapIn.Name); } } // the returned list might contain 0 objects return listToReturn; } /// /// See if the snapin is already loaded..returns load snapin info if true, null otherwise. /// /// internal static PSSnapInInfo IsSnapInLoaded(Collection loadedSnapins, PSSnapInInfo psSnapInInfo) { if (null == loadedSnapins) { return null; } foreach (PSSnapInInfo loadedPSSnapInInfo in loadedSnapins) { // See if the assembly-qualified names match and return the existing PSSnapInInfo // if they do. string loadedSnapInAssemblyName = loadedPSSnapInInfo.AssemblyName; if (string.Equals(loadedPSSnapInInfo.Name, psSnapInInfo.Name, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(loadedSnapInAssemblyName) && string.Equals(loadedSnapInAssemblyName, psSnapInInfo.AssemblyName, System.StringComparison.OrdinalIgnoreCase)) { return loadedPSSnapInInfo; } } return null; } /// /// Routine to get the list of loaded snapins... /// /// protected internal Collection GetSnapIns(string pattern) { // If RunspaceConfiguration is not null, then return the list that it has if (Runspace != null) { if (pattern != null) return Runspace.ConsoleInfo.GetPSSnapIn(pattern, _shouldGetAll); else return Runspace.ConsoleInfo.PSSnapIns; } WildcardPattern matcher = null; if (!String.IsNullOrEmpty(pattern)) { bool doWildCardSearch = WildcardPattern.ContainsWildcardCharacters(pattern); if (!doWildCardSearch) { // Verify PSSnapInID.. // This will throw if it not a valid name PSSnapInInfo.VerifyPSSnapInFormatThrowIfError(pattern); } matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); } Collection snapins = new Collection(); if (_shouldGetAll) { foreach (PSSnapInInfo snapinKey in PSSnapInReader.ReadAll()) { if (matcher == null || matcher.IsMatch(snapinKey.Name)) snapins.Add(snapinKey); } } else { // Otherwise, just scan through the list of cmdlets and rebuild the table. List cmdlets = InvokeCommand.GetCmdlets(); Dictionary snapinTable = new Dictionary(); foreach (CmdletInfo cmdlet in cmdlets) { PSSnapInInfo snapin = cmdlet.PSSnapIn; if (snapin != null && !snapinTable.ContainsKey(snapin)) snapinTable.Add(snapin, true); } foreach (PSSnapInInfo snapinKey in snapinTable.Keys) { if (matcher == null || matcher.IsMatch(snapinKey.Name)) snapins.Add(snapinKey); } } return snapins; } /// /// Use to indicate if all registered snapins should be listed by GetSnapins... /// protected internal bool ShouldGetAll { get { return _shouldGetAll; } set { _shouldGetAll = value; } } private bool _shouldGetAll; /// /// A single instance of the resource indirect reader. This is used to load the /// managed resource assemblies in a different app-domain so that they can be unloaded. /// For perf reasons we only want to create one instance for the duration of the command /// and be sure it gets disposed when the command completes. /// /// internal RegistryStringResourceIndirect ResourceReader { get { return _resourceReader ?? (_resourceReader = RegistryStringResourceIndirect.GetResourceIndirectReader()); } } private RegistryStringResourceIndirect _resourceReader; #endregion } #endregion #region Add-PSSnapIn /// /// Class that implements add-pssnapin cmdlet. /// [Cmdlet(VerbsCommon.Add, "PSSnapin", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113281")] [OutputType(typeof(PSSnapInInfo))] public sealed class AddPSSnapinCommand : PSSnapInCommandBase { #region Parameters /// /// Property that gets/sets PSSnapIn Ids for the cmdlet. /// /// An array of strings representing PSSnapIn ids. [Parameter( Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string[] Name { get { return _pssnapins; } set { _pssnapins = value; } } private string[] _pssnapins; /// /// Gets or sets the Passthru flag for the operation. /// If true, the PSSnapInInfo object is passed down the /// output pipeline. /// [Parameter()] public SwitchParameter PassThru { get { return _passThru; } set { _passThru = value; } } private bool _passThru; #endregion #region Overrides /// /// Adds pssnapins to console file and loads the pssnapin dlls into /// the current monad runtime. /// /// /// The new pssnapin information is not stored in the console file until /// the file is saved. /// protected override void ProcessRecord() { // Cache for the information stored in the registry // update the cache the first time a wildcard is found.. Collection listToSearch = null; foreach (string pattern in _pssnapins) { Exception exception = null; Collection listToAdd = new Collection(); try { // check whether there are any wildcard characters bool doWildCardSearch = WildcardPattern.ContainsWildcardCharacters(pattern); if (doWildCardSearch) { // wildcard found in the pattern // Get all the possible candidates for current monad version if (listToSearch == null) { // cache snapin registry information... // For 3.0 PowerShell, we still use "1" as the registry version key for // Snapin and Custom shell lookup/discovery. // For 3.0 PowerShell, we use "3" as the registry version key only for Engine // related data like ApplicationBase etc. listToSearch = PSSnapInReader.ReadAll(PSVersionInfo.RegistryVersion1Key); } listToAdd = SearchListForPattern(listToSearch, pattern); // listToAdd wont be null.. Diagnostics.Assert(listToAdd != null, "Pattern matching returned null"); if (listToAdd.Count == 0) { if (_passThru) { // passThru is specified and we have nothing to add... WriteNonTerminatingError(pattern, "NoPSSnapInsFound", PSTraceSource.NewArgumentException(pattern, MshSnapInCmdletResources.NoPSSnapInsFound, pattern), ErrorCategory.InvalidArgument); } continue; } } else { listToAdd.Add(pattern); } // now add all the snapins for this pattern... AddPSSnapIns(listToAdd); } catch (PSArgumentException ae) { exception = ae; } catch (System.Security.SecurityException se) { exception = se; } if (exception != null) { WriteNonTerminatingError(pattern, "AddPSSnapInRead", exception, ErrorCategory.InvalidArgument); } } } #endregion #region Private Methods /// /// Adds one or more snapins /// /// List of snapin IDs /// /// This is a helper method and should not throw any /// exceptions. All exceptions are caught and displayed /// to the user using write* methods /// private void AddPSSnapIns(Collection snapInList) { if (snapInList == null) { // nothing to add return; } //BUGBUG TODO - brucepay - this is a workaround for not being able to dynamically update // the set of cmdlets in a runspace if there is no RunspaceConfiguration object. // This is a temporary fix to unblock remoting tests and need to be corrected/completed // before we can ship... // If there is no RunspaceConfig object, then // use an InitialSessionState object to gather and // bind the cmdlets from the snapins... if (Context.RunspaceConfiguration == null) { Collection loadedSnapins = base.GetSnapIns(null); InitialSessionState iss = InitialSessionState.Create(); bool isAtleastOneSnapinLoaded = false; foreach (string snapIn in snapInList) { if (InitialSessionState.IsEngineModule(snapIn)) { WriteNonTerminatingError(snapIn, "LoadSystemSnapinAsModule", PSTraceSource.NewArgumentException(snapIn, MshSnapInCmdletResources.LoadSystemSnapinAsModule, snapIn), ErrorCategory.InvalidArgument); } else { PSSnapInException warning; try { // Read snapin data PSSnapInInfo newPSSnapIn = PSSnapInReader.Read(Utils.GetCurrentMajorVersion(), snapIn); PSSnapInInfo psSnapInInfo = IsSnapInLoaded(loadedSnapins, newPSSnapIn); // that means snapin is not already loaded ..so load the snapin // now. if (null == psSnapInInfo) { psSnapInInfo = iss.ImportPSSnapIn(snapIn, out warning); isAtleastOneSnapinLoaded = true; Context.InitialSessionState.ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo); } // Write psSnapInInfo object only if passthru is specified. if (_passThru) { // Load the pssnapin info properties that are localizable and redirected in the registry psSnapInInfo.LoadIndirectResources(ResourceReader); WriteObject(psSnapInInfo); } } catch (PSSnapInException pse) { WriteNonTerminatingError(snapIn, "AddPSSnapInRead", pse, ErrorCategory.InvalidData); } } } if (isAtleastOneSnapinLoaded) { // Now update the session state with the new stuff... iss.Bind(Context, /*updateOnly*/ true); } return; } foreach (string psSnapIn in snapInList) { Exception exception = null; try { PSSnapInException warning = null; PSSnapInInfo psSnapInInfo = this.Runspace.AddPSSnapIn(psSnapIn, out warning); if (warning != null) { WriteNonTerminatingError(psSnapIn, "AddPSSnapInRead", warning, ErrorCategory.InvalidData); } // Write psSnapInInfo object only if passthru is specified. if (_passThru) { // Load the pssnapin info properties that are localizable and redirected in the registry psSnapInInfo.LoadIndirectResources(ResourceReader); WriteObject(psSnapInInfo); } } catch (PSArgumentException ae) { exception = ae; } catch (PSSnapInException sle) { exception = sle; } catch (System.Security.SecurityException se) { exception = se; } if (exception != null) { WriteNonTerminatingError(psSnapIn, "AddPSSnapInRead", exception, ErrorCategory.InvalidArgument); } } } #endregion } #endregion #region Remove-PSSnapIn /// /// Class that implements remove-pssnapin cmdlet. /// [Cmdlet(VerbsCommon.Remove, "PSSnapin", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113378")] [OutputType(typeof(PSSnapInInfo))] public sealed class RemovePSSnapinCommand : PSSnapInCommandBase { #region Parameters /// /// Property that gets/sets PSSnapIn Ids for the cmdlet. /// /// An array of strings representing PSSnapIn ids. [Parameter( Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string[] Name { get { return _pssnapins; } set { _pssnapins = value; } } private string[] _pssnapins; /// /// Gets or sets the Passthru flag for the operation. /// If true, the PSSnapInInfo object is also passed /// down the output pipeline. /// [Parameter()] public SwitchParameter PassThru { get { return _passThru; } set { _passThru = value; } } private bool _passThru; #endregion #region Overrides /// /// Removes pssnapins from the current console file. /// /// /// The pssnapin is not unloaded from the current engine. So all the cmdlets that are /// represented by this pssnapin will continue to work. /// protected override void ProcessRecord() { foreach (string psSnapIn in _pssnapins) { Collection snapIns = GetSnapIns(psSnapIn); // snapIns won't be null.. Diagnostics.Assert(snapIns != null, "GetSnapIns() returned null"); if (snapIns.Count == 0) { WriteNonTerminatingError(psSnapIn, "NoPSSnapInsFound", PSTraceSource.NewArgumentException(psSnapIn, MshSnapInCmdletResources.NoPSSnapInsFound, psSnapIn), ErrorCategory.InvalidArgument); continue; } foreach (PSSnapInInfo snapIn in snapIns) { // confirm the operation first // this is always false if WhatIf is set if (ShouldProcess(snapIn.Name)) { Exception exception = null; if (this.Runspace == null && this.Context.InitialSessionState != null) { try { // Check if this snapin can be removed // Monad has specific restrictions on the mshsnapinid like // mshsnapinid should be A-Za-z0-9.-_ etc. PSSnapInInfo.VerifyPSSnapInFormatThrowIfError(snapIn.Name); if (MshConsoleInfo.IsDefaultPSSnapIn(snapIn.Name, this.Context.InitialSessionState.defaultSnapins)) { throw PSTraceSource.NewArgumentException(snapIn.Name, ConsoleInfoErrorStrings.CannotRemoveDefault, snapIn.Name); } // Handle the initial session state case... InitialSessionState iss = InitialSessionState.Create(); PSSnapInException warning; // Get the snapin information... iss.ImportPSSnapIn(snapIn, out warning); iss.Unbind(Context); Context.InitialSessionState.ImportedSnapins.Remove(snapIn.Name); } catch (PSArgumentException ae) { exception = ae; } if (exception != null) { WriteNonTerminatingError(psSnapIn, "RemovePSSnapIn", exception, ErrorCategory.InvalidArgument); } } else { try { PSSnapInException warning = null; PSSnapInInfo psSnapInInfo = this.Runspace.RemovePSSnapIn(snapIn.Name, out warning); if (warning != null) { WriteNonTerminatingError(snapIn.Name, "RemovePSSnapInRead", warning, ErrorCategory.InvalidData); } if (_passThru) { // Load the pssnapin info properties that are localizable and redirected in the registry psSnapInInfo.LoadIndirectResources(ResourceReader); WriteObject(psSnapInInfo); } } catch (PSArgumentException ae) { exception = ae; } if (exception != null) { WriteNonTerminatingError(psSnapIn, "RemovePSSnapIn", exception, ErrorCategory.InvalidArgument); } } } // ShouldContinue } } } #endregion } #endregion #region Get-PSSnapIn /// /// Class that implements get-pssnapin cmdlet. /// [Cmdlet(VerbsCommon.Get, "PSSnapin", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113330")] [OutputType(typeof(PSSnapInInfo))] public sealed class GetPSSnapinCommand : PSSnapInCommandBase { #region Parameters /// /// Name(s) of PSSnapIn(s). /// [Parameter(Position = 0, Mandatory = false)] public string[] Name { get { return _pssnapins; } set { _pssnapins = value; } } private string[] _pssnapins; /// /// Property that determines whether to get all pssnapins that are currently /// registered ( in registry ). /// /// /// A boolean that determines whether to get all pssnapins that are currently /// registered ( in registry ). /// [Parameter(Mandatory = false)] public SwitchParameter Registered { get { return ShouldGetAll; } set { ShouldGetAll = value; } } #endregion #region Overrides /// /// Constructs PSSnapInfo objects as requested by the user and writes them to the /// output buffer. /// protected override void BeginProcessing() { if (_pssnapins != null) { foreach (string psSnapIn in _pssnapins) { Exception exception = null; try { Collection psSnapInInfoList = GetSnapIns(psSnapIn); // psSnapInInfoList wont be null.. Diagnostics.Assert(psSnapInInfoList != null, "ConsoleInfo.GetPSSnapIn returned null"); if (psSnapInInfoList.Count == 0) { WriteNonTerminatingError(psSnapIn, "NoPSSnapInsFound", PSTraceSource.NewArgumentException(psSnapIn, MshSnapInCmdletResources.NoPSSnapInsFound, psSnapIn), ErrorCategory.InvalidArgument); continue; } foreach (PSSnapInInfo pssnapinInfo in psSnapInInfoList) { // Load the pssnapin info properties that are localizable and redirected in the registry pssnapinInfo.LoadIndirectResources(ResourceReader); WriteObject(pssnapinInfo); } } catch (System.Security.SecurityException se) { exception = se; } catch (PSArgumentException ae) { exception = ae; } if (exception != null) { WriteNonTerminatingError(psSnapIn, "GetPSSnapInRead", exception, ErrorCategory.InvalidArgument); } } } else if (ShouldGetAll) { Exception exception = null; try { Collection psSnapInInfoList = PSSnapInReader.ReadAll(); foreach (PSSnapInInfo pssnapinInfo in psSnapInInfoList) { // Load the pssnapin info properties that are localizable and redirected in the registry pssnapinInfo.LoadIndirectResources(ResourceReader); WriteObject(pssnapinInfo); } } catch (System.Security.SecurityException se) { exception = se; } catch (PSArgumentException ae) { exception = ae; } if (exception != null) { WriteNonTerminatingError(this, "GetPSSnapInRead", exception, ErrorCategory.InvalidArgument); } } else { // this should never throw.. Collection psSnapInInfoList = GetSnapIns(null); foreach (PSSnapInInfo pssnapinInfo in psSnapInInfoList) { // Load the pssnapin info properties that are localizable and redirected in the registry pssnapinInfo.LoadIndirectResources(ResourceReader); WriteObject(pssnapinInfo); } } } #endregion } #endregion }