Introduction

The ansible.windows.win_file module manages files and directories on Windows targets — create directories, delete files, create symbolic links, and set attributes. It's the Windows equivalent of ansible.builtin.file for Linux. Every Windows automation playbook uses it for directory setup, cleanup, and file organization.

Parameters

ParameterRequiredDefaultDescription
path✅—Target file or directory path
state❌filefile, directory, touch, absent, link
force❌falseForce creation of symlinks

State values:

StateDescription
directoryCreate directory (and parents) if it doesn't exist
touchCreate empty file or update timestamp
absentDelete file or directory recursively
fileAssert file exists (fail if not)
linkCreate symbolic link (requires src)

Create Directories

---
- name: Create directory structures on Windows
  hosts: windows
  tasks:
    - name: Create a single directory
      ansible.windows.win_file:
        path: C:\Apps\MyApplication
        state: directory

    - name: Create nested directories (parents created automatically)
      ansible.windows.win_file:
        path: C:\Apps\MyApplication\logs\archive\2026
        state: directory

    - name: Create multiple directories
      ansible.windows.win_file:
        path: "{{ item }}"
        state: directory
      loop:
        - C:\Apps\MyApplication\config
        - C:\Apps\MyApplication\data
        - C:\Apps\MyApplication\logs
        - C:\Apps\MyApplication\temp
        - C:\Apps\MyApplication\backups

Create Empty Files

---
- name: Create empty files (touch)
  hosts: windows
  tasks:
    - name: Create an empty log file
      ansible.windows.win_file:
        path: C:\Apps\MyApplication\logs\app.log
        state: touch

    - name: Touch multiple files
      ansible.windows.win_file:
        path: "{{ item }}"
        state: touch
      loop:
        - C:\Apps\MyApplication\logs\access.log
        - C:\Apps\MyApplication\logs\error.log
        - C:\Apps\MyApplication\data\.initialized

Delete Files and Directories

---
- name: Clean up files and directories
  hosts: windows
  tasks:
    - name: Delete a single file
      ansible.windows.win_file:
        path: C:\Temp\installer.exe
        state: absent

    - name: Delete a directory recursively
      ansible.windows.win_file:
        path: C:\Apps\OldApplication
        state: absent

    - name: Clean up temp files
      ansible.windows.win_file:
        path: "{{ item }}"
        state: absent
      loop:
        - C:\Temp\cache
        - C:\Temp\downloads
        - C:\Users\svcaccount\AppData\Local\Temp
---
- name: Manage symbolic links
  hosts: windows
  tasks:
    - name: Create symlink to current version
      ansible.windows.win_file:
        src: C:\Apps\MyApplication\v2.1.0
        path: C:\Apps\MyApplication\current
        state: link

    - name: Update symlink to new version (force overwrite)
      ansible.windows.win_file:
        src: C:\Apps\MyApplication\v2.2.0
        path: C:\Apps\MyApplication\current
        state: link
        force: true

Practical Patterns

Application Deployment Directory Setup

---
- name: Prepare application directory structure
  hosts: windows
  vars:
    app_name: "WebPortal"
    app_root: "D:\\Applications\\{{ app_name }}"
    app_version: "3.1.0"

  tasks:
    - name: Create application directories
      ansible.windows.win_file:
        path: "{{ item }}"
        state: directory
      loop:
        - "{{ app_root }}"
        - "{{ app_root }}\\releases"
        - "{{ app_root }}\\releases\\{{ app_version }}"
        - "{{ app_root }}\\shared"
        - "{{ app_root }}\\shared\\config"
        - "{{ app_root }}\\shared\\logs"
        - "{{ app_root }}\\shared\\data"

    - name: Point current to new release
      ansible.windows.win_file:
        src: "{{ app_root }}\\releases\\{{ app_version }}"
        path: "{{ app_root }}\\current"
        state: link
        force: true

IIS Site Setup

---
- name: Set up IIS web application directories
  hosts: webservers
  vars:
    sites:
      - { name: "main-site", port: 80 }
      - { name: "api", port: 8080 }
      - { name: "admin", port: 8443 }

  tasks:
    - name: Create web root directories
      ansible.windows.win_file:
        path: "C:\\inetpub\\{{ item.name }}"
        state: directory
      loop: "{{ sites }}"

    - name: Create log directories
      ansible.windows.win_file:
        path: "C:\\inetpub\\logs\\{{ item.name }}"
        state: directory
      loop: "{{ sites }}"

Cleanup Old Releases

---
- name: Clean up old application releases
  hosts: windows
  vars:
    app_root: "D:\\Applications\\WebPortal"
    keep_releases: 3

  tasks:
    - name: List release directories
      ansible.windows.win_find:
        paths: "{{ app_root }}\\releases"
        file_type: directory
      register: releases

    - name: Sort releases by date (oldest first)
      ansible.builtin.set_fact:
        old_releases: >-
          {{ releases.files
             | sort(attribute='creationtime')
             | map(attribute='path')
             | list }}

    - name: Remove old releases (keep latest N)
      ansible.windows.win_file:
        path: "{{ item }}"
        state: absent
      loop: "{{ old_releases[:-keep_releases] }}"
      when: (old_releases | length) > keep_releases

Check File Exists Before Acting

---
- name: Conditional operations based on file existence
  hosts: windows
  tasks:
    - name: Check if config exists
      ansible.windows.win_stat:
        path: C:\Apps\MyApp\config.json
      register: config_file

    - name: Create default config if missing
      ansible.windows.win_copy:
        content: '{"version": "1.0", "debug": false}'
        dest: C:\Apps\MyApp\config.json
      when: not config_file.stat.exists

    - name: Check if legacy directory exists
      ansible.windows.win_stat:
        path: C:\Apps\LegacyApp
      register: legacy_dir

    - name: Remove legacy application
      ansible.windows.win_file:
        path: C:\Apps\LegacyApp
        state: absent
      when: legacy_dir.stat.exists

win_file vs Other Windows File Modules

ModuleUse For
win_fileCreate/delete dirs, touch files, symlinks
win_copyCopy files (local→remote or remote→remote)
win_templateDeploy Jinja2 templates
win_statCheck if file/dir exists, get attributes
win_findSearch for files by pattern/age/size
win_aclSet NTFS permissions (ACLs)
win_ownerChange file ownership
win_lineinfileEdit lines in text files

Troubleshooting

"Access is denied"

The Ansible WinRM user doesn't have permissions on the target path. Either use a privileged account or set ACLs:

- name: Grant service account access
  ansible.windows.win_acl:
    path: "C:\\Apps\\MyApp"
    user: "DOMAIN\\svc_ansible"
    rights: FullControl
    type: allow
    state: present
    inherit: ContainerInherit, ObjectInherit

"The system cannot find the path specified"

The parent directory doesn't exist. state: directory creates parents automatically, but state: touch and state: file do not:

# This fails if C:\Apps\NewApp doesn't exist
- ansible.windows.win_file:
    path: C:\Apps\NewApp\app.log
    state: touch

# Fix: create parent first
- ansible.windows.win_file:
    path: C:\Apps\NewApp
    state: directory
- ansible.windows.win_file:
    path: C:\Apps\NewApp\app.log
    state: touch

"Cannot remove directory — in use"

A process has a file open or the directory is a current working directory. Stop the service first:

- name: Stop service before cleanup
  ansible.windows.win_service:
    name: MyAppService
    state: stopped

- name: Remove application directory
  ansible.windows.win_file:
    path: C:\Apps\MyApp
    state: absent

- name: Reinstall application
  ansible.windows.win_file:
    path: C:\Apps\MyApp
    state: directory

Conclusion

ansible.windows.win_file handles four jobs on Windows: create directories (state: directory), create empty files (state: touch), delete files or directories (state: absent), and create symlinks (state: link). Parent directories are created automatically with state: directory. For file content, use win_copy or win_template. For permissions, use win_acl. For existence checks, use win_stat. The module is idempotent — running it twice produces the same result.