Ansible – Workstation Software Updating – Part 2

Finally completed.

Ok, finally updated a few key entries to update based on a dynamic URL.

In your ansible folder you’ll require a Template Folder for the ultravnc.inf to be created from ultravnc.inf.j2 file. see the files contents at the bottom of this post.

Here is the playbook I currently have together.

---
- name: Silently update Windows software using Chocolatey
  hosts:
    - windows
  gather_facts: no
  tasks:
    - name: Remove PowerShell Execution Policy from Group Policy
      win_regedit:
        path: HKLM:\Software\Policies\Microsoft\Windows\PowerShell
        name: ExecutionPolicy
        state: absent

    - name: Ensure C:\temp exists
      ansible.windows.win_file:
        path: C:\temp
        state: directory

    - name: Check if Chocolatey is installed
      win_stat:
        path: C:\ProgramData\chocolatey\bin\choco.exe
      register: choco_installed

    - name: Set execution policy to allow script execution
      win_shell: Set-ExecutionPolicy Bypass -Scope Process -Force

    - name: Install Chocolatey
      win_shell: |
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Set-ExecutionPolicy Bypass -Scope Process -Force
        iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
      args:
        executable: powershell.exe
      when: not choco_installed.stat.exists
      register:

    - name: Upgrade Chocolatey
      win_shell: choco upgrade chocolatey -y
      args:
        executable: cmd.exe
      when: choco_installed.stat.exists
      register: choco_upgrade

    - name: Show upgrade result
      debug:
       var: choco_upgrade.stdout_lines
      when: choco_installed.stat.exists
##########################################################################

    - name: Copy Chrome installer to Windows UNC path
      win_get_url:
        url: "https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi"
        dest: "C:\\temp\\chrome.msi"  # UNC path for shared location
        force: yes

    - name: Copy Firefox ESR installer to Windows UNC path
      win_get_url:
        url: "https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=win64&lang=en-US"
        dest: "C:\\temp\\FirefoxESR.exe"  # UNC path for shared location
        force: yes

    - name: Download Visual C++ Redistributable
      win_get_url:
        url: https://aka.ms/vs/17/release/vc_redist.x64.exe
        dest: C:\temp\vc_redist.x64.exe

    - name: Download PowerShell 7 installer
      win_get_url:
        url: https://github.com/PowerShell/PowerShell/releases/download/v7.5.0/PowerShell-7.5.0-win-x64.msi
        dest: C:\\temp\\PowerShell-x64.msi


    - name: Download TortoiseGit Installer
      win_get_url:
        url: "https://download.tortoisegit.org/tgit/2.17.0.0/TortoiseGit-2.17.0.2-64bit.msi"
        dest: C:\\temp\\TortoiseGit.msi
#########################################################################
    - name: Run OfficeC2RClient update command
      win_command: '"C:\Program Files\Common Files\microsoft shared\ClickToRun\OfficeC2RClient.exe" /update user displaylevel=false forceappshutdown=true'
      ignore_errors: yes

    - name: Verify EXE file exists
      win_stat:
        path: "C:\\temp\\chrome.msi"
      register: chrome_msi

    - name: Fail if MSI file is missing or empty
      fail:
        msg: "Chrome MSI download failed or file is missing."
      when: not chrome_msi.stat.exists or chrome_msi.stat.size == 0

    - name: Install Chrome MSI
      win_package:
         path: "C:\\temp\\chrome.msi"
         arguments: "/qn /norestart"
         state: present
      register: install_chrome
      ignore_errors: yes

    - name: Ensure no other MSI installations are running
      win_shell: |
       $count = 0
       while (Get-Process -Name "msiexec" -ErrorAction SilentlyContinue) {
         Start-Sleep -Seconds 10
         $count++
         if ($count -ge 30) { Stop-Process -Name "msiexec" -Force }
       }
      register: msiexec_check
      changed_when: msiexec_check.rc == 0

    - name: Verify EXE file exists
      win_stat:
        path: "C:\\temp\\FirefoxESR.exe"
      register: firefox_exe

    - name: Fail if EXE file is missing or empty
      fail:
        msg: "Firefox EXE download failed or file is missing."
      when: not firefox_exe.stat.exists or firefox_exe.stat.size == 0

    - name: Wait for WinRM to stabilize
      wait_for:
        port: 5985
        delay: 10
        timeout: 120
        host: "{{ inventory_hostname }}"
      delegate_to: localhost

    - name: Install Firefox ESR using EXE
      win_command: 'C:\\temp\\FirefoxESR.exe /S'
      async: 600         # Max runtime in seconds
      poll: 0            # Fire and forget
      register: install_firefox

    - name: Upgrade Microsoft Edge using Chocolatey
      win_chocolatey:
        name: microsoft-edge
        state: latest

    - name: Install Visual C++ Redistributable
      win_command: C:\\temp\\vc_redist.x64.exe /quiet /norestart
      register: vc_install
      ignore_errors: yes
      failed_when: vc_install.rc not in [0, 3010]

    - name: Install TortoiseGit
      win_package:
        path: C:\\temp\\TortoiseGit.msi
        state: present
        arguments: /quiet /norestart

    - name: Install PowerShell 7 silently
      win_package:
        path: C:\\temp\\PowerShell-x64.msi
        product_id: PowerShell
        state: present
        arguments: /quiet /norestart

    - name: Ensure PuTTY is installed and updated via Chocolatey
      win_chocolatey:
        name: putty
        state: latest
        timeout: 3600

    - name: Install 7-Zip via Chocolatey
      win_chocolatey:
        name: 7zip.install
        state: present
        timeout: 600

    - name: Install Python using Chocolatey
      win_chocolatey:
        name: python
        state: latest

    - name: Uninstall older Python versions while keeping the latest
      win_shell: |
        $python_versions = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name LIKE 'Python%'" | Sort-Object Version -Descending
        if ($python_versions.Count -gt 1) {
            $old_versions = $python_versions[1..$python_versions.Count] # Exclude the latest version
            foreach ($version in $old_versions) {
                Write-Output "Uninstalling: $($version.Name) - $($version.Version)"
                $version.Uninstall() | Out-Null
            }
        } else {
            Write-Output "Only one Python version detected. No need to uninstall."
        }
      args:
        executable: powershell.exe


    - name: Silently update Notepad++
      win_chocolatey:
        name: notepadplusplus
        state: latest

    - name: Silently update Git
      win_chocolatey:
        name: git
        state: latest

    - name: Silently update WinSCP
      win_chocolatey:
        name: winscp
        state: latest

    - name: Install FileZilla
      win_chocolatey:
        name: filezilla
        state: present

    - name: Create UltraVNC INF file
      win_template:
        src: ultravnc_setup.inf.j2
        dest: C:\Temp\ultravnc.inf
        force: yes

 #   - name: Ensure the correct file is copied
 #     template:
 #       src: templates/ultravnc_setup.inf.j2
 #       dest: C:\Temp\ultravnc.inf
 #       force: yes

    - name: Install UltraVNC with custom parameters
      win_chocolatey:
        name: ultravnc
        state: latest
        install_args: '/loadinf="C:\\temp\\ultravnc.inf" /verysilent'
        force: yes

    - name: Check if UltraVNC service exists
      win_shell: |
        Get-Service | Where-Object { $_.Name -like '*uvnc*' }
      register: uvnc_service
      ignore_errors: yes

    - name: Stop UltraVNC service if found
      win_service:
        name: '*uvnc*'
        state: stopped
      when: uvnc_service.stdout != ""

    - name: Disable UltraVNC service if found
      win_service:
        name: '*uvnc*'
        start_mode: disabled
      when: uvnc_service.stdout != ""

#    - name: Remove UltraVNC executable if service is not found
#      win_file:
#        path: "C:\\Program Files\\uvnc bvba\\UltraVNC\\winvnc.exe"
#        state: absent
#      when: uvnc_service.stdout == ""

    - name: Parse latest version from Advanced IP Scanner download page using curl
      win_shell: |
         $html = curl.exe -s "https://www.advanced-ip-scanner.com/download/"
         $match = [regex]::Match($html, 'Advanced_IP_Scanner_(\d+\.\d+\.\d+\.\d+)\.exe')
         if ($match.Success) {
           $match.Groups[1].Value
         } else {
           "Not found"
         }
      register: download_version

    - name: Show download Version
      debug:
        var: download_version.stdout

    - name: Show download Version
      debug:
        msg: "{{ download_version.stdout | regex_replace('\\r\\n$', '') }}"

    - name: Build the download URL
      set_fact:
        download_url: "https://download.advanced-ip-scanner.com/download/files/Advanced_IP_Scanner_{{ download_version.stdout | regex_replace('\\r\\n$', '') }}.exe"

    - name: Show download URL
      debug:
        msg: "{{ download_url }}"


    - name: Download Advanced IP Scanner installer to target
      win_get_url:
        url: "{{ download_url }}"
        dest: C:\temp\AdvancedIPScannerSetup.exe

    - name: Install Advanced IP Scanner using EXE
      win_command: "C:\\temp\\AdvancedIPScannerSetup.exe /VERYSILENT /NORESTART /LOG=C:\\windows\\Temp\\AdvIPScanner.log"
      register: install_result
      ignore_errors: yes

    - name: Install Wireshark using Chocolatey
      win_chocolatey:
        name: wireshark
        state: present

    - name: Reboot Workstations
      win_reboot:
        msg: "Rebooting Workstation"
        reboot_timeout: 600
      when: vc_install.rc == 3010
      register: reboot_result

    - name: Show reboot status
      debug:
        msg: "Workstation was rebooted: {{ reboot_result.rebooted | default(false) }}"
      when: vc_install.rc == 3010

And here is the content of ultravnc.inf.j2 file.

This is because I want to install just the UltraVNC Viewer and not the complete Server Instance.

[Setup]
Lang=en
Group=UltraVNC
NoIcons=1
SetupType=custom
Components=ultravnc_viewer