' ' 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. ' Imports System Imports System.Text.RegularExpressions Imports System.Collections Imports System.Collections.ObjectModel Imports Microsoft.VisualBasic Imports System.Management.Automation Imports System.Management.Automation.Provider Imports System.ComponentModel Namespace Microsoft.Samples.PowerShell.Commands #Region "SelectStringCommand" ''' ''' A cmdlet to search through PSObjects for particular patterns. ''' ''' ''' Can be used to search any object like a file or a variable ''' whose provider exposes methods for reading and writing ''' contents ''' _ Public Class SelectStringCommand Inherits PSCmdlet #Region "Parameters" ''' ''' The Path of objects(files) to be searched ''' for the specified string/pattern. ''' ''' Path of the object(s) to search _ Public Property Path() As String() Get Return paths End Get Set(ByVal value As String()) paths = value End Set End Property Private paths() As String ''' ''' The pattern(s) used to find a match from the string ''' representation of the object. A result will be returned ''' if either of the patterns match (OR matching) ''' ''' ''' The patterns will be compiled into an array of wildcard ''' patterns if its a simple match (literal string matching) ''' else it will be converted into an array of compiled ''' regular expressions. ''' ''' Array of patterns to search. _ Public Property Pattern() As String() Get Return patterns End Get Set(ByVal value As String()) patterns = value End Set End Property Private patterns() As String Private regexPattern() As Regex Private wildcardPattern() As WildcardPattern ''' ''' A script block to call to perform the matching operations ''' instead of the matching performed by the Cmdlet ''' ''' Script block that will be called for matching _ Public Property Script() As ScriptBlock Get Return myScript End Get Set(ByVal value As ScriptBlock) myScript = value End Set End Property Private myScript As ScriptBlock = Nothing ''' ''' If set, match pattern string literally. ''' If not (default), search using pattern as a Regular ''' Expression ''' ''' True if matching literally _ Public Property SimpleMatch() As SwitchParameter Get Return mySimpleMatch End Get Set(ByVal value As SwitchParameter) mySimpleMatch = value End Set End Property Private mySimpleMatch As Boolean ''' ''' If true, then do case-sensitive searches. False by default. ''' ''' True, if case-sensitive searches are made _ Public Property CaseSensitive() As SwitchParameter Get Return myCaseSensitive End Get Set(ByVal value As SwitchParameter) myCaseSensitive = value End Set End Property Private myCaseSensitive As Boolean ''' ''' Allows to include particular files. Files not matching ''' one of these (if specified) are excluded. ''' _ Public Property Include() As String() Get Return includeStrings End Get Set(ByVal value As String()) includeStrings = value ReDim myInclude(includeStrings.Length - 1) Dim i As Integer For i = 0 To includeStrings.Length - 1 myInclude(i) = New WildcardPattern( _ includeStrings(i), WildcardOptions.IgnoreCase) Next i End Set End Property Friend includeStrings As String() = Nothing Friend myInclude As WildcardPattern() = Nothing ''' ''' Allows to exclude particular files. Files matching ''' one of these (if specified) are excluded. ''' _ Public Property Exclude() As String() Get Return excludeStrings End Get Set(ByVal value As String()) excludeStrings = value ReDim myExclude(excludeStrings.Length - 1) Dim i As Integer For i = 0 To excludeStrings.Length - 1 myExclude(i) = New WildcardPattern( _ excludeStrings(i), WildcardOptions.IgnoreCase) Next i End Set End Property Friend excludeStrings() As String Friend myExclude() As WildcardPattern #End Region #Region "Overrides" ''' ''' If regular expressions are used for pattern matching, ''' then build an array of compiled regular expressions ''' at startup.This increases performance during scanning ''' operations when simple matching is not used. ''' Protected Overrides Sub BeginProcessing() WriteDebug("Validating patterns.") If Not (patterns Is Nothing) Then Dim pattern As String For Each pattern In patterns If pattern Is Nothing Then ThrowTerminatingError(New ErrorRecord( _ New ArgumentNullException( _ "Search pattern cannot be null."), _ "NullSearchPattern", _ ErrorCategory.InvalidArgument, pattern)) End If Next pattern WriteVerbose("Search pattern(s) are valid.") ' If it's not a simple match, then ' compile the regular expressions once. If Not simpleMatch.ToBool() Then WriteDebug("Compiling search regular expressions.") Dim regexOptions As RegexOptions = RegexOptions.Compiled If Not caseSensitive.ToBool() Then regexOptions = regexOptions Or RegexOptions.IgnoreCase End If regexPattern = New Regex(patterns.Length) {} Dim i As Integer For i = 0 To patterns.Length - 1 Try regexPattern(i) = New Regex(patterns(i), regexOptions) Catch ex As ArgumentException ThrowTerminatingError(New ErrorRecord(ex, _ "InvalidRegularExpression", _ ErrorCategory.InvalidArgument, patterns(i))) End Try Next i 'loop through patterns to create RegEx objects WriteVerbose("Pattern(s) compiled into regular expressions.") ' if not a simple match ' If it's a simple match, then compile the ' wildcard patterns once Else WriteDebug("Compiling search wildcards.") Dim wildcardOptions As WildcardOptions = _ WildcardOptions.Compiled If Not caseSensitive.ToBool() Then wildcardOptions = wildcardOptions Or _ WildcardOptions.IgnoreCase End If wildcardPattern = New WildcardPattern(patterns.Length) {} Dim i As Integer For i = 0 To patterns.Length - 1 wildcardPattern(i) = New WildcardPattern( _ patterns(i), wildcardOptions) Next i WriteVerbose("Pattern(s) compiled into wildcard expressions.") End If ' if match is a simple match End If ' if valid patterns are available End Sub 'BeginProcessing ' end of function BeginProcessing() ''' ''' Process the input and search for the specified patterns ''' Protected Overrides Sub ProcessRecord() Dim lineNumber As UInt64 = 0 Dim result As MatchInfo Dim nonMatches As New ArrayList() ' Walk the list of paths and search the contents for ' any of the specified patterns Dim psPath As String For Each psPath In paths ' Once the filepaths are expanded, we may have more than one ' path, so process all referenced paths. Dim path As PathInfo For Each path In _ SessionState.Path.GetResolvedPSPathFromPSPath(psPath) WriteVerbose(("Processing path " + path.Path)) ' Check if the path represented is one to be excluded ' if so continue If Not MeetsIncludeExcludeCriteria(path.ProviderPath) Then GoTo ContinueForEach2 End If ' Get the content reader for the item(s) at the ' specified path Dim readerCollection As Collection(Of IContentReader) = _ Nothing Try readerCollection = _ Me.InvokeProvider.Content.GetReader(path.Path) Catch ex As PSNotSupportedException WriteError(New ErrorRecord(ex, _ "ContentAccessNotSupported", _ ErrorCategory.NotImplemented, path.Path)) Return End Try Dim reader As IContentReader For Each reader In readerCollection ' Reset the line number for this path. lineNumber = 0 ' Read in a single block (line in case of a file) ' from the object. Dim items As IList = reader.Read(1) ' Read and process one block(line) at a time until ' no more blocks(lines) exist While Not (items Is Nothing) AndAlso items.Count = 1 ' Increment the line number each time a line is ' processed. lineNumber += 1 Dim message As String = String.Format( _ "Testing line {0} : {1}", lineNumber, items(0)) WriteDebug(message) result = SelectString(items(0)) If Not (result Is Nothing) Then result.Path = path.Path result.LineNumber = lineNumber WriteObject(result) Else ' Add the block(line) that did notmatch to the ' collection of non-matches, which will be stored ' in the SessionState variable $NonMatches nonMatches.Add(items(0)) End If ' Get the next line from the object. items = reader.Read(1) End While Next reader ContinueForEach2: ' read and process one line at a time Next path ' loop through the reader collection Next psPath ' process all referenced paths ' walk the list of paths ' Store the list of non-matches in the ' session state variable $NonMatches. Try Me.SessionState.PSVariable.Set("NonMatches", nonMatches) Catch ex As SessionStateUnauthorizedAccessException WriteError(New ErrorRecord(ex, _ "CannotWriteVariableNonMatches", _ ErrorCategory.InvalidOperation, nonMatches)) End Try End Sub 'ProcessRecord ' protected override void ProcessRecord() #End Region #Region "PrivateMethods" ''' ''' Check for a match using the input string and the pattern(s) ''' specified. ''' ''' The string to test. ''' MatchInfo object containing information about ''' result of a match Private Function SelectString(ByVal input As Object) As MatchInfo Dim line As String = Nothing Try ' Convert the object to a string type ' safely using language support methods line = CStr(LanguagePrimitives.ConvertTo(input, GetType(String))) line = line.Trim(" "c, vbTab) Catch ex As PSInvalidCastException WriteError(New ErrorRecord(ex, _ "CannotCastObjectToString", _ ErrorCategory.InvalidOperation, input)) Return Nothing End Try Dim result As MatchInfo = Nothing ' If a scriptblock has been specified, call it ' with the path for processing. It will return ' one object. If Not (script Is Nothing) Then WriteDebug("Executing script block.") Dim psObjects As Collection(Of PSObject) = _ script.Invoke(line, simpleMatch, caseSensitive) Dim psObject As PSObject For Each psObject In psObjects If LanguagePrimitives.IsTrue(psObject) Then result = New MatchInfo() result.Line = line result.IgnoreCase = Not caseSensitive.ToBool() Exit For End If Next psObject ' if script block exists ' See if this line matches any of the match ' patterns. Else Dim patternIndex As Integer = 0 While patternIndex < patterns.Length If simpleMatch.ToBool() AndAlso _ wildcardPattern(patternIndex).IsMatch(line) OrElse _ (Not (regexPattern Is Nothing) AndAlso _ regexPattern(patternIndex).IsMatch(line)) Then result = New MatchInfo() result.IgnoreCase = Not caseSensitive.ToBool() result.Line = line result.Pattern = patterns(patternIndex) Exit While End If patternIndex += 1 End While End If ' loop through patterns and do a match ' no script block specified Return result End Function 'SelectString ' end of SelectString ''' ''' Check whether the supplied name meets the include/exclude criteria. ''' That is - it's on the include list if there is one and not on ''' the exclude list if there was one of those. ''' ''' path to validate ''' True if the path is acceptable. Private Function MeetsIncludeExcludeCriteria(ByVal path As String) _ As Boolean Dim ok As Boolean = False ' see if the file is on the include list... If Not (Me.include Is Nothing) Then Dim patternItem As WildcardPattern For Each patternItem In myInclude If patternItem.IsMatch(path) Then ok = True Exit For End If Next patternItem Else ok = True End If If Not ok Then Return False End If ' now see if it's on the exclude list... If Not (myExclude Is Nothing) Then Dim patternItem As WildcardPattern For Each patternItem In myExclude If patternItem.IsMatch(path) Then ok = False Exit For End If Next patternItem End If Return ok End Function 'MeetsIncludeExcludeCriteria 'MeetsIncludeExcludeCriteria #End Region End Class 'SelectStringCommand #End Region #Region "MatchInfo" ''' ''' Class representing the result of a pattern/literal match ''' that will be returned by the select-str command ''' Public Class MatchInfo ''' ''' Indicates if the match was done ignoring case. ''' ''' True if case was ignored. Public Property IgnoreCase() As Boolean Get Return myIgnoreCase End Get Set(ByVal value As Boolean) myIgnoreCase = value End Set End Property Private myIgnoreCase As Boolean ''' ''' Returns the number of the matching line. ''' ''' The number of the matching line. Public Property LineNumber() As UInt64 Get Return myLineNumber End Get Set(ByVal value As UInt64) myLineNumber = value End Set End Property Private myLineNumber As UInt64 ''' ''' Returns the text of the matching line. ''' ''' The text of the matching line. Public Property Line() As String Get Return myLine End Get Set(ByVal value As String) myLine = value End Set End Property Private myLine As String ''' ''' The full path of the object(file) containing the matching line. ''' ''' ''' It will be "inputStream" if the object came from the input ''' stream. ''' ''' The path name Public Property Path() As String Get Return myPath End Get Set(ByVal value As String) pathSet = True myPath = value End Set End Property Private myPath As String Private pathSet As Boolean ''' ''' Returns the pattern that was used in the match. ''' ''' The pattern string Public Property Pattern() As String Get Return myPattern End Get Set(ByVal value As String) myPattern = value End Set End Property Private myPattern As String Private Const MatchFormat As String = "{0}:{1}:{2}" ''' ''' Returns the string representation of this object. The format ''' depends on whether a path has been set for this object or ''' not. ''' ''' ''' If the path component is set, as would be the case when ''' matching in a file, ToString() would return the path, line ''' number and line text. If path is not set, then just the ''' line text is presented. ''' ''' The string representation of the match object Public Overrides Function ToString() As String If pathSet Then Return String.Format( _ System.Threading.Thread.CurrentThread.CurrentCulture, _ MatchFormat, Me.path, Me.lineNumber, Me.line) Else Return Me.line End If End Function 'ToString End Class 'MatchInfo #End Region #Region "PowerShell snap-in" ''' ''' Create this sample as a PowerShell snap-in ''' _ Public Class SelectStringPSSnapIn Inherits PSSnapIn ''' ''' Create an instance of the SelectStrPSSnapin ''' Public Sub New() End Sub 'New ''' ''' Get a name for this PowerShell snap-in. This name will be used ''' in registering this PowerShell snap-in. ''' Public Overrides ReadOnly Property Name() As String Get Return "SelectStrPSSnapIn" End Get End Property ''' ''' Vendor information for this PowerShell snap-in. ''' Public Overrides ReadOnly Property Vendor() As String Get Return "Microsoft" End Get End Property ''' ''' Gets resource information for vendor. This is a string of format: ''' resourceBaseName,resourceName. ''' Public Overrides ReadOnly Property VendorResource() As String Get Return "SelectStrSnapIn,Microsoft" End Get End Property ''' ''' Description of this PowerShell snap-in. ''' Public Overrides ReadOnly Property Description() As String Get Return "This is a PowerShell snap-in that " & _ "includes the select-str cmdlet." End Get End Property End Class 'SelectStringPSSnapIn #End Region End Namespace