Functions/GenXdev.FileSystem/Find-Item.Initialization.cs

// ################################################################################
// Part of PowerShell module : GenXdev.FileSystem
// Original cmdlet filename : Find-Item.Initialization.cs
// Original author : René Vaessen / GenXdev
// Version : 1.264.2025
// ################################################################################
// MIT License
//
// Copyright 2021-2025 GenXdev
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ################################################################################
 
 
 
using StreamRegex.Extensions.Core;
using StreamRegex.Extensions.RegexExtensions;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Diagnostics;
using System.Management;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Windows.ApplicationModel.Calls;
 
/// <summary>
/// Handles initialization for the FindItem cmdlet.
/// </summary>
public partial class FindItem : PSCmdlet
{
 
    /// <summary>
    /// Sets up the pattern matcher for content search.
    /// </summary>
    protected void InitializePatternMatcher()
    {
 
        // check if custom pattern provided
        HavePattern = ".*" != Pattern;
 
        // set base regex options
        // Configure regex options based on case sensitivity
        var regexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant;
 
        // add ignore case if not sensitive
        if (!CaseSensitivePattern)
        {
            regexOptions |= RegexOptions.IgnoreCase;
        }
 
        // create regex matcher
        PatternMatcher = new Regex(Pattern, regexOptions);
 
        // log creation if verbose
        if (UseVerboseOutput)
        {
            VerboseQueue.Enqueue($"Pattern matcher created with options: {regexOptions}");
        }
    }
 
    /// <summary>
    /// Sets up cancellation token with optional timeout.
    /// </summary>
    protected void InitializeCancellationToken()
    {
 
        // create token source
        // Set up cancellation with optional timeout
        cts = new CancellationTokenSource();
 
        // apply timeout if specified
        if (TimeoutSeconds.HasValue)
        {
            cts.CancelAfter(TimeSpan.FromSeconds(TimeoutSeconds.Value));
 
            // log timeout if verbose
            if (UseVerboseOutput)
            {
                VerboseQueue.Enqueue($"Search timeout set to {TimeoutSeconds.Value} seconds");
            }
        }
    }
 
    /// <summary>
    /// Configures buffering for pattern matching.
    /// </summary>
    protected void InitializeBufferingConfiguration()
    {
 
        // calculate max file size from ram
        // Calculate max file size based on available RAM to prevent memory
        // issues
        MaxFilesizeToProcess = Math.Max(1024 * 1024, Math.Min(Int32.MaxValue - 1, Convert.ToInt64(Math.Round((GetFreeRamInBytes() / 10d) / Convert.ToDouble(MaxDegreeOfParallelism), 0))));
 
        // set overlap size
        int overlapSize = (int)Math.Max(1024 * 1024 * 5, Math.Min(MaxFilesizeToProcess / 3, 1024 * 1024 * 2));
 
        // set buffer size
        PatternMatcherOptions.BufferSize = overlapSize * 3;
 
        // set overlap
        PatternMatcherOptions.OverlapSize = overlapSize;
 
        // disable value capture
        PatternMatcherOptions.DelegateOptions.CaptureValues = false;
 
        // log sizes if verbose
        if (UseVerboseOutput)
        {
            VerboseQueue.Enqueue($"MaxFilesizeToProcess: {MaxFilesizeToProcess:N0} bytes");
            VerboseQueue.Enqueue($"Pattern matcher buffer size: {PatternMatcherOptions.BufferSize:N0} bytes");
            VerboseQueue.Enqueue($"Pattern matcher overlap size: {PatternMatcherOptions.OverlapSize:N0} bytes");
        }
    }
 
    /// <summary>
    /// Resolves the relative base directory.
    /// </summary>
    protected void InitializeRelativeBaseDir()
    {
 
        // declare base dir
        string baseDir;
 
        // check if base path provided
        if (!string.IsNullOrEmpty(RelativeBasePath))
        {
 
            // use full path if rooted
            if (Path.IsPathRooted(RelativeBasePath))
            {
                baseDir = Path.GetFullPath(RelativeBasePath);
            }
            else
            {
 
                // combine with current if relative
                baseDir = Path.GetFullPath(Path.Combine(CurrentDirectory, RelativeBasePath));
            }
        }
        else
        {
 
            // default to current
            baseDir = Path.GetFullPath(CurrentDirectory);
        }
 
        // set relative base
        RelativeBasePath = baseDir;
    }
 
    /// <summary>
    /// Sets the current directory safely.
    /// </summary>
    protected void InitializeCurrentDirectory()
    {
 
        // get powershell location
        // Get current PowerShell location for base directory
        var psLocation = InvokeScript<string>("(Get-Location).Path");
 
        // declare safe dir
        string safeDir = null;
 
        // declare validity
        bool isValid = false;
 
        // try to validate
        try
        {
 
            // get full path
            // Validate and get full path
            safeDir = Path.GetFullPath(psLocation);
 
            // check existence
            isValid = System.IO.Directory.Exists(safeDir);
        }
        catch
        {
 
            // log invalid if verbose
            if (UseVerboseOutput)
            {
                VerboseQueue.Enqueue($"Invalid current directory path: {psLocation}");
            }
 
            // set invalid
            isValid = false;
        }
 
        // use if valid
        if (isValid)
        {
            CurrentDirectory = safeDir;
 
            // log if verbose
            if (UseVerboseOutput)
            {
                VerboseQueue.Enqueue($"Using current directory: {CurrentDirectory}");
            }
        }
        else
        {
 
            // default to profile
            // Default to user profile if current directory invalid
            CurrentDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
 
            // log default if verbose
            if (UseVerboseOutput)
            {
                VerboseQueue.Enqueue($"Defaulted to user profile directory: {CurrentDirectory}");
            }
        }
    }
 
    /// <summary>
    /// Sets up exclude patterns for wildcards.
    /// </summary>
    protected void InitializeExcludePatterns()
    {
 
        // set options based on casing
        this.CurrentWildCardOptions = CaseSearchMaskMatching == MatchCasing.PlatformDefault ? (
                     // windows or linux based?
                     Environment.OSVersion.Platform == PlatformID.Win32NT ?
                     WildcardOptions.IgnoreCase |
                     WildcardOptions.CultureInvariant : WildcardOptions.CultureInvariant
                ) : CaseSearchMaskMatching == MatchCasing.CaseInsensitive ? (
                   WildcardOptions.IgnoreCase |
                   WildcardOptions.CultureInvariant
                ) : WildcardOptions.CultureInvariant;
 
        // create file exclude patterns
        FileExcludePatterns = Exclude.Select(
            p => WildcardPattern.Get(p, CurrentWildCardOptions)
        ).ToArray();
 
        // create dir exclude patterns
        DirectoryExcludePatterns = Exclude.Select(
            p => WildcardPattern.Get(p.EndsWith("\\") ? p.TrimEnd('\\') : p, CurrentWildCardOptions)
        ).ToArray();
 
        // default git exclusion if none
        if (DirectoryExcludePatterns.Length == 0)
        {
            DirectoryExcludePatterns = new WildcardPattern[1] { new WildcardPattern("*\\.git", CurrentWildCardOptions) };
        }
 
    }
 
    /// <summary>
    /// Initializes wildcard matcher and deduplicates excludes.
    /// </summary>
    protected void InitializeWildcardMatcher()
    {
 
        // deduplicate excludes if present
        if (Exclude != null && Exclude.Length > 0)
        {
            Exclude = Exclude.Distinct().ToArray();
        }
        else
        {
 
            // set empty if none
            Exclude = Array.Empty<string>();
        }
    }
 
    /// <summary>
    /// Sets up visited nodes dictionary with appropriate comparer.
    /// </summary>
    protected void InitializeVisitedNodes()
    {
 
        // create dictionary with casing comparer
        VisitedNodes = new ConcurrentDictionary<string, bool>(
            comparer: (
                   this.CaseSearchMaskMatching == MatchCasing.PlatformDefault ?
                   (
                        // windows or linux based?
                        Environment.OSVersion.Platform == PlatformID.Win32NT ?
                        StringComparer.OrdinalIgnoreCase :
                        StringComparer.Ordinal
                   ) : (
 
                        this.CaseSearchMaskMatching == MatchCasing.CaseInsensitive ?
                            StringComparer.OrdinalIgnoreCase :
                            StringComparer.Ordinal
                   )
                )
        );
    }
 
    /// <summary>
    /// Initializes verbose output setting
    /// </summary>
    protected void InitializeVerboseOutput()
    {
 
        // attempt to set verbose
        try
        {
 
            // check verbose switch
            // Check for -Verbose switch
            bool verboseSwitch = MyInvocation.BoundParameters.ContainsKey("Verbose");
 
            // check preference if no switch
            // Fall back to VerbosePreference if no switch
            if (!verboseSwitch)
            {
 
                // get preference value
                var verbosePref = InvokeScript<string>("Write-Output ($VerbosePreference)");
 
                // evaluate preference
                verboseSwitch = !string.IsNullOrEmpty(verbosePref) &&
                               !verbosePref.Equals("SilentlyContinue", StringComparison.OrdinalIgnoreCase);
            }
 
            // set flag
            UseVerboseOutput = verboseSwitch;
        }
        catch
        {
 
            // default to false on error
            UseVerboseOutput = false;
        }
    }
 
    /// <summary>
    /// Prepares search directory from mask.
    /// </summary>
    /// <param name="searchMask">The search mask.</param>
    protected void InitializeSearchDirectory(string searchMask)
    {
 
        // normalize separators
        // Normalize separators to backslashes for consistency
        searchMask = searchMask.Replace("/", "\\");
 
        // normalize path
        // Normalize the path part for processing
        var normPath = NormalizePathForNonFileSystemUse(searchMask);
 
        // adjust trailing recurse
        // Adjust for trailing recursive patterns
        if (RecurseEndPatternWithSlashAtStartMatcher.IsMatch(normPath))
        {
            normPath += "\\";
        }
 
        // determine path types
        // Determine path type for correct handling
        bool isRooted = Path.IsPathRooted(normPath);
        bool isUncPath = normPath.StartsWith(@"\\");
        bool needsCurrentDir = !isUncPath && isRooted && (normPath.Length < 3 || normPath[2] != '\\');
        bool isRelative = !isRooted && !isUncPath;
 
        // remove leading dot
        // Remove leading .\ for clean path
        if (normPath.StartsWith(".\\"))
        {
            normPath = normPath.Substring(2);
        }
 
        // normalize duplicates in unc
        // Normalize duplicate separators
        if (isUncPath)
        {
 
            // find first separator after unc
            int i = normPath.IndexOf('\\', 2);
 
            // combine parts
            normPath = normPath.Substring(0, i) +
                        normPath.Substring(i).Replace("\\.\\", "\\").Replace("\\\\", "\\");
        }
        else
        {
 
            // normalize local
            normPath = normPath.Replace("\\.\\", "\\").Replace("\\\\", "\\");
        }
 
        // get drives to search
        var drives = GetDrivesToSearch().ToArray();
 
        // handle no specific drives
        // Handle search without specific drives
        if (drives.Length == 0)
        {
            ProcessSingleDriveSearch(normPath, isRelative, needsCurrentDir);
            return;
        }
 
        // process each drive
        // Process search for each specified drive
        foreach (var drive in drives)
        {
            ProcessDriveSearch(drive, normPath, isRelative, isRooted, isUncPath, needsCurrentDir);
        }
    }
 
    /// <summary>
    /// Processes search for single drive.
    /// </summary>
    /// <param name="searchMask">Mask.</param>
    /// <param name="isRelative">If relative.</param>
    /// <param name="needsCurrentDir">If needs current.</param>
    protected void ProcessSingleDriveSearch(string searchMask, bool isRelative, bool needsCurrentDir)
    {
 
        // check ending separator
        bool endsWithSeperator = searchMask.EndsWith("\\");
 
        // set final path
        var finalPath = searchMask;
 
        // handle relative
        if (isRelative)
        {
 
            // combine with current
            // Combine with current directory for relative paths
            finalPath = Path.Combine(CurrentDirectory, searchMask);
        }
        else if (needsCurrentDir)
        {
 
            // get drive current path
            // Get current path for the drive
            var currentPath = InvokeScript<string>($@"(Microsoft.PowerShell.Management\Get-Location -PSDrive {searchMask.Substring(0, 1)}).Path");
 
            // combine
            finalPath = Path.Combine(currentPath, searchMask.Substring(2));
        }
 
        // add separator if needed
        if (endsWithSeperator && !finalPath.EndsWith("\\"))
        {
            finalPath += "\\";
        }
 
        // add to queue
        AddToSearchQueue(finalPath);
    }
 
    /// <summary>
    /// Processes search for specific drive.
    /// </summary>
    /// <param name="drive">Drive info.</param>
    /// <param name="normPath">Normalized path.</param>
    /// <param name="isRelative">If relative.</param>
    /// <param name="isRooted">If rooted.</param>
    /// <param name="isUncPath">If UNC.</param>
    /// <param name="needsCurrentDir">If needs current.</param>
    protected void ProcessDriveSearch(DriveInfo drive, string normPath, bool isRelative, bool isRooted, bool isUncPath, bool needsCurrentDir)
    {
 
        // try processing
        try
        {
 
            // check root existence
            if (System.IO.Directory.Exists(drive.RootDirectory.FullName))
            {
 
                // set final path
                var finalPath = normPath;
 
                // handle relative
                if (isRelative)
                {
                    finalPath = Path.Combine(drive.RootDirectory.FullName, normPath);
                }
                else if (isRooted)
                {
 
                    // handle unc
                    if (isUncPath)
                    {
 
                        // find separator
                        int i = normPath.IndexOf('\\', 2);
 
                        // combine
                        finalPath = Path.Combine(drive.RootDirectory.FullName, normPath.Substring(i + 1));
                    }
                    else
                    {
 
                        // handle needs current
                        if (needsCurrentDir)
                        {
                            finalPath = drive.RootDirectory.FullName.Substring(0, 3) + normPath.Substring(2);
                        }
                        else
                        {
                            finalPath = drive.RootDirectory.FullName.Substring(0, 2) + normPath.Substring(2);
                        }
                    }
                }
 
                // add to queue
                AddToSearchQueue(finalPath);
            }
        }
        catch
        {
 
            // queue progress skip
            ProgressQueue.Enqueue($"Skipping drive {drive.Name} due to access issues.");
 
            // queue verbose skip
            VerboseQueue.Enqueue($"Skipping drive {drive.Name} due to access issues.");
        }
    }
}