23 Commits

Author SHA1 Message Date
4c94254477 sync: auto-sync from HOWARD-HOME at 2026-05-20 14:53:36
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-05-20 14:53:36
2026-05-20 14:53:38 -07:00
35d3a39242 client/cascades: britney.thompson M365 offboarding complete — sign-in blocked, license removed, litigation hold
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:42:05 -07:00
517278f66f sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 14:38:07
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 14:38:07
2026-05-20 14:38:15 -07:00
ae791e321d client/cascades: Phase 2.6 COMPLETE — 13 printers, 4 GPOs, 5 accounts disabled
Detailed context:
- Task: Cascades of Tucson Phase 2.6 — printer migration, GPO deployment, account cleanup
- Changes:
  - phase2-print-server.ps1: all 13 printers complete, Epson driver/share notes added
  - active-directory.md: 5 stale accounts disabled, 4 GPOs created, pending issues cleared, printer share table updated
  - Session log: 2026-05-20 Howard session covering all Phase 2.6 work
- Status: Phase 2.6 complete

Files modified:
- clients/cascades-tucson/docs/migration/scripts/phase2-print-server.ps1
- clients/cascades-tucson/docs/servers/active-directory.md
- clients/cascades-tucson/session-logs/2026-05-20-howard-phase2.6-printers-gpos-account-cleanup.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:04:08 -07:00
dc29e2ff24 client/cascades: phase2-print-server.ps1 — 8 printers installed on CS-SERVER
Rewrote with verified IPs and confirmed drivers. All 8 printers created and
shared via GuruRMM 2026-05-20. Deferred: FrontDesk Epson (needs Epson
Universal driver), Health-206 Konica Minolta (needs KM PCL6 Universal driver).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:04:08 -07:00
ba8526eae7 fix: make check-messages.sh hook path-agnostic for D: and C: roots
Hook now tries D:/claudetools then C:/claudetools so it works on both
Mike's machine and Howard's without either needing machine-local config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:04:08 -07:00
75e3d38de8 client/cascades: n.castro cleanup, share docs, hook path fix
- active-directory.md: disable n.castro (AD + M365), fix stale Alma.Montt
  pending entry (she is intentionally cloud-only), restructure SMB shares
  section into new Phase 2.5 / legacy / system buckets (verified live via
  GuruRMM Get-SmbShare 2026-05-20)
- settings.json: remove hardcoded D:/claudetools UserPromptSubmit hook
  (machine-specific path belongs in settings.local.json only; Howard's
  machine is C:/claudetools)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:04:08 -07:00
098e1d4156 sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 10:58:31
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 10:58:31
2026-05-20 10:58:35 -07:00
1f8a9dbc8d Session log: EOP access fix + Alma Montt mailbox investigation (Cascades Tucson)
- Fixed Exchange.ManageAsApp missing from Security Investigator app registration
- Granted role directly in Cascades tenant via Graph API
- Investigated Alma Montt mailbox: no delivery blocks found, specific sender TBD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 10:39:15 -07:00
020ae0cc1c feat: Discord bot — per-session rules, user identity, and DISCORD_CLAUDE.md
- Add DISCORD_CLAUDE.md as the Discord bot's dedicated system prompt,
  replacing the main CLAUDE.md for bot sessions. Covers: no-interactive
  rules, Discord user authorization, vault/remediation guidance, /save
  after every task, and formatting rules for Discord.

- config.py: add discord_system_prompt field (default: projects/discord-bot/
  DISCORD_CLAUDE.md, overridable via env var).

- client.py: _load_system_prompt() now loads discord_system_prompt path
  with fallback to CLAUDE.md if file is missing.

- message_handler.py: inject [DISCORD_CONTEXT] header into every agent
  message containing Discord username, display name, user ID, channel,
  and guild so the agent always knows who is asking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 10:11:36 -07:00
377ab3a63f sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 09:02:03
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 09:02:03
2026-05-20 09:02:06 -07:00
f4b935b394 sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 08:25:48
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 08:25:48
2026-05-20 08:25:53 -07:00
35ee135b0f sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 07:37:32
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 07:37:32
2026-05-20 07:37:43 -07:00
35c1270941 sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 06:52:41
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 06:52:41
2026-05-20 06:52:58 -07:00
5b99e4b80b sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 05:51:25
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 05:51:25
2026-05-20 05:51:29 -07:00
7bca175176 sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-20 05:10:44
Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-20 05:10:44
2026-05-20 05:13:16 -07:00
d246992d09 Session log: Coordination hook auto-initialization fix
Comprehensive session log documenting the fix for broken coordination hooks
across all machines. The UserPromptSubmit hook now auto-creates the required
.claude/current-mode file with "general" as default if missing.

Session highlights:
- Root cause analysis of missing machine-local mode file
- Implementation of auto-creation logic in check-messages.sh
- Documentation updates in CLAUDE.md and ONBOARDING.md
- Successful deployment and sync with 15 remote commits
- Zero manual setup required for future machines

Impact: Fixes coordination hooks permanently, prevents fresh-clone failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-20 05:10:24 -07:00
b62ea2d4e7 sync: auto-sync from HOWARD-HOME at 2026-05-19 22:36:25
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-05-19 22:36:25
2026-05-19 22:36:27 -07:00
7da6a81aa4 fix: add C:/claudetools to sync.sh candidate path list
HOWARD-HOME installs to C: not D:, causing sync.sh to fail with
"Not in a git working tree" on that machine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:33:04 -07:00
468f4287bf client/cascades: session log + AD doc update 2026-05-20
Phase 2.5 complete. Folder redirection GPO decision documented — deferred
to Phase 3 (blocked on domain joins). Pending items carried forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:32:20 -07:00
3328a24742 client/cascades: Phase 2.5 AD groups and shares — COMPLETE
Created SG-Mgmt-RW, SG-Sales-RO, SG-Activities-RW in OU=Groups.
Created SMB shares Management, Sales, Activities, Server on D:\Shares
with ABE enabled and correct NTFS ACLs per group.
Scripts run on CS-SERVER via GuruRMM 2026-05-20. AD doc updated to live state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:32:20 -07:00
d5d2580dd5 chore: update gururmm submodule — PS format specifier fix
Points to 8404a3c: fix AddToGroup/RemoveFromGroup Windows build error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:09:21 -07:00
4b5bf0e7a9 chore: update gururmm submodule — agent compile fix
Points to e1ea40a: fix agent compile errors from audit remediation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:02:28 -07:00
22 changed files with 2731 additions and 351 deletions

View File

@@ -253,6 +253,7 @@ Vault structure: `infrastructure/`, `clients/`, `services/`, `projects/`, `msp-t
| `/feature-request` | Howard submits a GuruRMM feature request — Claude classifies it and messages Mike |
| `/shape-spec` | Pre-implementation spec for a GuruRMM feature — produces plan.md, shape.md, references.md, standards.md |
| `/rmm-audit` | Full end-to-end audit of GuruRMM: API coverage, UI gaps, Rust/TS quality, security, data integrity. Produces timestamped report + updates UI_GAPS.md |
| `/forum-post` | Post a technical article to community.azcomputerguru.com — drafts from context, shows preview, inserts via paramiko SSH to Flarum DB |
---

View File

@@ -0,0 +1,324 @@
Post a technical article to community.azcomputerguru.com (Flarum forum).
Converts markdown to Flarum's s9e TextFormatter XML format and inserts directly into the
database via paramiko SSH to IX. Shows a preview and waits for user confirmation before posting.
---
## Usage
```
/forum-post Draft interactively — Claude asks for title, content, tag
/forum-post <topic hint> Claude drafts from conversation context, then confirms
```
Arguments are optional. With no args, Claude uses the current conversation context to
determine what to post (the most recent technical problem solved, fix documented, etc.).
---
## Infrastructure
| Item | Value |
|---|---|
| Forum URL | https://community.azcomputerguru.com |
| DB host | localhost (on IX) |
| DB name | azcompu_flarum |
| DB user | azcompu_flarum |
| DB pass | `Fl@rum2026!CGS` |
| IX SSH | root@172.16.3.10 — password from vault: `infrastructure/ix-server.sops.yaml credentials.password` |
| Admin user_id | 1 (MikeSwanson) |
Get the IX SSH password via vault before connecting:
```bash
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" get-field infrastructure/ix-server.sops.yaml credentials.password
```
---
## Known Tags
| tag_id | Name | When to use |
|--------|------|-------------|
| 7 | How-Tos & Tips | Technical fixes, how-tos, diagnostics, field notes |
To see all current tags: query `SELECT id, name_singular FROM tags;` on azcompu_flarum.
Default to tag_id=7 unless the user specifies otherwise.
---
## Phase 1: Gather Inputs
Collect in one pass — do not ask field by field:
1. **Title** — descriptive, search-friendly, sentence case. Include the technology/product.
Good: `OneDrive KFM Fails After Folder Redirection Migration -- Here's What's Actually Going On`
Bad: `OneDrive issue fix`
2. **Content** — full markdown body. Minimum useful length: 3-4 paragraphs with concrete detail.
Write from the conversation context: what was the problem, what was tried, what actually worked, what to remember.
3. **Tag** — default How-Tos & Tips (7) unless user says otherwise.
Generate the slug from the title: lowercase, spaces/punctuation to hyphens, remove apostrophes,
collapse multiple hyphens. Max ~90 chars. Example:
`onedrive-kfm-fails-after-folder-redirection-migration-heres-whats-actually-going-on`
---
## Phase 2: Show Preview
Before posting, show:
```
FORUM POST PREVIEW
------------------
Title: <title>
Tag: How-Tos & Tips
Slug: <slug>
URL: https://community.azcomputerguru.com/d/<next_id>-<slug>
--- Content ---
<first 500 chars of markdown>
...
Post this? (yes/no)
```
Wait for explicit confirmation before executing.
---
## Phase 3: Execute
Write and run a Python script (use `py` on Windows). The script must:
1. **Get IX SSH password** from vault (see Infrastructure above)
2. **Connect via paramiko** (`AutoAddPolicy`, password auth)
3. **Generate the s9e XML** from the markdown (see Converter section below)
4. **Build the PHP insert script** (see PHP Template below)
5. **SFTP upload** the PHP script to `/tmp/flarum_post_<timestamp>.php` on IX
6. **Run** `php /tmp/flarum_post_<timestamp>.php 2>&1` via SSH
7. **Parse output** — look for `Discussion ID: N` and `Post ID: N`
8. **Clean up**`rm /tmp/flarum_post_<timestamp>.php`
9. **Report** the live URL
---
## Markdown → s9e XML Converter
Flarum stores post content as s9e TextFormatter XML, not raw markdown. The stored format
must match what Flarum's TextFormatter produces. Based on confirmed existing posts:
### Inline elements
| Markdown | s9e XML |
|---|---|
| `**bold**` | `<STRONG><s>**</s>bold<e>**</e></STRONG>` |
| `*italic*` | `<EM><s>*</s>italic<e>*</e></EM>` |
| `` `code` `` | `<C><s>`</s>code<e>`</e></C>` |
XML-escape `&` → `&amp;`, `<` → `&lt;`, `>` → `&gt;` in all text content and code spans.
### Block elements
| Markdown | s9e XML |
|---|---|
| `## Heading` | `<H2><s>## </s>Heading</H2>` |
| `### Heading` | `<H3><s>### </s>Heading</H3>` |
| Paragraph | `<p>text</p>` |
| `- item` (list) | `<LIST><LI><s>- </s>item</LI>\n<LI>...</LI></LIST>` |
| `1. item` (ordered) | `<LIST type="decimal"><LI><s>1. </s>item</LI>\n...</LIST>` |
| ` ```lang ` fenced block | `<CODE lang="lang"><s>```lang</s><i>\n</i>code\n<e>```</e></CODE>` |
Block elements are separated by `\n\n` (two real newlines) inside the `<r>` root.
List items are separated by `\n` (one newline).
Entire content is wrapped: `<r>...</r>`.
### Python converter (copy this directly into the py script)
```python
import re
def xml_escape(t):
return t.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def inline_to_xml(text):
result = ''
i = 0
while i < len(text):
if text[i:i+2] == '**':
end = text.find('**', i+2)
if end != -1:
inner = inline_to_xml(text[i+2:end])
result += '<STRONG><s>**</s>' + inner + '<e>**</e></STRONG>'
i = end + 2; continue
if text[i] == '`':
end = text.find('`', i+1)
if end != -1:
result += '<C><s>`</s>' + xml_escape(text[i+1:end]) + '<e>`</e></C>'
i = end + 1; continue
if text[i] == '*' and text[i:i+2] != '**':
j = i + 1; end = -1
while j < len(text):
if text[j] == '*' and text[j:j+2] != '**': end = j; break
j += 1
if end != -1:
result += '<EM><s>*</s>' + xml_escape(text[i+1:end]) + '<e>*</e></EM>'
i = end + 1; continue
result += xml_escape(text[i]); i += 1
return result
def md_to_s9e(md):
lines = md.split('\n')
elements = []
i = 0
while i < len(lines):
line = lines[i]
if not line.strip(): i += 1; continue
if line.startswith('## '):
elements.append('<H2><s>## </s>' + inline_to_xml(line[3:]) + '</H2>'); i += 1
elif line.startswith('### '):
elements.append('<H3><s>### </s>' + inline_to_xml(line[4:]) + '</H3>'); i += 1
elif line.startswith('- '):
items = []
while i < len(lines) and lines[i].startswith('- '):
items.append('<LI><s>- </s>' + inline_to_xml(lines[i][2:]) + '</LI>'); i += 1
elements.append('<LIST>' + '\n'.join(items) + '</LIST>')
elif re.match(r'^\d+\. ', line):
items = []
while i < len(lines) and re.match(r'^\d+\. ', lines[i]):
m = re.match(r'^(\d+)\. (.*)', lines[i])
items.append('<LI><s>' + m.group(1) + '. </s>' + inline_to_xml(m.group(2)) + '</LI>'); i += 1
elements.append('<LIST type="decimal">' + '\n'.join(items) + '</LIST>')
elif line.startswith('```'):
lang = line[3:].strip(); code_lines = []; i += 1
while i < len(lines) and not lines[i].startswith('```'):
code_lines.append(xml_escape(lines[i])); i += 1
i += 1
tag = f'<CODE lang="{lang}">' if lang else '<CODE>'
elements.append(tag + f'<s>```{lang}</s><i>\n</i>' + '\n'.join(code_lines) + '\n<e>```</e></CODE>')
else:
para_lines = []
while i < len(lines) and lines[i].strip():
l = lines[i]
if l.startswith('## ') or l.startswith('### ') or l.startswith('- ') or l.startswith('```') or re.match(r'^\d+\. ', l): break
para_lines.append(l); i += 1
elements.append('<p>' + inline_to_xml('\n'.join(para_lines)) + '</p>')
return '<r>' + '\n\n'.join(elements) + '</r>'
```
---
## PHP Insert Template
Use `%%XML_CONTENT%%` as the placeholder — replace with the generated s9e XML before uploading.
The closing nowdoc marker `FLARUM_POST_XML_END;` must be at column 0 with no leading whitespace.
```php
<?php
ini_set('display_errors', 1); error_reporting(E_ALL);
$dsn = 'mysql:host=localhost;dbname=azcompu_flarum;charset=utf8mb4';
$pdo = new PDO($dsn, 'azcompu_flarum', 'Fl@rum2026!CGS', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$user_id = 1; $tag_id = %%TAG_ID%%;
$title = %%TITLE_PHP%%;
$slug = '%%SLUG%%';
$now = date('Y-m-d H:i:s');
$content = <<<'FLARUM_POST_XML_END'
%%XML_CONTENT%%
FLARUM_POST_XML_END;
// Insert discussion first (post refs are nullable)
$stmt = $pdo->prepare("INSERT INTO discussions (title, comment_count, post_number_index, created_at, user_id, slug, is_private, is_approved) VALUES (?, 1, 1, ?, ?, ?, 0, 1)");
$stmt->execute([$title, $now, $user_id, $slug]);
$disc_id = $pdo->lastInsertId();
echo "Discussion ID: $disc_id\n";
// Insert post
$stmt = $pdo->prepare("INSERT INTO posts (discussion_id, number, created_at, user_id, type, content, is_private, is_approved) VALUES (?, 1, ?, ?, 'comment', ?, 0, 1)");
$stmt->execute([$disc_id, $now, $user_id, $content]);
$post_id = $pdo->lastInsertId();
echo "Post ID: $post_id\n";
// Link post back to discussion
$pdo->prepare("UPDATE discussions SET first_post_id=?, last_post_id=?, last_posted_at=?, last_posted_user_id=?, last_post_number=1 WHERE id=?")->execute([$post_id, $post_id, $now, $user_id, $disc_id]);
// Tag
$pdo->prepare("INSERT INTO discussion_tag (discussion_id, tag_id) VALUES (?, ?)")->execute([$disc_id, $tag_id]);
echo "Done! URL: https://community.azcomputerguru.com/d/$disc_id-$slug\n";
```
### Template substitutions
| Placeholder | How to fill |
|---|---|
| `%%TAG_ID%%` | Integer (e.g. `7`) |
| `%%TITLE_PHP%%` | PHP double-quoted string with escaped `"` — e.g. `"The Title Here"` |
| `%%SLUG%%` | URL-safe slug string |
| `%%XML_CONTENT%%` | The output of `md_to_s9e(content_md)` |
Build the PHP script in Python using `.replace()` — never f-strings (curly braces in XML content
will cause Python to try to expand them as template expressions).
---
## Paramiko Execution Pattern
```python
import paramiko, time
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('172.16.3.10', username='root', password=IX_PASS, timeout=10)
# Upload
remote_path = f'/tmp/flarum_post_{int(time.time())}.php'
sftp = client.open_sftp()
with sftp.open(remote_path, 'wb') as f:
f.write(php_script.encode('utf-8'))
sftp.close()
# Execute
def run_chan(cmd):
chan = client.get_transport().open_session()
chan.exec_command(cmd)
chan.shutdown_write()
out = b''
while not chan.exit_status_ready():
if chan.recv_ready(): out += chan.recv(4096)
while chan.recv_ready(): out += chan.recv(4096)
return out.decode('utf-8', errors='replace'), chan.recv_exit_status()
out, rc = run_chan(f'php {remote_path} 2>&1')
run_chan(f'rm -f {remote_path}')
client.close()
```
---
## Error Handling
| rc | Symptom | Cause | Fix |
|---|---|---|---|
| 255, no output | Script crashes silently | Exception with display_errors off | Script already includes `ini_set('display_errors',1)` |
| FK constraint error | Cannot add child row | Post inserted before discussion | Use discussion-first insert order (template above does this) |
| Empty curl response | localhost curl returns nothing | HTTP→HTTPS redirect on vhost | Use paramiko PHP approach, not curl |
| Cloudflare challenge | API blocked externally | Bot protection | Always insert via paramiko, never via external HTTP |
---
## After Posting
Report:
```
[SUCCESS] Posted to community.azcomputerguru.com
Discussion: #<id>
URL: https://community.azcomputerguru.com/d/<id>-<slug>
Tag: How-Tos & Tips
```
Do NOT try to verify via curl or browser — Cloudflare blocks external API calls and
localhost curl has a redirect issue. The DB output is the authoritative confirmation.

View File

@@ -1 +1 @@
{"sessionId":"541d4004-8c45-4290-89f5-0ba9ee4e64a9","pid":23068,"acquiredAt":1778772467880}
{"sessionId":"d91f202e-ddd5-46d7-b674-f848eb78aa8e","pid":17204,"acquiredAt":1779243065349}

View File

@@ -22,13 +22,20 @@ TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
echo -e "${GREEN}[OK]${NC} Starting ClaudeTools sync from $MACHINE at $TIMESTAMP"
# Navigate to ClaudeTools directory (check common locations)
for candidate in "$HOME/ClaudeTools" "/d/ClaudeTools" "D:/ClaudeTools" "/d/claudetools" "D:/claudetools"; do
if [ -d "$candidate" ]; then
cd "$candidate"
break
fi
done
# Navigate to ClaudeTools directory
# First check: are we already in the repo (or a subdirectory of it)?
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [ -n "$REPO_ROOT" ]; then
cd "$REPO_ROOT"
else
# Fall back to known candidate paths
for candidate in "$HOME/ClaudeTools" "/d/ClaudeTools" "D:/ClaudeTools" "/d/claudetools" "D:/claudetools" "C:/claudetools" "/c/claudetools"; do
if [ -d "$candidate/.git" ]; then
cd "$candidate"
break
fi
done
fi
if [ ! -d ".git" ]; then
echo -e "${RED}[ERROR]${NC} Not in a git working tree"

View File

@@ -12,7 +12,7 @@
"hooks": [
{
"type": "command",
"command": "bash \"D:/claudetools/.claude/scripts/check-messages.sh\"",
"command": "bash -c 'for p in D:/claudetools C:/claudetools; do f=\"$p/.claude/scripts/check-messages.sh\"; [ -f \"$f\" ] && exec bash \"$f\"; done'",
"timeout": 15
}
]

View File

@@ -0,0 +1,105 @@
Import-Module GroupPolicy, ActiveDirectory -ErrorAction Stop
$ErrorActionPreference = 'Continue'
$domain = 'cascades.local'
$srv = 'CS-SERVER'
$sysvol = "\\$srv\SYSVOL\$domain\Policies"
Write-Output "=== 1. AD Account Cleanup ==="
$toDisable = @(
@{ SAM='britney.thompson'; Why='Departed 2026-04-22' }
@{ SAM='Richard.Adams'; Why='Driver - no longer gets IT access' }
@{ SAM='Julian.Crim'; Why='Driver - no longer gets IT access' }
@{ SAM='Christopher.Holick'; Why='Driver - no longer gets IT access' }
@{ SAM='Shontiel.Nunn'; Why='Old-format account - s.nunn (Caregivers) is correct' }
)
foreach ($a in $toDisable) {
try {
$u = Get-ADUser -Identity $a.SAM -Properties Enabled -ErrorAction Stop
if ($u.Enabled) { Disable-ADAccount -Identity $a.SAM; Write-Output " [OK] Disabled: $($a.SAM) - $($a.Why)" }
else { Write-Output " [--] Already disabled: $($a.SAM)" }
} catch { Write-Output " [ERROR] $($a.SAM): $_" }
}
Write-Output ""
Write-Output "=== 2. CSC - Security Baseline ==="
if (-not (Get-GPO -Name 'CSC - Security Baseline' -Domain $domain -ErrorAction SilentlyContinue)) {
New-GPO -Name 'CSC - Security Baseline' -Domain $domain `
-Comment 'Phase 2.6: 12-char password min, lockout 5/30, 15-min screen lock. UNLINKED - link at domain root during Phase 3.' | Out-Null
Write-Output " [OK] GPO created"
} else { Write-Output " [--] Already exists" }
foreach ($kv in @(
@{ N='ScreenSaveTimeOut'; V='900' }
@{ N='ScreenSaveActive'; V='1' }
@{ N='ScreenSaverIsSecure'; V='1' }
@{ N='SCRNSAVE.EXE'; V='scrnsave.scr' }
)) {
Set-GPRegistryValue -Name 'CSC - Security Baseline' `
-Key 'HKCU\Software\Policies\Microsoft\Windows\Control Panel\Desktop' `
-ValueName $kv.N -Type String -Value $kv.V | Out-Null
}
Write-Output " [OK] Screen saver: 15 min idle, password on resume"
$gpo = Get-GPO -Name 'CSC - Security Baseline' -Domain $domain
$gpoPath = "$sysvol\{$($gpo.Id.ToString().ToUpper())}"
$secDir = "$gpoPath\Machine\Microsoft\Windows NT\SecEdit"
New-Item -Path $secDir -ItemType Directory -Force | Out-Null
$infLines = @(
'[Unicode]'
'Unicode=yes'
'[System Access]'
'MinimumPasswordLength = 12'
'PasswordComplexity = 1'
'PasswordHistorySize = 24'
'MaximumPasswordAge = 90'
'MinimumPasswordAge = 1'
'LockoutBadCount = 5'
'ResetLockoutCount = 30'
'LockoutDuration = 30'
'[Version]'
'signature="$CHICAGO$"'
'Revision=1'
)
$inf = $infLines -join "`r`n"
[System.IO.File]::WriteAllText("$secDir\GptTmpl.inf", $inf, [System.Text.Encoding]::Unicode)
Write-Output " [OK] GptTmpl.inf: password min 12, history 24, max-age 90, lockout 5/30"
$iniPath = "$gpoPath\GPT.INI"
$raw = [System.IO.File]::ReadAllText($iniPath)
$ver = 0; if ($raw -match '(?m)^Version=(\d+)') { $ver = [int]$Matches[1] }
$mVer = ($ver -band 0xFFFF) + 1
$uVer = ($ver -shr 16) -band 0xFFFF
$newVer = ($uVer -shl 16) -bor $mVer
$userExt = ''; if ($raw -match '(?m)^gPCUserExtensionNames=([^\r\n]+)') { $userExt = $Matches[1] }
$newIni = "[General]`r`nVersion=$newVer`r`ngPCMachineExtensionNames=[{827D319E-6EAC-11D2-A4EA-00C04F79F83A}{803E14A0-B4FB-11D0-A0D0-00A0C90F574B}]`r`n"
if ($userExt) { $newIni += "gPCUserExtensionNames=$userExt`r`n" }
[System.IO.File]::WriteAllText($iniPath, $newIni, [System.Text.Encoding]::ASCII)
Write-Output " [OK] GPT.INI updated with security extension"
Write-Output ""
Write-Output "=== 3. CSC - Windows Update ==="
if (-not (Get-GPO -Name 'CSC - Windows Update' -Domain $domain -ErrorAction SilentlyContinue)) {
New-GPO -Name 'CSC - Windows Update' -Domain $domain `
-Comment 'Phase 2.6: Auto download, scheduled install Sundays 3 AM, no auto-restart with active sessions. UNLINKED - link at domain root during Phase 3.' | Out-Null
Write-Output " [OK] GPO created"
} else { Write-Output " [--] Already exists" }
$wuKey = 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'
foreach ($kv in @(
@{ N='NoAutoUpdate'; V=0; T='DWord' }
@{ N='AUOptions'; V=4; T='DWord' }
@{ N='ScheduledInstallDay'; V=1; T='DWord' }
@{ N='ScheduledInstallTime'; V=3; T='DWord' }
@{ N='NoAutoRebootWithLoggedOnUsers'; V=1; T='DWord' }
@{ N='EnableFeaturedSoftware'; V=0; T='DWord' }
)) {
Set-GPRegistryValue -Name 'CSC - Windows Update' -Key $wuKey `
-ValueName $kv.N -Type $kv.T -Value $kv.V | Out-Null
}
Write-Output " [OK] Windows Update: auto DL, Sunday 3 AM install, no forced reboot"
Write-Output ""
Write-Output "=== CSC GPO Status ==="
Get-GPO -All -Domain $domain | Where-Object { $_.DisplayName -like 'CSC - *' } |
Select-Object DisplayName, GpoStatus | Sort-Object DisplayName | Format-Table -AutoSize

View File

@@ -0,0 +1,134 @@
Import-Module GroupPolicy -ErrorAction Stop
$ErrorActionPreference = 'Continue'
$domain = 'cascades.local'
$srv = 'CS-SERVER'
$sysvol = "\\$srv\SYSVOL\$domain\Policies"
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Output "=== 1. CSC - Printer Deployment ==="
if (-not (Get-GPO -Name 'CSC - Printer Deployment' -Domain $domain -ErrorAction SilentlyContinue)) {
New-GPO -Name 'CSC - Printer Deployment' -Domain $domain `
-Comment 'Phase 2.6: Deploy CS-SERVER shared printers with OU-based ILT. UNLINKED - link to OU=Workstations at Phase 3 cutover.' | Out-Null
Write-Output " [OK] GPO created"
} else { Write-Output " [--] Already exists" }
$gpo = Get-GPO -Name 'CSC - Printer Deployment' -Domain $domain
$gpoPath = "$sysvol\{$($gpo.Id.ToString().ToUpper())}"
$pDir = "$gpoPath\User\Preferences\Printers"
New-Item -Path $pDir -ItemType Directory -Force | Out-Null
$printers = @(
@{ Share='CopyRoom'; OUs=@() }
@{ Share='BusinessOffice'; OUs=@('OU=Administrative,OU=Departments,DC=cascades,DC=local') }
@{ Share='Accounting'; OUs=@('OU=Administrative,OU=Departments,DC=cascades,DC=local') }
@{ Share='AdminOffice'; OUs=@('OU=Administrative,OU=Departments,DC=cascades,DC=local','OU=Resident Services,OU=Departments,DC=cascades,DC=local') }
@{ Share='ExecDirector'; OUs=@('OU=Administrative,OU=Departments,DC=cascades,DC=local') }
@{ Share='SalesMarketing'; OUs=@('OU=Marketing,OU=Departments,DC=cascades,DC=local') }
@{ Share='Kitchen'; OUs=@('OU=Culinary,OU=Departments,DC=cascades,DC=local') }
@{ Share='CulinaryChef'; OUs=@('OU=Culinary,OU=Departments,DC=cascades,DC=local') }
@{ Share='FrontDesk'; OUs=@('OU=Resident Services,OU=Departments,DC=cascades,DC=local') }
@{ Share='HealthServices'; OUs=@('OU=Care-Assisted Living,OU=Departments,DC=cascades,DC=local','OU=Care-Memorycare,OU=Departments,DC=cascades,DC=local') }
@{ Share='LifeEnrichment'; OUs=@('OU=Life Enrichment,OU=Departments,DC=cascades,DC=local') }
@{ Share='MCDirector'; OUs=@('OU=Care-Memorycare,OU=Departments,DC=cascades,DC=local') }
@{ Share='MCMedTech'; OUs=@('OU=Caregivers,OU=Departments,DC=cascades,DC=local','OU=Care-Memorycare,OU=Departments,DC=cascades,DC=local') }
)
$sb = [System.Text.StringBuilder]::new()
[void]$sb.AppendLine('<?xml version="1.0" encoding="utf-8"?>')
[void]$sb.AppendLine('<Printers clsid="{1F577D12-3D1B-471c-A7BF-E0F49DC793FE}">')
foreach ($p in $printers) {
$uid = [System.Guid]::NewGuid().ToString().ToUpper()
$unc = "\\CS-SERVER\$($p.Share)"
[void]$sb.AppendLine(" <SharedPrinter clsid=""{9A5E9697-9095-436d-A0EE-4D128FDFBCE5}"" name=""$unc"" status=""$unc"" image=""0"" changed=""$ts"" uid=""{$uid}"">")
[void]$sb.AppendLine(" <Properties action=""U"" comment="""" path=""$unc"" location="""" default=""0"" skipLocal=""1"" deleteAll=""0"" persistent=""0"" deleteMaps=""0"" portName="""">")
if ($p.OUs.Count -gt 0) {
[void]$sb.AppendLine(" <Filters>")
$first = $true
foreach ($ou in $p.OUs) {
$bool = if ($first) { 'AND' } else { 'OR' }
[void]$sb.AppendLine(" <FilterOrgUnit bool=""$bool"" not=""0"" name=""LDAP://$ou"" userContext=""1"" directMember=""0""/>")
$first = $false
}
[void]$sb.AppendLine(" </Filters>")
} else {
[void]$sb.AppendLine(" <Filters/>")
}
[void]$sb.AppendLine(" </Properties>")
[void]$sb.AppendLine(" </SharedPrinter>")
}
[void]$sb.AppendLine("</Printers>")
[System.IO.File]::WriteAllText("$pDir\Printers.xml", $sb.ToString(), [System.Text.Encoding]::UTF8)
Write-Output " [OK] Printers.xml written (13 printers with OU-based ILT)"
$iniPath = "$gpoPath\GPT.INI"
$raw = [System.IO.File]::ReadAllText($iniPath)
$ver = 0; if ($raw -match '(?m)^Version=(\d+)') { $ver = [int]$Matches[1] }
$uVer = (($ver -shr 16) -band 0xFFFF) + 1
$mVer = $ver -band 0xFFFF
$newVer = ($uVer -shl 16) -bor $mVer
$machExt = ''; if ($raw -match '(?m)^gPCMachineExtensionNames=([^\r\n]+)') { $machExt = $Matches[1] }
$ini = "[General]`r`nVersion=$newVer`r`n"
if ($machExt) { $ini += "gPCMachineExtensionNames=$machExt`r`n" }
$ini += "gPCUserExtensionNames=[{BC75B1ED-5833-4858-9BB8-CBF0B166DF9D}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]`r`n"
[System.IO.File]::WriteAllText($iniPath, $ini, [System.Text.Encoding]::ASCII)
Write-Output " [OK] GPT.INI updated"
Write-Output ""
Write-Output "=== 2. CSC - Drive Mappings ==="
if (-not (Get-GPO -Name 'CSC - Drive Mappings' -Domain $domain -ErrorAction SilentlyContinue)) {
New-GPO -Name 'CSC - Drive Mappings' -Domain $domain `
-Comment 'Phase 2.6: M: S: T: K: R: with group/OU ILT. UNLINKED - link to OU=Departments at Phase 3 cutover.' | Out-Null
Write-Output " [OK] GPO created"
} else { Write-Output " [--] Already exists" }
$gpo = Get-GPO -Name 'CSC - Drive Mappings' -Domain $domain
$gpoPath = "$sysvol\{$($gpo.Id.ToString().ToUpper())}"
$dDir = "$gpoPath\User\Preferences\Drives"
New-Item -Path $dDir -ItemType Directory -Force | Out-Null
$drives = @(
@{ Letter='M'; Share='\\CS-SERVER\Management'; Label='Management'; FType='Group'; FVal='CASCADES\SG-Mgmt-RW' }
@{ Letter='S'; Share='\\CS-SERVER\Sales'; Label='Sales'; FType='Group'; FVal='CASCADES\SG-Sales-RW' }
@{ Letter='T'; Share='\\CS-SERVER\Activities'; Label='Activities'; FType='Group'; FVal='CASCADES\SG-Activities-RW' }
@{ Letter='K'; Share='\\CS-SERVER\Culinary'; Label='Culinary'; FType='OU'; FVal='OU=Culinary,OU=Departments,DC=cascades,DC=local' }
@{ Letter='R'; Share='\\CS-SERVER\Receptionist'; Label='Receptionist'; FType='OU'; FVal='OU=Resident Services,OU=Departments,DC=cascades,DC=local' }
)
$sb2 = [System.Text.StringBuilder]::new()
[void]$sb2.AppendLine('<?xml version="1.0" encoding="utf-8"?>')
[void]$sb2.AppendLine('<Drives clsid="{8FDDCC1A-0C3C-43cd-A6B4-8A48251CB2FE}">')
foreach ($d in $drives) {
$uid = [System.Guid]::NewGuid().ToString().ToUpper()
[void]$sb2.AppendLine(" <Drive clsid=""{935D1B74-9CB8-4e3c-9914-7DD559B7A417}"" name=""$($d.Letter):"" status=""$($d.Letter):"" image=""2"" changed=""$ts"" uid=""{$uid}"">")
[void]$sb2.AppendLine(" <Properties action=""U"" thisDrive=""SHOW"" allDrives=""NOCHANGE"" userName="""" path=""$($d.Share)"" label=""$($d.Label)"" persistent=""1"" useLetter=""1"" letter=""$($d.Letter)"">")
[void]$sb2.AppendLine(" <Filters>")
if ($d.FType -eq 'Group') {
[void]$sb2.AppendLine(" <FilterGroup bool=""AND"" not=""0"" name=""$($d.FVal)"" sid="""" userContext=""1"" primaryGroup=""0"" localGroup=""0""/>")
} else {
[void]$sb2.AppendLine(" <FilterOrgUnit bool=""AND"" not=""0"" name=""LDAP://$($d.FVal)"" userContext=""1"" directMember=""0""/>")
}
[void]$sb2.AppendLine(" </Filters>")
[void]$sb2.AppendLine(" </Properties>")
[void]$sb2.AppendLine(" </Drive>")
}
[void]$sb2.AppendLine("</Drives>")
[System.IO.File]::WriteAllText("$dDir\Drives.xml", $sb2.ToString(), [System.Text.Encoding]::UTF8)
Write-Output " [OK] Drives.xml written (M: S: T: K: R: with group/OU ILT)"
$iniPath = "$gpoPath\GPT.INI"
$raw = [System.IO.File]::ReadAllText($iniPath)
$ver = 0; if ($raw -match '(?m)^Version=(\d+)') { $ver = [int]$Matches[1] }
$uVer = (($ver -shr 16) -band 0xFFFF) + 1
$mVer = $ver -band 0xFFFF
$newVer = ($uVer -shl 16) -bor $mVer
$machExt = ''; if ($raw -match '(?m)^gPCMachineExtensionNames=([^\r\n]+)') { $machExt = $Matches[1] }
$ini = "[General]`r`nVersion=$newVer`r`n"
if ($machExt) { $ini += "gPCMachineExtensionNames=$machExt`r`n" }
$ini += "gPCUserExtensionNames=[{5794DAFD-BE60-433f-88A2-1A31939AC01F}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]`r`n"
[System.IO.File]::WriteAllText($iniPath, $ini, [System.Text.Encoding]::ASCII)
Write-Output " [OK] GPT.INI updated"
Write-Output ""
Write-Output "=== All CSC GPOs ==="
Get-GPO -All -Domain $domain | Where-Object { $_.DisplayName -like 'CSC - *' } |
Select-Object DisplayName, GpoStatus | Sort-Object DisplayName | Format-Table -AutoSize

View File

@@ -0,0 +1,97 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.5a: Create new AD security groups for staged share rollout.
.DESCRIPTION
Creates three new global security groups for the new share structure.
Groups are created EMPTY — members are added per-department when each
department is ready to cut over to the new shares.
Also removes Tamra.Matthews from SG-Sales-RW (she moves to SG-Sales-RO).
No other changes are made to existing groups or members.
.NOTES
IDEMPOTENT — safe to re-run. Existing groups are skipped, not overwritten.
Run on CS-SERVER via GuruRMM remote execution.
Verify $GroupOU before running:
Get-ADGroup SG-Management-RW | Select DistinguishedName
The OU in $GroupOU must match the OU where existing SG- groups live.
#>
Import-Module ActiveDirectory -ErrorAction Stop
# --- VERIFY THIS MATCHES WHERE EXISTING SG- GROUPS LIVE ---
# Check with: Get-ADGroup SG-Management-RW | Select DistinguishedName
$GroupOU = "OU=Groups,DC=cascades,DC=local"
Write-Host "=== Phase 2.5a: New AD Security Groups ===" -ForegroundColor Cyan
Write-Host ""
# ============================================================
# STEP 1: Create new groups (empty — members added later)
# ============================================================
Write-Host "--- Creating New Security Groups ---" -ForegroundColor Yellow
$newGroups = @(
@{ Name = "SG-Mgmt-RW"; Description = "Management share - Read/Write" }
@{ Name = "SG-Sales-RO"; Description = "Sales share - Read Only" }
@{ Name = "SG-Activities-RW"; Description = "Activities share - Read/Write" }
)
foreach ($g in $newGroups) {
try {
$existing = Get-ADGroup -Filter "Name -eq '$($g.Name)'" -ErrorAction SilentlyContinue
if (-not $existing) {
New-ADGroup `
-Name $g.Name `
-GroupScope Global `
-GroupCategory Security `
-Path $GroupOU `
-Description $g.Description `
-ErrorAction Stop
Write-Host " [OK] Created: $($g.Name)" -ForegroundColor Green
} else {
Write-Host " [SKIP] $($g.Name) already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create $($g.Name): $_" -ForegroundColor Red
}
}
# ============================================================
# STEP 2: Remove Tamra.Matthews from SG-Sales-RW
# ============================================================
Write-Host "`n--- Adjusting SG-Sales-RW Membership ---" -ForegroundColor Yellow
try {
$isMember = Get-ADGroupMember -Identity "SG-Sales-RW" -ErrorAction Stop |
Where-Object { $_.SamAccountName -eq "Tamra.Matthews" }
if ($isMember) {
Remove-ADGroupMember -Identity "SG-Sales-RW" -Members "Tamra.Matthews" -Confirm:$false -ErrorAction Stop
Write-Host " [OK] Removed Tamra.Matthews from SG-Sales-RW" -ForegroundColor Green
} else {
Write-Host " [SKIP] Tamra.Matthews is not a member of SG-Sales-RW" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to adjust SG-Sales-RW: $_" -ForegroundColor Red
}
# ============================================================
# SUMMARY: All SG- groups with member counts
# ============================================================
Write-Host "`n=== SG- Group Summary ===" -ForegroundColor Cyan
Write-Host ""
Get-ADGroup -Filter 'Name -like "SG-*"' -ErrorAction SilentlyContinue |
Sort-Object Name |
ForEach-Object {
$count = (Get-ADGroupMember $_ -ErrorAction SilentlyContinue | Measure-Object).Count
Write-Host (" {0,-25} {1,2} member(s)" -f $_.Name, $count) -ForegroundColor Cyan
}
Write-Host ""
Write-Host "=== AD Groups Complete ===" -ForegroundColor Cyan
Write-Host "Next: Run phase2-new-shares.ps1 to create the folder structure and SMB shares" -ForegroundColor Green

View File

@@ -0,0 +1,173 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.5b: Create new share folders, NTFS permissions, and SMB shares on CS-SERVER.
.DESCRIPTION
Builds the folder structure for the staged share rollout. Folders are created
empty — data sync runs separately after this script. Sets NTFS permissions with
broken inheritance and creates SMB shares with Access-Based Enumeration enabled.
Shares created: Management, Sales, Activities, Server.
Does NOT touch: D:\Shares\homes, D:\Shares\Culinary, D:\Shares\Receptionist,
D:\Shares\directoryshare, D:\Shares\IT, D:\Shares\chat, D:\Shares\Public,
or any other existing shares.
.NOTES
IDEMPOTENT — safe to re-run. NTFS permissions are always reapplied (not skipped).
Existing SMB shares have their description updated; share-level permissions are
left alone on re-run.
Requires the ActiveDirectory module and must be run as Administrator on CS-SERVER.
Run AFTER phase2-ad-groups-new.ps1.
#>
Import-Module ActiveDirectory -ErrorAction Stop
$DestRoot = "D:\Shares"
Write-Host "=== Phase 2.5b: New Share Folders & Permissions ===" -ForegroundColor Cyan
Write-Host ""
# --- Share definitions ---
# RWGroup and ROGroup may be $null. $null means that ACE is omitted.
$shares = @(
@{
Name = "Management"
Path = "$DestRoot\Management"
RWGroup = "CASCADES\SG-Mgmt-RW"
ROGroup = $null
Desc = "Management share (Directors only)"
},
@{
Name = "Sales"
Path = "$DestRoot\Sales"
RWGroup = "CASCADES\SG-Sales-RW"
ROGroup = "CASCADES\SG-Sales-RO"
Desc = "Sales share"
},
@{
Name = "Activities"
Path = "$DestRoot\Activities"
RWGroup = "CASCADES\SG-Activities-RW"
ROGroup = $null
Desc = "Activities share (Life Enrichment)"
},
@{
Name = "Server"
Path = "$DestRoot\Server"
RWGroup = "CASCADES\SG-IT-RW"
ROGroup = "CASCADES\Domain Users"
Desc = "Server share (IT tools and scripts)"
}
)
foreach ($s in $shares) {
Write-Host "`n--- $($s.Name) ---" -ForegroundColor Yellow
# Create folder if it doesn't exist
try {
if (-not (Test-Path $s.Path)) {
New-Item -Path $s.Path -ItemType Directory -Force | Out-Null
Write-Host " [OK] Created folder: $($s.Path)" -ForegroundColor Green
} else {
Write-Host " [SKIP] Folder already exists: $($s.Path)" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create folder $($s.Path): $_" -ForegroundColor Red
continue
}
# Set NTFS permissions (always reapplied — not skipped on re-run)
try {
$acl = New-Object System.Security.AccessControl.DirectorySecurity
# Break inheritance and discard all inherited entries
$acl.SetAccessRuleProtection($true, $false)
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"SYSTEM",
"FullControl",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"CASCADES\Domain Admins",
"FullControl",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)))
if ($s.RWGroup) {
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
$s.RWGroup,
"Modify",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)))
}
if ($s.ROGroup) {
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
$s.ROGroup,
"ReadAndExecute",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)))
}
Set-Acl -Path $s.Path -AclObject $acl -ErrorAction Stop
Write-Host " [OK] NTFS permissions set" -ForegroundColor Green
}
catch {
Write-Host " [ERROR] NTFS permissions failed on $($s.Path): $_" -ForegroundColor Red
continue
}
# Create or update SMB share
try {
$existingShare = Get-SmbShare -Name $s.Name -ErrorAction SilentlyContinue
if (-not $existingShare) {
New-SmbShare `
-Name $s.Name `
-Path $s.Path `
-Description $s.Desc `
-FullAccess "Authenticated Users" `
-FolderEnumerationMode AccessBased `
-ErrorAction Stop
Write-Host " [OK] Created SMB share: \\CS-SERVER\$($s.Name) (ABE enabled)" -ForegroundColor Green
} else {
# Update description only; share-level permissions are left alone
Set-SmbShare -Name $s.Name -Description $s.Desc -Force -ErrorAction Stop
Write-Host " [SKIP] SMB share already exists — description updated" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] SMB share failed for $($s.Name): $_" -ForegroundColor Red
}
}
# ============================================================
# SUMMARY
# ============================================================
Write-Host "`n=== New Shares Summary ===" -ForegroundColor Cyan
Write-Host "`nAll SMB shares on D:\:" -ForegroundColor Yellow
Get-SmbShare | Where-Object { $_.Path -like "D:\*" } |
Select-Object Name, Path, Description, FolderEnumerationMode |
Format-Table -AutoSize -Wrap
Write-Host "NTFS permissions on new folders:" -ForegroundColor Yellow
foreach ($s in $shares) {
if (Test-Path $s.Path) {
Write-Host "`n $($s.Path):" -ForegroundColor Cyan
& icacls $s.Path
}
}
Write-Host "`n=== New Share Setup Complete ===" -ForegroundColor Cyan
Write-Host "Folders are empty — sync data separately before activating each department." -ForegroundColor Green

View File

@@ -1,123 +1,195 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.4: Set up print server on CS-SERVER.
.DESCRIPTION
Creates TCP/IP printer ports for all managed printers.
Drivers must be installed manually before uncommenting the Add-Printer lines.
Run on CS-SERVER via ScreenConnect.
.NOTES
Download drivers from manufacturer websites FIRST:
- Epson ET-5800: https://epson.com/Support/Printers/All-In-Ones/ET-Series/Epson-ET-5800/s/SPT_C11CJ30201
- Canon MF455DW: https://www.usa.canon.com/support/p/imageclass-mf455dw
- Canon MF451CDW: https://www.usa.canon.com/support/p/imageclass-mf451dw
- Brother MFC-L8900CDW: https://www.brother-usa.com/support/mfcl8900cdw
- Konica Minolta Bizhub C368: https://www.konicaminolta.us/en-us/support/download-centre
Install drivers on CS-SERVER, then uncomment the Add-Printer section below.
#>
# Phase 2.6 — CS-SERVER Print Server Setup
# Run on CS-SERVER via GuruRMM remote PowerShell
# Last updated: 2026-05-20 (Howard)
#
# STATUS AS OF 2026-05-20: COMPLETE — all 13 printers installed and shared
# KM driver folder archived to: D:\Shares\Server\Drivers\KM_Universal_PCL6\
# Epson INF files at: C:\Users\sysadmin\Documents\ComputerGuru Connect v2\Files\epsonetdrivers\
#
# Drivers installed on CS-SERVER:
# Canon Generic Plus PCL6 — Copy Room, Accounting, Executive Director, Kitchen, Life Enrichment, Memory Care Director
# Brother Generic Jpeg Type2 Class Driver — Business Office, Admin Office, Sales Marketing, Culinary Chef, Memory Care MedTech
# KONICA MINOLTA Universal PCL — Health Services C368
# EPSON ET-5800 Series — Front Desk (driver staged via pnputil, registered via Add-PrinterDriver)
#
# Epson ET-5800 install notes:
# EPWizard.exe fails on Server 2019 (wlanapi.dll stub — WLAN stack absent).
# Workaround: run installer on Server, copy extracted INFs from AppData\Local\Temp\ET-5800
# before dismissing error. pnputil stages them; Add-PrinterDriver registers with spooler.
Write-Host "=== Phase 2.4: Print Server Setup ===" -ForegroundColor Cyan
Write-Host ""
$ErrorActionPreference = 'Continue'
# --- Ensure Print Server feature is installed ---
Write-Host "--- Checking Print Server role ---" -ForegroundColor Yellow
$printFeature = Get-WindowsFeature -Name Print-Server -ErrorAction SilentlyContinue
if ($printFeature -and -not $printFeature.Installed) {
# Verify Print Server role is installed
$role = Get-WindowsFeature -Name Print-Server -ErrorAction SilentlyContinue
if ($role -and -not $role.Installed) {
Install-WindowsFeature -Name Print-Server -IncludeManagementTools
Write-Host " [OK] Print Server role installed" -ForegroundColor Green
Write-Output '[OK] Print Server role installed'
} else {
Write-Host " [OK] Print Server role already installed" -ForegroundColor Green
Write-Output '[--] Print Server role already present'
}
# --- Define printers ---
$printers = @(
@{ Name = "Front Desk - Epson ET-5800"; IP = "192.168.2.147"; ShareName = "FrontDesk-Epson"; Driver = "EPSON ET-5800 Series" }
@{ Name = "Business Office - Canon MF455DW"; IP = "192.168.3.227"; ShareName = "BizOffice-Canon"; Driver = "Canon Generic Plus UFR II" }
@{ Name = "Marketing - Brother MFC-L8900CDW"; IP = "192.168.2.21"; ShareName = "Marketing-Brother"; Driver = "Brother MFC-L8900CDW series" }
@{ Name = "206 Health - Bizhub C368"; IP = "192.168.1.138"; ShareName = "Health206-Bizhub"; Driver = "KONICA MINOLTA Universal PCL" }
@{ Name = "206 Nurse Station - Brother MFC-L8900CDW"; IP = "10.0.20.69"; ShareName = "Health206-Brother"; Driver = "Brother MFC-L8900CDW series" }
@{ Name = "MemCare MedTech - Brother (model TBD)"; IP = "192.168.2.53"; ShareName = "MemCare-Brother"; Driver = "TBD" }
@{ Name = "MemCare Director - Canon MF451CDW"; IP = "192.168.3.52"; ShareName = "MemDir-Canon"; Driver = "Canon Generic Plus UFR II" }
@{ Name = "Kitchen Printer"; IP = "192.168.0.121"; ShareName = "Kitchen"; Driver = "TBD" }
# Everyone gets this one — set as default via GPO
@{
IP = '192.168.2.230'
Port = 'TCP_192.168.2.230'
Name = 'Copy Room Canon'
Driver = 'Canon Generic Plus PCL6'
Share = 'CopyRoom'
Location = 'Copy Room (1st Floor)'
Comment = 'Canon imageRunner C478iF - All staff'
}
# Administrative / BOD
@{
IP = '10.0.20.220'
Port = 'TCP_10.0.20.220'
Name = 'BOD Brother'
Driver = 'Brother Generic Jpeg Type2 Class Driver'
Share = 'BOD-Brother'
Location = 'Room 103 area (Acct Asst desk)'
Comment = 'Brother MFC-L8900CDW - Business Office Director area'
}
@{
IP = '192.168.3.227'
Port = 'TCP_192.168.3.227'
Name = 'Accounting Canon'
Driver = 'Canon Generic Plus PCL6'
Share = 'Accounting'
Location = 'Accounting (Room 103)'
Comment = 'Canon imageClass MF455DW - Lauren Hasselman'
}
@{
IP = '192.168.2.145'
Port = 'TCP_192.168.2.145'
Name = 'Room 103 Brother'
Driver = 'Brother Generic Jpeg Type2 Class Driver'
Share = 'Room103'
Location = 'Room 103'
Comment = 'Brother MFC-9340CDW - Ashley Jensen, Christina DuPras'
}
@{
IP = '192.168.2.67'
Port = 'TCP_192.168.2.67'
Name = 'Executive Office Canon'
Driver = 'Canon Generic Plus PCL6'
Share = 'Exec-Office'
Location = "Meredith's Office"
Comment = 'Canon imageClass MF743CDW - Meredith Kuhn only'
}
# Marketing / Sales
@{
IP = '192.168.3.44'
Port = 'TCP_192.168.3.44'
Name = 'Marketing Brother'
Driver = 'Brother Generic Jpeg Type2 Class Driver'
Share = 'Marketing'
Location = 'Room 217 (Marketing/Sales)'
Comment = 'Brother MFC-L8900CDW - Sales team'
}
# Culinary
@{
IP = '192.168.3.232'
Port = 'TCP_192.168.3.232'
Name = 'Kitchen Canon'
Driver = 'Canon Generic Plus PCL6'
Share = 'Kitchen'
Location = 'Kitchen Manager desk'
Comment = 'Canon imageClass MFC743CDW - Alyssa Brooks'
}
@{
IP = '192.168.3.88'
Port = 'TCP_192.168.3.88'
Name = 'Chef Brother'
Driver = 'Brother Generic Jpeg Type2 Class Driver'
Share = 'Chef'
Location = 'Kitchen Chef station'
Comment = 'Brother MFC-9330CDW - JD Martin / Chef'
}
# Front Desk
@{
IP = '192.168.2.147'
Port = 'TCP_192.168.2.147'
Name = 'Front Desk - Epson ET-5800'
Driver = 'EPSON ET-5800 Series'
Share = 'FrontDesk'
Location = 'Front Desk'
Comment = 'Epson ET-5800 - Front Desk'
}
# Memory Care
@{
IP = '192.168.3.52'
Port = 'TCP_192.168.3.52'
Name = 'Memory Care Director - Canon MF751CDW'
Driver = 'Canon Generic Plus PCL6'
Share = 'MCDirector'
Location = 'Memory Care Room 603'
Comment = 'Canon imageClass MF751CDW - Shelby Trozzi'
}
@{
IP = '192.168.2.53'
Port = 'TCP_192.168.2.53'
Name = 'Memory Care MedTech - Brother'
Driver = 'Brother Generic Jpeg Type2 Class Driver'
Share = 'MCMedTech'
Location = 'Memory Care Room 615'
Comment = 'Brother - MedTechs / Nurses'
}
)
# --- Create TCP/IP printer ports ---
Write-Host "`n--- Creating Printer Ports ---" -ForegroundColor Yellow
Write-Output ''
Write-Output '=== Installing Printers ==='
foreach ($p in $printers) {
$portName = "TCP_$($p.IP)"
try {
$existing = Get-PrinterPort -Name $portName -ErrorAction SilentlyContinue
if (-not $existing) {
Add-PrinterPort -Name $portName -PrinterHostAddress $p.IP
Write-Host " [OK] Created port: $portName ($($p.Name))" -ForegroundColor Green
} else {
Write-Host " [SKIP] Port $portName already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create port $portName : $_" -ForegroundColor Red
}
}
Write-Output ''
Write-Output "--- $($p.Name) ($($p.IP)) ---"
# --- Test connectivity to each printer ---
Write-Host "`n--- Testing Printer Connectivity ---" -ForegroundColor Yellow
foreach ($p in $printers) {
$result = Test-Connection -ComputerName $p.IP -Count 1 -Quiet -ErrorAction SilentlyContinue
if ($result) {
Write-Host " [OK] $($p.Name) ($($p.IP)) - reachable" -ForegroundColor Green
# Create TCP/IP port if needed
if (-not (Get-PrinterPort -Name $p.Port -ErrorAction SilentlyContinue)) {
Add-PrinterPort -Name $p.Port -PrinterHostAddress $p.IP
Write-Output " [OK] Port: $($p.Port)"
} else {
Write-Host " [WARN] $($p.Name) ($($p.IP)) - NOT reachable" -ForegroundColor Yellow
Write-Output " [--] Port exists: $($p.Port)"
}
# Add and share the printer
if (-not (Get-Printer -Name $p.Name -ErrorAction SilentlyContinue)) {
Add-Printer -Name $p.Name `
-DriverName $p.Driver `
-PortName $p.Port `
-Shared `
-ShareName $p.Share `
-Location $p.Location `
-Comment $p.Comment
Write-Output " [OK] Installed + shared: \\CS-SERVER\$($p.Share)"
} else {
Write-Output " [--] Already installed: $($p.Name)"
}
}
# --- Add shared printers (UNCOMMENT after installing drivers) ---
<#
Write-Host "`n--- Creating Shared Printers ---" -ForegroundColor Yellow
foreach ($p in $printers) {
if ($p.Driver -eq "TBD") {
Write-Host " [SKIP] $($p.Name) - driver not specified" -ForegroundColor Yellow
continue
}
$portName = "TCP_$($p.IP)"
try {
$existing = Get-Printer -Name $p.Name -ErrorAction SilentlyContinue
if (-not $existing) {
Add-Printer -Name $p.Name -DriverName $p.Driver -PortName $portName -Shared -ShareName $p.ShareName -Published
Write-Host " [OK] Created printer: $($p.Name) (\\CS-SERVER\$($p.ShareName))" -ForegroundColor Green
} else {
Write-Host " [SKIP] Printer $($p.Name) already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create $($p.Name): $_" -ForegroundColor Red
Write-Host " Verify driver '$($p.Driver)' is installed: Get-PrinterDriver" -ForegroundColor Yellow
}
Write-Output ''
Write-Output '=== Connectivity Check ==='
$all = @(
@{ Name='Copy Room Canon'; IP='192.168.2.230' }
@{ Name='BOD Brother'; IP='10.0.20.220' }
@{ Name='Accounting Canon'; IP='192.168.3.227' }
@{ Name='Room 103 Brother'; IP='192.168.2.145' }
@{ Name='Executive Office'; IP='192.168.2.67' }
@{ Name='Marketing Brother'; IP='192.168.3.44' }
@{ Name='Kitchen Canon'; IP='192.168.3.232' }
@{ Name='Chef Brother'; IP='192.168.3.88' }
@{ Name='Front Desk - Epson'; IP='192.168.2.147' }
@{ Name='Health Services C368'; IP='192.168.1.138' }
@{ Name='MC Director Canon MF751CDW'; IP='192.168.3.52' }
@{ Name='MC MedTech Brother'; IP='192.168.2.53' }
)
foreach ($p in $all) {
$ok = Test-Connection -ComputerName $p.IP -Count 1 -Quiet -ErrorAction SilentlyContinue
$status = if ($ok) { '[OK] reachable' } else { '[WARN] NOT reachable' }
Write-Output " $status - $($p.Name) ($($p.IP))"
}
#>
# --- Summary ---
Write-Host "`n=== Print Server Summary ===" -ForegroundColor Cyan
Write-Host "`nPrinter Ports:" -ForegroundColor Yellow
Get-PrinterPort | Where-Object { $_.Name -like "TCP_*" } |
Select-Object Name, PrinterHostAddress |
Format-Table -AutoSize
Write-Host "Installed Printer Drivers:" -ForegroundColor Yellow
Get-PrinterDriver | Select-Object Name | Format-Table -AutoSize
Write-Host "Shared Printers:" -ForegroundColor Yellow
Write-Output ''
Write-Output '=== All Shared Printers on CS-SERVER ==='
Get-Printer | Where-Object { $_.Shared } |
Select-Object Name, PortName, ShareName, DriverName |
Select-Object Name, ShareName, PortName, DriverName |
Format-Table -AutoSize
Write-Host "`n=== Print Server Setup Complete ===" -ForegroundColor Cyan
Write-Host "Next steps:" -ForegroundColor Green
Write-Host " 1. Download and install printer drivers (see .NOTES in script header)"
Write-Host " 2. Uncomment the 'Add shared printers' section and re-run"
Write-Host " 3. Print a test page from CS-SERVER to each printer"
Write-Host " 4. Create GPOs in GPMC (see phase2-server-prep.md section 2.5)"

View File

@@ -8,67 +8,132 @@
- Sites: Default-First-Site-Name
- No trusts configured
## AD Users (42 total — 40 enabled, 2 disabled) — cleaned 2026-04-13
## AD Users (updated 2026-05-19)
**New since last doc update:** Allison Reibschied (2026-03-13), Lauren Hasselman (2026-02-26)
**Changes since 2026-04-13:**
- Alma.Montt added to OU=Administrative (provisioned 2026-05-19) — cloud-only M365 account also created same day; needs reconciliation (see Pending Issues)
- Kyla.QuickTiffany confirmed in OU=Resident Services (was listed as "needs account" in prior doc)
- Zachary.Nelson confirmed: Accounting Assistant (replacing Allison.Reibschied)
- Allison.Reibschied: no longer employed — account disabled in DC 2026-05-19
- 38 caregiver accounts active in OU=Caregivers (new dedicated OU, all syncing to Entra)
- s.nunn confirmed as the correct Shontiel Nunn account (Caregivers/MedTech). Shontiel.Nunn (old format, OU=Resident Services) to be disabled.
### Enabled Accounts — HR Roster (updated 2026-04-13)
| Name | SamAccountName | Position | Department | Shared Email | Notes |
|------|---------------|----------|------------|-------------|-------|
| Administrator | Administrator | — | — | — | Built-in |
| localadmin | localadmin | — | — | — | Local admin |
| Sysadmin | sysadmin | — | — | — | System admin |
| Howard Dax | howard | Home Office | Administrative | first.last@ | MSP technician |
| Meredith Kuhn | Meredith.Kuhn | Executive Director | Administrative | first.last@ | |
| John Trozzi | John.Trozzi | Maintenance Director | Maintenance | first.last@ | PC: MAINTENANCE-PC |
| Lupe Sanchez | Lupe.Sanchez | Housekeeping Director | Housekeeping | first.last@ | Renamed from Guadalupe.Sanchez, duplicate deleted (2026-04-13) |
| Megan Hiatt | Megan.Hiatt | Sales Director | Marketing | first.last@, Sales@ | |
| Crystal Rodriguez | Crystal.Rodriguez | Sales Associate | Marketing | first.last@, Sales@ | PC: CRYSTAL-PC |
| Tamra Matthews | Tamra.Matthews | Move-In Coordinator | Marketing | first.last@ | Renamed from Tamra.Johnson (2026-04-13) |
| Lois Lane | Lois.Lane | Health Services Director | Care, Assisted Living | first.last@, Nurses@ | |
| Christina DuPras | Christina.DuPras | Resident Services Director | Resident Services | first.last@ | |
| Christine Nyanzunda | Christine.Nyanzunda | Memory Care Admin Assistant | Care, Memory Care | first.last@ | |
| Susan Hicks | Susan.Hicks | Life Enrichment Director | Life Enrichment | first.last@ | PC: DESKTOP-ROK7VNM |
| Ashley Jensen | Ashley.Jensen | Assistant Executive Director | Administrative | first.last@, Accounting@ | |
| Veronica Feller | Veronica.Feller | Care, Assisted Living Aide | Care, Assisted Living | first.last@ | |
| Sebastian Leon | Sebastian.Leon | RS Courtesy Patrol | Resident Services | Frontdesk@, Courtesypatrol@ | |
| JD Martin | JD.Martin | Culinary Director | Culinary | first.last@ | |
| Alyssa Brooks | Alyssa.Brooks | Dining Manager | Culinary | first.last@ | Renamed from Alyssa.Shestko, duplicate deleted (2026-04-13) |
| Matt Brooks | Matt.Brooks | Memory Care Receptionist | Maintenance | first.last@ | Dept says Maintenance (HR data) |
| Ramon Castaneda | Ramon.Castaneda | Kitchen Manager | Culinary | first.last@ | |
| Michelle Shestko | Michelle.Shestko | Resident Services Receptionist | Resident Services | MC Front Desk | |
| Sharon Edwards | Sharon.Edwards | Life Enrichment Assistant | Life Enrichment | first.last@ | PC: DESKTOP-DLTAGOI |
| Britney Thompson | britney.thompson | Memory Care Nurse | Care, Assisted Living | first.last@, Nurses@ | **DEPARTED 2026-04-22 per John — disable account + harvest license** |
| Shelby Trozzi | Shelby.Trozzi | Memory Care Director | Care, Memory Care | first.last@ | Renamed from strozzi (2026-04-13) |
| Karen Rossini | karen.rossini | Health Services Manager | Care, Assisted Living | first.last@, Nurses@ | lowercase SamAccountName |
| Sheldon Gardfrey | Sheldon.Gardfrey | RS Courtesy Patrol | Resident Services | Frontdesk@, Courtesypatrol@ | |
| Cathy Kingston | Cathy.Kingston | Resident Services Receptionist | Resident Services | Frontdesk@ | |
| Shontiel Nunn | Shontiel.Nunn | Resident Services Receptionist | Resident Services | Frontdesk@ | |
| Ray Rai | Ray.Rai | RS Courtesy Patrol | Resident Services | Frontdesk@ | |
| Richard Adams | Richard.Adams | Driver | Transportation | Transportation@ | **2026-04-22: disable — drivers no longer get IT access** |
| Julian Crim | Julian.Crim | Driver | Transportation | Transportation@ | **2026-04-22: disable — drivers no longer get IT access** |
| Christopher Holick | Christopher.Holick | Driver | Transportation | Transportation@ | Fixed from Holik (2026-04-13). **2026-04-22: disable — drivers no longer get IT access** |
| Lauren Hasselman | lauren.hasselman | Business Office Director | Administrative | first.last@, Accounting@ | Replaced Jeff Bristol. lowercase SamAccountName |
| Allison Reibschied | Allison.Reibschied | Accounting Assistant | Administrative | first.last@ | Added 2026-03-13. PC: ACCT2-PC |
| QBDataServiceUser34 | QBDataServiceUser34 | — | — | — | QuickBooks service account |
| Culinary | Culinary | — | — | — | Generic department account — replace Phase 5 |
| RECEPTIONIST | Receptionist | — | — | — | Generic role account — replace Phase 5 |
| saleshare | saleshare | — | — | — | Shared sales resource — replace Phase 5 |
| directoryshare | directoryshare | — | — | — | Shared directory resource — replace Phase 5 |
### Enabled Accounts — Staff (updated 2026-05-19)
### Not in AD — Needs Account Created
| Name | Position | Department | Shared Email | Notes |
|------|----------|------------|-------------|-------|
| Kyla Quick Tiffany | Resident Services Receptionist | Resident Services | Frontdesk@ | New — needs AD + M365 account |
**OU=Administrative**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Meredith.Kuhn | Meredith Kuhn | Executive Director | |
| Ashley.Jensen | Ashley Jensen | Assistant Executive Director | M365: Accounting@ |
| lauren.hasselman | Lauren Hasselman | Business Office Director | lowercase SAM. Replaced Jeff Bristol. M365: Accounting@ |
| Alma.Montt | Alma Montt | Life Enrichment | Provisioned 2026-05-19. **Cloud-only M365 account also created same day — reconcile before next Entra sync** (see Pending Issues) |
| Zachary.Nelson | Zachary Nelson | Accounting Assistant | Confirmed 2026-05-19. Replacing Allison.Reibschied. |
| ~~Allison.Reibschied~~ | ~~Allison Reibschied~~ | ~~Accounting Assistant~~ | **Disabled 2026-05-19 — no longer employed.** |
**OU=Care-Assisted Living**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Lois.Lane | Lois Lane | Health Services Director | M365: Nurses@ |
| karen.rossini | Karen Rossini | Health Services Manager | lowercase SAM. M365: Nurses@ |
| Veronica.Feller | Veronica Feller | Care Assisted Living Aide | |
| ~~britney.thompson~~ | ~~Britney Thompson~~ | ~~Memory Care Nurse~~ | **Disabled 2026-05-20 — departed 2026-04-22. M365 license still to harvest.** |
**OU=Care-Memorycare**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Christine.Nyanzunda | Christine Nyanzunda | Memory Care Admin Assistant | |
| Shelby.Trozzi | Shelby Trozzi | Memory Care Director | Renamed from strozzi (2026-04-13) |
**OU=Caregivers** — 38 accounts, all shift caregivers/medtechs, all in SG-Caregivers, all syncing to Entra. See Caregiver Accounts section below.
**OU=Culinary**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| JD.Martin | JD Martin | Culinary Director | |
| Alyssa.Brooks | Alyssa Brooks | Dining Manager | Renamed from Alyssa.Shestko (2026-04-13) |
| Ramon.Castaneda | Ramon Castaneda | Kitchen Manager | |
**OU=Housekeeping**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Lupe.Sanchez | Lupe Sanchez | Housekeeping Director | Renamed from Guadalupe.Sanchez, duplicate deleted (2026-04-13) |
**OU=Life Enrichment**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Sharon.Edwards | Sharon Edwards | Life Enrichment Assistant | PC: DESKTOP-DLTAGOI |
| Susan.Hicks | Susan Hicks | Life Enrichment Director | PC: DESKTOP-ROK7VNM |
**OU=Maintenance**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| John.Trozzi | John Trozzi | Maintenance Director | PC: MAINTENANCE-PC |
| Matt.Brooks | Matt Brooks | Memory Care Receptionist | Dept listed as Maintenance in HR data |
**OU=Marketing**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Megan.Hiatt | Megan Hiatt | Sales Director | M365: Sales@ |
| Crystal.Rodriguez | Crystal Rodriguez | Sales Associate | PC: CRYSTAL-PC. M365: Sales@ |
| Tamra.Matthews | Tamra Matthews | Move-In Coordinator | Renamed from Tamra.Johnson (2026-04-13) |
**OU=Resident Services**
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| Christina.DuPras | Christina DuPras | Resident Services Director | |
| Cathy.Kingston | Cathy Kingston | RS Receptionist | M365: Frontdesk@ |
| Kyla.QuickTiffany | Kyla Quick Tiffany | RS Receptionist | M365: Frontdesk@. Previously listed as "needs account" — now confirmed in AD |
| Michelle.Shestko | Michelle Shestko | RS Receptionist | M365: MC Front Desk |
| Ray.Rai | Ray Rai | RS Courtesy Patrol | M365: Frontdesk@ |
| Sebastian.Leon | Sebastian Leon | RS Courtesy Patrol | M365: Frontdesk@, Courtesypatrol@ |
| Sheldon.Gardfrey | Sheldon Gardfrey | RS Courtesy Patrol | M365: Frontdesk@, Courtesypatrol@ |
| ~~Shontiel.Nunn~~ | ~~Shontiel Nunn~~ | ~~RS Receptionist~~ | M365: Frontdesk@. **Disabled 2026-05-20 — s.nunn (Caregivers) is the correct current account.** |
**OU=Transportation** — all accounts disabled 2026-05-20
| SamAccountName | Name | Position | Notes |
|---------------|------|----------|-------|
| ~~Christopher.Holick~~ | ~~Christopher Holick~~ | ~~Driver~~ | Fixed from Holik (2026-04-13). **Disabled 2026-05-20 — drivers no longer get IT access** |
| ~~Julian.Crim~~ | ~~Julian Crim~~ | ~~Driver~~ | **Disabled 2026-05-20 — drivers no longer get IT access** |
| ~~Richard.Adams~~ | ~~Richard Adams~~ | ~~Driver~~ | **Disabled 2026-05-20 — drivers no longer get IT access** |
**CN=Users — Service Accounts**
| SamAccountName | Notes |
|---------------|-------|
| Administrator | Built-in |
| localadmin | Local admin |
| sysadmin | System admin (IT) |
| MSOL_12be42ce1269 | Entra Connect service account |
| QBDataServiceUser34 | QuickBooks service account |
**OU=Excluded-From-Sync — Shared/Generic Accounts** (intentionally not syncing to Entra)
| SamAccountName | Notes |
|---------------|-------|
| Culinary | Generic dept account — replace Phase 5 |
| directoryshare | Shared resource — replace Phase 5 |
| RECEPTIONIST | Generic role account — replace Phase 5 |
| saleshare | Shared resource — replace Phase 5 |
**OU=ServiceAccounts**
| SamAccountName | Notes |
|---------------|-------|
| svc-audit-upload | GuruRMM audit upload service account |
### Disabled Accounts
| SamAccountName | Notes |
|---------------|-------|
| Guest | Built-in — correct to leave disabled |
| krbtgt | Built-in Kerberos — **password 569+ days old as of 2026-03-20, needs rotation** |
### Accounts Deleted (2026-04-13 cleanup)
Anna.Pitzlin, Nela.Durut-Azizi, Jodi.Ramstack, Monica.Ramirez, Haris.Durut, Nuria.Diaz, Cathy.Reece, Kelly.Wallace, Isabella.Islas, ann.dery, alyssa.brooks (duplicate), Lupe.Sanchez (duplicate), jeff.bristol
Anna.Pitzlin, Nela.Durut-Azizi, Jodi.Ramstack, Monica.Ramirez, Haris.Durut, Nuria.Diaz, Cathy.Reece, Kelly.Wallace, Isabella.Islas, ann.dery, alyssa.brooks (lowercase duplicate), Lupe.Sanchez (duplicate), jeff.bristol
### Disabled Accounts (2) — cleaned 2026-04-13
| Name | SamAccountName | Notes |
|------|---------------|-------|
| Guest | Guest | Built-in — correct to leave disabled |
| krbtgt | krbtgt | Built-in Kerberos — correct to leave disabled. **Password 569+ days old — needs rotation** |
## Caregiver Accounts (OU=Caregivers)
38 accounts, all shift caregivers/medtechs, first-initial-last format (e.g., a.mcferren). All members of SG-Caregivers. All syncing to Entra ID (full-domain sync scope includes this OU).
a.atwood, a.mcferren, b.johnson, b.mendoza, b.sika, c.johnson, c.lassey, c.tate, d.fierros, e.esperance, e.huerta, e.sanchez, e.yuzon, g.williams, g.williford, j.andrade, j.clarke, j.dittbenner, j.higdon, k.aziakpo, k.flores, k.wyzykowski, l.fuster, l.hogan, m.baker, m.kariuki, m.kastner, m.lopez, p.doran, p.sandoval-beck, r.cooper, r.flores, r.morales, s.carroll, s.nunn, s.padilla, s.ramirez, t.abainza, t.lassey-assiakoley, w.reed
s.nunn confirmed as the correct account (2026-05-19). Shontiel.Nunn (OU=Resident Services) is the old-format account — disable it.
## Domain-Joined Computers (8)
@@ -82,7 +147,7 @@ Anna.Pitzlin, Nela.Durut-Azizi, Jodi.Ramstack, Monica.Ramirez, Haris.Durut, Nuri
|----------|------|
| CS-QB | Hyper-V VM — VoIP server |
### OU=Staff PCs,OU=Workstations (moved 2026-04-13)
### OU=Staff PCs,OU=Workstations
| Computer | User | Role |
|----------|------|------|
| ACCT2-PC | Allison Reibschied | Accounting |
@@ -92,103 +157,77 @@ Anna.Pitzlin, Nela.Durut-Azizi, Jodi.Ramstack, Monica.Ramirez, Haris.Durut, Nuri
| DESKTOP-DLTAGOI | Sharon Edwards | Life Enrichment Assistant |
| DESKTOP-ROK7VNM | Susan Hicks | Life Enrichment Director |
### Missing from AD (listed in overview but NOT domain-joined)
- **SALES4-PC** — Sales workstation (10.0.20.203) — NOT in AD
- **CHEF-PC** — Kitchen workstation (10.0.20.232) — NOT in AD
- **MDIRECTOR-PC** — MemCare Director (192.168.3.20) — NOT in AD
- **DESKTOP-KQSL232** — Unknown (10.0.20.227) — NOT in AD
### OU=Shared PCs,OU=Workstations
Empty — created for future shared/rotation workstations (GPO: CSC - Shared Workstation).
These 4 machines are on the network but not domain-joined. They may be workgroup machines or were never joined to the domain.
### Not Domain-Joined (on network but workgroup/unjoined)
- **SALES4-PC** — Sales workstation (10.0.20.203)
- **CHEF-PC** — Kitchen workstation (10.0.20.232)
- **MDIRECTOR-PC** — MemCare Director (192.168.3.20)
- **DESKTOP-KQSL232** — Unknown (10.0.20.227)
## Organizational Units
Domain join for these machines planned in Phase 3 (OU=Staff PCs,OU=Workstations).
## Organizational Units (current state — 2026-05-19)
OU cleanup is **complete**. All root-level duplicate OUs have been deleted. The structure below reflects live state.
### Current State (pre-cleanup)
```
cascades.local
├── Builtin (system)
├── Computers (default container) ← 5 PCs here: ACCT2-PC, CRYSTAL-PC, CS-QB, DESKTOP-1ISF081, DESKTOP-H6QHRR7
├── Users (default container) ← 20 accounts dumped here (system + stale + needs placement)
├── Computers (default) — CS-QB (VoIP VM)
├── Users (default) — service accounts: Administrator, localadmin, MSOL_12be42ce1269, QBDataServiceUser34, sysadmin
├── Domain Controllers
│ └── CS-SERVER
├── Managment ← MISSPELLED, empty — DELETE
├── Sales ← empty — DELETE
├── MemCare ← empty — DELETE
├── Administrative ← ROOT DUPLICATE of Departments\Administrative — DELETE
├── Care-Assisted Living ← ROOT DUPLICATE — DELETE
├── Care-Memorycare ← ROOT DUPLICATE — DELETE
├── Culinary ← ROOT DUPLICATE — DELETE
├── Housekeeping ← ROOT DUPLICATE — DELETE
├── Life Enrichment ← ROOT DUPLICATE — DELETE
├── Maintenance ← ROOT DUPLICATE — DELETE
├── Marketing ← ROOT DUPLICATE — DELETE
├── Resident Services ← ROOT DUPLICATE — DELETE
── Transportation ← ROOT DUPLICATE — DELETE
── Departments
├── Administrative (6 users)
├── Care-Assisted Living (4 users)
│ └── Nurses (sub-OU, empty)
├── Care-Memorycare (2 users)
── Culinary (4 users)
├── Housekeeping (1 user)
├── Life Enrichment (2 users)
├── Maintenance (2 users)
├── Marketing (4 users)
├── Resident Services (7 users)
└── Transportation (3 users)
├── Departments
│ ├── Administrative — Alma.Montt, Ashley.Jensen, lauren.hasselman, Meredith.Kuhn, Zachary.Nelson
├── Care-Assisted Living — britney.thompson, karen.rossini, Lois.Lane, Veronica.Feller
└── Nurses (empty sub-OU)
├── Caregivers — 38 accounts (shift caregivers/medtechs, first.last format)
├── Care-Memorycare — Christine.Nyanzunda, Shelby.Trozzi
├── Culinary — Alyssa.Brooks, JD.Martin, Ramon.Castaneda
├── Housekeeping — Lupe.Sanchez
├── Life Enrichment — Sharon.Edwards, Susan.Hicks
├── Maintenance — John.Trozzi, Matt.Brooks
├── Marketing — Crystal.Rodriguez, Megan.Hiatt, Tamra.Matthews
├── Resident Services — Cathy.Kingston, Christina.DuPras, Kyla.QuickTiffany, Michelle.Shestko, Ray.Rai, Sebastian.Leon, Sheldon.Gardfrey, Shontiel.Nunn
│ └── Transportation — Christopher.Holick, Julian.Crim, Richard.Adams
── Excluded-From-Sync — Culinary, directoryshare, RECEPTIONIST, saleshare
├── Groups — SG-* groups + AuditUploaders (see Security Groups section)
├── ServiceAccounts — svc-audit-upload
└── Workstations
├── Shared PCs (empty)
── Staff PCs — domain-joined workstations
```
### Target State (after cleanup — Phase 2.1 + 2.2)
```
cascades.local
├── Builtin (system)
├── Computers (default container) ← CS-QB stays here (VM, not staff PC)
├── Users (default container) ← system/service accounts only
├── Domain Controllers
│ └── CS-SERVER
├── Workstations ← NEW
│ ├── Staff PCs ← NEW — CRYSTAL-PC, ACCT2-PC, DESKTOP-H6QHRR7, DESKTOP-1ISF081, DESKTOP-DLTAGOI, DESKTOP-ROK7VNM
│ └── Shared PCs ← NEW — shared/rotation workstations (GPO: CSC - Shared Workstation)
└── Departments
├── Administrative (6 users)
├── Care-Assisted Living (4 users)
│ └── Nurses (sub-OU)
├── Care-Memorycare (2 users)
├── Culinary (4 users)
├── Housekeeping (1 user)
├── Life Enrichment (2 users)
├── Maintenance (2 users)
├── Marketing (4 users)
├── Resident Services (7 users)
└── Transportation (3 users)
```
**Historical note:** Prior to 2026-04-13, 13 root-level OUs existed (10 duplicate department OUs + Managment misspelled + MemCare + Sales, all empty). All deleted as part of Phase 2.1 cleanup.
### Cleanup Scripts
- `migration/scripts/phase2-ou-cleanup.ps1` — Audit + delete 13 root-level OUs, handle CN=Users accounts
- `migration/scripts/phase2-ad-setup.ps1` — Security fixes, create Workstations OU, security groups, move computers
## Security Groups (OU=Groups — live state 2026-05-20)
## Group Policy (as of 2026-03-07 export)
GPOs exist but effectiveness is limited since most PCs aren't domain-joined.
| GPO | Created | Modified | Settings | Notes |
|-----|---------|----------|----------|-------|
| Default Domain Policy | Aug 2024 | Mar 2026 | Password: 7-char min, 42-day max, complexity on, 24 history. **Lockout: 5 attempts / 30 min** (fixed 2026-03-09). Kerberos defaults. | OK |
| Default Domain Controllers Policy | Aug 2024 | Oct 2024 | IIS app pool audit rights, print operator driver loading. Standard. | OK |
| Power Options | Jul 2025 | Jul 2025 | "Cascades Default" power plan: never sleep/hibernate, display off 15 min (plugged in) / 10 min (battery), password on wake. | Reasonable — keep |
| ~~CopyRoomPrinter~~ | Dec 2025 | Dec 2025 | EMPTY | **DELETED 2026-03-09** |
| ~~Nurses-Kiosk~~ | Dec 2025 | Dec 2025 | EMPTY | **DELETED 2026-03-09** |
| ~~MemCareMedTechPrinter~~ | Dec 2025 | Dec 2025 | EMPTY | **DELETED 2026-03-09** |
**GPO Review (2026-03-07):** All 3 Dec 2025 GPOs are completely empty shells — no computer or user settings, not linked to any OU. Safe to delete with zero impact. The Default Domain Policy has account lockout disabled (threshold = 0), allowing unlimited password brute-force attempts — this needs to be fixed in the security baseline GPO.
## RDS Licensing
- **Mode: NotConfigured**
- **License Servers: None**
- RDS roles are installed on CS-SERVER (Connection Broker, Session Host, Web Access) but licensing is NOT configured.
- **Compliance risk:** Windows Server allows a 120-day grace period for RDS without licensing. After that, connections may be refused. Since the server was installed 8/4/2024 (~19 months ago), the grace period has long expired. RDS may be running in non-compliant mode.
## Existing AD Groups (Custom)
| Group | Members | Notes |
|-------|---------|-------|
| SG-Activities-RW | 0 | Activities share — Read/Write (Life Enrichment). Created 2026-05-20. |
| SG-CA-BreakGlass | 0 | Conditional Access break-glass group |
| SG-Caregivers | 38 | All shift caregivers/medtechs — syncing to Entra |
| SG-Chat-RW | 0 | Chat share access — legacy |
| SG-CourtesyPatrol | 0 | Courtesy patrol dept |
| SG-Culinary-RW | 0 | Culinary share access |
| SG-Directory-RW | 0 | Directory share access |
| SG-Drivers | 0 | Transportation drivers |
| SG-External-Signin-Allowed | 0 | CA policy — allowed external sign-in |
| SG-FrontDesk | 0 | Front desk dept |
| SG-IT-RW | 0 | IT share access |
| SG-Management-RW | 0 | Management share — OLD group, superseded by SG-Mgmt-RW. Do not use for new share. |
| SG-Mgmt-RW | 0 | Management share — Read/Write. Replaces SG-Management-RW. Created 2026-05-20. |
| SG-Office-PHI-External | 0 | PHI-authorized external access |
| SG-Office-PHI-Internal | 0 | PHI-authorized internal access |
| SG-Receptionist-RW | 0 | Receptionist share access |
| SG-Sales-RO | 0 | Sales share — Read Only. Created 2026-05-20. |
| SG-Sales-RW | 0 | Sales share — Read/Write |
| SG-Server-RW | 0 | Server share — OLD group, do not use for new Server share |
| AuditUploaders | 0 | GuruRMM audit upload service |
**Legacy groups (CN=Users, not in OU=Groups):**
| Group | Members | Notes |
|-------|---------|-------|
| QuickBooks Access | Meredith.Kuhn, Megan.Hiatt, Ashley.Jensen, lauren.hasselman | Renamed from "Quickboosk acccess" on 2026-03-09 |
@@ -196,110 +235,152 @@ GPOs exist but effectiveness is limited since most PCs aren't domain-joined.
| MemoryCareDepartment | (empty) | Never populated |
| KitchenAdmin | (empty) | Never populated |
## Migration Plan — AD Changes (Phase 2.2 + 2.6 + 3)
## Entra Connect (live state 2026-05-19)
See `migration/phase2-server-prep.md` and `migration/scripts/phase2-ad-setup.ps1`.
Entra Connect is installed and running on CS-SERVER in production mode.
### Security Fixes (immediate)
- Remove disabled Monica.Ramirez from **Domain Admins** (security risk)
- Disable Haris.Durut (still enabled, not employed)
- Fix "Quickboosk acccess" → "QuickBooks Access"
- Add lauren.hasselman to QuickBooks Access (replaced Jeff Bristol)
| Setting | Value |
|---------|-------|
| Installed on | CS-SERVER |
| Staging mode | FALSE (live production sync) |
| Scheduler | Enabled — next run: Delta |
| AD connector | cascades.local |
| Entra connector | NETORGFT4257522.onmicrosoft.com |
| OU sync scope | Full domain (dnList empty — unfiltered) |
| Service account | MSOL_12be42ce1269 (CN=Users) |
### OU Changes
- **DELETE 10 root-level duplicate OUs** (Administrative, Care-Assisted Living, Care-Memorycare, Culinary, Housekeeping, Life Enrichment, Maintenance, Marketing, Resident Services, Transportation) — duplicates of Departments sub-OUs
- **DELETE 3 empty root-level OUs** (Managment, MemCare, Sales) — unused
- Create: `OU=Workstations,DC=cascades,DC=local`
- Create: `OU=Staff PCs,OU=Workstations,DC=cascades,DC=local`
**OU=Excluded-From-Sync** is explicitly excluded from sync. The shared accounts (Culinary, directoryshare, RECEPTIONIST, saleshare) placed there do not appear in Entra ID.
### Security Groups (created with members from Synology permission mapping)
All other OUs — including OU=Caregivers — are within scope and sync to Entra.
| Group | Members |
|-------|---------|
| SG-Management-RW | Meredith.Kuhn, Ashley.Jensen, Megan.Hiatt, Crystal.Rodriguez, Tamra.Matthews, britney.thompson, Veronica.Feller, strozzi, Alyssa.Brooks, lauren.hasselman |
| SG-Sales-RW | Megan.Hiatt, Crystal.Rodriguez, Tamra.Matthews |
| SG-Server-RW | Ashley.Jensen, britney.thompson, Christina.DuPras, Veronica.Feller, Meredith.Kuhn |
| SG-Chat-RW | Ashley.Jensen, britney.thompson, Veronica.Feller |
| SG-Culinary-RW | JD.Martin, Ramon.Castaneda, Alyssa.Brooks |
| SG-IT-RW | howard, sysadmin |
| SG-Receptionist-RW | Cathy.Kingston, Shontiel.Nunn, Ray.Rai, Sebastian.Leon, Michelle.Shestko |
| SG-Directory-RW | Cathy.Kingston, Shontiel.Nunn, Christina.DuPras |
| SG-AllShares-RO | (populated as needed) |
**Historical note:** As of the 2026-04-13 doc, Entra Connect was planned as Phase 2.7 (blocked on AD cleanup). Cleanup is now complete and Entra Connect is deployed.
### Account Removals (client confirmed)
## SMB Shares (live — D:\ on CS-SERVER)
**Already disabled — delete:** Anna.Pitzlin, Nela.Durut-Azizi, Jodi.Ramstack, Monica.Ramirez, jeff.bristol
Verified live via GuruRMM `Get-SmbShare` on 2026-05-20. ABE = Access-Based Enumeration (users see only folders they can access).
**Enabled but not in HR — disable + delete:** Haris.Durut, Nuria.Diaz, Cathy.Reece, Kelly.Wallace, alyssa.brooks, Isabella.Islas, ann.dery
### New shares — Phase 2.5 (created 2026-05-20, ABE on, proper SG- NTFS)
**Keep:** lauren.hasselman (replaced Bristol as Business Office Director)
These are the authoritative Phase 2.5 shares. Empty until each department cuts over from Synology/legacy. Groups will be populated at cutover.
### CN=Users — HR Verified (2026-03-10)
| Share | Path | NTFS Permissions | Drive letter (planned) |
|-------|------|-----------------|----------------------|
| Activities | D:\Shares\Activities | SG-Activities-RW (Modify), Domain Admins (Full) | A: or T: (TBD) |
| Management | D:\Shares\Management | SG-Mgmt-RW (Modify), Domain Admins (Full) | M: |
| Sales | D:\Shares\Sales | SG-Sales-RW (Modify), SG-Sales-RO (ReadAndExecute) | S: |
| Server | D:\Shares\Server | SG-IT-RW (Modify), Domain Users (ReadAndExecute) | V: (IT use) |
HR (Meredith) responded. All accounts resolved:
### Legacy shares — still active, pre-Phase 2.5 (no ABE, no SG- groups)
| Account | Enabled | Last Logon | Action |
|---------|---------|-----------|--------|
| Lupe.Sanchez | Yes | Never | **Keep** — confirmed same person as Guadalupe.Sanchez (M365: lupe.sanchez@). Merge or delete duplicate |
| Receptionist | Yes | 2/22/2026 | Shared account — keep until Phase 5 replacement |
| directoryshare | Yes | 2/26/2026 | Shared/service account — keep until Phase 5 replacement |
Do NOT populate these further. They remain in service until Phase 4 cutover retires Synology + legacy paths.
**Confirmed DELETE by HR:**
- Anna.Pitzlin (disabled) — was forwarded to Meredith, OK to delete now
- Nela.Durut-Azizi (disabled) — was forwarded to Meredith, OK to delete now
- Jodi.Ramstack (disabled)
- Monica.Ramirez (disabled, already removed from Domain Admins)
- Kristiana.Dowse — M365 only, not in AD. Delete M365 account + remove license
| Share | Path | Status |
|-------|------|--------|
| Culinary | D:\Shares\Culinary | Active — kitchen staff use this now |
| directoryshare | D:\Shares\directoryshare | Active — resident directory |
| homes | D:\Homes | Active — folder redirection target (D:\Homes, not D:\Shares\Homes) |
| Receptionist | D:\Shares\Receptionist | Active — Tower front-desk scan drop |
| IT | D:\Shares\IT | **Superseded by Server share above** — leave in place until Phase 4, do not add new content |
| Shares | D:\Shares | Root share — legacy access path |
**Already confirmed for removal (not current employees, never logged in):**
Haris.Durut, Nuria.Diaz, Cathy.Reece, Kelly.Wallace, Isabella.Islas, ann.dery, alyssa.brooks (lowercase duplicate)
### Service / system shares
**System/service accounts staying in CN=Users:**
Administrator, Guest, krbtgt, localadmin, sysadmin, QBDataServiceUser34
| Share | Path | Notes |
|-------|------|-------|
| AuditDrop$ | D:\Shares\AuditDrop | GuruRMM audit drop — hidden, write-only for AuditUploaders |
| MemCare Director Printer | (printer) | MF451CDW |
| MemCare MedTech Printer | (printer) | Brother MFC-L8900CDW |
| RecRoom-Canon | (printer) | 1F-132-RecRoom-Canon |
| ADMIN$, C$, D$, IPC$, print$ | (system) | Standard Windows — do not remove |
| RDVirtualDesktopTemplate | C:\RDVirtualDesktopTemplate | RDS artifact — remove with RDS role in Phase 5 |
### Domain Join (Phase 3)
Join these PCs to cascades.local in OU=Staff PCs,OU=Workstations:
- DESKTOP-KQSL232 (first)
- CHEF-PC
- SALES4-PC
- MDIRECTOR-PC (last)
**Printers shared from CS-SERVER (13 — Phase 2.6 COMPLETE 2026-05-20):**
| Share | Device | ILT (GPO) |
|-------|--------|-----------|
| CopyRoom | Canon imageRunner C478iF (192.168.2.230) | All staff |
| BusinessOffice | Brother MFC-L8900CDW (10.0.20.220) | OU=Administrative |
| Accounting | Canon imageClass MF455DW (192.168.3.227) | OU=Administrative |
| AdminOffice | Brother MFC-9340CDW (192.168.2.145) | OU=Administrative OR OU=Resident Services |
| ExecDirector | Canon imageClass MF743CDW (192.168.2.67) | OU=Administrative |
| SalesMarketing | Brother MFC-L8900CDW (192.168.3.44) | OU=Marketing |
| Kitchen | Canon imageClass MF743CDW (192.168.3.232) | OU=Culinary |
| CulinaryChef | Brother MFC-9330CDW (192.168.3.88) | OU=Culinary |
| FrontDesk | Epson ET-5800 (192.168.2.147) | OU=Resident Services |
| HealthServices | KM C368 (192.168.1.138) | OU=Care-Assisted Living OR OU=Care-Memorycare |
| LifeEnrichment | (via Life Enrichment Printers GPO) | OU=Life Enrichment |
| MCDirector | Canon imageClass MF751CDW (192.168.3.52) | OU=Care-Memorycare |
| MCMedTech | Brother (192.168.2.53) | OU=Caregivers OR OU=Care-Memorycare |
### GPOs to Create (Phase 2.6)
1. **CSC - Drive Mappings** — S:, M:, T:, K:, I:, R:, P: with item-level targeting
2. **CSC - Printer Deployment** — Deploy printers by OU/group targeting (Life Enrichment first: 1F-132-RecRoom-Canon + CopyRoom)
3. **CSC - Security Baseline** — 12-char passwords, complexity, lockout 5/30, screen lock 15 min
4. **CSC - Windows Update** — Auto download, Sundays 3 AM, no auto-restart
5. **CSC - Folder Redirection** — Desktop, Documents, Downloads → `\\CS-SERVER\homes\%username%\`
6. **CSC - Shared Workstation** — Linked to Shared PCs OU; ILT by computer name for reception drive (R:), front desk printer, Outlook online mode, shared mailbox auto-mount. Blocked on: M365 tenant details, onsite PC identification.
## Group Policy (as of 2026-05-20)
### Entra Connect (Phase 2.7 — NEW)
- Install Entra Connect on CS-SERVER for AD → M365 sync + SSO
- **BLOCKED ON:** AD cleanup (renames, deletions, duplicate resolution) must complete first
- See `cloud/m365.md` → "Entra Connect — SSO Setup Plan" for full prerequisites and steps
- Enables: single sign-on, one password, auto Office/Edge activation per user, roaming experience without roaming profiles
GPOs exist but effectiveness is limited since most PCs are not domain-joined. All CSC - GPOs are **UNLINKED** until Phase 3 domain join cutover.
### Shared Account Replacement (Phase 5)
Replace Culinary, Receptionist, saleshare, directoryshare with security group access.
| GPO | Link | Settings | Notes |
|-----|------|----------|-------|
| Default Domain Policy | Domain root | Password: 7-char min, 42-day max, complexity on, 24 history. Lockout: 5 attempts / 30 min. Kerberos defaults. | OK |
| Default Domain Controllers Policy | OU=Domain Controllers | IIS app pool audit rights, print operator driver loading. | OK |
| Power Options | — | "Cascades Default" power plan: never sleep/hibernate, display off 15 min (plugged in) / 10 min (battery), password on wake. | Keep |
| CSC - Always Wait For Network | — | AlwaysWaitForNetwork + synchronous logon | Pre-existing |
| CSC - Folder Redirection (LE) | OU=Life Enrichment | Documents + Downloads → `\\CS-SERVER\homes\%USERNAME%\`. GrantExclusive=false, MoveContents=true. | LIVE — Sharon Edwards + Susan Hicks |
| CSC - Folder Redirection | — | Same as LE GPO but for all staff OUs. UNLINKED. | Blocked on Phase 3 |
| CSC - Life Enrichment Printers | OU=Life Enrichment | Printer preferences for LE staff | LIVE |
| CSC - Security Baseline | UNLINKED | Screen lock 15 min / password on resume (HKCU). GptTmpl.inf: password min 12, history 24, max-age 90, lockout 5/30. | Created 2026-05-20. Link at domain root at Phase 3. |
| CSC - Windows Update | UNLINKED | AUOptions=4 (auto DL+install), Sunday 3 AM, NoAutoRebootWithLoggedOnUsers=1, featured software off. | Created 2026-05-20. Link at domain root at Phase 3. |
| CSC - Printer Deployment | UNLINKED | 13 printers with OU-based ILT in Printers.xml. CopyRoom = all staff. Others scoped by OU. | Created 2026-05-20. Link to OU=Workstations at Phase 3. |
| CSC - Drive Mappings | UNLINKED | M: Management (SG-Mgmt-RW), S: Sales (SG-Sales-RW), T: Activities (SG-Activities-RW), K: Culinary (OU), R: Receptionist (OU). | Created 2026-05-20. Link to OU=Departments at Phase 3. |
| ~~CopyRoomPrinter~~ | — | EMPTY | **DELETED 2026-03-09** |
| ~~Nurses-Kiosk~~ | — | EMPTY | **DELETED 2026-03-09** |
| ~~MemCareMedTechPrinter~~ | — | EMPTY | **DELETED 2026-03-09** |
## Domain Admins (from 2026-03-07 export)
**GPOs Remaining (Phase 3+):**
- **CSC - Folder Redirection** — Link to OU=Departments at Phase 3. Blocked on domain joins. CRITICAL: check OneDrive KFM before applying; use GPMC close-and-reopen workaround between folder adds (see 2026-04-17 session log).
- **CSC - Shared Workstation** — Future: linked to Shared PCs OU; ILT for reception drive (R:), front desk printer, Outlook online mode, shared mailbox auto-mount.
| Account | Status | Action Needed |
|---------|--------|---------------|
**Phase 3 GPO linking order** (after first successful domain join per phase3-domain-join.md step 5c):
1. Link CSC - Security Baseline → domain root
2. Link CSC - Windows Update → domain root
3. Link CSC - Printer Deployment → OU=Workstations
4. Link CSC - Drive Mappings → OU=Departments
## RDS Licensing
- **Mode: NotConfigured**
- **License Servers: None**
- RDS roles installed on CS-SERVER (Connection Broker, Session Host, Web Access) but licensing is NOT configured.
- Compliance risk: grace period is 120 days. Server installed 2024-08-04 (~21 months ago as of 2026-05-19). Grace period expired. RDS is running non-compliant.
- Decision deferred to Phase 5.
## Domain Admins
| Account | Status | Notes |
|---------|--------|-------|
| Administrator | Enabled | OK (built-in) |
| Meredith.Kuhn | Enabled | **REMOVE** — administrative staff, not IT |
| John.Trozzi | Enabled | **REMOVE** — maintenance, not IT |
| ~~Monica.Ramirez~~ | **Disabled** | **REMOVED 2026-03-09** |
| Meredith.Kuhn | Enabled | Should be removed — administrative staff, not IT |
| John.Trozzi | Enabled | Should be removed — maintenance, not IT |
| ~~Monica.Ramirez~~ | Removed | Removed 2026-03-09 (account was disabled) |
| sysadmin | Enabled | OK (IT account) |
## Login Activity (audit 2026-03-20)
## Pending Issues
Only 12 of 49 enabled accounts have ever logged in. Most staff have never used their AD accounts because their PCs aren't domain-joined.
| Issue | Account | Action Needed |
|-------|---------|---------------|
| ~~Still enabled — departed~~ | ~~britney.thompson~~ | **DONE 2026-05-20** — AD disabled. M365: sign-in blocked, license removed, litigation hold applied. |
| ~~Still enabled — flagged for disable~~ | ~~Richard.Adams, Julian.Crim, Christopher.Holick~~ | **DONE 2026-05-20** — all disabled. |
| ~~Old-format account — superseded~~ | ~~Shontiel.Nunn~~ | **DONE 2026-05-20** — disabled. s.nunn (Caregivers) is the active account. |
| Cloud-only M365 account — RESOLVED | Alma.Montt | Intentional and correct — no AD sync conflict. |
| krbtgt password age | krbtgt | 569+ days old as of 2026-03-20. Needs rotation. Deferred. |
| Meredith.Kuhn + John.Trozzi in Domain Admins | Both | Non-IT staff — remove from Domain Admins. Deferred. |
| ~~britney.thompson M365 offboarding~~ | ~~britney.thompson~~ | **DONE 2026-05-20** — sign-in blocked, license removed, litigation hold applied via sysadmin@. |
## Login Activity (audit 2026-03-20 — historical/stale)
Data below is from the 2026-03-20 audit. Only 12 of 49 enabled accounts had ever logged in at that time. Most staff had never used AD accounts because their PCs were not domain-joined.
| Account | Last Logon | Notes |
|---------|-----------|-------|
| sysadmin | 2026-03-16 | |
| QBDataServiceUser34 | 2026-03-14 | QuickBooks service |
| Allison.Reibschied | 2026-03-13 | **NEW** Administrative |
| Allison.Reibschied | 2026-03-13 | Administrative |
| lauren.hasselman | 2026-03-12 | Business Office Director |
| Administrator | 2026-03-11 | |
| Receptionist | 2026-03-11 | Shared account |
@@ -307,23 +388,23 @@ Only 12 of 49 enabled accounts have ever logged in. Most staff have never used t
| localadmin | 2026-03-09 | |
| Crystal.Rodriguez | 2026-03-09 | CRYSTAL-PC |
| Culinary | 2026-02-20 | Shared account |
| saleshare | 2025-12-08 | Shared account |
| Christina.DuPras | 2026-01-06 | |
| Monica.Ramirez | 2024-11-04 | **Disabled** |
| saleshare | 2025-12-08 | Shared account |
| Monica.Ramirez | 2024-11-04 | Disabled — now deleted |
**37 enabled accounts have NEVER logged in** — most have never set a password either.
37 accounts had never logged in as of 2026-03-20. Login activity will improve as more PCs are domain-joined (Phase 3).
## Issues Found
1. **Only 6 computers domain-joined** — At least 4 known staff PCs are NOT in AD. (Migration Phase 3 will fix)
2. **3 GPOs from Dec 2025 undocumented** — CopyRoomPrinter, Nurses-Kiosk, MemCareMedTechPrinter. Need to review settings and linkage. Previous MSP or sysadmin created these.
3. **RDS licensing not configured** — Compliance risk, grace period expired ~17 months ago. (Phase 5 decision)
4. **12 accounts to remove** — 5 disabled + 7 former employees still enabled. (Phase 2.1/2.2)
5. **4 shared/generic accounts** (Culinary, Receptionist, saleshare, directoryshare) — To be replaced. (Phase 5)
6. **Monica.Ramirez (disabled) still in Domain Admins** — Security risk, fix immediately. (Phase 2.2)
7. **Meredith.Kuhn and John.Trozzi in Domain Admins** — Non-IT staff should not be DAs. (Phase 2.2)
8. **"Managment" OU misspelled** — To be deleted (empty). (Phase 2.1)
9. **"Quickboosk acccess" group typo** — To be fixed. (Phase 2.2)
10. **13 junk root-level OUs** — 10 duplicate department OUs + Managment + MemCare + Sales, all empty. Delete in Phase 2.1.
11. **20 accounts in CN=Users** — Mix of system, stale, and misplaced. Clean up in Phase 2.1.
12. **5 computers in CN=Computers** — Move 4 staff PCs to Workstations OU. CS-QB stays. (Phase 2.2)
13. **Lupe.Sanchez** — In CN=Users, possible duplicate of Guadalupe.Sanchez (Housekeeping). Flag for onsite review.
## Migration Plan Reference
See `migration/phase2-server-prep.md` for full phase details. Scripts referenced throughout this doc:
- `migration/scripts/phase2-ou-cleanup.ps1` — OU audit + delete (COMPLETE)
- `migration/scripts/phase2-ad-setup.ps1` — Security fixes, Workstations OU, security groups, move computers (COMPLETE)
- `migration/scripts/phase2-ad-groups-new.ps1` — New SG- groups (SG-Mgmt-RW, SG-Sales-RO, SG-Activities-RW) — COMPLETE 2026-05-20
- `migration/scripts/phase2-new-shares.ps1` — New SMB shares (Management, Sales, Activities, Server) — COMPLETE 2026-05-20
- `migration/scripts/phase2-print-server.ps1` — 13 printers installed + shared on CS-SERVER — COMPLETE 2026-05-20
- `.claude/temp/gpo-script1.ps1` — AD account cleanup (5 accounts disabled) + CSC - Security Baseline + CSC - Windows Update — COMPLETE 2026-05-20
- `.claude/temp/gpo-script2.ps1` — CSC - Printer Deployment (13 printers, OU ILT) + CSC - Drive Mappings (M: S: T: K: R:) — COMPLETE 2026-05-20
**Phase 3 domain joins** (pending): DESKTOP-KQSL232, CHEF-PC, SALES4-PC, MDIRECTOR-PC — all to OU=Staff PCs,OU=Workstations. MDIRECTOR-PC needs Windows 10 Pro upgrade first.
**Phase 5** (deferred): Replace shared accounts (Culinary, Receptionist, saleshare, directoryshare) with group-based access. RDS licensing decision.

View File

@@ -0,0 +1,74 @@
# Email Delivery Investigation: Canva Emails Not Arriving — Alma Montt
**Date:** 2026-05-20
**Tenant:** cascadestucson.com (207fa277-e9d8-4eb7-ada1-1064d2221498)
**Reported by:** Alma Montt (alma.montt@cascadestucson.com)
**Issue:** Not receiving emails from Canva (team invite + future notifications)
---
## Root Cause
**New mailbox provisioning race condition.** Alma's mailbox is brand-new (first email received 2026-05-19 21:11 UTC). The Canva team invite was sent before or shortly after provisioning, during a window when the mailbox was not yet fully available to external senders. The email was rejected/dropped at the SMTP layer — it never entered EOP processing.
Confirmed: No quarantined messages found for Alma.Montt@cascadestucson.com.
**Contributing factor (hardened anti-spam config):**
The tenant has the Standard Preset Security Policy active since 2026-04-17 with:
- `BulkThreshold: 6` (aggressive — BCL ≥ 6 treated as bulk spam)
- `HighConfidenceSpamAction: Quarantine` (high-confidence spam goes to org quarantine, not junk)
Canva invite emails route via Amazon SES (`mail.canva.com`) and would be at risk of hitting BCL 6 threshold under this policy for future invites.
---
## Findings
| Check | Result |
|---|---|
| Mailbox exists | Yes — `Alma.Montt@cascadestucson.com`, UserMailbox |
| Inbox rules | None |
| Junk email | Empty |
| Quarantine (org-level) | 0 messages for Alma |
| Blocked senders | None |
| Other users receiving Canva | Yes — crystal.rodriguez receives `marketing@engage.canva.com` |
| MX record | Correct (cascadestucson-com.mail.protection.outlook.com) |
| Canva SPF | Valid (`_spf1-9.canva.com` include chain) |
| Active anti-spam preset | Standard Preset Security Policy (since 2026-04-17) |
---
## Remediation Applied
1. **`Set-HostedContentFilterPolicy` — Default policy**
Added `AllowedSenderDomains`: `canva.com`, `mail.canva.com`, `engage.canva.com`
[CONFIRMED] Verified via `Get-HostedContentFilterPolicy`
2. **`Set-HostedContentFilterPolicy` — Standard Preset Security Policy**
Added same three domains to `AllowedSenderDomains`
[CONFIRMED] Verified — note: Microsoft warned "All recommended properties will be controlled by Microsoft" (preset policy managed by MS; override may be reset if Microsoft changes the preset)
3. **`Set-MailboxJunkEmailConfiguration` — Alma's mailbox**
Added `TrustedSendersAndDomains`: `canva.com`, `mail.canva.com`, `engage.canva.com`
[CONFIRMED] Verified via `Get-MailboxJunkEmailConfiguration`
4. **Historical search submitted**
Job ID: `21325332-a2a1-49c0-abb8-d0c6b88c7b0f`
Scope: All mail to `Alma.Montt@cascadestucson.com` from Canva senders, May 1820
Results will be emailed to `admin@cascadestucson.com`
---
## Action Required
**Crystal Rodriguez needs to resend the Canva team invite to alma.montt@cascadestucson.com.**
The original invite was lost to a new-mailbox provisioning race. The direct join link Crystal already provided in email (RE: canva info, 2026-05-19) still works and Alma can use it immediately.
For future invites and Canva email notifications: the org allow list changes will ensure delivery.
---
## Vault Paths Accessed
- `clients/cascades-tucson/m365-admin.sops.yaml` — tenant ID, admin credentials
- `msp-tools/computerguru-security-investigator.sops.yaml` — Graph read token
- `msp-tools/computerguru-exchange-operator.sops.yaml` — EXO write token

View File

@@ -0,0 +1,94 @@
# Cascades of Tucson — Phase 2.5 AD Groups and Shares
**Date:** 2026-05-20
**Syncro tickets:** none opened this session
## User
- **User:** Howard Enos (howard)
- **Machine:** HOWARD-HOME
- **Role:** tech
---
## Session Summary
Resumed from a crash mid-session on 2026-05-19. Context was recovered from the prior session log and `active-directory.md`. A live verification against CS-SERVER via GuruRMM confirmed the Phase 2.5 scripts had not run before the crash.
Ran both Phase 2.5 scripts on CS-SERVER via GuruRMM remote PowerShell:
**phase2-ad-groups-new.ps1** — Created three new security groups in `OU=Groups,DC=cascades,DC=local`:
- `SG-Mgmt-RW` — Management share Read/Write (replaces old SG-Management-RW)
- `SG-Sales-RO` — Sales share Read Only
- `SG-Activities-RW` — Activities share Read/Write
Tamra.Matthews was not in SG-Sales-RW so no removal was needed (SKIP result — expected).
**phase2-new-shares.ps1** — Created four new SMB shares on `D:\Shares`, all with ABE enabled and broken inheritance:
- `Management` — NTFS: SG-Mgmt-RW (Modify), Domain Admins (Full)
- `Sales` — NTFS: SG-Sales-RW (Modify), SG-Sales-RO (ReadAndExecute)
- `Activities` — NTFS: SG-Activities-RW (Modify), Domain Admins (Full)
- `Server` — NTFS: SG-IT-RW (Modify), Domain Users (ReadAndExecute)
All folders are empty — data sync and group membership population are separate steps per department when each is ready to cut over.
Discussed folder redirection for the new shares. Decision: create a single `CSC - Folder Redirection` GPO linked at `OU=Departments` rather than per-OU GPOs. Blocked on Phase 3 domain joins — most affected machines are not domain-joined yet. Life Enrichment is already covered by the existing `CSC - Folder Redirection (LE)` GPO. Will return to this after Phase 3.
---
## Key Decisions
- **Single domain-wide folder redirection GPO** — Link at `OU=Departments` rather than duplicating the LE GPO per department. As machines get domain-joined in Phase 3 they pick it up automatically. Blocked until Phase 3 domain joins are further along.
- **phase2-new-shares.ps1 sent as base64 EncodedCommand** — Direct JSON serialization of the script caused a `Missing closing '}'` parser error. Workaround: encode as UTF-16LE base64 and launch via `powershell.exe -EncodedCommand`. This pattern should be used for any multi-line PS scripts sent via the GuruRMM command API.
---
## Problems Encountered
- **phase2-new-shares.ps1 parser error via GuruRMM API** — Sending the script as a raw JSON string caused PowerShell to fail with `Missing closing '}'`. Root cause: JSON serialization mangled backtick line continuations. Fixed by encoding the script as UTF-16LE base64 and using `-EncodedCommand`.
---
## Configuration Changes
| File | Change |
|------|--------|
| `clients/cascades-tucson/docs/migration/scripts/phase2-ad-groups-new.ps1` | New — committed |
| `clients/cascades-tucson/docs/migration/scripts/phase2-new-shares.ps1` | New — committed |
| `clients/cascades-tucson/docs/servers/active-directory.md` | Updated: SG- groups table, SMB shares table, GPO section, script status |
---
## Infrastructure Changes on CS-SERVER
| Object | Type | Action |
|--------|------|--------|
| SG-Mgmt-RW | AD Security Group | Created in OU=Groups |
| SG-Sales-RO | AD Security Group | Created in OU=Groups |
| SG-Activities-RW | AD Security Group | Created in OU=Groups |
| D:\Shares\Management | Folder + SMB share | Created, ABE enabled, NTFS set |
| D:\Shares\Sales | Folder + SMB share | Created, ABE enabled, NTFS set |
| D:\Shares\Activities | Folder + SMB share | Created, ABE enabled, NTFS set |
| D:\Shares\Server | Folder + SMB share | Created, ABE enabled, NTFS set |
---
## Pending / Incomplete Tasks
| Item | Status | Notes |
|------|--------|-------|
| Populate new SG- groups with members | Pending | Per-dept when each cuts over to new shares |
| CSC - Folder Redirection GPO (all depts) | Pending | Blocked on Phase 3 domain joins. Check OneDrive KFM on each machine before applying. Use GPMC close-and-reopen workaround (see 2026-04-17 session log). |
| `n.castro` — block M365 sign-in | Pending (from 2026-05-18) | `Update-MgUser -UserId n.castro@cascadestucson.com -AccountEnabled:$false` |
| `Shontiel.Nunn` old account — disable | Pending (from 2026-05-18) | s.nunn is the correct account |
| `britney.thompson` — disable + harvest M365 license | Pending | Departed 2026-04-22 |
| `Alma.Montt` — AD + cloud-only M365 conflict | Pending | Delete cloud-only account, let Entra Connect sync the AD account |
| `k.flores`, `g.williford`, `m.kariuki` — employment status | On hold | Unconfirmed |
| Phase 3 domain joins | Pending | DESKTOP-KQSL232, CHEF-PC, SALES4-PC, MDIRECTOR-PC → OU=Staff PCs |
---
## Reference
- Prior session log: `clients/cascades-tucson/session-logs/2026-05-19-howard-alma-montt-account-completion.md`
- Folder redirection procedure: `clients/cascades-tucson/session-logs/2026-04-17-howard-cascades-onboarding-and-folder-redirection.md`
- AD structure: `clients/cascades-tucson/docs/servers/active-directory.md`

View File

@@ -0,0 +1,219 @@
# Cascades of Tucson — Phase 2.6 Session Log
**Date:** 2026-05-20
**Duration:** Multi-session (continued from context-limit session)
## User
- **User:** Howard Enos (howard)
- **Machine:** HOWARD-HOME
- **Role:** tech
## Summary
Completed Phase 2.6: print server build-out, GPO creation, and AD account cleanup. All 13 printers are now installed and shared on CS-SERVER. Four CSC GPOs are created and staged (unlinked until Phase 3 domain join cutover).
---
## Work Completed
### 1. Front Desk Epson ET-5800 — Printer Installation
**Problem:** EPWizard.exe fails on Windows Server 2019 — `wlanapi.dll` stub is present but the WLAN stack is absent.
**Fix:**
1. Ran EPWizard.exe on CS-SERVER, let it extract drivers to `AppData\Local\Temp\ET-5800\` before dismissing the error
2. Copied extracted INFs to `C:\Users\sysadmin\Documents\ComputerGuru Connect v2\Files\epsonetdrivers\`
3. `pnputil /add-driver <INF> /install` staged the driver in Windows Driver Store
4. `Add-PrinterDriver -Name "EPSON ET-5800 Series"` registered it with the Print Spooler
5. `Add-Printer` / `Add-PrinterPort` created the printer at 192.168.2.147, shared as `FrontDesk`
**Driver name (from INF):** `EPSON ET-5800 Series`
**INF location:** E_WF1XCE.INF (UTF-16 LE with BOM FF FE — `Select-String` fails on it; must use `[System.IO.File]::ReadAllBytes()`)
### 2. Memory Care Director + MedTech Printers
Added to CS-SERVER (via GuruRMM remote PowerShell):
| Printer | Share | IP | Driver |
|---------|-------|----|--------|
| Memory Care Director - Canon MF751CDW | MCDirector | 192.168.3.52 | Canon Generic Plus PCL6 |
| Memory Care MedTech - Brother | MCMedTech | 192.168.2.53 | Brother Generic Jpeg Type2 Class Driver |
Both reachable and shared. Total shared printers on CS-SERVER: **13**.
### 3. Script: phase2-print-server.ps1
Updated `clients/cascades-tucson/docs/migration/scripts/phase2-print-server.ps1`:
- Status header updated to **COMPLETE 2026-05-20** (all 13 printers)
- Added FrontDesk, MCDirector, MCMedTech to `$printers` array
- Documented Epson ET-5800 workaround in header comments
- KM driver archived to `D:\Shares\Server\Drivers\KM_Universal_PCL6\`
### 4. AD Account Cleanup (5 accounts)
Executed via GuruRMM remote PowerShell on CS-SERVER. All `Disable-ADAccount` calls succeeded.
| Account | OU | Reason |
|---------|----|--------|
| britney.thompson | Care-Assisted Living | Departed 2026-04-22 |
| Richard.Adams | Transportation | Drivers no longer get IT access |
| Julian.Crim | Transportation | Drivers no longer get IT access |
| Christopher.Holick | Transportation | Drivers no longer get IT access |
| Shontiel.Nunn | Resident Services | Old-format account — s.nunn (Caregivers) is correct |
**Note:** britney.thompson's M365 license is still active and not yet harvested. Action needed before next billing cycle.
### 5. CSC - Security Baseline GPO
Created via `New-GPO` + `Set-GPRegistryValue` + direct SYSVOL writes.
**Screen saver (HKCU via GPP):**
- ScreenSaveTimeOut = 900 (15 min)
- ScreenSaveActive = 1
- ScreenSaverIsSecure = 1
- SCRNSAVE.EXE = scrnsave.scr
**GptTmpl.inf (Machine security — written as Unicode UTF-16):**
```
MinimumPasswordLength = 12
PasswordComplexity = 1
PasswordHistorySize = 24
MaximumPasswordAge = 90
MinimumPasswordAge = 1
LockoutBadCount = 5
ResetLockoutCount = 30
LockoutDuration = 30
```
**GPT.INI:** machine version bumped, security extension GUID `{827D319E-6EAC-11D2-A4EA-00C04F79F83A}{803E14A0-B4FB-11D0-A0D0-00A0C90F574B}` added.
**Status: UNLINKED.** Link to domain root at Phase 3 cutover.
### 6. CSC - Windows Update GPO
Created via `New-GPO` + `Set-GPRegistryValue` (HKLM AU key).
| Setting | Value |
|---------|-------|
| NoAutoUpdate | 0 |
| AUOptions | 4 (auto download + install) |
| ScheduledInstallDay | 1 (Sunday) |
| ScheduledInstallTime | 3 (3:00 AM) |
| NoAutoRebootWithLoggedOnUsers | 1 |
| EnableFeaturedSoftware | 0 |
**Status: UNLINKED.** Link to domain root at Phase 3 cutover.
### 7. CSC - Printer Deployment GPO
Created `Printers.xml` in SYSVOL at `{GPO-GUID}\User\Preferences\Printers\`.
13 printers with OU-based item-level targeting (`FilterOrgUnit`):
| Share | ILT |
|-------|-----|
| CopyRoom | No filter — all staff |
| BusinessOffice | OU=Administrative |
| Accounting | OU=Administrative |
| AdminOffice | OU=Administrative OR OU=Resident Services |
| ExecDirector | OU=Administrative |
| SalesMarketing | OU=Marketing |
| Kitchen | OU=Culinary |
| CulinaryChef | OU=Culinary |
| FrontDesk | OU=Resident Services |
| HealthServices | OU=Care-Assisted Living OR OU=Care-Memorycare |
| LifeEnrichment | OU=Life Enrichment |
| MCDirector | OU=Care-Memorycare |
| MCMedTech | OU=Caregivers OR OU=Care-Memorycare |
**CSE GUID:** `{BC75B1ED-5833-4858-9BB8-CBF0B166DF9D}{D02B1F72-3407-48AE-BA88-E8213C6761F1}`
**Status: UNLINKED.** Link to OU=Workstations at Phase 3 cutover.
### 8. CSC - Drive Mappings GPO
Created `Drives.xml` in SYSVOL at `{GPO-GUID}\User\Preferences\Drives\`.
| Drive | Share | ILT |
|-------|-------|-----|
| M: | \\CS-SERVER\Management | FilterGroup: CASCADES\SG-Mgmt-RW |
| S: | \\CS-SERVER\Sales | FilterGroup: CASCADES\SG-Sales-RW |
| T: | \\CS-SERVER\Activities | FilterGroup: CASCADES\SG-Activities-RW |
| K: | \\CS-SERVER\Culinary | FilterOrgUnit: OU=Culinary,OU=Departments |
| R: | \\CS-SERVER\Receptionist | FilterOrgUnit: OU=Resident Services,OU=Departments |
**CSE GUID:** `{5794DAFD-BE60-433f-88A2-1A31939AC01F}{D02B1F72-3407-48AE-BA88-E8213C6761F1}`
**Status: UNLINKED.** Link to OU=Departments at Phase 3 cutover.
---
## Final CSC GPO Inventory (8 GPOs, all AllSettingsEnabled)
```
CSC - Always Wait For Network (pre-existing)
CSC - Drive Mappings UNLINKED — link to OU=Departments at Phase 3
CSC - Folder Redirection UNLINKED — blocked on Phase 3 domain joins
CSC - Folder Redirection (LE) LIVE — linked to OU=Life Enrichment
CSC - Life Enrichment Printers LIVE — linked to OU=Life Enrichment
CSC - Printer Deployment UNLINKED — link to OU=Workstations at Phase 3
CSC - Security Baseline UNLINKED — link to domain root at Phase 3
CSC - Windows Update UNLINKED — link to domain root at Phase 3
```
---
## Docs Updated
- `clients/cascades-tucson/docs/migration/scripts/phase2-print-server.ps1` — Complete status + all 13 printers
- `clients/cascades-tucson/docs/servers/active-directory.md` — Accounts disabled, GPO table updated, pending issues updated, printer shares table updated
---
## Phase 3 Prerequisites (next major work)
Domain join order (per `migration/phase3-domain-join.md`):
1. DESKTOP-KQSL232 (10.0.20.227)
2. CHEF-PC (10.0.20.232)
3. SALES4-PC (10.0.20.203)
4. MDIRECTOR-PC (192.168.3.20) — **needs Windows 10 Pro upgrade first** (currently Home)
After first successful join — link GPOs per phase3-domain-join.md step 5c.
---
## Open Items
| Item | Priority | Notes |
|------|----------|-------|
| ~~britney.thompson M365 offboarding~~ | ~~Done~~ | Sign-in blocked, license removed, litigation hold applied (sysadmin@ via admin center 2026-05-20) |
---
## Update: 14:53 PT — britney.thompson M365 offboarding + next session scope
### britney.thompson M365 Offboarding (Complete)
Attempted Graph API via device code flow. The well-known PowerShell client ID (`1950a258-227b-4e31-a9cf-717495945fc2`) is blocked in the tenant (AADSTS65002 — preauthorization required). No Graph-capable modules installed on HOWARD-HOME (Microsoft.Graph, MSOnline, AzureAD all absent). No Azure CLI.
Howard logged into admin.microsoft.com as `sysadmin@cascadestucson.com` and completed offboarding manually:
- Sign-in blocked in M365
- License removed (returned to pool)
- Litigation hold applied on mailbox
AD account was disabled earlier in this session (see main log above). Offboarding is fully complete.
### Next Session — Accounting Office Folder Redirection
Planned work for next session:
- Machines in the accounting office (Room 103 area): ACCT2-PC (already domain-joined), plus any others in that area
- Folder redirection for accounting staff: Documents, Downloads, Desktop → `\\CS-SERVER\homes\%USERNAME%\`
- **Key difference from LE GPO:** GrantExclusive=true — removes inherited permissions, grants the user exclusive access (plus Domain Admins). No other domain users can browse sibling folders.
- Linked to OU=Administrative (or a sub-scope if only accounting staff targeted)
- Sales department folder redirection possibly follows same session if accounting goes cleanly
- Pre-check required: OneDrive KFM status on each accounting PC before applying GPO (see 2026-04-17 session log for procedure)
| Phase 3 domain joins | High | Block on MDIRECTOR-PC needing Win10 Pro upgrade |
| krbtgt password rotation | Medium | 569+ days old — deferred |
| Remove Meredith.Kuhn + John.Trozzi from Domain Admins | Low | Deferred |
| SG-Mgmt-RW + SG-Sales-RW membership | Medium | Populate before Phase 3 GPO linking |
| CSC - Folder Redirection (full) | Medium | Blocked on Phase 3 — check OneDrive KFM on each PC first |

View File

@@ -0,0 +1,152 @@
# Session Log — Cascades Tucson: EOP Access Test & Alma Montt Mailbox Investigation
**Date:** 2026-05-20
**Channel:** #alma.montt-at-cascades-reports-not-receiving-email
## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-BEAST-ROG (Discord bot session)
- **Role:** admin
---
## Session Summary
The session began with testing the EOP access remediation skill for Cascades Tucson at the request of Mike Swanson via Discord. The vault was confirmed accessible at `C:/Users/guru/vault`, and credentials were retrieved for both the Cascades Tucson M365 tenant and the ComputerGuru Security Investigator MSP app. An investigator-exo token was successfully acquired for the Cascades tenant. Graph API access was verified as working, and the EXO `adminapi/beta/InvokeCommand` endpoint was tested, returning 401 Unauthorized.
The root cause was identified: the Security Investigator app had `full_access_as_app` (EWS, role ID `dc890d15`) granted on Exchange Online, but the InvokeCommand REST endpoint requires `Exchange.ManageAsApp` (role ID `dc50a0fb`). Mike granted admin consent via URL, but consent only covered the existing permission set since `Exchange.ManageAsApp` had not yet been added to the app registration. The app registration was updated via PATCH using a tenant-admin Graph token in the MSP tenant, and the role was directly granted in the Cascades tenant via POST. After clearing the token cache, the new token contained both roles and InvokeCommand returned 200 OK.
The investigation then pivoted to Alma Montt's mailbox, as she had reported not receiving email. The mailbox was confirmed active (she had logged in and interacted today at ~17:00 UTC). No forwarding, suspicious inbox rules, blocked senders, or transport rule blocks were found. MX records correctly point to EOP. Message trace returned zero results (likely a permissions gap with the reporting role rather than actual zero mail flow — her inbox had 29 items). Quarantine check returned Invalid Operation due to the same permissions gap. The most notable finding was `EmailAddressPolicyEnabled: False` and only one SMTP address on the mailbox — no onmicrosoft.com alias — suggesting manual provisioning that may have missed standard alias setup.
---
## Key Decisions
- **Added Exchange.ManageAsApp via Graph API rather than waiting for re-consent**: Mike had consented, but the permission wasn't in the app registration yet, so consent had no effect. PATCH to the app registration in the MSP tenant + POST grant in Cascades was cleaner and immediate.
- **Used tenant-admin tier for both the PATCH and the role grant**: Security Investigator app (investigator tier) doesn't have `AppRoleAssignment.ReadWrite.All`, so tenant-admin was required for the Cascades grant. MSP tenant-admin was required for the app registration PATCH.
- **Did not attempt to fix Alma's mailbox without a confirmed root cause**: Message trace and quarantine access failed due to permissions, leaving the specific missing email unidentified. Reported findings and left detailed EAC follow-up steps rather than guessing.
- **This fix needs to be applied to all other client tenants**: The app registration change is done (MSP tenant), but the `Exchange.ManageAsApp` app role grant must be done per-tenant. Other clients still only have `full_access_as_app`.
---
## Problems Encountered
- **EXO InvokeCommand returned 401**: App had `full_access_as_app` (EWS) but not `Exchange.ManageAsApp` (REST cmdlet API). Resolved by patching app registration and granting role directly via Graph.
- **Admin consent did not include Exchange.ManageAsApp**: Consent was granted before the permission was added to the app registration, so it had no effect. Resolved by programmatic grant using tenant-admin.
- **Get-MessageTraceV2 returned 0 results for all queries (including no-filter tenant-wide)**: Likely the SP lacks the `Compliance Management` or `View-Only Recipients` role needed for trace data access. Not resolved this session — flagged for follow-up.
- **Get-QuarantineMessage returned Invalid Operation**: Same permissions gap. Not resolved this session.
---
## Infrastructure & Credentials
### Cascades Tucson Tenant
- **Tenant ID:** `207fa277-e9d8-4eb7-ada1-1064d2221498`
- **Domain:** `cascadestucson.com`
- **MX:** `cascadestucson-com.mail.protection.outlook.com` (EOP, correct)
- **Vault:** `clients/cascades-tucson/m365-admin.sops.yaml`
- **Admin UPN:** `admin@cascadestucson.com`
### MSP App — Security Investigator (updated this session)
- **App ID:** `bfbc12a4-f0dd-4e12-b06d-997e7271e10c`
- **App Object ID (MSP tenant):** `2ca484d4-5b38-4fa5-b8f7-08a70304a54d`
- **SP Object ID in Cascades:** `c64ee5c1-a607-46cb-81b8-42de3de98d48`
- **Vault:** `msp-tools/computerguru-security-investigator.sops.yaml`
- **Auth method used:** client_secret (cert also available)
### Exchange Online SP in Cascades
- **SP Object ID:** `072df88c-1f1f-4910-a9b2-b27144fde653`
- **Display Name:** Office 365 Exchange Online
### App Role IDs (Exchange Online resource `00000002-0000-0ff1-ce00-000000000000`)
| Role | ID |
|---|---|
| `full_access_as_app` (EWS) | `dc890d15-9560-4a4c-9b7f-a736ec74ec40` |
| `Exchange.ManageAsApp` (REST cmdlets) | `dc50a0fb-09a3-484d-be87-e023b12c6440` |
| `Exchange.ManageAsAppV2` | `ea3d980d-ebc7-4be4-8298-a167e5b8797c` |
---
## Commands Run
### Token acquisition
```bash
bash "$SKILL_DIR/scripts/get-token.sh" "207fa277-e9d8-4eb7-ada1-1064d2221498" "investigator-exo"
# Result: success, token TTL 55 min, cached at /tmp/remediation-tool/{tenant-id}/investigator-exo.jwt
```
### Patch app registration (MSP tenant)
```
PATCH https://graph.microsoft.com/v1.0/applications/2ca484d4-5b38-4fa5-b8f7-08a70304a54d
Authorization: Bearer <tenant-admin token for MSP tenant ce61461e-81a0-4c84-bb4a-7b354a9a356d>
Body: requiredResourceAccess with Exchange.ManageAsApp (dc50a0fb) added alongside existing full_access_as_app
Result: 204 No Content
```
### Grant role in Cascades
```
POST https://graph.microsoft.com/v1.0/servicePrincipals/072df88c-1f1f-4910-a9b2-b27144fde653/appRoleAssignedTo
Authorization: Bearer <tenant-admin token for Cascades>
Body: { principalId: c64ee5c1, resourceId: 072df88c, appRoleId: dc50a0fb }
Result: 201 Created — ComputerGuru - Security Investigator / Office 365 Exchange Online
```
### Confirmed working
```
POST https://outlook.office365.com/adminapi/beta/207fa277-e9d8-4eb7-ada1-1064d2221498/InvokeCommand
Body: { CmdletInput: { CmdletName: "Get-AcceptedDomain" } }
Result: 200 OK — cascadestucson.com returned
```
---
## Alma Montt Mailbox Findings
| Property | Value |
|---|---|
| PrimarySmtpAddress | `Alma.Montt@cascadestucson.com` |
| RecipientType | UserMailbox |
| EmailAddressPolicyEnabled | **False** (manually provisioned) |
| Email addresses | Only `Alma.Montt@cascadestucson.com` — no onmicrosoft alias |
| DeliverToMailboxAndForward | False |
| ForwardingAddress | None |
| LitigationHoldEnabled | False |
| HiddenFromAddressLists | False |
| LastLogonTime | 2026-05-20 17:01 UTC (today) |
| LastInteractionTime | 2026-05-20 17:17 UTC (today) |
| ItemCount | 128 |
| TotalSize | ~5MB |
| Inbox items | 29 |
| Junk Email items | 0 |
| Inbox rules | 1 (built-in Junk E-mail Rule only, no custom rules) |
| Hidden inbox rules | None |
| Blocked senders | None |
| TrustedListsOnly | False |
| Transport rules blocking | None (only rule: Fax Forward BCC) |
| Quarantine | Not checked — Get-QuarantineMessage returned Invalid Operation |
| Message trace (7d) | 0 results — SP likely needs Compliance Management role for trace data |
### What's known
Mail is reaching the mailbox — she logged in today and has 29 inbox items. No obvious delivery block found at EOP level.
### Likely next step
The missing email is probably specific (a particular sender or domain). Check via Exchange Admin Center:
- `https://admin.exchange.microsoft.com/#/messagetrace` → filter by recipient `Alma.Montt@cascadestucson.com` and the expected sender
- Check quarantine at `https://security.microsoft.com/quarantine` for her address
- Consider adding onmicrosoft alias via `Set-Mailbox -EmailAddresses @{Add="SMTP:alma.montt@cascadestucson.onmicrosoft.com"}` if senders may be using the old routing address
---
## Pending / Follow-Up
- [ ] **Apply Exchange.ManageAsApp grant to all other client tenants** — app registration updated, but each tenant needs the role granted individually. Can be done via `onboard-tenant.sh` or direct Graph POST per tenant.
- [ ] **Investigate Alma's specific missing email** — need to know which sender/domain she's not receiving from. Run targeted message trace in EAC or give SP the Compliance Management role for trace access.
- [ ] **Add Compliance Management or View-Only Recipients role to Security Investigator SP** in client tenants so message trace works via InvokeCommand.
- [ ] **Add onmicrosoft alias to Alma's mailbox** if confirmed she should have it (`EmailAddressPolicyEnabled: False` is unusual).
- [ ] **Update vault permissions list** for Security Investigator — vault entry currently lists `full_access_as_app (EXO)` but app now also has `Exchange.ManageAsApp`.
---
## Vault Paths Accessed
- `clients/cascades-tucson/m365-admin.sops.yaml`
- `msp-tools/computerguru-security-investigator.sops.yaml`
- `msp-tools/computerguru-tenant-admin.sops.yaml`

View File

@@ -49,7 +49,7 @@ STYLE AND JUDGMENT CALLS — these define the prompt:
1. What's your rule for full-narrative vs. short-form? Is it dollar threshold,
hospitalization, institutional defendant, contested liability — or just your
call case by case?
call case by case?
2. When the AI flags an injury mentioned in the intake notes but unsupported by
records, what do you want it to do? Include and flag, exclude silently, or

View File

@@ -0,0 +1,170 @@
# ClaudeTools Discord Bot — Operating Instructions
## What You Are
You are the ClaudeTools Discord Bot, running as a Windows service on BEAST (GURU-BEAST-ROG).
Working directory: `C:/Users/guru/ClaudeTools`
You are a fully capable Claude Code agent invoked by Discord messages. You complete tasks
autonomously and return results in a single turn. You are NOT the interactive coordinator
Claude — you have no back-and-forth loop.
---
## CRITICAL: No Interactive Interaction
**You are running inside a Discord bot. There is no mechanism for mid-task clarification.**
NEVER:
- Use AskUserQuestion or any interactive prompt
- Pause mid-task to ask "should I proceed?" or "which option?"
- Request confirmation before taking action
- Ask the user to supply information that is in the vault or derivable from context
ALWAYS:
- State any assumption you made at the top of your response, then proceed
- Complete the full task in one turn
- If a task is genuinely impossible (e.g., requires info that doesn't exist anywhere),
state why clearly and stop — do not ask what to do next
- Prefer doing something reasonable over asking what to do
---
## Who Is Asking: Discord User Identity
Every message is prefixed with a `[DISCORD_CONTEXT]` block containing the sender's Discord
username, display name, and user ID. Always read this block to determine who is asking.
### Known Team Members — Full Access
| Person | Discord Username | Notes |
|--------|-----------------|-------|
| Mike Swanson | (note on first interaction) | Owner, admin |
| Howard Enos | (note on first interaction) | Technician, full trust |
When a team member identifies themselves, note their Discord username in your session log
so future sessions can recognize them without re-introduction.
**Full access:** all tools, file operations, shell commands, git, M365 actions, vault reads,
service restarts, and all skills.
### Unknown Users — Restricted
Read-only and informational responses only. No file writes, no git operations, no system
changes, no M365 actions, no vault access. State clearly: "I can only provide informational
responses for unrecognized users."
---
## Vault Access
All credentials are in the SOPS vault. Use the vault wrapper — never hardcode paths:
```bash
VAULT="C:/Users/guru/ClaudeTools/.claude/scripts/vault.sh"
bash "$VAULT" search "keyword" # search without decrypting
bash "$VAULT" get-field <path> <field> # get one field
bash "$VAULT" get <path> # decrypt full entry
bash "$VAULT" list # list all entries
```
Vault structure:
- `msp-tools/` — MSP app credentials (remediation tool, CIPP, Syncro, etc.)
- `clients/` — Per-client M365, server, and device creds
- `infrastructure/` — Server, firewall, hosting creds
- `services/` — SaaS API keys
- `projects/` — Per-project credentials
**You can and should retrieve credentials from the vault directly.** Do not ask the user
for credentials that exist in the vault.
---
## Remediation Tool (/remediation-tool)
The remediation skill handles M365 investigation and gated remediation. It auto-triggers
for: "check X's mailbox", "breach check", "tenant sweep", "inbox rules", "credential
stuffing", "foreign sign-in", "risky user", "oauth consent".
### How to Use It Effectively From Discord
1. **Identify the client** from the request (e.g., "check Cascades Tucson" → client slug
`cascades-tucson`).
2. **Pull credentials from vault** before invoking the skill — do not wait for the skill
to ask:
- M365 tenant admin: `clients/<slug>/m365-admin.sops.yaml` or `m365.sops.yaml`
- MSP app certs (5 apps):
- `msp-tools/computerguru-security-investigator.sops.yaml`
- `msp-tools/computerguru-exchange-operator.sops.yaml`
- `msp-tools/computerguru-user-manager.sops.yaml`
- `msp-tools/computerguru-tenant-admin.sops.yaml`
- `msp-tools/computerguru-defender-addon.sops.yaml`
3. **Invoke the skill** with the tenant info and credential context already in hand.
4. **Report findings concisely** in Discord — use plain text, bullet points for findings,
code blocks for raw data. Keep it under 1800 chars per message when possible.
---
## Available Skills
| Skill | Trigger / Use |
|-------|--------------|
| `/remediation-tool` | M365 breach checks, tenant sweeps, mailbox audits |
| `/save` | Write session log + sync repo — run after EVERY completed task |
| `/sync` | Sync repo only, no log |
| `/context` | Search session logs for prior context |
| `/checkpoint` | Git commit + database checkpoint |
| `/syncro` | Syncro PSA ticket management |
---
## After Every Completed Task
Run `/save` at the end of every completed task. The session log should include:
- Who asked (Discord username + display name)
- What was requested
- What was done and the outcome
- Vault paths accessed (paths only, never credential values)
This creates an audit trail and keeps the repo in sync.
---
## Response Formatting for Discord
- Plain text, not heavy markdown — headers (`#`) do not render in Discord
- Use `**bold**` sparingly for key findings
- Use code blocks for commands, raw output, or structured data
- Keep individual messages under 1800 characters (the bot handles splitting, but shorter
is better)
- No emojis unless the user uses them first
- No filler phrases ("Great question!", "Certainly!", "I'd be happy to")
- State what you did, what you found, or what went wrong — nothing else
---
## Local Machine Rules (BEAST)
- Working directory: `C:/Users/guru/ClaudeTools`
- Full read access across the repo
- Write access for session logs, task files, and project work
- SSH uses `C:\Windows\System32\OpenSSH\ssh.exe` (never Git for Windows SSH)
- Python: use `py` not `python` or `python3`
- Do not modify `.claude/identity.json` or vault files
- Service management (NSSM, Windows services) requires explicit team-member request
---
## Updating These Instructions
This file lives at `projects/discord-bot/DISCORD_CLAUDE.md` in the ClaudeTools repo.
It can be updated by:
- Any Claude Code session with repo access (main session, this bot session, any machine)
- Direct Discord message from a team member: "update your instructions to..."
Changes take effect on the bot's next restart. To restart the bot service on BEAST:
```
nssm restart ClaudeToolsDiscordBot
```
After editing this file, commit and push via `/sync` or `/save`.

View File

@@ -20,8 +20,14 @@ logger = logging.getLogger(__name__)
def _load_system_prompt() -> str:
claude_md = settings.claudetools_root / ".claude" / "CLAUDE.md"
return claude_md.read_text(encoding="utf-8")
prompt_path = settings.claudetools_root / settings.discord_system_prompt
if prompt_path.exists():
return prompt_path.read_text(encoding="utf-8")
logger.warning(
"[WARNING] Discord system prompt not found at %s — falling back to CLAUDE.md",
prompt_path,
)
return (settings.claudetools_root / ".claude" / "CLAUDE.md").read_text(encoding="utf-8")
class ThreadAgent:

View File

@@ -22,6 +22,14 @@ class Settings(BaseSettings):
description="Path to ClaudeTools repository (agent cwd)",
)
# Path to Discord-specific system prompt, relative to claudetools_root.
# Edit projects/discord-bot/DISCORD_CLAUDE.md to change bot behavior without
# touching the main CLAUDE.md. Changes take effect on next bot restart.
discord_system_prompt: Path = Field(
default=Path("projects/discord-bot/DISCORD_CLAUDE.md"),
description="System prompt for Discord bot sessions (relative to claudetools_root)",
)
log_level: str = Field(default="INFO", description="Logging level")
log_file: Optional[Path] = Field(default=Path("logs/bot.log"), description="Log file")

View File

@@ -29,21 +29,37 @@ class MessageHandler:
if message.author == self.bot.user:
return
content = message.content
# Strip the bot mention to get the raw user text.
user_text = message.content
for mention in message.mentions:
if mention == self.bot.user:
content = content.replace(f"<@{mention.id}>", "").replace(
user_text = user_text.replace(f"<@{mention.id}>", "").replace(
f"<@!{mention.id}>", ""
).strip()
if not content and not message.attachments:
if not user_text and not message.attachments:
await message.reply("Hey! How can I help?")
return
# Build caller-identity header so the agent always knows who is asking.
author = message.author
display = getattr(author, "display_name", author.name)
guild_name = message.guild.name if message.guild else "DM"
channel_name = getattr(message.channel, "name", "unknown")
discord_ctx = (
"[DISCORD_CONTEXT]\n"
f"User: @{author.name}"
+ (f" (display: {display})" if display != author.name else "")
+ f" | ID: {author.id}\n"
f"Channel: #{channel_name} | Guild: {guild_name}\n"
"[/DISCORD_CONTEXT]\n\n"
)
content = (discord_ctx + user_text).strip()
if isinstance(message.channel, discord.Thread):
thread = message.channel
else:
name = self._thread_name(content) if content else "Attachment"
name = self._thread_name(user_text) if user_text else "Attachment"
thread = await message.create_thread(
name=name,
auto_archive_duration=1440,

View File

@@ -0,0 +1,647 @@
# Session Log -- 2026-05-20
## User
- **User:** Mike Swanson (mike)
- **Machine:** DESKTOP-0O8A1RL
- **Role:** admin
- **Session span:** 2026-05-20, afternoon
---
## Session Summary
This session focused entirely on the CryoWeave client website (cryoweave.com), a WordPress site hosted on the IX cPanel server (72.194.62.5). Work fell into three phases: site audit and immediate fixes, competitive/SEO research, and report delivery.
The session opened with a full browser-based audit of cryoweave.com across all pages: Home, About Us, Greg Schickling, Consulting, Designing & Manufacturing, Services, and Contact. Page text was extracted via browser automation and SEO meta tags were inspected via JavaScript. The most critical finding was a sitewide noindex/nofollow meta robots tag on every page -- a WordPress "Discourage search engines" setting that had been left enabled, making the entire site invisible to Google. Six typos were also identified across three pages.
Typo fixes were applied using WP-CLI via SSH (authenticated through PuTTY plink with the vault-stored root password and hostkey pinning). All six replacements were targeted to wp_posts using wp search-replace --precise scoped to specific surrounding strings to avoid false matches. The noindex issue was resolved with a single WP-CLI option update (blog_public 0 to 1). Both changes were verified live in the browser.
Competitive research was conducted via parallel web searches covering global cryogenic cable manufacturers, NASA/ESA-certified suppliers, handwoven ribbon cable specialists, and the emerging quantum computing cryogenic market. Key finding: fewer than 10 companies worldwide do what CryoWeave does, most are UK/European, and none combine handwoven construction + NASA dual-certification + small-batch willingness in the US. The quantum computing "wiring crisis" was identified as a significant untapped market -- scaling quantum computers require thousands of custom cryogenic connections per system, representing a $2.1B+ market in 2026 that CryoWeave's site makes zero reference to.
A detailed SEO and site strategy report was produced covering: competitive landscape, keyword strategy (primary/secondary/long-tail/emerging), site architecture recommendations, page-by-page title/meta/content changes, five new page outlines with full content structures, a technical SEO checklist, off-page/authority building recommendations, and a phased priority roadmap. The report was delivered as plain text opened in Notepad (%TEMP%\CryoWeave_SEO_Report.txt). Outlook COM automation was attempted but failed (process not running); M365 connector setup deferred to user.
---
## Key Decisions
- Targeted search-replace strings over bare word replacement -- Used specific surrounding context (e.g., "start our consolations with") rather than bare word to prevent unintended hits in serialized data. Scoped to wp_posts only.
- PuTTY plink over OpenSSH -- Standard OpenSSH password auth failed due to too many loaded keys in SSH agent. Switched to plink with explicit -hostkey fingerprint.
- WP-CLI over cPanel UI -- The cryoweave cPanel session was stuck on a forced password change screen. WP-CLI via SSH bypassed the UI entirely.
- WHM session token for cPanel access -- Used WHM create_user_session API to generate a one-time cPanel login URL. Redirected to forced-password-change page; led to WP-CLI fallback.
- Deferred "unnecessarily overpriced" phrasing -- Content judgment call, not a clear typo. Left for content refresh phase.
- Quantum computing identified as primary new market -- Based on $2.1B market size (2026), active demand from IBM/Google scaling, and total absence of competing content on CryoWeave's site.
- Notepad fallback for report delivery -- Outlook COM threw CO_E_SERVER_EXEC_FAILURE; Outlook was not running. M365 connector setup delegated to user.
---
## Problems Encountered
- OpenSSH auth failed -- "Too many authentication failures" on ssh root@172.16.3.10. Resolved via PuTTY plink with -hostkey SHA256:GZYP/o5XUoRtFRCv1iGjxmqGfQoEsMuiNQBJucoJUh8.
- cPanel forced password change -- WHM session token worked but account had a forced password change flag; every cPanel URL redirected to /passwd/index.html?msg=forced. Resolved by bypassing cPanel and using WP-CLI directly.
- WP-CLI reported 16 replacements per fix -- Expected: WordPress stores post revisions in wp_posts. Each fix hit live post plus all historical revisions. Live pages verified correct.
- /designing URL is blank -- cryoweave.com/designing returns a blank page. Correct URL is /manufacturing/. Added to technical SEO checklist as pending 301 redirect.
- site:cryoweave.com returned no CryoWeave pages -- Confirmed noindex had been active long enough for Google to have zero pages indexed.
- Outlook COM automation failed -- New-Object -ComObject Outlook.Application threw CO_E_SERVER_EXEC_FAILURE (0x80080005). Outlook was not running. Report delivered via Notepad instead.
---
## Configuration Changes
- WordPress blog_public option on cryoweave.com -- Changed 0 to 1. Removes sitewide noindex/nofollow.
- WordPress wp_posts content -- Six search-replace operations:
- "start our consolations with" -> "start our consultations with" (About Us, page ID 46)
- "packaged it so arrives safe" -> "packaged it so it arrives safe" (About Us, page ID 46)
- "Our consolation and design" -> "Our consultation and design" (About Us, page ID 46)
- "and/or manufactures by hand" -> "and/or manufacture by hand" (About Us, page ID 46)
- "Consolations longer than" -> "Consultations longer than" (Consulting, page ID 80)
- "electro-mechanical devises" -> "electro-mechanical devices" (Greg Schickling, page ID 63)
- Session log created: session-logs/2026-05-20-session.md
---
## Credentials & Secrets
No new credentials created this session.
Used from vault:
- infrastructure/ix-server.sops.yaml -- root SSH credentials for 172.16.3.10
- SSH hostkey fingerprint (ed25519): SHA256:GZYP/o5XUoRtFRCv1iGjxmqGfQoEsMuiNQBJucoJUh8
cPanel account details discovered:
- Domain: cryoweave.com
- cPanel username: cryoweave
- WordPress path: /home/cryoweave/public_html
- WordPress version: 6.1.10 (outdated -- current is 6.7+)
- PHP version: 8.1.34 (via ea-php81)
- cPanel account email: greg@cryoweave.com
- Account has forced password change flag active (blocks cPanel UI via session tokens)
---
## Infrastructure & Servers
IX cPanel server (LAN): 172.16.3.10
IX cPanel server (WAN): 72.194.62.5
WHM URL: https://ix.azcomputerguru.com:2087 / http://72.194.62.5:2086
cPanel version: 134.0.28
OS: CloudLinux v9.7.0
SSH hostkey (ed25519): SHA256:GZYP/o5XUoRtFRCv1iGjxmqGfQoEsMuiNQBJucoJUh8
CryoWeave WP path: /home/cryoweave/public_html
CryoWeave site: https://cryoweave.com
CryoWeave contact: greg@cryoweave.com / (520) 347-8440
CryoWeave address: 7437 E 22nd Tucson AZ 85710
---
## Commands & Outputs
Find cryoweave cPanel username:
plink -ssh -l root -pw "<vault>" -hostkey "SHA256:GZYP/..." 172.16.3.10 "grep -i cryoweave /etc/userdatadomains"
-> cryoweave.com: cryoweave==root==main==cryoweave.com==/home/cryoweave/public_html==...
List WordPress pages:
wp --allow-root --path=/home/cryoweave/public_html post list --post_type=page --fields=ID,post_title,post_status --format=table
-> About Us=46, Consulting=80, Greg Schickling=63, Services=70, Designing & Manufacturing=82, Contact=87
Verify and fix noindex:
wp --allow-root --path=/home/cryoweave/public_html option get blog_public -> 0
wp --allow-root --path=/home/cryoweave/public_html option update blog_public 1
-> Success: Updated 'blog_public' option.
Typo fix pattern (all 6 used same approach):
wp --allow-root --path=/home/cryoweave/public_html search-replace 'start our consolations with' 'start our consultations with' wp_posts --precise --report-changed-only
-> 16 replacements (live post + revisions, expected)
WHM API: generate cPanel session:
GET /cpsess<token>/json-api/create_user_session?api.version=1&user=cryoweave&service=cpaneld
-> Returned session URL; redirected to forced password change page
Post-fix robots meta verification (JS in browser):
document.querySelector('meta[name=robots]')?.content
Before: "noindex, nofollow"
After: "max-image-preview:large" (WP default, indexing now allowed)
---
## Pending / Incomplete Tasks
Phase 1 -- Foundation (highest ROI, do first):
[ ] Install Yoast SEO or RankMath plugin on cryoweave.com
[ ] Set unique title + meta description on every existing page (templates in report)
[ ] Submit sitemap to Google Search Console (create GSC account first)
[ ] Create and verify Google Business Profile for CryoWeave
[ ] Fix broken contact form (currently "undergoing maintenance")
Immediate technical:
[ ] Add 301 redirect: /designing -> /manufacturing/
[ ] Fix cryoweave cPanel forced password change flag
Phase 2 -- Content:
[ ] Build /cryogenic-cable-assembly/ landing page (outline in report)
[ ] Expand /manufacturing/ page (needs wire materials, temp range, ordering info)
Phase 3 -- New market pages:
[ ] /applications/quantum-computing-cables/
[ ] /nasa-certified/
[ ] /applications/space-cryogenic-cables/
[ ] /applications/scientific-instruments/
Phase 4 -- Authority:
[ ] Claim/correct ZoomInfo, Manta, BuzzFile, HigherGov listings
[ ] Add SAM.gov registration mention to site
[ ] University physics department supplier directory outreach
Phase 5 -- Maintenance:
[ ] WordPress update 6.1.10 -> 6.7+ (test staging first)
[ ] Image alt text audit
[ ] Google PageSpeed Insights review
Deferred:
[ ] Outlook email delivery of report -- requires M365 connector setup by user
[ ] "Unnecessarily overpriced" phrasing on Greg Schickling page
---
## Reference Information
CryoWeave WordPress page IDs:
About Us: 46 | Greg Schickling: 63 | Services: 70
Consulting: 80 | Designing & Manufacturing: 82 | Contact: 87
Competitors researched:
https://www.tekdatainterconnections.co.uk -- Tekdata/Cryoconnect (UK, closest competitor)
https://www.alpha3manufacturing.com -- Alpha3 Manufacturing (Tekdata parent)
http://www.cmr-direct.com -- CMR-Direct Cryoloom (raw loom supplier)
https://www.iceoxford.com -- ICE Oxford (UK, cryostat equipment)
https://www.meritec.com -- Meritec (Ohio, US)
https://starcryo.com -- STAR Cryoelectronics (New Mexico)
https://cryocoax.com -- CryoCoax/Intelliconnect (UK, RF coax only)
Market data:
Cryogenic cables market 2026: $2.148B -> $3.241B by 2035 (4.5% CAGR)
Quantum cryogenic system market 2024: $1.332B -> $1.715B by 2031 (7.4% CAGR)
Source: https://www.globenewswire.com/news-release/2026/02/23/3242363/
CryoWeave government contracting:
SAM.gov: UEI KWLXSZCJSQ17, CAGE 9LGQ4
Federal award on file: September 5, 2024
https://www.highergov.com/awardee/cryoweave-llc-476051844/
https://opengovus.com/sam-entity/081228946
M365 connector setup:
https://support.claude.com/en/articles/12542951-enable-and-use-the-microsoft-365-connector
Report delivered to:
C:\Users\guru\AppData\Local\Temp\CryoWeave_SEO_Report.txt (Notepad, local only)
---
## Update: Afternoon — Rob Activity Audit & Server Security
### Session Summary
This session was a two-track investigation into Rob's (employee) server activity and productivity validation on IX (172.16.3.10) and websvr (websvr.acghosting.com). The driving question: how much work is Rob actually doing, and can it be validated against server-side evidence rather than his Syncro ticket claims.
WHM access_log was analyzed for external root sessions not attributable to Mike. Three non-Mike root access vectors confirmed: 97.181.171.114 (Verizon Wireless AZ, March 2, 2026 — DNS Zone Editor, mass_edit_dns_zone), 74.244.177.66 (Starlink Phoenix, April 21, 2026 — DNS Zone Editor + graceful_reboot_landing/server reboot), and 174.234.68.59 (Verizon Wireless NV, December 2025 — two SSH root terminal sessions 2+ hours each). All three attributed to Rob. The arizonawebsitedesign.pro account was cleaned of a Duplicator installer (installer.php, Sep 2022), ALFA web shell framework remnant under .well-known/pki-validation/ALFA_DATA/, and two zero-byte PHP files.
WordPress session_tokens were queried across all Rob-associated admin accounts. Rob's `rob@azcomputerguru.com` guruadmin accounts remain active on 25+ client sites; last confirmed Rob-IP sessions from Cox 69.136.118.50 through May 2025. Recent sessions showing Mike's Comcast IP 76.18.103.222 were initially flagged as anomalous but Mike confirmed Rob has legitimate Tailscale access — his traffic exits through Mike's LAN. Rob also has a `magus/info@maguspressworks.com` identity (maguspressworks.com, registered April 2024) with admin on hightechmortgage_maindb, nwpool_db, nwpool_maindb, packetdial_2022 — all added October 20, 2025.
Productivity validation cross-referenced login timestamps against actual server-side evidence. WordPress post revisions confirmed real content work on hightechmortgage only (April 6, 2026: User Registration page edited/published, htm_user_class entries by magus). All other recent sessions (acepickupparts May 6, nwpool May 12, packetdial May 18-19, thrive May 2025) showed no post revisions or only an auto-draft. Rob's automation scripts in /root were inventoried — six scripts written and run once on January 13, 2026, plus scan_smart_slider.sh added April 11, 2026 in response to Smart Slider 3 Pro CVE. None are cron-scheduled.
SSH authorized_keys audited on both servers. A GoDaddy infrastructure key (root@224.235.109.208.host.secureserver.net) was found in IX root authorized_keys with no justification and was removed. websvr has a `rob` cPanel account with an empty crontab. Session ended with a discussion of non-root access architecture (WHM reseller + sudo-restricted SSH) for Rob. Mike has a meeting with Rob on 2026-05-21 to discuss productivity; outcome will be either implementing the reseller scheme or full lockout.
### Key Decisions
- **Tailscale exit node explains 76.18.103.222 sessions** — initially treated as anomalous. Mike confirmed Rob has Tailscale network access. Traffic egresses through Mike's LAN. No security incident.
- **GoDaddy key removed immediately** — no documented justification for external root SSH trust from GoDaddy infrastructure. Removed same session; backup retained on server.
- **Post revisions as primary work evidence** — reliable because they are created by the editing user with a timestamp. Filesystem mtimes not used due to noise from automated processes.
- **Automation scripts assessed as one-time work** — all report outputs dated January 13, 2026 only. Not scheduled, not maintained.
- **Discord IP pull ruled out** — Discord does not expose member IPs to server admins. Not viable without legal process.
### Problems Encountered
- **Collation mismatch on UNION query** — UNION ALL across multiple WordPress databases failed: `ERROR 1271 (HY000): Illegal mix of collations`. Resolved by running per-database queries instead.
- **plink batch mode rejected hostkeys** — Both IX and websvr failed with `FATAL ERROR: Cannot confirm a host key in batch mode`. Resolved by adding `-hostkey <fingerprint>` to all commands.
### Configuration Changes
- **Removed** GoDaddy SSH key from `/root/.ssh/authorized_keys` on 172.16.3.10
- Key: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP2RXPvnQjdn/hvLtzqiFibKEfcYZviwZIgr26VyVdMT root@224.235.109.208.host.secureserver.net`
- Backup: `/root/.ssh/authorized_keys.bak.20260520` on IX
- **Removed** from `/home/azwebdesign/public_html/` on IX:
- `installer.php` (75KB Duplicator installer, Sep 2022)
- `dup-installer-bootlog__970a9a6-14174455.txt`
- `.well-known/pki-validation/ALFA_DATA/` directory
- `.well-known/pki-validation/marijuan.php` (0 bytes)
- `.well-known/pki-validation/0r.php` (0 bytes)
### Infrastructure
- **websvr.acghosting.com** — 162.248.93.81, CentOS 7, WHM/cPanel, SSH hostkey: `SHA256:qcaW8BWq5UyM0l0g6DS9JfYbMZN/LTXLs3BIEZV8BE0`
- **websvr root password:** `[3H+_f.Yh4c0>@egH[6L!?u]S3s[9C82` — vault: `infrastructure/websvr-legacy-hosting.sops.yaml`
### Rob's Identity and Access Profile
- **Primary:** `rob@azcomputerguru.com` — guruadmin on 25+ WP client sites (batch-created 2020-03-17)
- **Secondary:** `magus / info@maguspressworks.com` — admin on hightechmortgage, nwpool (x2), packetdial_2022 (added 2025-10-20)
- **Business:** maguspressworks.com — Namecheap, registered 2024-04-12, updated 2026-03-14, privacy protected
- **Cox IP:** 69.136.118.50 — last Rob session May 20, 2025
- **Verizon mobile:** 97.181.171.114 (WHM Mar 2026), 174.234.68.59 (SSH Dec 2025)
- **Starlink Phoenix:** 74.244.177.66 (WHM Apr 2026)
- **Via Tailscale/Mike's LAN:** 76.18.103.222 — multiple WP sessions 2025-2026
### Rob's Automation Scripts (IX /root/)
All scripts run once only. Not cron-scheduled.
| Script | Purpose | Last Modified |
|---|---|---|
| `/root/scan_sites.sh` | Error logs, PHP memory errors, WP DB list | 2026-01-13 11:31 |
| `/root/check_dbs.sh` | DB bloat per site | 2026-01-13 11:32 |
| `/root/cleanup_error_logs.sh` | Truncate error logs for ~11 domains | 2026-01-13 20:01 |
| `/root/cleanup_wordfence.sh` | TRUNCATE Wordfence tables across all DBs | 2026-01-13 20:09 |
| `/root/generate_security_performance_report.sh` | Full IX security/perf audit | 2026-01-13 20:12 |
| `/root/scan_smart_slider.sh` | Smart Slider 3 Pro CVE scanner | 2026-04-11 05:09 |
| `/root/URGENT_SITE_ISSUES.txt` | Jan 13 scan findings (3.4KB) — not yet read | 2026-01-13 11:33 |
| `/root/IX_SECURITY_PERFORMANCE_REPORT_2026-01-13.txt` | Full report (37KB) — not yet read | 2026-01-13 20:16 |
### WordPress Last Login Summary (Rob Accounts)
| Database | Account | Last Login (UTC) | IP |
|---|---|---|---|
| cryoweave_maindb | guruadmin | 2023-03-17 16:06 | 70.162.90.195 |
| thrive_maindb | guruadmin | 2025-05-20 21:02 | 69.136.118.50 (Rob's Cox) |
| drsticken_maindb | guruadmin | 2024-05-31 19:42 | 69.136.118.50 |
| compoundfitness_maindb | guruadmin | 2024-05-22 20:30 | 69.136.118.50 |
| bruceext_maindb | guru | 2024-06-06 16:59 | 69.136.118.50 |
| blackswanarchery_maindb | guruadmin | 2025-07-24 17:27 | 76.18.103.222 (Tailscale) |
| acepickupparts_maindb | guruadmin | 2026-05-06 17:15 | 76.18.103.222 (Tailscale) |
| peacefulspirit_wp24 | guruadmin | 2026-05-09 04:24 | 76.18.103.222 (Tailscale) |
| hightechmortgage_maindb | magus | 2026-04-06 19:46 | 76.18.103.222 (Tailscale) |
| nwpool_maindb | magus | 2025-12-08 15:27 | 127.0.0.1 (server-side) |
| nwpool_db | magus | 2026-05-12 03:45 | 76.18.103.222 (Tailscale) |
| packetdial_2022 | magus | 2026-05-18 14:18 | 76.18.103.222 (Tailscale) |
| packetdial_2022 | magus | 2026-05-19 21:39 | 76.18.103.222 (Tailscale) |
### WordPress Content Confirmed vs. Login Events
| Site | Session Date | Work Found | User | Detail |
|---|---|---|---|---|
| hightechmortgage | 2026-04-06 | Yes | magus | Published "User Registration" page, htm_user_class (Client/Investor) entries |
| packetdial | 2026-05-18 | Minimal | magus | Auto-draft only + automated URL metrics update |
| peacefulspirit | 2026-05-09 | Other user | mara | "Meet The Staff" revised by `mara`, not Rob |
| acepickupparts | 2026-05-06 | None | — | Login, no post revisions |
| nwpool | 2026-05-12 | None | — | Login, no post revisions |
| thrive | 2025-05-20 | None | — | Login, no post revisions |
### IX SSH Authorized Keys (Post-Cleanup, 10 keys)
Rotating cPanel keys (4), azcomputerguru@local, claude-code, claude-code@localadmin, root@websvr.acghosting.com, guru@wsl, root@Jupiter.
Removed: `root@224.235.109.208.host.secureserver.net` (GoDaddy).
### Pending — 2026-05-21 Rob Meeting
Two outcomes:
**Option A — Continue employment / implement tracked access:**
1. Create WHM reseller account for Rob on IX (and websvr)
2. Grant ACL-based WHM privileges (DNS editor, WP Toolkit, assigned client accounts)
3. Create system user `rob` + `/etc/sudoers.d/rob` restricting SSH commands
4. Remove Rob's root-level WHM/SSH access
5. Assign specific client cPanel accounts to his reseller ownership
**Option B — Termination / lockout:**
1. Change root WHM password on IX and websvr
2. Remove Rob's Tailscale node
3. Disable guruadmin (rob@azcomputerguru.com) on all 25+ client WP sites
4. Disable magus (maguspressworks.com) on hightechmortgage, nwpool (x2), packetdial
5. Revoke any remaining API tokens or remote access tools
### Still Not Investigated
- `/root/URGENT_SITE_ISSUES.txt` — Rob's Jan 13, 2026 findings (3.4KB)
- `/root/IX_SECURITY_PERFORMANCE_REPORT_2026-01-13.txt` — full server report (37KB)
- DNS records Rob modified in March 2 and April 21 WHM sessions — what zones/records changed
- Reason for April 21 server reboot (graceful_reboot_landing) — authorized?
---
## Update: Evening — arizonahatters Wordfence Fix
### Summary
Rob's January 13, 2026 `URGENT_SITE_ISSUES.txt` identified arizonahatters as a critical issue: 468MB error_log, 429 PHP memory errors, and Wordfence table bloat. Rob had been partially credited for flagging this but the fix was never confirmed complete. Audited and fully resolved this session.
**Wordfence config before fix:** `scheduledScansEnabled=1`, `scansEnabled_malware=1`, `scansEnabled_fileScanner` not set (defaulting on). Full background scans running on a resource-constrained shared hosting account, accumulating bloat indefinitely.
**Actions taken:**
1. Disabled all Wordfence scanning in `arizonahatters_maindb.wp_wfconfig`:
- `scheduledScansEnabled` → 0
- `scansEnabled_malware` → 0
- Inserted `scansEnabled_fileScanner` → 0 (was absent, defaulting on)
2. Truncated large scan-data tables:
- `wp_wffilemods`: 11.52MB → 0.02MB
- `wp_wfknownfilelist`: 5.52MB → 0.02MB
3. Deleted stale log entries:
- `wp_wflogins`: deleted rows older than 90 days
- `wp_wfhits`: deleted rows older than 30 days
- `wp_wfblocks7`: deleted expired blocks
4. Ran `OPTIMIZE TABLE` on all five Wordfence tables
5. Cleared `/home/arizonahatters/public_html/error_log` (was 76K, 340 PHP memory errors)
**Result:** Total DB reduced from 32.24MB → 13.45MB. Wordfence real-time firewall and login protection remain active — only scheduled background scanning disabled.
**peacefulspirit confirmed fixed:** Rob's cleanup_wordfence.sh (Jan 13) had truncated WPML mails and Gravity Forms tables. Verified: wp_wpml_mails 156MB → 1.6MB, total DB ~310MB → ~100MB. That one Rob did follow through on.
### Configuration Changes
- `arizonahatters_maindb.wp_wfconfig``scheduledScansEnabled` = 0, `scansEnabled_malware` = 0, `scansEnabled_fileScanner` = 0 (inserted)
- `arizonahatters_maindb.wp_wffilemods` — TRUNCATED (11.52MB → 0.02MB)
- `arizonahatters_maindb.wp_wfknownfilelist` — TRUNCATED (5.52MB → 0.02MB)
- `arizonahatters_maindb.wp_wflogins` — deleted entries older than 90 days
- `arizonahatters_maindb.wp_wfhits` — deleted entries older than 30 days
- `arizonahatters_maindb.wp_wfblocks7` — deleted expired blocks
- All five tables: OPTIMIZE TABLE applied
- `/home/arizonahatters/public_html/error_log` — cleared (340 PHP memory exhaustion errors)
### Infrastructure
- arizonahatters cPanel account: `arizonahatters` on IX (172.16.3.10)
- WP DB: `arizonahatters_maindb`
- WP path: `/home/arizonahatters/public_html`
---
## Update: Late Evening — azcomputerguru.com Full SEO Audit & Fixes
### Session Summary
Performed a comprehensive SEO audit and implemented all automatable fixes on azcomputerguru.com (WordPress 6.9.4, IX server 172.16.3.10, Cloudflare Pro zone `1beb9917c22b54be32e5215df2c227ce`). Work divided into: server-side file edits, WordPress option/meta updates, and Cloudflare edge rule creation.
**Audit findings (abridged):** Homepage title was "Home Arizona Computer Guru" (generic); no meta description; logo link pointed to dev.computerguru.me; 5 images in footer referenced dev.computerguru.me URLs with no alt text; LocalBusiness schema absent; footer widget titles used H2 inflating heading hierarchy; duplicate CSS enqueue block; 4 canonical URLs returning 404 or redirecting via Apache only (blocked by Cloudflare WAF before reaching Apache); media library images missing alt text on 10 attachments.
**Server-side fixes (via SSH plink):**
- `header.php` — Fixed logo href from dev.computerguru.me to https://azcomputerguru.com/, added alt text to logo img
- `footer.php` — Replaced all 5 dev.computerguru.me image paths with production URLs, added descriptive alt text to all social/footer icons
- `functions.php` — Removed duplicate CSS enqueue block (lines 255-263), changed footer sidebar widget titles from H2 to H3, added `remove_action('wp_head','wp_generator')`, appended full `acg_localbusiness_schema()` function with LocalBusiness+ProfessionalService JSON-LD
- `.htaccess` — Added Apache-level 301 redirects for /services/, /about/, /blog/, /website-services/ (backup: .htaccess.bak-seo-20260520)
**WordPress DB updates (via WP-CLI):**
- `autodescription-site-settings` option — homepage_title: "Arizona Computer Guru | Managed IT Services Tucson AZ", homepage_description set (159 chars), homepage_tagline disabled
- Page ID 297 (`/tucson-seo/`) — `_genesis_description` meta set
- Media alt text — `_wp_attachment_image_alt` set on 10 attachment IDs: 1162, 20, 23, 24, 37, 1167, 1163, 1166, 1164, 1165
- Post 17 content — added alt text to gurucube and divider inline images
**Cloudflare edge redirects (Page Rules API):**
4 Page Rules created to redirect at the Cloudflare edge (before WAF bot checks reach Apache):
- `azcomputerguru.com/services*` → https://azcomputerguru.com/business-it-services-tucson/ [301]
- `azcomputerguru.com/about*` → https://azcomputerguru.com/history-of-arizona-computer-guru/ [301]
- `azcomputerguru.com/blog*` → https://azcomputerguru.com/category/blog/ [301]
- `azcomputerguru.com/website-services*` → https://azcomputerguru.com/tucson/web-services-for-tucson-businesses/ [301]
All 4 verified live returning HTTP 301 with browser user agent.
### Key Decisions
- **Cloudflare Page Rules over Transform Rules** — `http_request_dynamic_redirect` phase writes failed on all 3 stored tokens (none have Transform Rules Edit permission). Page Rules API worked with `api_token_full_dns`. Functionally equivalent for forwarding rules.
- **LocalBusiness schema via functions.php hook** — The SEO Framework (autodescription) v5.1.4 doesn't generate LocalBusiness schema. Added custom PHP at `wp_head` priority 5 to emit JSON-LD without a plugin.
- **Footer H2 → H3 only for footer sidebar** — Multiple sidebars registered in functions.php all used H2 for widget titles. Only changed the `footer` sidebar (last registration, `id='footer'`). Content-area sidebars left at H2 to avoid regressions on page sidebars.
- **TSF stores homepage SEO in option, not post meta** — `_genesis_title` / `_genesis_description` on post ID (homepage) are ignored by TSF v5+. Updated via `update_option('autodescription-site-settings', ...)`.
### Problems Encountered
- **Cloudflare Transform Rules permission denied** — Three tokens tried: `api_token_full_account`, `api_token_full_dns`, `api_token_legacy`. All failed on `PUT .../phases/http_request_dynamic_redirect/entrypoint`. Resolved by using Page Rules API instead.
- **Bot Fight Mode blocking curl test** — 301 redirects looked like 403s until a browser User-Agent was added to curl. Not an actual error — Cloudflare correctly redirects real browsers.
- **TSF homepage SEO not stored as post meta** — First attempted `wp post meta update 2 _genesis_title ...` per page SEO pattern. No effect on homepage title. Resolved by reading TSF source and updating the `autodescription-site-settings` option directly.
- **Apostrophe in WP-CLI shell arg** — `Tucson's` in description broke bash single-quote heredoc. Rewrote description without apostrophe.
### Configuration Changes
**IX server — azcomputerguru WP theme files (backups: *.bak-seo-20260520):**
- `/home/azcomputerguru/public_html/wp-content/themes/arizonacomputerguru/header.php` — logo link + alt
- `/home/azcomputerguru/public_html/wp-content/themes/arizonacomputerguru/footer.php` — all image URLs + alt text
- `/home/azcomputerguru/public_html/wp-content/themes/arizonacomputerguru/functions.php` — dedup CSS, H3 widgets, schema function
- `/home/azcomputerguru/public_html/.htaccess` — 4 Apache 301 redirects
**WordPress DB (azcomputerguru_maindb):**
- `wp_options.autodescription-site-settings` — homepage title + description
- `wp_postmeta``_genesis_description` on page 297, `_wp_attachment_image_alt` on 10 attachments, alt text on post 17 inline images
**Cloudflare (zone 1beb9917c22b54be32e5215df2c227ce):**
- Page Rule `7060e99901a3e4a09b3fc9c2e5156d05` — /services* → /business-it-services-tucson/ [301]
- Page Rule `84ea14b8d3fe2983b75267337602924a` — /about* → /history-of-arizona-computer-guru/ [301]
- Page Rule `fb1a7c5189bf9c1c2a356c438f4eccdc` — /blog* → /category/blog/ [301]
- Page Rule `a9c1964c60bb2b53d65f3955c7427691` — /website-services* → /tucson/web-services-for-tucson-businesses/ [301]
### Pending / Incomplete Tasks
- **Slider Revolution duplicate H1** — Two H1 tags in homepage slider layers; one has concatenation bug "ServicesDesign" (no space). Must be fixed via WP Admin → Slider Revolution. Cannot be automated.
- **Social icon hrefs** — Footer social icons still use `href="#"` placeholder. Need actual ACG social profile URLs.
- **Street address in LocalBusiness schema** — Only city/state currently; full postal address improves Knowledge Panel accuracy.
- **Generator tag suppression** — Slider Revolution, Site Kit, performance-lab plugins still outputting `<meta name="generator">`. Would require per-plugin suppression hooks.
- **Google Search Console** — No GSC property confirmed; sitemap submission pending.
### Infrastructure
- azcomputerguru.com WP path: `/home/azcomputerguru/public_html`
- Theme path: `/home/azcomputerguru/public_html/wp-content/themes/arizonacomputerguru/`
- SEO Plugin: The SEO Framework (autodescription) v5.1.4
- Cloudflare zone ID: `1beb9917c22b54be32e5215df2c227ce`
- Cloudflare account ID: `44594c346617d918bd3302a00b07e122`
---
## Update: 09:01 PT — LocalBusiness Schema Street Address
Added `streetAddress` and `postalCode` to the LocalBusiness JSON-LD schema in `functions.php`. The PostalAddress block previously had only city/state/country.
**Change:** `sed -i` insert on IX via plink — added two lines after existing PostalAddress fields:
- `'streetAddress' => '7437 E 22nd St'`
- `'postalCode' => '85710'`
Verified live: `curl https://azcomputerguru.com/` → ld+json block 1 (`@type: LocalBusiness`) shows full address object with all 5 fields correct.
**Pending after this session:**
- Slider Revolution duplicate H1 (WP Admin GUI only — two H1 layers in homepage slider, one has "ServicesDesign" concatenation bug)
- Footer social icon hrefs (still `href="#"` — need ACG social profile URLs from Mike)
- Google Search Console property + sitemap submission
---
## Update: 10:50 PT — Discord Bot Enhancements + Cascades Tucson Canva Remediation
### Session Summary
This update covers two distinct tasks: (1) Discord bot architecture improvements, and (2) a client email delivery investigation and remediation for Cascades Tucson.
**Discord Bot Enhancements:** Three files were modified to give the Discord bot its own behavioral ruleset, user identity awareness, and vault-guided remediation access. A new DISCORD_CLAUDE.md system prompt was created at projects/discord-bot/DISCORD_CLAUDE.md and wired into bot/config.py via a new discord_system_prompt setting. The prompt enforces no-interactive rules, defines known team members with full access vs. unknown users read-only, provides vault path guidance for all five MSP app certs, and instructs the bot to run /save after every completed task. In bot/claude/client.py, _load_system_prompt() was updated to load DISCORD_CLAUDE.md with a fallback to CLAUDE.md. In bot/handlers/message_handler.py, a [DISCORD_CONTEXT] block was injected before every message so the agent always knows who is asking.
**Cascades Tucson - Canva Email Delivery Investigation:** Alma Montt (alma.montt@cascadestucson.com) reported not receiving emails from Canva (team invite sent by Crystal Rodriguez). Investigation used Graph API (investigator tier) and EXO REST API (exchange-op tier). Alma's mailbox is brand-new (first email May 19, 2026). No quarantined messages found. No inbox rules. Junk folder empty. MX record correct. Root cause: new mailbox provisioning race -- the Canva invite was sent before/immediately after provisioning, dropped at SMTP layer before EOP. Contributing factor: Standard Preset Security Policy active since April 17 with HighConfidenceSpamAction: Quarantine and BulkThreshold: 6, which would catch Canva invite emails via Amazon SES on any future invite.
Three remediations applied: (1) Default anti-spam policy AllowedSenderDomains -- canva.com, mail.canva.com, engage.canva.com added; (2) Standard Preset Security Policy -- same domains added (MS warning: preset may be overridden by Microsoft); (3) Alma mailbox TrustedSendersAndDomains -- all three Canva domains added. Historical search job (21325332-a2a1-49c0-abb8-d0c6b88c7b0f) submitted, results to admin@cascadestucson.com. Action required: Crystal Rodriguez must resend the Canva team invite to alma.montt@cascadestucson.com.
### Key Decisions
- Separate DISCORD_CLAUDE.md from CLAUDE.md -- Discord needs no-interactivity and restricted user model rules that would conflict with interactive session posture
- Inject [DISCORD_CONTEXT] at message handler layer -- ensures every message carries identity regardless of system prompt evolution
- Track user_text separately before prepending context block -- prevents context block from affecting thread naming or empty-content detection
- AllowedSenderDomains added to both Default and Standard Preset policies -- preset is active but Default is fallback; both needed
- Historical search submitted for audit trail -- covers edge case where message was dropped pre-EOP
- Used InvokeCommand endpoint for EXO cmdlets -- direct resource endpoints (HostedContentFilterPolicy, TransportRule, QuarantineMessage) not available in /adminapi/beta/
### Problems Encountered
- EXO REST API direct resource endpoints missing for key objects (HostedContentFilterPolicy, TransportRule, QuarantineMessage) -- resolved via InvokeCommand endpoint that wraps EXO PowerShell cmdlets
- Get-QuarantineMessage required RecipientAddress as #Collection(String) not plain string -- fixed with @odata.type annotation
- HostedContentFilterPolicyPresentation is read-only (PATCH failed) -- InvokeCommand + Set-HostedContentFilterPolicy was the correct path
- message_handler.py empty content check was broken (evaluated against full content after discord_ctx prepend) -- fixed by tracking user_text before context injection
### Configuration Changes
Created:
- projects/discord-bot/DISCORD_CLAUDE.md (Discord-specific system prompt)
- clients/cascades-tucson/reports/2026-05-20-canva-email-delivery.md (investigation report)
Modified:
- projects/discord-bot/bot/config.py (added discord_system_prompt Path field)
- projects/discord-bot/bot/claude/client.py (updated _load_system_prompt() to load DISCORD_CLAUDE.md)
- projects/discord-bot/bot/handlers/message_handler.py (added [DISCORD_CONTEXT] injection, fixed empty-content check)
### Credentials & Secrets
Vault paths accessed (no new credentials):
- clients/cascades-tucson/m365-admin.sops.yaml -- admin@cascadestucson.com, tenant 207fa277-e9d8-4eb7-ada1-1064d2221498
- msp-tools/computerguru-security-investigator.sops.yaml
- msp-tools/computerguru-exchange-operator.sops.yaml (InvokeCommand write ops)
- msp-tools/computerguru-defender-addon.sops.yaml (TABL write not available)
- msp-tools/computerguru-tenant-admin.sops.yaml
### Infrastructure & Servers
- Cascades Tucson tenant: cascadestucson.com | 207fa277-e9d8-4eb7-ada1-1064d2221498
- EXO REST API: https://outlook.office365.com/adminapi/beta/207fa277-e9d8-4eb7-ada1-1064d2221498/
- Token cache: /tmp/remediation-tool/207fa277-e9d8-4eb7-ada1-1064d2221498/{tier}.jwt
### Commands & Outputs
InvokeCommand: Get-HostedContentFilterPolicy "Default"
Result: AllowedSenderDomains: ["canva.com", "mail.canva.com", "engage.canva.com"]
InvokeCommand: Get-MailboxJunkEmailConfiguration "Alma.Montt@cascadestucson.com"
Result: TrustedSendersAndDomains: ["canva.com", "mail.canva.com", "engage.canva.com"], Enabled: true
InvokeCommand: Get-QuarantineMessage (RecipientAddress array)
Result: 0 messages
Historical search: 21325332-a2a1-49c0-abb8-d0c6b88c7b0f (results to admin@cascadestucson.com)
### Pending / Incomplete Tasks
- Crystal to resend Canva invite to alma.montt@cascadestucson.com (cannot automate)
- Discord bot NSSM restart on BEAST: nssm restart ClaudeToolsDiscordBot
- Note Mike and Howard Discord usernames in DISCORD_CLAUDE.md after first bot interaction
- Standard Preset AllowedSenderDomains may revert (MS-managed preset) -- if Canva breaks again, add to TABL via ThreatSubmission.ReadWrite.All scope (not currently in any app)
- CryoWeave: Slider Revolution H1 fix, footer social icons, GSC property + sitemap
### Reference Information
- Report: clients/cascades-tucson/reports/2026-05-20-canva-email-delivery.md
- Discord prompt: projects/discord-bot/DISCORD_CLAUDE.md
- EXO InvokeCommand: POST /adminapi/beta/{tenant}/InvokeCommand
- Historical search job: GET /adminapi/beta/{tenant}/HistoricalSearch('21325332-a2a1-49c0-abb8-d0c6b88c7b0f')
---
## Update: 14:34 PT — Flarum forum posts + WordPress search audit + /forum-post skill
### User
- **User:** Mike Swanson (mike)
- **Machine:** DESKTOP-0O8A1RL
- **Role:** admin
### Session Summary
Posted two technical articles to community.azcomputerguru.com via paramiko SSH + PHP PDO inserts into the Flarum database. The Flarum posting workflow had to be developed from scratch this session — the forum's Cloudflare Bot Fight Mode blocks external API calls, and the local `curl` from IX returns empty due to an HTTP→HTTPS redirect issue on the vhost. The only working approach: SSH to IX, build a PHP script with PDO inserts, SFTP upload to `/tmp/`, execute via SSH channel, parse output for Discussion ID and Post ID.
First post was the OneDrive KFM folder redirection article (continued from a prior session context). The Flarum s9e TextFormatter XML format had to be reverse-engineered from existing posts — content is not stored as raw markdown but as a custom XML dialect with `<s>` (start marker) and `<e>` (end marker) wrappers inside each formatting element. A Python converter was written to translate markdown to this format. The first insert failed with a foreign key constraint because `posts.discussion_id` references `discussions.id` — reordering to insert discussion first (post refs are nullable at creation), then post, then UPDATE the discussion to set `first_post_id`/`last_post_id` resolved it.
After the OneDrive article was posted, ran a server-wide WordPress search indexing audit across the IX cPanel server. Found 194 wp-config.php files; 18 sites had `blog_public=0` (search discouraged). Of those, 7 were confirmed production sites whose owners were unaware. Fixed 13 sites (updated `blog_public` to 1), skipped the forms.bestmassageintucson.com site which is intentionally not indexed. The WordPress search audit findings were then written up as a second forum article and posted via the same paramiko workflow.
Converted the entire Flarum posting workflow into a reusable `/forum-post` skill at `.claude/commands/forum-post.md`. The skill covers all four phases: gather inputs (title, content, tag), preview, execute (paramiko SSH → SFTP upload PHP → execute → parse output), and report. Includes the complete Python md_to_s9e converter, PHP insert template with proper discussion-first ordering, error handling table, and hard rule to not verify via curl/browser. Added the command to CLAUDE.md's commands table.
### Key Decisions
- **paramiko over curl/API**: Cloudflare blocks external Flarum API calls; localhost curl on IX has redirect issues. Only working path is SSH→PHP→PDO. This is now the canonical approach documented in the skill.
- **Python .replace() not f-strings for PHP template substitution**: XML content contains `{}` curly braces; Python f-strings would try to expand them, causing a KeyError. Used `.replace('%%XML_CONTENT%%', xml_content)` throughout.
- **PHP nowdoc for XML content**: `<<<'FLARUM_POST_XML_END'` heredoc with closing marker at column 0 — no escaping needed for XML special characters inside.
- **Discussion-first insert order**: Flarum's `posts.discussion_id` has a FK constraint. Insert discussion with NULL post refs, then post, then UPDATE discussion. Template hardcodes this order.
- **bestmassage skip**: forms.bestmassageintucson.com is intentionally noindexed (likely a contact form subdomain) — excluded from the bulk fix.
- **Unique /tmp filename per run**: `flarum_post_<timestamp>.php` prevents collision if skill is run concurrently; always cleaned up after execution.
### Problems Encountered
- **FK constraint on posts.discussion_id**: First insert attempt failed with `SQLSTATE[23000]: 1452 Cannot add or update a child row`. Fix: reorder inserts — discussion first (post refs nullable), then post, then UPDATE.
- **PHP exit 255 no output**: `display_errors` was off by default. Added `ini_set('display_errors', 1); error_reporting(E_ALL);` to the PHP script to surface the FK error above.
- **Shell quoting in WP-config scan**: First bash-based scan used nested quotes and got empty blog_public values for all sites. Fix: Python + paramiko SFTP to read files directly, parse with regex — no quoting issues.
- **Python f-string curly brace conflict**: Initial PHP template using f-strings caused KeyError on `{` in XML content. Fix: switched to `.replace()` with `%%PLACEHOLDER%%` markers throughout.
- **Flarum admin detection**: `users` table has no `is_admin` column. Admin status is in `group_user` table (group_id=1 = Admin). user_id=1 (MikeSwanson) confirmed admin.
- **Chrome extension disconnected throughout**: Could not visually verify forum posts. Both posts confirmed via DB output only (Discussion ID/Post ID echoed by PHP script).
### Configuration Changes
- **Created:** `.claude/commands/forum-post.md` — Complete /forum-post skill with infrastructure refs, md→s9e converter, PHP template, paramiko pattern, error table
- **Modified:** `.claude/CLAUDE.md` — Added `/forum-post` to Commands & Skills table
- **Created (tmp):** `.claude/tmp/flarum_do_insert2.py` — Working OneDrive KFM post script
- **Created (tmp):** `.claude/tmp/flarum_search_insert.py` — WordPress search post script
- **Created (tmp):** `.claude/tmp/check_search.py`, `check_search2.py` — WP audit scripts
- **Created (tmp):** `.claude/tmp/fix_search.py` — Bulk blog_public fix script
### Credentials & Secrets
- **IX SSH root password:** `t4qygLl7{1zJcUj#022W^FBQ>}qYp-Od` — vault: `infrastructure/ix-server.sops.yaml credentials.password`
- **Flarum DB:** host=localhost (on IX), dbname=`azcompu_flarum`, user=`azcompu_flarum`, pass=`Fl@rum2026!CGS`
### Infrastructure & Servers
- **IX server:** 172.16.3.10 — root SSH, runs WHM/cPanel, hosts ~95 active WordPress sites + the Flarum forum
- **Flarum forum:** community.azcomputerguru.com — DB on localhost (IX), admin user_id=1 (MikeSwanson), group_id=1=Admin
- **WordPress sites:** Under `/home/*/public_html/` on IX. 194 wp-config.php files found, ~95 real sites
### Commands & Outputs
```bash
# WP search audit (Python paramiko approach)
py D:/claudetools/.claude/tmp/check_search2.py
# Found 18 disabled, 77 enabled, rest errors/stubs
# Bulk fix
py D:/claudetools/.claude/tmp/fix_search.py
# FIXED (13): [list of sites]
# SKIPPED (1): forms.bestmassageintucson.com
# Flarum post #1 (OneDrive KFM)
py D:/claudetools/.claude/tmp/flarum_do_insert2.py
# Discussion ID: 8
# Post ID: 7
# Flarum post #2 (WordPress search)
py D:/claudetools/.claude/tmp/flarum_search_insert.py
# Discussion ID: 9
# Post ID: 8
```
### Pending / Incomplete Tasks
- **WebSvr GuruRMM agent**: Blocked — CentOS 7 glibc 2.17 incompatible with current musl/glibc build targets. Options: add `x86_64-unknown-linux-musl` build target to pipeline, or skip (WebSvr is legacy/decommission candidate). No decision made.
- **IX backup offsite**: No remote transport configured in WHM. Comet Backup cron exists but binary not in PATH — non-functional. Needs S3/SFTP/Backblaze destination or Comet fix.
- **azcomputerguru.com SEO remaining**:
- Slider Revolution duplicate H1 — manual WP Admin fix
- Footer social icons still `href="#"` — need actual social profile URLs from Mike
- Google Search Console property + sitemap submission
- **Chrome extension**: Remained disconnected all session — could not visually verify forum posts
### Reference Information
- Forum post #1 (OneDrive KFM): https://community.azcomputerguru.com/d/8-onedrive-kfm-fails-after-folder-redirection-migration-heres-whats-actually-going-on
- Forum post #2 (WordPress search): https://community.azcomputerguru.com/d/9-wordpress-discourage-search-engines-setting-how-7-production-sites-lost-their-indexing
- Flarum s9e XML format reference: `.claude/commands/forum-post.md` (Markdown → s9e XML Converter section)
- forum-post skill: `.claude/commands/forum-post.md`