sync: auto-sync from HOWARD-HOME at 2026-04-22 21:40:31
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-04-22 21:40:31
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,10 @@
|
|||||||
# Backups (local only - don't commit to repo)
|
# Backups (local only - don't commit to repo)
|
||||||
backups/
|
backups/
|
||||||
|
|
||||||
|
# Remediation-tool cache (live Graph API responses — may contain user data)
|
||||||
|
.cache-remediation/
|
||||||
|
tmp-remediation/
|
||||||
|
|
||||||
# Local settings (machine-specific)
|
# Local settings (machine-specific)
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.claude/identity.json
|
.claude/identity.json
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ These are in-flight and feed the same Business Premium purchase decision:
|
|||||||
|
|
||||||
| AD SamAccountName | M365 UPN | License | Notes |
|
| AD SamAccountName | M365 UPN | License | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| howard | dax.howard@cascadestucson.com | Business Standard | Alias: cara.lespron@ (reused mailbox from former employee) |
|
| *(formerly AD `howard`)* | dax.howard@cascadestucson.com | Business Standard | **Corrected 2026-04-22:** the AD `howard` account was NOT Dax Howard — it was an orphan MSP-created account (display "howard", desc "Home Offie" typo) that was mistakenly mapped to Dax Howard's mailbox. AD account deleted 2026-04-22 (recoverable from AD Recycle Bin 180 days — ObjectGUID 2050d21f-7649-4033-b1fd-83cfc286b056). Dax Howard's M365 account has no AD counterpart and is cloud-only. `cara.lespron@` alias is leftover from the former-employee Cara Lespron whose mailbox was repurposed to Dax Howard — strip this alias unless Dax confirms he still uses it. |
|
||||||
| sysadmin | sysadmin@cascadestucson.com | Power Automate Free | Display: "Computer Guru Support" — no mailbox license |
|
| sysadmin | sysadmin@cascadestucson.com | Power Automate Free | Display: "Computer Guru Support" — no mailbox license |
|
||||||
| Meredith.Kuhn | meredith.kuhn@cascadestucson.com | Business Standard | |
|
| Meredith.Kuhn | meredith.kuhn@cascadestucson.com | Business Standard | |
|
||||||
| John.Trozzi | john.trozzi@cascadestucson.com | Business Standard | |
|
| John.Trozzi | john.trozzi@cascadestucson.com | Business Standard | |
|
||||||
@@ -180,8 +180,8 @@ AD account + Entra sync, no M365 license. Access shared mailboxes via outlook.of
|
|||||||
| a.r.jensen018 | a.r.jensen018@gmail.com | Ashley Jensen's personal? |
|
| a.r.jensen018 | a.r.jensen018@gmail.com | Ashley Jensen's personal? |
|
||||||
| Debora Morris | deboram@teepasnow.com | External partner |
|
| Debora Morris | deboram@teepasnow.com | External partner |
|
||||||
| duprasc2002 | duprasc2002@yahoo.com | Christina DuPras personal? Created 2026-03-04 |
|
| duprasc2002 | duprasc2002@yahoo.com | Christina DuPras personal? Created 2026-03-04 |
|
||||||
| howaed | howaed@azcomputerguru.com | **Typo** of howard — delete |
|
| ~~howaed~~ | ~~howaed@azcomputerguru.com~~ | Typo of howard — already deleted (not present in tenant as of 2026-04-22) |
|
||||||
| howard | howard@azcomputerguru.com | Howard (MSP) external account |
|
| ~~howard~~ | ~~howard@azcomputerguru.com~~ | **DELETED 2026-04-22** — external guest for Howard Enos (MSP). Removed per Howard's decision; MSP admin access preserved via `sysadmin@cascadestucson.com` (has Global Admin). |
|
||||||
| karenrossini7 | karenrossini7@gmail.com | Karen Rossini's personal? |
|
| karenrossini7 | karenrossini7@gmail.com | Karen Rossini's personal? |
|
||||||
|
|
||||||
#### Blocked / former employee accounts in M365
|
#### Blocked / former employee accounts in M365
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
# Entra Connect Install — Risk Register
|
||||||
|
|
||||||
|
**Built from:** G1 AD audit 2026-04-22 (`reports/2026-04-22-g1-ad-audit.md`) + post-reboot readiness check (`reports/2026-04-22-cs-server-entra-readiness-post-reboot.md`) + HIPAA review (`docs/security/hipaa-review-2026-04-22.md`)
|
||||||
|
**Purpose:** Know every possible failure mode before Gate G1 executes. Each risk has: probability, impact, what the audit proved, mitigation, detection, rollback.
|
||||||
|
|
||||||
|
## Executive summary
|
||||||
|
|
||||||
|
The audit turned up **several encouraging confirmations** and a **manageable set of genuine risks**. None are install-blockers. The biggest real concern is password hygiene (Gate G5), which our plan already handles via directory-sync-only initial posture.
|
||||||
|
|
||||||
|
### Already GOOD (no remediation needed)
|
||||||
|
|
||||||
|
- Forest 2016 / Domain 2016 / Schema 88 — supports modern Connect
|
||||||
|
- `cascadestucson.com` UPN suffix already configured forest-wide
|
||||||
|
- AD Recycle Bin enabled (can undo bad renames)
|
||||||
|
- No MSOL_* accounts — clean install target
|
||||||
|
- `dcdiag` silent (all tests pass)
|
||||||
|
- Time sync stratum 2 / time.nist.gov (confirmed post-reboot)
|
||||||
|
- All 3 critical Microsoft sync endpoints resolve
|
||||||
|
- Department OU structure exists (`OU=Departments\OU=Administrative`, etc.)
|
||||||
|
- `OU=ServiceAccounts` exists
|
||||||
|
- `OU=Workstations\OU=Staff PCs` + `OU=Shared PCs` already structured
|
||||||
|
- **UPN prefixes match the M365 format for 95% of users** — the pre-audit-assumed mismatches have already been cleaned up:
|
||||||
|
- `Tamra.Matthews` in AD matches `tamra.matthews@` in M365 (rename already done)
|
||||||
|
- `Shelby.Trozzi` in AD matches `Shelby.Trozzi@` in M365 (rename already done)
|
||||||
|
- `Alyssa.Brooks` in AD matches `alyssa.brooks@` in M365
|
||||||
|
- `Christopher.Holick` in AD matches `christopher.holick@` in M365 (typo fix already done)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk register
|
||||||
|
|
||||||
|
### CRITICAL — must fix before Gate G3 (staging-mode install)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R1. Sync-scope drift: `CN=Users` + role accounts would be synced by default
|
||||||
|
|
||||||
|
**Probability:** Certain | **Impact:** High
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
- **8 accounts in `CN=Users`** (default container, not an OU):
|
||||||
|
`Administrator`, `directoryshare`, `Guest`, `krbtgt`, `localadmin`, `QBDataServiceUser34`, `Receptionist`, `sysadmin`
|
||||||
|
- **4 role-based accounts** in departmental OUs with `@cascades.local` UPN suffix:
|
||||||
|
`Culinary` (in `OU=Culinary`), `Receptionist` (in `CN=Users`), `saleshare` (in `OU=Marketing`), `directoryshare` (in `CN=Users`)
|
||||||
|
|
||||||
|
**Why it matters:** Default Entra Connect scope is "all users". Without filter rules, these synthetic / service / shared-credential accounts sync to Entra and either (a) consume licenses, (b) get blocked by the verified-domain check since `@cascades.local` isn't a tenant domain, or (c) muddy the HIPAA-compliance story with shared-login objects now in the cloud identity plane.
|
||||||
|
|
||||||
|
**Mitigation:**
|
||||||
|
- Configure Entra Connect **OU-scoped filtering**: include only `OU=Departments` (real staff) + optionally `OU=ServiceAccounts`. Exclude `CN=Users` entirely.
|
||||||
|
- **Before install**, create `OU=Excluded-From-Sync,DC=cascades,DC=local` and move the 4 role-based accounts there so they're scoped out cleanly.
|
||||||
|
- Alternative: attribute-based filter on `extensionAttribute1` set to "NoSync" — more flexible long-term.
|
||||||
|
|
||||||
|
**Detection:** Staging-mode preview will list exactly which accounts would sync. Review before exit-staging.
|
||||||
|
|
||||||
|
**Rollback if missed:** Remove from sync scope, re-run sync, Entra deletes the synced objects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R2. 13 enabled users have **null `PasswordLastSet`** — sign-in would fail after PHS turns on
|
||||||
|
|
||||||
|
**Probability:** Certain for these users | **Impact:** High (key staff locked out)
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
|
||||||
|
| SAM | whenCreated | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| Meredith.Kuhn | 2024-08-28 | Executive Director |
|
||||||
|
| John.Trozzi | 2024-08-28 | Facilities Director |
|
||||||
|
| Megan.Hiatt | 2024-08-28 | Sales Director |
|
||||||
|
| Tamra.Matthews | 2024-08-28 | Move-In Coordinator |
|
||||||
|
| Christine.Nyanzunda | 2024-08-28 | MC Admin / MedTech |
|
||||||
|
| Ashley.Jensen | 2024-08-28 | Assistant Executive Director |
|
||||||
|
| Veronica.Feller | 2024-08-28 | AL Aide |
|
||||||
|
| Sebastian.Leon | 2024-08-28 | Courtesy Patrol |
|
||||||
|
| JD.Martin | 2024-08-28 | Culinary Director |
|
||||||
|
| Matt.Brooks | 2024-08-28 | MC Receptionist / Maintenance |
|
||||||
|
| Ramon.Castaneda | 2024-08-28 | Kitchen Manager |
|
||||||
|
| Michelle.Shestko | 2024-08-28 | MC Receptionist |
|
||||||
|
| britney.thompson | 2025-06-12 | *(departed 2026-04-22)* |
|
||||||
|
|
||||||
|
All have `PasswordLastSet = NULL` — AD account created but never had a password set. Last-logon is also `never` for all of them. They've never signed into Windows; they only use M365 with cloud-managed passwords.
|
||||||
|
|
||||||
|
**Why it matters:** If we enable **Password Hash Sync (Gate G5)**, Entra Connect typically pushes the AD hash to overwrite the cloud password. For null-hash users, Microsoft's behavior:
|
||||||
|
- Password Hash Sync **does not** overwrite cloud password with "empty" — the cloud password persists.
|
||||||
|
- BUT: these users have no AD password, so Windows sign-in never worked for them anyway — not a change.
|
||||||
|
- The real risk: if we *ever* enable "hybrid" features (password writeback, etc.) and later set a placeholder AD password, we'd silently break their cloud sign-in.
|
||||||
|
|
||||||
|
**Mitigation (staged):**
|
||||||
|
1. **Gate G4 (directory sync only, no PHS)** — safe for these users. They keep signing into M365 with their current cloud password.
|
||||||
|
2. **Before Gate G5 (PHS enablement)** — decision point per user:
|
||||||
|
- (a) Set real AD password, tell user "from now on your M365 password is [X]" — but MOST won't want to change their M365 password. Disruptive.
|
||||||
|
- (b) Leave null, don't enable PHS for them — requires excluding them from PHS scope (per-user filter).
|
||||||
|
- (c) **Preferred: don't enable PHS at all; stay on directory-sync-only permanently.** Users keep their cloud passwords; their AD accounts are just there for group membership + OU governance. This trades the "single password" benefit for zero risk to currently-working sign-in.
|
||||||
|
|
||||||
|
**Detection:** Track sign-in failures per user in Entra sign-in logs after any PHS change.
|
||||||
|
|
||||||
|
**Rollback:** Disable PHS in Entra Connect — cloud passwords stop being overwritten on next sync.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R3. Matt.Brooks UPN mismatch — AD `Matt.Brooks` vs M365 `matthew.brooks@`
|
||||||
|
|
||||||
|
**Probability:** Certain | **Impact:** Medium (one duplicate user)
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
- AD: `SAM=Matt.Brooks`, `UPN=Matt.Brooks@cascadestucson.com`
|
||||||
|
- M365 (per `docs/cloud/m365.md` line 59): `matthew.brooks@cascadestucson.com`
|
||||||
|
|
||||||
|
**Why it matters:** Soft-match uses UPN + proxyAddresses. AD UPN `Matt.Brooks@` doesn't match M365 UPN `matthew.brooks@`. With no `proxyAddresses` on the AD user (see R4), the engine has no fallback. Will likely **create a duplicate Entra user** for Matt.
|
||||||
|
|
||||||
|
**Mitigation (pick one, before staging):**
|
||||||
|
- (a) Change AD UPN from `Matt.Brooks@` → `matthew.brooks@` (preferred — keeps M365 mailbox stable)
|
||||||
|
- (b) Change M365 UPN from `matthew.brooks@` → `matt.brooks@` (disrupts Matt's cached Outlook / Teams sessions, one-time prompt)
|
||||||
|
|
||||||
|
**Detection:** Staging-mode preview report will flag this as "would create new user" instead of "would match existing".
|
||||||
|
|
||||||
|
**Rollback:** If we miss it and dupe gets created, use Entra portal to soft-delete the duplicate + hard-match manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R4. No users have `mail` or `proxyAddresses` populated in AD
|
||||||
|
|
||||||
|
**Probability:** Certain (all 42 enabled users affected) | **Impact:** Medium (single point of failure for soft-match)
|
||||||
|
|
||||||
|
**What the audit showed:** **Every single AD user** has empty `mail` and empty `proxyAddresses`. Soft-match relies on:
|
||||||
|
1. `proxyAddresses` (SMTP: prefix) — primary
|
||||||
|
2. UPN — fallback
|
||||||
|
|
||||||
|
With no proxyAddresses, we rely on UPN matching, which is case-insensitive but format-exact. Anything off by even a character creates a duplicate.
|
||||||
|
|
||||||
|
**Why it matters:** Combined with R3 (Matt's UPN mismatch), we're one typo away from duplicates. Also, when M365 mailboxes have aliases (e.g., `cara.lespron@` on Howard's mailbox, `tamra.johnson@` alias on Tamra's, `ashley.jenson@` typo alias on Ashley's), those ONLY match via proxyAddresses — not UPN. The aliases would orphan.
|
||||||
|
|
||||||
|
**Mitigation:**
|
||||||
|
- **Before staging install**, script a bulk update to populate each AD user's `proxyAddresses` from their M365 SMTP addresses. Takes ~30 min to write + run. See G1 remediation script (draft pending).
|
||||||
|
- `mail` attribute can be set to the primary SMTP — feeds Outlook profile defaults.
|
||||||
|
|
||||||
|
**Detection:** Staging preview shows match source per user (UPN vs SMTP). Review before exit.
|
||||||
|
|
||||||
|
**Rollback:** AD attribute changes are reversible via another script.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HIGH — should fix before Gate G3, or early during G3 review
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R5. Missing security groups (all 16 `SG-*` groups our CA design assumes)
|
||||||
|
|
||||||
|
**Probability:** Certain | **Impact:** High (CA policies can't target non-existent groups)
|
||||||
|
|
||||||
|
**What the audit showed:** Zero of the 16 planned `SG-*` groups exist in AD:
|
||||||
|
`SG-External-Signin-Allowed`, `SG-Caregivers`, `SG-FrontDesk`, `SG-CourtesyPatrol`, `SG-Drivers`, `SG-Management-RW`, `SG-Sales-RW`, `SG-Culinary-RW`, `SG-IT-RW`, `SG-Receptionist-RW`, `SG-Directory-RW`, `SG-Server-RW`, `SG-Chat-RW`, `SG-Office-PHI-External`, `SG-Office-PHI-Internal`, `SG-CA-BreakGlass`.
|
||||||
|
|
||||||
|
**Why it matters:** Gate G6 (CA Report-Only) needs these groups to exist and be populated before policies can target them. Can't target empty groups.
|
||||||
|
|
||||||
|
**Mitigation:** Script creation of all 16 groups under `OU=Groups,DC=cascades,DC=local` (already exists — audit shows 1 group in it) BEFORE Gate G6. Populate membership from the rollout plan's persona tables.
|
||||||
|
|
||||||
|
**Detection:** CA policy assignment wizard in Entra portal will show "group not found" if we target before creating.
|
||||||
|
|
||||||
|
**Rollback:** `Remove-ADGroup` — safe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R6. M365-side orphan accounts need cleanup before sync
|
||||||
|
|
||||||
|
**Probability:** Certain | **Impact:** Medium (post-sync confusion)
|
||||||
|
|
||||||
|
**Accounts to resolve (per `docs/cloud/m365.md`):**
|
||||||
|
|
||||||
|
| M365 UPN | Status | Action before sync |
|
||||||
|
|---|---|---|
|
||||||
|
| `kristiana.dowse@cascadestucson.com` | HR confirmed not current employee | Delete from M365 |
|
||||||
|
| `howaed@azcomputerguru.com` | Typo duplicate of `howard@` | Delete from M365 |
|
||||||
|
| `nick.pavloff@cascadestucson.com` | Created 2026-03-07, no AD account | **Decide: create AD account or delete M365 account.** If kept cloud-only, exclude from sync-expected list. |
|
||||||
|
| `anna.pitzlin@cascadestucson.com` | Former employee, forwarded to Meredith | Already blocked — delete if HR confirms |
|
||||||
|
| `nela.durut-azizi@cascadestucson.com` | Former employee | Already blocked — delete if HR confirms |
|
||||||
|
| `jeff.bristol@cascadestucson.com` | Former employee | Already blocked — decision to delete vs keep for mail history |
|
||||||
|
| `stephanie.devin@cascadestucson.com` | Former? | Verify with Meredith |
|
||||||
|
| `admin@NETORGFT4257522.onmicrosoft.com` | Sandra Fish (blocked 2026-04-14) | Delete |
|
||||||
|
|
||||||
|
**Why it matters:** Post-sync, mismatched AD-vs-M365 state creates orphan objects that muddy audit logs and risk accidental reactivation.
|
||||||
|
|
||||||
|
**Mitigation:** Clean up M365 side in Gate G2 (role-account-to-shared-mailbox conversion phase). Delete / block / convert each per the table above.
|
||||||
|
|
||||||
|
**Detection:** M365 admin portal user list before/after. Entra staging preview will flag M365 users with no AD counterpart.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R7. Role-based account UPNs use unverified domain `@cascades.local`
|
||||||
|
|
||||||
|
**Probability:** Certain | **Impact:** Low (these shouldn't sync anyway)
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
- `Culinary@cascades.local`
|
||||||
|
- `Receptionist@cascades.local`
|
||||||
|
- `directoryshare@cascades.local`
|
||||||
|
|
||||||
|
**Why it matters:** If these sync, Entra tries to create users with UPNs in a domain that isn't verified on the tenant. Sync will fail for these objects specifically with a clear error. Not a tenant-wide failure.
|
||||||
|
|
||||||
|
**Mitigation:** These accounts are explicitly excluded by R1's OU scoping. Confirmed via dual filter.
|
||||||
|
|
||||||
|
**Detection:** Staging preview shows sync-blocked objects.
|
||||||
|
|
||||||
|
**Rollback:** N/A — sync failure is self-protecting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R8. No Entra Connect service account exists yet
|
||||||
|
|
||||||
|
**Probability:** N/A — create at install | **Impact:** High (blocks install)
|
||||||
|
|
||||||
|
**What the audit showed:** Nothing in `OU=ServiceAccounts` matches "MSOL_*", "AADSync*", or similar patterns. The only service account there is `svc-audit-upload` (Syncro-specific).
|
||||||
|
|
||||||
|
**Why it matters:** Entra Connect install creates an MSOL_* AD account automatically during install. But best practice: pre-provision a dedicated service account in `OU=ServiceAccounts` with explicit delegated permissions (AD replication rights + BUILTIN\Users read).
|
||||||
|
|
||||||
|
**Mitigation:** Installer will create `MSOL_<hex>` automatically on install — acceptable. Alternative: pre-create `svc-entra-connect` with delegated rights using Microsoft's `Set-ADSyncPermissions` cmdlet.
|
||||||
|
|
||||||
|
**Detection:** Post-install, verify the service account appears in AD and has correct delegated rights.
|
||||||
|
|
||||||
|
**Rollback:** Uninstall Connect removes the auto-created service account.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R9. `krbtgt` password is 602 days old
|
||||||
|
|
||||||
|
**Probability:** Not a Connect issue | **Impact:** Low (for Connect), High (for AD security)
|
||||||
|
|
||||||
|
**What the audit showed:** `krbtgt` password last set 2024-08-28, age 602 days.
|
||||||
|
|
||||||
|
**Why it matters:** Not an Entra Connect concern. But this is a pre-existing HIPAA / AD security finding (audit gap #20 in hipaa.md). Standard best practice is 180-day rotation.
|
||||||
|
|
||||||
|
**Mitigation:** Run `Reset-KrbTgt-Password.ps1` script (MS-published) — two iterations ~24 hours apart. Do this OUTSIDE the Entra Connect window to avoid changing two things at once.
|
||||||
|
|
||||||
|
**Detection:** Event Log shows TGT-related errors if done wrong.
|
||||||
|
|
||||||
|
**Rollback:** Can't rollback a krbtgt reset, but two rotations clear any old TGTs cleanly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MEDIUM — can fix during or after G4 (directory sync active)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R10. DisplayName inconsistencies (will propagate to Entra)
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
- `Crystal.Rodriguez` display name = `'Crystal Rodriguez'` (two spaces)
|
||||||
|
- `howard` display name = `'howard'` (should be `Howard Dax` per GivenName+Surname)
|
||||||
|
- `Cathy.Kingston` display name = `'Cathy.Kingston'` (SAM-like, should be `Cathy Kingston`)
|
||||||
|
|
||||||
|
**Why it matters:** These propagate to Entra / M365 directory, address book, Teams people cards. Cosmetic but visible.
|
||||||
|
|
||||||
|
**Mitigation:** Fix during Gate G4 or post-sync — reversible. Set correct DisplayName on each.
|
||||||
|
|
||||||
|
**Detection:** M365 People / address book reflects the bad names.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R11. 10 enabled accounts with `PasswordNeverExpires=True`
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
|
||||||
|
| SAM | Risk | Action |
|
||||||
|
|---|---|---|
|
||||||
|
| Administrator | Normal for built-in | Leave |
|
||||||
|
| localadmin | Normal for emergency local | Leave |
|
||||||
|
| sysadmin | Normal for MSP service | Leave |
|
||||||
|
| QBDataServiceUser34 | Service account | Leave (won't sync) |
|
||||||
|
| howard | Should rotate | Address separately |
|
||||||
|
| Culinary | Should be Phase 5 cleaned up | Leave, out of scope |
|
||||||
|
| Receptionist | Same | Leave, out of scope |
|
||||||
|
| directoryshare | Same | Leave, out of scope |
|
||||||
|
| Lois.Lane | Exception on purpose? | **Investigate** |
|
||||||
|
| Shelby.Trozzi | Exception on purpose? | **Investigate** |
|
||||||
|
| svc-audit-upload | Service account | Correct |
|
||||||
|
|
||||||
|
**Why it matters:** Not a Connect blocker, but HIPAA password-policy audit finding. Lois + Shelby flagged to rotate.
|
||||||
|
|
||||||
|
**Mitigation:** Post-install, enforce AD password policy + flip their `PasswordNeverExpires` off.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R12. 28 staff users show `lastLogon = never`
|
||||||
|
|
||||||
|
**What the audit showed:** 28 enabled accounts have no recorded logon history — they've never signed into Windows. Consistent with the null-PasswordLastSet finding — these are cloud-primary users who've never touched a domain-joined PC.
|
||||||
|
|
||||||
|
**Why it matters:** Entra Connect doesn't care. Just confirming this is consistent with our mental model: Cascades has long operated as "AD for mail + group membership" but users actually authenticate to M365 cloud-only. Not broken, just noteworthy.
|
||||||
|
|
||||||
|
**Mitigation:** None needed. Our Gate G4 directory-sync-only posture accepts this reality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R13. 8 domain computers — known set, no surprises
|
||||||
|
|
||||||
|
**What the audit showed:**
|
||||||
|
|
||||||
|
| Computer | OS | Last Logon |
|
||||||
|
|---|---|---|
|
||||||
|
| CS-SERVER | Server 2019 | 2026-04-22 |
|
||||||
|
| ACCT2-PC | Win 11 Pro WS | 2026-04-22 |
|
||||||
|
| CRYSTAL-PC | Win 11 Pro | 2026-04-16 |
|
||||||
|
| DESKTOP-DLTAGOI | Win 11 Pro WS | 2026-04-13 (Sharon Edwards, our FR pilot) |
|
||||||
|
| DESKTOP-H6QHRR7 | Win 11 Pro WS | 2026-04-13 |
|
||||||
|
| DESKTOP-ROK7VNM | Win 11 Pro WS | 2026-04-13 (Susan Hicks) |
|
||||||
|
| CS-QB | Win 10 Pro | 2026-04-16 (VoIP VM on CS-SERVER) |
|
||||||
|
| DESKTOP-1ISF081 | Win 10 Pro | 2025-03-22 (stale — 13 months no logon) |
|
||||||
|
|
||||||
|
**Why it matters:** Sync default includes computer accounts. For hybrid join, that's desired. For our scope (just user identity), exclude computers from sync.
|
||||||
|
|
||||||
|
**Mitigation:** Set Entra Connect sync-scope filter to `Users` + `Groups` only initially. Add computers later when hybrid join is a real goal.
|
||||||
|
|
||||||
|
**Action item:** Investigate `DESKTOP-1ISF081` — 13 months no logon, likely retired. Disable if confirmed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### LOW — track but not blocking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R14. 9 deleted objects in AD Recycle Bin from prior cleanup
|
||||||
|
|
||||||
|
All former employees. Recycle Bin keeps them 180 days for recovery. Not a Connect concern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R15. Disabled accounts that still exist
|
||||||
|
|
||||||
|
Only 2 per audit: `Guest` (built-in) and `krbtgt` (disabled/system). Both excluded from sync automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### R16. Missing mail infrastructure attributes
|
||||||
|
|
||||||
|
**What the audit showed:** No users have `msExchMailboxGuid`, `msExchRecipientDisplayType`, or other Exchange schema attributes populated in AD.
|
||||||
|
|
||||||
|
**Why it matters:** Would matter for Exchange Hybrid (on-prem Exchange coexistence). Not relevant for cloud-only Exchange Online.
|
||||||
|
|
||||||
|
**Mitigation:** None needed for our scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install-day go/no-go checklist
|
||||||
|
|
||||||
|
All must be satisfied before exiting Gate G3 (staging mode):
|
||||||
|
|
||||||
|
- [ ] `OU=Excluded-From-Sync` created; role accounts moved there (R1)
|
||||||
|
- [ ] `proxyAddresses` populated for all real staff from M365 SMTP (R4)
|
||||||
|
- [ ] Matt.Brooks UPN unified between AD and M365 (R3)
|
||||||
|
- [ ] M365 orphans resolved: Kristiana, howaed, Sandra Fish admin, nick.pavloff (R6)
|
||||||
|
- [ ] Role-based M365 accounts converted to shared mailboxes (accounting@, frontdesk@, etc.) (Gate G2)
|
||||||
|
- [ ] Sync-scope filter set to `OU=Departments` only, user+group objects only (R1, R13)
|
||||||
|
- [ ] 16 `SG-*` security groups pre-created and populated (R5)
|
||||||
|
- [ ] Break-glass admin account created + vaulted (independent of Connect, but gate G7 needs it)
|
||||||
|
- [ ] Microsoft BAA signed (Wave 0 — independent of Connect but HIPAA-critical)
|
||||||
|
|
||||||
|
## Items that can wait until after G4
|
||||||
|
|
||||||
|
- Fix DisplayName inconsistencies (R10)
|
||||||
|
- Rotate krbtgt (R9) — schedule OUTSIDE the Connect window
|
||||||
|
- Clean up PasswordNeverExpires exceptions on Lois + Shelby (R11)
|
||||||
|
- Disable DESKTOP-1ISF081 stale computer account (R13)
|
||||||
|
|
||||||
|
## Rollback summary per gate
|
||||||
|
|
||||||
|
| Gate | Rollback |
|
||||||
|
|---|---|
|
||||||
|
| G1 | AD changes (renames, attribute populates) reversible via recorded before-state |
|
||||||
|
| G2 | Shared-mailbox conversion reversible (undo from Exchange Admin) |
|
||||||
|
| G3 | Uninstall Connect; tenant stays unchanged (staging mode never wrote) |
|
||||||
|
| G4 | Uninstall Connect; run `Set-MsolDirSyncEnabled -EnableDirSync $false` to stop tenant-side sync (takes ~72h to fully disable) |
|
||||||
|
| G5 | Disable PHS via Entra Connect config; cloud passwords stop being overwritten |
|
||||||
|
| G6 | Delete CA policies; no sign-ins were being blocked anyway |
|
||||||
|
| G7 | Flip each CA policy back to Report-only |
|
||||||
|
| G8 | Remove ALIS Enterprise App registration; ALIS users revert to local credentials |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Full G1 audit source data: `reports/2026-04-22-g1-ad-audit.md`*
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Remove AD `howard` account (misspelled/orphan account, not used by anyone).
|
||||||
|
# Captures pre-state to D:\Backups and confirms removal. AD Recycle Bin keeps
|
||||||
|
# the object for 180 days so Restore-ADObject is available if needed.
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
Import-Module ActiveDirectory
|
||||||
|
|
||||||
|
$ts = Get-Date -Format 'yyyy-MM-dd-HHmmss'
|
||||||
|
$bd = "D:\Backups\howard-delete-$ts"
|
||||||
|
New-Item -Path $bd -ItemType Directory -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
$u = Get-ADUser -Identity howard -Properties *
|
||||||
|
Write-Output 'Pre-delete state:'
|
||||||
|
Write-Output " SAM: $($u.SamAccountName)"
|
||||||
|
Write-Output " UPN: $($u.UserPrincipalName)"
|
||||||
|
Write-Output " Display: $($u.DisplayName)"
|
||||||
|
Write-Output " Description: $($u.Description)"
|
||||||
|
Write-Output " mail: $($u.mail)"
|
||||||
|
Write-Output " proxyAddrs: $(($u.proxyAddresses) -join '; ')"
|
||||||
|
Write-Output " DN: $($u.DistinguishedName)"
|
||||||
|
Write-Output " Enabled: $($u.Enabled)"
|
||||||
|
Write-Output " PwdLastSet: $($u.PasswordLastSet)"
|
||||||
|
Write-Output " Created: $($u.whenCreated)"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output ' Group memberships:'
|
||||||
|
Get-ADPrincipalGroupMembership -Identity howard | ForEach-Object {
|
||||||
|
Write-Output " - $($_.Name)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$u | Export-Clixml "$bd\howard-pre.xml"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output "Pre-state exported to: $bd\howard-pre.xml"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Removing AD user howard...'
|
||||||
|
Remove-ADUser -Identity howard -Confirm:$false
|
||||||
|
Write-Output '[OK] Remove-ADUser returned without error.'
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Verifying removal:'
|
||||||
|
try {
|
||||||
|
Get-ADUser -Identity howard -ErrorAction Stop | Out-Null
|
||||||
|
Write-Output '[FAIL] Account still exists'
|
||||||
|
exit 1
|
||||||
|
} catch {
|
||||||
|
Write-Output "[OK] Get-ADUser -Identity howard returns: $($_.Exception.Message.Split([char]10)[0])"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Recycle Bin (180 day retention) entry for rollback:'
|
||||||
|
$deleted = Get-ADObject -Filter { SamAccountName -eq 'howard' } -IncludeDeletedObjects -Properties whenChanged, isDeleted, ObjectGUID, lastKnownParent
|
||||||
|
$deleted | Select-Object Name, ObjectGUID, isDeleted, whenChanged, lastKnownParent | Format-List | Out-String | Write-Output
|
||||||
|
Write-Output 'Rollback command (within 180 days):'
|
||||||
|
if ($deleted) {
|
||||||
|
$guid = $deleted | Select-Object -First 1 -ExpandProperty ObjectGUID
|
||||||
|
Write-Output " Restore-ADObject -Identity $guid"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Output "FAIL: $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Entra Connect G1 Pre-flight AD Audit - CS-SERVER
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Purpose: collect everything needed to assess Entra Connect sync risk before
|
||||||
|
# install. Strictly READ-ONLY. No writes, no renames, no state changes.
|
||||||
|
#
|
||||||
|
# Maps to Wave 0.5 Gate G1 in docs/cloud/user-account-rollout-plan.md.
|
||||||
|
# Output is analyzed against the risk register at
|
||||||
|
# docs/migration/entra-connect-risk-register-2026-04-22.md.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Import-Module ActiveDirectory -ErrorAction Stop
|
||||||
|
|
||||||
|
function Section($n) {
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output ('=' * 76)
|
||||||
|
Write-Output "== $n"
|
||||||
|
Write-Output ('=' * 76)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Note($m) { Write-Output " $m" }
|
||||||
|
|
||||||
|
Write-Output "G1 AD Audit - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
|
||||||
|
Write-Output "Host: $env:COMPUTERNAME"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '1. Forest / Domain / Schema'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$forest = Get-ADForest
|
||||||
|
$domain = Get-ADDomain
|
||||||
|
Write-Output "Forest FQDN: $($forest.Name)"
|
||||||
|
Write-Output "Forest mode: $($forest.ForestMode)"
|
||||||
|
Write-Output "Domain mode: $($domain.DomainMode)"
|
||||||
|
Write-Output "Domain DN: $($domain.DistinguishedName)"
|
||||||
|
Write-Output "NetBIOS: $($domain.NetBIOSName)"
|
||||||
|
Write-Output "UPN suffixes (forest):"
|
||||||
|
foreach ($s in $forest.UPNSuffixes) { Write-Output " - $s" }
|
||||||
|
if ($forest.UPNSuffixes.Count -eq 0) { Write-Output ' (none configured beyond default cascades.local)' }
|
||||||
|
|
||||||
|
$schema = Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion
|
||||||
|
Write-Output "Schema objectVersion: $($schema.objectVersion)"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'FSMO roles:'
|
||||||
|
Write-Output " Schema Master: $($forest.SchemaMaster)"
|
||||||
|
Write-Output " Domain Naming Master: $($forest.DomainNamingMaster)"
|
||||||
|
Write-Output " PDC Emulator: $($domain.PDCEmulator)"
|
||||||
|
Write-Output " RID Master: $($domain.RIDMaster)"
|
||||||
|
Write-Output " Infrastructure Master: $($domain.InfrastructureMaster)"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '2. OU structure'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$ous = Get-ADOrganizationalUnit -Filter *
|
||||||
|
Write-Output "Total OUs: $($ous.Count)"
|
||||||
|
Write-Output ''
|
||||||
|
foreach ($ou in $ous | Sort-Object DistinguishedName) {
|
||||||
|
$uc = (Get-ADUser -Filter * -SearchBase $ou.DistinguishedName -SearchScope OneLevel | Measure-Object).Count
|
||||||
|
$gc = (Get-ADGroup -Filter * -SearchBase $ou.DistinguishedName -SearchScope OneLevel | Measure-Object).Count
|
||||||
|
$cc = (Get-ADComputer -Filter * -SearchBase $ou.DistinguishedName -SearchScope OneLevel | Measure-Object).Count
|
||||||
|
Write-Output (" {0,-80} users={1} groups={2} computers={3}" -f $ou.DistinguishedName, $uc, $gc, $cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Users in CN=Users (default container, not an OU)
|
||||||
|
$defaultUsersDN = "CN=Users,$($domain.DistinguishedName)"
|
||||||
|
$usersInDefault = Get-ADUser -Filter * -SearchBase $defaultUsersDN -SearchScope OneLevel
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output "Users in default CN=Users container (not ideal — should be in an OU for sync scope):"
|
||||||
|
Write-Output " Count: $($usersInDefault.Count)"
|
||||||
|
foreach ($u in $usersInDefault) {
|
||||||
|
Write-Output " - $($u.SamAccountName) ($($u.Name))"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '3. All AD users - identity attributes for sync match'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$users = Get-ADUser -Filter * -Properties `
|
||||||
|
SamAccountName, UserPrincipalName, DisplayName, GivenName, Surname, `
|
||||||
|
Mail, proxyAddresses, Enabled, PasswordLastSet, PasswordNeverExpires, `
|
||||||
|
PasswordNotRequired, LockedOut, whenCreated, whenChanged, `
|
||||||
|
Department, Title, Office, Description, lastLogonTimestamp, `
|
||||||
|
mS-DS-ConsistencyGuid, objectGUID, DistinguishedName
|
||||||
|
|
||||||
|
Write-Output "Total users: $($users.Count)"
|
||||||
|
Write-Output "Enabled: $($users | Where-Object Enabled -eq $true | Measure-Object | Select-Object -ExpandProperty Count)"
|
||||||
|
Write-Output "Disabled: $($users | Where-Object Enabled -eq $false | Measure-Object | Select-Object -ExpandProperty Count)"
|
||||||
|
Write-Output ''
|
||||||
|
|
||||||
|
Write-Output 'Per-user identity dump (focus: anything that affects soft-match):'
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'SAM | UPN | Mail | ProxyAddrs | PwdLastSet | PwdNeverExp | Enabled | OU'
|
||||||
|
Write-Output '---'
|
||||||
|
foreach ($u in $users | Sort-Object SamAccountName) {
|
||||||
|
$ou = $u.DistinguishedName -replace '^CN=[^,]+,', ''
|
||||||
|
$ou = $ou -replace ",DC=cascades,DC=local$", ''
|
||||||
|
$px = if ($u.proxyAddresses) { ($u.proxyAddresses -join ';') } else { '' }
|
||||||
|
$pls = if ($u.PasswordLastSet) { $u.PasswordLastSet.ToString('yyyy-MM-dd') } else { 'NULL' }
|
||||||
|
Write-Output ("{0} | {1} | {2} | {3} | {4} | {5} | {6} | {7}" -f `
|
||||||
|
$u.SamAccountName, $u.UserPrincipalName, $u.Mail, $px, $pls, $u.PasswordNeverExpires, $u.Enabled, $ou)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '4. Soft-match risk scan - accounts likely to duplicate in Entra'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Write-Output '--- Users with NO proxyAddresses and NO mail (soft-match engine has nothing to work with):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if (-not $u.Mail -and -not $u.proxyAddresses -and $u.Enabled) {
|
||||||
|
Write-Output " - $($u.SamAccountName) (UPN: $($u.UserPrincipalName))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Users whose UPN suffix is NOT cascadestucson.com (will mismatch M365 target unless renamed):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.Enabled -and $u.UserPrincipalName -and $u.UserPrincipalName -notmatch '@cascadestucson\.com$') {
|
||||||
|
Write-Output " - $($u.SamAccountName) UPN=$($u.UserPrincipalName)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Users whose SAM does not match their UPN prefix (name mismatch candidates):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.Enabled -and $u.UserPrincipalName) {
|
||||||
|
$upnPrefix = $u.UserPrincipalName -replace '@.*$', ''
|
||||||
|
if ($u.SamAccountName -ne $upnPrefix) {
|
||||||
|
Write-Output " - SAM='$($u.SamAccountName)' UPN='$($u.UserPrincipalName)'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Users with DisplayName different from Given+Surname (may cause display oddities post-sync):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.Enabled -and $u.GivenName -and $u.Surname) {
|
||||||
|
$expected = "$($u.GivenName) $($u.Surname)"
|
||||||
|
if ($u.DisplayName -and $u.DisplayName -ne $expected) {
|
||||||
|
Write-Output " - SAM=$($u.SamAccountName) display='$($u.DisplayName)' expected='$expected'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '5. Password hygiene (affects whether sync-derived sign-in will work)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Write-Output '--- Enabled users with null PasswordLastSet (never set a password):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.Enabled -and -not $u.PasswordLastSet) {
|
||||||
|
Write-Output " - $($u.SamAccountName) whenCreated=$($u.whenCreated.ToString('yyyy-MM-dd'))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Users with PasswordNotRequired=True:'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.PasswordNotRequired) {
|
||||||
|
Write-Output " - $($u.SamAccountName) enabled=$($u.Enabled)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Users with PasswordNeverExpires=True:'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.PasswordNeverExpires -and $u.Enabled) {
|
||||||
|
Write-Output " - $($u.SamAccountName) PwdLastSet=$($u.PasswordLastSet) Description='$($u.Description)'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output '--- Currently locked-out accounts:'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.LockedOut) {
|
||||||
|
Write-Output " - $($u.SamAccountName) enabled=$($u.Enabled)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$krbtgt = Get-ADUser krbtgt -Properties PasswordLastSet
|
||||||
|
$krbtgtAge = if ($krbtgt.PasswordLastSet) { ((Get-Date) - $krbtgt.PasswordLastSet).Days } else { '?' }
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output "krbtgt password last set: $($krbtgt.PasswordLastSet) (age: $krbtgtAge days)"
|
||||||
|
Write-Output ' (best practice: rotate every 180 days; > 180 is a known audit finding)'
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '6. Role-based / shared accounts (should NOT sync)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$roleLike = @('Receptionist','receptionist','Culinary','saleshare','directoryshare','Front Desk',
|
||||||
|
'frontdesk','Accounting','accounting','Nurse','nurse','mcnurse','memcarenurse',
|
||||||
|
'Security','security','HR','hr','medtech','Memcare')
|
||||||
|
Write-Output 'Accounts whose SamAccountName looks role-based (should be EXCLUDED from sync scope):'
|
||||||
|
foreach ($u in $users) {
|
||||||
|
foreach ($pattern in $roleLike) {
|
||||||
|
if ($u.SamAccountName -eq $pattern -or $u.SamAccountName -ieq $pattern) {
|
||||||
|
Write-Output " - $($u.SamAccountName) enabled=$($u.Enabled) OU=$($u.DistinguishedName)"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Service / built-in accounts (context - usually excluded from sync):'
|
||||||
|
$builtin = @('Administrator','krbtgt','Guest','localadmin','sysadmin','QBDataServiceUser34','howard')
|
||||||
|
foreach ($u in $users) {
|
||||||
|
if ($u.SamAccountName -in $builtin) {
|
||||||
|
Write-Output " - $($u.SamAccountName) enabled=$($u.Enabled)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '7. Likely-departed accounts still enabled (HIPAA termination risk)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Accounts enabled but no logon activity in >= 90 days are candidate departures
|
||||||
|
$cutoff = (Get-Date).AddDays(-90)
|
||||||
|
Write-Output "Enabled accounts with no logon activity in 90+ days:"
|
||||||
|
foreach ($u in $users | Sort-Object lastLogonTimestamp) {
|
||||||
|
if ($u.Enabled -and $u.SamAccountName -notin $builtin) {
|
||||||
|
$ll = if ($u.lastLogonTimestamp) { [DateTime]::FromFileTime($u.lastLogonTimestamp) } else { $null }
|
||||||
|
if (-not $ll -or $ll -lt $cutoff) {
|
||||||
|
$llStr = if ($ll) { $ll.ToString('yyyy-MM-dd') } else { 'never' }
|
||||||
|
Write-Output " - $($u.SamAccountName) lastLogon=$llStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '8. AD groups inventory'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$groups = Get-ADGroup -Filter * -Properties Members,Description,whenCreated,GroupScope,GroupCategory
|
||||||
|
Write-Output "Total groups: $($groups.Count)"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Group name | Scope | Category | Members | Created | Description'
|
||||||
|
Write-Output '---'
|
||||||
|
foreach ($g in $groups | Sort-Object Name) {
|
||||||
|
$mc = if ($g.Members) { $g.Members.Count } else { 0 }
|
||||||
|
Write-Output ("{0} | {1} | {2} | {3} | {4} | {5}" -f `
|
||||||
|
$g.SamAccountName, $g.GroupScope, $g.GroupCategory, $mc, `
|
||||||
|
$g.whenCreated.ToString('yyyy-MM-dd'), ($g.Description -replace '\r?\n',' '))
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Security groups our rollout plan assumes (checking existence):'
|
||||||
|
$needed = @(
|
||||||
|
'SG-External-Signin-Allowed','SG-Caregivers','SG-FrontDesk','SG-CourtesyPatrol',
|
||||||
|
'SG-Drivers','SG-Management-RW','SG-Sales-RW','SG-Culinary-RW','SG-IT-RW',
|
||||||
|
'SG-Receptionist-RW','SG-Directory-RW','SG-Server-RW','SG-Chat-RW','SG-Office-PHI-External',
|
||||||
|
'SG-Office-PHI-Internal','SG-CA-BreakGlass'
|
||||||
|
)
|
||||||
|
foreach ($n in $needed) {
|
||||||
|
try {
|
||||||
|
$g = Get-ADGroup -Identity $n -ErrorAction Stop
|
||||||
|
Write-Output " [EXISTS] $n"
|
||||||
|
} catch {
|
||||||
|
Write-Output " [MISSING] $n (needs creation)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '9. Computers (for sync scope decision)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$computers = Get-ADComputer -Filter * -Properties OperatingSystem,Enabled,lastLogonTimestamp
|
||||||
|
Write-Output "Total computer accounts: $($computers.Count)"
|
||||||
|
Write-Output "Enabled: $(($computers | Where-Object Enabled -eq $true).Count)"
|
||||||
|
Write-Output "Disabled: $(($computers | Where-Object Enabled -eq $false).Count)"
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output 'Computer | OS | Enabled | LastLogon'
|
||||||
|
Write-Output '---'
|
||||||
|
foreach ($c in $computers | Sort-Object Name) {
|
||||||
|
$ll = if ($c.lastLogonTimestamp) { [DateTime]::FromFileTime($c.lastLogonTimestamp).ToString('yyyy-MM-dd') } else { 'never' }
|
||||||
|
Write-Output ("{0} | {1} | {2} | {3}" -f $c.Name, $c.OperatingSystem, $c.Enabled, $ll)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '10. Existing Entra Connect / MSOL account check'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Entra Connect creates MSOL_<hex> service accounts in AD. If any exist from a
|
||||||
|
# prior install attempt, soft-match will behave unpredictably.
|
||||||
|
$msol = Get-ADUser -Filter 'SamAccountName -like "MSOL_*"' -ErrorAction SilentlyContinue
|
||||||
|
if ($msol) {
|
||||||
|
Write-Output 'EXISTING MSOL accounts found (indicates prior Connect attempt or residue):'
|
||||||
|
foreach ($m in $msol) {
|
||||||
|
Write-Output " - $($m.SamAccountName) enabled=$($m.Enabled)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Output '[OK] No MSOL_* accounts in AD (clean install target)'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '11. Recycle Bin + deleted objects'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$rb = Get-ADOptionalFeature 'Recycle Bin Feature'
|
||||||
|
Write-Output "AD Recycle Bin enabled: $([bool]$rb.EnabledScopes)"
|
||||||
|
|
||||||
|
$deleted = Get-ADObject -Filter 'isDeleted -eq $true' -IncludeDeletedObjects -ResultSetSize 10 -Properties whenCreated,lastKnownParent
|
||||||
|
if ($deleted) {
|
||||||
|
Write-Output "Deleted objects (sample of 10):"
|
||||||
|
foreach ($d in $deleted) {
|
||||||
|
Write-Output " - $($d.Name) deletedFrom='$($d.lastKnownParent)'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '12. DNS / DC connectivity (sync path)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Write-Output '--- Key outbound hostnames (already verified clean in readiness check, re-verify):'
|
||||||
|
foreach ($h in 'login.microsoftonline.com','login.windows.net','adminwebservice.microsoftonline.com') {
|
||||||
|
try {
|
||||||
|
$r = Resolve-DnsName $h -Type A -ErrorAction Stop
|
||||||
|
Write-Output " [OK] $h -> $($r[0].IPAddress)"
|
||||||
|
} catch {
|
||||||
|
Write-Output " [FAIL] $h -> $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '13. DC health quick recheck'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# dcdiag already validated tonight but re-check in case the reboot shook anything
|
||||||
|
dcdiag /test:services /test:replications /test:fsmocheck /test:advertising /q 2>&1 |
|
||||||
|
Where-Object { $_ -match 'failed|warning|error' } |
|
||||||
|
ForEach-Object { Write-Output " $_" }
|
||||||
|
Write-Output '(Any entries above mean a dcdiag warning/failure; otherwise silent = all pass.)'
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '14. NTP time sync (re-verify)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$tsource = (w32tm /query /source 2>&1) -join ' '
|
||||||
|
$tdrift = (w32tm /query /status 2>&1 | Select-String 'Last Successful Sync Time|ReferenceId|Stratum').ToString()
|
||||||
|
Write-Output "Source: $tsource"
|
||||||
|
Write-Output $tdrift
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section 'Done'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Write-Output "Completed at $(Get-Date)"
|
||||||
357
clients/cascades-tucson/docs/migration/scripts/g1-ad-hygiene.ps1
Normal file
357
clients/cascades-tucson/docs/migration/scripts/g1-ad-hygiene.ps1
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Entra Connect Gate G1 - AD Hygiene / Pre-sync Cleanup
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Runs on CS-SERVER via GuruRMM. Idempotent. Each section reports what it
|
||||||
|
# would do (dry-run) OR what it did (execute). Pre-state backups always run.
|
||||||
|
#
|
||||||
|
# USAGE:
|
||||||
|
# Default invocation is DRY-RUN (no writes). Run with $doExecute=$true to
|
||||||
|
# apply changes.
|
||||||
|
#
|
||||||
|
# Sections:
|
||||||
|
# 0. Pre-state backup (always runs, no changes)
|
||||||
|
# 1. OU=Excluded-From-Sync (create; move 4 role accounts)
|
||||||
|
# 2. proxyAddresses populate (34 users - live data from M365 Graph)
|
||||||
|
# 3. SG-* security groups (create 16 groups in OU=Groups)
|
||||||
|
# 4. DisplayName cosmetics (3 fixes: Crystal, howard, Cathy)
|
||||||
|
# 5. Summary (counts, rollback path)
|
||||||
|
#
|
||||||
|
# Maps to risk register: docs/migration/entra-connect-risk-register-2026-04-22.md
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Toggle at top of script so it's obvious in GuruRMM command preview
|
||||||
|
$doExecute = $false # CHANGE TO $true TO APPLY
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
Import-Module ActiveDirectory -ErrorAction Stop
|
||||||
|
|
||||||
|
$ts = Get-Date -Format 'yyyy-MM-dd-HHmmss'
|
||||||
|
$backupDir = "D:\Backups\g1-hygiene-$ts"
|
||||||
|
$mode = if ($doExecute) { 'EXECUTE' } else { 'DRY-RUN (no changes)' }
|
||||||
|
|
||||||
|
function Section($n) {
|
||||||
|
Write-Output ''
|
||||||
|
Write-Output ('=' * 76)
|
||||||
|
Write-Output "== $n"
|
||||||
|
Write-Output ('=' * 76)
|
||||||
|
}
|
||||||
|
function Log($level, $msg) {
|
||||||
|
$prefix = switch ($level) {
|
||||||
|
'WOULD' { '[WOULD]' }
|
||||||
|
'DID' { '[DID] ' }
|
||||||
|
'OK' { '[OK] ' }
|
||||||
|
'SKIP' { '[SKIP] ' }
|
||||||
|
'WARN' { '[WARN] ' }
|
||||||
|
'FAIL' { '[FAIL] ' }
|
||||||
|
default { ' ' }
|
||||||
|
}
|
||||||
|
Write-Output "$prefix $msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "G1 AD Hygiene - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
|
||||||
|
Write-Output "Host: $env:COMPUTERNAME"
|
||||||
|
Write-Output "Mode: $mode"
|
||||||
|
Write-Output "Backup dir: $backupDir"
|
||||||
|
|
||||||
|
# Counters
|
||||||
|
$script:stats = @{ created=0; moved=0; updated=0; skipped=0; errors=0 }
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '0. Pre-state backup (always runs)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $backupDir)) {
|
||||||
|
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Full user dump with relevant attributes for diff-later
|
||||||
|
Get-ADUser -Filter * -Properties SamAccountName,UserPrincipalName,DisplayName,GivenName,Surname,`
|
||||||
|
Mail,proxyAddresses,Enabled,PasswordLastSet,PasswordNeverExpires,DistinguishedName,whenCreated,whenChanged |
|
||||||
|
Select-Object SamAccountName,UserPrincipalName,DisplayName,GivenName,Surname,Mail,
|
||||||
|
@{Name='ProxyAddresses';Expression={($_.proxyAddresses -join ';')}},
|
||||||
|
Enabled,PasswordLastSet,PasswordNeverExpires,DistinguishedName |
|
||||||
|
Export-Csv "$backupDir\users-pre.csv" -NoTypeInformation -Encoding UTF8
|
||||||
|
Log 'OK' "Exported users-pre.csv"
|
||||||
|
|
||||||
|
Get-ADGroup -Filter * -Properties Description,GroupScope,GroupCategory,Members |
|
||||||
|
Select-Object SamAccountName,Name,GroupScope,GroupCategory,Description,
|
||||||
|
@{Name='MemberCount';Expression={$_.Members.Count}},
|
||||||
|
DistinguishedName |
|
||||||
|
Export-Csv "$backupDir\groups-pre.csv" -NoTypeInformation -Encoding UTF8
|
||||||
|
Log 'OK' "Exported groups-pre.csv"
|
||||||
|
|
||||||
|
Get-ADOrganizationalUnit -Filter * |
|
||||||
|
Select-Object Name,DistinguishedName |
|
||||||
|
Export-Csv "$backupDir\ous-pre.csv" -NoTypeInformation -Encoding UTF8
|
||||||
|
Log 'OK' "Exported ous-pre.csv"
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Log 'OK' "Pre-state saved at $backupDir"
|
||||||
|
Write-Output "Rollback commands (if needed after execute):"
|
||||||
|
Write-Output " - proxyAddresses: Set-ADUser from users-pre.csv column ProxyAddresses"
|
||||||
|
Write-Output " - OU moves: Move-ADObject back to old DistinguishedName"
|
||||||
|
Write-Output " - Groups created today: Remove-ADGroup (safe since memberless)"
|
||||||
|
} catch {
|
||||||
|
Log 'FAIL' "Pre-state backup failed: $_"
|
||||||
|
$script:stats.errors++
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '1. OU=Excluded-From-Sync + move 4 role accounts'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$domainDN = (Get-ADDomain).DistinguishedName
|
||||||
|
$excludedOU = "OU=Excluded-From-Sync,$domainDN"
|
||||||
|
|
||||||
|
# Create OU
|
||||||
|
$existing = $null
|
||||||
|
try { $existing = Get-ADOrganizationalUnit -Identity $excludedOU -ErrorAction Stop } catch {}
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
Log 'SKIP' "OU=Excluded-From-Sync already exists"
|
||||||
|
} else {
|
||||||
|
if ($doExecute) {
|
||||||
|
try {
|
||||||
|
New-ADOrganizationalUnit -Name 'Excluded-From-Sync' -Path $domainDN `
|
||||||
|
-Description 'Accounts scoped OUT of Entra Connect sync. Do not move staff users here.' `
|
||||||
|
-ProtectedFromAccidentalDeletion $true
|
||||||
|
Log 'DID' "Created OU=Excluded-From-Sync"
|
||||||
|
$script:stats.created++
|
||||||
|
} catch {
|
||||||
|
Log 'FAIL' "New-ADOrganizationalUnit failed: $_"
|
||||||
|
$script:stats.errors++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log 'WOULD' "Create OU=Excluded-From-Sync (ProtectedFromAccidentalDeletion=true)"
|
||||||
|
$script:stats.created++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Move role accounts
|
||||||
|
$roleAccounts = @('Culinary','Receptionist','saleshare','directoryshare')
|
||||||
|
foreach ($sam in $roleAccounts) {
|
||||||
|
try {
|
||||||
|
$u = Get-ADUser -Identity $sam -Properties DistinguishedName -ErrorAction Stop
|
||||||
|
$currentOU = ($u.DistinguishedName -split ',', 2)[1]
|
||||||
|
if ($currentOU -eq $excludedOU) {
|
||||||
|
Log 'SKIP' "$sam already in Excluded-From-Sync"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($doExecute) {
|
||||||
|
Move-ADObject -Identity $u.DistinguishedName -TargetPath $excludedOU
|
||||||
|
Log 'DID' "Moved ${sam}: $currentOU -> $excludedOU"
|
||||||
|
$script:stats.moved++
|
||||||
|
} else {
|
||||||
|
Log 'WOULD' "Move $sam from $currentOU to $excludedOU"
|
||||||
|
$script:stats.moved++
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log 'WARN' "Cannot resolve $sam - $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '2. Populate proxyAddresses (34 users - live data from M365 Graph 2026-04-22)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Mapping derived from live Graph API pull. Primary SMTP uses uppercase 'SMTP:'
|
||||||
|
# prefix (Exchange convention for primary). Aliases use lowercase 'smtp:'.
|
||||||
|
$proxyMap = @{
|
||||||
|
# 24 users with existing M365 mailboxes
|
||||||
|
'Allison.Reibschied' = @('Allison.Reibschied@cascadestucson.com')
|
||||||
|
'Alyssa.Brooks' = @('alyssa.brooks@cascadestucson.com')
|
||||||
|
'Ashley.Jensen' = @('ashley.jensen@cascadestucson.com','ashley.jenson@cascadestucson.com')
|
||||||
|
'britney.thompson' = @('Britney.Thompson@cascadestucson.com')
|
||||||
|
'Christina.DuPras' = @('christina.dupras@cascadestucson.com')
|
||||||
|
'Christine.Nyanzunda' = @('christine.nyanzunda@cascadestucson.com')
|
||||||
|
'Crystal.Rodriguez' = @('crystal.rodriguez@cascadestucson.com','crystal.suszek@cascadestucson.com')
|
||||||
|
'howard' = @('dax.howard@cascadestucson.com','cara.lespron@cascadestucson.com')
|
||||||
|
'JD.Martin' = @('jd.martin@cascadestucson.com')
|
||||||
|
'John.Trozzi' = @('john.trozzi@cascadestucson.com')
|
||||||
|
'karen.rossini' = @('karen.rossini@cascadestucson.com')
|
||||||
|
'lauren.hasselman' = @('lauren.hasselman@cascadestucson.com')
|
||||||
|
'Lois.Lane' = @('lois.lane@cascadestucson.com')
|
||||||
|
'Lupe.Sanchez' = @('lupe.sanchez@cascadestucson.com')
|
||||||
|
'Matt.Brooks' = @('matthew.brooks@cascadestucson.com') # soft-match bridge (M365 UPN is matthew.brooks)
|
||||||
|
'Megan.Hiatt' = @('megan.hiatt@cascadestucson.com')
|
||||||
|
'Meredith.Kuhn' = @('meredith.kuhn@cascadestucson.com')
|
||||||
|
'Ramon.Castaneda' = @('ramon.castaneda@cascadestucson.com','ramon.castanada@cascadestucson.com','ramon.casteneda@cascadestucson.com')
|
||||||
|
'Sharon.Edwards' = @('sharon.edwards@cascadestucson.com')
|
||||||
|
'Shelby.Trozzi' = @('Shelby.Trozzi@cascadestucson.com')
|
||||||
|
'Susan.Hicks' = @('susan.hicks@cascadestucson.com')
|
||||||
|
'sysadmin' = @('sysadmin@cascadestucson.com')
|
||||||
|
'Tamra.Matthews' = @('tamra.matthews@cascadestucson.com','tamra.johnson@cascadestucson.com')
|
||||||
|
'Veronica.Feller' = @('veronica.feller@cascadestucson.com')
|
||||||
|
# 10 AD-only users (no M365 mailbox yet; reserve canonical SMTP for when they get licensed)
|
||||||
|
'Cathy.Kingston' = @('cathy.kingston@cascadestucson.com')
|
||||||
|
'Christopher.Holick' = @('christopher.holick@cascadestucson.com')
|
||||||
|
'Julian.Crim' = @('julian.crim@cascadestucson.com')
|
||||||
|
'Kyla.QuickTiffany' = @('kyla.quicktiffany@cascadestucson.com')
|
||||||
|
'Michelle.Shestko' = @('michelle.shestko@cascadestucson.com')
|
||||||
|
'Ray.Rai' = @('ray.rai@cascadestucson.com')
|
||||||
|
'Richard.Adams' = @('richard.adams@cascadestucson.com')
|
||||||
|
'Sebastian.Leon' = @('sebastian.leon@cascadestucson.com')
|
||||||
|
'Sheldon.Gardfrey' = @('sheldon.gardfrey@cascadestucson.com')
|
||||||
|
'Shontiel.Nunn' = @('shontiel.nunn@cascadestucson.com')
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($sam in ($proxyMap.Keys | Sort-Object)) {
|
||||||
|
try {
|
||||||
|
$u = Get-ADUser -Identity $sam -Properties proxyAddresses,mail -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
Log 'WARN' "AD user '$sam' not found - $($_.Exception.Message.Split([char]10)[0])"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build the target proxyAddresses value: SMTP:<primary> + smtp:<aliases>
|
||||||
|
$primary = $proxyMap[$sam][0]
|
||||||
|
$aliases = @($proxyMap[$sam] | Select-Object -Skip 1)
|
||||||
|
$targetProxies = @("SMTP:$primary") + ($aliases | ForEach-Object { "smtp:$_" })
|
||||||
|
$targetMail = $primary
|
||||||
|
|
||||||
|
$currentProxies = @($u.proxyAddresses)
|
||||||
|
$currentSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
|
foreach ($p in $currentProxies) { [void]$currentSet.Add($p) }
|
||||||
|
$targetSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
|
foreach ($p in $targetProxies) { [void]$targetSet.Add($p) }
|
||||||
|
|
||||||
|
if ($currentSet.SetEquals($targetSet) -and $u.mail -eq $targetMail) {
|
||||||
|
Log 'SKIP' "$sam proxyAddresses already current"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentStr = if ($currentProxies.Count -eq 0) { '<empty>' } else { $currentProxies -join '; ' }
|
||||||
|
$targetStr = $targetProxies -join '; '
|
||||||
|
|
||||||
|
if ($doExecute) {
|
||||||
|
try {
|
||||||
|
Set-ADUser -Identity $sam -Replace @{
|
||||||
|
proxyAddresses = $targetProxies
|
||||||
|
mail = $targetMail
|
||||||
|
} -ErrorAction Stop
|
||||||
|
Log 'DID' "$sam"
|
||||||
|
Write-Output " before: $currentStr"
|
||||||
|
Write-Output " after: $targetStr"
|
||||||
|
Write-Output " mail=$targetMail"
|
||||||
|
$script:stats.updated++
|
||||||
|
} catch {
|
||||||
|
Log 'FAIL' "$sam Set-ADUser failed: $_"
|
||||||
|
$script:stats.errors++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log 'WOULD' "$sam"
|
||||||
|
Write-Output " before: $currentStr"
|
||||||
|
Write-Output " after: $targetStr"
|
||||||
|
Write-Output " mail=<empty> -> $targetMail"
|
||||||
|
$script:stats.updated++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '3. Create 16 SG-* security groups (CA / file-share / break-glass)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$groupsOU = "OU=Groups,$domainDN"
|
||||||
|
# Verify Groups OU exists
|
||||||
|
try { Get-ADOrganizationalUnit -Identity $groupsOU -ErrorAction Stop | Out-Null }
|
||||||
|
catch {
|
||||||
|
Log 'FAIL' "OU=Groups does not exist at $groupsOU - cannot create groups"
|
||||||
|
$script:stats.errors++
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupsToCreate = @(
|
||||||
|
@{Name='SG-External-Signin-Allowed'; Desc='Members may sign in from outside Cascades building (CA policy target).'},
|
||||||
|
@{Name='SG-Caregivers'; Desc='All shift-work caregivers. CA policy target for shared-phone mobile policy.'},
|
||||||
|
@{Name='SG-FrontDesk'; Desc='Front desk receptionists sharing reception PCs.'},
|
||||||
|
@{Name='SG-CourtesyPatrol'; Desc='Courtesy patrol staff.'},
|
||||||
|
@{Name='SG-Drivers'; Desc='Transportation drivers (AD accounts being disabled 2026-04-22 - group retained for history).'},
|
||||||
|
@{Name='SG-Management-RW'; Desc='Read/write on \\CS-SERVER\Management file share (Phase 4).'},
|
||||||
|
@{Name='SG-Sales-RW'; Desc='Read/write on \\CS-SERVER\SalesDept file share (Phase 4).'},
|
||||||
|
@{Name='SG-Culinary-RW'; Desc='Read/write on \\CS-SERVER\Culinary file share (Phase 4).'},
|
||||||
|
@{Name='SG-IT-RW'; Desc='Read/write on \\CS-SERVER\IT file share (Phase 4).'},
|
||||||
|
@{Name='SG-Receptionist-RW'; Desc='Read/write on \\CS-SERVER\Receptionist file share (Phase 4).'},
|
||||||
|
@{Name='SG-Directory-RW'; Desc='Read/write on \\CS-SERVER\directoryshare file share (Phase 4).'},
|
||||||
|
@{Name='SG-Server-RW'; Desc='Read/write on \\CS-SERVER\Server share (IT admin, Phase 4).'},
|
||||||
|
@{Name='SG-Chat-RW'; Desc='Read/write on \\CS-SERVER\chat file share (Phase 4).'},
|
||||||
|
@{Name='SG-Office-PHI-External'; Desc='Office PHI staff with external sign-in permission (CA policy).'},
|
||||||
|
@{Name='SG-Office-PHI-Internal'; Desc='Office PHI staff limited to in-building sign-in (CA policy).'},
|
||||||
|
@{Name='SG-CA-BreakGlass'; Desc='Break-glass accounts excluded from all Conditional Access policies.'}
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($g in $groupsToCreate) {
|
||||||
|
try {
|
||||||
|
$existing = Get-ADGroup -Identity $g.Name -ErrorAction Stop
|
||||||
|
Log 'SKIP' "$($g.Name) already exists"
|
||||||
|
continue
|
||||||
|
} catch {
|
||||||
|
# group missing - proceed to create
|
||||||
|
}
|
||||||
|
if ($doExecute) {
|
||||||
|
try {
|
||||||
|
New-ADGroup -Name $g.Name -SamAccountName $g.Name -GroupCategory Security -GroupScope Global `
|
||||||
|
-Path $groupsOU -Description $g.Desc
|
||||||
|
Log 'DID' "Created $($g.Name)"
|
||||||
|
$script:stats.created++
|
||||||
|
} catch {
|
||||||
|
Log 'FAIL' "Create $($g.Name) failed: $_"
|
||||||
|
$script:stats.errors++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log 'WOULD' "Create $($g.Name) (Global Security) in $groupsOU"
|
||||||
|
Write-Output " desc: $($g.Desc)"
|
||||||
|
$script:stats.created++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '4. DisplayName cosmetic fixes (3 users)'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
$displayFixes = @(
|
||||||
|
@{SAM='Crystal.Rodriguez'; Current='Crystal Rodriguez'; Target='Crystal Rodriguez'}
|
||||||
|
@{SAM='howard'; Current='howard'; Target='Howard Dax'}
|
||||||
|
@{SAM='Cathy.Kingston'; Current='Cathy.Kingston'; Target='Cathy Kingston'}
|
||||||
|
)
|
||||||
|
foreach ($d in $displayFixes) {
|
||||||
|
try {
|
||||||
|
$u = Get-ADUser -Identity $d.SAM -Properties DisplayName -ErrorAction Stop
|
||||||
|
if ($u.DisplayName -eq $d.Target) {
|
||||||
|
Log 'SKIP' "$($d.SAM) DisplayName already '$($d.Target)'"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($doExecute) {
|
||||||
|
Set-ADUser -Identity $d.SAM -DisplayName $d.Target
|
||||||
|
Log 'DID' "$($d.SAM) DisplayName: '$($u.DisplayName)' -> '$($d.Target)'"
|
||||||
|
$script:stats.updated++
|
||||||
|
} else {
|
||||||
|
Log 'WOULD' "$($d.SAM) DisplayName: '$($u.DisplayName)' -> '$($d.Target)'"
|
||||||
|
$script:stats.updated++
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log 'WARN' "$($d.SAM) lookup failed: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Section '5. Summary'
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
Write-Output "Mode: $mode"
|
||||||
|
Write-Output "Created: $($script:stats.created)"
|
||||||
|
Write-Output "Moved: $($script:stats.moved)"
|
||||||
|
Write-Output "Updated: $($script:stats.updated)"
|
||||||
|
Write-Output "Skipped: $($script:stats.skipped)"
|
||||||
|
Write-Output "Errors: $($script:stats.errors)"
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Backup dir: $backupDir"
|
||||||
|
Write-Output ""
|
||||||
|
|
||||||
|
if (-not $doExecute) {
|
||||||
|
Write-Output 'DRY-RUN complete. To execute:'
|
||||||
|
Write-Output ' 1. Review the [WOULD] lines above'
|
||||||
|
Write-Output ' 2. Re-run this script with $doExecute = $true'
|
||||||
|
Write-Output ' 3. Compare post-state vs pre-state CSVs in the backup dir'
|
||||||
|
} else {
|
||||||
|
Write-Output 'EXECUTE complete. Recommended next steps:'
|
||||||
|
Write-Output ' 1. Re-run in DRY-RUN to confirm 0 [WOULD] entries (idempotency check)'
|
||||||
|
Write-Output " 2. Export users-post.csv for the audit trail (in $backupDir)"
|
||||||
|
Write-Output ' 3. Proceed to Gate G2 (M365 role-account shared-mailbox conversion)'
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Completed at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
|
||||||
426
clients/cascades-tucson/reports/2026-04-22-g1-ad-audit.md
Normal file
426
clients/cascades-tucson/reports/2026-04-22-g1-ad-audit.md
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
# CS-SERVER G1 AD Audit
|
||||||
|
|
||||||
|
**Command ID:** e83befb7-4c8c-4f24-9b24-56cb641e6464
|
||||||
|
**Run:** 2026-04-23T02:55:50.634848Z
|
||||||
|
**Exit:** 0
|
||||||
|
|
||||||
|
## STDOUT
|
||||||
|
|
||||||
|
```
|
||||||
|
G1 AD Audit - 2026-04-22 19:55:51 -07:00
|
||||||
|
|
||||||
|
Host: CS-SERVER
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 1. Forest / Domain / Schema
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Forest FQDN: cascades.local
|
||||||
|
|
||||||
|
Forest mode: Windows2016Forest
|
||||||
|
|
||||||
|
Domain mode: Windows2016Domain
|
||||||
|
|
||||||
|
Domain DN: DC=cascades,DC=local
|
||||||
|
|
||||||
|
NetBIOS: CASCADES
|
||||||
|
|
||||||
|
UPN suffixes (forest):
|
||||||
|
|
||||||
|
- cascadestucson.com
|
||||||
|
|
||||||
|
Schema objectVersion: 88
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FSMO roles:
|
||||||
|
|
||||||
|
Schema Master: CS-SERVER.cascades.local
|
||||||
|
|
||||||
|
Domain Naming Master: CS-SERVER.cascades.local
|
||||||
|
|
||||||
|
PDC Emulator: CS-SERVER.cascades.local
|
||||||
|
|
||||||
|
RID Master: CS-SERVER.cascades.local
|
||||||
|
|
||||||
|
Infrastructure Master: CS-SERVER.cascades.local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 2. OU structure
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Total OUs: 18
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OU=Administrative,OU=Departments,DC=cascades,DC=local users=5 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Care-Assisted Living,OU=Departments,DC=cascades,DC=local users=4 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Care-Memorycare,OU=Departments,DC=cascades,DC=local users=2 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Culinary,OU=Departments,DC=cascades,DC=local users=4 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Departments,DC=cascades,DC=local users=0 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Domain Controllers,DC=cascades,DC=local users=0 groups=0 computers=1
|
||||||
|
|
||||||
|
OU=Groups,DC=cascades,DC=local users=0 groups=1 computers=0
|
||||||
|
|
||||||
|
OU=Housekeeping,OU=Departments,DC=cascades,DC=local users=1 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Life Enrichment,OU=Departments,DC=cascades,DC=local users=2 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Maintenance,OU=Departments,DC=cascades,DC=local users=2 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Marketing,OU=Departments,DC=cascades,DC=local users=4 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Nurses,OU=Care-Assisted Living,OU=Departments,DC=cascades,DC=local users=0 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Resident Services,OU=Departments,DC=cascades,DC=local users=8 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=ServiceAccounts,DC=cascades,DC=local users=1 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Shared PCs,OU=Workstations,DC=cascades,DC=local users=0 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Staff PCs,OU=Workstations,DC=cascades,DC=local users=0 groups=0 computers=6
|
||||||
|
|
||||||
|
OU=Transportation,OU=Departments,DC=cascades,DC=local users=3 groups=0 computers=0
|
||||||
|
|
||||||
|
OU=Workstations,DC=cascades,DC=local users=0 groups=0 computers=0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Users in default CN=Users container (not ideal - should be in an OU for sync scope):
|
||||||
|
|
||||||
|
Count: 8
|
||||||
|
|
||||||
|
- Administrator (Administrator)
|
||||||
|
|
||||||
|
- directoryshare (directoryshare)
|
||||||
|
|
||||||
|
- Guest (Guest)
|
||||||
|
|
||||||
|
- krbtgt (krbtgt)
|
||||||
|
|
||||||
|
- localadmin (localadmin)
|
||||||
|
|
||||||
|
- QBDataServiceUser34 (QBDataServiceUser34)
|
||||||
|
|
||||||
|
- Receptionist (RECEPTIONIST)
|
||||||
|
|
||||||
|
- sysadmin (Sysadmin)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 3. All AD users - identity attributes for sync match
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
Total users: 44
|
||||||
|
|
||||||
|
Enabled: 42
|
||||||
|
|
||||||
|
Disabled: 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Per-user identity dump (focus: anything that affects soft-match):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SAM | UPN | Mail | ProxyAddrs | PwdLastSet | PwdNeverExp | Enabled | OU
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Administrator | | | | 2024-08-04 | True | True | CN=Users
|
||||||
|
|
||||||
|
Allison.Reibschied | Allison.Reibschied@cascadestucson.com | | | 2026-03-13 | False | True | OU=Administrative,OU=Departments
|
||||||
|
|
||||||
|
Alyssa.Brooks | Alyssa.Brooks@cascadestucson.com | | | 2025-12-12 | False | True | OU=Culinary,OU=Departments
|
||||||
|
|
||||||
|
Ashley.Jensen | Ashley.Jensen@cascadestucson.com | | | NULL | False | True | OU=Administrative,OU=Departments
|
||||||
|
|
||||||
|
britney.thompson | britney.thompson@cascadestucson.com | | | NULL | False | True | OU=Care-Assisted Living,OU=Departments
|
||||||
|
|
||||||
|
Cathy.Kingston | Cathy.Kingston@cascadestucson.com | | | 2025-12-12 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Christina.DuPras | Christina.DuPras@cascadestucson.com | | | 2026-01-06 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Christine.Nyanzunda | Christine.Nyanzunda@cascadestucson.com | | | NULL | False | True | OU=Care-Memorycare,OU=Departments
|
||||||
|
|
||||||
|
Christopher.Holick | Christopher.Holick@cascadestucson.com | | | 2025-12-12 | False | True | OU=Transportation,OU=Departments
|
||||||
|
|
||||||
|
Crystal.Rodriguez | Crystal.Rodriguez@cascadestucson.com | | | 2026-04-20 | False | True | OU=Marketing,OU=Departments
|
||||||
|
|
||||||
|
Culinary | Culinary@cascades.local | | | 2024-12-27 | True | True | OU=Culinary,OU=Departments
|
||||||
|
|
||||||
|
directoryshare | directoryshare@cascades.local | | | 2025-12-01 | True | True | CN=Users
|
||||||
|
|
||||||
|
Guest | | | | NULL | True | False | CN=Users
|
||||||
|
|
||||||
|
howard | howard@cascadestucson.com | | | 2025-08-11 | True | True | OU=Administrative,OU=Departments
|
||||||
|
|
||||||
|
JD.Martin | JD.Martin@cascadestucson.com | | | NULL | False | True | OU=Culinary,OU=Departments
|
||||||
|
|
||||||
|
John.Trozzi | John.Trozzi@cascadestucson.com | | | NULL | False | True | OU=Maintenance,OU=Departments
|
||||||
|
|
||||||
|
Julian.Crim | Julian.Crim@cascadestucson.com | | | 2025-12-12 | False | True | OU=Transportation,OU=Departments
|
||||||
|
|
||||||
|
karen.rossini | karen.rossini@cascadestucson.com | | | 2025-12-12 | False | True | OU=Care-Assisted Living,OU=Departments
|
||||||
|
|
||||||
|
krbtgt | | | | 2024-08-28 | False | False | CN=Users
|
||||||
|
|
||||||
|
Kyla.QuickTiffany | Kyla.QuickTiffany@cascadestucson.com | | | 2026-04-13 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
lauren.hasselman | lauren.hasselman@cascadestucson.com | | | 2026-04-01 | False | True | OU=Administrative,OU=Departments
|
||||||
|
|
||||||
|
localadmin | | | | 2024-12-03 | True | True | CN=Users
|
||||||
|
|
||||||
|
Lois.Lane | Lois.Lane@cascadestucson.com | | | 2025-12-22 | True | True | OU=Care-Assisted Living,OU=Departments
|
||||||
|
|
||||||
|
Lupe.Sanchez | Lupe.Sanchez@cascadestucson.com | | | 2025-12-12 | False | True | OU=Housekeeping,OU=Departments
|
||||||
|
|
||||||
|
Matt.Brooks | Matt.Brooks@cascadestucson.com | | | NULL | False | True | OU=Maintenance,OU=Departments
|
||||||
|
|
||||||
|
Megan.Hiatt | Megan.Hiatt@cascadestucson.com | | | NULL | False | True | OU=Marketing,OU=Departments
|
||||||
|
|
||||||
|
Meredith.Kuhn | Meredith.Kuhn@cascadestucson.com | | | NULL | False | True | OU=Administrative,OU=Departments
|
||||||
|
|
||||||
|
Michelle.Shestko | Michelle.Shestko@cascadestucson.com | | | NULL | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
QBDataServiceUser34 | | | | 2024-10-02 | True | True | CN=Users
|
||||||
|
|
||||||
|
Ramon.Castaneda | Ramon.Castaneda@cascadestucson.com | | | NULL | False | True | OU=Culinary,OU=Departments
|
||||||
|
|
||||||
|
Ray.Rai | Ray.Rai@cascadestucson.com | | | 2025-12-12 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Receptionist | RECEPTIONIST@cascades.local | | | 2026-04-07 | True | True | CN=Users
|
||||||
|
|
||||||
|
Richard.Adams | Richard.Adams@cascadestucson.com | | | 2025-12-12 | False | True | OU=Transportation,OU=Departments
|
||||||
|
|
||||||
|
saleshare | | | | 2025-10-27 | False | True | OU=Marketing,OU=Departments
|
||||||
|
|
||||||
|
Sebastian.Leon | Sebastian.Leon@cascadestucson.com | | | NULL | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Sharon.Edwards | Sharon.Edwards@cascadestucson.com | | | 2026-04-13 | False | True | OU=Life Enrichment,OU=Departments
|
||||||
|
|
||||||
|
Shelby.Trozzi | Shelby.Trozzi@cascadestucson.com | | | 2025-12-11 | True | True | OU=Care-Memorycare,OU=Departments
|
||||||
|
|
||||||
|
Sheldon.Gardfrey | Sheldon.Gardfrey@cascadestucson.com | | | 2025-12-12 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Shontiel.Nunn | Shontiel.Nunn@cascadestucson.com | | | 2025-12-12 | False | True | OU=Resident Services,OU=Departments
|
||||||
|
|
||||||
|
Susan.Hicks | Susan.Hicks@cascadestucson.com | | | 2026-04-13 | False | True | OU=Life Enrichment,OU=Departments
|
||||||
|
|
||||||
|
svc-audit-upload | | | | 2026-04-17 | True | True | OU=ServiceAccounts
|
||||||
|
|
||||||
|
sysadmin | sysadmin@cascadestucson.com | | | 2024-09-29 | True | True | CN=Users
|
||||||
|
|
||||||
|
Tamra.Matthews | Tamra.Matthews@cascadestucson.com | | | NULL | False | True | OU=Marketing,OU=Departments
|
||||||
|
|
||||||
|
Veronica.Feller | Veronica.Feller@cascadestucson.com | | | NULL | False | True | OU=Care-Assisted Living,OU=Departments
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 4. Soft-match risk scan - accounts likely to duplicate in Entra
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
--- Users with NO proxyAddresses and NO mail (soft-match engine has nothing to work with):
|
||||||
|
|
||||||
|
- Administrator (UPN: )
|
||||||
|
|
||||||
|
- localadmin (UPN: )
|
||||||
|
|
||||||
|
- Meredith.Kuhn (UPN: Meredith.Kuhn@cascadestucson.com)
|
||||||
|
|
||||||
|
- John.Trozzi (UPN: John.Trozzi@cascadestucson.com)
|
||||||
|
|
||||||
|
- Megan.Hiatt (UPN: Megan.Hiatt@cascadestucson.com)
|
||||||
|
|
||||||
|
- Crystal.Rodriguez (UPN: Crystal.Rodriguez@cascadestucson.com)
|
||||||
|
|
||||||
|
- Tamra.Matthews (UPN: Tamra.Matthews@cascadestucson.com)
|
||||||
|
|
||||||
|
- Lois.Lane (UPN: Lois.Lane@cascadestucson.com)
|
||||||
|
|
||||||
|
- Christina.DuPras (UPN: Christina.DuPras@cascadestucson.com)
|
||||||
|
|
||||||
|
- Christine.Nyanzunda (UPN: Christine.Nyanzunda@cascadestucson.com)
|
||||||
|
|
||||||
|
- Susan.Hicks (UPN: Susan.Hicks@cascadestucson.com)
|
||||||
|
|
||||||
|
- Ashley.Jensen (UPN: Ashley.Jensen@cascadestucson.com)
|
||||||
|
|
||||||
|
- Veronica.Feller (UPN: Veronica.Feller@cascadestucson.com)
|
||||||
|
|
||||||
|
- Sebastian.Leon (UPN: Sebastian.Leon@cascadestucson.com)
|
||||||
|
|
||||||
|
- JD.Martin (UPN: JD.Martin@cascadestucson.com)
|
||||||
|
|
||||||
|
- Matt.Brooks (UPN: Matt.Brooks@cascadestucson.com)
|
||||||
|
|
||||||
|
- Ramon.Castaneda (UPN: Ramon.Castaneda@cascadestucson.com)
|
||||||
|
|
||||||
|
- Michelle.Shestko (UPN: Michelle.Shestko@cascadestucson.com)
|
||||||
|
|
||||||
|
- Sharon.Edwards (UPN: Sharon.Edwards@cascadestucson.com)
|
||||||
|
|
||||||
|
- sysadmin (UPN: sysadmin@cascadestucson.com)
|
||||||
|
|
||||||
|
- QBDataServiceUser34 (UPN: )
|
||||||
|
|
||||||
|
- Culinary (UPN: Culinary@cascades.local)
|
||||||
|
|
||||||
|
- Receptionist (UPN: RECEPTIONIST@cascades.local)
|
||||||
|
|
||||||
|
- britney.thompson (UPN: britney.thompson@cascadestucson.com)
|
||||||
|
|
||||||
|
- howard (UPN: howard@cascadestucson.com)
|
||||||
|
|
||||||
|
- saleshare (UPN: )
|
||||||
|
|
||||||
|
- directoryshare (UPN: directoryshare@cascades.local)
|
||||||
|
|
||||||
|
- Shelby.Trozzi (UPN: Shelby.Trozzi@cascadestucson.com)
|
||||||
|
|
||||||
|
- karen.rossini (UPN: karen.rossini@cascadestucson.com)
|
||||||
|
|
||||||
|
- Alyssa.Brooks (UPN: Alyssa.Brooks@cascadestucson.com)
|
||||||
|
|
||||||
|
- Lupe.Sanchez (UPN: Lupe.Sanchez@cascadestucson.com)
|
||||||
|
|
||||||
|
- Sheldon.Gardfrey (UPN: Sheldon.Gardfrey@cascadestucson.com)
|
||||||
|
|
||||||
|
- Cathy.Kingston (UPN: Cathy.Kingston@cascadestucson.com)
|
||||||
|
|
||||||
|
- Shontiel.Nunn (UPN: Shontiel.Nunn@cascadestucson.com)
|
||||||
|
|
||||||
|
- Ray.Rai (UPN: Ray.Rai@cascadestucson.com)
|
||||||
|
|
||||||
|
- Richard.Adams (UPN: Richard.Adams@cascadestucson.com)
|
||||||
|
|
||||||
|
- Julian.Crim (UPN: Julian.Crim@cascadestucson.com)
|
||||||
|
|
||||||
|
- Christopher.Holick (UPN: Christopher.Holick@cascadestucson.com)
|
||||||
|
|
||||||
|
- lauren.hasselman (UPN: lauren.hasselman@cascadestucson.com)
|
||||||
|
|
||||||
|
- Allison.Reibschied (UPN: Allison.Reibschied@cascadestucson.com)
|
||||||
|
|
||||||
|
- Kyla.QuickTiffany (UPN: Kyla.QuickTiffany@cascadestucson.com)
|
||||||
|
|
||||||
|
- svc-audit-upload (UPN: )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Users whose UPN suffix is NOT cascadestucson.com (will mismatch M365 target unless renamed):
|
||||||
|
|
||||||
|
- Culinary UPN=Culinary@cascades.local
|
||||||
|
|
||||||
|
- Receptionist UPN=RECEPTIONIST@cascades.local
|
||||||
|
|
||||||
|
- directoryshare UPN=directoryshare@cascades.local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Users whose SAM does not match their UPN prefix (name mismatch candidates):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Users with DisplayName different from Given+Surname (may cause display oddities post-sync):
|
||||||
|
|
||||||
|
- SAM=Crystal.Rodriguez display='Crystal Rodriguez' expected='Crystal Rodriguez'
|
||||||
|
|
||||||
|
- SAM=howard display='howard' expected='Howard Dax'
|
||||||
|
|
||||||
|
- SAM=Cathy.Kingston display='Cathy.Kingston' expected='Cathy Kingston'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 5. Password hygiene (affects whether sync-derived sign-in will work)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
--- Enabled users with null PasswordLastSet (never set a password):
|
||||||
|
|
||||||
|
- Meredith.Kuhn whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- John.Trozzi whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Megan.Hiatt whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Tamra.Matthews whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Christine.Nyanzunda whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Ashley.Jensen whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Veronica.Feller whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Sebastian.Leon whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- JD.Martin whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Matt.Brooks whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Ramon.Castaneda whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- Michelle.Shestko whenCreated=2024-08-28
|
||||||
|
|
||||||
|
- britney.thompson whenCreated=2025-06-12
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Users with PasswordNotRequired=True:
|
||||||
|
|
||||||
|
- Guest enabled=False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Users with PasswordNeverExpires=True:
|
||||||
|
|
||||||
|
- Administrator PwdLastSet=08/04/2024 18:39:34 Description='Built-in account for administering the computer/domain'
|
||||||
|
|
||||||
|
- localadmin PwdLastSet=12/03/2024 15:22:15 Description=''
|
||||||
|
|
||||||
|
- Lois.Lane PwdLastSet=12/22/2025 09:52:55 Description=''
|
||||||
|
|
||||||
|
- sysadmin PwdLastSet=09/29/2024 21:23:28 Description=''
|
||||||
|
|
||||||
|
- QBDataServiceUser34 PwdLastSet=10/02/2024 12:22:12 Description='This account has been established to run the QuickBooks database system.'
|
||||||
|
|
||||||
|
- Culinary PwdLastSet=12/27/2024 10:17:03 Description=''
|
||||||
|
|
||||||
|
- Receptionist PwdLastSet=04/07/2026 12:54:47 Description=''
|
||||||
|
|
||||||
|
- howard PwdLastSet=08/11/2025 13:18:03 Description='Home Offie'
|
||||||
|
|
||||||
|
- directoryshare PwdLastSet=12/01/2025 10:02:12 Description=''
|
||||||
|
|
||||||
|
- Shelby.Trozzi PwdLastSet=12/11/2025 14:03:27 Description=''
|
||||||
|
|
||||||
|
- svc-audit-upload PwdLastSet=04/17/2026 15:21:13 Description='Write-only access to \\CS-SERVER\AuditDrop$. Used by Syncro audit upload script. Do not use interactively.'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Currently locked-out accounts:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
krbtgt password last set: 08/28/2024 10:02:37 (age: 602 days)
|
||||||
244
clients/cascades-tucson/reports/2026-04-22-g1-dryrun.md
Normal file
244
clients/cascades-tucson/reports/2026-04-22-g1-dryrun.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# G1 AD Hygiene Dry-Run
|
||||||
|
|
||||||
|
**Command ID:** 110f0836-9fa7-4773-b82c-e7f0eb9b5bbe
|
||||||
|
**Exit:** 0
|
||||||
|
**Completed:** 2026-04-23T03:26:52.186400Z
|
||||||
|
|
||||||
|
## STDOUT
|
||||||
|
|
||||||
|
```
|
||||||
|
G1 AD Hygiene - 2026-04-22 20:26:50 -07:00
|
||||||
|
|
||||||
|
Host: CS-SERVER
|
||||||
|
|
||||||
|
Mode: DRY-RUN (no changes)
|
||||||
|
|
||||||
|
Backup dir: D:\Backups\g1-hygiene-2026-04-22-202650
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 0. Pre-state backup (always runs)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[OK] Exported users-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported groups-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported ous-pre.csv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[OK] Pre-state saved at D:\Backups\g1-hygiene-2026-04-22-202650
|
||||||
|
|
||||||
|
Rollback commands (if needed after execute):
|
||||||
|
|
||||||
|
- proxyAddresses: Set-ADUser from users-pre.csv column ProxyAddresses
|
||||||
|
|
||||||
|
- OU moves: Move-ADObject back to old DistinguishedName
|
||||||
|
|
||||||
|
- Groups created today: Remove-ADGroup (safe since memberless)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 1. OU=Excluded-From-Sync + move 4 role accounts
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[WOULD] Create OU=Excluded-From-Sync (ProtectedFromAccidentalDeletion=true)
|
||||||
|
|
||||||
|
[WOULD] Move Culinary from OU=Culinary,OU=Departments,DC=cascades,DC=local to OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[WOULD] Move Receptionist from CN=Users,DC=cascades,DC=local to OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[WOULD] Move saleshare from OU=Marketing,OU=Departments,DC=cascades,DC=local to OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[WOULD] Move directoryshare from CN=Users,DC=cascades,DC=local to OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 2. Populate proxyAddresses (34 users - live data from M365 Graph 2026-04-22)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[WOULD] Allison.Reibschied
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:Allison.Reibschied@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> Allison.Reibschied@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Alyssa.Brooks
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:alyssa.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> alyssa.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Ashley.Jensen
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:ashley.jensen@cascadestucson.com; smtp:ashley.jenson@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> ashley.jensen@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] britney.thompson
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:Britney.Thompson@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> Britney.Thompson@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Cathy.Kingston
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:cathy.kingston@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> cathy.kingston@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Christina.DuPras
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christina.dupras@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> christina.dupras@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Christine.Nyanzunda
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christine.nyanzunda@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> christine.nyanzunda@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Christopher.Holick
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christopher.holick@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> christopher.holick@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Crystal.Rodriguez
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:crystal.rodriguez@cascadestucson.com; smtp:crystal.suszek@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> crystal.rodriguez@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] howard
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:dax.howard@cascadestucson.com; smtp:cara.lespron@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> dax.howard@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] JD.Martin
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:jd.martin@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> jd.martin@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] John.Trozzi
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:john.trozzi@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> john.trozzi@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Julian.Crim
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:julian.crim@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> julian.crim@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] karen.rossini
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:karen.rossini@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> karen.rossini@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Kyla.QuickTiffany
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:kyla.quicktiffany@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> kyla.quicktiffany@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] lauren.hasselman
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lauren.hasselman@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> lauren.hasselman@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Lois.Lane
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lois.lane@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> lois.lane@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Lupe.Sanchez
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lupe.sanchez@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> lupe.sanchez@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Matt.Brooks
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:matthew.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> matthew.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Megan.Hiatt
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:megan.hiatt@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> megan.hiatt@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Meredith.Kuhn
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:meredith.kuhn@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> meredith.kuhn@cascadestucson.com
|
||||||
|
|
||||||
|
[WOULD] Michelle.Shestko
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:michelle.shestko@cascadestucson.com
|
||||||
|
|
||||||
|
mail=<empty> -> michelle.shestko@cascadestucson.com
|
||||||
228
clients/cascades-tucson/reports/2026-04-22-g1-execute.md
Normal file
228
clients/cascades-tucson/reports/2026-04-22-g1-execute.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# G1 AD Hygiene - EXECUTE
|
||||||
|
|
||||||
|
**Command ID:** d49bb8dd-4916-4634-bf0c-c46bbcfcd81b
|
||||||
|
**Exit:** 0
|
||||||
|
**Completed:** 2026-04-23T03:32:39.186512Z
|
||||||
|
|
||||||
|
## STDOUT
|
||||||
|
|
||||||
|
```
|
||||||
|
G1 AD Hygiene - 2026-04-22 20:32:32 -07:00
|
||||||
|
|
||||||
|
Host: CS-SERVER
|
||||||
|
|
||||||
|
Mode: EXECUTE
|
||||||
|
|
||||||
|
Backup dir: D:\Backups\g1-hygiene-2026-04-22-203232
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 0. Pre-state backup (always runs)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[OK] Exported users-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported groups-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported ous-pre.csv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[OK] Pre-state saved at D:\Backups\g1-hygiene-2026-04-22-203232
|
||||||
|
|
||||||
|
Rollback commands (if needed after execute):
|
||||||
|
|
||||||
|
- proxyAddresses: Set-ADUser from users-pre.csv column ProxyAddresses
|
||||||
|
|
||||||
|
- OU moves: Move-ADObject back to old DistinguishedName
|
||||||
|
|
||||||
|
- Groups created today: Remove-ADGroup (safe since memberless)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 1. OU=Excluded-From-Sync + move 4 role accounts
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[DID] Created OU=Excluded-From-Sync
|
||||||
|
|
||||||
|
[DID] Moved Culinary: OU=Culinary,OU=Departments,DC=cascades,DC=local -> OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[DID] Moved Receptionist: CN=Users,DC=cascades,DC=local -> OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[DID] Moved saleshare: OU=Marketing,OU=Departments,DC=cascades,DC=local -> OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
[DID] Moved directoryshare: CN=Users,DC=cascades,DC=local -> OU=Excluded-From-Sync,DC=cascades,DC=local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 2. Populate proxyAddresses (34 users - live data from M365 Graph 2026-04-22)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[DID] Allison.Reibschied
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:Allison.Reibschied@cascadestucson.com
|
||||||
|
|
||||||
|
mail=Allison.Reibschied@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Alyssa.Brooks
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:alyssa.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
mail=alyssa.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Ashley.Jensen
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:ashley.jensen@cascadestucson.com; smtp:ashley.jenson@cascadestucson.com
|
||||||
|
|
||||||
|
mail=ashley.jensen@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] britney.thompson
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:Britney.Thompson@cascadestucson.com
|
||||||
|
|
||||||
|
mail=Britney.Thompson@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Cathy.Kingston
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:cathy.kingston@cascadestucson.com
|
||||||
|
|
||||||
|
mail=cathy.kingston@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Christina.DuPras
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christina.dupras@cascadestucson.com
|
||||||
|
|
||||||
|
mail=christina.dupras@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Christine.Nyanzunda
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christine.nyanzunda@cascadestucson.com
|
||||||
|
|
||||||
|
mail=christine.nyanzunda@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Christopher.Holick
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:christopher.holick@cascadestucson.com
|
||||||
|
|
||||||
|
mail=christopher.holick@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Crystal.Rodriguez
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:crystal.rodriguez@cascadestucson.com; smtp:crystal.suszek@cascadestucson.com
|
||||||
|
|
||||||
|
mail=crystal.rodriguez@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] howard
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:dax.howard@cascadestucson.com; smtp:cara.lespron@cascadestucson.com
|
||||||
|
|
||||||
|
mail=dax.howard@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] JD.Martin
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:jd.martin@cascadestucson.com
|
||||||
|
|
||||||
|
mail=jd.martin@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] John.Trozzi
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:john.trozzi@cascadestucson.com
|
||||||
|
|
||||||
|
mail=john.trozzi@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Julian.Crim
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:julian.crim@cascadestucson.com
|
||||||
|
|
||||||
|
mail=julian.crim@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] karen.rossini
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:karen.rossini@cascadestucson.com
|
||||||
|
|
||||||
|
mail=karen.rossini@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Kyla.QuickTiffany
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:kyla.quicktiffany@cascadestucson.com
|
||||||
|
|
||||||
|
mail=kyla.quicktiffany@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] lauren.hasselman
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lauren.hasselman@cascadestucson.com
|
||||||
|
|
||||||
|
mail=lauren.hasselman@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Lois.Lane
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lois.lane@cascadestucson.com
|
||||||
|
|
||||||
|
mail=lois.lane@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Lupe.Sanchez
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:lupe.sanchez@cascadestucson.com
|
||||||
|
|
||||||
|
mail=lupe.sanchez@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Matt.Brooks
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:matthew.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
mail=matthew.brooks@cascadestucson.com
|
||||||
|
|
||||||
|
[DID] Megan.Hiatt
|
||||||
|
|
||||||
|
before: <empty>
|
||||||
|
|
||||||
|
after: SMTP:megan.hiatt@cascadestucson.com
|
||||||
|
|
||||||
|
mail=megan.hiatt@cascadestucson.com
|
||||||
121
clients/cascades-tucson/reports/2026-04-22-g1-post-verify.md
Normal file
121
clients/cascades-tucson/reports/2026-04-22-g1-post-verify.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# G1 AD Hygiene - Post-Execute Idempotency Verification
|
||||||
|
|
||||||
|
**Command ID:** 2bd999a0-b9a1-4599-a44b-d90f32a332ad
|
||||||
|
**Exit:** 0
|
||||||
|
**Completed:** 2026-04-23T03:33:28.670135Z
|
||||||
|
|
||||||
|
## STDOUT
|
||||||
|
|
||||||
|
```
|
||||||
|
G1 AD Hygiene - 2026-04-22 20:33:27 -07:00
|
||||||
|
|
||||||
|
Host: CS-SERVER
|
||||||
|
|
||||||
|
Mode: DRY-RUN (no changes)
|
||||||
|
|
||||||
|
Backup dir: D:\Backups\g1-hygiene-2026-04-22-203327
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 0. Pre-state backup (always runs)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[OK] Exported users-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported groups-pre.csv
|
||||||
|
|
||||||
|
[OK] Exported ous-pre.csv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[OK] Pre-state saved at D:\Backups\g1-hygiene-2026-04-22-203327
|
||||||
|
|
||||||
|
Rollback commands (if needed after execute):
|
||||||
|
|
||||||
|
- proxyAddresses: Set-ADUser from users-pre.csv column ProxyAddresses
|
||||||
|
|
||||||
|
- OU moves: Move-ADObject back to old DistinguishedName
|
||||||
|
|
||||||
|
- Groups created today: Remove-ADGroup (safe since memberless)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 1. OU=Excluded-From-Sync + move 4 role accounts
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[SKIP] OU=Excluded-From-Sync already exists
|
||||||
|
|
||||||
|
[SKIP] Culinary already in Excluded-From-Sync
|
||||||
|
|
||||||
|
[SKIP] Receptionist already in Excluded-From-Sync
|
||||||
|
|
||||||
|
[SKIP] saleshare already in Excluded-From-Sync
|
||||||
|
|
||||||
|
[SKIP] directoryshare already in Excluded-From-Sync
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
== 2. Populate proxyAddresses (34 users - live data from M365 Graph 2026-04-22)
|
||||||
|
|
||||||
|
============================================================================
|
||||||
|
|
||||||
|
[SKIP] Allison.Reibschied proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Alyssa.Brooks proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Ashley.Jensen proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] britney.thompson proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Cathy.Kingston proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Christina.DuPras proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Christine.Nyanzunda proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Christopher.Holick proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Crystal.Rodriguez proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] howard proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] JD.Martin proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] John.Trozzi proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Julian.Crim proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] karen.rossini proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Kyla.QuickTiffany proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] lauren.hasselman proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Lois.Lane proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Lupe.Sanchez proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Matt.Brooks proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Megan.Hiatt proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Meredith.Kuhn proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Michelle.Shestko proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Ramon.Castaneda proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Ray.Rai proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Richard.Adams proxyAddresses already current
|
||||||
|
|
||||||
|
[SKIP] Sebastian.Leon proxyAddresses already current
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Howard Account Cleanup - 2026-04-22
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Two separate humans share the name "Howard":
|
||||||
|
- **Howard Enos** — MSP tech at Computer Guru. Accesses Cascades tenant.
|
||||||
|
- **Dax Howard** — a person who runs Cascades. Has the `dax.howard@cascadestucson.com` member mailbox.
|
||||||
|
|
||||||
|
Prior documentation (`docs/cloud/m365.md` as it existed) incorrectly mapped the AD `howard` account to Dax Howard's M365 mailbox, conflating the two identities. The G1 hygiene script executed earlier tonight populated the AD `howard` account's `proxyAddresses` / `mail` / `DisplayName` with Dax Howard's attributes — a genuine error that would have caused a duplicate / orphan at Entra Connect sync time.
|
||||||
|
|
||||||
|
## Decision (Howard Enos, 2026-04-22)
|
||||||
|
|
||||||
|
1. **Remove the external guest** `howard@azcomputerguru.com` from Cascades tenant — was redundant; MSP access preserved via `sysadmin@cascadestucson.com`.
|
||||||
|
2. **Remove the AD `howard` account** — orphan MSP-created account (desc "Home Offie" typo, PasswordNeverExpires, unused group memberships). Not in active use.
|
||||||
|
3. **Leave `dax.howard@cascadestucson.com` alone** — Dax Howard's real Cascades mailbox. Keep as-is.
|
||||||
|
|
||||||
|
## Pre-state checks (safety)
|
||||||
|
|
||||||
|
- **Global Admin role membership** (Graph): `sysadmin@cascadestucson.com` (Computer Guru Support) holds the Global Administrator role. Admin path preserved after guest removal.
|
||||||
|
- **Guest's role/group memberships** (Graph): none. Pure sign-in account with no authorization side effects.
|
||||||
|
- **AD `howard`'s group memberships** (AD): only `Domain Users` (default). No custom groups depending on it.
|
||||||
|
|
||||||
|
## Actions executed
|
||||||
|
|
||||||
|
### 1. Deleted M365 guest `howard@azcomputerguru.com`
|
||||||
|
|
||||||
|
- Tier: `user-manager` (Graph write)
|
||||||
|
- Object ID: `db2aee97-9c5d-40ce-8610-b75efc3ca906`
|
||||||
|
- UPN: `howard_azcomputerguru.com#EXT#@NETORGFT4257522.onmicrosoft.com`
|
||||||
|
- HTTP: 204 (success)
|
||||||
|
- Verify: subsequent GET returns 404 Request_ResourceNotFound
|
||||||
|
- **Soft-delete recovery window:** 30 days in Entra (`Restore-MgDirectoryDeletedItem`)
|
||||||
|
|
||||||
|
### 2. Deleted AD user `howard`
|
||||||
|
|
||||||
|
- Ran via GuruRMM on CS-SERVER (agent `6766e973-e703-47c1-be56-76950290f87c`)
|
||||||
|
- Script: `docs/migration/scripts/ad-howard-delete.ps1`
|
||||||
|
- Pre-state exported: `D:\Backups\howard-delete-2026-04-22-205158\howard-pre.xml`
|
||||||
|
- Pre-state captured:
|
||||||
|
- SAM=howard
|
||||||
|
- UPN=howard@cascadestucson.com
|
||||||
|
- Display="Howard Dax" (wrong value from earlier G1 script — was correct to delete)
|
||||||
|
- Description="Home Offie" (typo)
|
||||||
|
- mail=dax.howard@cascadestucson.com (wrong value from G1 — was correct to delete)
|
||||||
|
- proxyAddresses=SMTP:dax.howard@cascadestucson.com, smtp:cara.lespron@cascadestucson.com (wrong — belonged to Dax)
|
||||||
|
- Groups: Domain Users only
|
||||||
|
- Removed via `Remove-ADUser -Identity howard -Confirm:$false`
|
||||||
|
- Verified: `Get-ADUser -Identity howard` returns "Cannot find an object"
|
||||||
|
- **AD Recycle Bin recovery window:** 180 days
|
||||||
|
- **Rollback:** `Restore-ADObject -Identity 2050d21f-7649-4033-b1fd-83cfc286b056`
|
||||||
|
|
||||||
|
### 3. Updated docs/cloud/m365.md
|
||||||
|
|
||||||
|
- Corrected the `howard | dax.howard@` mapping line (root cause of the confusion)
|
||||||
|
- Struck through the `howard@azcomputerguru.com` + `howaed@azcomputerguru.com` external guest entries (both no longer in tenant)
|
||||||
|
|
||||||
|
## Dax Howard — open questions
|
||||||
|
|
||||||
|
Dax Howard has a Cascades M365 member account (`dax.howard@cascadestucson.com`, Business Standard license, alias `cara.lespron@`) but no AD account. As of 2026-04-22, he's:
|
||||||
|
|
||||||
|
- Not on the returned staff CSV from Meredith/John (`reports/cascades-staff-2026-04-22.csv`)
|
||||||
|
- Not on the working account-setup list (`docs/cloud/cascades-staff-working-list-2026-04-22.md`)
|
||||||
|
- Has an active licensed mailbox
|
||||||
|
|
||||||
|
**Questions to ask Meredith when convenient:**
|
||||||
|
1. Who is Dax Howard? (executive / regional / legacy?)
|
||||||
|
2. Is his mailbox actively used?
|
||||||
|
3. Should he have an AD account created for hybrid identity?
|
||||||
|
4. Is the `cara.lespron@` alias still needed, or can it be removed?
|
||||||
|
|
||||||
|
Not blocking Wave 0.5 — he's cloud-only and stays that way unless an AD counterpart is added.
|
||||||
|
|
||||||
|
## Impact on Entra Connect sync plan
|
||||||
|
|
||||||
|
- AD now has 41 enabled users (was 42, howard removed).
|
||||||
|
- The earlier G1 hygiene run's wrong `proxyAddresses` / `mail` / `DisplayName` attributes on AD `howard` are gone with the account — no lingering mismatch to clean up.
|
||||||
|
- Dax Howard stays cloud-only; his M365 account will show as "Cloud Only" in Entra admin post-sync, separate from the AD-synced population. No conflict.
|
||||||
|
- No need to re-run the G1 hygiene script — the deletion is idempotent.
|
||||||
|
|
||||||
|
## Session log for future reference
|
||||||
|
|
||||||
|
Added this cleanup as a concrete lesson: **always verify identity mapping from live Graph data, not from prior doc state**, before bulk AD attribute changes. The G1 script's proxyAddresses mapping came from `docs/cloud/m365.md` which had the wrong assumption baked in. The error was caught immediately by Howard spotting the dual-identity before it reached a sync attempt.
|
||||||
Reference in New Issue
Block a user