Introduction
Copying files to Windows remote hosts is a fundamental task in Windows automation with Ansible. The ansible.windows.win_copy module provides a reliable way to transfer files from the Ansible controller to Windows targets over WinRM.
In this guide, you'll learn how to use win_copy effectively, including copying single files, directories, creating files from content, handling large files, and troubleshooting common issues.
The ansible.windows.win_copy Module
The full module name is ansible.windows.win_copy, part of the ansible.windows collection for managing Windows hosts. This module has been stable for years and is the standard way to copy files to Windows targets.
Key Characteristics
- Transfers files over WinRM (not SSH)
- Supports single files, directories, and inline content
- Uses checksums to determine if transfer is needed (idempotent)
- Not efficient for large files — use
win_get_urlinstead for files hosted on web servers - Opposite operation:
ansible.windows.win_fetch(remote → controller) - Linux equivalent:
ansible.builtin.copy
Parameters Reference
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dest | path | Yes | — | Remote absolute path (use \ for Windows paths) |
src | path | No* | — | Local source file/directory path |
content | string | No* | — | Text content to write directly to file |
backup | boolean | No | false | Create backup before overwriting |
force | boolean | No | true | Transfer even if file exists (set false to skip existing) |
remote_src | boolean | No | false | If true, src is on the remote host |
decrypt | boolean | No | true | Decrypt Ansible Vault encrypted source files |
*Either src or content is required, but not both.
Basic Examples
Copy a Single File
---
- name: Copy file to Windows host
hosts: windows
tasks:
- name: Copy report to Desktop
ansible.windows.win_copy:
src: files/report.txt
dest: C:\Users\admin\Desktop\report.txt
Copy with Backup
---
- name: Copy with backup
hosts: windows
tasks:
- name: Update config with backup of original
ansible.windows.win_copy:
src: files/app.config
dest: C:\Program Files\MyApp\app.config
backup: true
Create File from Content
---
- name: Create file from content
hosts: windows
tasks:
- name: Create environment config
ansible.windows.win_copy:
dest: C:\ProgramData\MyApp\settings.ini
content: |
[Settings]
Environment=Production
LogLevel=Warning
MaxConnections=50
ServerName={{ inventory_hostname }}
Advanced Patterns
Copy an Entire Directory
---
- name: Copy directory to Windows
hosts: windows
vars:
app_dir: C:\Program Files\MyApp
tasks:
- name: Ensure app directory exists
ansible.windows.win_file:
path: "{{ app_dir }}"
state: directory
- name: Copy application files (directory contents)
ansible.windows.win_copy:
src: app_files/
dest: "{{ app_dir }}\\"
Important: A trailing / on src copies only the contents. Without the trailing slash, the directory itself is copied as a subdirectory.
Copy from Remote Source
---
- name: Copy from network share
hosts: windows
tasks:
- name: Copy installer from file share
ansible.windows.win_copy:
src: \\fileserver\share\installers\app-2.0.msi
dest: C:\Temp\app-2.0.msi
remote_src: true
Conditional Copy (Skip if Exists)
---
- name: Copy only if not present
hosts: windows
tasks:
- name: Deploy initial config (don't overwrite customizations)
ansible.windows.win_copy:
src: files/default.config
dest: C:\ProgramData\MyApp\user.config
force: false
Copy with Variables and Loops
---
- name: Copy multiple files
hosts: windows
vars:
config_files:
- { src: 'web.config', dest: 'C:\inetpub\wwwroot\web.config' }
- { src: 'app.config', dest: 'C:\Services\MyService\app.config' }
- { src: 'nlog.config', dest: 'C:\Services\MyService\nlog.config' }
tasks:
- name: Copy configuration files
ansible.windows.win_copy:
src: "files/{{ item.src }}"
dest: "{{ item.dest }}"
backup: true
loop: "{{ config_files }}"
Complete Playbook: Application Deployment
---
- name: Deploy Windows Application
hosts: windows
become: true
become_method: runas
become_user: SYSTEM
vars:
app_name: MyWebService
app_dir: C:\Program Files\{{ app_name }}
log_dir: C:\ProgramData\{{ app_name }}\Logs
service_account: NT SERVICE\{{ app_name }}
tasks:
- name: Create application directories
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ app_dir }}"
- "{{ log_dir }}"
- "{{ app_dir }}\\config"
- name: Copy application binaries
ansible.windows.win_copy:
src: "build/{{ app_name }}/"
dest: "{{ app_dir }}\\"
notify: Restart service
- name: Deploy application configuration
ansible.windows.win_copy:
dest: "{{ app_dir }}\\config\\appsettings.json"
content: |
{
"Logging": {
"LogLevel": {
"Default": "Information"
},
"FilePath": "{{ log_dir | regex_replace('\\\\', '\\\\\\\\') }}\\\\app.log"
},
"ConnectionStrings": {
"Default": "Server={{ db_server }};Database={{ db_name }};Integrated Security=true"
},
"AppSettings": {
"Environment": "{{ ansible_env.COMPUTERNAME }}",
"MaxWorkers": 8
}
}
notify: Restart service
- name: Create Windows service health check script
ansible.windows.win_copy:
dest: "{{ app_dir }}\\healthcheck.ps1"
content: |
$response = Invoke-WebRequest -Uri "http://localhost:8080/health" -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Output "Healthy"
exit 0
} else {
Write-Output "Unhealthy: $($response.StatusCode)"
exit 1
}
handlers:
- name: Restart service
ansible.windows.win_service:
name: "{{ app_name }}"
state: restarted
Performance Considerations
Since win_copy transfers data over WinRM, it's not efficient for large files. Consider these alternatives:
| File Size | Recommended Approach |
|---|---|
| < 10 MB | win_copy is fine |
| 10-100 MB | win_get_url from a web server or file share |
| > 100 MB | win_get_url or win_package with network source |
| Many small files | win_copy with directory src |
# Better for large files - download from web server
- name: Download large installer
ansible.windows.win_get_url:
url: https://artifacts.example.com/releases/app-2.0.msi
dest: C:\Temp\app-2.0.msi
checksum: sha256:abc123...
Troubleshooting
Common Error: Path Not Found
fatal: [win-host]: FAILED! => {"msg": "Destination directory C:\\NonExistent does not exist"}
Fix: Create the directory first with win_file:
- name: Ensure directory exists
ansible.windows.win_file:
path: C:\MyApp\Config
state: directory
- name: Copy file
ansible.windows.win_copy:
src: config.xml
dest: C:\MyApp\Config\config.xml
Common Error: Access Denied
fatal: [win-host]: FAILED! => {"msg": "Access is denied"}
Fix: Use become with appropriate privileges:
- name: Copy to protected directory
ansible.windows.win_copy:
src: files/service.config
dest: C:\Windows\System32\config\service.config
become: true
become_method: runas
become_user: Administrator
Common Error: WinRM Timeout on Large Files
Fix: Increase the WinRM timeout in your inventory or ansible.cfg:
[windows:vars]
ansible_winrm_operation_timeout_sec=120
ansible_winrm_read_timeout_sec=150
Related Modules
- ansible.builtin.copy — Copy files to Linux hosts
- ansible.windows.win_get_url — Download files from URLs on Windows
- ansible.windows.win_template — Template files for Windows
- ansible.windows.win_fetch — Fetch files from Windows to controller
Conclusion
The ansible.windows.win_copy module is essential for Windows automation with Ansible. It handles single files, directories, and inline content creation with idempotent behavior via checksum verification. For large file transfers, prefer win_get_url or network shares. Always ensure target directories exist and your WinRM user has appropriate permissions.