Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power Automate

Automating File, Folder, and Email Operations with Power Automate Desktop

Master production-grade file and email automation in Power Automate Desktop. Learn how to build reliable pipelines that process incoming files, manage dynamic folder structures, read and send Outlook emails with attachments, and wire everything together into a robust, self-monitoring bot.

🔥 Expert27 min readSep 22, 2026Updated Sep 27, 2026
Automating File, Folder, and Email Operations with Power Automate Desktop
On this page
  • Introduction
  • Prerequisites
  • How Power Automate Desktop Models Files and Folders
  • Getting, Filtering, and Iterating Files
  • The Get Files in Folder Action
  • Filtering Beyond Single Wildcards
  • Date-Based Filtering
  • Performance With Large Directories
  • File Operations: Copy, Move, Rename, Delete
  • Copy vs. Move: The Architectural Choice
  • Renaming with Dynamic Patterns
  • Building a Copy-Then-Archive Pattern
  • Deleting Files Safely
  • Folder Operations: Creating Dynamic Directory Structures
  • Creating Date-Partitioned Folders
  • Enumerating and Pruning Old Archive Folders
  • Email Operations with Outlook
  • Understanding PAD's Email Action Groups
  • Reading and Processing Emails
  • The Email Object Model
  • Sending Emails with Attachments
  • Sending HTML Email
  • Wiring It Together: A Production Invoice Processing Pipeline
  • Flow Architecture
  • Subflow: Initialize
  • Subflow: DownloadEmailAttachments
  • Subflow: ProcessFiles
  • Subflow: SendStatusReport
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • "File not found" After Rename
  • Outlook "Could Not Open Item" Error
  • Files Being Skipped Silently
  • "Destination folder does not exist" on Move
  • Email Attachments Saving With Duplicate Names
  • Performance: Flow Takes 20+ Minutes for 200 Files
  • Summary & Next Steps
  • Automating File, Folder, and Email Operations with Power Automate Desktop

    Introduction

    Picture this: your finance team receives 40 vendor invoices every morning, dropped into a shared network folder as PDFs, Excel files, and the occasional Word document. Someone — maybe you, maybe someone you manage — opens each one, renames it according to a naming convention, sorts it into the right subfolder by vendor and month, and then fires off a confirmation email. It takes 90 minutes. Every day. That's nearly 400 hours a year of pure mechanical repetition.

    File, folder, and email operations are the connective tissue of almost every real-world automation. Even the most sophisticated desktop flow — one that logs into a legacy ERP, scrapes a web portal, or processes database exports — usually starts by picking up a file from somewhere and ends by moving it somewhere else and notifying someone. Yet this "connective tissue" is where most Power Automate Desktop beginners underestimate complexity. Paths that work on their machine break on the production server. Files that should be there aren't. Outlook throws a COM exception because someone left a calendar reminder open. Getting these operations production-grade requires understanding not just the happy path, but the failure modes, edge cases, and design decisions that separate a fragile script from a reliable bot.

    By the end of this lesson you will have a deep, working understanding of how Power Automate Desktop handles files, folders, and Outlook email. You'll know how to build flows that are genuinely robust — flows you can hand off to an unattended runner at 2 AM without losing sleep.

    What you'll learn:

    • How the File and Folder action groups work under the hood, including path handling, error semantics, and performance implications for large directories
    • Advanced file filtering, renaming patterns, and multi-step copy/move orchestration using loops and data tables
    • Folder creation and management strategies for dynamic, date-driven directory structures
    • Sending, reading, and processing emails through Outlook and SMTP with full attachment handling
    • How to wire file and email operations together into a cohesive, production-ready pipeline with proper error handling

    Prerequisites

    This article assumes you're working at an expert level with Power Automate Desktop. You should be comfortable with:

    • Building and running desktop flows with the PAD designer (if you need a refresher, see Getting Started with Power Automate Desktop: Installing, Recording, and Running Your First Desktop Flow)
    • Variables, lists, and data tables — we'll use them extensively (see Variables, Lists, and Data Tables in Power Automate Desktop: A Complete Practitioner's Guide)
    • Error handling with On Block Error (concepts from Error Handling in Desktop Flows: On Block Error, Retry Policies, and Recovery Screenshots)
    • Basic understanding of Windows file system concepts — paths, extensions, attributes

    You'll also benefit from familiarity with subflows, since we'll be decomposing our pipeline that way (see Subflows and Reusable Logic in Power Automate Desktop).


    How Power Automate Desktop Models Files and Folders

    Before you write a single action, you need to understand what PAD actually hands you when it "gets" a file or folder. This is the gap that causes most beginner failures.

    When you use Get files in folder, PAD returns a List of File objects — not strings, not paths, but typed objects. Each File object exposes properties you access via dot notation:

    %CurrentFile.Name%          → "Invoice_Contoso_2024-11.pdf"
    %CurrentFile.NameWithoutExtension% → "Invoice_Contoso_2024-11"
    %CurrentFile.Extension%     → ".pdf"
    %CurrentFile.FullName%      → "C:\Invoices\Incoming\Invoice_Contoso_2024-11.pdf"
    %CurrentFile.Directory%     → "C:\Invoices\Incoming"
    %CurrentFile.Size%          → 204800  (bytes, as integer)
    %CurrentFile.CreationTime%  → DateTime object
    %CurrentFile.LastModified%  → DateTime object
    

    Similarly, Get subfolders in folder returns a List of Folder objects with properties like %CurrentFolder.Name%, %CurrentFolder.FullName%, and %CurrentFolder.Parent%.

    Understanding this object model matters enormously for dynamic path construction. When you need to build a destination path, you do it by combining properties:

    %CurrentFile.Directory%\Processed\%CurrentFile.Name%
    

    Or more commonly, using variables:

    %OutputFolder%\%CurrentFile.NameWithoutExtension%_%Timestamp%.pdf
    

    The file object also carries metadata that you'd otherwise need a separate system call to get. %CurrentFile.LastModified% lets you filter files older than a certain date without ever opening the file. %CurrentFile.Size% lets you skip zero-byte placeholder files that sometimes appear in network shares before the actual content is written.

    Key insight

    PAD's file and folder objects are lazy snapshots. They capture the state of the file system at the moment the "Get files" action ran. If a file is deleted or renamed by another process after that point, the object still holds the old data. In shared environments, always check file existence before operating on it.


    Getting, Filtering, and Iterating Files

    The Get Files in Folder Action

    The Get files in folder action is your primary entry point for file-based automation. Its configuration panel gives you several options worth understanding in depth:

    • Folder: The root path to scan. This can be a literal path or a variable. Use variables in production — hard-coded paths are a deployment liability.
    • File filter: A wildcard pattern. *.pdf gets only PDFs. Invoice_*.xlsx gets Excel files whose names start with "Invoice_". You can only specify one filter pattern here, which is a common frustration.
    • Include subfolders: When enabled, PAD recursively descends the entire directory tree. For deep hierarchies with thousands of files, this has real performance implications — we'll cover that below.
    • Sort by / Sort direction: You can sort by Name, Size, Creation Time, or Last Modified. This matters if your logic depends on processing order (e.g., always process oldest files first).
    • Fail if folder does not exist: Toggle this off if you want to handle missing folders gracefully yourself.

    Filtering Beyond Single Wildcards

    Here's a real problem: you need all .pdf AND .xlsx files from the incoming folder. The action only takes one filter. Two approaches:

    Approach 1: Run Get Files twice, merge lists

    Get files in folder:
      Folder: %IncomingFolder%
      Filter: *.pdf
      → Store in: PDFFiles
    
    Get files in folder:
      Folder: %IncomingFolder%
      Filter: *.xlsx
      → Store in: ExcelFiles
    
    # Combine them
    Set variable: CombinedFiles = PDFFiles
    For each ExcelFile in ExcelFiles:
        Add item to list: Item = ExcelFile, List = CombinedFiles
    

    Approach 2: Get all files, filter with conditions in the loop

    Get files in folder:
      Folder: %IncomingFolder%
      Filter: *.*
      → Store in: AllFiles
    
    For each CurrentFile in AllFiles:
        If %CurrentFile.Extension% = '.pdf' OR %CurrentFile.Extension% = '.xlsx':
            # Process this file
        End if
    

    Approach 1 keeps your list clean before the loop. Approach 2 is simpler but mixes filtering logic with processing logic. For complex pipelines with many file types, Approach 1 scales better and is easier to test in isolation.

    Date-Based Filtering

    A very common requirement: process only files modified in the last 24 hours. PAD doesn't expose a "modified after" parameter in Get Files, so you handle it in the loop:

    Get current date and time → Store in: %Now%
    Subtract from datetime: DateTime = %Now%, Hours = 24 → Store in: %Cutoff%
    
    Get files in folder: Folder = %IncomingFolder%, Filter = *.pdf → Store in: %IncomingFiles%
    
    For each CurrentFile in IncomingFiles:
        If %CurrentFile.LastModified% >= %Cutoff%:
            # File is recent, process it
        End if
    End for
    

    Tip

    When filtering by date on a network share, be aware of clock skew between the machine running your flow and the file server. A file written "now" on the server might appear to have a timestamp 3–5 seconds in the future from your bot's perspective. Add a small buffer — check for files modified in the last 25 hours rather than 24 when running daily.

    Performance With Large Directories

    "Include subfolders" on a network share with 50,000 files will block for 30–90 seconds before returning. This is because PAD enumerates the entire tree synchronously. Design considerations:

    • Limit root depth: Have upstream processes drop files into date-partitioned folders (e.g., Incoming\2024\11\). Your bot only scans today's folder.
    • Two-pass processing: First pass renames or moves files to a "staging" subfolder so a subsequent run ignores them.
    • Monitor folder sizes: As part of your flow's startup checks, count files in the incoming folder. If it exceeds a threshold (say, 500), log a warning and alert the operations team — something upstream is broken.

    File Operations: Copy, Move, Rename, Delete

    Copy vs. Move: The Architectural Choice

    The decision between copying and moving files is more significant than it appears. It encodes your process's failure semantics:

    • Copy then delete (two-step move): If the delete fails after a successful copy, you have the file in both locations. Your flow can retry the delete safely. Good for high-reliability scenarios.
    • Move directly: Atomic on the same drive and volume (the OS just updates directory entries). Non-atomic across drives or network boundaries — the OS physically copies bytes then deletes, which creates a window where both copies exist.
    • Never delete originals in the same run that processes them: Process into an output folder, move originals to an archive folder. Keep the archive for 30 days. This is the only approach that survives "we processed the wrong batch" phone calls.

    Renaming with Dynamic Patterns

    The Rename file(s) action supports several modes:

    • Set new name: Explicit name, no extension change by default
    • Add text: Prefix or suffix to existing name
    • Remove text: Strip a substring from the name
    • Change extension: Useful when converting file formats
    • Add datetime: Append or prepend a timestamp — but the format is controlled by a dropdown, not a custom format string

    For a production naming pattern like VENDOR_YYYYMMDD_ORIGINALNAME.pdf, you need to build the name yourself and use "Set new name":

    Get current date and time → Store in: %Now%
    Format datetime: DateTime = %Now%, Format = 'yyyyMMdd' → Store in: %DateStamp%
    
    For each CurrentFile in IncomingFiles:
        # Extract vendor from filename (assume first segment before underscore)
        Split text: Text = %CurrentFile.NameWithoutExtension%, Delimiter = '_'
        → Store in: %FileNameParts%
        
        Set variable: VendorCode = %FileNameParts[0]%
        Set variable: NewName = %VendorCode%_%DateStamp%_%CurrentFile.Name%
        
        Rename file: File = %CurrentFile.FullName%, 
                     Rename scheme = 'Set new name', 
                     New name = %NewName%
        → Store renamed file in: %RenamedFile%
    

    Warning

    The Rename file action returns the renamed file object in its output variable. Crucially, your original %CurrentFile% variable now points to a file that no longer exists at its old path. If you need to continue operating on that file later in the loop, use the output variable from the Rename action — not %CurrentFile%. This is a very common source of "file not found" errors in complex loops.

    Building a Copy-Then-Archive Pattern

    Here's the pattern you'll use in almost every file processing flow:

    # Phase 1: Copy to working folder
    For each CurrentFile in IncomingFiles:
        Copy file: File = %CurrentFile.FullName%,
                   Destination = %WorkingFolder%,
                   If file exists = Overwrite
        → Store in: %WorkingFile%
    End for
    
    # Phase 2: Process files in working folder
    Get files in folder: Folder = %WorkingFolder%, Filter = *.* 
    → Store in: %WorkingFiles%
    
    For each WFile in WorkingFiles:
        # Do your processing here
        # (read data, transform, write output, etc.)
    End for
    
    # Phase 3: Move originals to archive (only after processing succeeds)
    For each CurrentFile in IncomingFiles:
        Move file: File = %CurrentFile.FullName%,
                   Destination = %ArchiveFolder%,
                   If file exists = Overwrite
    End for
    

    Separating these phases — copy, process, archive — means any failure in Phase 2 leaves your originals untouched in the incoming folder. Your next run picks them up again.

    Deleting Files Safely

    PAD's Delete file(s) action is permanent by default — it bypasses the Recycle Bin. For production flows, either:

    1. Move to an archive folder instead of deleting
    2. Or use Move file to Recycle Bin via the %WINDIR%\system32\cmd.exe shell command with recycle.exe — though this requires administrative setup
    3. Or keep files in a "Processed" subfolder and have a separate scheduled cleanup flow delete files older than N days

    Folder Operations: Creating Dynamic Directory Structures

    Creating Date-Partitioned Folders

    A recurring requirement: ensure a folder exists before writing files into it. The Create folder action fails if the folder already exists — unless you check first.

    Get current date and time → Store in: %Now%
    Format datetime: DateTime = %Now%, Format = 'yyyy' → Store in: %Year%
    Format datetime: DateTime = %Now%, Format = 'MM' → Store in: %Month%
    Format datetime: DateTime = %Now%, Format = 'MMMM' → Store in: %MonthName%
    
    Set variable: YearFolder = %ArchiveRoot%\%Year%
    Set variable: MonthFolder = %YearFolder%\%Month%_%MonthName%
    
    # Create year folder if it doesn't exist
    If not folder exists: Folder = %YearFolder%:
        Create folder: Folder path = %YearFolder%
    End if
    
    # Create month folder if it doesn't exist
    If not folder exists: Folder = %MonthFolder%:
        Create folder: Folder path = %MonthFolder%
    End if
    

    Note

    PAD's If folder exists and If not folder exists conditionals check at runtime. In multi-threaded or multi-bot environments where two flows might run simultaneously, there's a race condition: both check, both find the folder missing, both try to create it — and one fails. Wrap folder creation in an On Block Error handler that ignores "already exists" errors, or centralize folder provisioning in a flow that runs before workers start.

    Enumerating and Pruning Old Archive Folders

    A maintenance subflow that you should build into any production archive system:

    Get subfolders in folder: Folder = %ArchiveRoot%, 
                              Include subfolders = No
    → Store in: %YearFolders%
    
    Get current date and time → Store in: %Now%
    Subtract from datetime: DateTime = %Now%, Days = 365 → Store in: %RetentionCutoff%
    
    For each YearFolder in YearFolders:
        Get subfolders in folder: Folder = %YearFolder.FullName%
        → Store in: %MonthFolders%
        
        For each MonthFolder in MonthFolders:
            Get files in folder: Folder = %MonthFolder.FullName%
            → Store in: %ArchivedFiles%
            
            If %ArchivedFiles.Count% = 0:
                # Empty folder, check if it's old enough to remove
                If %MonthFolder.CreationTime% < %RetentionCutoff%:
                    Delete folder: Folder = %MonthFolder.FullName%
                End if
            End if
        End for
    End for
    

    This pattern — walking a folder tree and applying age-based policies — is something you'll adapt for dozens of different retention and cleanup scenarios.


    Email Operations with Outlook

    Understanding PAD's Email Action Groups

    PAD gives you two distinct groups for email:

    1. Outlook actions: Directly control an installed Outlook application via COM automation. Requires Outlook to be installed, licensed, and configured with an account. Very powerful — you can access folders, read headers, handle attachments, move messages, mark as read, and more.

    2. Email (IMAP/POP3/SMTP) actions: Protocol-level access with explicit server credentials. More portable (works without Outlook), but limited to basic send/receive. Use this for server-side or unattended scenarios where Outlook can't be assumed.

    For enterprise scenarios on Windows machines with licensed Outlook, the Outlook actions are almost always the right choice. For cross-platform or credential-driven scenarios (e.g., an unattended bot using a service account email), SMTP/IMAP is cleaner.

    Key insight

    Outlook COM automation requires Outlook to be in a running, responsive state. On an unattended machine, Outlook might not be open. The Launch Outlook action handles this — it opens Outlook if it isn't already running. But it won't succeed if Outlook is stuck on a dialog box (password prompt, update nag, profile corruption warning). Unattended email automation requires a properly configured, dialog-free Outlook profile. Consider using a dedicated service account profile with saved credentials and auto-save disabled. See Attended vs Unattended RPA: Choosing a Run Mode and Configuring Machines in Power Automate for machine configuration guidance.

    Reading and Processing Emails

    A realistic scenario: every morning, vendors send invoices as email attachments. Your bot needs to download those attachments to the incoming folder.

    # Step 1: Connect to Outlook
    Launch Outlook → Store instance in: %OutlookInstance%
    
    # Step 2: Get emails from inbox matching our criteria
    Retrieve email messages from Outlook:
        Account: vendor.processing@company.com
        Mail folder: Inbox
        Email status: Unread
        Mark as read on retrieval: Yes
        From contains: @vendor.com
        Subject contains: Invoice
        Retrieve: All messages
    → Store in: %VendorEmails%
    
    # Step 3: Process each email
    For each Email in VendorEmails:
        # Check if email has attachments
        If %Email.HasAttachments% = True:
            # Save attachments to incoming folder
            Save Outlook email message attachments:
                Account: vendor.processing@company.com
                Email message: %Email%
                Attachment folder: %IncomingFolder%
            → Store attachment paths in: %SavedAttachments%
            
            # Log what we received
            Write text to file: 
                File: %LogFile%
                Text: %Email.From% | %Email.Subject% | %Now% | %SavedAttachments.Count% attachments
            
            # Move email to processed folder
            Move Outlook email message:
                Account: vendor.processing@company.com
                Email message: %Email%
                Mail folder: Processed\Invoices
        End if
    End for
    

    The Email Object Model

    When PAD retrieves email messages, each message object exposes:

    %Email.From%          → "Jane Smith <jane@vendor.com>"
    %Email.To%            → "vendor.processing@company.com"
    %Email.CC%            → ""
    %Email.Subject%       → "Invoice #4721 - November 2024"
    %Email.Body%          → Full plain-text body
    %Email.HTMLBody%      → HTML version of body
    %Email.Date%          → DateTime object
    %Email.HasAttachments% → True/False
    %Email.IsRead%        → True/False  
    %Email.EntryId%       → Outlook unique ID (use this to reference the email later)
    

    Warning

    %Email.From% returns the full formatted string including display name and angle brackets — "Jane Smith <jane@vendor.com>" — not just the email address. If you need to parse just the address for matching or logging, you'll need to use text manipulation: split on <, take the second part, strip the >. Build this into a reusable subflow rather than repeating it every time you need a clean email address.

    Sending Emails with Attachments

    Sending through Outlook is straightforward but has a few nuances for production use:

    # Build a recipient list dynamically from a data table
    Set variable: RecipientList = ''
    
    For each Row in VendorContactTable:
        Set variable: RecipientList = %RecipientList%;%Row['Email']%
    End for
    
    # Strip leading semicolon
    Trim text: Text = %RecipientList%, What to trim = Leading characters
    → Store in: %RecipientList%
    
    # Build the email body with dynamic content
    Set variable: EmailBody = 
    'Dear Team,
    
    The following invoices have been processed for %MonthName% %Year%:
    
    - Total files processed: %ProcessedCount%
    - Total files failed: %FailedCount%
    - Archive location: %MonthFolder%
    
    Please review the processing log at %LogFile% for details.
    
    This is an automated message from the Invoice Processing Bot.'
    
    # Send via Outlook
    Send email through Outlook:
        Account: vendor.processing@company.com
        To: %RecipientList%
        CC: finance.manager@company.com
        Subject: Invoice Processing Complete - %MonthName% %Year%
        Body: %EmailBody%
        Body is HTML: No
        Attachments: %LogFile%
    

    Sending HTML Email

    For HTML emails, build your body string as valid HTML. PAD doesn't validate the HTML — it passes whatever string you provide directly to Outlook's HTML body property. This means you can use full HTML with inline styles:

    Set variable: HTMLBody = 
    '<!DOCTYPE html>
    <html>
    <body style="font-family: Calibri, sans-serif; font-size: 14px;">
    <h2 style="color: #1F4E79;">Invoice Processing Summary</h2>
    <table border="1" cellpadding="6" style="border-collapse: collapse; width: 100%;">
    <tr style="background-color: #BDD7EE;">
      <th>Vendor</th><th>File</th><th>Status</th>
    </tr>'
    
    For each Row in ProcessingResults:
        Set variable: RowColor = '#FFFFFF'
        If %Row['Status']% = 'Failed':
            Set variable: RowColor = '#FFE0E0'
        End if
        
        Set variable: HTMLBody = %HTMLBody% & 
        '<tr style="background-color: ' & %RowColor% & ';">
          <td>' & %Row['Vendor']% & '</td>
          <td>' & %Row['FileName']% & '</td>
          <td>' & %Row['Status']% & '</td>
        </tr>'
    End for
    
    Set variable: HTMLBody = %HTMLBody% & '</table></body></html>'
    
    Send email through Outlook:
        Body: %HTMLBody%
        Body is HTML: Yes
    

    This pattern — building a processing results data table throughout your flow and then rendering it as HTML at the end — produces professional-looking status emails that operations teams actually find useful.

    Tip

    Avoid using %Tab% or %NewLine% special variables inside HTML body strings. HTML ignores whitespace anyway. But be careful with quote characters inside your HTML — single quotes inside double-quoted variable values (or vice versa) are fine, but if you need both, use HTML entities: &apos; for single quote, &quot; for double quote.


    Wiring It Together: A Production Invoice Processing Pipeline

    Let's build the full pipeline. This is a realistic scenario that combines everything we've covered. We'll structure it as a main flow that calls subflows — a pattern discussed in depth in Subflows and Reusable Logic in Power Automate Desktop.

    Flow Architecture

    Main Flow
    ├── Subflow: Initialize (set variables, create folders, connect Outlook)
    ├── Subflow: DownloadEmailAttachments (fetch from inbox, save to Incoming)
    ├── Subflow: ProcessFiles (rename, validate, copy to working folder)
    ├── Subflow: ArchiveAndCleanup (move originals, delete working copies)
    └── Subflow: SendStatusReport (build HTML report, email stakeholders)
    

    Subflow: Initialize

    # --- Subflow: Initialize ---
    
    # Base paths from environment (set as input variables from cloud flow trigger)
    # See: Triggering Desktop Flows from Cloud Flows
    Set variable: BaseFolder = %InputBaseFolder%
    Set variable: IncomingFolder = %BaseFolder%\Incoming
    Set variable: WorkingFolder = %BaseFolder%\Working
    Set variable: ArchiveFolder = %BaseFolder%\Archive
    Set variable: LogFolder = %BaseFolder%\Logs
    
    # Ensure all required folders exist
    For each FolderPath in ['%IncomingFolder%', '%WorkingFolder%', '%LogFolder%']:
        If not folder exists: Folder = %FolderPath%:
            Create folder: Folder path = %FolderPath%
        End if
    End for
    
    # Create today's archive folder
    Get current date and time → Store in: %Now%
    Format datetime: DateTime = %Now%, Format = 'yyyy\\MM_MMMM' → Store in: %ArchiveSuffix%
    Set variable: TodayArchive = %ArchiveFolder%\%ArchiveSuffix%
    
    If not folder exists: Folder = %TodayArchive%:
        Create folder: Folder path = %TodayArchive%
    End if
    
    # Set up log file for this run
    Format datetime: DateTime = %Now%, Format = 'yyyyMMdd_HHmmss' → Store in: %RunStamp%
    Set variable: LogFile = %LogFolder%\ProcessingLog_%RunStamp%.txt
    Write text to file: File = %LogFile%, 
                        Text = 'Invoice Processing Started: %Now%', 
                        Append = False
    
    # Initialize tracking variables
    Set variable: ProcessedCount = 0
    Set variable: FailedCount = 0
    Create new data table: → Store in: %ProcessingResults%
        Columns: ['Vendor', 'FileName', 'Status', 'Notes']
    
    # Connect to Outlook
    Launch Outlook → Store in: %OutlookInstance%
    

    Subflow: DownloadEmailAttachments

    # --- Subflow: DownloadEmailAttachments ---
    
    On block error:
        Action: Continue flow
        Write text to file: File = %LogFile%, 
                            Text = 'ERROR in DownloadEmailAttachments: %LastError%', 
                            Append = True
    End on block error
    
    Retrieve email messages from Outlook:
        Account: %OutlookAccount%
        Mail folder: Inbox
        Email status: Unread
        Subject contains: Invoice
        Retrieve: All messages
    → Store in: %InvoiceEmails%
    
    Write text to file: File = %LogFile%, 
                        Text = 'Found %InvoiceEmails.Count% invoice emails', 
                        Append = True
    
    For each Email in InvoiceEmails:
        If %Email.HasAttachments% = True:
            On block error:
                Action: Continue flow
                Write text to file: File = %LogFile%, 
                                    Text = 'Failed to save attachments from: %Email.From%', 
                                    Append = True
            End on block error
            
            Save Outlook email message attachments:
                Account: %OutlookAccount%
                Email message: %Email%
                Attachment folder: %IncomingFolder%
            → Store in: %SavedAttachments%
            
            Write text to file: File = %LogFile%, 
                                Text = 'Saved %SavedAttachments.Count% attachments from %Email.From%', 
                                Append = True
            
            Move Outlook email message:
                Account: %OutlookAccount%
                Email message: %Email%
                Mail folder: Processed\Invoices
        Else:
            Write text to file: File = %LogFile%, 
                                Text = 'WARNING: Email from %Email.From% has no attachments - Subject: %Email.Subject%', 
                                Append = True
        End if
    End for
    

    Subflow: ProcessFiles

    # --- Subflow: ProcessFiles ---
    
    Get files in folder: Folder = %IncomingFolder%, Filter = *.* 
    → Store in: %IncomingFiles%
    
    Write text to file: File = %LogFile%, 
                        Text = 'Processing %IncomingFiles.Count% files', 
                        Append = True
    
    For each CurrentFile in IncomingFiles:
        # Skip zero-byte files (incomplete uploads)
        If %CurrentFile.Size% = 0:
            Write text to file: File = %LogFile%, 
                                Text = 'SKIP: Zero-byte file %CurrentFile.Name%', 
                                Append = True
            Continue
        End if
        
        # Only process PDF and Excel
        If %CurrentFile.Extension% <> '.pdf' AND %CurrentFile.Extension% <> '.xlsx':
            Write text to file: File = %LogFile%, 
                                Text = 'SKIP: Unsupported extension %CurrentFile.Name%', 
                                Append = True
            Continue
        End if
        
        On block error:
            Action: Continue flow
            Set variable: FailedCount = %FailedCount% + 1
            Add row to data table: 
                DataTable = %ProcessingResults%
                Row: ['Unknown', '%CurrentFile.Name%', 'Failed', '%LastError%']
            Write text to file: File = %LogFile%, 
                                Text = 'FAILED: %CurrentFile.Name% - %LastError%', 
                                Append = True
        End on block error
        
        # Parse vendor from filename (format: VENDORCODE_anything.ext)
        Split text: Text = %CurrentFile.NameWithoutExtension%, Delimiter = '_'
        → Store in: %NameParts%
        Set variable: VendorCode = %NameParts[0]%
        
        # Build standardized name
        Format datetime: DateTime = %Now%, Format = 'yyyyMMdd' → Store in: %DateStamp%
        Set variable: StandardName = %VendorCode%_%DateStamp%_%CurrentFile.Name%
        
        # Copy to working folder with standard name
        Copy file: File = %CurrentFile.FullName%, 
                   Destination = %WorkingFolder%\%StandardName%,
                   If file exists = Overwrite
        
        # Track success
        Set variable: ProcessedCount = %ProcessedCount% + 1
        Add row to data table:
            DataTable = %ProcessingResults%
            Row: ['%VendorCode%', '%StandardName%', 'Processed', '']
        
        # Move original to today's archive
        Move file: File = %CurrentFile.FullName%, 
                   Destination = %TodayArchive%\%CurrentFile.Name%,
                   If file exists = Overwrite
    End for
    

    Subflow: SendStatusReport

    # --- Subflow: SendStatusReport ---
    
    # Build HTML status table from ProcessingResults data table
    Set variable: TableRows = ''
    
    For each Row in ProcessingResults:
        Set variable: StatusColor = '#E2EFDA'  # Green for success
        If %Row['Status']% = 'Failed':
            Set variable: StatusColor = '#FCE4D6'  # Orange for failure
        End if
        
        Set variable: TableRows = %TableRows% & 
            '<tr style="background:' & %StatusColor% & ';">' &
            '<td style="padding:6px;border:1px solid #ccc;">' & %Row['Vendor']% & '</td>' &
            '<td style="padding:6px;border:1px solid #ccc;">' & %Row['FileName']% & '</td>' &
            '<td style="padding:6px;border:1px solid #ccc;font-weight:bold;">' & %Row['Status']% & '</td>' &
            '<td style="padding:6px;border:1px solid #ccc;">' & %Row['Notes']% & '</td>' &
            '</tr>'
    End for
    
    Set variable: HTMLReport = 
    '<html><body style="font-family:Calibri,sans-serif;">
    <h2 style="color:#1F4E79;">Invoice Processing Report</h2>
    <p><strong>Run time:</strong> ' & %Now% & '</p>
    <p><strong>Processed:</strong> ' & %ProcessedCount% & ' &nbsp; 
       <strong>Failed:</strong> ' & %FailedCount% & '</p>
    <table style="border-collapse:collapse;width:100%">
    <tr style="background:#2E75B6;color:white;">
      <th style="padding:8px;">Vendor</th>
      <th style="padding:8px;">File</th>
      <th style="padding:8px;">Status</th>
      <th style="padding:8px;">Notes</th>
    </tr>' & %TableRows% & '</table>
    <p style="color:#888;font-size:12px;">Automated message from Invoice Processing Bot</p>
    </body></html>'
    
    Send email through Outlook:
        Account: %OutlookAccount%
        To: %NotificationRecipients%
        Subject: Invoice Processing Complete: %ProcessedCount% processed, %FailedCount% failed - %DateStamp%
        Body: %HTMLReport%
        Body is HTML: Yes
        Attachments: %LogFile%
    

    Hands-On Exercise

    Build the following flow from scratch. This will take approximately 60–90 minutes and covers all the major patterns from this lesson.

    Scenario: A shared folder (C:\Reports\Incoming) receives CSV export files from three different systems each morning. Files are named in formats like CRM_export_20241115.csv, ERP_daily_20241115.csv, and WH_stock_20241115.csv. Your flow should:

    1. Collect all .csv files from the incoming folder
    2. Skip any files older than 48 hours (the day before yesterday's leftovers that weren't processed)
    3. Create a subfolder under C:\Reports\Archive named YYYY-MM-DD for today's date if it doesn't already exist
    4. Copy each valid file to the archive subfolder, renaming it to include the system prefix and today's date stamp
    5. Move successfully processed originals to C:\Reports\Processed
    6. Log each file's name, size, source system, and status to a text file in C:\Reports\Logs
    7. Send a summary email to yourself via Outlook when the run completes, including the log file as an attachment and a count of files by source system in the body

    Extension challenge: Modify the flow to handle the case where the incoming folder doesn't exist yet (perhaps the upstream system hasn't run). Instead of failing, log a warning and send a different email with subject "WARNING: No incoming folder found" and skip all file processing.


    Common Mistakes & Troubleshooting

    "File not found" After Rename

    Symptom: Your loop renames a file, then immediately tries to move or read it — and gets a "file not found" error.

    Cause: You're using %CurrentFile.FullName% which still holds the pre-rename path. After a rename, use the output variable of the Rename action.

    Fix: Always capture the rename output: Rename file → Store in: %RenamedFile%, then operate on %RenamedFile.FullName%.

    Outlook "Could Not Open Item" Error

    Symptom: The Retrieve emails action fails intermittently with a COM exception or "item could not be opened."

    Cause: Usually a profile or connectivity issue. Can also be a corrupted OST file or a cached message that Outlook can't render.

    Fix: Wrap in an On Block Error with retry (3 attempts, 30-second delay). For persistent failures, the Outlook profile on the machine needs attention. On unattended bots, this almost always means the machine profile hasn't been configured to auto-connect to Exchange without prompting.

    Files Being Skipped Silently

    Symptom: Some files aren't getting processed, but you see no errors.

    Cause: Usually a filter mismatch. Extension check using %CurrentFile.Extension% on a file named Invoice.PDF (uppercase) returns .PDF — and your comparison is against .pdf. PAD string comparisons in conditions are case-sensitive.

    Fix: Normalize extensions to lowercase before comparing:

    Convert text to lowercase: Text = %CurrentFile.Extension% → Store in: %ExtLower%
    If %ExtLower% = '.pdf' OR %ExtLower% = '.xlsx':
    

    "Destination folder does not exist" on Move

    Symptom: Move file fails because the destination path doesn't exist — even though you thought you created it.

    Cause: Your folder creation used a path built from variables, but a variable had a trailing space or included an invalid character.

    Fix: Before any move or copy, check folder existence explicitly. Also log the exact destination path string to your log file so you can inspect it. Path debugging is much easier when you can see the actual string the action received.

    Email Attachments Saving With Duplicate Names

    Symptom: Two emails from the same vendor both have an attachment named invoice.pdf. The second one overwrites the first.

    Cause: The Save Outlook email message attachments action names files by their original attachment name. If two emails have identically-named attachments, the second one overwrites the first in the destination folder.

    Fix: After saving, rename each attachment to include a unique identifier. Use %Email.EntryId% (the Outlook unique message ID) as part of the new name, or use a sequence counter. Better yet, save to a message-specific subfolder:

    Set variable: EmailSubfolder = %IncomingFolder%\%Email.EntryId%
    Create folder: Folder path = %EmailSubfolder%
    Save attachments: Attachment folder = %EmailSubfolder%
    

    Warning

    File system paths on Windows have a 260-character limit by default (the MAX_PATH limit). If %Email.EntryId% is long (and Outlook entry IDs can be very long hex strings), your subfolder path might exceed 260 characters, causing mysterious failures. Use the first 20 characters of the entry ID: Substring of text: Text = %Email.EntryId%, Start = 0, Count = 20.

    Performance: Flow Takes 20+ Minutes for 200 Files

    Symptom: Processing 200 files takes much longer than expected.

    Cause: Each file operation — copy, move, rename — is an individual OS call that goes through PAD's action engine. On network paths, latency per operation compounds significantly.

    Fix:

    • Process locally: Copy all files from the network share to a local temp folder at the start. Process locally. Copy results back and archive originals at the end. This converts N network copy operations + N network move operations into 1 bulk copy + 1 bulk copy back + N local operations.
    • Parallelize with subflows: PAD doesn't natively parallelize loops, but you can split the file list into batches and launch multiple parallel flows. This requires architectural work but can cut runtime proportionally.
    • Pre-filter aggressively: The fewer files in your processing loop, the faster. Use date filters, size filters, and extension filters before the loop, not inside it.

    Summary & Next Steps

    You've now built a deep, nuanced understanding of how Power Automate Desktop handles file system and email operations at a production level. The core ideas to carry forward:

    • File objects are typed, not strings. Use their properties (.FullName, .Extension, .LastModified) directly rather than parsing path strings manually.
    • Design for failure. The copy-process-archive pattern keeps originals safe. Never delete source files in the same run that processes them.
    • Track rename outputs. After any rename, the original file object is stale. Use the action's output variable.
    • Outlook automation needs a pristine profile. Unattended Outlook operations fail silently when the profile requires interaction. Invest in machine configuration upfront.
    • Build a data table of results throughout the flow, then render it as HTML at the end. Your operations team will thank you.

    For your next steps, consider how this pipeline integrates with the wider automation ecosystem. The files your bot downloads and processes might need to be read into Excel for further data manipulation. If some of those invoice PDFs are scanned, you'll want to combine this pipeline with OCR text extraction. And once your flow is solid, triggering it from a cloud flow — so it runs automatically when a SharePoint folder receives a file or when an email arrives — is the natural evolution. That integration is covered in Triggering Desktop Flows from Cloud Flows: Passing Inputs and Returning Outputs.

    Finally, make sure your flow handles its credentials safely. The Outlook account details, any SMTP passwords, and shared folder credentials used in your flow should not be hardcoded as plain-text variables. See Handling Credentials Securely in Desktop Flows: Sensitive Variables and Azure Key Vault for the right approach before you deploy this to production.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Power Automate Desktop & RPA

    Previous

    Handling Credentials Securely in Desktop Flows: Sensitive Variables and Azure Key Vault

    Next

    Scripting Inside Desktop Flows: Running PowerShell, Python, and VBScript Actions

    Related Insights

    Power AutomateExpert

    Building a Multi-Step Microsoft 365 Connector Flow That Handles Attachments: Receiving Files via Outlook, Storing to SharePoint, and Notifying Approvers in Teams

    27 min
    Power AutomatePractitioner

    Implementing Power Automate Managed Identity Authentication for Azure Connector Actions: Eliminating Stored Credentials in Production Cloud Flows

    23 min
    Power AutomatePractitioner

    Automating Web Application Login and Session Persistence in Power Automate Desktop: Handling Cookie Consent, SSO Redirects, and Session Timeouts in Unattended Browser Flows

    25 min

    On this page

    • Introduction
    • Prerequisites
    • How Power Automate Desktop Models Files and Folders
    • Getting, Filtering, and Iterating Files
    • The Get Files in Folder Action
    • Filtering Beyond Single Wildcards
    • Date-Based Filtering
    • Performance With Large Directories
    • File Operations: Copy, Move, Rename, Delete
    • Copy vs. Move: The Architectural Choice
    • Renaming with Dynamic Patterns
    • Building a Copy-Then-Archive Pattern
    • Deleting Files Safely
    • Folder Operations: Creating Dynamic Directory Structures
    • Creating Date-Partitioned Folders
    • Enumerating and Pruning Old Archive Folders
    • Email Operations with Outlook
    • Understanding PAD's Email Action Groups
    • Reading and Processing Emails
    • The Email Object Model
    • Sending Emails with Attachments
    • Sending HTML Email
    • Wiring It Together: A Production Invoice Processing Pipeline
    • Flow Architecture
    • Subflow: Initialize
    • Subflow: DownloadEmailAttachments
    • Subflow: ProcessFiles
    • Subflow: SendStatusReport
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • "File not found" After Rename
    • Outlook "Could Not Open Item" Error
    • Files Being Skipped Silently
    • "Destination folder does not exist" on Move
    • Email Attachments Saving With Duplicate Names
    • Performance: Flow Takes 20+ Minutes for 200 Files
    • Summary & Next Steps