在 Windows 服务上通过 WinSCP 下载 SFTP 文件时冻结
Downloading SFTP files through WinSCP on a Windows Service freezes
我正在尝试构建一个使用 SFTP 下载一些日志文件并将其导入数据库的服务。
因为Delphi没有附带SFTP组件,我创建了一个BAT文件来使用WinSCP下载日志
DownloadLogs.bat:
WinSCP.com < DownloadLogs.commands
DownloadLogs.commands:
open sftp://root:password@myserver.com
option confirm off
get -delete /var/lib/3cxpbx/Instance1/Data/Logs/CDRLogs files
exit
这是我的服务:
procedure TsrvCentralita.ServiceExecute(Sender: TService);
const SecondsBetweenExecutions = 10;
var Counter: integer;
dmLogs: TdmLogs;
begin
Counter := 0;
while not Terminated do begin
Inc(Counter);
if Counter > SecondsBetweenExecutions then begin
Counter := 0;
dmLogs := TdmLogs.Create(Self);
try
if dmLogs.DownloadLogs then dmLogs.ImportLogs;
finally
dmLogs.Free;
end;
end;
Sleep(1000);
ServiceThread.ProcessRequests(False);
end;
end;
这就是我调用 BAT 文件的方式:
function ExecAppWait(AppName: string; Params: string = ''; Directory: string = ''; Hidden: boolean = False): Boolean;
var ShellExInfo: TShellExecuteInfo;
begin
FillChar(ShellExInfo, SizeOf(ShellExInfo), 0);
with ShellExInfo do begin
cbSize := SizeOf(ShellExInfo);
fMask := see_Mask_NoCloseProcess;
Wnd := Application.Handle;
lpFile := PChar(AppName);
lpDirectory := PChar(Directory);
lpParameters := PChar(Params);
if Hidden then nShow := sw_Hide
else nShow := sw_ShowNormal;
end;
Result := ShellExecuteEx(@ShellExInfo);
if Result then
while WaitForSingleObject(ShellExInfo.HProcess, 100) = WAIT_TIMEOUT do begin
Application.ProcessMessages; // give processor time to other tasks
if Application.Terminated then
Break;
end;
end;
function TdmLogs.DownloadLogs(Hidden: boolean = True): boolean;
var Path: string;
begin
Path := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'SFTP');;
ExecAppWait(TPath.Combine(Path, 'LogsCentralita.bat'), '', Hidden);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
当我在我的应用程序上调试 DownloadLogs 函数时,它工作正常,但是当 运行 作为服务时它冻结了。你知道哪里出了问题吗?我不能从服务中调用 CMD.exe 吗?
谢谢。
更新
根据 Martin Prikryl 的回答,我现在以这种方式执行 WinSCP:
function TdmCentralita.DownloadLogs(SaveOutput: boolean = False): boolean;
var IniFile: TIniFile;
Path, Params, User, Password, Server, Hostkey, RemotePath: string;
begin
IniFile := TIniFile.Create(TPath.ChangeExtension(GetModuleName(HInstance), '.ini'));
Server := IniFile.ReadString('Centralita', 'Servidor', '');
Hostkey := IniFile.ReadString('Centralita', 'Hostkey', '');
User := IniFile.ReadString('Centralita', 'Usuario', 'root');
Password := DecryptStr(IniFile.ReadString('Centralita', 'Password', ''), 223);
RemotePath := IniFile.ReadString('Centralita', 'PathRemoto', '');
IniFile.Free;
while (RightStr(RemotePath, 1) = '\') or (RightStr(RemotePath, 1) = '/') do RemotePath := Copy(RemotePath, 1, Length(RemotePath) - 1);
RemotePath := RemotePath + '/*.log';
Path := TPath.Combine(TPath.GetDirectoryName(GetModuleName(HInstance)), 'SFTP');
if not TDirectory.Exists(TPath.Combine(Path, 'files')) then TDirectory.CreateDirectory(TPath.Combine(Path, 'files'));
Params := '/ini=null /command "open sftp://' + User + ':' + Password + '@' + Server + ' -hostkey=""' + Hostkey + '""" "option confirm off" "get -delete ' + RemotePath + ' files\*" "exit"';
if SaveOutput then Params := Params + ' /log="' + Path + '\Log.txt" /loglevel=0';
ExecAppWait('WinSCP.com', Params, Path, True);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
您的脚本不包含 SSH 主机密钥。由于您提供命令的方式很奇怪(输入重定向而不是 /script
或 /command
开关),WinSCP 以交互模式启动。所以它提示进行主机密钥验证,然后挂起。
将 -hostkey
开关添加到您的 open
命令。参见:
- Verifying the host key in script
- My script works fine when executed manually, but fails or hangs when run by Windows Scheduler, SSIS or other automation service. What am I doing wrong?
并使用 /script
or /command
switches 使 WinSCP 在任何问题上中止,而不是挂起。
您还应该阅读批处理文件输出,以便将来更好地处理错误。
我正在尝试构建一个使用 SFTP 下载一些日志文件并将其导入数据库的服务。
因为Delphi没有附带SFTP组件,我创建了一个BAT文件来使用WinSCP下载日志
DownloadLogs.bat:
WinSCP.com < DownloadLogs.commands
DownloadLogs.commands:
open sftp://root:password@myserver.com
option confirm off
get -delete /var/lib/3cxpbx/Instance1/Data/Logs/CDRLogs files
exit
这是我的服务:
procedure TsrvCentralita.ServiceExecute(Sender: TService);
const SecondsBetweenExecutions = 10;
var Counter: integer;
dmLogs: TdmLogs;
begin
Counter := 0;
while not Terminated do begin
Inc(Counter);
if Counter > SecondsBetweenExecutions then begin
Counter := 0;
dmLogs := TdmLogs.Create(Self);
try
if dmLogs.DownloadLogs then dmLogs.ImportLogs;
finally
dmLogs.Free;
end;
end;
Sleep(1000);
ServiceThread.ProcessRequests(False);
end;
end;
这就是我调用 BAT 文件的方式:
function ExecAppWait(AppName: string; Params: string = ''; Directory: string = ''; Hidden: boolean = False): Boolean;
var ShellExInfo: TShellExecuteInfo;
begin
FillChar(ShellExInfo, SizeOf(ShellExInfo), 0);
with ShellExInfo do begin
cbSize := SizeOf(ShellExInfo);
fMask := see_Mask_NoCloseProcess;
Wnd := Application.Handle;
lpFile := PChar(AppName);
lpDirectory := PChar(Directory);
lpParameters := PChar(Params);
if Hidden then nShow := sw_Hide
else nShow := sw_ShowNormal;
end;
Result := ShellExecuteEx(@ShellExInfo);
if Result then
while WaitForSingleObject(ShellExInfo.HProcess, 100) = WAIT_TIMEOUT do begin
Application.ProcessMessages; // give processor time to other tasks
if Application.Terminated then
Break;
end;
end;
function TdmLogs.DownloadLogs(Hidden: boolean = True): boolean;
var Path: string;
begin
Path := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'SFTP');;
ExecAppWait(TPath.Combine(Path, 'LogsCentralita.bat'), '', Hidden);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
当我在我的应用程序上调试 DownloadLogs 函数时,它工作正常,但是当 运行 作为服务时它冻结了。你知道哪里出了问题吗?我不能从服务中调用 CMD.exe 吗?
谢谢。
更新
根据 Martin Prikryl 的回答,我现在以这种方式执行 WinSCP:
function TdmCentralita.DownloadLogs(SaveOutput: boolean = False): boolean;
var IniFile: TIniFile;
Path, Params, User, Password, Server, Hostkey, RemotePath: string;
begin
IniFile := TIniFile.Create(TPath.ChangeExtension(GetModuleName(HInstance), '.ini'));
Server := IniFile.ReadString('Centralita', 'Servidor', '');
Hostkey := IniFile.ReadString('Centralita', 'Hostkey', '');
User := IniFile.ReadString('Centralita', 'Usuario', 'root');
Password := DecryptStr(IniFile.ReadString('Centralita', 'Password', ''), 223);
RemotePath := IniFile.ReadString('Centralita', 'PathRemoto', '');
IniFile.Free;
while (RightStr(RemotePath, 1) = '\') or (RightStr(RemotePath, 1) = '/') do RemotePath := Copy(RemotePath, 1, Length(RemotePath) - 1);
RemotePath := RemotePath + '/*.log';
Path := TPath.Combine(TPath.GetDirectoryName(GetModuleName(HInstance)), 'SFTP');
if not TDirectory.Exists(TPath.Combine(Path, 'files')) then TDirectory.CreateDirectory(TPath.Combine(Path, 'files'));
Params := '/ini=null /command "open sftp://' + User + ':' + Password + '@' + Server + ' -hostkey=""' + Hostkey + '""" "option confirm off" "get -delete ' + RemotePath + ' files\*" "exit"';
if SaveOutput then Params := Params + ' /log="' + Path + '\Log.txt" /loglevel=0';
ExecAppWait('WinSCP.com', Params, Path, True);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
您的脚本不包含 SSH 主机密钥。由于您提供命令的方式很奇怪(输入重定向而不是 /script
或 /command
开关),WinSCP 以交互模式启动。所以它提示进行主机密钥验证,然后挂起。
将 -hostkey
开关添加到您的 open
命令。参见:
- Verifying the host key in script
- My script works fine when executed manually, but fails or hangs when run by Windows Scheduler, SSIS or other automation service. What am I doing wrong?
并使用 /script
or /command
switches 使 WinSCP 在任何问题上中止,而不是挂起。
您还应该阅读批处理文件输出,以便将来更好地处理错误。