// // Copyright (c) 2009 Microsoft Corporation. All rights reserved. // // DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You // bear the risk of using it. Microsoft gives no express warranties, // guarantees or conditions. You may have additional consumer rights // under your local laws which this agreement cannot change. To the extent // permitted under your local laws, Microsoft excludes the implied warranties // of merchantability, fitness for a particular purpose and non-infringement. // System namespaces needed using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; // Windows PowerShell namespaces needed using System.Management.Automation; namespace Microsoft.Samples.PowerShell.Commands { /// /// A cmdlet to select properties from objects. /// /// /// Making this a sealed class. Change this if you need to derrive from this class. /// [Cmdlet("Select", "Obj")] public sealed class SelectObjCommand : Cmdlet { #region Parameters /// /// Object that this cmdlet will work on. /// Can be either from pipeline or directly specified /// as an argument to the cmdlet. /// /// [Parameter(ValueFromPipeline = true)] public PSObject InputObject { set { inputObject = value; } get { return inputObject; } } private PSObject inputObject; /// /// Property parameter for the cmdlet. /// Takes an array of strings as input. /// [Parameter(Position = 0)] public string[] Property { get { return properties; } set { properties = value; } } private string[] properties; /// /// Helps to select specific number of objects from the /// last object. /// [Parameter] [ValidateRange(1, int.MaxValue)] public int Last { get { return last; } set { last = value; } } private int last = 0; /// /// Helps to select specific number of objects from the /// first object. /// [Parameter] [ValidateRange(1, int.MaxValue)] public int First { get { return first; } set { first = value; } } private int first = 0; /// /// Expand specific properties from the selected object. /// /// [Parameter] public string Expand { get { return expand; } set { expand = value; } } private string expand = null; /// /// Specifies whether to select unique objects. /// /// true or false [Parameter] public bool Unique { get { return unique; } set { unique = value; } } private bool unique = false; #endregion #region Internal Properties/Data private SelectObjQueue inputQueue; private List uniques = null; #endregion #region Cmdlet Overrides /// /// Initialize cmdlet data here. /// This method is called only once even when the cmdlet /// is used in the middle a pipe. /// protected override void BeginProcessing() { WriteVerbose("Initializing Object Queue"); if (unique) { uniques = new List(); } inputQueue = new SelectObjQueue(first, last); } /// /// Do per object related work here. /// This method is called for every object that is coming /// from the input pipe. /// protected override void ProcessRecord() { if (null == inputObject) { return; } WriteVerbose("Received an input Object. Processing..."); inputQueue.Enqueue(inputObject); // Determine whether we can process this object right away. // We can process the object // (a) if no first or last is specified. // (b) first is specified and the limit is not reached. PSObject streamingInputObject = inputQueue.StreamingDequeue(); if (streamingInputObject != null) { ProcessObject(streamingInputObject); WriteVerbose("Processed InputObject"); } } /// /// This method is called after all the objects are processed. /// This method is called as the last instruction in a pipe. /// /// For select-obj, we will do all the object manipulation /// in this method. /// protected override void EndProcessing() { WriteVerbose("Processing remaining Input objects"); foreach (PSObject targetObject in inputQueue) { ProcessObject(targetObject); } if (uniques != null) { foreach (UniquePSObjectHelper obj in uniques) { if (obj.WrittenObject == null) { continue; } WriteObject(obj.WrittenObject); } } } #endregion #region Internal Methods private void ProcessObject(PSObject inputObject) { if ((properties == null || properties.Length == 0) && string.IsNullOrEmpty(expand)) { SendToOutputPipe(inputObject, new List()); return; } // Collect property values for the properties specified by // the user. List matchedProperties = new List(); if (properties != null && properties.Length > 0) { WriteVerbose("Collecting property values for the user specified properties"); // Create a collection of property values. foreach (string property in properties) { ProcessParameter(property, inputObject, matchedProperties); } } if (!string.IsNullOrEmpty(expand)) { WriteVerbose("Expanding a property"); ProcessExpandParameter(inputObject, matchedProperties); } else { // Create a new PSObject and embed properties that the user requested. PSObject outputObject = new PSObject(); if (matchedProperties.Count != 0) { foreach (PSNoteProperty noteProperty in matchedProperties) { try { outputObject.Properties.Add(noteProperty); } catch (ExtendedTypeSystemException) { WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject); } } } SendToOutputPipe(outputObject, matchedProperties); } } /// /// Gets the value for the property specified by from /// and adds the value to /// /// Property to get the value for. /// Input object to get the value from. /// Collection to add the value to. /// /// is assumed to be initialized. /// private void ProcessParameter(string propertyName, PSObject inputObject, List matchedProperties) { object propValue = GetPropValue(inputObject, propertyName); // propValue can be null. PSNoteProperty newProperty = new PSNoteProperty(propertyName, propValue); matchedProperties.Add(newProperty); } private void ProcessExpandParameter(PSObject inputObject, List matchedProperties) { // expand parameter is used // Expand the property value from the inputObject object propValue = null; try { propValue = GetPropValue(inputObject, expand); } catch (Exception e) { WriteNonTerminatingError(expand, "PropertyAccessException", e, ErrorCategory.InvalidData); return; } if (null == propValue) { string message = string.Format(CultureInfo.CurrentCulture,GetErrorMessage(PROPERTYNOTFOUND), expand); Exception e = new ArgumentException(message); WriteNonTerminatingError(expand, "PropertyNotFound", e, ErrorCategory.InvalidData); return; } // Expand property value using an enumerator. System.Collections.IEnumerable results = LanguagePrimitives.GetEnumerable(propValue); if (results == null) { string message = string.Format(CultureInfo.CurrentCulture,GetErrorMessage(NOTHINGTOEXPAND), expand); Exception e = new ArgumentException(message); WriteNonTerminatingError(expand, "NothingToExpand", e, ErrorCategory.InvalidArgument); return; } foreach (object expandedValue in results) { if (expandedValue == null) { WriteVerbose("One of the expanded values is null"); continue; } PSObject expandedObject = PSObject.AsPSObject(expandedValue); foreach (PSNoteProperty noteProperty in matchedProperties) { try { if (expandedObject.Properties[noteProperty.Name] != null) { WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject); } else { expandedObject.Properties.Add(noteProperty); } } catch (ExtendedTypeSystemException) { WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject); } } SendToOutputPipe(expandedObject, matchedProperties); } } private void SendToOutputPipe(PSObject objectToWrite, List addedNoteProperties) { if (!unique) { if (objectToWrite != null) { WriteObject(objectToWrite); } return; } bool isObjUnique = true; foreach (UniquePSObjectHelper uniqueObj in uniques) { ObjectCommandComparer comparer = new ObjectCommandComparer(true, System.Threading.Thread.CurrentThread.CurrentCulture, true); if ((comparer.Compare(objectToWrite.BaseObject, uniqueObj.WrittenObject.BaseObject) == 0) && (uniqueObj.NotePropertyCount == addedNoteProperties.Count)) { bool found = true; foreach (PSNoteProperty note in addedNoteProperties) { PSMemberInfo prop = uniqueObj.WrittenObject.Properties[note.Name]; if (prop == null || comparer.Compare(prop.Value, note.Value) != 0) { found = false; break; } } if (found) { isObjUnique = false; break; } } else { continue; } } if (isObjUnique) { uniques.Add(new UniquePSObjectHelper(objectToWrite, addedNoteProperties.Count)); } } /// /// Returns the value of property from the inputObject. /// /// Object for which the property value is needed. /// Name of the property for which the value is returned. /// /// null if property is not found, /// value of the property otherwise. /// private object GetPropValue(PSObject inputObject, string property) { // This is an internal method. Validate input passed to this method. Debug.Assert(inputObject != null, "Cannot work with a null object."); Debug.Assert(!string.IsNullOrEmpty(property), "Property should not be null or Empty"); // We have no globbing: try an exact match, because this is quicker. PSMemberInfo x = inputObject.Members[property]; return null == x ? null : x.Value; } #endregion #region Error Handling / Error Messages private const int PROPERTYALREADYEXISTS = 0; private const int PROPERTYNOTFOUND = 1; private const int NOTHINGTOEXPAND = 2; private string[] errors = new string[] { "Property cannot be processed because property {0} already exists.", "Property \"{0}\" cannot be found", "Cannot expand property \"{0}\" because it has nothing to expand." }; /// /// Gets error message string corresponding to /// /// Error ID of the error message /// A string representing the error. /// /// You can this error subsystem to return locale specific error messages. /// private string GetErrorMessage(int errorID) { return errors[errorID]; } /// /// 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)); } internal void WriteAlreadyExistingPropertyError(string propertyName, object inputObject) { string message = string.Format(CultureInfo.CurrentCulture, GetErrorMessage(PROPERTYALREADYEXISTS), propertyName); Exception e = new ArgumentException(message); WriteNonTerminatingError(inputObject, "PropertyAlreadyExists", e, ErrorCategory.InvalidData); } #endregion #region SelectObj Queue private class SelectObjQueue : Queue { #region SelectObjQueue Data int headLimit = 0; int tailLimit = 0; int streamedObjectCount = 0; #endregion #region SelectObjQueue Construction public SelectObjQueue(int first, int last) { headLimit = first; tailLimit = last; } #endregion #region SelectObjQueue Methods new public void Enqueue(PSObject obj) { if (tailLimit > 0 && this.Count >= tailLimit) { base.Dequeue(); } base.Enqueue(obj); } public PSObject StreamingDequeue() { if ((headLimit == 0 && tailLimit == 0) || streamedObjectCount < headLimit) { Debug.Assert(this.Count > 0, "Streaming an empty queue"); streamedObjectCount++; return Dequeue(); } if (tailLimit == 0) { Dequeue(); } return null; } #endregion } #endregion #region UniquePSObjectHelper private class UniquePSObjectHelper { internal UniquePSObjectHelper(PSObject o, int notePropertyCount) { WrittenObject = o; this.notePropertyCount = notePropertyCount; } internal readonly PSObject WrittenObject; internal int NotePropertyCount { get { return notePropertyCount; } } private int notePropertyCount; } #endregion } }