sync: auto-sync from HOWARD-HOME at 2026-07-06 16:10:24
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-07-06 16:10:24
This commit is contained in:
@@ -13,13 +13,18 @@ extension. Read-only by default; writes gated behind `--confirm`.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
SC="bash $CLAUDETOOLS_ROOT/.claude/scripts/py.sh C:/claudetools/.claude/skills/screenconnect/scripts/sc.py"
|
SC="bash $CLAUDETOOLS_ROOT/.claude/scripts/py.sh C:/claudetools/.claude/skills/screenconnect/scripts/sc.py"
|
||||||
$SC status # auth check + instance info
|
$SC status # auth check + fleet counts
|
||||||
$SC sessions --name "<hostname>" # find sessions by Name
|
$SC sessions # WHOLE Access fleet (all agents)
|
||||||
$SC session <sessionID> # full session detail
|
$SC sessions --company "X" # one client's machines (CP1)
|
||||||
|
$SC sessions --like DELL # partial-name match
|
||||||
|
$SC sessions --name "<hostname>" # exact Name match
|
||||||
|
$SC sessions --filter "SessionType = 'Access'" # raw session-filter expression
|
||||||
|
$SC sessions --limit 25 # cap printed rows (--json is always full)
|
||||||
|
$SC session <sessionID> # full session detail
|
||||||
$SC build-installer --platform msi --name HOST --company "X" --site "Y" --tag "Z"
|
$SC build-installer --platform msi --name HOST --company "X" --site "Y" --tag "Z"
|
||||||
$SC send-command --session <id> --command "..." --confirm
|
$SC send-command --session <id> --command "..." --confirm
|
||||||
$SC set-properties --session <id> --props-json '["Company","Site","Tag"]' --confirm
|
$SC set-properties --session <id> --props-json '["Company","Site","Tag"]' --confirm
|
||||||
$SC raw --method GetSessionsByName --body '{"sessionName":""}'
|
$SC raw --method GetSessionsByFilter --body '{"sessionFilter":"SessionType = '"'"'Access'"'"'"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Transport auto-selects httpx, else stdlib urllib (no hard dependency).
|
Transport auto-selects httpx, else stdlib urllib (no hard dependency).
|
||||||
@@ -56,18 +61,24 @@ Company/Site/Tag, ran a command, re-tagged via set-properties).
|
|||||||
## Method surface (probed live 2026-06-22)
|
## Method surface (probed live 2026-06-22)
|
||||||
|
|
||||||
**Available (CLI-exposed):**
|
**Available (CLI-exposed):**
|
||||||
- Reads: `GetSessionsByName` (matches the Name field; "" -> blank-name sessions),
|
- Full-fleet listing: `GetSessionsByFilter` `{"sessionFilter":"<expr>"}` (probed live
|
||||||
`GetSessionDetailsBySessionID`, `GetSessionBySessionID`.
|
2026-07-06) - THE enumeration path. `SessionType = 'Access'` returns every
|
||||||
|
unattended agent (958 on this instance); `SessionType = 'Support'` the on-demand
|
||||||
|
sessions (24). Filter language: single-quoted literals, `=`/`LIKE` (with `*`
|
||||||
|
wildcards), `AND`/`OR`. Per-client `CustomProperty1 = 'Safesite'`; partial name
|
||||||
|
`Name LIKE '*DELL*'`. The CLI `sessions` command wraps this (default = whole fleet).
|
||||||
|
- Reads: `GetSessionsByName` (EXACT Name match; "" -> only blank-name sessions, NOT
|
||||||
|
the fleet - use the filter for listing), `GetSessionDetailsBySessionID`,
|
||||||
|
`GetSessionBySessionID`.
|
||||||
- Writes (gated): `SendCommandToSession` `[sessionID, command]`,
|
- Writes (gated): `SendCommandToSession` `[sessionID, command]`,
|
||||||
`SendMessageToSession` `[sessionID, message]`,
|
`SendMessageToSession` `[sessionID, message]`,
|
||||||
`UpdateSessionCustomProperties` `[sessionID, [cp1,cp2,cp3,...]]`,
|
`UpdateSessionCustomProperties` `[sessionID, [cp1,cp2,cp3,...]]`,
|
||||||
`CreateSession` (array; not the primary path - the installer creates access sessions).
|
`CreateSession` (array; not the primary path - the installer creates access sessions).
|
||||||
|
|
||||||
**MISSING on this extension version (NOT a deploy/control blocker):**
|
**Absent aliases (not needed):**
|
||||||
- `GetSessions` / `GetAllSessions` / `GetSessionGroups` (full-fleet inventory) ->
|
- `GetSessions` / `GetAllSessions` / `GetSessionGroups` -> "web method does not
|
||||||
"web method does not exist". Listing ALL agents needs Mike to update the RESTful
|
exist". No extension update required: `GetSessionsByFilter` covers full-fleet
|
||||||
API Manager extension to expose a list-all method. Until then, find sessions by
|
inventory. (Earlier notes flagged this as a gap pending Mike; it is resolved.)
|
||||||
Name (the installer sets the Name = machine name, so by-name lookup works).
|
|
||||||
|
|
||||||
## Safety gating
|
## Safety gating
|
||||||
|
|
||||||
@@ -121,5 +132,5 @@ Advance via RMM_THOUGHTS -> `/shape-spec`. Do NOT build until Mike gives the go.
|
|||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
Verified method/param spec, auth, installer params, and the GetSessions gap:
|
Verified method/param spec, auth, installer params, and the session-filter language:
|
||||||
`references/api-reference.md`.
|
`references/api-reference.md`.
|
||||||
|
|||||||
@@ -22,14 +22,27 @@ probing 2026-06-22.
|
|||||||
|
|
||||||
| Method | Params | Status | Notes |
|
| Method | Params | Status | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `GetSessionsByName` | `{"sessionName":"<name>"}` | VERIFIED | Matches the session Name field. "" returns blank-Name (unattended) sessions. CLI `sessions`. |
|
| `GetSessionsByFilter` | `{"sessionFilter":"<expr>"}` | VERIFIED | **Full-fleet listing.** Session-filter expression. `SessionType = 'Access'` -> 958 agents; `SessionType = 'Support'` -> 24. CLI `sessions`. See filter syntax below. |
|
||||||
|
| `GetSessionsByName` | `{"sessionName":"<name>"}` | VERIFIED | EXACT Name match. "" returns only blank-Name sessions (NOT the fleet). CLI `sessions --name`. |
|
||||||
| `GetSessionDetailsBySessionID` | `{"sessionID":"<id>"}` | VERIFIED | Full session object (CustomPropertyValues, ActiveConnections, ...). CLI `session`. |
|
| `GetSessionDetailsBySessionID` | `{"sessionID":"<id>"}` | VERIFIED | Full session object (CustomPropertyValues, ActiveConnections, ...). CLI `session`. |
|
||||||
| `GetSessionBySessionID` | `{"sessionID":"<id>"}` | VERIFIED | Returns the session (or [] if none). |
|
| `GetSessionBySessionID` | `{"sessionID":"<id>"}` | VERIFIED | Returns the session (or [] if none). |
|
||||||
| `SendCommandToSession` | `["<sessionID>","<command>"]` | VERIFIED (gated) | Runs a backstage command on the guest. CLI `send-command`. STATE-CHANGING. |
|
| `SendCommandToSession` | `["<sessionID>","<command>"]` | VERIFIED (gated) | Runs a backstage command on the guest. CLI `send-command`. STATE-CHANGING. |
|
||||||
| `SendMessageToSession` | `["<sessionID>","<message>"]` | wired (array) | Chat message to the guest. CLI `send-message`. STATE-CHANGING. |
|
| `SendMessageToSession` | `["<sessionID>","<message>"]` | wired (array) | Chat message to the guest. CLI `send-message`. STATE-CHANGING. |
|
||||||
| `UpdateSessionCustomProperties` | `["<sessionID>",["cp1","cp2","cp3",...]]` | VERIFIED (gated) | Set CP1=Company/CP2=Site/CP3=Tag. CLI `set-properties`. STATE-CHANGING. |
|
| `UpdateSessionCustomProperties` | `["<sessionID>",["cp1","cp2","cp3",...]]` | VERIFIED (gated) | Set CP1=Company/CP2=Site/CP3=Tag. CLI `set-properties`. STATE-CHANGING. |
|
||||||
| `CreateSession` | array (signature TBD) | EXISTS | Not the primary path - the installer creates access sessions. `raw` only. |
|
| `CreateSession` | array (signature TBD) | EXISTS | Not the primary path - the installer creates access sessions. `raw` only. |
|
||||||
| `GetSessions` / `GetAllSessions` / `GetSessionGroups` | - | MISSING | "web method does not exist". Full-fleet inventory needs an extension update (Mike). |
|
| `GetSessions` / `GetAllSessions` / `GetSessionGroups` | - | ABSENT | "web method does not exist" - but not needed; `GetSessionsByFilter` covers inventory. |
|
||||||
|
|
||||||
|
## Session-filter language (GetSessionsByFilter, probed live 2026-07-06)
|
||||||
|
|
||||||
|
Param name is `sessionFilter` (not `filter`). Empty/null -> "Session filter cannot be
|
||||||
|
null". String literals are single-quoted (double-quotes also work but need JSON
|
||||||
|
escaping). Verified operators/forms:
|
||||||
|
|
||||||
|
- `SessionType = 'Access'` (=2, the unattended agent fleet) | `'Support'` (=0) | `'Meeting'` (none here).
|
||||||
|
- `CustomProperty1 = 'Safesite'` -> per-client (CP1=Company, CP2=Site, CP3=Tag; up to CP8).
|
||||||
|
- `Name = '0124-DELL3540'` (exact) | `Name LIKE '*DELL*'` (partial, `*` wildcard, case-insensitive).
|
||||||
|
- Compound: `CustomProperty1 = 'Safesite' AND SessionType = 'Access'`; `AND`/`OR` supported.
|
||||||
|
- A bare token (`"DELL"`) or `"*"` matches nothing - always name a field.
|
||||||
|
|
||||||
## Parameterized access installer (the deploy capability)
|
## Parameterized access installer (the deploy capability)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ Read-only subcommands run freely. State-changing subcommands (send-command,
|
|||||||
send-message, set-properties) refuse to run without --confirm.
|
send-message, set-properties) refuse to run without --confirm.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python sc.py status # auth check + instance info
|
python sc.py status # auth check + fleet counts
|
||||||
python sc.py sessions [--name NAME] [--json]
|
python sc.py sessions # whole Access fleet
|
||||||
|
python sc.py sessions --company "Safesite" # one client
|
||||||
|
python sc.py sessions --like DELL # partial name match
|
||||||
|
python sc.py sessions --name HOST # exact name
|
||||||
|
python sc.py sessions --filter "SessionType = 'Access'" # raw filter
|
||||||
python sc.py session <sessionID> # full session detail
|
python sc.py session <sessionID> # full session detail
|
||||||
python sc.py send-command --session <id> --command "..." --confirm
|
python sc.py send-command --session <id> --command "..." --confirm
|
||||||
python sc.py send-message --session <id> --message "..." --confirm
|
python sc.py send-message --session <id> --message "..." --confirm
|
||||||
@@ -97,20 +101,41 @@ def _gated(action_desc: str, confirm: bool) -> bool:
|
|||||||
|
|
||||||
# --- handlers ---
|
# --- handlers ---
|
||||||
def cmd_status(client, args):
|
def cmd_status(client, args):
|
||||||
# Auth check via the one verified method; report instance + custom-property map.
|
# Auth check via the fleet filter; report instance + custom-property map + counts.
|
||||||
sessions = client.get_sessions_by_name("")
|
access = client.list_sessions(session_type="access")
|
||||||
n = len(sessions) if isinstance(sessions, list) else 0
|
support = client.list_sessions(session_type="support")
|
||||||
|
na = len(access) if isinstance(access, list) else 0
|
||||||
|
ns = len(support) if isinstance(support, list) else 0
|
||||||
print(f"[OK] Authenticated to {SC_BASE_URL}")
|
print(f"[OK] Authenticated to {SC_BASE_URL}")
|
||||||
print(f" extension: {client.extension_guid}")
|
print(f" extension: {client.extension_guid}")
|
||||||
print(f" custom properties: {CUSTOM_PROPERTIES}")
|
print(f" custom properties: {CUSTOM_PROPERTIES}")
|
||||||
print(f" GetSessionsByName('') returned {n} session(s)")
|
print(f" fleet: {na} Access (unattended) + {ns} Support session(s)")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_sessions(client, args):
|
def cmd_sessions(client, args):
|
||||||
_print_sessions(client.get_sessions_by_name(args.name)) if not args.json else _emit(
|
# Full-fleet listing is the default (no selector -> every Access session).
|
||||||
client.get_sessions_by_name(args.name), True
|
# A raw --filter wins; otherwise build a filter from the structured flags.
|
||||||
)
|
if args.filter:
|
||||||
|
data = client.get_sessions_by_filter(args.filter)
|
||||||
|
else:
|
||||||
|
data = client.list_sessions(
|
||||||
|
session_type=args.type,
|
||||||
|
name=args.name or None,
|
||||||
|
name_like=args.like,
|
||||||
|
company=args.company,
|
||||||
|
site=args.site,
|
||||||
|
tag=args.tag,
|
||||||
|
)
|
||||||
|
if isinstance(data, list) and args.limit and len(data) > args.limit:
|
||||||
|
shown = data[: args.limit]
|
||||||
|
if args.json:
|
||||||
|
_emit(shown, True)
|
||||||
|
else:
|
||||||
|
_print_sessions(shown)
|
||||||
|
print(f" ... {len(data) - args.limit} more (raise --limit or use --json)")
|
||||||
|
return 0
|
||||||
|
_emit(data, True) if args.json else _print_sessions(data)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -192,8 +217,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
|
|
||||||
sub.add_parser("status", help="Auth check + instance info.", parents=[common])
|
sub.add_parser("status", help="Auth check + instance info.", parents=[common])
|
||||||
|
|
||||||
sp = sub.add_parser("sessions", help="List sessions by Name (verified).", parents=[common])
|
sp = sub.add_parser("sessions",
|
||||||
sp.add_argument("--name", default="", help="Session Name filter (blank = unattended).")
|
help="List sessions (default = whole Access fleet).",
|
||||||
|
parents=[common])
|
||||||
|
sp.add_argument("--name", default="", help="Exact session Name match.")
|
||||||
|
sp.add_argument("--like", help="Partial Name match (wrapped in *...*).")
|
||||||
|
sp.add_argument("--company", help="Filter by CP1 = Company.")
|
||||||
|
sp.add_argument("--site", help="Filter by CP2 = Site.")
|
||||||
|
sp.add_argument("--tag", help="Filter by CP3 = Tag.")
|
||||||
|
sp.add_argument("--type", default="access",
|
||||||
|
help="access (default) | support | meeting | all.")
|
||||||
|
sp.add_argument("--filter", help="Raw session-filter expression (overrides the flags).")
|
||||||
|
sp.add_argument("--limit", type=int, default=0,
|
||||||
|
help="Cap rows printed (0 = no cap; --json is always full).")
|
||||||
|
|
||||||
sp = sub.add_parser("session", help="Session detail.", parents=[common])
|
sp = sub.add_parser("session", help="Session detail.", parents=[common])
|
||||||
sp.add_argument("session_id")
|
sp.add_argument("session_id")
|
||||||
|
|||||||
@@ -16,13 +16,20 @@ application/json and the body is the method's parameters (object or array).
|
|||||||
Credentials: never hardcoded. api_secret loaded at runtime from the SOPS vault,
|
Credentials: never hardcoded. api_secret loaded at runtime from the SOPS vault,
|
||||||
or the SCREENCONNECT_API_SECRET env var (testing override).
|
or the SCREENCONNECT_API_SECRET env var (testing override).
|
||||||
|
|
||||||
INSTANCE METHOD SURFACE (probed live 2026-06-22): control IS available -
|
INSTANCE METHOD SURFACE (probed live 2026-06-22, extended 2026-07-06): control IS
|
||||||
GetSessionsByName, GetSessionDetailsBySessionID, GetSessionBySessionID (reads,
|
available - GetSessionsByFilter, GetSessionsByName, GetSessionDetailsBySessionID,
|
||||||
JSON-object params); SendCommandToSession, SendMessageToSession,
|
GetSessionBySessionID (reads, JSON-object params); SendCommandToSession,
|
||||||
UpdateSessionCustomProperties, CreateSession (writes, POSITIONAL-ARRAY params).
|
SendMessageToSession, UpdateSessionCustomProperties, CreateSession (writes,
|
||||||
MISSING on this extension version: GetSessions/GetAllSessions/GetSessionGroups
|
POSITIONAL-ARRAY params).
|
||||||
(full-fleet inventory) - "web method does not exist"; pending an admin update of
|
|
||||||
the RESTful API Manager extension. `raw()` probes arbitrary methods.
|
FULL-FLEET LISTING (fixed 2026-07-06): GetSessionsByFilter({"sessionFilter": <expr>})
|
||||||
|
IS exposed and is the enumeration path. It takes a ScreenConnect session-filter
|
||||||
|
expression, e.g. SessionType = 'Access' (all 958 unattended agents), CustomProperty1
|
||||||
|
= 'Safesite' (per-client), Name LIKE '*DELL*' (partial name). SessionType 'Access'
|
||||||
|
(=2) is the agent fleet; 'Support' (=0) the on-demand sessions. The bare aliases
|
||||||
|
GetSessions/GetAllSessions/GetSessionGroups remain absent, but GetSessionsByFilter
|
||||||
|
covers the inventory use case fully - no extension update needed. `raw()` probes
|
||||||
|
arbitrary methods.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -63,6 +70,10 @@ SKILL_DIR = Path(__file__).resolve().parent.parent
|
|||||||
# Custom-property mapping on this instance (from the vault notes).
|
# Custom-property mapping on this instance (from the vault notes).
|
||||||
CUSTOM_PROPERTIES = {"CP1": "Company", "CP2": "Site", "CP3": "Tag"}
|
CUSTOM_PROPERTIES = {"CP1": "Company", "CP2": "Site", "CP3": "Tag"}
|
||||||
|
|
||||||
|
# SessionType filter literals (verified live 2026-07-06): 'Access' (=2) is the
|
||||||
|
# unattended agent fleet; 'Support' (=0) the on-demand sessions.
|
||||||
|
SESSION_TYPE_LITERALS = {"access": "Access", "support": "Support", "meeting": "Meeting"}
|
||||||
|
|
||||||
|
|
||||||
class ScreenConnectError(RuntimeError):
|
class ScreenConnectError(RuntimeError):
|
||||||
"""Raised for transport or API errors."""
|
"""Raised for transport or API errors."""
|
||||||
@@ -200,17 +211,72 @@ class ScreenConnectClient:
|
|||||||
# VERIFIED methods (work on the current instance)
|
# VERIFIED methods (work on the current instance)
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
def get_sessions_by_name(self, session_name: str = "") -> Any:
|
def get_sessions_by_name(self, session_name: str = "") -> Any:
|
||||||
"""List sessions whose Name matches `session_name` (RESTful API Manager
|
"""List sessions whose Name EXACTLY equals `session_name` (RESTful API
|
||||||
GetSessionsByName). VERIFIED LIVE. Empty string returns sessions with a
|
Manager GetSessionsByName). VERIFIED LIVE. This is an exact match, not a
|
||||||
blank Name (the unattended access agents on this instance)."""
|
contains match ("" returns only the blank-Name sessions, not the fleet).
|
||||||
|
For full-fleet or partial-name listing use list_sessions()/get_sessions_by_filter()."""
|
||||||
return self.call("GetSessionsByName", {"sessionName": session_name})
|
return self.call("GetSessionsByName", {"sessionName": session_name})
|
||||||
|
|
||||||
|
def get_sessions_by_filter(self, session_filter: str) -> Any:
|
||||||
|
"""Enumerate sessions matching a ScreenConnect session-filter expression
|
||||||
|
(GetSessionsByFilter, param `sessionFilter`). VERIFIED LIVE 2026-07-06 -
|
||||||
|
this IS the full-fleet listing path. Examples:
|
||||||
|
SessionType = 'Access' -> every unattended agent (the fleet)
|
||||||
|
CustomProperty1 = 'Safesite' -> one client's machines
|
||||||
|
Name LIKE '*DELL*' -> partial name (case-insensitive)
|
||||||
|
Combine with AND/OR. String literals are single-quoted."""
|
||||||
|
return self.call("GetSessionsByFilter", {"sessionFilter": session_filter})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _filter_literal(value: str) -> str:
|
||||||
|
"""Single-quote a value for the session-filter language, escaping any
|
||||||
|
embedded single quote by doubling it (SQL-style)."""
|
||||||
|
return "'" + str(value).replace("'", "''") + "'"
|
||||||
|
|
||||||
|
def build_session_filter(
|
||||||
|
self,
|
||||||
|
session_type: Optional[str] = "access",
|
||||||
|
name: Optional[str] = None,
|
||||||
|
name_like: Optional[str] = None,
|
||||||
|
company: Optional[str] = None,
|
||||||
|
site: Optional[str] = None,
|
||||||
|
tag: Optional[str] = None,
|
||||||
|
extra: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Assemble a session-filter expression from structured parts (AND-joined).
|
||||||
|
`session_type`: access | support | meeting | all (None/all -> no type clause).
|
||||||
|
`name` exact, `name_like` partial (wrapped in *...*), company/site/tag map to
|
||||||
|
CP1/CP2/CP3, `extra` is a raw clause appended verbatim. Defaults to the whole
|
||||||
|
Access fleet when nothing else is specified."""
|
||||||
|
clauses: list[str] = []
|
||||||
|
if session_type and session_type.lower() != "all":
|
||||||
|
literal = SESSION_TYPE_LITERALS.get(session_type.lower(), session_type)
|
||||||
|
clauses.append(f"SessionType = {self._filter_literal(literal)}")
|
||||||
|
if name:
|
||||||
|
clauses.append(f"Name = {self._filter_literal(name)}")
|
||||||
|
if name_like:
|
||||||
|
clauses.append(f"Name LIKE {self._filter_literal('*' + name_like + '*')}")
|
||||||
|
if company:
|
||||||
|
clauses.append(f"CustomProperty1 = {self._filter_literal(company)}")
|
||||||
|
if site:
|
||||||
|
clauses.append(f"CustomProperty2 = {self._filter_literal(site)}")
|
||||||
|
if tag:
|
||||||
|
clauses.append(f"CustomProperty3 = {self._filter_literal(tag)}")
|
||||||
|
if extra:
|
||||||
|
clauses.append(extra)
|
||||||
|
return " AND ".join(clauses) if clauses else "SessionType = 'Access'"
|
||||||
|
|
||||||
|
def list_sessions(self, **kwargs) -> Any:
|
||||||
|
"""Convenience: build a filter from keyword parts and enumerate. See
|
||||||
|
build_session_filter for the accepted keywords."""
|
||||||
|
return self.get_sessions_by_filter(self.build_session_filter(**kwargs))
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# Control + detail methods (all VERIFIED LIVE 2026-06-22 on the ACG instance).
|
# Control + detail methods (all VERIFIED LIVE 2026-06-22 on the ACG instance).
|
||||||
# Reads take a JSON object {sessionID:...}; the POST/write methods take a
|
# Reads take a JSON object {sessionID:...}; the POST/write methods take a
|
||||||
# POSITIONAL ARRAY. NOTE: full-fleet inventory (GetSessions) is NOT exposed by
|
# POSITIONAL ARRAY. Full-fleet inventory is served by GetSessionsByFilter
|
||||||
# this extension version (returns "web method does not exist") - pending an
|
# (see get_sessions_by_filter / list_sessions above) - the bare GetSessions
|
||||||
# admin update of the RESTful API Manager extension (see SKILL.md).
|
# alias is absent but not needed.
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
def get_session_details(self, session_id: str) -> Any:
|
def get_session_details(self, session_id: str) -> Any:
|
||||||
"""GetSessionDetailsBySessionID - full detail for one session. VERIFIED."""
|
"""GetSessionDetailsBySessionID - full detail for one session. VERIFIED."""
|
||||||
|
|||||||
@@ -43,9 +43,15 @@ def check(name, args, *, want_rc=None, out_has=None, out_json_ok=False):
|
|||||||
|
|
||||||
|
|
||||||
# --- reads: succeed (rc 0) [hit the live instance] ---
|
# --- reads: succeed (rc 0) [hit the live instance] ---
|
||||||
check("status", ["status"], want_rc=0, out_has="Authenticated")
|
check("status", ["status"], want_rc=0, out_has="fleet:")
|
||||||
check("sessions", ["sessions"], want_rc=0, out_has="Sessions:")
|
check("sessions (full fleet)", ["sessions"], want_rc=0, out_has="Sessions:")
|
||||||
check("sessions json", ["sessions", "--json"], want_rc=0, out_json_ok=True)
|
check("sessions json", ["sessions", "--json"], want_rc=0, out_json_ok=True)
|
||||||
|
check("sessions --limit", ["sessions", "--limit", "5"], want_rc=0, out_has="Sessions:")
|
||||||
|
check("sessions --like", ["sessions", "--like", "DELL", "--json"], want_rc=0, out_json_ok=True)
|
||||||
|
check("sessions --filter", ["sessions", "--filter", "SessionType = 'Access'", "--json"],
|
||||||
|
want_rc=0, out_json_ok=True)
|
||||||
|
check("sessions --company", ["sessions", "--company", "Safesite", "--json"],
|
||||||
|
want_rc=0, out_json_ok=True)
|
||||||
|
|
||||||
# --- build-installer: pure URL build (no network) ---
|
# --- build-installer: pure URL build (no network) ---
|
||||||
check("build-installer", ["build-installer", "--name", "HOST", "--company", "AZ Computer Guru",
|
check("build-installer", ["build-installer", "--name", "HOST", "--company", "AZ Computer Guru",
|
||||||
|
|||||||
@@ -36,21 +36,23 @@ audit. See `wiki/projects/gps-rmm-audit.md`.
|
|||||||
| Grabb & Durando Law | 1 | 49 | GND-SERVER cloud plan **failing / 0 bytes (flapping)** | **NOT BACKING UP** |
|
| Grabb & Durando Law | 1 | 49 | GND-SERVER cloud plan **failing / 0 bytes (flapping)** | **NOT BACKING UP** |
|
||||||
| Bill Tedards | 1 | 45 | DESKTOP-SUFJR0J **last good 359d ago** | **NOT BACKING UP** (stale ~12 mo) |
|
| Bill Tedards | 1 | 45 | DESKTOP-SUFJR0J **last good 359d ago** | **NOT BACKING UP** (stale ~12 mo) |
|
||||||
| Arizona Medical Transit | 1 | 0 | MSP360 company exists, **0 plans** | **NOT BACKING UP** |
|
| Arizona Medical Transit | 1 | 0 | MSP360 company exists, **0 plans** | **NOT BACKING UP** |
|
||||||
| Reliant Well Drilling | 1 | 40 | none — RMM machines online, run Datto **EDR+RMM only, no backup client** | **UNCONFIRMED** (agentless SIRIS not ruled out) |
|
| Reliant Well Drilling | 1 | 40 | **CONFIRMED none** — both PCs scanned 2026-07-06: no backup software/task/WSB, Datto EDR+RMM only | **NOT BACKING UP** (endpoint-verified) |
|
||||||
| The Prairie Schooner | 1 | 10 | none — 2 servers online, Datto **EDR only, no backup client** | **UNCONFIRMED** (SIRIS could image servers agentlessly) |
|
| The Prairie Schooner | 1 | 10 | **CONFIRMED none current** — TPS-SERVER has Windows Server Backup to a USB disk but **last ran 3/7/2018 (~8 yr dead)**; other PCs nothing | **NOT BACKING UP** (endpoint-verified) |
|
||||||
| Janet Altschuler | 2 | 0 | **CONFIRMED none** — direct scan of both machines 2026-07-06: no backup software/service/task, no Windows Backup, no Datto backup agent (only Dropbox, stopped) | **NOT BACKING UP** (endpoint-verified) |
|
| Janet Altschuler | 2 | 0 | **CONFIRMED none** — direct scan of both machines 2026-07-06: no backup software/service/task, no Windows Backup, no Datto backup agent (only Dropbox, stopped) | **NOT BACKING UP** (endpoint-verified) |
|
||||||
| Design and Brand Envoys | 1 | 0 | none — machines online, Datto **EDR+RMM only, no backup client** | **UNCONFIRMED** |
|
| Design and Brand Envoys | 1 | 0 | **CONFIRMED none** — 2 PCs scanned 2026-07-06: no backup software/task/WSB (OSGOOD-PAUL offline, unscanned) | **NOT BACKING UP** (endpoint-verified) |
|
||||||
|
|
||||||
**Rollup (16 billed):** 7 OK · 2 REVIEW (seat>machine) · 3 NOT BACKING UP (Grabb, Tedards,
|
**Rollup (16 billed):** 7 OK · 2 REVIEW (seat>machine) · **7 NOT BACKING UP** (Grabb, Tedards,
|
||||||
AMT) · 4 HELD-unconfirmed pending Datto Workplace enumeration.
|
AMT, + the 4 now endpoint-verified: Reliant, Prairie, Janet Altschuler, Design & Brand Envoys).
|
||||||
|
|
||||||
The 4 UNCONFIRMED are billed for backup but have **no destination in any backend we can read**
|
All 4 were **endpoint-verified 2026-07-06** (GuruRMM now on their machines; full backup-software
|
||||||
(MSP360/B2/Seafile/ownCloud/Datto Workplace). 2026-07-06 RMM endpoint scan: their online
|
scan): **NO backup software, service, scheduled backup job, Windows Server Backup, or Datto
|
||||||
machines run **Datto EDR + Datto RMM only — NO Datto backup client** (no Workplace, no SIRIS
|
backup client** on any machine — and nothing in MSP360/B2/Seafile/ownCloud/Datto Workplace.
|
||||||
Windows Agent). The one thing endpoint detection CANNOT see is an **agentless Datto SIRIS/BCDR
|
Prairie's TPS-SERVER has a Windows Server Backup to a USB disk but it **last ran 3/7/2018
|
||||||
appliance** (hypervisor-snapshot backup, no on-box agent) — only the Datto BCDR partner portal
|
(~8 years dead)**. Only residual endpoint-invisible possibility = an **agentless Datto SIRIS/BCDR
|
||||||
(no ACG skill/creds) would confirm that. Sent to Mike+Winter 2026-07-06 asking where these
|
appliance** — but there's no Datto backup agent on any of them and 3 of 4 have no server, so
|
||||||
back up; also re-requested the Datto Workplace super-admin API key (current key = 0 projects).
|
effectively ruled out. **Conclusion: none of these 4 are backing up.** Sent to Mike+Winter
|
||||||
|
2026-07-06 (message updated with the verified result); also re-requested the Datto Workplace
|
||||||
|
super-admin API key (current key = 0 projects).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user