' ' Copyright (c) 2006 Microsoft Corporation. All rights reserved. ' ' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ' ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO ' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A ' PARTICULAR PURPOSE. ' Description: This class will demonstrate creating a sample Select-Obj ' cmdlet. This cmdlet mimics the behavior of the Select-Object cmdlet ' included in the Windows PowerShell distribution. ' ' For a description of Select-Object run "get-help Select-Object" from ' Windows PowerShell command line. ' ' System namespaces needed Imports System Imports System.Collections.Generic Imports System.Diagnostics Imports System.Globalization Imports Microsoft.VisualBasic ' Windows PowerShell namespaces needed Imports System.Management.Automation Namespace Microsoft.Samples.PowerShell.Commands ''' ''' A cmdlet to select properties from objects. ''' ''' ''' Making this an un-derivable class. Depending on your needs ''' change this. ''' _ Public NotInheritable Class SelectObjCommand Inherits Cmdlet #Region "Parameters" ''' ''' Object that this cmdlet will work on. ''' Can be either from pipeline or directly specified ''' as an argument to the cmdlet ''' ''' _ Public Property InputObject() As PSObject Get Return myInputObject End Get Set(ByVal value As PSObject) myInputObject = value End Set End Property Private myInputObject As PSObject ''' ''' Property parameter for the cmdlet. ''' Takes an array of strings as input. ''' _ Public Property [Property]() As String() Get Return properties End Get Set(ByVal value As String()) properties = value End Set End Property Private properties() As String ''' ''' Helps to select specific number of objects from the ''' last object. ''' _ Public Property Last() As Integer Get Return myLast End Get Set(ByVal value As Integer) myLast = value End Set End Property Private myLast As Integer = 0 ''' ''' Helps to select specific number of objects from the ''' first object. ''' _ Public Property First() As Integer Get Return myFirst End Get Set(ByVal value As Integer) myFirst = value End Set End Property Private myFirst As Integer = 0 ''' ''' Expand specific properties from the selected object ''' ''' _ Public Property Expand() As String Get Return myExpand End Get Set(ByVal value As String) myExpand = value End Set End Property Private myExpand As String = Nothing ''' ''' Select unique objects or not ''' ''' true or false _ Public Property Unique() As Boolean Get Return myUnique End Get Set(ByVal value As Boolean) myUnique = value End Set End Property Private myUnique As Boolean = False #End Region #Region "Internal Properties/Data" Private inputQueue As SelectObjQueue Private uniques As List(Of UniquePSObjectHelper) = Nothing #End Region #Region "Cmdlet Overrides" ''' ''' Intialize cmdlet data here. ''' This method is called only once even when the cmdlet ''' is used in the middle a pipe. ''' Protected Overrides Sub BeginProcessing() WriteVerbose("Initializing Object Queue") If unique Then uniques = New List(Of UniquePSObjectHelper)() End If inputQueue = New SelectObjQueue(myFirst, myLast) End Sub 'BeginProcessing ''' ''' Do per object related work here. ''' This method is called for every object that is coming ''' from the input pipe. ''' Protected Overrides Sub ProcessRecord() If myInputObject Is Nothing Then Return End If WriteVerbose("Received an input Object. Processing...") inputQueue.Enqueue(myInputObject) ' Lets see 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.. Dim streamingInputObject As PSObject = inputQueue.StreamingDequeue() If Not (streamingInputObject Is Nothing) Then ProcessObject(streamingInputObject) WriteVerbose("Processed InputObject") End If End Sub 'ProcessRecord ''' ''' 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 Overrides Sub EndProcessing() WriteVerbose("Processing remaining Input objects") Dim targetObject As PSObject For Each targetObject In inputQueue ProcessObject(targetObject) Next targetObject If Not (uniques Is Nothing) Then Dim obj As UniquePSObjectHelper For Each obj In uniques If obj.WrittenObject Is Nothing Then GoTo ContinueForEach1 End If WriteObject(obj.WrittenObject) ContinueForEach1: Next obj End If End Sub 'EndProcessing #End Region #Region "Internal Methods" Private Sub ProcessObject(ByVal inputObject As PSObject) If (properties Is Nothing OrElse properties.Length = 0) _ AndAlso String.IsNullOrEmpty(expand) _ Then SendToOutputPipe(inputObject, New List(Of PSNoteProperty)()) Return End If ' Collect property values for the properties specified by ' the user. Dim matchedProperties As List(Of PSNoteProperty) = _ New List(Of PSNoteProperty)() If Not (properties Is Nothing) AndAlso properties.Length > 0 _ Then WriteVerbose("Collecting property values for the " & _ "user specified properties") ' make a collection of property values Dim propertyName As String For Each propertyName In properties ProcessParameter(propertyName, inputObject, _ matchedProperties) Next propertyName End If If Not String.IsNullOrEmpty(expand) Then WriteVerbose("Expanding a property") ProcessExpandParameter(inputObject, matchedProperties) Else ' Create a new psobject and embed properties that the ' user asked for. Dim outputObject As New PSObject() If matchedProperties.Count <> 0 Then Dim noteProperty As PSNoteProperty For Each noteProperty In matchedProperties Try outputObject.Properties.Add(noteProperty) Catch WriteAlreadyExistingPropertyError( _ noteProperty.Name, inputObject) End Try Next noteProperty End If SendToOutputPipe(outputObject, matchedProperties) End If End Sub 'ProcessObject ''' ''' 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 Sub ProcessParameter(ByVal propertyName As String, _ ByVal inputObject As PSObject, _ ByVal matchedProperties As List(Of PSNoteProperty)) Dim propValue As Object = GetPropValue(inputObject, propertyName) ' propValue can be null.. Dim newProperty As New PSNoteProperty(propertyName, propValue) matchedProperties.Add(newProperty) End Sub Private Sub ProcessExpandParameter(ByRef inputObject As PSObject, _ ByVal matchedProperties As List(Of PSNoteProperty)) ' expand parameter is used ' expand the property value from the inputobject Dim propValue As Object = Nothing Try propValue = GetPropValue(inputObject, expand) Catch e As Exception WriteNonTerminatingError(expand, "PropertyAccessException", e, _ ErrorCategory.InvalidData) Return End Try If propValue Is Nothing Then Dim message As String = String.Format( _ CultureInfo.CurrentCulture, _ GetErrorMessage(PROPERTYNOTFOUND), expand) Dim e As ArgumentException = New ArgumentException(message) WriteNonTerminatingError(expand, _ "PropertyNotFound", e, ErrorCategory.InvalidData) Return End If ' Expand prop value using a Enumerator Dim results As System.Collections.IEnumerable = _ LanguagePrimitives.GetEnumerable(propValue) If results Is Nothing Then Dim message As String = String.Format( _ CultureInfo.CurrentCulture, _ GetErrorMessage(NOTHINGTOEXPAND), expand) Dim e As ArgumentException = New ArgumentException(message) WriteNonTerminatingError(expand, _ "NothingToExpand", e, ErrorCategory.InvalidArgument) Return End If Dim expandedValue As Object For Each expandedValue In results If expandedValue Is Nothing Then WriteVerbose("One of the expanded values is null") GoTo ContinueForEach1 End If Dim expandedObject As PSObject = _ PSObject.AsPSObject(expandedValue) Dim noteProperty As PSNoteProperty For Each noteProperty In matchedProperties Try If Not (expandedObject.Properties( _ noteProperty.Name) Is Nothing) _ Then WriteAlreadyExistingPropertyError( _ noteProperty.Name, inputObject) Else expandedObject.Properties.Add(noteProperty) End If Catch WriteAlreadyExistingPropertyError( _ noteProperty.Name, inputObject) End Try Next noteProperty SendToOutputPipe(expandedObject, matchedProperties) ContinueForEach1: Next expandedValue End Sub Private Sub SendToOutputPipe(ByVal objectToWrite As PSObject, _ ByVal addedNoteProperties As List(Of PSNoteProperty)) If Not unique Then If Not (objectToWrite Is Nothing) Then WriteObject(objectToWrite) End If Return End If Dim isObjUnique As Boolean = True Dim uniqueObj As UniquePSObjectHelper For Each uniqueObj In uniques Dim comparer As New ObjectCommandComparer(True, _ System.Threading.Thread.CurrentThread.CurrentCulture, True) If comparer.Compare(objectToWrite.BaseObject, _ uniqueObj.WrittenObject.BaseObject) = 0 AndAlso _ uniqueObj.NotePropertyCount = addedNoteProperties.Count _ Then Dim found As Boolean = True Dim note As PSNoteProperty For Each note In addedNoteProperties Dim prop As PSMemberInfo = _ uniqueObj.WrittenObject.Properties(note.Name) If prop Is Nothing OrElse _ comparer.Compare(prop.Value, note.Value) <> 0 Then found = False Exit For End If Next note If found Then isObjUnique = False Exit For End If Else GoTo ContinueForEach1 End If ContinueForEach1: Next uniqueObj If isObjUnique Then uniques.Add(New UniquePSObjectHelper(objectToWrite, _ addedNoteProperties.Count)) End If End Sub ''' ''' 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 Function GetPropValue(ByRef inputObject As PSObject, _ ByRef propertyName As String) As Object ' This is an internal method. Please make sure valid input is passed ' to this method. Debug.Assert(Not (inputObject Is Nothing), _ "Cannot work with a null object.") Debug.Assert(Not String.IsNullOrEmpty(propertyName), _ "Property should not be null or Empty") ' we have no globbing: try an exact match, because this is quicker. Dim x As PSMemberInfo = inputObject.Members(propertyName) If (x Is Nothing) Then Return Nothing Else Return x.Value End If End Function #End Region #Region "Error Handling / Error Messages" Public Const PROPERTYALREADYEXISTS As Integer = 0 Public Const PROPERTYNOTFOUND As Integer = 1 Public Const NOTHINGTOEXPAND As Integer = 2 Dim errors() As 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 Function GetErrorMessage(ByRef errorID As Integer) As String Return errors(errorID) End Function ''' ''' Writes a non-terminating error onto the pipeline. ''' ''' ''' Object which caused this exception. ''' ''' ErrorId for this error. ''' Complete exception object. ''' ErrorCategory for this exception. Friend Sub WriteNonTerminatingError( _ ByRef targetObject As Object, _ ByRef errorId As String, _ ByRef innerException As Exception, _ ByRef category As ErrorCategory) WriteError(New ErrorRecord(innerException, errorId, category, _ targetObject)) End Sub Friend Sub WriteAlreadyExistingPropertyError( _ ByRef propertyName As String, _ ByRef inputObject As Object) Dim message As String = _ String.Format(CultureInfo.CurrentCulture, _ GetErrorMessage(PROPERTYALREADYEXISTS), propertyName) Dim e As ArgumentException = New ArgumentException(message) WriteNonTerminatingError(inputObject, "PropertyAlreadyExists", _ e, ErrorCategory.InvalidData) End Sub #End Region #Region "SelectObj Queue" Private Class SelectObjQueue Inherits Queue(Of PSObject) #Region "SelectObjQueue Data" Dim headLimit As Integer = 0 Dim tailLimit As Integer = 0 Dim streamedObjectCount As Integer = 0 #End Region #Region "SelectObjQueue Construction" Public Sub New(ByRef first As Integer, ByRef last As Integer) headLimit = first tailLimit = last End Sub #End Region #Region "SelectObjQueue Methods" Public Overloads Sub Enqueue(ByRef obj As PSObject) If tailLimit > 0 AndAlso Me.Count >= tailLimit Then MyBase.Dequeue() End If MyBase.Enqueue(obj) End Sub Public Function StreamingDequeue() As PSObject If headLimit = 0 AndAlso tailLimit = 0 OrElse _ streamedObjectCount < headLimit Then Debug.Assert(Me.Count > 0, "Streaming an empty queue") streamedObjectCount += 1 Return Dequeue() End If If tailLimit = 0 Then Dequeue() End If Return Nothing End Function #End Region End Class #End Region #Region "UniquePSObjectHelper" Private Class UniquePSObjectHelper Friend Sub New(ByRef o As PSObject, _ ByRef notePropertyCount As Integer) WrittenObject = o Me.myNotePropertyCount = notePropertyCount End Sub Friend ReadOnly WrittenObject As PSObject Friend ReadOnly Property NotePropertyCount() As Integer Get Return myNotePropertyCount End Get End Property Private myNotePropertyCount As Integer End Class #End Region End Class 'SelectObjCommand End Namespace