You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

File Watcher PowerShell脚本无法自动上传文件至FTP服务器求助

问题描述

运行以下PowerShell脚本后,无法实现新增文件自动上传至FTP服务器的功能,仅得到如下输出:

PS C:\Users\Administrator\Desktop\SCRIPT> .\NEWR4.PS1
已加载WinSCP .NET程序集
请输入您的FTP用户名: ****
请输入您的FTP密码: ********
请输入源目录的UNC路径: \HOPE5\MEDIA
会话选项已设置
会话及调试日志路径已设置
已连接至FTP服务器
文件监视器已设置

Id Name PSJobTypeName State HasMoreData Location Command

1 f1cabfda-26c... NotStarted False ...
文件监视器事件已注册

对应的脚本代码:

# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
Write-Host "Loaded WinSCP .NET assembly"

# Prompt for username and password
$UserName = Read-Host -Prompt 'Enter your FTP username'
$Password = Read-Host -Prompt 'Enter your FTP password' -AsSecureString

# Prompt for source directory
$SourceDirectory = Read-Host -Prompt 'Enter the UNC path of the source directory'

# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Ftp
    HostName = "ftp.host.com"
    UserName = $UserName
    Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
}
Write-Host "Session options set up"

$session = New-Object WinSCP.Session

# Set session and debug log paths
$session.SessionLogPath = "C:\temp\WinSCP.log"
$session.DebugLogPath = "C:\temp\WinSCP-debug.log"
Write-Host "Session and debug log paths set"

try {
    # Connect
    $session.Open($sessionOptions)
    Write-Host "Connected to FTP server"

    # Watcher
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = $SourceDirectory
    $watcher.Filter = "*.*"
    $watcher.IncludeSubdirectories = $false
    $watcher.EnableRaisingEvents = $true  
    Write-Host "File watcher set up"

    $action = { 
        $path = $Event.SourceEventArgs.FullPath
        $extension = [System.IO.Path]::GetExtension($path)
        if ($extension -eq ".mxf" -or $extension -eq ".mp4" -or $extension -eq ".xml" -or $extension -eq ".oseq") {
            if ($path -notlike "*.$TMP") {
                try {
                    # Check if file is still growing
                    $size = (Get-Item $path).Length
                    Start-Sleep -Seconds 60
                    $newSize = (Get-Item $path).Length
                    if ($size -eq $newSize) {
                        # Check if file is newer than 1 day
                        $creationTime = (Get-Item $path).CreationTime
                        $timeDifference = New-TimeSpan -Start $creationTime -End (Get-Date)
                        if ($timeDifference.TotalDays -le 1) {
                            # Check if remote directory exists
                            $remotePath = "/$(Split-Path $path -Leaf)"
                            if (-not $session.FileExists($remotePath)) {
                                # Create remote directory
                                $session.CreateDirectory($remotePath)
                                Write-Host ("Created remote directory {0}" -f $remotePath)
                            }

                            # Upload files
                            Write-Host ("Attempting to upload file {0}" -f $path)
                            $transferResult = $session.PutFiles($path, $remotePath)

                            # Throw on any error
                            $transferResult.Check()

                            # Print results
                            foreach ($transfer in $transferResult.Transfers) {
                                Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
                                Add-Content -Path "C:\temp\WinSCP-success.log" -Value ("Upload of {0} succeeded" -f $transfer.FileName)
                            }
                        } else {
                            Write-Host ("File {0} is older than 1 day, skipping this round" -f $path)
                        }
                    } else {
                        Write-Host ("File {0} is still growing, skipping this round" -f $path)
                    }
                } catch {
                    Write-Host ("Error: {0}" -f $_.Exception.Message)
                    Add-Content -Path "C:\temp\WinSCP-error.log" -Value ("Error: {0}" -f $_.Exception.Message)
                    if ($_.Exception.InnerException -ne $null) {
                        Write-Host ("Inner Exception: {0}" -f $_.Exception.InnerException.Message)
                        Add-Content -Path "C:\temp\WinSCP-error.log" -Value ("Inner Exception: {0}" -f $_.Exception.InnerException.Message)
                    }
                }
            }
        }
    } 

    Register-ObjectEvent $watcher "Created" -Action $action
    Write-Host "File watcher event registered"
    while ($true) { sleep 5 }
} finally {
    # Disconnect, clean up
    $session.Dispose()
    Write-Host "Disconnected from FTP server"
}
问题分析与修复

核心问题点

  • 变量作用域隔离:Register-ObjectEvent的-Action脚本块属于独立作用域,无法直接访问主作用域的$session对象,导致上传逻辑无法调用WinSCP会话。
  • UNC路径格式错误:输入的\HOPE5\MEDIA缺少开头的\\,FileSystemWatcher无法识别为有效网络共享路径。
  • 远程路径逻辑错误:用文件名创建远程目录,会导致尝试将文件上传到同名目录下,完全不符合上传需求。
  • 未定义的临时文件变量:$TMP未初始化,过滤临时文件的条件无效。
  • 事件输出无法实时查看:后台事件作业的Write-Host内容不会同步到主控制台,错误和成功信息无法及时获取。

修复后的脚本

# 加载WinSCP .NET程序集
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
Write-Host "已加载WinSCP .NET程序集"

# 提示输入用户名和密码
$UserName = Read-Host -Prompt '请输入您的FTP用户名'
$Password = Read-Host -Prompt '请输入您的FTP密码' -AsSecureString
$plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))

# 验证并获取正确UNC路径
do {
    $SourceDirectory = Read-Host -Prompt '请输入源目录的UNC路径(格式:\\服务器\共享)'
} while (-not $SourceDirectory.StartsWith('\\'))

# 设置FTP会话选项
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Ftp
    HostName = "ftp.host.com"
    UserName = $UserName
    Password = $plainPassword
}
Write-Host "会话选项已设置"

# 创建日志目录(不存在则自动创建)
$logDir = "C:\temp"
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null }

# 初始化WinSCP会话
$session = New-Object WinSCP.Session
$session.SessionLogPath = Join-Path $logDir "WinSCP.log"
$session.DebugLogPath = Join-Path $logDir "WinSCP-debug.log"
Write-Host "会话及调试日志路径已设置"

try {
    # 连接FTP服务器
    $session.Open($sessionOptions)
    Write-Host "已连接至FTP服务器"

    # 配置文件监视器
    $watcher = New-Object System.IO.FileSystemWatcher
    $watcher.Path = $SourceDirectory
    $watcher.Filter = "*.*"
    $watcher.IncludeSubdirectories = $false
    $watcher.EnableRaisingEvents = $true  
    Write-Host "文件监视器已设置"

    # 将会话对象存入脚本级变量,供事件脚本块访问
    $script:ftpSession = $session

    # 定义文件创建事件的处理逻辑
    $action = { 
        $filePath = $Event.SourceEventArgs.FullPath
        $fileExt = [System.IO.Path]::GetExtension($filePath).ToLower()
        $allowedExts = @(".mxf", ".mp4", ".xml", ".oseq")
        $tmpSuffix = ".tmp"

        # 过滤允许的文件类型和非临时文件
        if ($allowedExts -contains $fileExt -and $filePath -notlike "*$tmpSuffix") {
            try {
                # 循环等待文件写入完成(最多等待5分钟)
                $waitElapsed = 0
                $maxWait = 300
                do {
                    $currentSize = (Get-Item $filePath).Length
                    Start-Sleep -Seconds 10
                    $newSize = (Get-Item $filePath).Length
                    $waitElapsed +=10
                } while ($currentSize -ne $newSize -and $waitElapsed -lt $maxWait)

                # 处理写入超时情况
                if ($waitElapsed -ge $maxWait) {
                    $logMsg = "文件 $filePath 写入超时,跳过上传"
                    Write-Host $logMsg
                    Add-Content -Path "C:\temp\WinSCP-error.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $logMsg"
                    return
                }

                # 检查文件是否在1天内创建
                $createTime = (Get-Item $filePath).CreationTime
                $timeDiff = New-TimeSpan -Start $createTime -End (Get-Date)
                if ($timeDiff.TotalDays -gt 1) {
                    $logMsg = "文件 $filePath 已超过1天,跳过上传"
                    Write-Host $logMsg
                    Add-Content -Path "C:\temp\WinSCP-success.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $logMsg"
                    return
                }

                # 设置远程上传路径(FTP根目录,保持原文件名)
                $fileName = [System.IO.Path]::GetFileName($filePath)
                $remotePath = "/$fileName"

                # 检查远程文件是否已存在
                if ($script:ftpSession.FileExists($remotePath)) {
                    $logMsg = "远程服务器已存在文件 $fileName,跳过上传"
                    Write-Host $logMsg
                    Add-Content -Path "C:\temp\WinSCP-success.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $logMsg"
                    return
                }

                # 执行文件上传
                $logMsg = "开始上传文件 $filePath"
                Write-Host $logMsg
                Add-Content -Path "C:\temp\WinSCP-success.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $logMsg"

                $transferResult = $script:ftpSession.PutFiles($filePath, $remotePath)
                $transferResult.Check() # 抛出上传过程中的错误

                # 记录上传成功信息
                foreach ($transfer in $transferResult.Transfers) {
                    $successMsg = "文件 $($transfer.FileName) 上传成功"
                    Write-Host $successMsg
                    Add-Content -Path "C:\temp\WinSCP-success.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $successMsg"
                }
            } catch {
                # 记录错误信息
                $errorMsg = "上传错误: $($_.Exception.Message)"
                Write-Host $errorMsg -ForegroundColor Red
                Add-Content -Path "C:\temp\WinSCP-error.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $errorMsg"
                if ($_.Exception.InnerException) {
                    $innerErrMsg = "内部错误: $($_.Exception.InnerException.Message)"
                    Write-Host $innerErrMsg -ForegroundColor Red
                    Add-Content -Path "C:\temp\WinSCP-error.log" -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $innerErrMsg"
                }
            }
        }
    } 

    # 注册文件创建事件
    $eventJob = Register-ObjectEvent $watcher "Created" -Action $action
    Write-Host "文件监视器事件已注册"

    # 保持脚本运行,同步显示事件输出
    Write-Host "脚本正在运行,按Ctrl+C停止..."
    while ($true) {
        if ($eventJob.HasMoreData) {
            Receive-Job $eventJob
        }
        Start-Sleep -Seconds 5
    }
} finally {
    # 清理资源
    if ($watcher) { $watcher.Dispose() }
    if ($session) { 
        $session.Dispose()
        Write-Host "已断开与FTP服务器的连接"
    }
    if ($eventJob) { Unregister-Event $eventJob.Id }
}

关键修复说明

  • 作用域处理:通过$script:ftpSession将主作用域的会话对象暴露给事件脚本块,解决跨作用域访问问题。
  • 路径验证:强制输入符合格式的UNC路径,避免无效路径导致监视器无法工作。
  • 远程路径修正:直接使用文件名作为远程路径,确保文件正确上传到FTP根目录(可根据需求修改为指定目录)。
  • 文件写入等待优化:用循环检查文件大小替代固定等待,更高效适配不同大小的文件。
  • 日志增强:添加时间戳,便于问题追溯;同步事件输出到主控制台,实时查看运行状态。

内容的提问来源于stack exchange,提问作者David Greenway

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 03:18:11