Ansible Veeam Modules Preview

Creating an initial set of Ansible Veeam modules has been my goal for a while. It was clear that this had to be possible with the Ansible concept of Windows modules written in Powershell. A Veeam Backup & Replication server needs to be a Windows Server and the Veeam console is shipped with a feature-rich PowerShell SnapIn, which can be used by the Ansible Windows modules written in Powershell.

Ansible Veeam Modules Preview - Concept

So my basic concept is an Ansible PowerShell module running on the Veeam server. As the Veeam Backup & Replication server needs to be a Windows Server, it will be accessed via WinRM by Ansible. I have already written about Ansible and WinRM in my previous blog post: Veeam unattended installation with Ansible

In the current version of the modules, no remote connection to the Veeam Server via PowerShell is planned. So the Veeam Backup & Replication server needs to be accessed via WinRM. If an additional hop via a Windows server with only the Veeam Console installed is required, I would be glad about feedback.

Ansible Veeam Modules

So far I have created just a small set of modules to validate my concept. The goal that I have set myself was to add a VMware ESXi standalone host to the Veeam configuration.

Caution, now follows a lot of code!

Veeam Facts

This was the first Ansible Veeam Module I have created. It’s a read-only module that gathers configuration details about the Veeam Backup & Replication server. The return can be written into a variable and used by other Ansible Veeam Modules.

Please check the Github Project for the detailed syntax.

#!powershell

# Copyright: (c) 2019, Markus Kraus <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#AnsibleRequires -OSVersion 6.2

$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0

# Functions
Function Connect-VeeamServer {
    try {
        Add-PSSnapin -PassThru VeeamPSSnapIn -ErrorAction Stop | Out-Null
    }
    catch {
        Fail-Json -obj @{} -message  "Failed to load Veeam SnapIn on the target: $($_.Exception.Message)"     
    }

    try {
        Connect-VBRServer -Server localhost
    }
    catch {
        Fail-Json -obj @{} -message "Failed to connect VBR Server on the target: $($_.Exception.Message)"  
    }
}
Function Disconnect-VeeamServer {
    try {
        Disconnect-VBRServer
    }
    catch {
        Fail-Json -obj @{} -message "Failed to disconnect VBR Server on the target: $($_.Exception.Message)"  
    }
}

# Connect
Connect-VeeamServer

# Create a new result object
$result = @{
    changed = $false
    veeam_facts = @{
        veeam_connection = @()
        veeam_repositories = @()
        veeam_servers = @()
        veeam_credentials = @()
    }
}

# Get Veeam Connection
try {
    $Connection = Get-VBRServerSession
} catch {
    Fail-Json -obj $result -message "Failed to get connection details on the target: $($_.Exception.Message)"
}

# Get Veeam Server
try {
    [Array]$ServerList = Get-VBRServer
}
catch {
    Fail-Json -obj $result -message "Failed to get server details on the target: $($_.Exception.Message)"   
}

# Get Veeam Repositories
try {
    [Array]$RepoList = Get-VBRBackupRepository | Where-Object {$_.Type -ne "SanSnapshotOnly"} 
}
catch {
    Fail-Json -obj $result -message "Failed to get repository details on the target: $($_.Exception.Message)"   
}

# Get Veeam Credentials
try {
    [Array]$CredList = Get-VBRCredentials    
}
catch {
    Fail-Json -obj $result -message "Failed to get credential details on the target: $($_.Exception.Message)"   
}

# Create result
$connection_info = @{}
$connection_info["user"] = $Connection.user
$connection_info["server"] = $Connection.server
$connection_info["port"] = $Connection.port

$result.veeam_facts.veeam_connection += $connection_info

foreach ($Repo in $RepoList) {
    $repo_info = @{}
    $repo_info["name"] = $repo.name
    $repo_info["type"] = $repo.typedisplay
    $repo_info["host"] = $($ServerList | Where-Object {$_.Id -eq $repo.HostId}).name
    $repo_info["friendlypath"] = $repo.friendlypath
    $repo_info["description"] = $repo.description

    $result.veeam_facts.veeam_repositories += $repo_info
}

foreach ($Server in $ServerList) {
    $server_info = @{}
    $server_info["id"] = $server.id
    $server_info["name"] = $server.name
    $server_info["description"] = $server.description
    $server_info["type"] = $server.info.typedescription

    $result.veeam_facts.veeam_servers += $server_info
}

foreach ($Cred in $CredList) {
    $cred_info = @{}
    $cred_info["id"] = $cred.id
    $cred_info["name"] = $cred.name
    $cred_info["username"] = $cred.username
    $cred_info["encryptedpassword"] = $cred.encryptedpassword
    $cred_info["description"] = $cred.description

    $result.veeam_facts.veeam_credentials += $cred_info
}

# Disconnect
Disconnect-VeeamServer

# Return result
Exit-Json -obj $result
ok: [10.0.2.16] => {
    "my_facts": {
        "changed": false,
        "failed": false,
        "veeam_facts": {
            "veeam_connection": [
                {
                    "port": 9392,
                    "server": "localhost",
                    "user": "VEEAM01\\Administrator"
                }
            ],
            "veeam_credentials": [
                {
                    "description": "Lab User for Standalone Host",
                    "encryptedpassword": null,
                    "id": "ae0fa0f8-d0ed-4014-9e0c-b84d56bc9084",
                    "name": "root",
                    "username": "root"
                }
            ],
            "veeam_repositories": [
                {
                    "description": "Created by Veeam Backup",
                    "friendlypath": "C:\\Backup",
                    "host": "Veeam01",
                    "name": "Default Backup Repository",
                    "type": "Windows"
                }
            ],
            "veeam_servers": [
                {
                    "description": "Backup server",
                    "id": "6745a759-2205-4cd2-b172-8ec8f7e60ef8",
                    "name": "Veeam01",
                    "type": "Microsoft Windows Server"
                },
                {
                    "description": "Created by Powershell at 25.06.2019 22:39:24.",
                    "id": "aedd0693-657c-4384-9e53-cd6bb605a637",
                    "name": "192.168.234.101",
                    "type": "VMware ESXi Server"
                }
            ]
        }
    }
}

A simple Ansible playbook that returns all veeam server facts looks like that:

- name: Get all VBR Facts
  hosts: veeam
  gather_facts: no
  tasks:
  - name: Get Veeam Facts
    veeam_connection_facts:
    register: my_facts
  - name: Debug Veeam Facts
    debug:
        var: my_facts

Veeam Credentials

The next step towards manage servers in Veeam Backup & Replication is adding the required credentials. The veeam_credential module is able to add and remove credentials of the types Windows, Linux and Standard. The return of the add method (state: present) is the ID of the newly created credentials. This ID can be used by other Ansible Veeam Modules.

In addition to the PowerShell script, all Ansible Windows Modules do have a python “script” that contains the documentation of the module.

Ansible Veeam Modules Preview - Module Doc

Please check the Github Project for the detailed syntax.

#!powershell

# Copyright: (c) 2019, Markus Kraus <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#AnsibleRequires -OSVersion 6.2
#AnsibleRequires -CSharpUtil Ansible.Basic

$spec = @{
    options = @{
        type = @{ type = "str"; choices = "windows", "linux", "standard"; default = "standard" }
        username = @{ type = "str" }
        password = @{ type = "str" }
        state = @{ type = "str"; choices = "absent", "present"; default = "present" }
        description = @{ type = "str"; default = "Created by Ansible"}
        id = @{ type = "str"}

    }
    required_if = @(@("state", "present", @("username", "password")),
                    @("state", "absent", @("id")))
    supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0

# Functions
Function Connect-VeeamServer {
    try {
        Add-PSSnapin -PassThru VeeamPSSnapIn -ErrorAction Stop | Out-Null
    }
    catch {
        Fail-Json -obj @{} -message  "Failed to load Veeam SnapIn on the target: $($_.Exception.Message)"  
    }

    try {
        Connect-VBRServer -Server localhost
    }
    catch {
        Fail-Json -obj @{} -message "Failed to connect VBR Server on the target: $($_.Exception.Message)"  
    }
}
Function Disconnect-VeeamServer {
    try {
        Disconnect-VBRServer
    }
    catch {
        Fail-Json -obj @{} -message "Failed to disconnect VBR Server on the target: $($_.Exception.Message)"  
    }
}

# Connect
Connect-VeeamServer

switch ( $module.Params.state) {
    "present" { if ($module.Params.type -eq "standard") {
                    $Cred = Add-VBRCredentials -User $module.Params.username -Password "$($module.Params.password)" -Description "$($module.Params.description)"
                    }
                    else {
                        $Cred = Add-VBRCredentials -Type $module.Params.type -User $module.Params.username -Password "$($module.Params.password)" -Description "$($module.Params.description)"
                    }
                $module.Result.changed = $true
                $module.Result.id = $Cred.id
                }
    "absent" { $RemoveCred = Get-VBRCredentials | Where-Object {$_.id -eq $module.Params.id}
               $Cred = Remove-VBRCredentials -Credential $RemoveCred
               $module.Result.changed = $true
            }
    Default {}
}

# Disconnect
Disconnect-VeeamServer

# Return result
$module.ExitJson()
ok: [10.0.2.16] => {
    "my_cred": {
        "changed": true,
        "failed": false,
        "id": "86a42430-5756-4921-9d06-fd1d56599741"
    }
}

A simple playbook that creates new Veeam Server Credentials looks like that:

Ansible Veeam Modules Preview - Add Credentials
- name: Add new Credentials to VBR Server
  hosts: veeam
  gather_facts: no
  vars:
    query: "veeam_facts.veeam_credentials[?id=='{{ my_cred.id }}']"
    my_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
  tasks:
  - name: Add Credential
    veeam_credential:
        state: present
        type: windows
        username: Administrator
        password: "{{ my_password }}"
        description: My dummy description
    register: my_cred
  - name: Debug Veeam Credentials
    debug:
        var: my_cred
  - name: Get Veeam Facts
    veeam_connection_facts:
    register: my_facts
  - name: Debug Veeam Credential Facts
    debug:
        var: my_facts  | json_query(query)
  - name: Remove Credential
    veeam_credential:
        state: absent
        id: "{{ my_cred.id }}"

Veeam Servers

The next part of my preview is an Ansible module that is able to add servers to the Veeam Backup & Replication configuration. A server in Veeam jargon can be for example a VMware ESXi, a VMware vCenter or a Windows server (more server types). The initial version of the Ansible module can only add VMware ESXi Servers.

Please check the Github Project for the detailed syntax.

#!powershell

# Copyright: (c) 2019, Markus Kraus <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#AnsibleRequires -OSVersion 6.2
#AnsibleRequires -CSharpUtil Ansible.Basic

$spec = @{
    options = @{
        type = @{ type = "str"; choices = "esxi", "vcenter", "windows"; default = "esxi" }
        credential_id = @{ type = "str" }
        state = @{ type = "str"; choices = "absent", "present"; default = "present" }
        name = @{ type = "str" }
        id = @{ type = "str" }

    }
    required_if = @(@("state", "present", @("type", "name", "credential_id")),
                    @("state", "absent", @("id")))
    supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0

# Functions
Function Connect-VeeamServer {
    try {
        Add-PSSnapin -PassThru VeeamPSSnapIn -ErrorAction Stop | Out-Null
    }
    catch {
        Fail-Json -obj @{} -message  "Failed to load Veeam SnapIn on the target: $($_.Exception.Message)"  
            
    }

    try {
        Connect-VBRServer -Server localhost
    }
    catch {
        Fail-Json -obj @{} -message "Failed to connect VBR Server on the target: $($_.Exception.Message)"  
    }
}
Function Disconnect-VeeamServer {
    try {
        Disconnect-VBRServer
    }
    catch {
        Fail-Json -obj @{} -message "Failed to disconnect VBR Server on the target: $($_.Exception.Message)"  
    }
}

# Connect
Connect-VeeamServer

switch ( $module.Params.state) {
    "present" { $Cred = Get-VBRCredentials | Where-Object {$_.id -eq $module.Params.credential_id}
                    switch ($module.Params.type) {
                        "esxi" {    $Server = Add-VBRESXi –Name $module.Params.name -Credentials $Cred
                                    $module.Result.changed = $true
                                    $module.Result.id = $Server.id  
                                }
                        "vcenter" {Fail-Json -obj @{} -message "Type not yet implemented."
                                    }
                        "windows" {Fail-Json -obj @{} -message "Type not yet implemented."
                                    }
                        Default { }
                    }
                }
    "absent" { Fail-Json -obj @{} -message "State not yet implemented."
                }
    Default {}
}

# Disconnect
Disconnect-VeeamServer

# Return result
$module.ExitJson()
ok: [10.0.2.16] => {
    "my_facts.veeam_facts.veeam_servers": [
        {
            "description": "Backup server",
            "id": "6745a759-2205-4cd2-b172-8ec8f7e60ef8",
            "name": "Veeam01",
            "type": "Microsoft Windows Server"
        },
        {
            "description": "Created by Powershell at 25.06.2019 22:39:24.",
            "id": "aedd0693-657c-4384-9e53-cd6bb605a637",
            "name": "192.168.234.101",
            "type": "VMware ESXi Server"
        }
    ]
}

A simple playbook that executes all required tasks to add a new VMware ESXi Server to the Veeam Backup & Replication configuration looks like that:

Ansible Veeam Modules Preview - Add VMware ESXi Server
- name: Add ESXi Host to VBR Server
  hosts: veeam
  gather_facts: no
  vars:
    root_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
  tasks:
  - name: Add root credential
    veeam_credential:
        state: present
        type: standard
        username: root
        password: "{{ root_password }}"
        description: "Lab User for Standalone Host"
    register: root_cred
  - name: Debug root credential
    debug:
        var: root_cred
  - name: Add esxi server
    veeam_server:
        state: present
        type: esxi
        credential_id: "{{ root_cred.id }}"
        name: 192.168.234.101
    register: esxi_server
  - name: Get Veeam Facts
    veeam_connection_facts:
    register: my_facts
  - name: Debug Veeam Servers from Facts
    debug:
        var: my_facts.veeam_facts.veeam_servers

GitHub Project

The Ansible Veeam Modules and example playbooks are available via GitHub. The source code state at the publishing date of this blog post is a dedicated release number:

Feel free to contribute to the project by opening an issue, creating a pull request, leaving a comment or using the feedback form for the Veeam Ansible Module. Every input is welcome!

4 Comments

  1. VJ 14. July 2019
    • Markus Kraus 14. July 2019
  2. Keiran 17. July 2019

Leave a Reply