Sample code for 30+ languages & platforms
PureBasic

SSH Parallel Remote Commands on Multiple Servers

See more SSH Examples

Demonstrates running the same command on several servers simultaneously. It is simply a matter of using one Ssh object per server, starting the command on each with QuickCmdSend, and then polling each connection with QuickCmdCheck until all have finished.

Background: This is the fan-out pattern behind configuration-management and monitoring tools: query or update a whole fleet in roughly the time the slowest single host takes, rather than the sum of all of them. Each server needs its own object because each has its own connection and authentication state. Note that results come back in whatever order the servers finish, which is why the example tracks completion per connection. Production code would keep the objects in arrays instead of repeating the logic per server.

Chilkat PureBasic Downloads

PureBasic
IncludeFile "CkSsh.pb"

Procedure ChilkatExample()

    success.i = 0

    ;  This example requires the Chilkat API to have been previously unlocked.
    ;  See Global Unlock Sample for sample code.

    ;  Demonstrates running the same command on several servers simultaneously.  It is simply a
    ;  matter of using one Ssh object per server.

    ssh1.i = CkSsh::ckCreate()
    If ssh1.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    ssh2.i = CkSsh::ckCreate()
    If ssh2.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    ssh3.i = CkSsh::ckCreate()
    If ssh3.i = 0
        Debug "Failed to create object."
        ProcedureReturn
    EndIf

    port.i = 22

    ;  Normally you would not hard-code the password in source.  You should instead obtain it
    ;  from an interactive prompt, environment variable, or a secrets vault.
    password.s = "mySshPassword"

    success = CkSsh::ckConnect(ssh1,"ssh-server1.example.com",port)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh1)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    success = CkSsh::ckAuthenticatePw(ssh1,"mySshLogin",password)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh1)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    success = CkSsh::ckConnect(ssh2,"ssh-server2.example.com",port)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh2)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    success = CkSsh::ckAuthenticatePw(ssh2,"mySshLogin",password)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh2)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    success = CkSsh::ckConnect(ssh3,"ssh-server3.example.com",port)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh3)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    success = CkSsh::ckAuthenticatePw(ssh3,"mySshLogin",password)
    If success = 0
        Debug CkSsh::ckLastErrorText(ssh3)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    ;  This command sleeps for 5 seconds and then prints the system date/time, making the parallel
    ;  execution easy to observe.
    cmd.s = "sleep 5; date"

    ;  Start the command on each server.  QuickCmdSend returns immediately.
    channel1.i = CkSsh::ckQuickCmdSend(ssh1,cmd)
    If channel1 < 0
        Debug CkSsh::ckLastErrorText(ssh1)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    channel2.i = CkSsh::ckQuickCmdSend(ssh2,cmd)
    If channel2 < 0
        Debug CkSsh::ckLastErrorText(ssh2)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    channel3.i = CkSsh::ckQuickCmdSend(ssh3,cmd)
    If channel3 < 0
        Debug CkSsh::ckLastErrorText(ssh3)
        CkSsh::ckDispose(ssh1)
        CkSsh::ckDispose(ssh2)
        CkSsh::ckDispose(ssh3)
        ProcedureReturn
    EndIf

    ;  The command is now running on all three servers at once.  Poll each connection until every
    ;  command has finished.  (Production code would keep the objects in arrays rather than
    ;  repeating the logic.)
    pollTimeoutMs.i = 50
    numFinished.i = 0
    finished1.i = 0
    finished2.i = 0
    finished3.i = 0

    While numFinished < 3
        If Not finished1
            ch1.i = CkSsh::ckQuickCmdCheck(ssh1,pollTimeoutMs)
            If ch1 = -2
                Debug CkSsh::ckLastErrorText(ssh1)
                CkSsh::ckDispose(ssh1)
                CkSsh::ckDispose(ssh2)
                CkSsh::ckDispose(ssh3)
                ProcedureReturn
            EndIf

            If ch1 >= 0
                Debug "---- ssh1 channel " + Str(ch1) + " finished ----"
                out1.s = CkSsh::ckGetReceivedText(ssh1,ch1,"utf-8")
                If CkSsh::ckLastMethodSuccess(ssh1) = 0
                    Debug CkSsh::ckLastErrorText(ssh1)
                    CkSsh::ckDispose(ssh1)
                    CkSsh::ckDispose(ssh2)
                    CkSsh::ckDispose(ssh3)
                    ProcedureReturn
                EndIf

                Debug out1
                finished1 = 1
                numFinished = numFinished + 1
            EndIf

        EndIf

        If Not finished2
            ch2.i = CkSsh::ckQuickCmdCheck(ssh2,pollTimeoutMs)
            If ch2 = -2
                Debug CkSsh::ckLastErrorText(ssh2)
                CkSsh::ckDispose(ssh1)
                CkSsh::ckDispose(ssh2)
                CkSsh::ckDispose(ssh3)
                ProcedureReturn
            EndIf

            If ch2 >= 0
                Debug "---- ssh2 channel " + Str(ch2) + " finished ----"
                out2.s = CkSsh::ckGetReceivedText(ssh2,ch2,"utf-8")
                If CkSsh::ckLastMethodSuccess(ssh2) = 0
                    Debug CkSsh::ckLastErrorText(ssh2)
                    CkSsh::ckDispose(ssh1)
                    CkSsh::ckDispose(ssh2)
                    CkSsh::ckDispose(ssh3)
                    ProcedureReturn
                EndIf

                Debug out2
                finished2 = 1
                numFinished = numFinished + 1
            EndIf

        EndIf

        If Not finished3
            ch3.i = CkSsh::ckQuickCmdCheck(ssh3,pollTimeoutMs)
            If ch3 = -2
                Debug CkSsh::ckLastErrorText(ssh3)
                CkSsh::ckDispose(ssh1)
                CkSsh::ckDispose(ssh2)
                CkSsh::ckDispose(ssh3)
                ProcedureReturn
            EndIf

            If ch3 >= 0
                Debug "---- ssh3 channel " + Str(ch3) + " finished ----"
                out3.s = CkSsh::ckGetReceivedText(ssh3,ch3,"utf-8")
                If CkSsh::ckLastMethodSuccess(ssh3) = 0
                    Debug CkSsh::ckLastErrorText(ssh3)
                    CkSsh::ckDispose(ssh1)
                    CkSsh::ckDispose(ssh2)
                    CkSsh::ckDispose(ssh3)
                    ProcedureReturn
                EndIf

                Debug out3
                finished3 = 1
                numFinished = numFinished + 1
            EndIf

        EndIf

    Wend

    CkSsh::ckDisconnect(ssh1)
    CkSsh::ckDisconnect(ssh2)
    CkSsh::ckDisconnect(ssh3)


    CkSsh::ckDispose(ssh1)
    CkSsh::ckDispose(ssh2)
    CkSsh::ckDispose(ssh3)


    ProcedureReturn
EndProcedure