Ansible win_shell Module: Run PowerShell Commands on Windows (Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
How to run PowerShell and CMD commands on Windows with Ansible win_shell module. Difference between win_command vs win_shell. Practical YAML playbook examples.

Ansible modules - win_command vs win_shell
How to automate the execution of PowerShell or cmd.exe code on windows target hosts using Ansible Playbook with win_command and win_shell modules.What is the difference between win_command vs win_shell Ansible modules? These two Ansible modules are confused one for another but they're fundamentally different. Both modules allow you to execute win_command on a target host but in a slightly different way. I'm Luca Berton and welcome to today's episode of Ansible Pilot.
See also: Ansible shell Module: Run Shell Commands with Pipes & Redirects (Guide)
win_command vs win_shell
•win_command
• Executes a command on a remote Windows node
• it bypasses the Windows shell
• always set changed to True
• win_shell
• Execute shell commands on Windows target hosts
• redirections and win_shell's inbuilt functionality
• always set changed to True
The win_command and win_shell Ansible modules execute win_commands on the Windows target node.
Generally speaking, is always better to use a specialized Ansible module to execute a task.
However, sometimes the only way is to execute a Windows PowerShell or cmd.exe via the win_command or win_shell module.
Let me reinforce again, you should avoid as much as possible the usage of win_command/win_shell instead of a better module.
Both modules execute PowerShell or cmd.exe commands on Windows target nodes but in a sensible different way.
The win_command modules execute win_commands on the target machine without using the target win_shell, it simply executes the win_command. The target win_shell is for example sending any PowerShell or cmd.exe. t will not be processed through the shell, so variables like $env:HOME and operations like "<", ">", "|", and ";" are not available. On the other side, every command executed using the win_shell module has all PowerShell or cmd.exe features so it could be expanded in runtime. From the security point of viewwin_command module is more robust and has a more predictable outcome because it bypasses the shell.
Both modules returned always changed status because Ansible is not able to predict if the execution has or has not altered the target system.
For non-Windows targets, use the ansible.builtin.command and ansible.builtin.shell modules instead.
win_command
• Executes a command on a remote Windows nodeThe win_command module won't be impacted by local win_shell variables because it bypasses the win_shell. At the same time, it may not be able to run win_shell built-in features and redirections.
See also: Ansible troubleshooting - Module Failure on Windows-target
win_shell
• Execute shell commands on target hostsThe win_shell Ansible module is potentially more dangerous than the win_command module and should only be used when you actually really need the PowerShell or cmd.exe functionalities. So if you're not stringing two commands together o variables like $env:HOME and operations like "<", ">", "|", and ";" you don't really need the win_shell module. Similarly, expanding shell variables requires the win_shell module. If you're not using these features, don't use the win_shell module. Sometimes it's the only way, I know.
Links
• ansible.windows.win_command • ansible.windows.win_shell## Playbook
Let me show you the difference between win_command vs win_shell Ansible modules in three Ansible Playbooks.
win_command code
---
- name: win_command module Playbook
hosts: all
tasks:
- name: check netstat
ansible.windows.win_command: "netstat -e"
register: command_output
- name: command output
ansible.builtin.debug:
var: command_output.stdout_lines
win_command execution
ansible-pilot $ ansible-playbook -i virtualmachines/win/inventory commmand_shell/win_date.yml
PLAY [win_shell module Playbook] **********************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [WindowsServer]
TASK [check getdate] ******************************************************************************
changed: [WindowsServer]
TASK [command output] *****************************************************************************
ok: [WindowsServer] => {
"command_output.stdout_lines": [
"Playbook",
"",
"Friday, April 8, 2022 11:19:21 AM",
"",
""
]
}
PLAY RECAP ****************************************************************************************
WindowsServer : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
win_shell code
---
- name: win_shell module Playbook
hosts: all
tasks:
- name: check getdate
ansible.windows.win_shell: |
hostname
Get-Date
register: command_output
- name: command output
ansible.builtin.debug:
var: command_output.stdout_lines
win_shell execution
ansible-pilot $ ansible-playbook -i virtualmachines/win/inventory commmand_shell/win_date.yml
PLAY [win_shell module Playbook] **********************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [WindowsServer]
TASK [check getdate] ******************************************************************************
changed: [WindowsServer]
TASK [command output] *****************************************************************************
ok: [WindowsServer] => {
"command_output.stdout_lines": [
"Playbook",
"",
"Friday, April 8, 2022 18:19:36 AM",
"",
""
]
}
PLAY RECAP ****************************************************************************************
WindowsServer : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
wrong module code
The win_command doesn't execute multiple PowerShell lines.
---
- name: win_command module Playbook
hosts: all
tasks:
- name: check getdate
ansible.windows.win_command: |
hostname
Get-Date
register: command_output
- name: command output
ansible.builtin.debug:
var: command_output.stdout_lines
wrong module execution
ansible-pilot $ ansible-playbook -i virtualmachines/win/inventory commmand_shell/win_date_command.yml
PLAY [win_command module Playbook] ********************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [WindowsServer]
TASK [check getdate] ******************************************************************************
changed: [WindowsServer]
TASK [command output] *****************************************************************************
ok: [WindowsServer] => {
"command_output.stdout_lines": [
"Playbook"
]
}
PLAY RECAP ****************************************************************************************
WindowsServer : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
See also: Ansible for Windows: Complete Guide to Managing Windows Hosts
Control the changed status and make tasks idempotent
Both modules always report changed, because Ansible cannot know whether
your command altered the system. For a read-only command like the netstat or
Get-Date examples above, that is misleading — it makes every run look like it
modified the host. Suppress it with changed_when: false:
- name: check netstat (read-only, never reports changed)
ansible.windows.win_command: "netstat -e"
register: command_output
changed_when: false
When the command does change something, make it idempotent so it only runs
when needed. Both win_command and win_shell accept creates (skip if a path
already exists) and removes (skip if a path is absent):
- name: Run the installer only once
ansible.windows.win_command: "C:\\setup\\install.exe /quiet"
args:
creates: "C:\\Program Files\\MyApp\\app.exe"
You can also drive the changed status from the command's own output with
changed_when, for example marking the task changed only when a script prints a
known token:
- name: Apply config and report changed only when it actually changed
ansible.windows.win_shell: .\apply-config.ps1
register: cfg
changed_when: "'CONFIG_UPDATED' in cfg.stdout"
Conclusion
Now you know the difference between win_command vs win_shell Ansible modules and their use case.
You know how to use it based on your use case.
Related Articles
• managing inventory in Ansible • the Ansible Windows reference • configure a Windows host for Ansible (WinRM) • run command and shell on Linux targetsCategory: installation
Watch the video: Ansible win_shell Module: Run PowerShell Commands on Windows (Guide) — Video Tutorial