mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 23:41:58 -06:00
221 lines
10 KiB
YAML
221 lines
10 KiB
YAML
- hosts: all
|
|
gather_facts: false
|
|
tasks:
|
|
- name: Collect summary via PowerShell (hostname, OS, uptime, last reboot)
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$hostname = $env:COMPUTERNAME
|
|
$username = $env:USERNAME
|
|
$domain = $env:USERDOMAIN
|
|
$w = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
|
$product = [string]$w.ProductName
|
|
$display = [string]$w.DisplayVersion
|
|
$build = [string]$w.CurrentBuildNumber
|
|
$os = ($product + ' ' + $display).Trim()
|
|
$boot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
|
|
$uptime = [int]((Get-Date) - $boot).TotalSeconds
|
|
# Produce Last Reboot as UTC string to match UI expectations (YYYY-MM-DD HH:MM:SS)
|
|
$bootUtc = (Get-Date $boot).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss')
|
|
$out = [pscustomobject]@{
|
|
hostname=$hostname;
|
|
operating_system=$os;
|
|
username=$username;
|
|
domain=$domain;
|
|
uptime_sec=$uptime;
|
|
last_reboot=$bootUtc
|
|
}
|
|
$out | ConvertTo-Json -Depth 4
|
|
register: summary_raw
|
|
changed_when: false
|
|
|
|
- name: Parse summary JSON
|
|
ansible.builtin.set_fact:
|
|
_summary: "{{ summary_raw.stdout | from_json | default({}, true) }}"
|
|
|
|
- name: Collect installed software
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
$paths = @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
|
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*')
|
|
$items = foreach ($p in $paths) {
|
|
if (Test-Path $p) {
|
|
Get-ItemProperty -Path $p | Where-Object { $_.DisplayName } | ForEach-Object {
|
|
[pscustomobject]@{ name=[string]$_.DisplayName; version=[string]$_.DisplayVersion }
|
|
}
|
|
}
|
|
}
|
|
$items | Sort-Object name -Unique | ConvertTo-Json -Depth 4
|
|
register: software_raw
|
|
changed_when: false
|
|
|
|
- name: Parse software JSON
|
|
ansible.builtin.set_fact:
|
|
_software: "{{ software_raw.stdout | default('[]') | from_json | default([], true) }}"
|
|
|
|
- name: Collect memory modules
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
Get-CimInstance Win32_PhysicalMemory | ForEach-Object {
|
|
[pscustomobject]@{ slot=$_.BankLabel; speed=[string]$_.Speed; serial=[string]$_.SerialNumber; capacity=[string]$_.Capacity }
|
|
} | ConvertTo-Json -Depth 4
|
|
register: memory_raw
|
|
changed_when: false
|
|
|
|
- name: Parse memory JSON
|
|
ansible.builtin.set_fact:
|
|
_memory: "{{ memory_raw.stdout | default('[]') | from_json | default([], true) }}"
|
|
|
|
- name: Collect storage volumes
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
Get-CimInstance Win32_LogicalDisk | ForEach-Object {
|
|
$total = [double]$_.Size; $free = [double]$_.FreeSpace; $used = $total - $free;
|
|
$usage = if ($total -gt 0) { [math]::Round(($used / $total) * 100, 2) } else { 0 }
|
|
$type = switch ($_.DriveType) { 2 {'Removable'} 3 {'Fixed Disk'} default {'Unknown'} }
|
|
[pscustomobject]@{ drive=$_.DeviceID; disk_type=$type; usage=$usage; total=$total; free=$free; used=$used }
|
|
} | ConvertTo-Json -Depth 4
|
|
register: storage_raw
|
|
changed_when: false
|
|
|
|
- name: Parse storage JSON
|
|
ansible.builtin.set_fact:
|
|
_storage: "{{ storage_raw.stdout | default('[]') | from_json | default([], true) }}"
|
|
|
|
- name: Collect network adapters
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
try {
|
|
$ip = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.IPAddress -and $_.IPAddress -notmatch '^169\.254\.' -and $_.IPAddress -ne '127.0.0.1' }
|
|
$ad = Get-NetAdapter | ForEach-Object { $_ | Select-Object -Property Name, InterfaceAlias, MacAddress }
|
|
$map = @{}; foreach($a in $ad){ $map[$a.InterfaceAlias] = $a.MacAddress }
|
|
$tmp = @{}
|
|
foreach($e in $ip){
|
|
$alias = if ($e.InterfaceAlias) { $e.InterfaceAlias } else { 'unknown' }
|
|
$item = $tmp[$alias]
|
|
if (-not $item) { $item = [pscustomobject]@{ adapter=$alias; ips=@(); mac='' }; $tmp[$alias] = $item }
|
|
$item.mac = $map[$alias]
|
|
if ($e.IPAddress -and $item.ips -notcontains $e.IPAddress) { $item.ips += $e.IPAddress }
|
|
}
|
|
$out = $tmp.GetEnumerator() | ForEach-Object { $_.Value }
|
|
} catch { $out = @() }
|
|
$out | ConvertTo-Json -Depth 4
|
|
register: network_raw
|
|
changed_when: false
|
|
|
|
- name: Parse network JSON
|
|
ansible.builtin.set_fact:
|
|
_network: "{{ network_raw.stdout | default('[]') | from_json | default([], true) }}"
|
|
|
|
- name: Compose device details structure
|
|
ansible.builtin.set_fact:
|
|
device_details:
|
|
summary: "{{ _summary }}"
|
|
software: "{{ _software }}"
|
|
memory: "{{ _memory }}"
|
|
storage: "{{ _storage }}"
|
|
network: "{{ _network }}"
|
|
|
|
- name: Derive internal IP from adapter list
|
|
ansible.builtin.set_fact:
|
|
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'internal_ip': (_network | map(attribute='ips') | list | flatten | select('match','^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.)') | list | first | default('')) })) }) }}"
|
|
|
|
- name: Detect device type (Server/Workstation/Virtual Machine)
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
function _getCim($cls){ try { return Get-CimInstance $cls -ErrorAction Stop } catch { try { return Get-WmiObject -Class $cls -ErrorAction Stop } catch { return $null } } }
|
|
$os = _getCim 'Win32_OperatingSystem'
|
|
$cs = _getCim 'Win32_ComputerSystem'
|
|
$caption = ""; if ($os) { $caption = [string]$os.Caption }
|
|
$model = ""; if ($cs) { $model = [string]$cs.Model }
|
|
$manu = ""; if ($cs) { $manu = [string]$cs.Manufacturer }
|
|
$virt = $false
|
|
if ($model -match 'Virtual' -or $manu -match 'Microsoft Corporation' -and $model -match 'Virtual Machine' -or $manu -match 'VMware' -or $manu -match 'innotek' -or $manu -match 'VirtualBox' -or $manu -match 'QEMU' -or $manu -match 'Xen' -or $manu -match 'Parallels') { $virt = $true }
|
|
if ($virt) { 'Virtual Machine' }
|
|
elseif ($caption -match 'Server') { 'Server' }
|
|
else { 'Workstation' }
|
|
register: device_type_raw
|
|
changed_when: false
|
|
|
|
- name: Attach device type to summary
|
|
ansible.builtin.set_fact:
|
|
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'device_type': (device_type_raw.stdout | default('') | trim) })) }) }}"
|
|
|
|
- name: Collect external IP (best-effort)
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
$ip = ''
|
|
try { $ip = (Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 3).ip } catch {}
|
|
if (-not $ip) { try { $ip = (Invoke-WebRequest -Uri 'https://checkip.amazonaws.com' -TimeoutSec 3).Content.Trim() } catch {} }
|
|
if (-not $ip) { try { $ip = (Invoke-WebRequest -Uri 'https://ifconfig.me/ip' -TimeoutSec 3).Content.Trim() } catch {} }
|
|
$ip
|
|
register: external_ip_raw
|
|
changed_when: false
|
|
|
|
- name: Attach external IP to summary
|
|
ansible.builtin.set_fact:
|
|
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'external_ip': (external_ip_raw.stdout | default('') | trim) })) }) }}"
|
|
|
|
- name: Collect currently logged-in users (interactive + RDP)
|
|
ansible.builtin.shell: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
function Get-InteractiveUsers {
|
|
$users = @()
|
|
try {
|
|
$ls = Get-CimInstance Win32_LogonSession | Where-Object { $_.LogonType -in 2,10 }
|
|
foreach ($sess in $ls) {
|
|
$accs = Get-CimAssociatedInstance -InputObject $sess -Association Win32_LoggedOnUser -ResultClassName Win32_Account
|
|
foreach ($a in $accs) {
|
|
if (-not $a -or -not $a.Name) { continue }
|
|
$nm = [string]$a.Name
|
|
$dm = [string]$a.Domain
|
|
if ($nm -match '\$$') { continue }
|
|
if ($dm -eq 'NT AUTHORITY' -or $dm -eq 'NT SERVICE') { continue }
|
|
if ($nm -like 'DWM-*' -or $nm -like 'UMFD-*') { continue }
|
|
if ($dm) { $users += ("{0}\\{1}" -f $dm,$nm) } else { $users += $nm }
|
|
}
|
|
}
|
|
} catch {}
|
|
$users | Sort-Object -Unique
|
|
}
|
|
|
|
function Get-QuserUsers {
|
|
$list=@()
|
|
try {
|
|
$q = (quser 2>$null) -split "`r?`n"
|
|
foreach ($line in $q) {
|
|
if (-not $line) { continue }
|
|
if ($line -match '^USERNAME') { continue }
|
|
$s = ($line -replace '^>','').Trim()
|
|
if (-not $s) { continue }
|
|
$parts = $s -split '\s+'
|
|
if ($parts.Length -lt 1) { continue }
|
|
$u = $parts[0]
|
|
if (-not $u) { continue }
|
|
if ($u -match '\$$') { continue }
|
|
if ($u -like 'DWM-*' -or $u -like 'UMFD-*') { continue }
|
|
$list += $u
|
|
}
|
|
} catch {}
|
|
$list | Sort-Object -Unique
|
|
}
|
|
|
|
$u1 = Get-InteractiveUsers
|
|
$u2 = Get-QuserUsers
|
|
$combined = @()
|
|
foreach ($u in $u1) { if ($combined -notcontains $u) { $combined += $u } }
|
|
foreach ($u in $u2) { if ($combined -notcontains $u) { $combined += $u } }
|
|
if ($combined.Count -eq 0) { 'No Users Logged In' } else { $combined -join ', ' }
|
|
register: last_user_raw
|
|
changed_when: false
|
|
|
|
- name: Attach last_user string to summary
|
|
ansible.builtin.set_fact:
|
|
device_details: "{{ device_details | combine({'summary': (device_details.summary | combine({'last_user': (last_user_raw.stdout | default('') | trim) })) }) }}"
|
|
|
|
- name: Write device details JSON
|
|
ansible.builtin.copy:
|
|
content: "{{ device_details | to_nice_json }}"
|
|
dest: "{{ output_file }}"
|
|
run_once: true
|