Powershell FTP发送大文件系统。OutOfMemoryException
我有一个部分基于此处的脚本: 这一切对于小文件来说都非常好,但我正试图使用它使我们用于将access mdb文件导出到只有ftp的客户端的过程更加健壮 我的第一个测试涉及一个10MB的文件,在获取内容阶段遇到System.OutOfMemoryException 在获取尝试期间,powershell ISE的使用率接近2GIG 这是一个完整的脚本示例(请温柔一点,我对它还比较陌生): 我不能将代码放在注释中,所以,多亏了keith的指针,我将文件访问位移到了底部,以便将其与其他文件链接起来,就像这样Powershell FTP发送大文件系统。OutOfMemoryException,powershell,ftp,out-of-memory,Powershell,Ftp,Out Of Memory,我有一个部分基于此处的脚本: 这一切对于小文件来说都非常好,但我正试图使用它使我们用于将access mdb文件导出到只有ftp的客户端的过程更加健壮 我的第一个测试涉及一个10MB的文件,在获取内容阶段遇到System.OutOfMemoryException 在获取尝试期间,powershell ISE的使用率接近2GIG 这是一个完整的脚本示例(请温柔一点,我对它还比较陌生): 我不能将代码放在注释中,所以,多亏了keith的指针,我将文件访问位移到了底部,以便将其与其他文件链接起来,就像
trap [Exception]{
$script:succeeded = $false
$script:errorMessage = $_.Exception.Message
Add-Content $logFile $((get-Date -format "yyyy-MM-dd hh:mm:ss") + "|1|Check File Connection|" + $_.Exception.Message)
$sourceStream.Close()
$sourceStream.Dispose()
#write-host $((get-Date -format "yyyy-MM-dd hh:mm:ss") + "|1|Attempt to open file|" + $_.Exception.Message)
#write-host $("TRAPPED: " + $_.Exception.GetType().FullName)
exit
}
$sourceStream = New-Object IO.FileStream ($(New-Object System.IO.FileInfo $sourceFile),[IO.FileMode]::Open)
[byte[]]$readbuffer = New-Object byte[] 1024
# get the request stream, and write the bytes into it
$rs = $ftprequest.GetRequestStream()
do{
$readlength = $sourceStream.Read($readbuffer,0,1024)
$rs.Write($readbuffer,0,$readlength)
} while ($readlength -ne 0)
我只需要找出原因:调用带有“0”参数的“GetResponse”异常:“无法访问已处理的对象。
每隔一次我运行它。这是在ISE中运行它的一个怪癖,还是我在初始声明或最终处理方面做了一些重大错误
完成后我将发布完整的最终脚本,因为我认为这将是一个很好的ftp导出示例,包含错误捕获和日志记录
好的,这是完整的脚本。Dispose已被编辑,但无论是否运行,脚本在5分钟内都会收到一条消息,告诉我无法使用disposed对象,或者告诉我getResponse()已生成一个错误(226)文件传输(在ISE中运行)。虽然这在正常操作期间不会出现问题,但我希望正确地注销FTP会话,并在脚本末尾清理资源,并确保根据需要正确声明它们
#####
# User variables to control the script
#####
# How many times connection will be re-tried
$connectionTries = 5
#time between tries in seconds
$connectionTryInterval = 1
#Where to log the output
$logFile = "D:\MyPath\ftplog.txt"
#maximum log file size in KB before it is archived
$logFileMaxSize = 500
#log to file or console - #true=log to file, #false = log to console
$logToFile=$false
#formatted date part for the specific file to transfer
#This is appended to the filename base. Leave as "" for none
$datePart = ""
#base part of the file name
$fileNameBase = "MyFile"
#file extension
$fileExtension = ".mdb"
#location of the source file (please include trailing backslash)
$sourceLocation = "D:\MyPath\"
#location and credentials of the target ftp server
$userName = "iamafish"
$password = "ihavenofingers"
$ftpServer = "10.0.1.100"
######
# Main Script
#####
function logEntry($entryType, $section, $message)
{
#just to make a one point switch for logging to console for testing
# $entryType: 0 = success, 1 = Error
# $section: The section of the script the log entry was generated from
# $message: the log message
#This is pipe separated to fit in with my standard MSSQL linked flat file schema for easy querying
$logString = "$(get-Date -format "yyyy-MM-dd hh:mm:ss")|$entryType|$section|$message"
if($script:logtoFile)
{
Add-Content $logFile $logString
}
else
{
write-host $logString
}
}
#If there is a log file and it is longer than the declared limit then archive it with the current timestamp
if (test-path $logfile)
{
if( $((get-item $logFile).Length/1kb) -gt $logFileMaxSize)
{
write-host $("archiving log to ftplog_" + (get-date -format yyyyMMddhhmmss) + ".txt")
rename-item $logFile $("ftplog_" + (get-date -format yyyyMMddhhmmss) + ".txt")
New-Item $logFile -type file
}
}
else
{
New-Item $logFile -type file
}
#contruct source file and destination uri
$fileName = $fileNameBase + $datePart + $fileExtension
$sourceFile = $sourceLocation + $fileName
$destination = "ftp://" + $ftpServer + "/" + $fileName
#Check if the source file exists
if ((test-path $sourceFile) -eq $false)
{
logEntry 1 "Check Source File" $("File not found: " + $sourceFile)
Exit
}
# Create a FTPWebRequest object to handle the connection to the ftp server
$ftpRequest = [System.Net.FtpWebRequest]::create($destination)
# set the request's network credentials for an authenticated connection
$ftpRequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftpRequest.UseBinary = $true
$ftpRequest.KeepAlive = $false
$succeeded = $true
$try = 1
do{
trap [Exception]{
$script:succeeded = $false
logEntry 1 "Check FTP Connection" $_.Exception.Message
$script:try++
start-sleep -s $connectionTryInterval
continue
}
$ftpResponse = $ftpRequest.GetResponse()
} while(($try -le $connectionTries) -and (-not $succeeded))
if ($succeeded) {
logEntry 0 "Connection to FTP" "Success"
# Open a filestream to the source file
trap [Exception]{
logEntry 1 "Check File Connection" $_.Exception.Message
$sourceStream.Close()
$ftpResponse.Close()
exit
}
$sourceStream = New-Object IO.FileStream ($(New-Object System.IO.FileInfo $sourceFile),[IO.FileMode]::Open)
[byte[]]$readbuffer = New-Object byte[] 1024
logEntry 0 "Starting file transfer" "Success"
# get the request stream, and write the bytes into it
$rs = $ftpRequest.GetRequestStream()
do{
$readlength = $sourceStream.Read($readbuffer,0,1024)
$rs.Write($readbuffer,0,$readlength)
} while ($readlength -ne 0)
logEntry 0 "Transfer complete" "Success"
# be sure to clean up after ourselves
$rs.Close()
#$rs.Dispose()
$sourceStream.Close()
#$sourceStream.Dispose()
}
$ftpResponse.Close()
尝试在末尾捕获传输正常响应的示例:
logEntry 0 "Starting file transfer" "Success"
# get the request stream, and write the bytes into it
$rs = $ftpRequest.GetRequestStream()
do{
$readlength = $sourceStream.Read($readbuffer,0,1024)
$rs.Write($readbuffer,0,$readlength)
} while ($readlength -ne 0)
$rs.Close()
#start-sleep -s 2
trap [Exception]{
$script:succeeded = $false
logEntry 1 "Check FTP Connection" $_.Exception.Message
continue
}
$ftpResponse = $ftpRequest.GetResponse()
与其使用Get Content将整个文件读取到内存中,不如尝试一次读取一个块并将其写入FTP请求流。我会使用较低级别的.NET文件流API来进行读取。诚然,您不会认为10MB会造成内存问题 此外,请确保在获取请求流并写入请求流后获得响应。响应流的获取是上载数据的方式。从文档: 当使用FtpWebRequest对象 将文件上载到服务器时,必须 将文件内容写入请求 通过调用 GetRequestStream方法或其 异步对等体 BeginGetRequestStream和 EndGetRequestStream方法。您必须 写入流并关闭 在发送请求之前进行流处理 请求通过发送到服务器 调用GetResponse方法或其 异步对等体 BeginGetResponse和EndGetResponse 方法。当请求的操作 完成后,将创建一个FtpWebResponse对象 返回。FtpWebResponse对象 提供操作的状态 以及从 服务器
与其使用Get Content将整个文件读取到内存中,不如尝试一次读取一个块并将其写入FTP请求流。我会使用较低级别的.NET文件流API来进行读取。诚然,您不会认为10MB会造成内存问题 此外,请确保在获取请求流并写入请求流后获得响应。响应流的获取是上载数据的方式。从文档: 当使用FtpWebRequest对象 将文件上载到服务器时,必须 将文件内容写入请求 通过调用 GetRequestStream方法或其 异步对等体 BeginGetRequestStream和 EndGetRequestStream方法。您必须 写入流并关闭 在发送请求之前进行流处理 请求通过发送到服务器 调用GetResponse方法或其 异步对等体 BeginGetResponse和EndGetResponse 方法。当请求的操作 完成后,将创建一个FtpWebResponse对象 返回。FtpWebResponse对象 提供操作的状态 以及从 服务器
我自己也遇到了一个类似的问题,RAM使用率影响了GB上传3MB文件,我发现:
$content = gc -en byte $sourceFile
与:
提供更好的性能。正如其他地方提到的,对于真正大的文件,分块将是一个更好的解决方案,因为这样您就不会同时将整个文件保存在内存中,但上面的代码至少只消耗(文件大小)字节的RAM,这意味着它应该可以达到~10s MB的范围。我自己也遇到了类似的问题,RAM使用量影响了GB上传3MB文件,我发现:
$content = gc -en byte $sourceFile
与:
提供更好的性能。正如其他地方提到的,对于真正大的文件,分块将是一个更好的解决方案,因为这样您就不会同时将整个文件保存在内存中,但上面的代码至少只消耗(文件大小)字节的RAM,这意味着它应该可以达到~10s MB的范围。以字节数组的形式读取它似乎是内存杀手。@keith hill谢谢。我做了很多环顾四周的工作,并如上所述修改了脚本。现在虽然当我第一次运行它时,它工作正常,但当我第二次运行它时,调用“GetResponse”时出现异常具有“0”参数的:“无法访问已释放的对象。每次交替运行时,都会出现错误。也许我不明白hwo-dispose实际上是如何工作的。我的.net有点欠缺。我不太了解您的代码,尤其是更新的部分以及它与代码其余部分的关系。在顶部的代码中,在将数据写入请求流之前,您似乎得到了响应(提交了数据)。如果我在重新运行之前留出5分钟时间,问题似乎就消失了。在发布一个完全更新的wierd字符串之前,我们正在处理一些wierd字符串连接问题$日志字符串=$(获取日期-格式“yyyy-MM-dd hh:MM:ss”)+“|”+($type)+“|”+($section)+“|”
$content = [System.IO.File]::ReadAllBytes($sourceFile)