another technical blog...technically

Thursday, August 15, 2019

Morning routine with BluePrism and PowerShell

If you have deployed lots/tons of robots, you know that maintenance is the big elephant in the room.
Blue Prism scheduler is not so "friendly" and sometimes is not so responsive.

Lately I implemented some warmup scripts that simply restart SQL Server service at 00.00 AM and Blue Prism application server at 1:00AM. Resource PCs are rebooted as well at 2:00 AM.
Resource PCs are scheduled to start working at 3:00 AM even if nobody is at the office, but the problem come when the first colleague start to work at 7:00 AM.

Imagine you have something like 40 machines and you have to connect to each one and check if the schedule has started correctly... i know, it's a bit boring task.
The script i will show you is made of different subscript, according to your scenario you can use split it in more pieces.

Please note that in out project we have very complex schedule, every machine need to run different processes at different hours, with different cut-offs.
For every machine we have two schedule:
  1. The one with include login and then alternates different processes (eg. MACHINE01)
  2. Equal to the previous one without login (e.g. MACHINE01.Backup)
The script knows what machines are involved and their respective Backup schedule.
So at first, open as many RDP sessions as the involved machines, when all machines are visible and tiled on the screen, it starts to check (using telnet) what machines are running something.
If the machine running the script is equipped with Blue Prism, it will try lo launch the schedule using AutomateC.exe.

The code is quite self-explaining, so please have a look below and enjoy

# BEGIN Configs
$PATH_BUFFERFILE = "C:\temp\roboot.txt"
$PATH_AUTOMATEC = "C:\Program Files\Blue Prism Limited\Blue Prism Automate\AutomateC.exe"
$PATH_RDPFILES = "P:\Users\Varro\Desktop\RDP\"

$SCEHDULE_LASTCHECK = '20:30'

$RESOURCEPC_PORT = 8182
$RESOURCEPC_SCHEDULES = @{}
$RESOURCEPC_SCHEDULES.Add('MACHINE01_HOSTNAME','MACHINE01.Backup')
$RESOURCEPC_SCHEDULES.Add('MACHINE02_HOSTNAME','MACHINE02.Backup')
$RESOURCEPC_SCHEDULES.Add('MACHINE03_HOSTNAME','MACHINE03.Backup')
$RESOURCEPC_SCHEDULES.Add('MACHINE04_HOSTNAME','MACHINE04.Backup')
$RESOURCEPC_SCHEDULES.Add('MACHINE05_HOSTNAME','MACHINE05.Backup')
# END Configs

# BEGIN - Functions written by someone smarter than me
Function Show-Process($Process, [Switch]$Maximize)
{
  $sig = '
    [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
    [DllImport("user32.dll")] public static extern int SetForegroundWindow(IntPtr hwnd);
  '
  
  if ($Maximize) { $Mode = 3 } else { $Mode = 4 }
  $type = Add-Type -MemberDefinition $sig -Name WindowAPI -PassThru
  $hwnd = $process.MainWindowHandle
  $null = $type::ShowWindowAsync($hwnd, $Mode)
  $null = $type::SetForegroundWindow($hwnd) 
}

Function Get-Telnet
{   Param (
        [Parameter(ValueFromPipeline=$true)]
        [String[]]$Commands = @("username","password","disable clipaging","sh config"),
        [string]$RemoteHost = "HostnameOrIPAddress",
        [string]$Port = "23",
        [int]$WaitTime = 1000,
        [string]$OutputPath = "\\server\share\switchbackup.txt"
    )
    #Attach to the remote device, setup streaming requirements
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024 
        $Encoding = New-Object System.Text.AsciiEncoding

        #Now start issuing the commands
        ForEach ($Command in $Commands)
        {   $Writer.WriteLine($Command) 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
        }
        #All commands issued, but since the last command is usually going to be
        #the longest let's wait a little longer for it to finish
        Start-Sleep -Milliseconds ($WaitTime * 4)
        $Result = ""
        #Save all the results
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $Result += ($Encoding.GetString($Buffer, 0, $Read))
        }
    }
    Else     
    {   $Result = "Unable to connect to host: $($RemoteHost):$Port"
    }
    #Done, now save the results to a file
    $Result | Out-File $OutputPath
    return $Result
}
# END - Functions written by someone smarter than me

# MAIN
# 1. Close all RDP session
Get-Process | Where-Object { $_.Path -like "*mstsc*" } | Stop-Process -Force

# 2. Wait all RDP sessions are closed
Do {
    $rdpSessions = Get-Process | Where-Object { $_.Path -like "*mstsc*" }
    Start-Sleep -s 1
} While ($rdpSessions.Count -ne 0)

#3. Open RDP sessions
ForEach ($resourcePc in $RESOURCEPC_SCHEDULES.Keys) {   
    $arg = $PATH_RDPFILES + $resourcePc + ".rdp"
    Start-Process "mstsc" -ArgumentList """$arg"""
    Start-Sleep -s 1
}

#4. Wait all RDP sessions are opened
Do {
    $rdpSessions = Get-Process | Where-Object { $_.Path -like "*mstsc*" }
    Start-Sleep -s 2
} While ($rdpSessions.Count -ne $RESOURCEPC_SCHEDULES.Keys.Count)


#5. Wait to be logged in every RDP session the script opened, then click enter or something else
$process = Get-Process -Id $PID
Write-Host $process
Show-Process -Process $process -Maximize

$key = Read-Host "Press ENTER key when all are connected"

#6. Tile all RDP sessions vertically
$ShelleExp = New-Object -ComObject Shell.Application
$ShelleExp.TileVertically()

#7. Just deciding what to do according to the hour of the day (don't start if current time > 8.30 in this case or on weekend days)
$now = (Get-Date)
$nowDay = $now.DayOfWeek.value__
Write-Host "Today is $nowDay - $now.TimeOfDay"

if ($now.TimeOfDay -gt $SCEHDULE_LASTCHECK -And $nowDay -ne 6 -And $nowday -ne 7)
{
    Write-Host "Too late... maybe tomorrow" -BackgroundColor Red -ForegroundColor White
} 
else 
{

    #8. Checking if machines are working (using telnet to get those data)
    [System.Collections.ArrayList]$resourcePC_Problematic = @()
    foreach ($resourcePc in $RESOURCEPC_SCHEDULES.Keys)
    {
        Write-Host "Check $resourcePc machine" -ForegroundColor Yellow
        # Remove buffer file, call telnet and get message
        Remove-Item $PATH_BUFFERFILE -ErrorAction Ignore
        $telnetContent = Get-Telnet -RemoteHost $resourcePc -Port $RESOURCEPC_PORT -Commands "status" -OutputPath "$PATH_BUFFERFILE"
        Write-Host $telnetContent -ForegroundColor Yellow
        
        #9a. If contains running, something is running, so move on
        if ($telnetContent.Contains("RUNNING")) 
        { 
            Write-Host "$resourcePc is working" -ForegroundColor Green
        }
        #9b. If does not contains running, time to run the schedule
        else
        {
            $scheduleName = $resourcePc_Schedulazioni[$resourcePc]
            Write-Host "Run now scheduled task $scheduleName on $resourcePc" -BackgroundColor Red -ForegroundColor White
            $cmd = "cmd.exe /C ""$PATH_AUTOMATEC"" /sso /startschedule /schedule $scheduleName"
            Invoke-Expression -Command:$cmd
        }
        
        #9c. Clean it up
        Remove-Item $PATH_BUFFERFILE
        Write-Host 
        Write-Host 
    }

    #10. Check again all machines to check are running something, if not, write who's doing nothing
    foreach ($resourcePc in $RESOURCEPC_SCHEDULES.Keys)
    {
        Remove-Item $PATH_BUFFERFILE -ErrorAction Ignore
        $telnetContent = Get-Telnet -RemoteHost $resourcePc -Port $RESOURCEPC_PORT -Commands "status" -OutputPath "$PATH_BUFFERFILE"
        if (!$telnetContent.Contains("RUNNING")) 
        { 
            $resourcePC_Problematic.Add($resourcePc)
        }
    }

    Write-Host "Resource PC not booted: $resourcePC_Problematic" -BackgroundColor Red -ForegroundColor White
}


Read-Host "Premere ENTER to exit"
Share:

Me, myself and I

My Photo
I'm just another IT guy sharing his knowledge with all of you out there.
Wanna know more?