Sample code for 30+ languages & platforms
Xbase++

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 Xbase++ Downloads

Xbase++
LOCAL nSuccess
LOCAL oSsh1
LOCAL oSsh2
LOCAL oSsh3
LOCAL nPort
LOCAL cPassword
LOCAL cCmd
LOCAL nChannel1
LOCAL nChannel2
LOCAL nChannel3
LOCAL nPollTimeoutMs
LOCAL nNumFinished
LOCAL nFinished1
LOCAL nFinished2
LOCAL nFinished3
LOCAL nCh1
LOCAL cOut1
LOCAL nCh2
LOCAL cOut2
LOCAL nCh3
LOCAL cOut3

nSuccess := 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.

oSsh1 := CreateObject("Chilkat.Ssh")
oSsh2 := CreateObject("Chilkat.Ssh")
oSsh3 := CreateObject("Chilkat.Ssh")

nPort := 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.
cPassword := "mySshPassword"

nSuccess := oSsh1:Connect("ssh-server1.example.com", nPort)
IF (nSuccess == 0)
    ? oSsh1:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nSuccess := oSsh1:AuthenticatePw("mySshLogin", cPassword)
IF (nSuccess == 0)
    ? oSsh1:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nSuccess := oSsh2:Connect("ssh-server2.example.com", nPort)
IF (nSuccess == 0)
    ? oSsh2:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nSuccess := oSsh2:AuthenticatePw("mySshLogin", cPassword)
IF (nSuccess == 0)
    ? oSsh2:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nSuccess := oSsh3:Connect("ssh-server3.example.com", nPort)
IF (nSuccess == 0)
    ? oSsh3:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nSuccess := oSsh3:AuthenticatePw("mySshLogin", cPassword)
IF (nSuccess == 0)
    ? oSsh3:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

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

//  Start the command on each server.  QuickCmdSend returns immediately.
nChannel1 := oSsh1:QuickCmdSend(cCmd)
IF (nChannel1 < 0)
    ? oSsh1:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nChannel2 := oSsh2:QuickCmdSend(cCmd)
IF (nChannel2 < 0)
    ? oSsh2:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
ENDIF

nChannel3 := oSsh3:QuickCmdSend(cCmd)
IF (nChannel3 < 0)
    ? oSsh3:LastErrorText
    oSsh1:destroy()
    oSsh2:destroy()
    oSsh3:destroy()
    RETURN
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.)
nPollTimeoutMs := 50
nNumFinished := 0
nFinished1 := 0
nFinished2 := 0
nFinished3 := 0

DO WHILE nNumFinished < 3
    IF (.NOT. nFinished1)
        nCh1 := oSsh1:QuickCmdCheck(nPollTimeoutMs)
        IF (nCh1 == -2)
            ? oSsh1:LastErrorText
            oSsh1:destroy()
            oSsh2:destroy()
            oSsh3:destroy()
            RETURN
        ENDIF

        IF (nCh1 >= 0)
            ? "---- ssh1 channel " + Str(nCh1) + " finished ----"
            cOut1 := oSsh1:GetReceivedText(nCh1, "utf-8")
            IF (oSsh1:LastMethodSuccess == 0)
                ? oSsh1:LastErrorText
                oSsh1:destroy()
                oSsh2:destroy()
                oSsh3:destroy()
                RETURN
            ENDIF

            ? cOut1
            nFinished1 := 1
            nNumFinished := nNumFinished + 1
        ENDIF

    ENDIF

    IF (.NOT. nFinished2)
        nCh2 := oSsh2:QuickCmdCheck(nPollTimeoutMs)
        IF (nCh2 == -2)
            ? oSsh2:LastErrorText
            oSsh1:destroy()
            oSsh2:destroy()
            oSsh3:destroy()
            RETURN
        ENDIF

        IF (nCh2 >= 0)
            ? "---- ssh2 channel " + Str(nCh2) + " finished ----"
            cOut2 := oSsh2:GetReceivedText(nCh2, "utf-8")
            IF (oSsh2:LastMethodSuccess == 0)
                ? oSsh2:LastErrorText
                oSsh1:destroy()
                oSsh2:destroy()
                oSsh3:destroy()
                RETURN
            ENDIF

            ? cOut2
            nFinished2 := 1
            nNumFinished := nNumFinished + 1
        ENDIF

    ENDIF

    IF (.NOT. nFinished3)
        nCh3 := oSsh3:QuickCmdCheck(nPollTimeoutMs)
        IF (nCh3 == -2)
            ? oSsh3:LastErrorText
            oSsh1:destroy()
            oSsh2:destroy()
            oSsh3:destroy()
            RETURN
        ENDIF

        IF (nCh3 >= 0)
            ? "---- ssh3 channel " + Str(nCh3) + " finished ----"
            cOut3 := oSsh3:GetReceivedText(nCh3, "utf-8")
            IF (oSsh3:LastMethodSuccess == 0)
                ? oSsh3:LastErrorText
                oSsh1:destroy()
                oSsh2:destroy()
                oSsh3:destroy()
                RETURN
            ENDIF

            ? cOut3
            nFinished3 := 1
            nNumFinished := nNumFinished + 1
        ENDIF

    ENDIF

ENDDO

oSsh1:Disconnect()
oSsh2:Disconnect()
oSsh3:Disconnect()

oSsh1:destroy()
oSsh2:destroy()
oSsh3:destroy()