Back to Blog
6 min read

RPA with Azure: Scaling Robotic Process Automation in the Cloud

Combining Power Automate Desktop with Azure infrastructure enables enterprise-scale RPA. This guide covers architecture patterns, deployment strategies, and best practices for cloud-powered robotic process automation.

Enterprise RPA Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Azure Cloud                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   Power     │  │   Azure     │  │    Azure Storage    │  │
│  │  Automate   │──│  Service    │──│    (Queues/Blob)    │  │
│  │   Cloud     │  │    Bus      │  │                     │  │
│  └──────┬──────┘  └─────────────┘  └─────────────────────┘  │
│         │                                                    │
│  ┌──────┴──────────────────────────────────────────────┐    │
│  │              On-premises Data Gateway                │    │
│  └──────────────────────┬───────────────────────────────┘    │
└─────────────────────────┼───────────────────────────────────┘

┌─────────────────────────┼───────────────────────────────────┐
│                 Bot Farm (Azure VMs)                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │
│  │  Bot 1   │  │  Bot 2   │  │  Bot 3   │  │  Bot N   │    │
│  │ (VM)     │  │ (VM)     │  │ (VM)     │  │ (VM)     │    │
│  │ PAD      │  │ PAD      │  │ PAD      │  │ PAD      │    │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘    │
└─────────────────────────────────────────────────────────────┘

Setting Up Bot Machines

Azure VM Configuration

# Create VM for RPA bot
az vm create \
    --resource-group rpa-rg \
    --name rpa-bot-01 \
    --image Win2022Datacenter \
    --size Standard_D4s_v3 \
    --admin-username rpaadmin \
    --admin-password $RPA_ADMIN_PASSWORD \
    --nsg rpa-nsg

# Enable auto-shutdown for cost optimization
az vm auto-shutdown \
    --resource-group rpa-rg \
    --name rpa-bot-01 \
    --time 2200 \
    --timezone "Eastern Standard Time"

Terraform Infrastructure

resource "azurerm_windows_virtual_machine" "rpa_bot" {
  count               = var.bot_count
  name                = "rpa-bot-${count.index + 1}"
  resource_group_name = azurerm_resource_group.rpa.name
  location            = azurerm_resource_group.rpa.location
  size                = "Standard_D4s_v3"
  admin_username      = var.admin_username
  admin_password      = var.admin_password

  network_interface_ids = [
    azurerm_network_interface.rpa_bot[count.index].id
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-Datacenter"
    version   = "latest"
  }

  identity {
    type = "SystemAssigned"
  }

  tags = {
    purpose = "rpa-bot"
    index   = count.index + 1
  }
}

# Install Power Automate Desktop via extension
resource "azurerm_virtual_machine_extension" "pad_install" {
  count                = var.bot_count
  name                 = "install-pad"
  virtual_machine_id   = azurerm_windows_virtual_machine.rpa_bot[count.index].id
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.10"

  settings = jsonencode({
    commandToExecute = "powershell -ExecutionPolicy Unrestricted -File install-pad.ps1"
  })
}

VM Setup Script

# install-pad.ps1
# Configure VM for RPA

# Install Power Automate Desktop
$padInstaller = "https://go.microsoft.com/fwlink/?linkid=2102613"
Invoke-WebRequest -Uri $padInstaller -OutFile "$env:TEMP\Setup.Microsoft.PowerAutomate.exe"
Start-Process "$env:TEMP\Setup.Microsoft.PowerAutomate.exe" -ArgumentList "/quiet /norestart" -Wait

# Install required applications
# Chrome for web automation
$chromeInstaller = "https://dl.google.com/chrome/install/GoogleChromeStandaloneEnterprise64.msi"
Invoke-WebRequest -Uri $chromeInstaller -OutFile "$env:TEMP\chrome.msi"
Start-Process msiexec.exe -ArgumentList "/i $env:TEMP\chrome.msi /quiet /norestart" -Wait

# Configure auto-logon for unattended bots
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
Set-ItemProperty -Path $regPath -Name "AutoAdminLogon" -Value "1"
Set-ItemProperty -Path $regPath -Name "DefaultUserName" -Value $env:RPA_USER
Set-ItemProperty -Path $regPath -Name "DefaultPassword" -Value $env:RPA_PASSWORD

# Disable screen lock
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Personalization" /v NoLockScreen /t REG_DWORD /d 1 /f

# Set screen resolution for consistent automation
Set-DisplayResolution -Width 1920 -Height 1080 -Force

Write-Host "RPA bot setup complete"

Queue-Based Processing

Azure Service Bus Queue

# Queue processor for RPA work items
from azure.servicebus import ServiceBusClient
import json

connection_str = os.environ["SERVICEBUS_CONNECTION_STRING"]
queue_name = "rpa-work-items"

def process_queue():
    with ServiceBusClient.from_connection_string(connection_str) as client:
        with client.get_queue_receiver(queue_name) as receiver:
            for msg in receiver:
                try:
                    work_item = json.loads(str(msg))

                    # Trigger desktop flow via Power Automate API
                    result = trigger_desktop_flow(
                        flow_id=work_item['flowId'],
                        inputs=work_item['inputs']
                    )

                    # Complete message on success
                    receiver.complete_message(msg)

                except Exception as e:
                    # Dead-letter on failure
                    receiver.dead_letter_message(msg, reason=str(e))

Cloud Flow Orchestrator

{
    "definition": {
        "triggers": {
            "When_message_received": {
                "type": "ApiConnection",
                "inputs": {
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['servicebus']['connectionId']"
                        }
                    },
                    "method": "get",
                    "path": "/@{encodeURIComponent('rpa-work-items')}/messages/head"
                },
                "recurrence": {
                    "frequency": "Minute",
                    "interval": 1
                }
            }
        },
        "actions": {
            "Parse_Message": {
                "type": "ParseJson",
                "inputs": {
                    "content": "@base64ToString(triggerBody()?['ContentData'])",
                    "schema": {
                        "type": "object",
                        "properties": {
                            "workItemId": {"type": "string"},
                            "processType": {"type": "string"},
                            "inputData": {"type": "object"}
                        }
                    }
                }
            },
            "Route_To_Bot": {
                "type": "Switch",
                "expression": "@body('Parse_Message')?['processType']",
                "cases": {
                    "Invoice_Processing": {
                        "actions": {
                            "Run_Invoice_Bot": {
                                "type": "ApiConnection",
                                "inputs": {
                                    "method": "post",
                                    "path": "/flows/@{encodeURIComponent('invoice-processing-flow')}/runs"
                                }
                            }
                        }
                    },
                    "Data_Entry": {
                        "actions": {
                            "Run_Data_Entry_Bot": {
                                "type": "ApiConnection",
                                "inputs": {
                                    "method": "post",
                                    "path": "/flows/@{encodeURIComponent('data-entry-flow')}/runs"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Monitoring and Observability

Application Insights Integration

# In desktop flow - send telemetry
$appInsightsKey = $env:APPINSIGHTS_INSTRUMENTATIONKEY

function Send-Telemetry {
    param(
        [string]$EventName,
        [hashtable]$Properties
    )

    $telemetry = @{
        name = "Microsoft.ApplicationInsights.$appInsightsKey.Event"
        time = (Get-Date).ToUniversalTime().ToString("o")
        iKey = $appInsightsKey
        data = @{
            baseType = "EventData"
            baseData = @{
                name = $EventName
                properties = $Properties
            }
        }
    }

    $body = $telemetry | ConvertTo-Json -Depth 10
    Invoke-RestMethod -Uri "https://dc.services.visualstudio.com/v2/track" -Method Post -Body $body
}

# Usage
Send-Telemetry -EventName "InvoiceProcessed" -Properties @{
    InvoiceNumber = $invoiceNumber
    Amount = $amount
    ProcessingTime = $processingTime
    BotName = $env:COMPUTERNAME
}

Power Automate Analytics

-- Query flow run history
SELECT
    flow_name,
    run_status,
    COUNT(*) as run_count,
    AVG(duration_seconds) as avg_duration,
    SUM(CASE WHEN run_status = 'Failed' THEN 1 ELSE 0 END) as failures
FROM flow_runs
WHERE run_date >= DATEADD(day, -7, GETDATE())
GROUP BY flow_name, run_status
ORDER BY run_count DESC

Error Recovery

# Robust desktop flow with recovery

BLOCK 'Main Process'
ON BLOCK ERROR
    # Capture state
    System.TakeScreenshot File: 'C:\Logs\error_%DateTime%.png'

    # Log error details
    File.WriteText File: 'C:\Logs\error_%DateTime%.txt' Text: '%LastError%'

    # Try recovery
    IF %AttemptCount% < 3 THEN
        # Reset application state
        System.KillProcess ProcessName: 'targetapp'
        Wait Seconds: 5

        # Retry
        Variables.IncreaseVariable Name: 'AttemptCount' Value: 1
        GOTO 'Main Process'
    ELSE
        # Escalate
        HTTP.Post Url: '%WebhookUrl%' Body: '{"error": "%LastError%", "bot": "%MachineName%"}'
    END
END
    # Main processing logic
    # ...
END

Scaling Strategies

Horizontal Scaling

# Auto-scaling VM scale set
resource "azurerm_virtual_machine_scale_set" "rpa_bots" {
  name                = "rpa-bot-vmss"
  location            = azurerm_resource_group.rpa.location
  resource_group_name = azurerm_resource_group.rpa.name

  sku {
    name     = "Standard_D4s_v3"
    tier     = "Standard"
    capacity = var.initial_bot_count
  }

  # Scale based on queue depth
  automatic_os_upgrade = false

  os_profile_windows_config {
    provision_vm_agent = true
  }
}

resource "azurerm_monitor_autoscale_setting" "rpa_scale" {
  name                = "rpa-autoscale"
  resource_group_name = azurerm_resource_group.rpa.name
  location            = azurerm_resource_group.rpa.location
  target_resource_id  = azurerm_virtual_machine_scale_set.rpa_bots.id

  profile {
    name = "default"

    capacity {
      default = var.initial_bot_count
      minimum = 1
      maximum = var.max_bot_count
    }

    rule {
      metric_trigger {
        metric_name        = "MessageCount"
        metric_resource_id = azurerm_servicebus_queue.rpa_queue.id
        operator           = "GreaterThan"
        threshold          = 100
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
      }

      scale_action {
        direction = "Increase"
        type      = "ChangeCount"
        value     = "2"
        cooldown  = "PT10M"
      }
    }
  }
}

Best Practices

enterprise_rpa:
  security:
    - Use managed identities for Azure resources
    - Store credentials in Key Vault
    - Enable audit logging
    - Implement least privilege access

  reliability:
    - Design for idempotency
    - Implement retry logic
    - Use dead-letter queues
    - Monitor bot health

  cost_optimization:
    - Auto-shutdown non-production bots
    - Right-size VMs based on workload
    - Use spot instances for batch processing
    - Consolidate light workloads

  operations:
    - Centralize logging
    - Create runbooks for common issues
    - Document bot dependencies
    - Plan for application updates

Conclusion

Cloud-powered RPA with Azure provides:

  • Scalable bot infrastructure
  • Queue-based work distribution
  • Centralized monitoring
  • Enterprise-grade security

Combining Power Automate Desktop with Azure services enables organizations to scale RPA from departmental to enterprise-wide automation.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.