This is an old revision of the document!

Search recursively for text in remote directory / Grep files over SFTP/FTP protocol

The following example uses WinSCP .NET assembly from a PowerShell script. If you have another preferred language, you can easily translate it.

In the latest beta version, the example is distributed in WinSCP installer as a WinSCP extension.

You can run the script (e.g. SearchText.ps1) from WinSCP GUI using local custom command:

powershell.exe -File SearchText.ps1 -sessionUrl "!S" -path "!/" -text "!?Text:?!" -pause

Advertisement

See also Listing files matching wildcard.

You can alter the script for other tasks, instead of grepping the matching files. You can for example remove or download the matching files. Just modify the action in the Action on match block accordingly.

In the Beta Version

In the latest beta version, you can simplify the implementation by using Session.EnumerateRemoteFiles.

# @name         &Search for Text...
# @command      powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" -sessionUrl "!S" -path "!/" -text "%Text%" -wildcard "%Wildcard%" -pause -sessionLogPath "%SessionLogPath%"
# @description  Searches recursively for a text in the current remote directory
# @version      2
# @homepage     https://winscp.net/eng/docs/library_example_recursive_search_text
# @require      WinSCP 5.8.4
# @option       Text -run textbox "Text:"
# @option       Wildcard -run textbox "Wildcard:" "*.*"
# @option       SessionLogPath -config file "&Session log file:"
 
param (
    # Use Generate URL function to obtain a value for -sessionUrl parameter.
    $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xx-xx-xx@example.com/",
    [Parameter(Mandatory)]
    $path,
    [Parameter(Mandatory)]
    $text,
    $wildcard = "*.*",
    $sessionLogPath = $Null,
    [Switch]
    $pause = $False
)
 
try
{
    # Load WinSCP .NET assembly
    $assemblyPath = if ($env:WINSCP_PATH) { $env:WINSCP_PATH } else { $PSScriptRoot }
    Add-Type -Path (Join-Path $assemblyPath "WinSCPnet.dll")
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.ParseUrl($sessionUrl)
 
    $session = New-Object WinSCP.Session
 
    try
    {
        $session.SessionLogPath = $sessionLogPath
 
        # Connect
        $session.Open($sessionOptions)
 
        # Recursivelly enumerate files to grep
        $fileInfos =
            $session.EnumerateRemoteFiles(
                $path, $wildcard, [WinSCP.EnumerationOptions]::AllDirectories)
 
        foreach ($fileInfo in $fileInfos)
        {
            # Action on match
 
            # Modify the code below if you want to do another task with
            # matching files, instead of grepping their contents
 
            Write-Host ("File {0} matches mask, searching contents..." -f $fileInfo.FullName)
            $tempPath = ($env:temp + "\" + $fileInfo.Name)
            # Download file to temporary directory
            $transferResult = $session.GetFiles($session.EscapeFileMask($fileInfo.FullName), $tempPath)
            # Did the download succeeded?
            if (!$transferResult.IsSuccess)
            {
                # Print error (but continue with other files)
                Write-Host $transferResult.Failures[0].Message
            }
            else
            {
                # Search and print lines containing "text".
                # Use -Pattern instead of -SimpleMatch for regex search
                $matchInfo = Select-String -Path $tempPath -SimpleMatch $text
                # Print the results
                foreach ($match in $matchInfo)
                {
                    Write-Host ($fileInfo.FullName + ":" + $match.LineNumber + ":" + $match.Line)
                }
                # Delete temporary local copy
                Remove-Item $tempPath
            }
        }
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
 
    $result = 0
}
catch [Exception]
{
    Write-Host ("Error: {0}" -f $_.Exception.Message)
    $result = 1
}
 
# Pause if -pause switch was used
if ($pause)
{
    Write-Host "Press any key to exit..."
    [System.Console]::ReadKey() | Out-Null
}
 
exit $result

Advertisement

In the Stable Version

param (
    # Use Generate URL function to obtain a value for -sessionUrl parameter.
    $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xx-xx-xx@example.com/",
    [Parameter(Mandatory)]
    $path,
    [Parameter(Mandatory)]
    $text,
    $wildcard = "*.*",
    [Switch]
    $pause = $False
)
 
function SearchDirectory ($session, $path, $wildcard, $text)
{
    Write-Host ("Searching directory {0} ..." -f $path)
 
    $directoryInfo = $session.ListDirectory($path)
 
    foreach ($fileInfo in $directoryInfo.Files)
    {
        $filePath = ($path + "/" + $fileInfo.Name) 
        
        if ($fileInfo.IsDirectory)
        {
            # Skip references to current and parent directories
            if (($fileInfo.Name -ne ".") -and
                ($fileInfo.Name -ne ".."))
            {
                # Recurse into subdirectory
                SearchDirectory $session $filePath $wildcard $text
            }
        }
        else
        {
            # Does file name match wildcard?
            if ($fileInfo.Name -Like $wildcard)
            {
                # Action on match
 
                # Modify the code below if you want to do another task with
                # matching files, instead of grepping their contents
 
                Write-Host ("File {0} matches mask, searching contents..." -f $filePath)
                $tempPath = ($env:temp + "\" + $fileInfo.Name)
                # Download file to temporary directory
                $transferResult = $session.GetFiles($session.EscapeFileMask($filePath), $tempPath)
                # Did the download succeeded?
                if (!$transferResult.IsSuccess)
                {
                    # Print error (but continue with other files)
                    Write-Host $transferResult.Failures[0].Message
                }
                else
                {
                    # Search and print lines containing "text".
                    # Use -Pattern instead of -SimpleMatch for regex search
                    $matchInfo = Select-String -Path $tempPath -SimpleMatch $text
                    # Print the results
                    foreach ($match in $matchInfo)
                    {
                        Write-Host ($filePath + ":" + $match.LineNumber + ":" + $match.Line)
                    }
                    # Delete temporary local copy
                    Remove-Item $tempPath
                }
            }
        }
    }
}
 
try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "WinSCPnet.dll"
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.ParseUrl($sessionUrl)
 
    $session = New-Object WinSCP.Session
    
    try
    {
        # Connect
        $session.Open($sessionOptions)
        
        # Start recursive search
        SearchDirectory $session $path $wildcard $text
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
 
    $result = 0
}
catch [Exception]
{
    Write-Host ("Error: {0}" -f $_.Exception.Message)
    $result = 1
}
 
# Pause if -pause switch was used
if ($pause)
{
    Write-Host "Press any key to exit..."
    [System.Console]::ReadKey() | Out-Null
}
 
exit $result

Advertisement

Last modified: by martin