//
// 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
};
}
}
}
}