在 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 命令。参见:


并使用 /script or /command switches 使 WinSCP 在任何问题上中止,而不是挂起。

您还应该阅读批处理文件输出,以便将来更好地处理错误。