Legacy systems don't give up their data easily — especially when they're running inside Citrix or IE. This lesson teaches you how to build Power Automate Desktop flows that reliably extract data from virtual environments using layered selector strategies, OCR, image recognition, and production-grade session management.

Picture this: your team spends four hours every Monday morning logging into a Citrix-hosted legacy portal, pulling vendor invoice data screen by screen, and pasting it into Excel for reconciliation. Nobody likes doing it. The application is ancient, the Citrix session times out unpredictably, and the UI has no API. Management wants it automated, and they've handed you Power Automate Desktop.
This is not a toy problem. Citrix-hosted applications and Internet Explorer-era web apps represent a genuinely difficult class of automation targets. They don't expose clean DOM elements to the browser extension. They resist standard UI selectors because everything is rendered remotely and displayed as a stream of pixels. And they require a level of session management that simply clicking through a form doesn't need. Yet they also happen to be exactly the kind of system where RPA delivers the most dramatic ROI — because nobody has replaced them for decades, and nobody plans to.
By the end of this lesson, you'll know how to build desktop flows that reliably interact with Internet Explorer and Citrix-hosted applications. You'll understand when to use native selectors versus image recognition, how to manage IE and Citrix sessions so they don't drop mid-run, and how to extract structured data from environments where no direct programmatic access exists.
What you'll learn:
This lesson assumes you're comfortable with the Power Automate Desktop environment — you know how to create flows, place actions, and run them. You should have a working understanding of UI element capture and selector syntax, as covered in UI Elements and Selectors in Power Automate Desktop: Building Automations That Don't Break. Familiarity with error handling blocks is also helpful; if you want a refresher, see Error Handling in Desktop Flows: On Block Error, Retry Policies, and Recovery Screenshots.
You'll need:
Before writing a single action, it's worth being precise about what you're dealing with, because IE and Citrix present distinct challenges that require different solutions.
Internet Explorer support in Power Automate Desktop works through a dedicated browser extension and a set of IE-specific actions distinct from the Chrome/Edge/Firefox actions. The underlying mechanism is the same — PAD attaches to the browser's accessibility tree and sends commands via the extension — but the IE DOM is older, less standardized, and often built with technologies like ActiveX or VBScript that genuinely don't behave like modern HTML.
The practical consequences:
The web automation in Power Automate Desktop article covers the general browser action pattern well. Here we'll focus on what's unique to IE.
Citrix (and similar virtual desktop platforms like VMware Horizon or Amazon WorkSpaces) renders the remote desktop as a compressed video stream. From Power Automate Desktop's perspective, the Citrix window is opaque — there's no accessibility tree, no DOM, no structured element hierarchy. You're looking at pixels.
This means the standard "Attach to browser" or "Get UI element" approaches don't work inside the Citrix window. Everything inside the Citrix viewport must be automated through one of three mechanisms:
Each of these has a failure mode, which is why production Citrix automation typically combines all three in a layered strategy.
Key insight
The Citrix Workspace app itself is a native Windows application with a real accessibility tree. You can interact with the Citrix toolbar, the connection bar, and the session menu using standard UI selectors. What's inside the remote desktop viewport requires fallback techniques. Don't confuse the container with the content.
The most common beginner mistake with IE automation is using the wrong attachment strategy. You have two choices: launch a new IE instance yourself, or attach to an already-running one. For most enterprise scenarios, the application requires a specific authenticated session that can't be reproduced by simply navigating to a URL. So attaching to an existing instance is usually right.
Use the Attach to running Internet Explorer action. The key parameter is the URL match — use a partial URL pattern that uniquely identifies your target page without depending on session tokens in the query string. For example:
URL contains: vendorportal.corp.internal/invoices
Not:
URL equals: vendorportal.corp.internal/invoices?session=a1b2c3&user=jsmith
Session tokens change every login. A partial URL match survives that variability.
After attaching, always add a Wait for page to load action before attempting any element interactions. IE's rendering is slower than modern browsers, and the accessibility tree isn't fully populated until the page load event fires.
Legacy IE applications built with framesets require explicit frame navigation in your selectors. When you capture an element inside a frame using the UI element recorder, PAD should detect the frame context automatically — but sometimes it captures the element relative to the top-level page, which breaks when the frame URL changes between sessions.
Inspect the selector for any element inside a frame. It should look something like:
:root[Id="IE - Vendor Portal"]
> webPage[Url="vendorportal.corp.internal/invoices"]
> frame[Id="mainFrame"]
> input[Id="invoiceNumber"]
If the frame URL is dynamic (session-scoped), replace the Url attribute with a Name attribute match if the frame has a stable name attribute, or use Index as a fallback:
:root[Id="IE - Vendor Portal"]
> webPage[Url*="vendorportal.corp.internal"]
> frame[Index="0"]
> input[Id="invoiceNumber"]
The *= operator means "contains" — it's your friend for partial URL matching in selector attributes.
For data extraction, the Extract data from web page action with IE is powerful but requires configuration. When you launch the extraction wizard and hover over a table, PAD offers to extract the entire table structure. Accept this, but then verify what it produces.
For a vendor invoice table with columns: Invoice Number, Vendor, Amount, Due Date — PAD will generate a DataTable output. Immediately after extraction, write a sanity check:
# After Extract data from web page → InvoiceData (DataTable)
If InvoiceData.RowsCount = 0 Then
Log message: 'Warning: Extracted table has 0 rows. Page may not have loaded.'
# Retry logic here
End If
Zero-row extractions are the silent failure mode of web extraction. The action succeeds (no error) but returns nothing because the table wasn't loaded yet.
Tip
If your IE page paginates data across multiple pages with a "Next" button, wrap your extraction in a loop. Capture the selector for the "Next" button and for the disabled/hidden state it enters on the last page. Use a Loop with a condition that checks whether the button is visible and enabled before clicking it.
Reliable Citrix automation never depends on a single technique. Before writing any actions, map out your target workflows with this hierarchy:
Layer 1 — Keyboard-first. Keyboard shortcuts and Tab navigation work reliably in Citrix because keystrokes are transmitted as input events, not pixel commands. Wherever the application supports keyboard access — and most enterprise apps do — use it. Send keys to navigate menus, fill fields, and trigger actions. This is your most stable layer.
Layer 2 — Image recognition. For elements you can't reach by keyboard, use image-based clicks. Capture a template image of a stable UI landmark (a button label, a distinctive icon, a section header) and use Move mouse to image followed by Send mouse click to interact with it. Keep template images small and specific — a 40×20 pixel crop of just the button text is more reliable than a 200×150 screenshot of the whole panel.
Layer 3 — OCR extraction. For reading data, use OCR. Power Automate Desktop's OCR engine can extract text from a defined screen region. This is how you get actual values out of the Citrix viewport without any DOM access.
The Send keys action works seamlessly inside the Citrix window as long as focus is correctly placed. The critical step is ensuring the Citrix session window has focus before you send anything.
Use Focus window targeting the Citrix Workspace app window before any key sequence:
Focus window: [Citrix Workspace - VendorSystem]
Wait: 0.5 seconds
Send keys: {Tab}{Tab}{Tab} # Navigate to Invoice Number field
Wait: 0.3 seconds
Send keys: %InvoiceNumber% # Type the invoice number variable
Send keys: {Return}
The %VariableName% syntax injects variable values into the key sequence. Note that special characters in the variable value (like & or <) need escaping — wrap the variable in the Send keys action using the "Insert special key" menu rather than typing the variable directly if your data contains HTML-special characters.
Warning
Never rely on absolute screen coordinates for Citrix navigation. Resolution, DPI scaling, and window positioning can all shift the coordinate map. Always anchor your coordinates to a detected image or OCR result rather than hardcoding pixel values like (450, 320).
When you must click a button that has no keyboard equivalent, use the Move mouse to image on screen action. Here's the practical setup:
In the action sequence:
Move mouse to image on screen: [submit_invoice_button.png]
Tolerance: 0.80
Wait for image: Yes
Timeout: 10 seconds
→ ImageX, ImageY (output coordinates)
Send mouse click: Left click at current position
The Wait for image option is essential — it tells the action to keep searching until the image appears or the timeout expires, rather than failing immediately if the image isn't there. This gracefully handles the latency of Citrix rendering.
If the image fails to match, check whether Citrix's compression settings are degrading the image quality. High-compression sessions (common on VPN) can blur text in button labels enough to drop the match below threshold. In that case, target a graphic element (an icon with solid colors) rather than text, since solid colors survive compression better than rendered fonts.
Tip
Prefer images with high contrast and simple geometry as recognition anchors. A solid-color icon or a bordered button corner is more compression-resistant than a text label with anti-aliased edges.
This is the core skill for Citrix data extraction, and it pairs naturally with the OCR techniques covered in Extracting Text from PDFs, Images, and Scanned Documents with OCR in Power Automate Desktop. The difference here is that your source is a live screen region rather than a file.
Use the Extract text with OCR action with the From screen area source option. Define the region as a relative offset from the Citrix window position rather than an absolute coordinate — this way, if the user repositions the Citrix window, your region moves with it.
In practice:
Get window position: [Citrix Workspace - VendorSystem]
→ WindowX, WindowY
# Invoice Number field is 120px from left edge, 340px from top
Set variable: FieldX = WindowX + 120
Set variable: FieldY = WindowY + 340
Set variable: FieldWidth = 180
Set variable: FieldHeight = 22
Extract text with OCR:
Source: Screen area
X: %FieldX%
Y: %FieldY%
Width: %FieldWidth%
Height: %FieldHeight%
→ ExtractedText
OCR output is always a string. For numeric fields like invoice amounts, you'll need to parse and clean the result:
# Clean currency formatting: "$12,450.00" → 12450.00
Replace text: %ExtractedText% search: "$" replace: ""
Replace text: %ExtractedText% search: "," replace: ""
Convert text to number: %ExtractedText% → InvoiceAmount
For structured data across multiple rows (like a grid of invoices), loop through the rows by incrementing the Y coordinate by the row height:
Set variable: RowHeight = 18 # pixels per row
Set variable: CurrentRow = 0
Loop: %CurrentRow% < %TotalRows%
Set variable: CurrentY = WindowY + 340 + (CurrentRow * RowHeight)
# Extract each column field at fixed X offsets
Extract text with OCR: area at [WindowX+120, CurrentY, 180, 18] → InvoiceNum
Extract text with OCR: area at [WindowX+310, CurrentY, 200, 18] → VendorName
Extract text with OCR: area at [WindowX+520, CurrentY, 100, 18] → AmountText
# Append row to DataTable
Add row to DataTable: %InvoiceNum%, %VendorName%, %AmountText% → InvoiceData
Set variable: CurrentRow = CurrentRow + 1
End Loop
This pattern is essentially building your own table extractor from pixel coordinates. It's fragile if the layout changes, but it's often the only option.
Note
Citrix sessions at different DPI settings or screen resolutions will produce different pixel offsets. If your flow runs on multiple machines or the Citrix session resolution varies, detect the resolution at runtime and apply a scaling factor to your offsets, or normalize the Citrix window size programmatically before beginning extraction.
Session management is where many Citrix and IE automations fail in production — not because the UI interaction is wrong, but because the session expires mid-flow, the connection drops, or the application pops an idle timeout dialog that the flow doesn't handle.
IE sessions are governed by authentication cookies, which expire based on the application's session timeout policy. For flows that run longer than that timeout:
Pattern 1: Keep-alive actions. Every N minutes (based on the session timeout, usually 15-30 minutes), have the flow perform a lightweight page interaction — scroll, move the mouse, or click a non-destructive element. This resets the inactivity timer.
Pattern 2: Re-authentication subflow. Build a subflow that handles the full login sequence. Check for the login page before each major operation using a Get web page element action — if the login form is present, call the re-authentication subflow. This is described in Subflows and Reusable Logic in Power Automate Desktop, and the pattern applies directly here.
# Check for session expiry before each extraction batch
If element exists: [login_form_selector]
Call subflow: IE_ReAuthenticate
End If
Store credentials using sensitive variables or Key Vault integration rather than hardcoding them — see Handling Credentials Securely in Desktop Flows: Sensitive Variables and Azure Key Vault for the right approach.
Citrix session management is more complex because you're managing two layers: the Citrix protocol session (managed by Workspace app) and the application session running inside it.
Preventing Citrix disconnection: Citrix servers disconnect idle sessions based on server-side policy, typically 15-60 minutes of inactivity. Your flow can avoid triggering the idle timer by sending a no-op keystroke periodically:
# At the start of long processing loops, reset the Citrix idle timer
Focus window: [Citrix Workspace - VendorSystem]
Send keys: {Shift} # Shift alone doesn't change anything, but registers activity
Detecting and recovering from disconnection: Build a detection step that runs at the top of every major loop iteration. If the Citrix window is gone (disconnected), attempt to reconnect:
On block error:
Subflow: Citrix_Reconnect
Retry action
# Citrix_Reconnect subflow:
If window not exists: [Citrix Workspace - VendorSystem]
Launch application: CitrixWorkspace.exe with URL [citrix://VendorSystem]
Wait for window: [Citrix Workspace - VendorSystem] timeout: 60s
Wait: 10 seconds # Let session restore
Call subflow: App_NavigateToLastPosition
End If
Handling in-application timeout dialogs: Many enterprise applications running inside Citrix show their own "You have been idle for X minutes. Click OK to continue" dialogs. Use image recognition to detect this dialog's appearance and dismiss it:
# Check for idle warning dialog (runs periodically or at start of each action)
If image exists on screen: [idle_warning_dialog.png]
Move mouse to image: [ok_button_in_dialog.png]
Send mouse click: Left click
Wait: 1 second
End If
Wrap this detection into a subflow and call it at the beginning of each major workflow section. This is much cleaner than sprinkling the check everywhere.
Both IE and Citrix environments have failure modes that don't occur in clean modern web automation. The flow that works in testing will encounter at least three of these in its first week of production.
Pages load slowly. Citrix rendering lags. UI elements appear after delays. The fix is patience, but intelligent patience:
# Don't just Wait 5 seconds. Wait for the specific condition.
Loop:
If element exists: [data_grid_selector]
Break
End If
Wait: 1 second
End Loop
For Citrix specifically, "wait for image" with a meaningful timeout beats arbitrary waits. If you're waiting for a results screen to load, wait for the image of the first column header to appear:
Move mouse to image on screen: [results_header.png]
Wait for image: Yes
Timeout: 30 seconds
If this times out, you know definitively that the screen hasn't loaded — which is an actionable error, not a silent wrong-data extraction.
If your Citrix data extraction loop crashes midway through 500 rows, you don't want to restart from row 1. Build resumable extraction by writing your progress to a file:
# Before each row extraction:
Write text to file: %CurrentRow% → "citrix_extraction_progress.txt"
# At flow startup:
If file exists: "citrix_extraction_progress.txt"
Read text from file: → ResumeRow
Set variable: StartRow = Convert.ToInteger(ResumeRow)
Else
Set variable: StartRow = 0
End If
This pattern, combined with appending extracted rows incrementally to Excel rather than building in memory and writing at the end, makes your flow restartable without data loss. The approach of writing data progressively to Excel is covered in more depth in Automating Excel with Power Automate Desktop: Reading, Writing, and Running Macros.
IE pages sometimes change element IDs between sessions (dynamically generated IDs like ctrl_ctl00_form1_gridview1_row3_col2). When your selector breaks because the ID changed, shift to a more structural selector:
Build a selector that uses multiple attributes in combination rather than a single volatile attribute:
# Fragile — depends on generated ID:
webPage > table[Id="ctl00_form1_GridView1"] > tr[Index="3"] > td[Index="2"]
# More robust — uses visible text and position:
webPage > table[ClassName~="invoice-grid"] > tr > td[OwnText="INV-2024-0891"]
The OwnText attribute matches the visible text of the element. For a cell containing a specific invoice number, this is far more stable than a generated control ID.
Warning
OwnText selectors work well for static labels but are a terrible choice for cells whose content changes every run (like amounts or dates). For those, navigate via a stable anchor element and use Index-based traversal to get the adjacent cell.
Let's build a complete, production-pattern flow. The scenario: a vendor invoice portal runs inside Citrix, showing 20 rows of invoices per screen across multiple pages. You need to extract all invoices for the current month and write them to Excel.
CitrixWindowTitle (text), OutputExcelPath (text), TargetMonth (text, format "YYYY-MM")InvoiceData with columns: InvoiceNum, Vendor, Amount, DueDate, Status# Subflow: Connect_And_Navigate
Focus window: %CitrixWindowTitle%
Wait: 2 seconds
# Navigate to invoice search via keyboard
Send keys: {Alt}f # Open File menu (or application-specific shortcut)
Wait: 0.5 seconds
Send keys: {Alt}i # Invoices module
Wait: 1 second
# Wait for invoice search screen
Move mouse to image on screen: [invoice_search_header.png]
Wait for image: Yes
Timeout: 20 seconds
# Enter month filter
Send keys: {Tab}{Tab} # Navigate to Month filter field
Send keys: %TargetMonth%
Send keys: {Return}
Wait: 2 seconds
# Get Citrix window position for relative coordinate calculation
Get window position: %CitrixWindowTitle%
→ WinX, WinY
Set variable: MorePages = True
Set variable: TotalRowsExtracted = 0
Loop while: %MorePages% = True
# Check for and dismiss any idle warning
Call subflow: Check_Idle_Warning
# Detect number of rows on current page
# (Use OCR on the "Showing X-Y of Z" status bar)
Extract text with OCR: area at [WinX+600, WinY+520, 200, 20]
→ PageStatusText
# Parse "Showing 1-20 of 147" → extract 20 (rows on this page)
# Use regex or text manipulation
Get subtext: %PageStatusText% start after "Showing" end before "-"
→ RowStart
Get subtext: %PageStatusText% start after "-" end before "of"
→ RowEnd
Set variable: RowsThisPage = Convert.ToInteger(RowEnd) - Convert.ToInteger(RowStart) + 1
# Extract each row
Set variable: RowIndex = 0
Loop while: %RowIndex% < %RowsThisPage%
Set variable: RowY = WinY + 180 + (RowIndex * 18)
Extract text with OCR: area at [WinX+50, RowY, 120, 16] → InvNum
Extract text with OCR: area at [WinX+180, RowY, 180, 16] → Vendor
Extract text with OCR: area at [WinX+370, RowY, 90, 16] → Amount
Extract text with OCR: area at [WinX+470, RowY, 90, 16] → DueDate
Extract text with OCR: area at [WinX+570, RowY, 80, 16] → Status
Add row to DataTable: %InvoiceData%
Values: %InvNum%, %Vendor%, %Amount%, %DueDate%, %Status%
Set variable: RowIndex = RowIndex + 1
Set variable: TotalRowsExtracted = TotalRowsExtracted + 1
End Loop
# Save progress checkpoint
Write text to file: %TotalRowsExtracted% → "extraction_progress.txt"
# Check for Next Page button
If image exists on screen: [next_page_active.png]
Move mouse to image: [next_page_active.png]
Send mouse click: Left click
Wait: 2 seconds
Else
Set variable: MorePages = False
End If
End Loop
Launch Excel: %OutputExcelPath%
Set active Excel worksheet: "Invoices"
# Write DataTable to Excel starting at A2 (row 1 has headers)
Write DataTable to Excel: %InvoiceData%
Starting cell: A2
Include headers: No # Headers already in row 1
Save Excel
Close Excel
For writing DataTables to Excel with full control over headers, formatting, and sheet selection, see Working with Excel Ranges, Sheets, and Named Cells in Power Automate Desktop.
Wrap the entire extraction loop in an On block error handler:
On block error:
# Take screenshot for diagnostics
Take screenshot: → ErrorScreenshot
Save screenshot: "error_%CurrentDateTime%.png"
# Log error details
Write text to file: "Error at row %TotalRowsExtracted%: %LastError%"
→ "extraction_errors.txt"
# Attempt recovery
Call subflow: Citrix_Reconnect
# Resume from last checkpoint
Read text from file: "extraction_progress.txt" → ResumeFrom
# Continue loop (not restart from 0)
This error pattern draws on the recovery screenshot technique described in Error Handling in Desktop Flows: On Block Error, Retry Policies, and Recovery Screenshots.
The IE page is visually present, but PAD can't find the element. Causes and fixes:
frame step.For unattended automation, the machine running PAD must have an active desktop session — it cannot run in a locked or disconnected state, because Citrix Workspace needs a visible desktop to render its window. This is a fundamental constraint of Citrix automation. See Attended vs Unattended RPA: Choosing a Run Mode and Configuring Machines in Power Automate for the implications on your machine configuration.
The common workaround is to use a dedicated virtual machine that stays logged in, with the screen saver disabled and Windows session lock disabled via Group Policy.
Warning
Never configure a machine for unattended Citrix automation by disabling the screen lock through personal account settings — this creates a security exposure. Work with your IT team to apply a targeted Group Policy Object that allows the automation service account to run without screen locking while keeping the policy enforced for all other users.
The most frequent cause: your development environment uses IE 11 but production uses Edge in IE Compatibility Mode (or vice versa). These behave differently in PAD — "Attach to Internet Explorer" connects to actual IE.exe processes, while Edge's IE Mode runs inside an Edge process and needs the Edge browser actions instead.
Verify which mode your production environment uses before writing a single selector.
Automating IE and Citrix applications is genuinely hard, but the difficulty is bounded. The core of what you've learned here is a framework for working within the constraints of these environments rather than fighting them.
For IE, the key moves are: attach to existing sessions rather than launching fresh, build selectors that survive session variability using partial matches and structural traversal, handle frames explicitly, and check for zero-row extractions as a silent failure mode.
For Citrix, the key moves are: use keyboard-first automation where possible, layer image recognition and OCR for everything else, anchor all coordinates to the window position rather than the screen, manage both the Citrix protocol session and the application session independently, and build recovery logic that checkpoints progress so failures don't mean starting over.
The session management patterns — periodic keep-alive actions, session state detection before each operation, and credential-handling via secure variables — apply to both environments and are what separate flows that run in testing from flows that run reliably in production for months.
Where to go from here:
The automation you build here isn't glamorous, but it's valuable. These legacy systems aren't going anywhere, and every hour of manual data extraction you eliminate is a direct, measurable win.
Power Automate Desktop & RPA