65 lines
4.5 KiB
Python
65 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
# rebuild-and-push.py — rebuild the "machines not in GuruRMM" list using the CORRECT
|
|
# ScreenConnect online signal (ActiveConnections ProcessType==2 = guest agent connected;
|
|
# GuestInfoUpdateTime for recency), NOT Syncro's stale last_synced_at. Then push the
|
|
# generic STAGING installer to every machine that is ONLINE right now (it will actually
|
|
# land instead of queuing). Machines enroll into Staging -> reassign-staging.py sorts them.
|
|
#
|
|
# Env: RMM, TOK (GuruRMM), SK (Syncro), SC_SECRET (ScreenConnect). --dry = no pushes.
|
|
import json,urllib.request,ssl,os,sys,base64,datetime,urllib.parse
|
|
sys.stdout.reconfigure(encoding='utf-8',errors='replace')
|
|
RMM=os.environ["RMM"]; TOK=os.environ["TOK"]; SK=os.environ["SK"]; SEC=os.environ["SC_SECRET"]
|
|
ctx=ssl.create_default_context(); DRY="--dry" in sys.argv
|
|
SB="https://computerguru.screenconnect.com/App_Extensions/2d558935-686a-4bd0-9991-07539f5fe749/Service.ashx"
|
|
STAGING_ONELINER="irm 'https://rmm.azcomputerguru.com/install/DARK-STORM-3150/windows'|iex"
|
|
ENC=base64.b64encode(STAGING_ONELINER.encode("utf-16-le")).decode()
|
|
PUSHCMD=f"powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {ENC}"
|
|
def scp(m,b):
|
|
req=urllib.request.Request(f"{SB}/{m}",data=json.dumps(b).encode(),method="POST",headers={"CTRLAuthHeader":SEC,"Origin":"https://computerguru.screenconnect.com","Content-Type":"application/json"})
|
|
try:return json.loads(urllib.request.urlopen(req,context=ctx,timeout=25).read().decode("utf-8","replace"))
|
|
except Exception as e:return {"__err":str(e)}
|
|
def rget(p):
|
|
return json.loads("".join(c for c in urllib.request.urlopen(urllib.request.Request(f"{RMM}{p}",headers={"Authorization":f"Bearer {TOK}"}),context=ctx,timeout=30).read().decode("utf-8","replace") if ord(c)>=32 or c in "\t\n\r"))
|
|
def assets(cid):
|
|
raw=urllib.request.urlopen(urllib.request.Request(f"https://computerguru.syncromsp.com/api/v1/customer_assets?customer_id={cid}&per_page=100&api_key={SK}"),context=ctx,timeout=25).read()
|
|
raw="".join(chr(b) for b in raw if b>=32 or b in (9,10,13))
|
|
return [a["name"] for a in json.loads(raw).get("assets",[]) if a.get("asset_type")=="Syncro Device" and a.get("name")]
|
|
def online(s): return any(c.get("ProcessType")==2 for c in (s.get("ActiveConnections") or []))
|
|
def recency_days(s):
|
|
for f in ("GuestInfoUpdateTime","LastGuestConnectedEventTime"):
|
|
t=s.get(f)
|
|
if t and not str(t).startswith("0001"):
|
|
try: return (datetime.datetime.utcnow()-datetime.datetime.strptime(str(t)[:19],"%Y-%m-%dT%H:%M:%S")).days
|
|
except: pass
|
|
return None
|
|
rmm=set(a["hostname"].lower() for a in rget("/api/agents") if a.get("hostname"))
|
|
tgts=json.load(open("projects/gps-rmm-audit/targets.json"))["clients"]
|
|
groups={"ONLINE now":[],"active <=14d":[],"stale 15-45d":[],"dead/no-session":[]}
|
|
pushed=0; failed=0
|
|
for t in tgts:
|
|
for h in assets(t["cid"]):
|
|
if h.lower() in rmm: continue
|
|
ss=scp("GetSessionsByName",{"sessionName":h})
|
|
if not (isinstance(ss,list) and ss): groups["dead/no-session"].append((t["client"],h,"no SC")); continue
|
|
if len(ss)>1: continue # ambiguous shared name -> skip
|
|
s=ss[0]; d=recency_days(s)
|
|
if online(s):
|
|
groups["ONLINE now"].append((t["client"],h,f"{d}d" if d is not None else "?"))
|
|
if not DRY:
|
|
r=scp("SendCommandToSession",[s["SessionID"],PUSHCMD])
|
|
if isinstance(r,dict) and r.get("__err"): failed+=1
|
|
else: pushed+=1
|
|
elif d is not None and d<=14: groups["active <=14d"].append((t["client"],h,f"{d}d"))
|
|
elif d is not None and d<=45: groups["stale 15-45d"].append((t["client"],h,f"{d}d"))
|
|
else: groups["dead/no-session"].append((t["client"],h,f"{d}d" if d is not None else "old"))
|
|
out=["# Machines not in GuruRMM — by ScreenConnect reachability (CORRECTED)","",
|
|
"Online = ActiveConnections ProcessType==2 (guest agent connected). Recency = GuestInfoUpdateTime.",
|
|
f"Rebuilt {datetime.date(2026,7,4)}. Staging installer pushed to all ONLINE machines (land in Staging -> reassign-staging.py).",""]
|
|
for g,items in groups.items():
|
|
out.append(f"## {g} ({len(items)})")
|
|
for c,h,d in sorted(items): out.append(f"- {h} [{c}] ({d})")
|
|
out.append("")
|
|
open("projects/gps-rmm-audit/needs-screenconnect.md","w",encoding="utf-8").write("\n".join(out))
|
|
print(f"ONLINE now: {len(groups['ONLINE now'])} | active<=14d: {len(groups['active <=14d'])} | stale15-45: {len(groups['stale 15-45d'])} | dead: {len(groups['dead/no-session'])}")
|
|
print(f"Staging installer pushed: {pushed} (failed {failed}){' [DRY]' if DRY else ''}")
|