2025-11-28 00:35:46 +09:00

546 lines
23 KiB
C++

//
// 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
{
/// <summary>
/// A cmdlet to search through PSObjects / Paths for particular patterns.
/// </summary>
/// <remarks>
/// Can be used to search any object like a file or a variable
/// whose provider exposes methods for reading and writing
/// contents
/// </remarks>
[Cmdlet("Select","Str",
DefaultParameterSetName="PatternParameterSet")]
public ref class SelectStrCmdlet : public PSCmdlet
{
private:
#pragma region Private Data
array<String^>^ paths;
array<String^>^ patterns;
array<Regex^>^ regexPattern;
array<WildcardPattern^>^ wildcardPattern;
ScriptBlock^ script;
bool simpleMatch;
bool caseSensitive;
array<String^>^ includeStrings;
array<WildcardPattern^>^ include;
array<String^>^ excludeStrings;
array<WildcardPattern^>^ exclude;
#pragma endregion
public:
#pragma region Parameters
/// <summary>
/// The Path of objects(files) to be searched
/// for the specified string/pattern.
/// </summary>
/// <value>Path of the object(s) to search</value>
[Parameter(
Position = 0,
ParameterSetName = "ScriptParameterSet",
Mandatory = true)]
[Parameter(
Position = 0,
ParameterSetName = "PatternParameterSet",
ValueFromPipeline = true,
Mandatory = true)]
[Alias("PSPath")]
property array<String^>^ Path
{
array<String^>^ get()
{
return paths;
}
void set(array<String^>^ value)
{
paths = value;
}
}
/// <summary>
/// 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)
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <value>Array of patterns to search.</value>
[Parameter(
Position = 1,
ParameterSetName = "PatternParameterSet",
Mandatory = true)]
property array<String^>^ Pattern
{
array<String^>^ get()
{
return patterns;
}
void set(array<String^>^ value)
{
patterns = value;
}
}
/// <summary>
/// A script block to call to perform the matching operations
/// instead of the matching performed by the Cmdlet
/// </summary>
/// <value>Script block that will be called for matching</value>
[Parameter(
Position = 1,
ParameterSetName = "ScriptParameterSet",
Mandatory = true)]
property ScriptBlock^ Script
{
ScriptBlock^ get()
{
return script;
}
void set(ScriptBlock^ value)
{
script = value;
}
}
/// <summary>
/// If set, match pattern string literally.
/// If not (default), search using pattern as a Regular
/// Expression
/// </summary>
/// <value>True if matching literally</value>
[Parameter]
property SwitchParameter SimpleMatch
{
SwitchParameter get()
{
return simpleMatch;
}
void set(SwitchParameter value)
{
simpleMatch = value;
}
}
/// <summary>
/// If true, then do case-sensitive searches. False by default.
/// </summary>
/// <value>True, if case-sensitive searches are made</value>
[Parameter]
property SwitchParameter CaseSensitive
{
SwitchParameter get()
{
return caseSensitive;
}
void set(SwitchParameter value)
{
caseSensitive = value;
}
}
/// <summary>
/// Allows to include particular files. Files not matching
/// one of these (if specified) are excluded.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
property array<String^>^ Include
{
array<String^>^ get()
{
return includeStrings;
}
void set(array<String^>^ value)
{
includeStrings = value;
this->include = gcnew array<WildcardPattern^>(includeStrings->Length);
for (int i = 0; i < includeStrings->Length; i++)
{
this->include[i] = gcnew WildcardPattern(includeStrings[i], WildcardOptions::IgnoreCase);
}
}
}
/// <summary>
/// Allows to exclude particular files. Files matching
/// one of these (if specified) are excluded.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
property array<String^>^ Exclude
{
array<String^>^ get()
{
return excludeStrings;
}
void set(array<String^>^ value)
{
excludeStrings = value;
this->exclude = gcnew array<WildcardPattern^>(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
/// <summary>
/// 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.
/// </summary>
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<Regex^>(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<WildcardPattern^>(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
}
/// <summary>
/// Process the input and search for the specified patterns
/// </summary>
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<IContentReader^>^ 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
/// <summary>
/// Check for a match using the input string and the pattern(s)
/// specified.
/// </summary>
/// <param name="input">The string to test.</param>
/// <returns>MatchInfo object containing information about
/// result of a match</returns>
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<PSObject^>^ 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
/// <summary>
/// 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.
/// </summary>
/// <param name="path">path to validate</param>
/// <returns>True if the path is acceptable.</returns>
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
};
}
}
}
}