mirror of
https://git.ecdf.ed.ac.uk/win_automation/windows-ansible.git
synced 2026-10-26 16:17:21 +00:00
win_update updated
This commit is contained in:
1 parent
f36bd67c5c
commit
4b74f26c01
4 files changed
+209
-29
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- hosts: "{{ target_host }}"
|
- hosts: "meta-os_windows"
|
||||||
gather_facts: true
|
gather_facts: false
|
||||||
vars_files:
|
vars_files:
|
||||||
- ../vars/common.yml
|
- ../vars/common.yml
|
||||||
- ../vars/connection.yml
|
- ../vars/connection.yml
|
||||||
|
|||||||
@@ -1,30 +1,65 @@
|
|||||||
---
|
---
|
||||||
- name: Update VM
|
- name: Install client VM updates and reboot as much as needed
|
||||||
tags: update_vm
|
|
||||||
block:
|
block:
|
||||||
- name: Clear update cache
|
- name: Install Windows updates (looped)
|
||||||
win_shell: |
|
tags: update_windows
|
||||||
UsoClient RefreshSettings
|
|
||||||
UsoClient ScanInstallWait
|
|
||||||
|
|
||||||
- name: Update Windows
|
|
||||||
ansible.windows.win_updates:
|
ansible.windows.win_updates:
|
||||||
category_names: '*'
|
category_names:
|
||||||
skip_optional: true
|
- CriticalUpdates
|
||||||
|
- DefinitionUpdates
|
||||||
|
- SecurityUpdates
|
||||||
|
- UpdateRollups
|
||||||
reboot: true
|
reboot: true
|
||||||
reboot_timeout: 3600
|
register: update_result
|
||||||
ignore_errors: true
|
retries: 5
|
||||||
|
delay: 30
|
||||||
|
until: update_result.reboot_required == false
|
||||||
|
|
||||||
- name: Update Windows (again)
|
- name: Wait for host to come back online
|
||||||
|
tags: update_windows
|
||||||
|
ansible.builtin.wait_for_connection:
|
||||||
|
timeout: 600
|
||||||
|
|
||||||
|
- name: Update all choco packages
|
||||||
|
win_command: choco upgrade all -y
|
||||||
|
tags: update_choco
|
||||||
|
|
||||||
|
- name: Update Start Menu Shortcuts in XML on remote Windows machines
|
||||||
|
tags: update_start_menu
|
||||||
|
block:
|
||||||
|
- name: Copy Python script to target machine
|
||||||
|
ansible.windows.win_template:
|
||||||
|
src: "{{ script_name }}"
|
||||||
|
dest: "{{ script_path }}"
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Copy current XML to backup location
|
||||||
|
win_copy:
|
||||||
|
remote_src: true
|
||||||
|
src: "{{ start_menu_xml }}"
|
||||||
|
dest: "{{ backup_xml }}"
|
||||||
|
|
||||||
|
- name: Run Python script on remote machine
|
||||||
|
win_command: python "{{ script_path }}.xml"
|
||||||
|
when: "inventory_hostname in groups['meta-type_client']|default([])"
|
||||||
|
|
||||||
|
- name: Install Server VM updates and reboot as much as needed
|
||||||
|
tags: update_windows
|
||||||
|
block:
|
||||||
|
- name: Install Windows updates (looped)
|
||||||
ansible.windows.win_updates:
|
ansible.windows.win_updates:
|
||||||
category_names: '*'
|
category_names:
|
||||||
|
- CriticalUpdates
|
||||||
|
- DefinitionUpdates
|
||||||
|
- SecurityUpdates
|
||||||
|
- UpdateRollups
|
||||||
reboot: true
|
reboot: true
|
||||||
reboot_timeout: 3600
|
register: update_result
|
||||||
ignore_errors: true
|
retries: 5
|
||||||
|
delay: 30
|
||||||
|
until: update_result.reboot_required == false
|
||||||
|
|
||||||
- name: Logging action
|
- name: Wait for host to come back online
|
||||||
vars:
|
ansible.builtin.wait_for_connection:
|
||||||
ansible_connection: local
|
timeout: 600
|
||||||
ansible.builtin.shell:
|
when: "inventory_hostname in groups['meta-type_server']"
|
||||||
cmd: echo "Update {{ os_project }}-{{ ansible_hostname }} $(date '+%Y-%m-%d %H:%M:%S')" >> /deployments/{{ os_project }}-{{ win_project }}/action.log
|
|
||||||
delegate_to: localhost
|
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
original_xml = "{{ start_menu_xml }}"
|
||||||
|
backup_xml = "{{ backup_xml }}"
|
||||||
|
|
||||||
|
# This script assumes backup and copying already handled by Ansible
|
||||||
|
|
||||||
|
START_MENU_PATHS = {
|
||||||
|
"%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs": os.path.expandvars(r"%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs")
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_version(filename):
|
||||||
|
match = re.search(r'(\d+\.\d+\.\d+|\d+)', filename)
|
||||||
|
if match:
|
||||||
|
return tuple(map(int, match.group(0).split('.')))
|
||||||
|
return (0,)
|
||||||
|
|
||||||
|
def adjust_app_name(app_name):
|
||||||
|
return f"{app_name} " if len(app_name) == 1 else app_name
|
||||||
|
|
||||||
|
def remove_bracketed_text(filename):
|
||||||
|
return re.sub(r"[\(\[].*?[\)\]]", "", filename).strip()
|
||||||
|
|
||||||
|
def folder_has_standalone_version(folder_name):
|
||||||
|
return bool(re.search(r'\b\d+(?:\.\d+){1,3}\b', folder_name))
|
||||||
|
|
||||||
|
def clean_folder_name(folder_name):
|
||||||
|
return re.sub(r'\b\d+(?:\.\d+){1,3}\b', '', folder_name).strip()
|
||||||
|
|
||||||
|
def remove_version_number(app_name):
|
||||||
|
if re.search(r'\d+\.\d+(\.\d+)*$', app_name):
|
||||||
|
return re.sub(r'(\s*\d+\.\d+(\.\d+)*\s*)$', '', app_name).strip()
|
||||||
|
return app_name
|
||||||
|
|
||||||
|
def is_extra_word_present(filename, base_name):
|
||||||
|
name_without_ext = os.path.splitext(filename)[0].lower()
|
||||||
|
return name_without_ext != base_name.lower()
|
||||||
|
|
||||||
|
def version_sort_key(filename, base_name):
|
||||||
|
version = extract_version(filename)
|
||||||
|
cleaned_name = remove_bracketed_text(filename)
|
||||||
|
padded_version = version + (0,) * (3 - len(version))
|
||||||
|
return (
|
||||||
|
is_extra_word_present(filename, base_name),
|
||||||
|
len(cleaned_name),
|
||||||
|
-padded_version[0],
|
||||||
|
-padded_version[1] if len(padded_version) > 1 else 0,
|
||||||
|
-padded_version[2] if len(padded_version) > 2 else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_clean_base_name(app_name):
|
||||||
|
return re.sub(r"(\s*\d+(\.\d+)*\s*)+$", "", app_name).strip()
|
||||||
|
|
||||||
|
def find_best_match(app_name, available_files):
|
||||||
|
base_name = get_clean_base_name(app_name)
|
||||||
|
adjusted_name = adjust_app_name(base_name)
|
||||||
|
start_matches = [file for file in available_files if file.lower().startswith(adjusted_name.lower())]
|
||||||
|
exact_match = [f for f in start_matches if os.path.splitext(f)[0].lower() == adjusted_name.lower()]
|
||||||
|
if exact_match:
|
||||||
|
print(f"Exact match for {app_name}: {exact_match[0]}")
|
||||||
|
return exact_match[0]
|
||||||
|
best = min(start_matches, key=lambda f: version_sort_key(f, adjusted_name)) if start_matches else None
|
||||||
|
print(f"Best match for {app_name}: {best}")
|
||||||
|
return best
|
||||||
|
|
||||||
|
def convert_to_env_path(abs_path):
|
||||||
|
for env_path, abs_root in START_MENU_PATHS.items():
|
||||||
|
if abs_path.startswith(abs_root):
|
||||||
|
return abs_path.replace(abs_root, env_path)
|
||||||
|
return abs_path
|
||||||
|
|
||||||
|
def find_shortcut_paths(app_names):
|
||||||
|
app_paths = {}
|
||||||
|
all_lnk_files = {}
|
||||||
|
|
||||||
|
for _, start_menu in START_MENU_PATHS.items():
|
||||||
|
for root, _, files in os.walk(start_menu):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(".lnk"):
|
||||||
|
full_path = os.path.join(root, file)
|
||||||
|
all_lnk_files[file] = full_path
|
||||||
|
|
||||||
|
for app_name in app_names:
|
||||||
|
best_match = find_best_match(app_name, all_lnk_files.keys())
|
||||||
|
if best_match:
|
||||||
|
env_path = convert_to_env_path(all_lnk_files[best_match])
|
||||||
|
app_paths[app_name] = env_path
|
||||||
|
print(f"Mapped {app_name} to {env_path}")
|
||||||
|
else:
|
||||||
|
print(f"No match found for {app_name}")
|
||||||
|
|
||||||
|
return app_paths
|
||||||
|
|
||||||
|
def extract_app_names_and_paths(xml_file):
|
||||||
|
tree = ET.parse(xml_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
apps = {}
|
||||||
|
|
||||||
|
for tile in root.findall(".//{*}DesktopApplicationTile"):
|
||||||
|
link_path = tile.get("DesktopApplicationLinkPath")
|
||||||
|
if link_path:
|
||||||
|
folder_name = os.path.basename(os.path.dirname(link_path))
|
||||||
|
lnk_name = os.path.splitext(os.path.basename(link_path))[0]
|
||||||
|
|
||||||
|
if folder_has_standalone_version(folder_name):
|
||||||
|
app_name = clean_folder_name(folder_name)
|
||||||
|
else:
|
||||||
|
app_name = remove_bracketed_text(lnk_name)
|
||||||
|
|
||||||
|
app_name = remove_version_number(app_name)
|
||||||
|
app_name = adjust_app_name(app_name)
|
||||||
|
apps[app_name] = link_path
|
||||||
|
print(f"Extracted {app_name} from {link_path}")
|
||||||
|
|
||||||
|
return apps
|
||||||
|
|
||||||
|
def update_xml_paths(xml_file, old_new_paths):
|
||||||
|
with open(xml_file, "r", encoding="utf-8") as file:
|
||||||
|
content = file.read()
|
||||||
|
|
||||||
|
for old_path, new_path in old_new_paths:
|
||||||
|
content = content.replace(old_path, new_path)
|
||||||
|
print(f"Replaced {old_path} with {new_path}")
|
||||||
|
|
||||||
|
with open(xml_file, "w", encoding="utf-8") as file:
|
||||||
|
file.write(content)
|
||||||
|
|
||||||
|
print("Start menu XML updated successfully.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
xml_file = original_xml
|
||||||
|
extracted_apps_with_paths = extract_app_names_and_paths(xml_file)
|
||||||
|
app_shortcuts = find_shortcut_paths(extracted_apps_with_paths.keys())
|
||||||
|
|
||||||
|
old_new_paths = []
|
||||||
|
for app_name, old_path in extracted_apps_with_paths.items():
|
||||||
|
new_path = app_shortcuts.get(app_name)
|
||||||
|
if new_path:
|
||||||
|
old_new_paths.append((old_path, new_path))
|
||||||
|
else:
|
||||||
|
print(f"Warning: No new path found for {app_name}. Keeping old path.")
|
||||||
|
|
||||||
|
update_xml_paths(xml_file, old_new_paths)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
services:
|
start_menu_xml: "C:\\eidf\\{{ win_project }}.xml"
|
||||||
- wuauserv
|
backup_xml: "C:\\eidf\\{{ win_project }}-old.xml"
|
||||||
- bits
|
script_name: newXML.py
|
||||||
- cryptSvc
|
script_path: "C:\\eidf\\{{ script_name }}"
|
||||||
- msiserver
|
|
||||||
Reference in new issue
Block a user