AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible win_shell and win_command: Run Commands on Windows Complete Guide

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to Ansible win_shell and win_command modules. Run PowerShell scripts, CMD commands, and batch files on Windows.

win_shell and win_command run commands on Windows hosts. win_command executes a process directly (no shell). win_shell runs through PowerShell (supports pipes, redirects, variables). Here's when to use each, with practical examples.

win_command vs win_shell

| Feature | win_command | win_shell | |---------|------------|-----------| | Shell | None (direct process) | PowerShell | | Pipes | ❌ | ✅ | | Redirects | ❌ | ✅ | | Variables | ❌ | ✅ $env:PATH | | Wildcards | ❌ | ✅ | | Security | Safer (no injection) | Flexible but riskier | | Use when | Simple commands | PowerShell features needed |

# win_command — direct execution, no shell
- ansible.windows.win_command: ipconfig /all

# win_shell — through PowerShell - ansible.windows.win_shell: Get-Process | Where-Object { $_.CPU -gt 100 }

See also: Ansible for Windows: Complete Guide to Managing Windows Hosts

win_command Examples

Basic Commands

- name: Check Windows version
  ansible.windows.win_command: systeminfo
  register: sysinfo

- name: Get IP configuration ansible.windows.win_command: ipconfig /all register: ipconfig

- name: Run executable with arguments ansible.windows.win_command: C:\tools\setup.exe /silent /norestart

- name: Check service status ansible.windows.win_command: sc query W3SVC register: iis_status

Idempotent with creates/removes

- name: Extract archive (skip if already done)
  ansible.windows.win_command: 7z x C:\temp\app.zip -oC:\app
  args:
    creates: C:\app\bin\app.exe

- name: Run installer (skip if installed) ansible.windows.win_command: C:\temp\installer.msi /quiet args: creates: C:\Program Files\MyApp\app.exe

- name: Remove temp files (only if they exist) ansible.windows.win_command: del /q C:\temp\*.tmp args: removes: C:\temp\*.tmp

Working Directory

- name: Run command in specific directory
  ansible.windows.win_command: npm install
  args:
    chdir: C:\projects\myapp

win_shell Examples

PowerShell One-Liners

- name: Get disk space
  ansible.windows.win_shell: |
    Get-PSDrive -PSProvider FileSystem |
    Select-Object Name, @{N='Free(GB)';E={[math]::Round($_.Free/1GB,2)}},
    @{N='Used(GB)';E={[math]::Round($_.Used/1GB,2)}}
  register: disk_space

- name: Find large files ansible.windows.win_shell: | Get-ChildItem -Path C:\ -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 100MB } | Sort-Object Length -Descending | Select-Object -First 10 FullName, @{N='Size(MB)';E={[math]::Round($_.Length/1MB,2)}} register: large_files

- name: Get installed software ansible.windows.win_shell: | Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher | Sort-Object DisplayName | ConvertTo-Json register: installed_software

PowerShell Scripts

- name: Configure Windows features
  ansible.windows.win_shell: |
    $features = @('Web-Server', 'Web-Asp-Net45', 'Web-Mgmt-Console')
    foreach ($feature in $features) {
        $installed = Get-WindowsFeature -Name $feature
        if (-not $installed.Installed) {
            Install-WindowsFeature -Name $feature -IncludeManagementTools
            Write-Output "Installed: $feature"
        } else {
            Write-Output "Already installed: $feature"
        }
    }

- name: Manage Windows Firewall ansible.windows.win_shell: | # Allow inbound HTTP and HTTPS $rules = @( @{Name='Allow HTTP'; Port=80}, @{Name='Allow HTTPS'; Port=443} ) foreach ($rule in $rules) { $existing = Get-NetFirewallRule -DisplayName $rule.Name -ErrorAction SilentlyContinue if (-not $existing) { New-NetFirewallRule -DisplayName $rule.Name ` -Direction Inbound -Protocol TCP ` -LocalPort $rule.Port -Action Allow } }

Using PowerShell Variables

- name: Create user with PowerShell
  ansible.windows.win_shell: |
    $password = ConvertTo-SecureString "{{ vault_user_password }}" -AsPlainText -Force
    New-LocalUser -Name "{{ username }}" -Password $password -FullName "{{ full_name }}" -Description "Service account"
    Add-LocalGroupMember -Group "Administrators" -Member "{{ username }}"

- name: Configure environment variables ansible.windows.win_shell: | [System.Environment]::SetEnvironmentVariable("APP_HOME", "C:\app", "Machine") [System.Environment]::SetEnvironmentVariable("APP_ENV", "{{ environment }}", "Machine")

Error Handling

- name: Run with error handling
  ansible.windows.win_shell: |
    try {
        $result = Invoke-WebRequest -Uri "https://api.example.com/health" -TimeoutSec 10
        if ($result.StatusCode -eq 200) {
            Write-Output "Service healthy"
            exit 0
        }
    } catch {
        Write-Error "Health check failed: $_"
        exit 1
    }
  register: health_check
  failed_when: health_check.rc != 0

- name: Capture both stdout and stderr ansible.windows.win_shell: | Write-Output "Standard output" Write-Error "Error output" exit 0 register: result

- name: Show outputs ansible.builtin.debug: msg: stdout: "{{ result.stdout }}" stderr: "{{ result.stderr }}" rc: "{{ result.rc }}"

See also: AAP 2.6 Windows Automation: WinRM, PowerShell, and Active Directory Management

Running Scripts from Files

win_shell with Script Files

- name: Copy and run PowerShell script
  block:
    - name: Copy script
      ansible.windows.win_copy:
        src: scripts/configure-iis.ps1
        dest: C:\temp\configure-iis.ps1

- name: Execute script ansible.windows.win_shell: C:\temp\configure-iis.ps1 -SiteName "{{ site_name }}" -Port {{ port }} args: chdir: C:\temp

- name: Clean up script ansible.windows.win_file: path: C:\temp\configure-iis.ps1 state: absent

win_script (Alternative)

# Copies and runs a script in one step
- name: Run local script on remote Windows
  ansible.builtin.script: scripts/setup.ps1
  args:
    executable: powershell.exe

Output Encoding

# Force UTF-8 output
- name: Get content with UTF-8
  ansible.windows.win_shell: |
    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
    Get-Content -Path C:\data\report.txt -Encoding UTF8

# Parse JSON output - name: Get structured data ansible.windows.win_shell: | Get-Service | Where-Object { $_.Status -eq 'Running' } | Select-Object Name, DisplayName, Status | ConvertTo-Json register: services

- name: Use parsed data ansible.builtin.debug: msg: "Running services: {{ (services.stdout | from_json) | length }}"

See also: Ansible for Windows Server Enterprise Management: Active Directory, IIS, and Group Policy

Real-World Examples

IIS Website Configuration

---
- name: Configure IIS
  hosts: windows_web
  tasks:
    - name: Install IIS features
      ansible.windows.win_shell: |
        Install-WindowsFeature -Name Web-Server, Web-Asp-Net45, Web-Mgmt-Console -IncludeManagementTools

- name: Create website ansible.windows.win_shell: | Import-Module WebAdministration $siteName = "{{ site_name }}" $sitePath = "C:\inetpub\{{ site_name }}"

if (-not (Test-Path $sitePath)) { New-Item -Path $sitePath -ItemType Directory }

$site = Get-Website -Name $siteName -ErrorAction SilentlyContinue if (-not $site) { New-Website -Name $siteName -PhysicalPath $sitePath -Port {{ site_port }} Write-Output "Created website: $siteName" } else { Write-Output "Website already exists: $siteName" }

- name: Configure app pool ansible.windows.win_shell: | Import-Module WebAdministration Set-ItemProperty "IIS:\AppPools\{{ site_name }}" -Name processModel.identityType -Value 3 Set-ItemProperty "IIS:\AppPools\{{ site_name }}" -Name recycling.periodicRestart.time -Value "02:00:00"

Windows Service Management

- name: Install and configure Windows service
  ansible.windows.win_shell: |
    $serviceName = "{{ service_name }}"
    $servicePath = "{{ service_path }}"

$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if (-not $service) { New-Service -Name $serviceName ` -BinaryPathName $servicePath ` -DisplayName "{{ service_display_name }}" ` -StartupType Automatic ` -Description "{{ service_description }}" Start-Service -Name $serviceName Write-Output "Service created and started" } else { Write-Output "Service already exists" }

Registry Management

- name: Configure registry settings
  ansible.windows.win_shell: |
    # Disable Windows Update auto-restart
    $path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
    if (-not (Test-Path $path)) {
        New-Item -Path $path -Force
    }
    Set-ItemProperty -Path $path -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -Type DWord

# Set timezone Set-TimeZone -Id "{{ timezone }}"

# Configure power settings powercfg /change standby-timeout-ac 0 powercfg /change monitor-timeout-ac 30

Prefer Dedicated Modules When Available

# ❌ Using win_shell when a module exists
- ansible.windows.win_shell: Install-WindowsFeature -Name Web-Server

# ✅ Use the dedicated module (idempotent, structured output) - ansible.windows.win_feature: name: Web-Server state: present

# ❌ win_shell for service management - ansible.windows.win_shell: Start-Service W3SVC

# ✅ Dedicated module - ansible.windows.win_service: name: W3SVC state: started

# ❌ win_shell for file operations - ansible.windows.win_shell: Copy-Item C:\source\* C:\dest\

# ✅ Dedicated module - ansible.windows.win_copy: src: C:\source\ dest: C:\dest\ remote_src: true

Use win_shell/win_command only when no dedicated module exists for the task.

FAQ

When should I use win_shell vs win_command?

Use win_command for simple executables that don't need shell features (pipes, variables, redirects). Use win_shell when you need PowerShell functionality. win_command is safer against injection since it doesn't interpret shell metacharacters.

How do I handle special characters in win_shell?

Ansible uses Jinja2 templating, which conflicts with PowerShell's $ and {{ }}. Use {% raw %}...{% endraw %} for literal PowerShell variables, or pass values through Ansible variables:

- ansible.windows.win_shell: |
    $name = '{{ username }}'  # Ansible variable
    {% raw %}
    $items = @(1,2,3)         # PowerShell variable
    foreach ($item in $items) { Write-Output $item }
    {% endraw %}

Why does my win_shell task always show "changed"?

win_shell and win_command always report changed because Ansible can't know if the command modified anything. Use changed_when to control this:

- ansible.windows.win_shell: Get-Service W3SVC
  register: result
  changed_when: false  # Read-only command

How do I run CMD (not PowerShell) commands?

- ansible.windows.win_shell: cmd.exe /c dir C:\Windows
# Or use win_command which doesn't use PowerShell
- ansible.windows.win_command: cmd.exe /c dir C:\Windows

Can I run elevated (admin) PowerShell?

Ansible runs as the connected user. Use become: true with become_method: runas for privilege escalation:

- ansible.windows.win_shell: Install-WindowsFeature Web-Server
  become: true
  become_method: runas
  become_user: Administrator

Conclusion

Use win_command for simple process execution and win_shell when you need PowerShell features. Always prefer dedicated Ansible modules over shell commands when available — they're idempotent and return structured data. For complex PowerShell logic, use win_shell with proper error handling, changed_when, and creates/removes for idempotency.

Related Articles

Ansible for Windows AutomationAnsible Connection TypesAnsible shell vs command vs rawAnsible Error Handling Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home