// // 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. // #include "MatchInfo.h" using namespace System; using namespace System::Collections; using namespace System::Collections::ObjectModel; using namespace System::Text::RegularExpressions; using namespace System::Globalization; using namespace System::Management::Automation; using namespace System::Management::Automation::Provider; // This sample demonstrates the following: // 1.Usage of PSPath // 2.Usage of Scriptblocks // 3.Usage of Session state namespace Microsoft { namespace Samples { namespace PowerShell { namespace Commands { /// /// A cmdlet to search through PSObjects / Paths 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 /// [Cmdlet("Select","Str", DefaultParameterSetName="PatternParameterSet")] public ref class SelectStrCmdlet : public PSCmdlet { private: #pragma region Private Data array^ paths; array^ patterns; array^ regexPattern; array^ wildcardPattern; ScriptBlock^ script; bool simpleMatch; bool caseSensitive; array^ includeStrings; array^ include; array^ excludeStrings; array^ exclude; #pragma endregion public: #pragma region Parameters /// /// The Path of objects(files) to be searched /// for the specified string/pattern. /// /// Path of the object(s) to search [Parameter( Position = 0, ParameterSetName = "ScriptParameterSet", Mandatory = true)] [Parameter( Position = 0, ParameterSetName = "PatternParameterSet", ValueFromPipeline = true, Mandatory = true)] [Alias("PSPath")] property array^ Path { array^ get() { return paths; } void set(array^ value) { paths = value; } } /// /// 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. [Parameter( Position = 1, ParameterSetName = "PatternParameterSet", Mandatory = true)] property array^ Pattern { array^ get() { return patterns; } void set(array^ value) { patterns = value; } } /// /// 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 [Parameter( Position = 1, ParameterSetName = "ScriptParameterSet", Mandatory = true)] property ScriptBlock^ Script { ScriptBlock^ get() { return script; } void set(ScriptBlock^ value) { script = value; } } /// /// If set, match pattern string literally. /// If not (default), search using pattern as a Regular /// Expression /// /// True if matching literally [Parameter] property SwitchParameter SimpleMatch { SwitchParameter get() { return simpleMatch; } void set(SwitchParameter value) { simpleMatch = value; } } /// /// If true, then do case-sensitive searches. False by default. /// /// True, if case-sensitive searches are made [Parameter] property SwitchParameter CaseSensitive { SwitchParameter get() { return caseSensitive; } void set(SwitchParameter value) { caseSensitive = value; } } /// /// Allows to include particular files. Files not matching /// one of these (if specified) are excluded. /// [Parameter] [ValidateNotNullOrEmpty] property array^ Include { array^ get() { return includeStrings; } void set(array^ value) { includeStrings = value; this->include = gcnew array(includeStrings->Length); for (int i = 0; i < includeStrings->Length; i++) { this->include[i] = gcnew WildcardPattern(includeStrings[i], WildcardOptions::IgnoreCase); } } } /// /// Allows to exclude particular files. Files matching /// one of these (if specified) are excluded. /// [Parameter] [ValidateNotNullOrEmpty] property array^ Exclude { array^ get() { return excludeStrings; } void set(array^ value) { excludeStrings = value; this->exclude = gcnew array(excludeStrings->Length); for (int i = 0; i < excludeStrings->Length; i++) { this->exclude[i] = gcnew WildcardPattern(excludeStrings[i], WildcardOptions::IgnoreCase); } } } #pragma endregion protected: #pragma region CmdletOverrides /// /// 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. /// virtual void BeginProcessing() override { WriteVerbose("Search pattern(s) are valid."); // If it's not a simple match, then // compile the regular expressions once. if (!simpleMatch) { WriteDebug("Compiling search regular expressions."); RegexOptions regexOptions = RegexOptions::Compiled; if (!caseSensitive) { regexOptions = regexOptions | RegexOptions::IgnoreCase; } regexPattern = gcnew array(patterns->Length); for (int i = 0; i < patterns->Length; i++) { try { regexPattern[i] = gcnew Regex(patterns[i], regexOptions); } catch (ArgumentException^ ex) { ThrowTerminatingError(gcnew ErrorRecord( ex, "InvalidRegularExpression", ErrorCategory::InvalidArgument, patterns[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."); WildcardOptions wildcardOptions = WildcardOptions::Compiled; if (!caseSensitive) { wildcardOptions = wildcardOptions | WildcardOptions::IgnoreCase; } wildcardPattern = gcnew array(patterns->Length); for (int i = 0; i < patterns->Length; i++) { wildcardPattern[i] = gcnew WildcardPattern(patterns[i], wildcardOptions); } WriteVerbose("Pattern(s) compiled into wildcard expressions."); }// if match is a simple match } /// /// Process the input and search for the specified patterns /// virtual void ProcessRecord() override { UInt64 lineNumber = 0; MatchInfo^ result; ArrayList^ nonMatches = gcnew ArrayList(); // Walk the list of paths and search the contents for // any of the specified patterns for each (String^ psPath in paths) { // Once the filepaths are expanded, we may have more than one // path, so process all referenced paths. for each(PathInfo^ 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 (!MeetsIncludeExcludeCriteria(path->ProviderPath)) continue; // Get the content reader for the item(s) at the // specified path Collection^ readerCollection = nullptr; try { readerCollection = this->InvokeProvider->Content->GetReader(path->Path); } catch (PSNotSupportedException^ ex) { WriteError(gcnew ErrorRecord(ex, "ContentAccessNotSupported", ErrorCategory::NotImplemented, path->Path)); continue; } for each(IContentReader^ 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. IList^ items = reader->Read(1); // Read and process one block(line) at a time until // no more blocks(lines) exist while ((items != nullptr) && (items->Count == 1)) { // Increment the line number each time a line is // processed. lineNumber++; String^ message = String::Format(CultureInfo::InvariantCulture, "Testing line {0} : {1}", lineNumber, items[0]); WriteDebug(message); result = SelectString(items[0]); if (result != nullptr) { 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]); } // Get the next line from the object. items = reader->Read(1); }// read and process one line at a time }// loop through the reader collection }// process all referenced paths }// walk the list of paths // Store the list of non-matches in the // session state variable $NonMatches. try { this->SessionState->PSVariable->Set("NonMatches", nonMatches); } catch (SessionStateUnauthorizedAccessException^ ex) { WriteError(gcnew ErrorRecord(ex, "CannotWriteVariableNonMatches", ErrorCategory::InvalidOperation, nonMatches)); } } #pragma endregion private: #pragma region Private methods /// /// 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 MatchInfo^ SelectString(Object^ input) { String^ line = nullptr; try { // Convert the object to a string type // safely using language support methods line = (String^)LanguagePrimitives::ConvertTo( input, System::String::typeid, CultureInfo::InvariantCulture); line = line->Trim(' ','\t'); } catch (PSInvalidCastException^ ex) { WriteError(gcnew ErrorRecord( ex, "CannotCastObjectToString", ErrorCategory::InvalidOperation, input )); return nullptr; } MatchInfo^ result = nullptr; // If a scriptblock has been specified, call it // with the line for processing. It will return // one object. if (script != nullptr) { WriteDebug("Executing script block."); Collection^ psObjects = script->Invoke( line, simpleMatch, caseSensitive); for each (PSObject^ psObject in psObjects) { if (LanguagePrimitives::IsTrue(psObject)) { result = gcnew MatchInfo(); result->Line = line; result->IgnoreCase = !caseSensitive; break; } } }// if script block exists // See if this line matches any of the match // patterns. else { int patternIndex = 0; while (patternIndex < patterns->Length) { if ((simpleMatch && wildcardPattern[patternIndex]->IsMatch(line)) || (regexPattern != nullptr && regexPattern[patternIndex]->IsMatch(line)) ) { result = gcnew MatchInfo(); result->IgnoreCase = !caseSensitive; result->Line = line; result->Pattern = patterns[patternIndex]; break; } patternIndex++; }// loop through patterns and do a match }// no script block specified return result; }// 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. bool MeetsIncludeExcludeCriteria(String^ path) { bool ok = false; // see if the file is on the include list... if (this->include != nullptr) { for each (WildcardPattern^ patternItem in this->include) { if (patternItem->IsMatch(path)) { ok = true; break; } } } else { ok = true; } if (!ok) return false; // now see if it's on the exclude list... if (this->exclude != nullptr) { for each (WildcardPattern^ patternItem in this->exclude) { if (patternItem->IsMatch(path)) { ok = false; break; } } } return ok; } //MeetsIncludeExcludeCriteria #pragma endregion }; } } } }