Set up a Windows 11 PC for Ansible
Install SSHD
PS > Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0'
Add ssh key
Add your public key to the administrators authorized_keys file:
PS > "ssh-ed25519 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | set-content 'C:\ProgramData\ssh\administrators_authorized_keys' -Encoding ASCII
Use icacls to restrict the permissions:
PS > icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
Configure SSHD
# Disable root login (not sure if this has an effect on windows)
PermitRootLogin no
# Strict authentication modes
StrictModes yes
# Enable key authentication
PubkeyAuthentication yes
# Disable password and other authentication methods
PasswordAuthentication no
PermityEmptyPasswords no
GSSAPIAuthentication no
# Default sftp thing
Subsystem sftp sftp-server.exe
# Use the ProgramData authorized keys file instead of C:\Users\<username>\.ssh\authorized_keys
# but only for the administrators group
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
# Allows our Domain "Workstation Administrators" group to log in
AllowGroups contoso.local\Workstation Administrators
Start the SSHD service
PS > Start-Service sshd
Ansible
I have ansible installed inside WSL Debian.
Activate the ansible environment and start the ssh agent so ansible can log in with the key.
You can configure ansible_ssh_key_file
within the hosts but I find the ssh-agent method easier
because inside WSL all Windows file have 0777 permissions and therefore ssh refuses to load the key.
Take note that with the ssh-add
command we are specifiying -
for stdin and
redirecting the private key file, this is what bypasses the 0777 permission issue.
bash > source ~/ansible-env/bin/activate
(ansible-env) bash > eval `ssh-agent`
Agent pid 87705
(ansible-env) bash > ssh-add - < id_windows
Identity added: (stdin) (<keyname>)
If using fish shell, you have to source the fish version of the environment activation script. I have a fish alias for ssh-agent as it only works with bash normally.
fish > source ~/ansible-env/bin/activate.fish
(ansible-env) fish > ssh-agent
(ansible-env) fish > ssh-add - < id_windows
Identity added: (stdin) (<keyname>)
Configure the ansible inventory as usual. Use the FQDN including the AD domain.
I don't use my regular account, instead I put my workstation administrator account which is a member of our "contoso.local\Workstation Administrators" AD group.
---
all:
hosts:
DESKTOP-XXXXXX.contoso.local:
DESKTOP-YYYYYY.contoso.local:
DESKTOP-ZZZZZZ.contoso.local:
vars:
ansible_user: <workstation administrator account>
ansible_connection: ssh
ansible_shell_type: cmd
Test the connection with an ad-hoc command. If you have not connected before, you will have to check and accept the host keys.
fish > ansible -i hosts.yml all -m win_ping
DESKTOP-XXXXXX.contoso.local | OK => {
"changed": true,
"ping": "pong"
}
DESKTOP-YYYYYY.contoso.local | OK => {
"changed": true,
"ping": "pong"
}
DESKTOP-ZZZZZZ.contoso.local | OK => {
"changed": true,
"ping": "pong"
}
Test a playbook, I created one which shows the uptime.
---
- name: Get Uptime
gather_facts: no
hosts: all
tasks:
- name: Fetch uptime
ansible.windows.win_powershell:
script: |
$bt=(Get-CimInstance Win32_OperatingSystem -Property LastBootUpTime).LastBootUpTime;
$ut=(Get-Date).Subtract($bt);
[string]::Format("Up {5:0} days {6:0} hours {7:0} minutes, since {0:0000}-{1:00}-{2:00} {3:00}:{4:00}", $bt.Year, $bt.Month, $bt.Hour, $bt.Minute, $ut.Days, $ut.Hours, $ut.Minutes);
register: uptime
- name: Show uptime
ansible.builtin.debug:
var: uptime.output
Run the playbook to retrieve the uptime from the computers.
fish > ansible-playbook -i hosts.yml get_uptime.yml
PLAY [Get Uptime] ****
TASK [Fetch uptime] ****
changed: [DESKTOP-XXXXXX.contoso.local]
TASK [Show uptime] ****
ok: [DESKTOP-XXXXXX.contoso.local] => {
"uptime.output": [
"Up 0 days 5 hours 54 minutes, since 2025-03-07 12:35"
]
}
PLAY RECAP ****
DESKTOP-XXXXXX.contoso.local : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0