(************************************************) (*TPlinkremote Version 1.0 (06.06.2006) *) (* *) (*Copyright ® 2006 by Uwe Zeh *) (*E-Mail: daliuz@gmx.net *) (*- - - - - - - - - - - - - - - - - - - - - - - *) (*Ich übernehme keine Haftung für etwaige *) (*Schäden, die durch diese Komponente verursacht*) (*werden. *) (*- - - - - - - - - - - - - - - - - - - - - - - *) (*Diese Komponente ist FREEWARE. *) (*Alle Rechte vorbehalten *) (* *) (* Vielen Dank an Felix John für die Infos *) (* auf http://www.felix-colibri.com *) (************************************************) unit Plinkremote; interface uses Windows, StrUtils, SysUtils, Classes, Controls, Forms, Dialogs, UPipeThread; const LINE_FEED = #10; CARRIAGE_RETURN = #13; NEW_LINE = CARRIAGE_RETURN + LINE_FEED; type TTerminateStatus = (tsRunning, tsWaitForTerminate, tsTerminated); TPlinkRemote = class; //Class forward TPipe = record ReadHandle: THandle; WriteHandle: THandle; end; TExecuteWaitEvent = procedure( const ProcessInfo: TProcessInformation; var ATerminate: Boolean ) of object; TPlinkDataAvailableEvent = procedure( Sender: TPlinkRemote; const Buffer: string ) of object; TPlinkDataErrorEvent = procedure( Sender: TPlinkRemote; const Buffer: string ) of object; TPlinkCloseEvent = procedure( Sender: TPlinkRemote ) of object; TPlinkErrorEvent = procedure( Sender: TPlinkRemote; const Error: string ) of object; TPlinkRemote = class(TComponent) private FTerminate: TTerminateStatus; FInPipe: TPipe; FOutPipe: TPipe; FErrorPipe: TPipe; FFilename: string; FParameters: string; Fm_EscFLag: Boolean; Fm_EscBuffer: string[80]; FClearEscSeq: Boolean; FLogFileName: string; FLogging: Boolean; FPipe: TPipeThread; FProcessInfo : TProcessInformation; function IsRunning(create_event: Boolean): Boolean; function CreatePipeEx(var pv_pipe: TPipe): boolean; procedure ClosePipe(p_pipe: TPipe); function CreatePipes: Boolean; procedure ClosePipes; procedure ReadOutput; procedure ReadError; function ReadPipe(const APipe : TPipe) : String; function ASCII2ANSI(AText:AnsiString):AnsiString; procedure ExecuteFile(const AFilename: string; AParameter, ACurrentDir: string; AWait: Boolean); function CleanEscSeq(const buffer: string): string; function f_check_Escapeseq(const chr: char): char; procedure WriteLog(p_text, log_file_name: string); function IsTrustedKey(const Buffer: String): Boolean; function GetExecuted: Boolean; procedure PipeReadPipeData; protected FOnDataAvailable: TPlinkDataAvailableEvent; FOnDataError: TPlinkDataErrorEvent; FOnPlinkclose: TPlinkCloseEvent; FOnError: TPlinkErrorEvent; public destructor Destroy; override; constructor Create(AOwner: TComponent); override; published function Execute : Boolean; procedure Terminate; procedure SendText(AText : String); property OnDataError: TPlinkDataErrorEvent read FOnDataError write FOnDataError; property OnDataAvailable: TPlinkDataAvailableEvent read FOnDataAvailable write FOnDataAvailable; property OnPlinkClose: TPlinkCloseEvent read FOnPlinkclose write FOnPlinkclose; property OnError: TPlinkErrorEvent read FOnError write FOnError; property Filename: string read FFilename write FFilename; property Parameters: string read FParameters write FParameters; property ClearEsqSeq: Boolean read FClearEscSeq write FClearEscSeq default false; property LogFileName: string read FLogFileName write FLogFileName; property LogFile: Boolean read FLogging write FLogging default false; property Executed : Boolean read GetExecuted; end; {procedure Register;} implementation function TPlinkRemote.CreatePipeEx(var pv_pipe: TPipe): boolean; const k_pipe_buffer_size = 8192; begin with pv_pipe do begin // -- Create the pipe result:= CreatePipe(ReadHandle, WriteHandle, nil, k_pipe_buffer_size); if result then result:= DuplicateHandle( GetCurrentProcess, ReadHandle, GetCurrentProcess, @ReadHandle, 0, True, DUPLICATE_CLOSE_SOURCE OR DUPLICATE_SAME_ACCESS ); if result then result:= DuplicateHandle( GetCurrentProcess, WriteHandle, GetCurrentProcess, @WriteHandle, 0, True, DUPLICATE_CLOSE_SOURCE OR DUPLICATE_SAME_ACCESS ); end; end; // CreatePipeEx procedure TPlinkRemote.ClosePipe(p_pipe: TPipe); begin with p_pipe do begin CloseHandle(ReadHandle); CloseHandle(WriteHandle); end; end; // ClosePipe function TPlinkRemote.CreatePipes: Boolean; begin Result:= CreatePipeEx(FInPipe) and CreatePipeEx(FOutPipe) and CreatePipeEx(FErrorPipe); end; // CreatePipes procedure TPlinkRemote.ClosePipes; begin ClosePipe(FInPipe); ClosePipe(FOutPipe); ClosePipe(FErrorPipe); end; // ClosePipes procedure TPlinkRemote.ExecuteFile(const AFilename: string; AParameter, ACurrentDir: string; AWait: Boolean); var StartupInfo: TStartupInfo; FileDirectory: pchar; begin if not CreatePipes then begin if Assigned(OnError) then OnError(Self, 'Error creating I/O pipes!'); end else begin if (ACurrentDir <> '') and (AnsiLastChar(ACurrentDir) = '\') then Delete(ACurrentDir, Length(ACurrentDir), 1); if ACurrentDir = '' then FileDirectory := nil else FileDirectory := PChar(ACurrentDir); FillChar(StartupInfo, SizeOf(StartupInfo), 0); with StartupInfo do begin cb := SizeOf(StartupInfo); dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; wShowWindow := SW_HIDE; // show no window hStdInput:= FInPipe.ReadHandle; // read input from input pipe hStdError:= FErrorPipe.WriteHandle; // Write errors to the error pipe hStdOutput:= FOutPipe.WriteHandle; // Write Ouput to output pipe end; FillChar(FProcessInfo, SizeOf(FProcessInfo), 0); AParameter := Format('%s %s', [AFilename, TrimRight(AParameter)]); if FTerminate = tsTerminated then begin if CreateProcess( nil, PChar(AParameter), nil, nil, true, CREATE_DEFAULT_ERROR_MODE or CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, FileDirectory, StartupInfo, FProcessInfo) then begin FPipe.ProcessHandle := FProcessInfo.hProcess; FTerminate := tsRunning; FPipe.Resume; end; end else begin if Assigned(OnError) then OnError(Self, 'Prozess "'+AFilename+'" konnte nicht gestartet werden!'); end; end; end; procedure TPlinkRemote.ReadOutput; var Output : string; begin Output := ReadPipe(FOutPipe); if Output <> '' then begin if Assigned(FOnDataAvailable) then FOnDataAvailable(Self, Output); end; // received some bytes end; function TPlinkRemote.ASCII2ANSI(AText:AnsiString):AnsiString; const cMaxLength = 255; var PText : PAnsiChar; begin Result:=''; PText:=AnsiStrAlloc(cMaxLength); while AText <> '' do begin StrPCopy(PText, copy(AText, 1, cMaxLength-1)); OemToAnsi(PText, PText); Result:=Result + StrPas(PText); delete(AText, 1, cMaxLength-1); end; StrDispose(PText); end; function TPlinkRemote.ReadPipe(const APipe: TPipe): String; var BufferReadCount, OutLen: Cardinal; BytesRemaining : Cardinal; Buffer : array [0..1023] of AnsiChar; R:AnsiString; begin Result := ''; if APipe.ReadHandle = INVALID_HANDLE_VALUE then begin if Assigned(OnError) then OnError(Self, 'Error I/O pipes!'); end else begin // -- check to see if there is any data to read from stdout PeekNamedPipe( APipe.ReadHandle, nil, 0, nil, @BufferReadCount, nil ); if BufferReadCount <> 0 then begin FillChar(Buffer, sizeof(Buffer), 'z'); // -- read by 1024 bytes chunks BytesRemaining := BufferReadCount; OutLen := 0; while BytesRemaining >= 1024 do begin // -- read the stdout pipe ReadFile(APipe.ReadHandle, Buffer, 1024, BufferReadCount, nil); Dec(BytesRemaining, BufferReadCount); SetLength(R, OutLen + BufferReadCount); Move(Buffer, R[OutLen + 1], BufferReadCount); Inc(OutLen, BufferReadCount); end; if BytesRemaining > 0 then begin ReadFile(APipe.ReadHandle, Buffer, BytesRemaining, BufferReadCount, nil); SetLength(R, OutLen + BufferReadCount); Move(Buffer, R[OutLen + 1], BufferReadCount); end; R:=ASCII2ANSI(R); {$WARNINGS OFF} Result:=AnsiToUtf8(R); {$WARNINGS ON} if FClearEscSeq then Result := CleanEscSeq(Result); if FLogging then WriteLog(Result, FLogFileName); end; // received some bytes end; Result:=StringReplace( Result, CARRIAGE_RETURN + CARRIAGE_RETURN + LINE_FEED, CARRIAGE_RETURN + LINE_FEED, [rfReplaceAll] ); end; // ReadOutput procedure TPlinkRemote.ReadError; var Output : String; begin Output := ReadPipe(FErrorPipe); if Output <> '' then begin if not IsTrustedKey(Output) and Assigned(OnDataError) then OnDataError(Self, Output); end; end; // ReadError procedure TPlinkRemote.SendText(AText: String); var WrittenBytes: Cardinal; Text:AnsiString; begin // -- check if the process is still active if IsRunning(true) then begin {$WARNINGS OFF} Text:=Utf8ToAnsi(AText); {$WARNINGS ON} if Text <> '' then WriteFile(FInPipe.WriteHandle, Text[1], Length(Text), WrittenBytes, nil); end; end; // SendText function TPlinkRemote.Execute: boolean; begin // -- check if the process is still active Result := false; if (FTerminate = tsTerminated) and not IsRunning(false) then begin if not FileExists(Filename)then begin if Assigned(OnError) then OnError(Self, 'Datei nicht gefunden: ' + Filename); end else begin // --start program ExecuteFile(Filename, FParameters, ExtractFileDir(Filename), True); Result := true; end; end else if Assigned(OnError) then OnError(Self, 'Prozess: ' + FFilename + ' bereits gestartet!!'); end; procedure TPlinkRemote.Terminate; begin if FTerminate <> tsTerminated then FTerminate := tsWaitForTerminate; end; destructor TPlinkRemote.Destroy; begin // Initiere ein Beenden des Programms if FTerminate <> tsTerminated then begin Terminate; PipeReadPipeData; end; FPipe.Terminate; Application.ProcessMessages; if FPipe.Suspended then FPipe.Resume; Application.ProcessMessages; FPipe.Free; inherited; end; constructor TPlinkRemote.Create(AOwner: Tcomponent); begin inherited Create(AOwner); FPipe := TPipeThread.Create(true); FPipe.ReadPipeData := PipeReadPipeData; FTerminate := tsTerminated; FOnDataAvailable:=nil; FOnDataError:=nil; FOnPlinkclose:=nil; FOnError:=nil; end; function TPlinkRemote.CleanEscSeq(const buffer: string): string; var i : integer; chr: char; begin result:=''; Fm_EscFlag := FALSE; for I := 1 to Length(Buffer) do begin chr:= f_check_Escapeseq(buffer[I]); if chr <> #0 then result:=result + chr; end; end; function TPlinkRemote.f_check_Escapeseq(const chr: char): char; var bProcess : Boolean; begin Result := #0; if Fm_EscFLag then begin bProcess := False; if (Length(Fm_EscBuffer) = 0) and CharInSet(Chr, ['D', 'M', 'E', 'H', '7', '8', '=', '>', '<']) then begin bProcess := True; end else if (Length(Fm_EscBuffer) = 1) and (Fm_EscBuffer[1] in ['(', ')', '*', '+']) then begin bProcess := True; end else if CharInSet(Chr, ['0'..'9', ';', '?', ' ']) or ( (Length(Fm_EscBuffer) = 0) and CharInSet(chr, ['[', '(', ')', '*', '+'])) then begin {$WARNINGS OFF} Fm_EscBuffer := Fm_EscBuffer + Chr; {$WARNINGS ON} if Length(Fm_EscBuffer) >= High(Fm_EscBuffer) then begin MessageBeep(MB_ICONASTERISK); Fm_EscBuffer := ''; Fm_EscFlag := FALSE; end; end else bProcess := True; if bProcess then begin Fm_EscBuffer := ''; Fm_EscFlag := FALSE; end; end else if chr = #27 then begin Fm_EscBuffer := ''; Fm_EscFlag := TRUE; end else result:= chr; end; procedure TPlinkRemote.WriteLog(p_text: string; log_file_name: string); var log_file: file; begin AssignFile(log_file,log_file_name); if not FileExists(log_file_name)then Rewrite(log_file, 1) else reset(log_file, 1); Seek(log_file, FileSize(log_file)); BlockWrite(log_file, p_text[1], Length(p_text)); Close(log_file); end; // write_string function TPlinkRemote.IsRunning(create_event: Boolean): Boolean; var l_process_exit_code: Cardinal; begin Result := false; // -- check if the process is still active GetExitCodeProcess(FProcessInfo.hProcess, l_process_exit_code); if l_process_exit_code = STILL_ACTIVE then result:= true; if not result and create_event and Assigned(OnPlinkClose) then OnPlinkClose(Self); end; procedure Register; begin RegisterComponents('PuttyComp', [TPlinkRemote]); end; function TPlinkRemote.IsTrustedKey(const Buffer: String): Boolean; begin result:= false; if Pos('host key is not cached in the registry', buffer) > 0 then begin result:= true; if MessageDlg(Buffer, mtInformation, [mbYes, mbNo], 0) = mrYes then SendText('y') else SendText('n'); end; if Pos('host key does not match the one PuTTY', buffer) > 0 then begin result:= true; if MessageDlg(Buffer, mtError, [mbYes, mbNo], 0) = mrYes then SendText('y') else SendText('n'); end; end; function TPlinkRemote.GetExecuted: Boolean; begin Result := IsRunning(false); end; procedure TPlinkRemote.PipeReadPipeData; begin if FTerminate <> tsTerminated then begin ReadOutput; ReadError; if FTerminate = tsWaitForTerminate then begin FPipe.WillSuspend := true; TerminateProcess(FProcessInfo.hProcess, 0); //Cardinal(-1)); CloseHandle(FProcessInfo.hProcess); // close programm CloseHandle(FProcessInfo.hThread); ClosePipes; // close all pipes FTerminate := tsTerminated; IsRunning(true); // create a on closeevent end; end; end; end.