Powershell write txt file many times. How fix?

Hi, want to save information about a file sent to FTP only once to a text file called “sentFiles.txt”. Files are sent to the server FTP correctly, but information about the sent files is written repeatedly for the same files to the sentFiles.txt file and it is issue.

I tried several code changes, but to no avail. I don’t know how to change it. Above whole my code. Please help.

# Set the script parameters
$localPath = "C:\links" # local location on the hard drive
$prefix = "YourPrefix" # prefix for text file names
$ftpServer = "your FTP" # name of the FTP or FTPS server
$ftpPort = 21 # port on the FTP server
$ftpUser = "user name" # user name on the FTP server
$ftpPassword = "password" # password on the FTP server
$ftpPath = "/upload" # FTP path

# Load the module to handle FTP
Import-Module -Name WinSCP -ErrorAction SilentlyContinue

# Create an FTP session without SSL/TLS encryption
$sessionOption = New-WinSCPSessionOption -HostName $ftpServer -PortNumber $ftpPort -Credential (New-Object System.Management.Automation.PSCredential ($ftpUser, (ConvertTo-SecureString $ftpPassword -AsPlainText -Force))) -Protocol Ftp -FtpSecure None
$session = New-WinSCPSession -SessionOption $sessionOption -ErrorAction SilentlyContinue

# Read the list of files sent to the FTP server from a text file
$sentFilePath = Join-Path -Path $localPath -ChildPath $sentFile -ErrorAction SilentlyContinue
if (Test-Path -Path $sentFilePath -ErrorAction SilentlyContinue) {
    $sentFiles = Get-Content -Path $sentFilePath -ErrorAction SilentlyContinue
}
else {
    $sentFiles = @()
}

# Create an infinite loop
while ($true) {
    # Get the list of text files from the local path
    $localFiles = Get-ChildItem -Path $localPath -Filter "$prefix*.txt" -ErrorAction SilentlyContinue

    # Check if there are new text files
    foreach ($localFile in $localFiles) {
        # Skip files with a size of 0 bytes or older than 7 days
        if ($localFile.Length -eq 0 -or $localFile.LastWriteTime -lt (Get-Date).AddDays(-7)) {
            continue
        }

        # Get the name, size and modification date of the file
        $localName = $localFile.Name
        $localSize = $localFile.Length
        $localDate = $localFile.LastWriteTime.ToString()

        # Check if the file has already been sent to the FTP server
        $isSent = $false
        foreach ($sentFile in $sentFiles) {
            # Compare the name, size and modification date of the file
            if ($sentFile -eq "$localName,$localSize,$localDate") {
                $isSent = $true
                break
            }
        }

        # If the file has not been sent, send it to the FTP server
        if (-not $isSent) {
            try {
                # Copy the file to the FTP server with the resume option
                $session.PutFiles($localFile.FullName, "$ftpPath/$localName").Check()

                # Add the file to the list of sent files
                $sentFiles += "$localName,$localSize,$localDate"

                # Display information about sending the file
                Write-Host "Sent file $localName to FTP server successfully." -ErrorAction SilentlyContinue

                # Save information about the sent file in a text file
                "$localName,$localSize,$localDate" | Out-File -FilePath "C:\links\sentFiles.txt" -Append -ErrorAction SilentlyContinue
            }
            catch {
                # Skip exceptions and continue the script
                continue
            }
        }
    }

    # Wait a minute before checking again
    Start-Sleep -Seconds 60 -ErrorAction SilentlyContinue
}

Hi, welcome to the forum :wave:

The while loop seems to work OK. I think the problem is with this section:

$sentFilePath = Join-Path -Path $localPath -ChildPath $sentFile -ErrorAction SilentlyContinue
if (Test-Path -Path $sentFilePath -ErrorAction SilentlyContinue) {
    $sentFiles = Get-Content -Path $sentFilePath -ErrorAction SilentlyContinue
}
else {
    $sentFiles = @()
}

You don’t define $sentFile at the start of your script which means that Test-Path returns $false and you start with an empty array every time you run your script. Because $sentFiles is empty on the first run, you’re going to process all your files again.

I would be inclined to move the files to a different folder once they’re uploaded rather than maintain a text file, perhaps with logging as well. You may also consider a FileSystemWatcher rather than an infinite loop.

You should also be aware that arrays are a fixed size and using += destroys and recreates the array when you add something to it. If you have lots of files this will eventually cause performance problems. You should consider using Generic.List and its Add() method.

1 Like

Hi Matt,

Thanks for tips.
I modified my script according to your instructions. I’m a noob in coding, so I can’t do more at this point, even if I want to.

And currently the script is not sending files to the FTP server. The Powershell script console shows no errors, but the files are not uploaded to FTP.

I used 3 methods to send files to FTP:

FtpWebRequest
PutFiles for WinSCPSession
WebClient

Could you help me fix this code?

Below is my modified script:

# Set the script parameters
$localPath = "C:\links" # local location on the hard drive
$prefix = "YourPrefix" # prefix for text file names
$ftpServer = "your FTP" # name of the FTP or FTPS server
$ftpPort = 21 # port on the FTP server
$ftpUser = "user name" # user name on the FTP server
$ftpPassword = "password" # password on the FTP server
$ftpPath = "/upload" # FTP path

# Load the module to handle FTP
Import-Module -Name WinSCP -ErrorAction SilentlyContinue

# Create an FTP session without SSL/TLS encryption
$sessionOption = New-WinSCPSessionOption -HostName $ftpServer -PortNumber $ftpPort -Credential (New-Object System.Management.Automation.PSCredential ($ftpUser, (ConvertTo-SecureString $ftpPassword -AsPlainText -Force))) -Protocol Ftp -FtpSecure None
$session = New-WinSCPSession -SessionOption $sessionOption -ErrorAction SilentlyContinue

# Create a folder for sent files, if it does not exist
$sentPath = Join-Path -Path $localPath -ChildPath "sent"
if (-not (Test-Path -Path $sentPath)) {
    New-Item -Path $sentPath -ItemType Directory
}

# Create a file system watcher that monitors the local folder
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $localPath
$watcher.Filter = "$prefix*.txt"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true

# Register an event that runs when a new file appears
$job = Start-Job -ScriptBlock {
  param($watcher, $localPath, $sentPath, $ftpPath)
  Register-EngineEvent -SourceIdentifier FileSystemWatcher.Created -Action {

      # Get the name of the new file
      $fileName = $Event.SourceEventArgs.Name

      # Create a local and remote path for the file
      $localFile = Join-Path -Path $localPath -ChildPath $fileName
      $remoteFile = "$ftpPath/$fileName"

      # Check the file properties before sending it to the FTP server
      $fileInfo = Get-Item -Path $localFile
      $fileAge = (Get-Date) - $fileInfo.LastWriteTime
      $fileSize = $fileInfo.Length
      if ($fileAge -lt (New-TimeSpan -Days 7) -and $fileSize -gt 0) {

          # Send the file to the FTP server, using the WinSCPSession method
          $session.PutFiles($localFile, $remoteFile).Check()

          # Move the file to the sent folder, using the Move-Item command
          Move-Item -Path $localFile -Destination $sentPath

          # Display information about sending the file
          Write-Host "File $fileName sent to FTP server successfully."
      }
      else {
          # Display information about skipping the file
          Write-Host "File $fileName skipped due to old or empty file."
      }
  }
} -ArgumentList $watcher, $localPath, $sentPath, $ftpPath

# monitoring starts now:
$watcher.EnableRaisingEvents = $true

Write-Host "Watching for changes to $Path"

# since the FileSystemWatcher is no longer blocking PowerShell
# we need a way to pause PowerShell while being responsive to
# incoming events. Use an endless loop to keep PowerShell busy:
do
{
  # Wait-Event waits for a second and stays responsive to events
  # Start-Sleep in contrast would NOT work and ignore incoming events
  Wait-Event -Timeout 1

  # write a dot to indicate we are still monitoring:
  Write-Host "." -NoNewline
      
} while ($true)
finally
{
  # this gets executed when user presses CTRL+C:
  
  # stop monitoring
  $watcher.EnableRaisingEvents = $false
  
  # remove the event handler
  Unregister-Event -SourceIdentifier FileSystemWatcher.Created
  
  # event handlers are technically implemented as a special kind
  # of background job, so remove the job now:
  Remove-Job -Id $job.Id
  
  # properly dispose the FileSystemWatcher:
  $watcher.Dispose()
  
  # receive the results from the job
  Receive-Job -Id $job.Id | Select-Object -Property Output, Error, Progress
  
  Write-Warning "Event Handler disabled, monitoring ends."
}

These look like ChatGPT scripts.

I’m sorry, but I’m not spending time debugging ChatGPT output. May I ask what you’ve done to troubleshoot the problem yourself?

Like I wrote before:

I used 3 methods to send files to FTP:

FtpWebRequest
PutFiles for WinSCPSession
WebClient

I meant what have you done to troubleshoot the PowerShell code?

You’ve concluded it’s a problem uploading the files and changed the FTP method. When I run it, it doesn’t even move the file to the ‘sent’ folder, or output that it moved the file. This would happen even if the upload didn’t work. To me, that suggests that there’s a problem with your watcher, event monitoring, or the action you’ve defined.

The script also has a finally block, but no try block, which is a pretty fundamental error.

1 Like