Introduction
Backing up Windows systems with Ansible automates what's often a manual, error-prone process. Whether you're protecting Windows 10/11 workstations, Windows Server 2019/2022, or entire fleets, Ansible modules like ansible.windows.win_copy, community.windows.win_robocopy, and ansible.windows.win_shell give you repeatable, idempotent backup automation. This guide covers file-level backups, system state, database dumps, and scheduled backup tasks.
Prerequisites
# Requirements:
# - WinRM configured on target hosts (or SSH for Windows)
# - ansible.windows collection installed
# - community.windows collection installed (for win_robocopy)
# Install required collections
# ansible-galaxy collection install ansible.windows community.windows
Backup Methods Comparison
Method | Best For | Module | Speed
────────────────────|────────────────────|───────────────────────|──────
win_robocopy | Large folder sync | community.windows | Fast
win_copy | Small files/config | ansible.windows | Medium
win_shell + robocopy| Custom robocopy | ansible.windows | Fast
fetch | Pull to controller | ansible.builtin | Slow
VSS + wbadmin | System state | ansible.windows | Slow
Enterprise Backup Scenario
For an enterprise backup playbook, keep Windows hosts in a dedicated inventory group and use this workflow for file shares, IIS data, registry exports, SQL Server dumps, and scheduled tasks. If the same backup policy also covers Linux hosts, pair this Windows playbook with a separate Linux file backup role or a general Ansible backup and restore guide so each operating system uses the right modules while sharing retention, verification, and destination naming rules.
Basic File Backup with win_copy
# backup-files.yml
---
- name: Backup Windows files to network share
hosts: windows_hosts
vars:
backup_date: "{{ ansible_date_time.date }}"
backup_dest: "\\\\backup-server\\backups\\{{ inventory_hostname }}\\{{ backup_date }}"
tasks:
- name: Create backup directory
ansible.windows.win_file:
path: "{{ backup_dest }}"
state: directory
- name: Backup application config files
ansible.windows.win_copy:
src: "C:\\App\\config\\"
dest: "{{ backup_dest }}\\config\\"
remote_src: true
- name: Backup IIS configuration
ansible.windows.win_copy:
src: "C:\\Windows\\System32\\inetsrv\\config\\applicationHost.config"
dest: "{{ backup_dest }}\\iis-config\\"
remote_src: true
Folder Backup with win_robocopy
win_robocopy is the best choice for large directory backups — it's fast, supports mirroring, and handles retries.
# backup-robocopy.yml
---
- name: Backup Windows folders with Robocopy
hosts: windows_hosts
vars:
backup_source: "C:\\ImportantData"
backup_dest: "D:\\Backups\\{{ inventory_hostname }}\\{{ ansible_date_time.date }}"
tasks:
- name: Mirror data folder to backup location
community.windows.win_robocopy:
src: "{{ backup_source }}"
dest: "{{ backup_dest }}"
recurse: true
flags:
- /MIR # Mirror (exact copy, deletes extras)
- /Z # Restartable mode (network resilience)
- /MT:16 # 16 threads for speed
- /R:3 # Retry 3 times on failure
- /W:5 # Wait 5 seconds between retries
- /NFL # No file list in output (cleaner logs)
- /NDL # No directory list in output
- /NP # No progress percentage
register: robocopy_result
- name: Show backup summary
ansible.builtin.debug:
msg: >-
Backup complete:
{{ robocopy_result.msg | default('OK') }}
Complete Backup Playbook
# full-backup-windows.yml
---
- name: Complete Windows backup
hosts: windows_hosts
vars:
backup_root: "\\\\nas01\\backups"
backup_dir: "{{ backup_root }}\\{{ inventory_hostname }}\\{{ ansible_date_time.date }}"
retention_days: 30
backup_sources:
- name: "user-data"
src: "C:\\Users\\Public\\Documents"
- name: "app-config"
src: "C:\\ProgramData\\MyApp"
- name: "iis-sites"
src: "C:\\inetpub\\wwwroot"
tasks:
# --- Pre-backup checks ---
- name: Verify backup share is accessible
ansible.windows.win_shell: |
if (Test-Path "{{ backup_root }}") { "OK" } else { throw "Backup share not accessible" }
register: share_check
changed_when: false
- name: Create dated backup directory
ansible.windows.win_file:
path: "{{ backup_dir }}"
state: directory
# --- File backups ---
- name: Backup each source directory
community.windows.win_robocopy:
src: "{{ item.src }}"
dest: "{{ backup_dir }}\\{{ item.name }}"
recurse: true
flags:
- /MIR
- /Z
- /MT:8
- /R:3
- /W:10
- /XO # Exclude older files (only copy newer)
loop: "{{ backup_sources }}"
loop_control:
label: "{{ item.name }}"
register: backup_results
# --- Registry backup ---
- name: Export critical registry keys
ansible.windows.win_shell: |
reg export "HKLM\SOFTWARE\MyApp" "{{ backup_dir }}\registry-myapp.reg" /y
args:
creates: "{{ backup_dir }}\\registry-myapp.reg"
# --- Windows System State ---
- name: Backup Windows System State (servers only)
ansible.windows.win_shell: |
wbadmin start systemstatebackup -backupTarget:{{ backup_dir }}\systemstate -quiet
when: ansible_os_name is search("Server")
register: systemstate
failed_when: systemstate.rc not in [0, 4]
changed_when: systemstate.rc == 0
# --- Backup verification ---
- name: Verify backup files exist
ansible.windows.win_stat:
path: "{{ backup_dir }}\\{{ item.name }}"
loop: "{{ backup_sources }}"
loop_control:
label: "{{ item.name }}"
register: verify
failed_when: not verify.stat.exists
# --- Cleanup old backups ---
- name: Remove backups older than {{ retention_days }} days
ansible.windows.win_shell: |
$cutoff = (Get-Date).AddDays(-{{ retention_days }})
$hostDir = "{{ backup_root }}\{{ inventory_hostname }}"
Get-ChildItem $hostDir -Directory | Where-Object {
try { [datetime]::ParseExact($_.Name, 'yyyy-MM-dd', $null) -lt $cutoff }
catch { $false }
} | Remove-Item -Recurse -Force
register: cleanup
changed_when: "'Remove-Item' in cleanup.stdout"
# --- Summary ---
- name: Backup summary
ansible.builtin.debug:
msg: |
✅ Backup completed for {{ inventory_hostname }}
📁 Location: {{ backup_dir }}
📦 Sources backed up: {{ backup_sources | length }}
🗑️ Retention: {{ retention_days }} days
Backup with Scheduled Task
# schedule-backup.yml
---
- name: Schedule daily Windows backup
hosts: windows_hosts
tasks:
- name: Copy backup script to host
ansible.windows.win_copy:
content: |
# Daily backup script (managed by Ansible)
$date = Get-Date -Format "yyyy-MM-dd"
$dest = "\\backup-server\backups\$env:COMPUTERNAME\$date"
New-Item -Path $dest -ItemType Directory -Force
# Robocopy important folders
robocopy "C:\ImportantData" "$dest\data" /MIR /Z /MT:8 /R:3 /W:10 /NFL /NDL /NP
robocopy "C:\ProgramData\MyApp" "$dest\config" /MIR /Z /R:3 /NFL /NDL /NP
# Log result
"Backup completed: $(Get-Date)" | Out-File "$dest\backup.log"
dest: "C:\\Scripts\\daily-backup.ps1"
- name: Create scheduled task for daily backup
community.windows.win_scheduled_task:
name: "Ansible Daily Backup"
description: "Daily backup managed by Ansible"
actions:
- path: "powershell.exe"
arguments: "-ExecutionPolicy Bypass -File C:\\Scripts\\daily-backup.ps1"
triggers:
- type: daily
start_boundary: "2026-01-01T02:00:00" # 2 AM daily
username: SYSTEM
run_level: highest
state: present
enabled: true
- name: Run backup immediately (first time)
ansible.windows.win_shell: |
Start-ScheduledTask -TaskName "Ansible Daily Backup"
when: ansible_date_time.hour | int < 3 # Only if before 3 AM
Backup SQL Server Database
# backup-sqlserver.yml
---
- name: Backup SQL Server databases
hosts: sql_servers
vars:
backup_dir: "D:\\SQLBackups\\{{ ansible_date_time.date }}"
databases:
- name: "ProductionDB"
type: "FULL"
- name: "ReportingDB"
type: "FULL"
tasks:
- name: Create backup directory
ansible.windows.win_file:
path: "{{ backup_dir }}"
state: directory
- name: Backup each database
ansible.windows.win_shell: |
$db = "{{ item.name }}"
$backupFile = "{{ backup_dir }}\${db}_{{ item.type }}_{{ ansible_date_time.date }}.bak"
Invoke-Sqlcmd -Query "BACKUP DATABASE [$db] TO DISK='$backupFile' WITH {{ item.type }}, COMPRESSION, CHECKSUM"
# Verify backup
Invoke-Sqlcmd -Query "RESTORE VERIFYONLY FROM DISK='$backupFile'"
Write-Output "Backup verified: $backupFile"
loop: "{{ databases }}"
loop_control:
label: "{{ item.name }}"
register: sql_backup
- name: Copy backups to network share
community.windows.win_robocopy:
src: "{{ backup_dir }}"
dest: "\\\\backup-server\\sql-backups\\{{ inventory_hostname }}\\{{ ansible_date_time.date }}"
recurse: true
flags:
- /Z
- /R:3
Pull Backups to Ansible Controller
# fetch-backup.yml — Pull files FROM Windows TO the controller
---
- name: Fetch backup files from Windows hosts
hosts: windows_hosts
tasks:
- name: Create archive of config directory
ansible.windows.win_shell: |
Compress-Archive -Path "C:\ProgramData\MyApp\*" `
-DestinationPath "C:\Temp\config-backup.zip" -Force
- name: Fetch archive to controller
ansible.builtin.fetch:
src: "C:\\Temp\\config-backup.zip"
dest: "/backups/{{ inventory_hostname }}/config-backup.zip"
flat: true
- name: Clean up temp archive
ansible.windows.win_file:
path: "C:\\Temp\\config-backup.zip"
state: absent
Common Mistakes
# ❌ MISTAKE 1: Using win_copy for large directories (slow)
# Use win_robocopy instead — it's 10x faster for large folder trees
# ❌ MISTAKE 2: No error handling on network share access
# Always verify share is accessible before starting backup
- name: Check share access
ansible.windows.win_shell: Test-Path "\\\\backup-server\\share"
register: share_test
failed_when: "'True' not in share_test.stdout"
# ❌ MISTAKE 3: Robocopy exit codes misunderstood
# Robocopy uses non-standard exit codes:
# 0 = No changes, 1 = Files copied, 2 = Extra files found
# 3 = Files copied + extra, 8+ = Errors
# community.windows.win_robocopy handles this correctly
# ❌ MISTAKE 4: No backup verification
# Always verify backups exist after creation:
- name: Verify backup
ansible.windows.win_stat:
path: "{{ backup_path }}"
register: verify
failed_when: not verify.stat.exists or verify.stat.size < 1024
# ❌ MISTAKE 5: No retention policy (disk fills up)
# Always include cleanup of old backups in your playbook
Inventory Example
# inventory/windows.ini
[windows_hosts]
web01.corp.local
web02.corp.local
dc01.corp.local
[windows_hosts:vars]
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore
ansible_port=5986
Related Articles
- Ansible win_copy Module Guide
- Ansible Windows Automation
- Ansible Vault Encrypt Secrets
- Ansible Scheduled Tasks
- Ansible Error Handling
Conclusion
Ansible makes Windows backup automation repeatable and auditable. Use win_robocopy for large folder synchronization, win_copy for small config files, fetch to pull backups to the controller, and scheduled tasks for unattended daily backups. Combine with retention policies and verification steps for production-grade backup automation. Start with the complete backup playbook above and customize the source paths and retention period for your environment.